diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | INSTALL | 10 | ||||
-rw-r--r-- | Makefile.am | 1030 | ||||
-rw-r--r-- | NEWS | 18 | ||||
-rw-r--r-- | configure.ac | 165 | ||||
-rw-r--r-- | doc/user.xml | 131 | ||||
-rw-r--r-- | m4/ax_append_link_flags.m4 | 61 | ||||
-rw-r--r-- | m4/ax_check_link_flag.m4 | 71 | ||||
-rw-r--r-- | m4/ax_cxx_compile_stdcxx_0x.m4 | 107 | ||||
-rw-r--r-- | m4/faad.m4 | 128 | ||||
-rw-r--r-- | src/AllCommands.cxx | 387 | ||||
-rw-r--r-- | src/AllCommands.hxx (renamed from src/db_internal.h) | 21 | ||||
-rw-r--r-- | src/ArchiveFile.hxx | 56 | ||||
-rw-r--r-- | src/ArchiveList.cxx (renamed from src/archive_list.c) | 12 | ||||
-rw-r--r-- | src/ArchiveList.hxx (renamed from src/archive_list.h) | 6 | ||||
-rw-r--r-- | src/ArchiveLookup.cxx (renamed from src/archive_api.c) | 4 | ||||
-rw-r--r-- | src/ArchiveLookup.hxx (renamed from src/archive_api.h) | 12 | ||||
-rw-r--r-- | src/ArchivePlugin.cxx | 43 | ||||
-rw-r--r-- | src/ArchivePlugin.hxx (renamed from src/archive_plugin.h) | 64 | ||||
-rw-r--r-- | src/ArchiveVisitor.hxx | 28 | ||||
-rw-r--r-- | src/AudioCompress/compress.h | 9 | ||||
-rw-r--r-- | src/AudioConfig.cxx (renamed from src/audio_config.c) | 19 | ||||
-rw-r--r-- | src/AudioConfig.hxx (renamed from src/audio_config.h) | 8 | ||||
-rw-r--r-- | src/AudioParser.cxx (renamed from src/audio_parser.c) | 2 | ||||
-rw-r--r-- | src/AudioParser.hxx (renamed from src/audio_parser.h) | 10 | ||||
-rw-r--r-- | src/Client.cxx (renamed from src/client.c) | 15 | ||||
-rw-r--r-- | src/Client.hxx (renamed from src/client.h) | 40 | ||||
-rw-r--r-- | src/ClientEvent.cxx (renamed from src/song_print.h) | 25 | ||||
-rw-r--r-- | src/ClientExpire.cxx (renamed from src/db_lock.c) | 32 | ||||
-rw-r--r-- | src/ClientFile.cxx (renamed from src/client_file.c) | 12 | ||||
-rw-r--r-- | src/ClientFile.hxx (renamed from src/client_file.h) | 13 | ||||
-rw-r--r-- | src/ClientGlobal.cxx (renamed from src/client_global.c) | 31 | ||||
-rw-r--r-- | src/ClientIdle.cxx | 75 | ||||
-rw-r--r-- | src/ClientInternal.hxx | 133 | ||||
-rw-r--r-- | src/ClientList.cxx | 56 | ||||
-rw-r--r-- | src/ClientList.hxx | 61 | ||||
-rw-r--r-- | src/ClientMessage.cxx | 41 | ||||
-rw-r--r-- | src/ClientMessage.hxx (renamed from src/exclude.h) | 45 | ||||
-rw-r--r-- | src/ClientNew.cxx (renamed from src/client_new.c) | 105 | ||||
-rw-r--r-- | src/ClientProcess.cxx (renamed from src/client_process.c) | 53 | ||||
-rw-r--r-- | src/ClientRead.cxx | 66 | ||||
-rw-r--r-- | src/ClientSubscribe.cxx | 92 | ||||
-rw-r--r-- | src/ClientSubscribe.hxx (renamed from src/client_subscribe.h) | 25 | ||||
-rw-r--r-- | src/ClientWrite.cxx | 87 | ||||
-rw-r--r-- | src/CommandError.cxx | 134 | ||||
-rw-r--r-- | src/CommandError.hxx | 38 | ||||
-rw-r--r-- | src/CommandLine.cxx (renamed from src/cmdline.c) | 115 | ||||
-rw-r--r-- | src/CommandLine.hxx (renamed from src/cmdline.h) | 8 | ||||
-rw-r--r-- | src/CommandListBuilder.cxx | 43 | ||||
-rw-r--r-- | src/CommandListBuilder.hxx | 109 | ||||
-rw-r--r-- | src/ConfigData.cxx | 142 | ||||
-rw-r--r-- | src/ConfigData.hxx | 142 | ||||
-rw-r--r-- | src/ConfigFile.cxx | 285 | ||||
-rw-r--r-- | src/ConfigFile.hxx | 31 | ||||
-rw-r--r-- | src/ConfigGlobal.cxx | 176 | ||||
-rw-r--r-- | src/ConfigGlobal.hxx | 109 | ||||
-rw-r--r-- | src/ConfigOption.hxx | 90 | ||||
-rw-r--r-- | src/ConfigParser.cxx | 43 | ||||
-rw-r--r-- | src/ConfigParser.hxx | 26 | ||||
-rw-r--r-- | src/ConfigQuark.hxx | 36 | ||||
-rw-r--r-- | src/ConfigTemplates.cxx | 96 | ||||
-rw-r--r-- | src/ConfigTemplates.hxx | 33 | ||||
-rw-r--r-- | src/CrossFade.cxx (renamed from src/crossfade.c) | 16 | ||||
-rw-r--r-- | src/CrossFade.hxx (renamed from src/crossfade.h) | 6 | ||||
-rw-r--r-- | src/DatabaseCommands.cxx | 220 | ||||
-rw-r--r-- | src/DatabaseCommands.hxx | 57 | ||||
-rw-r--r-- | src/DatabaseGlue.cxx (renamed from src/database.c) | 129 | ||||
-rw-r--r-- | src/DatabaseGlue.hxx | 59 | ||||
-rw-r--r-- | src/DatabaseHelpers.cxx | 134 | ||||
-rw-r--r-- | src/DatabaseHelpers.hxx | 41 | ||||
-rw-r--r-- | src/DatabaseLock.cxx | 28 | ||||
-rw-r--r-- | src/DatabaseLock.hxx (renamed from src/db_lock.h) | 29 | ||||
-rw-r--r-- | src/DatabasePlaylist.cxx | 50 | ||||
-rw-r--r-- | src/DatabasePlaylist.hxx (renamed from src/db_save.h) | 19 | ||||
-rw-r--r-- | src/DatabasePlugin.hxx | 148 | ||||
-rw-r--r-- | src/DatabasePrint.cxx | 231 | ||||
-rw-r--r-- | src/DatabasePrint.hxx (renamed from src/db_print.h) | 42 | ||||
-rw-r--r-- | src/DatabaseQueue.cxx | 54 | ||||
-rw-r--r-- | src/DatabaseQueue.hxx (renamed from src/inotify_queue.h) | 16 | ||||
-rw-r--r-- | src/DatabaseRegistry.cxx | 43 | ||||
-rw-r--r-- | src/DatabaseRegistry.hxx | 37 | ||||
-rw-r--r-- | src/DatabaseSave.cxx (renamed from src/db_save.c) | 44 | ||||
-rw-r--r-- | src/DatabaseSave.hxx | 36 | ||||
-rw-r--r-- | src/DatabaseSelection.cxx (renamed from src/decoder_print.h) | 15 | ||||
-rw-r--r-- | src/DatabaseSelection.hxx (renamed from src/db_selection.h) | 32 | ||||
-rw-r--r-- | src/DatabaseSimple.hxx (renamed from src/database.h) | 65 | ||||
-rw-r--r-- | src/DatabaseVisitor.hxx (renamed from src/input_internal.h) | 29 | ||||
-rw-r--r-- | src/DecoderAPI.cxx (renamed from src/decoder_api.c) | 90 | ||||
-rw-r--r-- | src/DecoderControl.cxx | 193 | ||||
-rw-r--r-- | src/DecoderControl.hxx | 297 | ||||
-rw-r--r-- | src/DecoderInternal.cxx (renamed from src/decoder_internal.c) | 39 | ||||
-rw-r--r-- | src/DecoderInternal.hxx (renamed from src/decoder_internal.h) | 24 | ||||
-rw-r--r-- | src/DecoderList.cxx (renamed from src/decoder_list.c) | 26 | ||||
-rw-r--r-- | src/DecoderList.hxx (renamed from src/decoder_list.h) | 8 | ||||
-rw-r--r-- | src/DecoderPrint.cxx (renamed from src/decoder_print.c) | 12 | ||||
-rw-r--r-- | src/DecoderPrint.hxx | 28 | ||||
-rw-r--r-- | src/DecoderThread.cxx (renamed from src/decoder_thread.c) | 144 | ||||
-rw-r--r-- | src/DecoderThread.hxx (renamed from src/decoder_thread.h) | 6 | ||||
-rw-r--r-- | src/DespotifyUtils.cxx (renamed from src/despotify_utils.c) | 51 | ||||
-rw-r--r-- | src/DespotifyUtils.hxx (renamed from src/despotify_utils.h) | 0 | ||||
-rw-r--r-- | src/Directory.cxx | 335 | ||||
-rw-r--r-- | src/Directory.hxx | 264 | ||||
-rw-r--r-- | src/DirectorySave.cxx (renamed from src/directory_save.c) | 77 | ||||
-rw-r--r-- | src/DirectorySave.hxx (renamed from src/directory_save.h) | 17 | ||||
-rw-r--r-- | src/ExcludeList.cxx (renamed from src/exclude.c) | 50 | ||||
-rw-r--r-- | src/ExcludeList.hxx | 80 | ||||
-rw-r--r-- | src/FilterConfig.cxx (renamed from src/filter_config.c) | 32 | ||||
-rw-r--r-- | src/FilterConfig.hxx (renamed from src/filter_config.h) | 15 | ||||
-rw-r--r-- | src/FilterInternal.hxx | 71 | ||||
-rw-r--r-- | src/FilterPlugin.cxx (renamed from src/filter_plugin.c) | 67 | ||||
-rw-r--r-- | src/FilterPlugin.hxx | 70 | ||||
-rw-r--r-- | src/FilterRegistry.cxx (renamed from src/filter_registry.c) | 6 | ||||
-rw-r--r-- | src/FilterRegistry.hxx (renamed from src/filter_registry.h) | 6 | ||||
-rw-r--r-- | src/GlobalEvents.cxx | 114 | ||||
-rw-r--r-- | src/GlobalEvents.hxx | 71 | ||||
-rw-r--r-- | src/IOThread.cxx (renamed from src/io_thread.c) | 104 | ||||
-rw-r--r-- | src/IOThread.hxx (renamed from src/io_thread.h) | 31 | ||||
-rw-r--r-- | src/IcyMetaDataParser.cxx (renamed from src/icy_metadata.c) | 93 | ||||
-rw-r--r-- | src/IcyMetaDataParser.hxx | 83 | ||||
-rw-r--r-- | src/IcyMetaDataServer.cxx (renamed from src/icy_server.c) | 29 | ||||
-rw-r--r-- | src/IcyMetaDataServer.hxx (renamed from src/icy_server.h) | 13 | ||||
-rw-r--r-- | src/IdTable.hxx | 91 | ||||
-rw-r--r-- | src/Idle.cxx (renamed from src/idle.c) | 45 | ||||
-rw-r--r-- | src/Idle.hxx (renamed from src/idle.h) | 18 | ||||
-rw-r--r-- | src/InotifyQueue.cxx (renamed from src/inotify_queue.c) | 74 | ||||
-rw-r--r-- | src/InotifyQueue.hxx (renamed from src/filter_internal.h) | 33 | ||||
-rw-r--r-- | src/InotifySource.cxx (renamed from src/inotify_source.c) | 104 | ||||
-rw-r--r-- | src/InotifySource.hxx | 72 | ||||
-rw-r--r-- | src/InotifyUpdate.cxx (renamed from src/inotify_update.c) | 199 | ||||
-rw-r--r-- | src/InotifyUpdate.hxx (renamed from src/inotify_update.h) | 6 | ||||
-rw-r--r-- | src/InputInit.cxx (renamed from src/input_init.c) | 8 | ||||
-rw-r--r-- | src/InputInit.hxx (renamed from src/input_init.h) | 11 | ||||
-rw-r--r-- | src/InputInternal.cxx | 39 | ||||
-rw-r--r-- | src/InputInternal.hxx | 33 | ||||
-rw-r--r-- | src/InputPlugin.hxx (renamed from src/input_plugin.h) | 9 | ||||
-rw-r--r-- | src/InputRegistry.cxx (renamed from src/input_registry.c) | 20 | ||||
-rw-r--r-- | src/InputRegistry.hxx (renamed from src/input_registry.h) | 8 | ||||
-rw-r--r-- | src/InputStream.cxx (renamed from src/input_stream.c) | 164 | ||||
-rw-r--r-- | src/InputStream.hxx | 114 | ||||
-rw-r--r-- | src/Listen.cxx (renamed from src/listen.c) | 55 | ||||
-rw-r--r-- | src/Listen.hxx (renamed from src/listen.h) | 8 | ||||
-rw-r--r-- | src/Log.cxx (renamed from src/log.c) | 8 | ||||
-rw-r--r-- | src/Log.hxx (renamed from src/log.h) | 6 | ||||
-rw-r--r-- | src/Main.cxx (renamed from src/main.c) | 225 | ||||
-rw-r--r-- | src/Main.hxx (renamed from src/main.h) | 16 | ||||
-rw-r--r-- | src/Mapper.cxx | 302 | ||||
-rw-r--r-- | src/Mapper.hxx (renamed from src/mapper.h) | 68 | ||||
-rw-r--r-- | src/MessageCommands.cxx | 136 | ||||
-rw-r--r-- | src/MessageCommands.hxx | 42 | ||||
-rw-r--r-- | src/MixerAll.cxx (renamed from src/mixer_all.c) | 18 | ||||
-rw-r--r-- | src/MixerAll.hxx (renamed from src/mixer_all.h) | 8 | ||||
-rw-r--r-- | src/MixerControl.cxx (renamed from src/mixer_control.c) | 6 | ||||
-rw-r--r-- | src/MixerControl.hxx (renamed from src/mixer_control.h) | 18 | ||||
-rw-r--r-- | src/MixerInternal.cxx (renamed from src/mixer_api.c) | 4 | ||||
-rw-r--r-- | src/MixerInternal.hxx (renamed from src/mixer_api.h) | 10 | ||||
-rw-r--r-- | src/MixerList.hxx (renamed from src/mixer_list.h) | 6 | ||||
-rw-r--r-- | src/MixerPlugin.hxx (renamed from src/mixer_plugin.h) | 10 | ||||
-rw-r--r-- | src/MixerType.cxx (renamed from src/mixer_type.c) | 4 | ||||
-rw-r--r-- | src/MixerType.hxx (renamed from src/mixer_type.h) | 6 | ||||
-rw-r--r-- | src/MusicBuffer.cxx | 79 | ||||
-rw-r--r-- | src/MusicBuffer.hxx (renamed from src/buffer.h) | 6 | ||||
-rw-r--r-- | src/MusicChunk.cxx | 84 | ||||
-rw-r--r-- | src/MusicChunk.hxx (renamed from src/chunk.h) | 104 | ||||
-rw-r--r-- | src/MusicPipe.cxx (renamed from src/pipe.c) | 77 | ||||
-rw-r--r-- | src/MusicPipe.hxx (renamed from src/pipe.h) | 11 | ||||
-rw-r--r-- | src/OtherCommands.cxx | 307 | ||||
-rw-r--r-- | src/OtherCommands.hxx | 69 | ||||
-rw-r--r-- | src/OutputAll.cxx (renamed from src/output_all.c) | 66 | ||||
-rw-r--r-- | src/OutputAll.hxx (renamed from src/output_all.h) | 11 | ||||
-rw-r--r-- | src/OutputCommand.cxx (renamed from src/output_command.c) | 17 | ||||
-rw-r--r-- | src/OutputCommand.hxx (renamed from src/output_command.h) | 8 | ||||
-rw-r--r-- | src/OutputCommands.cxx | 74 | ||||
-rw-r--r-- | src/OutputCommands.hxx | 36 | ||||
-rw-r--r-- | src/OutputControl.cxx (renamed from src/output_control.c) | 29 | ||||
-rw-r--r-- | src/OutputControl.hxx (renamed from src/output_control.h) | 17 | ||||
-rw-r--r-- | src/OutputError.hxx | 35 | ||||
-rw-r--r-- | src/OutputFinish.cxx (renamed from src/output_finish.c) | 20 | ||||
-rw-r--r-- | src/OutputInit.cxx (renamed from src/output_init.c) | 70 | ||||
-rw-r--r-- | src/OutputList.cxx (renamed from src/output_list.c) | 18 | ||||
-rw-r--r-- | src/OutputList.hxx (renamed from src/output_list.h) | 6 | ||||
-rw-r--r-- | src/OutputPlugin.cxx (renamed from src/output_plugin.c) | 6 | ||||
-rw-r--r-- | src/OutputPrint.cxx (renamed from src/output_print.c) | 12 | ||||
-rw-r--r-- | src/OutputPrint.hxx (renamed from src/output_print.h) | 10 | ||||
-rw-r--r-- | src/OutputState.cxx (renamed from src/output_state.c) | 6 | ||||
-rw-r--r-- | src/OutputState.hxx (renamed from src/output_state.h) | 7 | ||||
-rw-r--r-- | src/OutputThread.cxx (renamed from src/output_thread.c) | 96 | ||||
-rw-r--r-- | src/OutputThread.hxx (renamed from src/output_thread.h) | 6 | ||||
-rw-r--r-- | src/Page.cxx (renamed from src/client_list.c) | 63 | ||||
-rw-r--r-- | src/Page.hxx (renamed from src/page.h) | 92 | ||||
-rw-r--r-- | src/Partition.hxx | 165 | ||||
-rw-r--r-- | src/PcmChannels.cxx (renamed from src/pcm_channels.c) | 116 | ||||
-rw-r--r-- | src/PcmChannels.hxx (renamed from src/pcm_channels.h) | 23 | ||||
-rw-r--r-- | src/PcmConvert.cxx | 326 | ||||
-rw-r--r-- | src/PcmConvert.hxx | 115 | ||||
-rw-r--r-- | src/PcmDither.cxx | 89 | ||||
-rw-r--r-- | src/PcmDither.hxx (renamed from src/pcm_dither.h) | 33 | ||||
-rw-r--r-- | src/PcmFormat.cxx (renamed from src/pcm_format.c) | 239 | ||||
-rw-r--r-- | src/PcmFormat.hxx (renamed from src/pcm_format.h) | 10 | ||||
-rw-r--r-- | src/PcmMix.cxx | 211 | ||||
-rw-r--r-- | src/PcmMix.hxx (renamed from src/pcm_mix.h) | 10 | ||||
-rw-r--r-- | src/PcmPrng.hxx (renamed from src/pcm_prng.h) | 6 | ||||
-rw-r--r-- | src/PcmUtils.hxx | 66 | ||||
-rw-r--r-- | src/PcmVolume.cxx (renamed from src/pcm_volume.c) | 26 | ||||
-rw-r--r-- | src/PcmVolume.hxx (renamed from src/pcm_volume.h) | 9 | ||||
-rw-r--r-- | src/Permission.cxx (renamed from src/permission.c) | 34 | ||||
-rw-r--r-- | src/Permission.hxx (renamed from src/permission.h) | 8 | ||||
-rw-r--r-- | src/PlayerCommands.cxx | 392 | ||||
-rw-r--r-- | src/PlayerCommands.hxx | 90 | ||||
-rw-r--r-- | src/PlayerControl.cxx | 323 | ||||
-rw-r--r-- | src/PlayerControl.hxx | 325 | ||||
-rw-r--r-- | src/PlayerThread.cxx (renamed from src/player_thread.c) | 318 | ||||
-rw-r--r-- | src/PlayerThread.hxx (renamed from src/player_thread.h) | 6 | ||||
-rw-r--r-- | src/Playlist.cxx | 344 | ||||
-rw-r--r-- | src/Playlist.hxx | 256 | ||||
-rw-r--r-- | src/PlaylistAny.cxx (renamed from src/playlist_any.c) | 17 | ||||
-rw-r--r-- | src/PlaylistAny.hxx (renamed from src/playlist_any.h) | 11 | ||||
-rw-r--r-- | src/PlaylistCommands.cxx | 224 | ||||
-rw-r--r-- | src/PlaylistCommands.hxx | 60 | ||||
-rw-r--r-- | src/PlaylistControl.cxx | 264 | ||||
-rw-r--r-- | src/PlaylistDatabase.cxx (renamed from src/playlist_database.c) | 31 | ||||
-rw-r--r-- | src/PlaylistDatabase.hxx (renamed from src/playlist_database.h) | 18 | ||||
-rw-r--r-- | src/PlaylistEdit.cxx | 423 | ||||
-rw-r--r-- | src/PlaylistFile.cxx (renamed from src/stored_playlist.c) | 328 | ||||
-rw-r--r-- | src/PlaylistFile.hxx (renamed from src/stored_playlist.h) | 39 | ||||
-rw-r--r-- | src/PlaylistGlobal.cxx (renamed from src/playlist_global.c) | 31 | ||||
-rw-r--r-- | src/PlaylistGlobal.hxx | 26 | ||||
-rw-r--r-- | src/PlaylistInfo.hxx | 63 | ||||
-rw-r--r-- | src/PlaylistMapper.cxx (renamed from src/playlist_mapper.c) | 40 | ||||
-rw-r--r-- | src/PlaylistMapper.hxx (renamed from src/playlist_mapper.h) | 11 | ||||
-rw-r--r-- | src/PlaylistPlugin.hxx (renamed from src/playlist_plugin.h) | 14 | ||||
-rw-r--r-- | src/PlaylistPrint.cxx (renamed from src/playlist_print.c) | 132 | ||||
-rw-r--r-- | src/PlaylistPrint.hxx (renamed from src/playlist_print.h) | 41 | ||||
-rw-r--r-- | src/PlaylistQueue.cxx (renamed from src/playlist_queue.c) | 37 | ||||
-rw-r--r-- | src/PlaylistQueue.hxx (renamed from src/playlist_queue.h) | 8 | ||||
-rw-r--r-- | src/PlaylistRegistry.cxx (renamed from src/playlist_list.c) | 57 | ||||
-rw-r--r-- | src/PlaylistRegistry.hxx (renamed from src/playlist_list.h) | 15 | ||||
-rw-r--r-- | src/PlaylistSave.cxx (renamed from src/playlist_save.c) | 102 | ||||
-rw-r--r-- | src/PlaylistSave.hxx (renamed from src/playlist_save.h) | 3 | ||||
-rw-r--r-- | src/PlaylistSong.cxx (renamed from src/playlist_song.c) | 50 | ||||
-rw-r--r-- | src/PlaylistSong.hxx (renamed from src/playlist_song.h) | 8 | ||||
-rw-r--r-- | src/PlaylistState.cxx (renamed from src/playlist_state.c) | 105 | ||||
-rw-r--r-- | src/PlaylistState.hxx (renamed from src/playlist_state.h) | 11 | ||||
-rw-r--r-- | src/PlaylistVector.cxx | 68 | ||||
-rw-r--r-- | src/PlaylistVector.hxx | 56 | ||||
-rw-r--r-- | src/Queue.cxx | 499 | ||||
-rw-r--r-- | src/Queue.hxx | 368 | ||||
-rw-r--r-- | src/QueueCommands.cxx | 375 | ||||
-rw-r--r-- | src/QueueCommands.hxx | 84 | ||||
-rw-r--r-- | src/QueuePrint.cxx | 107 | ||||
-rw-r--r-- | src/QueuePrint.hxx (renamed from src/queue_print.h) | 26 | ||||
-rw-r--r-- | src/QueueSave.cxx (renamed from src/queue_save.c) | 65 | ||||
-rw-r--r-- | src/QueueSave.hxx (renamed from src/queue_save.h) | 11 | ||||
-rw-r--r-- | src/ReplayGainConfig.cxx (renamed from src/replay_gain_config.c) | 21 | ||||
-rw-r--r-- | src/ReplayGainInfo.cxx (renamed from src/replay_gain_info.c) | 2 | ||||
-rw-r--r-- | src/SignalHandlers.cxx (renamed from src/sig_handlers.c) | 15 | ||||
-rw-r--r-- | src/SignalHandlers.hxx | 25 | ||||
-rw-r--r-- | src/SocketError.hxx | 156 | ||||
-rw-r--r-- | src/SocketUtil.cxx (renamed from src/socket_util.c) | 31 | ||||
-rw-r--r-- | src/SocketUtil.hxx (renamed from src/socket_util.h) | 10 | ||||
-rw-r--r-- | src/Song.cxx (renamed from src/song.c) | 99 | ||||
-rw-r--r-- | src/SongFilter.cxx | 166 | ||||
-rw-r--r-- | src/SongFilter.hxx | 107 | ||||
-rw-r--r-- | src/SongPointer.hxx (renamed from src/notify.h) | 60 | ||||
-rw-r--r-- | src/SongPrint.cxx (renamed from src/song_print.c) | 54 | ||||
-rw-r--r-- | src/SongPrint.hxx | 32 | ||||
-rw-r--r-- | src/SongSave.cxx (renamed from src/song_save.c) | 19 | ||||
-rw-r--r-- | src/SongSave.hxx (renamed from src/song_save.h) | 15 | ||||
-rw-r--r-- | src/SongSticker.cxx (renamed from src/song_sticker.c) | 47 | ||||
-rw-r--r-- | src/SongSticker.hxx (renamed from src/song_sticker.h) | 17 | ||||
-rw-r--r-- | src/SongUpdate.cxx (renamed from src/song_update.c) | 66 | ||||
-rw-r--r-- | src/StateFile.cxx | 124 | ||||
-rw-r--r-- | src/StateFile.hxx (renamed from src/state_file.h) | 41 | ||||
-rw-r--r-- | src/Stats.cxx | 89 | ||||
-rw-r--r-- | src/StickerCommands.cxx | 176 | ||||
-rw-r--r-- | src/StickerCommands.hxx | 30 | ||||
-rw-r--r-- | src/StickerDatabase.cxx (renamed from src/sticker.c) | 95 | ||||
-rw-r--r-- | src/StickerDatabase.hxx (renamed from src/sticker.h) | 18 | ||||
-rw-r--r-- | src/StickerPrint.cxx (renamed from src/sticker_print.c) | 16 | ||||
-rw-r--r-- | src/StickerPrint.hxx (renamed from src/sticker_print.h) | 13 | ||||
-rw-r--r-- | src/Tag.cxx (renamed from src/tag.c) | 135 | ||||
-rw-r--r-- | src/TagFile.cxx (renamed from src/tag_file.c) | 24 | ||||
-rw-r--r-- | src/TagFile.hxx (renamed from src/tag_file.h) | 6 | ||||
-rw-r--r-- | src/TagInternal.hxx (renamed from src/tag_internal.h) | 8 | ||||
-rw-r--r-- | src/TagNames.c | 44 | ||||
-rw-r--r-- | src/TagPool.cxx (renamed from src/tag_pool.c) | 32 | ||||
-rw-r--r-- | src/TagPool.hxx (renamed from src/tag_pool.h) | 15 | ||||
-rw-r--r-- | src/TagPrint.cxx (renamed from src/tag_print.c) | 12 | ||||
-rw-r--r-- | src/TagPrint.hxx (renamed from src/tag_print.h) | 12 | ||||
-rw-r--r-- | src/TagSave.cxx (renamed from src/tag_save.c) | 6 | ||||
-rw-r--r-- | src/TagSave.hxx (renamed from src/tag_save.h) | 6 | ||||
-rw-r--r-- | src/TextFile.cxx (renamed from src/text_file.c) | 15 | ||||
-rw-r--r-- | src/TextFile.hxx | 67 | ||||
-rw-r--r-- | src/TimePrint.cxx (renamed from src/glib_socket.h) | 33 | ||||
-rw-r--r-- | src/TimePrint.hxx | 33 | ||||
-rw-r--r-- | src/UpdateArchive.cxx (renamed from src/update_archive.c) | 75 | ||||
-rw-r--r-- | src/UpdateArchive.hxx (renamed from src/update_archive.h) | 22 | ||||
-rw-r--r-- | src/UpdateContainer.cxx (renamed from src/update_container.c) | 50 | ||||
-rw-r--r-- | src/UpdateContainer.hxx (renamed from src/update_container.h) | 11 | ||||
-rw-r--r-- | src/UpdateDatabase.cxx (renamed from src/update_db.c) | 30 | ||||
-rw-r--r-- | src/UpdateDatabase.hxx (renamed from src/update_db.h) | 16 | ||||
-rw-r--r-- | src/UpdateGlue.cxx (renamed from src/update.c) | 39 | ||||
-rw-r--r-- | src/UpdateGlue.hxx (renamed from src/update.h) | 8 | ||||
-rw-r--r-- | src/UpdateIO.cxx | 113 | ||||
-rw-r--r-- | src/UpdateIO.hxx (renamed from src/update_io.h) | 19 | ||||
-rw-r--r-- | src/UpdateInternal.hxx (renamed from src/update_internal.h) | 2 | ||||
-rw-r--r-- | src/UpdateQueue.cxx (renamed from src/update_queue.c) | 4 | ||||
-rw-r--r-- | src/UpdateQueue.hxx (renamed from src/update_queue.h) | 8 | ||||
-rw-r--r-- | src/UpdateRemove.cxx (renamed from src/update_remove.c) | 48 | ||||
-rw-r--r-- | src/UpdateRemove.hxx (renamed from src/update_remove.h) | 9 | ||||
-rw-r--r-- | src/UpdateSong.cxx (renamed from src/update_song.c) | 39 | ||||
-rw-r--r-- | src/UpdateSong.hxx (renamed from src/update_song.h) | 11 | ||||
-rw-r--r-- | src/UpdateWalk.cxx (renamed from src/update_walk.c) | 169 | ||||
-rw-r--r-- | src/UpdateWalk.hxx (renamed from src/update_walk.h) | 8 | ||||
-rw-r--r-- | src/Volume.cxx (renamed from src/volume.c) | 21 | ||||
-rw-r--r-- | src/Volume.hxx (renamed from src/volume.h) | 7 | ||||
-rw-r--r-- | src/Win32Main.cxx (renamed from src/main_win32.c) | 35 | ||||
-rw-r--r-- | src/ZeroconfAvahi.cxx (renamed from src/zeroconf-avahi.c) | 27 | ||||
-rw-r--r-- | src/ZeroconfAvahi.hxx | 31 | ||||
-rw-r--r-- | src/ZeroconfBonjour.cxx (renamed from src/zeroconf-bonjour.c) | 69 | ||||
-rw-r--r-- | src/ZeroconfBonjour.hxx | 31 | ||||
-rw-r--r-- | src/ZeroconfGlue.cxx (renamed from src/zeroconf.c) | 22 | ||||
-rw-r--r-- | src/ZeroconfGlue.hxx (renamed from src/zeroconf.h) | 24 | ||||
-rw-r--r-- | src/ZeroconfInternal.hxx (renamed from src/zeroconf-internal.h) | 8 | ||||
-rw-r--r-- | src/archive/Bzip2ArchivePlugin.cxx | 298 | ||||
-rw-r--r-- | src/archive/Bzip2ArchivePlugin.hxx (renamed from src/archive/bz2_archive_plugin.h) | 6 | ||||
-rw-r--r-- | src/archive/Iso9660ArchivePlugin.cxx | 266 | ||||
-rw-r--r-- | src/archive/Iso9660ArchivePlugin.hxx (renamed from src/archive/iso9660_archive_plugin.h) | 6 | ||||
-rw-r--r-- | src/archive/ZzipArchivePlugin.cxx | 230 | ||||
-rw-r--r-- | src/archive/ZzipArchivePlugin.hxx (renamed from src/archive/zzip_archive_plugin.h) | 6 | ||||
-rw-r--r-- | src/archive/bz2_archive_plugin.c | 314 | ||||
-rw-r--r-- | src/archive/iso9660_archive_plugin.c | 290 | ||||
-rw-r--r-- | src/archive/zzip_archive_plugin.c | 246 | ||||
-rw-r--r-- | src/archive_internal.h | 34 | ||||
-rw-r--r-- | src/archive_plugin.c | 94 | ||||
-rw-r--r-- | src/audio_check.h | 10 | ||||
-rw-r--r-- | src/audio_format.h | 27 | ||||
-rw-r--r-- | src/buffer.c | 137 | ||||
-rw-r--r-- | src/chunk.c | 102 | ||||
-rw-r--r-- | src/client_event.c | 108 | ||||
-rw-r--r-- | src/client_expire.c | 90 | ||||
-rw-r--r-- | src/client_idle.c | 96 | ||||
-rw-r--r-- | src/client_idle.h | 45 | ||||
-rw-r--r-- | src/client_internal.h | 175 | ||||
-rw-r--r-- | src/client_message.c | 96 | ||||
-rw-r--r-- | src/client_message.h | 72 | ||||
-rw-r--r-- | src/client_read.c | 113 | ||||
-rw-r--r-- | src/client_subscribe.c | 123 | ||||
-rw-r--r-- | src/client_write.c | 284 | ||||
-rw-r--r-- | src/clock.h | 6 | ||||
-rw-r--r-- | src/command.c | 2298 | ||||
-rw-r--r-- | src/command.h | 49 | ||||
-rw-r--r-- | src/conf.c | 666 | ||||
-rw-r--r-- | src/conf.h | 202 | ||||
-rw-r--r-- | src/cue/cue_parser.c | 2 | ||||
-rw-r--r-- | src/db/ProxyDatabasePlugin.cxx | 475 | ||||
-rw-r--r-- | src/db/ProxyDatabasePlugin.hxx | 27 | ||||
-rw-r--r-- | src/db/SimpleDatabasePlugin.cxx | 349 | ||||
-rw-r--r-- | src/db/SimpleDatabasePlugin.hxx | 102 | ||||
-rw-r--r-- | src/db/simple_db_plugin.c | 357 | ||||
-rw-r--r-- | src/dbUtils.c | 209 | ||||
-rw-r--r-- | src/dbUtils.h | 56 | ||||
-rw-r--r-- | src/db_plugin.h | 156 | ||||
-rw-r--r-- | src/db_print.c | 393 | ||||
-rw-r--r-- | src/db_visitor.h | 54 | ||||
-rw-r--r-- | src/decoder/AdPlugDecoderPlugin.cxx | 148 | ||||
-rw-r--r-- | src/decoder/AdPlugDecoderPlugin.h | 25 | ||||
-rw-r--r-- | src/decoder/FLACCommon.cxx (renamed from src/decoder/_flac_common.c) | 67 | ||||
-rw-r--r-- | src/decoder/FLACCommon.hxx (renamed from src/decoder/_flac_common.h) | 30 | ||||
-rw-r--r-- | src/decoder/FLACDecoderPlugin.cxx (renamed from src/decoder/flac_decoder_plugin.c) | 291 | ||||
-rw-r--r-- | src/decoder/FLACDecoderPlugin.h | 26 | ||||
-rw-r--r-- | src/decoder/FLACIOHandle.cxx | 114 | ||||
-rw-r--r-- | src/decoder/FLACIOHandle.hxx | 45 | ||||
-rw-r--r-- | src/decoder/FLACInput.cxx | 149 | ||||
-rw-r--r-- | src/decoder/FLACInput.hxx | 72 | ||||
-rw-r--r-- | src/decoder/FLACMetaData.cxx (renamed from src/decoder/flac_metadata.c) | 136 | ||||
-rw-r--r-- | src/decoder/FLACMetaData.hxx | 140 | ||||
-rw-r--r-- | src/decoder/FLAC_PCM.cxx (renamed from src/decoder/flac_pcm.c) | 4 | ||||
-rw-r--r-- | src/decoder/FLAC_PCM.hxx (renamed from src/decoder/flac_pcm.h) | 6 | ||||
-rw-r--r-- | src/decoder/FfmpegDecoderPlugin.cxx (renamed from src/decoder/ffmpeg_decoder_plugin.c) | 122 | ||||
-rw-r--r-- | src/decoder/FfmpegDecoderPlugin.hxx | 25 | ||||
-rw-r--r-- | src/decoder/FfmpegMetaData.cxx (renamed from src/decoder/ffmpeg_metadata.c) | 18 | ||||
-rw-r--r-- | src/decoder/FfmpegMetaData.hxx (renamed from src/decoder/ffmpeg_metadata.h) | 16 | ||||
-rw-r--r-- | src/decoder/OggCodec.cxx (renamed from src/decoder/_ogg_common.c) | 20 | ||||
-rw-r--r-- | src/decoder/OggCodec.hxx (renamed from src/decoder/_ogg_common.h) | 16 | ||||
-rw-r--r-- | src/decoder/OggFind.cxx | 37 | ||||
-rw-r--r-- | src/decoder/OggFind.hxx | 38 | ||||
-rw-r--r-- | src/decoder/OggSyncState.hxx | 78 | ||||
-rw-r--r-- | src/decoder/OggUtil.cxx | 118 | ||||
-rw-r--r-- | src/decoder/OggUtil.hxx | 87 | ||||
-rw-r--r-- | src/decoder/OpusDecoderPlugin.cxx | 400 | ||||
-rw-r--r-- | src/decoder/OpusDecoderPlugin.h | 25 | ||||
-rw-r--r-- | src/decoder/OpusHead.cxx | 44 | ||||
-rw-r--r-- | src/decoder/OpusHead.hxx | 30 | ||||
-rw-r--r-- | src/decoder/OpusReader.hxx | 98 | ||||
-rw-r--r-- | src/decoder/OpusTags.cxx | 77 | ||||
-rw-r--r-- | src/decoder/OpusTags.hxx | 31 | ||||
-rw-r--r-- | src/decoder/VorbisComments.cxx (renamed from src/decoder/vorbis_comments.c) | 16 | ||||
-rw-r--r-- | src/decoder/VorbisComments.hxx (renamed from src/decoder/vorbis_comments.h) | 6 | ||||
-rw-r--r-- | src/decoder/VorbisDecoderPlugin.cxx (renamed from src/decoder/vorbis_decoder_plugin.c) | 157 | ||||
-rw-r--r-- | src/decoder/VorbisDecoderPlugin.h | 25 | ||||
-rw-r--r-- | src/decoder/WavpackDecoderPlugin.cxx (renamed from src/decoder/wavpack_decoder_plugin.c) | 65 | ||||
-rw-r--r-- | src/decoder/WavpackDecoderPlugin.hxx | 25 | ||||
-rw-r--r-- | src/decoder/XiphTags.c | 28 | ||||
-rw-r--r-- | src/decoder/XiphTags.h | 28 | ||||
-rw-r--r-- | src/decoder/audiofile_decoder_plugin.c | 10 | ||||
-rw-r--r-- | src/decoder/dsdiff_decoder_plugin.c | 151 | ||||
-rw-r--r-- | src/decoder/dsdlib.c | 72 | ||||
-rw-r--r-- | src/decoder/dsdlib.h | 5 | ||||
-rw-r--r-- | src/decoder/dsf_decoder_plugin.c | 30 | ||||
-rw-r--r-- | src/decoder/faad_decoder_plugin.c | 72 | ||||
-rw-r--r-- | src/decoder/flac_compat.h | 114 | ||||
-rw-r--r-- | src/decoder/flac_metadata.h | 64 | ||||
-rw-r--r-- | src/decoder/mad_decoder_plugin.c | 21 | ||||
-rw-r--r-- | src/decoder/modplug_decoder_plugin.c | 13 | ||||
-rw-r--r-- | src/decoder/mp4ff_decoder_plugin.c | 448 | ||||
-rw-r--r-- | src/decoder/mpcdec_decoder_plugin.c | 8 | ||||
-rw-r--r-- | src/decoder/pcm_decoder_plugin.c | 13 | ||||
-rw-r--r-- | src/decoder/sidplay_decoder_plugin.cxx | 10 | ||||
-rw-r--r-- | src/decoder/sndfile_decoder_plugin.c | 6 | ||||
-rw-r--r-- | src/decoder_api.h | 14 | ||||
-rw-r--r-- | src/decoder_control.c | 190 | ||||
-rw-r--r-- | src/decoder_control.h | 277 | ||||
-rw-r--r-- | src/decoder_error.h | 35 | ||||
-rw-r--r-- | src/decoder_plugin.h | 8 | ||||
-rw-r--r-- | src/directory.c | 314 | ||||
-rw-r--r-- | src/directory.h | 262 | ||||
-rw-r--r-- | src/dsd2pcm/dsd2pcm.hpp | 6 | ||||
-rw-r--r-- | src/dsd2pcm/noiseshape.hpp | 9 | ||||
-rw-r--r-- | src/dummy.cxx | 30 | ||||
-rw-r--r-- | src/encoder/OggStream.hxx | 128 | ||||
-rw-r--r-- | src/encoder/OpusEncoderPlugin.cxx | 429 | ||||
-rw-r--r-- | src/encoder/OpusEncoderPlugin.hxx | 25 | ||||
-rw-r--r-- | src/encoder/VorbisEncoderPlugin.cxx (renamed from src/encoder/vorbis_encoder.c) | 130 | ||||
-rw-r--r-- | src/encoder/VorbisEncoderPlugin.hxx | 25 | ||||
-rw-r--r-- | src/encoder/flac_encoder.c | 35 | ||||
-rw-r--r-- | src/encoder/lame_encoder.c | 7 | ||||
-rw-r--r-- | src/encoder/null_encoder.c | 23 | ||||
-rw-r--r-- | src/encoder/twolame_encoder.c | 9 | ||||
-rw-r--r-- | src/encoder/wave_encoder.c | 24 | ||||
-rw-r--r-- | src/encoder_list.c | 8 | ||||
-rw-r--r-- | src/encoder_list.h | 8 | ||||
-rw-r--r-- | src/encoder_plugin.h | 2 | ||||
-rw-r--r-- | src/event/BufferedSocket.cxx | 150 | ||||
-rw-r--r-- | src/event/BufferedSocket.hxx | 104 | ||||
-rw-r--r-- | src/event/FullyBufferedSocket.cxx | 132 | ||||
-rw-r--r-- | src/event/FullyBufferedSocket.hxx | 63 | ||||
-rw-r--r-- | src/event/Loop.hxx | 88 | ||||
-rw-r--r-- | src/event/MultiSocketMonitor.cxx | 107 | ||||
-rw-r--r-- | src/event/MultiSocketMonitor.hxx | 125 | ||||
-rw-r--r-- | src/event/ServerSocket.cxx | 438 | ||||
-rw-r--r-- | src/event/ServerSocket.hxx | 121 | ||||
-rw-r--r-- | src/event/SocketMonitor.cxx | 167 | ||||
-rw-r--r-- | src/event/SocketMonitor.hxx | 148 | ||||
-rw-r--r-- | src/event/TimeoutMonitor.cxx | 65 | ||||
-rw-r--r-- | src/event/TimeoutMonitor.hxx | 60 | ||||
-rw-r--r-- | src/event/WakeFD.cxx | 225 | ||||
-rw-r--r-- | src/event/WakeFD.hxx | 80 | ||||
-rw-r--r-- | src/event_pipe.c | 164 | ||||
-rw-r--r-- | src/event_pipe.h | 71 | ||||
-rw-r--r-- | src/fd_util.c | 14 | ||||
-rw-r--r-- | src/fd_util.h | 19 | ||||
-rw-r--r-- | src/filter/AutoConvertFilterPlugin.cxx | 132 | ||||
-rw-r--r-- | src/filter/AutoConvertFilterPlugin.hxx (renamed from src/filter/autoconvert_filter_plugin.h) | 12 | ||||
-rw-r--r-- | src/filter/ChainFilterPlugin.cxx | 184 | ||||
-rw-r--r-- | src/filter/ChainFilterPlugin.hxx (renamed from src/filter/chain_filter_plugin.h) | 12 | ||||
-rw-r--r-- | src/filter/ConvertFilterPlugin.cxx | 119 | ||||
-rw-r--r-- | src/filter/ConvertFilterPlugin.hxx (renamed from src/filter/convert_filter_plugin.h) | 11 | ||||
-rw-r--r-- | src/filter/NormalizeFilterPlugin.cxx | 84 | ||||
-rw-r--r-- | src/filter/NullFilterPlugin.cxx | 60 | ||||
-rw-r--r-- | src/filter/ReplayGainFilterPlugin.cxx | 242 | ||||
-rw-r--r-- | src/filter/ReplayGainFilterPlugin.hxx (renamed from src/filter/replay_gain_filter_plugin.h) | 16 | ||||
-rw-r--r-- | src/filter/RouteFilterPlugin.cxx (renamed from src/filter/route_filter_plugin.c) | 177 | ||||
-rw-r--r-- | src/filter/VolumeFilterPlugin.cxx | 148 | ||||
-rw-r--r-- | src/filter/VolumeFilterPlugin.hxx (renamed from src/filter/volume_filter_plugin.h) | 12 | ||||
-rw-r--r-- | src/filter/autoconvert_filter_plugin.c | 169 | ||||
-rw-r--r-- | src/filter/chain_filter_plugin.c | 213 | ||||
-rw-r--r-- | src/filter/convert_filter_plugin.c | 146 | ||||
-rw-r--r-- | src/filter/normalize_filter_plugin.c | 112 | ||||
-rw-r--r-- | src/filter/null_filter_plugin.c | 93 | ||||
-rw-r--r-- | src/filter/replay_gain_filter_plugin.c | 245 | ||||
-rw-r--r-- | src/filter/volume_filter_plugin.c | 158 | ||||
-rw-r--r-- | src/filter_plugin.h | 150 | ||||
-rw-r--r-- | src/fs/DirectoryReader.hxx | 87 | ||||
-rw-r--r-- | src/fs/FileSystem.cxx | 43 | ||||
-rw-r--r-- | src/fs/FileSystem.hxx | 163 | ||||
-rw-r--r-- | src/fs/Path.cxx (renamed from src/path.c) | 98 | ||||
-rw-r--r-- | src/fs/Path.hxx | 267 | ||||
-rw-r--r-- | src/gcc.h | 41 | ||||
-rw-r--r-- | src/gerror.h (renamed from src/sig_handlers.h) | 6 | ||||
-rw-r--r-- | src/glib_compat.h | 11 | ||||
-rw-r--r-- | src/icy_metadata.h | 99 | ||||
-rw-r--r-- | src/inotify_source.h | 61 | ||||
-rw-r--r-- | src/input/ArchiveInputPlugin.cxx (renamed from src/input/archive_input_plugin.c) | 40 | ||||
-rw-r--r-- | src/input/ArchiveInputPlugin.hxx (renamed from src/input/archive_input_plugin.h) | 6 | ||||
-rw-r--r-- | src/input/CdioParanoiaInputPlugin.cxx (renamed from src/input/cdio_paranoia_input_plugin.c) | 114 | ||||
-rw-r--r-- | src/input/CdioParanoiaInputPlugin.hxx (renamed from src/input/cdio_paranoia_input_plugin.h) | 6 | ||||
-rw-r--r-- | src/input/CurlInputPlugin.cxx (renamed from src/input/curl_input_plugin.c) | 596 | ||||
-rw-r--r-- | src/input/CurlInputPlugin.hxx (renamed from src/input/curl_input_plugin.h) | 6 | ||||
-rw-r--r-- | src/input/DespotifyInputPlugin.cxx (renamed from src/input/despotify_input_plugin.c) | 107 | ||||
-rw-r--r-- | src/input/DespotifyInputPlugin.hxx (renamed from src/input/despotify_input_plugin.h) | 6 | ||||
-rw-r--r-- | src/input/FfmpegInputPlugin.cxx (renamed from src/input/ffmpeg_input_plugin.c) | 136 | ||||
-rw-r--r-- | src/input/FfmpegInputPlugin.hxx (renamed from src/input/ffmpeg_input_plugin.h) | 6 | ||||
-rw-r--r-- | src/input/FileInputPlugin.cxx (renamed from src/input/file_input_plugin.c) | 92 | ||||
-rw-r--r-- | src/input/FileInputPlugin.hxx (renamed from src/input/file_input_plugin.h) | 6 | ||||
-rw-r--r-- | src/input/MmsInputPlugin.cxx (renamed from src/input/mms_input_plugin.c) | 81 | ||||
-rw-r--r-- | src/input/MmsInputPlugin.hxx (renamed from src/input/mms_input_plugin.h) | 0 | ||||
-rw-r--r-- | src/input/RewindInputPlugin.cxx (renamed from src/input/rewind_input_plugin.c) | 148 | ||||
-rw-r--r-- | src/input/RewindInputPlugin.hxx (renamed from src/input/rewind_input_plugin.h) | 6 | ||||
-rw-r--r-- | src/input/SoupInputPlugin.cxx (renamed from src/input/soup_input_plugin.c) | 287 | ||||
-rw-r--r-- | src/input/SoupInputPlugin.hxx (renamed from src/input/soup_input_plugin.h) | 6 | ||||
-rw-r--r-- | src/input_internal.c | 73 | ||||
-rw-r--r-- | src/input_stream.h | 112 | ||||
-rw-r--r-- | src/io_error.h | 51 | ||||
-rw-r--r-- | src/locate.c | 239 | ||||
-rw-r--r-- | src/locate.h | 92 | ||||
-rw-r--r-- | src/ls.cxx (renamed from src/ls.c) | 14 | ||||
-rw-r--r-- | src/ls.hxx (renamed from src/ls.h) | 11 | ||||
-rw-r--r-- | src/mapper.c | 275 | ||||
-rw-r--r-- | src/mixer/AlsaMixerPlugin.cxx (renamed from src/mixer/alsa_mixer_plugin.c) | 191 | ||||
-rw-r--r-- | src/mixer/OssMixerPlugin.cxx (renamed from src/mixer/oss_mixer_plugin.c) | 18 | ||||
-rw-r--r-- | src/mixer/PulseMixerPlugin.cxx (renamed from src/mixer/pulse_mixer_plugin.c) | 32 | ||||
-rw-r--r-- | src/mixer/PulseMixerPlugin.h (renamed from src/mixer/pulse_mixer_plugin.h) | 10 | ||||
-rw-r--r-- | src/mixer/RoarMixerPlugin.cxx (renamed from src/mixer/roar_mixer_plugin.c) | 78 | ||||
-rw-r--r-- | src/mixer/SoftwareMixerPlugin.cxx (renamed from src/mixer/software_mixer_plugin.c) | 30 | ||||
-rw-r--r-- | src/mixer/SoftwareMixerPlugin.hxx (renamed from src/mixer/software_mixer_plugin.h) | 10 | ||||
-rw-r--r-- | src/mixer/WinmmMixerPlugin.cxx (renamed from src/mixer/winmm_mixer_plugin.c) | 25 | ||||
-rw-r--r-- | src/mpd_error.h | 1 | ||||
-rw-r--r-- | src/notify.c | 58 | ||||
-rw-r--r-- | src/notify.cxx | 45 | ||||
-rw-r--r-- | src/notify.hxx | 53 | ||||
-rw-r--r-- | src/output/AlsaOutputPlugin.cxx (renamed from src/output/alsa_output_plugin.c) | 212 | ||||
-rw-r--r-- | src/output/AlsaOutputPlugin.hxx (renamed from src/output/alsa_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/HttpdClient.cxx | 444 | ||||
-rw-r--r-- | src/output/HttpdClient.hxx | 186 | ||||
-rw-r--r-- | src/output/HttpdInternal.hxx (renamed from src/output/httpd_internal.h) | 122 | ||||
-rw-r--r-- | src/output/HttpdOutputPlugin.cxx | 570 | ||||
-rw-r--r-- | src/output/HttpdOutputPlugin.hxx (renamed from src/output/httpd_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/NullOutputPlugin.cxx (renamed from src/output/null_output_plugin.c) | 43 | ||||
-rw-r--r-- | src/output/NullOutputPlugin.hxx (renamed from src/output/null_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/OSXOutputPlugin.cxx (renamed from src/output/osx_output_plugin.c) | 89 | ||||
-rw-r--r-- | src/output/OSXOutputPlugin.hxx (renamed from src/output/osx_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/OssOutputPlugin.cxx (renamed from src/output/oss_output_plugin.c) | 67 | ||||
-rw-r--r-- | src/output/OssOutputPlugin.hxx (renamed from src/output/oss_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/RoarOutputPlugin.cxx (renamed from src/output/roar_output_plugin.c) | 150 | ||||
-rw-r--r-- | src/output/RoarOutputPlugin.hxx (renamed from src/output/roar_output_plugin.h) | 10 | ||||
-rw-r--r-- | src/output/WinmmOutputPlugin.cxx (renamed from src/output/winmm_output_plugin.c) | 87 | ||||
-rw-r--r-- | src/output/WinmmOutputPlugin.hxx (renamed from src/output/winmm_output_plugin.h) | 15 | ||||
-rw-r--r-- | src/output/fifo_output_plugin.c | 1 | ||||
-rw-r--r-- | src/output/httpd_client.c | 764 | ||||
-rw-r--r-- | src/output/httpd_client.h | 71 | ||||
-rw-r--r-- | src/output/httpd_output_plugin.c | 623 | ||||
-rw-r--r-- | src/output/pulse_output_plugin.c | 4 | ||||
-rw-r--r-- | src/output/pulse_output_plugin.h | 12 | ||||
-rw-r--r-- | src/output/shout_output_plugin.c | 104 | ||||
-rw-r--r-- | src/output_internal.h | 29 | ||||
-rw-r--r-- | src/output_plugin.h | 15 | ||||
-rw-r--r-- | src/page.c | 89 | ||||
-rw-r--r-- | src/path.h | 55 | ||||
-rw-r--r-- | src/pcm_buffer.h | 8 | ||||
-rw-r--r-- | src/pcm_convert.c | 379 | ||||
-rw-r--r-- | src/pcm_convert.h | 94 | ||||
-rw-r--r-- | src/pcm_dither.c | 93 | ||||
-rw-r--r-- | src/pcm_export.h | 8 | ||||
-rw-r--r-- | src/pcm_mix.c | 280 | ||||
-rw-r--r-- | src/pcm_utils.h | 94 | ||||
-rw-r--r-- | src/player_control.c | 391 | ||||
-rw-r--r-- | src/player_control.h | 287 | ||||
-rw-r--r-- | src/playlist.c | 452 | ||||
-rw-r--r-- | src/playlist.h | 256 | ||||
-rw-r--r-- | src/playlist/AsxPlaylistPlugin.cxx (renamed from src/playlist/asx_playlist_plugin.c) | 126 | ||||
-rw-r--r-- | src/playlist/AsxPlaylistPlugin.hxx (renamed from src/playlist/asx_playlist_plugin.h) | 6 | ||||
-rw-r--r-- | src/playlist/CuePlaylistPlugin.cxx (renamed from src/playlist/cue_playlist_plugin.c) | 36 | ||||
-rw-r--r-- | src/playlist/CuePlaylistPlugin.hxx (renamed from src/playlist/cue_playlist_plugin.h) | 6 | ||||
-rw-r--r-- | src/playlist/DespotifyPlaylistPlugin.cxx | 146 | ||||
-rw-r--r-- | src/playlist/DespotifyPlaylistPlugin.hxx (renamed from src/playlist/despotify_playlist_plugin.h) | 6 | ||||
-rw-r--r-- | src/playlist/EmbeddedCuePlaylistPlugin.cxx (renamed from src/playlist/embcue_playlist_plugin.c) | 43 | ||||
-rw-r--r-- | src/playlist/EmbeddedCuePlaylistPlugin.hxx (renamed from src/playlist/embcue_playlist_plugin.h) | 6 | ||||
-rw-r--r-- | src/playlist/ExtM3uPlaylistPlugin.cxx (renamed from src/playlist/extm3u_playlist_plugin.c) | 45 | ||||
-rw-r--r-- | src/playlist/ExtM3uPlaylistPlugin.hxx (renamed from src/playlist/extm3u_playlist_plugin.h) | 6 | ||||
-rw-r--r-- | src/playlist/LastFMPlaylistPlugin.cxx (renamed from src/playlist/lastfm_playlist_plugin.c) | 43 | ||||
-rw-r--r-- | src/playlist/LastFMPlaylistPlugin.hxx (renamed from src/playlist/lastfm_playlist_plugin.h) | 6 | ||||
-rw-r--r-- | src/playlist/M3uPlaylistPlugin.cxx (renamed from src/playlist/m3u_playlist_plugin.c) | 36 | ||||
-rw-r--r-- | src/playlist/M3uPlaylistPlugin.hxx (renamed from src/playlist/m3u_playlist_plugin.h) | 6 | ||||
-rw-r--r-- | src/playlist/MemoryPlaylistProvider.cxx | 69 | ||||
-rw-r--r-- | src/playlist/MemoryPlaylistProvider.hxx (renamed from src/db/simple_db_plugin.h) | 29 | ||||
-rw-r--r-- | src/playlist/PlsPlaylistPlugin.cxx (renamed from src/playlist/pls_playlist_plugin.c) | 81 | ||||
-rw-r--r-- | src/playlist/PlsPlaylistPlugin.hxx (renamed from src/playlist/pls_playlist_plugin.h) | 6 | ||||
-rw-r--r-- | src/playlist/RssPlaylistPlugin.cxx (renamed from src/playlist/rss_playlist_plugin.c) | 125 | ||||
-rw-r--r-- | src/playlist/RssPlaylistPlugin.hxx (renamed from src/playlist/rss_playlist_plugin.h) | 6 | ||||
-rw-r--r-- | src/playlist/SoundCloudPlaylistPlugin.cxx (renamed from src/playlist/soundcloud_playlist_plugin.c) | 78 | ||||
-rw-r--r-- | src/playlist/SoundCloudPlaylistPlugin.hxx (renamed from src/playlist/soundcloud_playlist_plugin.h) | 6 | ||||
-rw-r--r-- | src/playlist/XspfPlaylistPlugin.cxx (renamed from src/playlist/xspf_playlist_plugin.c) | 156 | ||||
-rw-r--r-- | src/playlist/XspfPlaylistPlugin.hxx (renamed from src/playlist/xspf_playlist_plugin.h) | 6 | ||||
-rw-r--r-- | src/playlist/despotify_playlist_plugin.c | 217 | ||||
-rw-r--r-- | src/playlist_control.c | 288 | ||||
-rw-r--r-- | src/playlist_edit.c | 487 | ||||
-rw-r--r-- | src/playlist_internal.h | 56 | ||||
-rw-r--r-- | src/playlist_vector.c | 114 | ||||
-rw-r--r-- | src/playlist_vector.h | 80 | ||||
-rw-r--r-- | src/protocol/ArgParser.cxx (renamed from src/protocol/argparser.c) | 18 | ||||
-rw-r--r-- | src/protocol/ArgParser.hxx (renamed from src/protocol/argparser.h) | 20 | ||||
-rw-r--r-- | src/protocol/Result.cxx (renamed from src/protocol/result.c) | 12 | ||||
-rw-r--r-- | src/protocol/Result.hxx (renamed from src/protocol/result.h) | 19 | ||||
-rw-r--r-- | src/queue.c | 603 | ||||
-rw-r--r-- | src/queue.h | 379 | ||||
-rw-r--r-- | src/queue_print.c | 122 | ||||
-rw-r--r-- | src/replay_gain_config.h | 10 | ||||
-rw-r--r-- | src/replay_gain_info.h | 16 | ||||
-rw-r--r-- | src/resolver.h | 14 | ||||
-rw-r--r-- | src/server_socket.c | 483 | ||||
-rw-r--r-- | src/server_socket.h | 92 | ||||
-rw-r--r-- | src/song.h | 73 | ||||
-rw-r--r-- | src/state_file.c | 162 | ||||
-rw-r--r-- | src/stats.c | 128 | ||||
-rw-r--r-- | src/stats.h | 5 | ||||
-rw-r--r-- | src/string_util.c | 36 | ||||
-rw-r--r-- | src/string_util.h | 46 | ||||
-rw-r--r-- | src/strset.c | 148 | ||||
-rw-r--r-- | src/strset.h | 50 | ||||
-rw-r--r-- | src/tag.h | 10 | ||||
-rw-r--r-- | src/tag_id3.c | 5 | ||||
-rw-r--r-- | src/tag_id3.h | 18 | ||||
-rw-r--r-- | src/tag_table.h | 5 | ||||
-rw-r--r-- | src/text_file.h | 39 | ||||
-rw-r--r-- | src/text_input_stream.c | 2 | ||||
-rw-r--r-- | src/thread/Cond.hxx | 37 | ||||
-rw-r--r-- | src/thread/CriticalSection.hxx | 68 | ||||
-rw-r--r-- | src/thread/GLibCond.hxx | 88 | ||||
-rw-r--r-- | src/thread/GLibMutex.hxx | 90 | ||||
-rw-r--r-- | src/thread/Mutex.hxx | 54 | ||||
-rw-r--r-- | src/thread/PosixCond.hxx | 60 | ||||
-rw-r--r-- | src/thread/PosixMutex.hxx | 62 | ||||
-rw-r--r-- | src/thread/WindowsCond.hxx | 63 | ||||
-rw-r--r-- | src/timer.h | 8 | ||||
-rw-r--r-- | src/tokenizer.c | 2 | ||||
-rw-r--r-- | src/tokenizer.h | 2 | ||||
-rw-r--r-- | src/update_io.c | 117 | ||||
-rw-r--r-- | src/uri.h | 10 | ||||
-rw-r--r-- | src/util/HugeAllocator.cxx | 87 | ||||
-rw-r--r-- | src/util/HugeAllocator.hxx | 82 | ||||
-rw-r--r-- | src/util/LazyRandomEngine.cxx | 31 | ||||
-rw-r--r-- | src/util/LazyRandomEngine.hxx | 67 | ||||
-rw-r--r-- | src/util/Manual.hxx | 111 | ||||
-rw-r--r-- | src/util/PeakBuffer.cxx | 143 | ||||
-rw-r--r-- | src/util/PeakBuffer.hxx | 66 | ||||
-rw-r--r-- | src/util/RefCount.hxx (renamed from src/refcount.h) | 44 | ||||
-rw-r--r-- | src/util/SliceBuffer.hxx | 161 | ||||
-rw-r--r-- | src/util/bit_reverse.h | 5 | ||||
-rw-r--r-- | src/util/fifo_buffer.c (renamed from src/fifo_buffer.c) | 8 | ||||
-rw-r--r-- | src/util/fifo_buffer.h (renamed from src/fifo_buffer.h) | 11 | ||||
-rw-r--r-- | src/util/growing_fifo.c (renamed from src/growing_fifo.c) | 0 | ||||
-rw-r--r-- | src/util/growing_fifo.h (renamed from src/growing_fifo.h) | 0 | ||||
-rw-r--r-- | src/util/list.h | 53 | ||||
-rw-r--r-- | src/utils.c | 1 | ||||
-rw-r--r-- | src/utils.h | 12 | ||||
-rw-r--r-- | test/DumpDatabase.cxx | 147 | ||||
-rw-r--r-- | test/FakeReplayGainConfig.cxx | 25 | ||||
-rw-r--r-- | test/FakeSong.cxx | 33 | ||||
-rw-r--r-- | test/dump_playlist.cxx (renamed from test/dump_playlist.c) | 39 | ||||
-rw-r--r-- | test/dump_rva2.c | 9 | ||||
-rw-r--r-- | test/dump_text_file.cxx (renamed from test/dump_text_file.c) | 23 | ||||
-rw-r--r-- | test/read_conf.cxx (renamed from test/read_conf.c) | 13 | ||||
-rw-r--r-- | test/read_mixer.cxx (renamed from test/read_mixer.c) | 26 | ||||
-rw-r--r-- | test/read_tags.cxx (renamed from test/read_tags.c) | 67 | ||||
-rw-r--r-- | test/run_convert.cxx (renamed from test/run_convert.c) | 20 | ||||
-rw-r--r-- | test/run_decoder.cxx (renamed from test/run_decoder.c) | 44 | ||||
-rw-r--r-- | test/run_encoder.cxx (renamed from test/run_encoder.c) | 13 | ||||
-rw-r--r-- | test/run_filter.cxx (renamed from test/run_filter.c) | 62 | ||||
-rw-r--r-- | test/run_inotify.cxx (renamed from test/run_inotify.c) | 31 | ||||
-rw-r--r-- | test/run_input.cxx (renamed from test/run_input.c) | 30 | ||||
-rw-r--r-- | test/run_normalize.cxx (renamed from test/run_normalize.c) | 4 | ||||
-rw-r--r-- | test/run_output.cxx (renamed from test/run_output.c) | 78 | ||||
-rw-r--r-- | test/run_tcp_connect.c | 164 | ||||
-rw-r--r-- | test/software_volume.cxx (renamed from test/software_volume.c) | 9 | ||||
-rw-r--r-- | test/test_pcm_all.hxx (renamed from test/test_pcm_all.h) | 52 | ||||
-rw-r--r-- | test/test_pcm_channels.cxx (renamed from test/test_pcm_channels.c) | 25 | ||||
-rw-r--r-- | test/test_pcm_dither.cxx (renamed from test/test_pcm_dither.c) | 51 | ||||
-rw-r--r-- | test/test_pcm_format.cxx | 130 | ||||
-rw-r--r-- | test/test_pcm_main.cxx (renamed from test/test_pcm_main.c) | 14 | ||||
-rw-r--r-- | test/test_pcm_mix.cxx | 84 | ||||
-rw-r--r-- | test/test_pcm_pack.cxx (renamed from test/test_pcm_pack.c) | 42 | ||||
-rw-r--r-- | test/test_pcm_util.hxx | 85 | ||||
-rw-r--r-- | test/test_pcm_volume.cxx (renamed from test/test_pcm_volume.c) | 101 | ||||
-rw-r--r-- | test/test_queue_priority.c | 175 | ||||
-rw-r--r-- | test/test_queue_priority.cxx | 188 | ||||
-rw-r--r-- | test/test_vorbis_encoder.cxx (renamed from test/test_vorbis_encoder.c) | 8 | ||||
-rw-r--r-- | test/visit_archive.cxx | 123 | ||||
-rw-r--r-- | valgrind.suppressions | 42 |
688 files changed, 33651 insertions, 27139 deletions
@@ -72,3 +72,4 @@ test/dump_rva2 test/dump_text_file test/test_byte_reverse test/test_vorbis_encoder +test/DumpDatabase @@ -81,14 +81,17 @@ Alternative for MP3 support. Ogg Vorbis - http://www.xiph.org/ogg/vorbis/ For Ogg Vorbis support. You will need libogg and libvorbis. +libopus - http://www.opus-codec.org/ +Opus codec support + FLAC - http://flac.sourceforge.net/ -For FLAC support. You will need version 1.1.0 or higher of libflac. +For FLAC support. You will need version 1.2 or higher of libFLAC. Audio File - http://www.68k.org/~michael/audiofile/ For WAVE, AIFF, and AU support. You will need libaudiofile. FAAD2 - http://www.audiocoding.com/ -For MP4/AAC support. You will need libmp4ff. +For MP4/AAC support. libmpcdec - http://www.musepack.net/ For Musepack support. @@ -114,6 +117,9 @@ WAVE, AIFF, and many others. libwavpack - http://www.wavpack.com/ For WavPack playback. +libadplug - http://adplug.sourceforge.net/ +For AdLib playback. + despotify - https://github.com/SimonKagstrom/despotify For Spotify playback. diff --git a/Makefile.am b/Makefile.am index a0b32f8b..2e302929 100644 --- a/Makefile.am +++ b/Makefile.am @@ -9,9 +9,13 @@ bin_PROGRAMS = src/mpd noinst_LIBRARIES = \ libutil.a \ + libevent.a \ libpcm.a \ + libconf.a \ libtag.a \ libinput.a \ + libfs.a \ + libdb_plugins.a \ libplaylist_plugins.a \ libdecoder_plugins.a \ libfilter_plugins.a \ @@ -19,10 +23,12 @@ noinst_LIBRARIES = \ liboutput_plugins.a src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \ + $(LIBMPDCLIENT_CFLAGS) \ $(AVAHI_CFLAGS) \ $(LIBWRAP_CFLAGS) \ $(SQLITE_CFLAGS) src_mpd_LDADD = \ + $(DB_LIBS) \ $(PLAYLIST_LIBS) \ $(AVAHI_LIBS) \ $(LIBWRAP_LDFLAGS) \ @@ -35,190 +41,68 @@ src_mpd_LDADD = \ $(FILTER_LIBS) \ $(ENCODER_LIBS) \ $(MIXER_LIBS) \ + libconf.a \ + libevent.a \ libutil.a \ + libfs.a \ $(SYSTEMD_DAEMON_LIBS) \ $(GLIB_LIBS) mpd_headers = \ src/check.h \ - src/notify.h \ src/ack.h \ src/ape.h \ src/audio_format.h \ src/audio_check.h \ - src/audio_parser.h \ src/output_internal.h \ src/output_api.h \ - src/output_list.h \ - src/output_all.h \ - src/output_thread.h \ - src/output_control.h \ - src/output_state.h \ - src/output_print.h \ - src/output_command.h \ src/filter_internal.h \ - src/filter_config.h \ - src/filter_plugin.h \ - src/filter_registry.h \ - src/filter/autoconvert_filter_plugin.h \ - src/filter/chain_filter_plugin.h \ - src/filter/convert_filter_plugin.h \ - src/filter/replay_gain_filter_plugin.h \ - src/filter/volume_filter_plugin.h \ src/command.h \ - src/idle.h \ - src/cmdline.h \ src/conf.h \ - src/crossfade.h \ - src/dbUtils.h \ - src/decoder_thread.h \ - src/decoder_control.h \ src/decoder_plugin.h \ src/decoder_command.h \ src/decoder_buffer.h \ src/decoder_api.h \ src/decoder_plugin.h \ - src/decoder_internal.h \ - src/directory.h \ - src/directory_save.h \ - src/database.h \ src/encoder_plugin.h \ src/encoder_list.h \ src/encoder_api.h \ - src/exclude.h \ src/fd_util.h \ + src/gerror.h \ src/glib_compat.h \ - src/update.h \ - src/inotify_source.h \ - src/inotify_queue.h \ - src/inotify_update.h \ src/gcc.h \ - src/decoder_list.h \ - src/decoder_print.h \ - src/decoder/flac_compat.h \ - src/decoder/flac_metadata.h \ - src/decoder/flac_pcm.h \ - src/decoder/_flac_common.h \ - src/decoder/_ogg_common.h \ src/decoder/pcm_decoder_plugin.h \ - src/input_init.h \ - src/input_plugin.h \ - src/input_registry.h \ src/input_stream.h \ - src/input/file_input_plugin.h \ - src/input/ffmpeg_input_plugin.h \ - src/input/curl_input_plugin.h \ - src/input/rewind_input_plugin.h \ - src/input/mms_input_plugin.h \ - src/input/despotify_input_plugin.h \ - src/input/cdio_paranoia_input_plugin.h \ - src/despotify_utils.h \ - src/text_file.h \ src/text_input_stream.h \ - src/icy_server.h \ - src/icy_metadata.h \ - src/client.h \ - src/client_internal.h \ - src/server_socket.h \ - src/listen.h \ - src/log.h \ src/ls.h \ - src/main.h \ - src/mixer_all.h \ - src/mixer_api.h \ - src/mixer_control.h \ - src/mixer_list.h \ - src/event_pipe.h \ src/mixer_plugin.h \ - src/mixer_type.h \ - src/mixer/software_mixer_plugin.h \ - src/mixer/pulse_mixer_plugin.h \ src/daemon.h \ src/AudioCompress/config.h \ src/AudioCompress/compress.h \ - src/buffer.h \ - src/pipe.h \ - src/chunk.h \ - src/path.h \ - src/mapper.h \ src/open.h \ - src/output/httpd_client.h \ - src/output/httpd_internal.h \ - src/page.h \ - src/permission.h \ - src/player_thread.h \ - src/player_control.h \ - src/playlist.h \ + src/Playlist.hxx \ src/playlist_error.h \ - src/playlist_internal.h \ - src/playlist_print.h \ - src/playlist_save.h \ - src/playlist_state.h \ - src/playlist_plugin.h \ - src/playlist_list.h \ - src/playlist_mapper.h \ - src/playlist_any.h \ - src/playlist_song.h \ - src/playlist_queue.h \ - src/playlist_vector.h \ - src/playlist_database.h \ - src/playlist/extm3u_playlist_plugin.h \ - src/playlist/m3u_playlist_plugin.h \ - src/playlist/pls_playlist_plugin.h \ - src/playlist/xspf_playlist_plugin.h \ - src/playlist/asx_playlist_plugin.h \ - src/playlist/rss_playlist_plugin.h \ - src/playlist/lastfm_playlist_plugin.h \ - src/playlist/despotify_playlist_plugin.h \ - src/playlist/cue_playlist_plugin.h \ src/poison.h \ src/riff.h \ src/aiff.h \ - src/queue.h \ - src/queue_print.h \ - src/queue_save.h \ - src/refcount.h \ src/replay_gain_config.h \ src/replay_gain_info.h \ src/replay_gain_ape.h \ - src/sig_handlers.h \ + src/TimePrint.cxx src/TimePrint.hxx \ src/song.h \ - src/song_print.h \ - src/song_save.h \ - src/song_sticker.h \ src/song_sort.c src/song_sort.h \ - src/socket_util.h \ - src/state_file.h \ src/stats.h \ - src/sticker.h \ - src/sticker_print.h \ src/tag.h \ src/tag_internal.h \ - src/tag_pool.h \ src/tag_table.h \ src/tag_ape.h \ src/tag_id3.h \ src/tag_rva2.h \ - src/tag_print.h \ - src/tag_save.h \ src/tokenizer.h \ - src/strset.h \ src/uri.h \ src/utils.h \ src/string_util.h \ - src/volume.h \ - src/zeroconf.h src/zeroconf-internal.h \ - src/locate.h \ - src/stored_playlist.h \ src/timer.h \ - src/archive_api.h \ - src/archive_internal.h \ - src/archive_list.h \ - src/archive_plugin.h \ - src/archive/bz2_archive_plugin.h \ - src/archive/iso9660_archive_plugin.h \ - src/archive/zzip_archive_plugin.h \ - src/input/archive_input_plugin.h \ src/mpd_error.h src_mpd_SOURCES = \ @@ -226,133 +110,149 @@ src_mpd_SOURCES = \ $(DECODER_SRC) \ $(OUTPUT_API_SRC) \ $(MIXER_API_SRC) \ - src/glib_socket.h \ + src/thread/Mutex.hxx \ + src/thread/PosixMutex.hxx \ + src/thread/CriticalSection.hxx \ + src/thread/GLibMutex.hxx \ + src/thread/Cond.hxx \ + src/thread/PosixCond.hxx \ + src/thread/WindowsCond.hxx \ + src/thread/GLibCond.hxx \ src/clock.c src/clock.h \ - src/notify.c \ - src/audio_config.c src/audio_config.h \ + src/notify.cxx src/notify.hxx \ + src/AudioConfig.cxx src/AudioConfig.hxx \ src/audio_check.c \ src/audio_format.c \ - src/audio_parser.c \ - src/protocol/argparser.c src/protocol/argparser.h \ - src/protocol/result.c src/protocol/result.h \ - src/command.c \ - src/idle.c \ - src/cmdline.c \ - src/conf.c \ - src/crossfade.c \ + src/AudioParser.cxx src/AudioParser.hxx \ + src/protocol/ArgParser.cxx src/protocol/ArgParser.hxx \ + src/protocol/Result.cxx src/protocol/Result.hxx \ + src/CommandError.cxx src/CommandError.hxx \ + src/AllCommands.cxx src/AllCommands.hxx \ + src/QueueCommands.cxx src/QueueCommands.hxx \ + src/PlayerCommands.cxx src/PlayerCommands.hxx \ + src/PlaylistCommands.cxx src/PlaylistCommands.hxx \ + src/DatabaseCommands.cxx src/DatabaseCommands.hxx \ + src/OutputCommands.cxx src/OutputCommands.hxx \ + src/MessageCommands.cxx src/MessageCommands.hxx \ + src/OtherCommands.cxx src/OtherCommands.hxx \ + src/Idle.cxx src/Idle.hxx \ + src/CommandLine.cxx src/CommandLine.hxx \ + src/CrossFade.cxx src/CrossFade.hxx \ src/cue/cue_parser.c src/cue/cue_parser.h \ - src/dbUtils.c \ - src/decoder_thread.c \ - src/decoder_control.c \ - src/decoder_api.c \ - src/decoder_internal.c \ - src/decoder_print.c \ - src/directory.c \ - src/directory_save.c \ - src/database.c \ - src/db_internal.h \ + src/decoder_error.h \ + src/DecoderThread.cxx src/DecoderThread.hxx \ + src/DecoderControl.cxx src/DecoderControl.hxx \ + src/DecoderAPI.cxx \ + src/DecoderInternal.cxx src/DecoderInternal.hxx \ + src/DecoderPrint.cxx src/DecoderPrint.hxx \ + src/Directory.cxx src/Directory.hxx \ + src/DirectorySave.cxx src/DirectorySave.hxx \ + src/DatabaseSimple.hxx \ + src/DatabaseGlue.cxx src/DatabaseGlue.hxx \ + src/DatabasePrint.cxx src/DatabasePrint.hxx \ + src/DatabaseQueue.cxx src/DatabaseQueue.hxx \ + src/DatabasePlaylist.cxx src/DatabasePlaylist.hxx \ src/db_error.h \ - src/db_lock.c src/db_lock.h \ - src/db_save.c src/db_save.h \ - src/db_print.c src/db_print.h \ - src/db_plugin.h \ - src/db_visitor.h \ - src/db_selection.h \ - src/db/simple_db_plugin.c src/db/simple_db_plugin.h \ - src/exclude.c \ + src/DatabaseLock.cxx src/DatabaseLock.hxx \ + src/DatabaseSave.cxx src/DatabaseSave.hxx \ + src/DatabasePlugin.hxx \ + src/DatabaseVisitor.hxx \ + src/DatabaseSelection.cxx src/DatabaseSelection.hxx \ + src/ExcludeList.cxx src/ExcludeList.hxx \ src/fd_util.c \ - src/fifo_buffer.c src/fifo_buffer.h \ - src/growing_fifo.c src/growing_fifo.h \ - src/filter_config.c \ - src/filter_plugin.c \ - src/filter_registry.c \ - src/update.c \ - src/update_queue.c src/update_queue.h \ - src/update_io.c src/update_io.h \ - src/update_db.c src/update_db.h \ - src/update_walk.c src/update_walk.h \ - src/update_song.c src/update_song.h \ - src/update_container.c src/update_container.h \ - src/update_internal.h \ - src/update_remove.c src/update_remove.h \ - src/client.c \ - src/client_event.c \ - src/client_expire.c \ - src/client_global.c \ - src/client_idle.h \ - src/client_idle.c \ - src/client_list.c \ - src/client_new.c \ - src/client_process.c \ - src/client_read.c \ - src/client_write.c \ - src/client_message.h \ - src/client_message.c \ - src/client_subscribe.h \ - src/client_subscribe.c \ - src/client_file.c src/client_file.h \ - src/server_socket.c \ - src/listen.c \ - src/log.c \ - src/ls.c \ - src/io_thread.c src/io_thread.h \ - src/main.c \ - src/main_win32.c \ - src/event_pipe.c \ + src/FilterConfig.cxx src/FilterConfig.hxx \ + src/FilterPlugin.cxx src/FilterPlugin.hxx \ + src/FilterRegistry.cxx src/FilterRegistry.hxx \ + src/UpdateGlue.cxx src/UpdateGlue.hxx \ + src/UpdateQueue.cxx src/UpdateQueue.hxx \ + src/UpdateIO.cxx src/UpdateIO.hxx \ + src/UpdateDatabase.cxx src/UpdateDatabase.hxx \ + src/UpdateWalk.cxx src/UpdateWalk.hxx \ + src/UpdateSong.cxx src/UpdateSong.hxx \ + src/UpdateContainer.cxx src/UpdateContainer.hxx \ + src/UpdateInternal.hxx \ + src/UpdateRemove.cxx src/UpdateRemove.hxx \ + src/CommandListBuilder.cxx src/CommandListBuilder.hxx \ + src/Client.cxx src/Client.hxx \ + src/ClientInternal.hxx \ + src/ClientEvent.cxx \ + src/ClientExpire.cxx \ + src/ClientGlobal.cxx \ + src/ClientIdle.cxx \ + src/ClientList.cxx src/ClientList.hxx \ + src/ClientNew.cxx \ + src/ClientProcess.cxx \ + src/ClientRead.cxx \ + src/ClientWrite.cxx \ + src/ClientMessage.cxx src/ClientMessage.hxx \ + src/ClientSubscribe.cxx src/ClientSubscribe.hxx \ + src/ClientFile.cxx src/ClientFile.hxx \ + src/Listen.cxx src/Listen.hxx \ + src/Log.cxx src/Log.hxx \ + src/ls.cxx \ + src/SocketError.hxx \ + src/io_error.h \ + src/IOThread.cxx src/IOThread.hxx \ + src/Main.cxx src/Main.hxx \ + src/Win32Main.cxx \ + src/GlobalEvents.cxx src/GlobalEvents.hxx \ src/daemon.c \ src/AudioCompress/compress.c \ - src/buffer.c \ - src/pipe.c \ - src/chunk.c \ - src/path.c \ - src/mapper.c \ - src/page.c \ - src/permission.c \ - src/player_thread.c \ - src/player_control.c \ - src/playlist.c \ - src/playlist_global.c \ - src/playlist_control.c \ - src/playlist_edit.c \ - src/playlist_print.c \ - src/playlist_save.c \ - src/playlist_mapper.c \ - src/playlist_any.c \ - src/playlist_song.c \ - src/playlist_state.c \ - src/playlist_queue.c \ - src/playlist_vector.c \ - src/playlist_database.c \ - src/queue.c \ - src/queue_print.c \ - src/queue_save.c \ - src/replay_gain_config.c \ - src/replay_gain_info.c \ - src/sig_handlers.c \ - src/song.c \ - src/song_update.c \ - src/song_print.c \ - src/song_save.c \ + src/MusicBuffer.cxx src/MusicBuffer.hxx \ + src/MusicPipe.cxx src/MusicPipe.hxx \ + src/MusicChunk.cxx src/MusicChunk.hxx \ + src/Mapper.cxx src/Mapper.hxx \ + src/Page.cxx src/Page.hxx \ + src/Partition.hxx \ + src/Permission.cxx src/Permission.hxx \ + src/PlayerThread.cxx src/PlayerThread.hxx \ + src/PlayerControl.cxx src/PlayerControl.hxx \ + src/Playlist.cxx \ + src/PlaylistGlobal.cxx src/PlaylistGlobal.hxx \ + src/PlaylistControl.cxx \ + src/PlaylistEdit.cxx \ + src/PlaylistPrint.cxx src/PlaylistPrint.hxx \ + src/PlaylistSave.cxx src/PlaylistSave.hxx \ + src/PlaylistMapper.cxx src/PlaylistMapper.hxx \ + src/PlaylistAny.cxx src/PlaylistAny.hxx \ + src/PlaylistSong.cxx src/PlaylistSong.hxx \ + src/PlaylistState.cxx src/PlaylistState.hxx \ + src/PlaylistQueue.cxx src/PlaylistQueue.hxx \ + src/PlaylistVector.cxx src/PlaylistVector.hxx \ + src/PlaylistInfo.hxx \ + src/PlaylistDatabase.cxx \ + src/IdTable.hxx \ + src/Queue.cxx src/Queue.hxx \ + src/QueuePrint.cxx src/QueuePrint.hxx \ + src/QueueSave.cxx src/QueueSave.hxx \ + src/ReplayGainConfig.cxx \ + src/ReplayGainInfo.cxx \ + src/SignalHandlers.cxx src/SignalHandlers.hxx \ + src/Song.cxx \ + src/SongUpdate.cxx \ + src/SongPrint.cxx src/SongPrint.hxx \ + src/SongSave.cxx src/SongSave.hxx \ src/resolver.c src/resolver.h \ - src/socket_util.c \ - src/state_file.c \ - src/stats.c \ - src/tag.c \ - src/tag_pool.c \ - src/tag_print.c \ - src/tag_save.c \ + src/SocketUtil.cxx src/SocketUtil.hxx \ + src/StateFile.cxx src/StateFile.hxx \ + src/Stats.cxx \ + src/Tag.cxx \ + src/TagNames.c \ + src/TagPool.cxx src/TagPool.hxx \ + src/TagPrint.cxx src/TagPrint.hxx \ + src/TagSave.cxx src/TagSave.hxx \ src/tag_handler.c src/tag_handler.h \ - src/tag_file.c src/tag_file.h \ + src/TagFile.cxx src/TagFile.hxx \ src/tokenizer.c \ - src/text_file.c \ + src/TextFile.cxx src/TextFile.hxx \ src/text_input_stream.c \ - src/strset.c \ src/uri.c \ src/utils.c \ src/string_util.c \ - src/volume.c \ - src/locate.c \ - src/stored_playlist.c \ + src/Volume.cxx src/Volume.hxx \ + src/SongFilter.cxx src/SongFilter.hxx \ + src/SongPointer.hxx \ + src/PlaylistFile.cxx src/PlaylistFile.hxx \ src/timer.c # @@ -371,51 +271,72 @@ endif if ENABLE_DESPOTIFY src_mpd_SOURCES += \ - src/despotify_utils.c + src/DespotifyUtils.cxx src/DespotifyUtils.hxx endif if ENABLE_INOTIFY src_mpd_SOURCES += \ - src/inotify_source.c \ - src/inotify_queue.c \ - src/inotify_update.c + src/InotifySource.cxx src/InotifySource.hxx \ + src/InotifyQueue.cxx src/InotifyQueue.hxx \ + src/InotifyUpdate.cxx src/InotifyUpdate.hxx endif if ENABLE_SQLITE src_mpd_SOURCES += \ - src/sticker.c \ - src/sticker_print.c \ - src/song_sticker.c + src/StickerCommands.cxx src/StickerCommands.hxx \ + src/StickerDatabase.cxx src/StickerDatabase.hxx \ + src/StickerPrint.cxx src/StickerPrint.hxx \ + src/SongSticker.cxx src/SongSticker.hxx endif # Generic utility library libutil_a_SOURCES = \ + src/util/Manual.hxx \ + src/util/RefCount.hxx \ + src/util/fifo_buffer.c src/util/fifo_buffer.h \ + src/util/growing_fifo.c src/util/growing_fifo.h \ + src/util/LazyRandomEngine.cxx src/util/LazyRandomEngine.hxx \ + src/util/SliceBuffer.hxx \ + src/util/HugeAllocator.cxx src/util/HugeAllocator.hxx \ + src/util/PeakBuffer.cxx src/util/PeakBuffer.hxx \ src/util/list.h \ src/util/list_sort.c src/util/list_sort.h \ src/util/byte_reverse.c src/util/byte_reverse.h \ src/util/bit_reverse.c src/util/bit_reverse.h +# Event loop library + +libevent_a_SOURCES = \ + src/event/WakeFD.cxx src/event/WakeFD.hxx \ + src/event/TimeoutMonitor.hxx src/event/TimeoutMonitor.cxx \ + src/event/SocketMonitor.cxx src/event/SocketMonitor.hxx \ + src/event/BufferedSocket.cxx src/event/BufferedSocket.hxx \ + src/event/FullyBufferedSocket.cxx src/event/FullyBufferedSocket.hxx \ + src/event/MultiSocketMonitor.cxx src/event/MultiSocketMonitor.hxx \ + src/event/ServerSocket.cxx src/event/ServerSocket.hxx \ + src/event/Loop.hxx + # PCM library libpcm_a_SOURCES = \ src/pcm_buffer.c src/pcm_buffer.h \ src/pcm_export.c src/pcm_export.h \ - src/pcm_convert.c src/pcm_convert.h \ + src/PcmConvert.cxx src/PcmConvert.hxx \ src/dsd2pcm/dsd2pcm.c src/dsd2pcm/dsd2pcm.h \ src/pcm_dsd.c src/pcm_dsd.h \ src/pcm_dsd_usb.c src/pcm_dsd_usb.h \ - src/pcm_volume.c src/pcm_volume.h \ - src/pcm_mix.c src/pcm_mix.h \ - src/pcm_channels.c src/pcm_channels.h \ + src/PcmVolume.cxx src/PcmVolume.hxx \ + src/PcmMix.cxx src/PcmMix.hxx \ + src/PcmChannels.cxx src/PcmChannels.hxx \ src/pcm_pack.c src/pcm_pack.h \ - src/pcm_format.c src/pcm_format.h \ + src/PcmFormat.cxx src/PcmFormat.hxx \ src/pcm_resample.c src/pcm_resample.h \ src/pcm_resample_fallback.c \ src/pcm_resample_internal.h \ - src/pcm_dither.c src/pcm_dither.h \ - src/pcm_prng.h \ - src/pcm_utils.h + src/PcmDither.cxx src/PcmDither.hxx \ + src/PcmPrng.hxx \ + src/PcmUtils.hxx libpcm_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(SAMPLERATE_CFLAGS) @@ -427,6 +348,29 @@ if HAVE_LIBSAMPLERATE libpcm_a_SOURCES += src/pcm_resample_libsamplerate.c endif +# File system library + +libfs_a_SOURCES = \ + src/fs/Path.cxx src/fs/Path.hxx \ + src/fs/FileSystem.cxx src/fs/FileSystem.hxx \ + src/fs/DirectoryReader.hxx + +# database plugins + +libdb_plugins_a_SOURCES = \ + src/DatabaseRegistry.cxx src/DatabaseRegistry.hxx \ + src/DatabaseHelpers.cxx src/DatabaseHelpers.hxx \ + src/db/SimpleDatabasePlugin.cxx src/db/SimpleDatabasePlugin.hxx + +if HAVE_LIBMPDCLIENT +libdb_plugins_a_SOURCES += \ + src/db/ProxyDatabasePlugin.cxx src/db/ProxyDatabasePlugin.hxx +endif + +DB_LIBS = \ + libdb_plugins.a \ + $(LIBMPDCLIENT_LIBS) + # archive plugins if ENABLE_ARCHIVE @@ -434,13 +378,15 @@ if ENABLE_ARCHIVE noinst_LIBRARIES += libarchive.a src_mpd_SOURCES += \ - src/update_archive.c src/update_archive.h + src/UpdateArchive.cxx src/UpdateArchive.hxx libarchive_a_SOURCES = \ - src/archive_api.c \ - src/archive_list.c \ - src/archive_plugin.c \ - src/input/archive_input_plugin.c + src/ArchiveLookup.cxx src/ArchiveLookup.hxx \ + src/ArchiveList.cxx src/ArchiveList.hxx \ + src/ArchivePlugin.cxx src/ArchivePlugin.hxx \ + src/ArchiveVisitor.hxx \ + src/ArchiveFile.hxx \ + src/input/ArchiveInputPlugin.cxx src/input/ArchiveInputPlugin.hxx libarchive_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(BZ2_CFLAGS) \ $(ISO9660_CFLAGS) \ @@ -453,21 +399,37 @@ ARCHIVE_LIBS = \ $(ZZIP_LIBS) if HAVE_BZ2 -libarchive_a_SOURCES += src/archive/bz2_archive_plugin.c +libarchive_a_SOURCES += \ + src/archive/Bzip2ArchivePlugin.cxx \ + src/archive/Bzip2ArchivePlugin.hxx endif if HAVE_ZZIP -libarchive_a_SOURCES += src/archive/zzip_archive_plugin.c +libarchive_a_SOURCES += \ + src/archive/ZzipArchivePlugin.cxx \ + src/archive/ZzipArchivePlugin.hxx endif if HAVE_ISO9660 -libarchive_a_SOURCES += src/archive/iso9660_archive_plugin.c +libarchive_a_SOURCES += \ + src/archive/Iso9660ArchivePlugin.cxx \ + src/archive/Iso9660ArchivePlugin.hxx endif else ARCHIVE_LIBS = endif +# configuration library + +libconf_a_SOURCES = \ + src/ConfigData.cxx src/ConfigData.hxx \ + src/ConfigParser.cxx src/ConfigParser.hxx \ + src/ConfigGlobal.cxx src/ConfigGlobal.hxx \ + src/ConfigFile.cxx src/ConfigFile.hxx \ + src/ConfigTemplates.cxx src/ConfigTemplates.hxx \ + src/ConfigQuark.hxx \ + src/ConfigOption.hxx # tag plugins @@ -501,7 +463,7 @@ libdecoder_plugins_a_SOURCES = \ src/decoder/dsdlib.h \ src/decoder_buffer.c \ src/decoder_plugin.c \ - src/decoder_list.c + src/DecoderList.cxx src/DecoderList.hxx libdecoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(VORBIS_CFLAGS) $(TREMOR_CFLAGS) \ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \ @@ -515,8 +477,10 @@ libdecoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(WAVPACK_CFLAGS) \ $(MAD_CFLAGS) \ $(MPG123_CFLAGS) \ + $(OPUS_CFLAGS) \ $(FFMPEG_CFLAGS) \ $(MPCDEC_CFLAGS) \ + $(ADPLUG_CFLAGS) \ $(FAAD_CFLAGS) DECODER_LIBS = \ @@ -532,9 +496,10 @@ DECODER_LIBS = \ $(WAVPACK_LIBS) \ $(MAD_LIBS) \ $(MPG123_LIBS) \ - $(MP4FF_LIBS) \ + $(OPUS_LIBS) \ $(FFMPEG_LIBS) \ $(MPCDEC_LIBS) \ + $(ADPLUG_LIBS) \ $(FAAD_LIBS) DECODER_SRC = @@ -551,38 +516,57 @@ if HAVE_MPCDEC libdecoder_plugins_a_SOURCES += src/decoder/mpcdec_decoder_plugin.c endif -if HAVE_WAVPACK -libdecoder_plugins_a_SOURCES += src/decoder/wavpack_decoder_plugin.c +if HAVE_OPUS +libdecoder_plugins_a_SOURCES += \ + src/decoder/OggUtil.cxx \ + src/decoder/OggUtil.hxx \ + src/decoder/OggFind.cxx src/decoder/OggFind.hxx \ + src/decoder/OpusReader.hxx \ + src/decoder/OpusHead.hxx \ + src/decoder/OpusHead.cxx \ + src/decoder/OpusTags.cxx \ + src/decoder/OpusTags.hxx \ + src/decoder/OpusDecoderPlugin.cxx \ + src/decoder/OpusDecoderPlugin.h endif -if HAVE_FAAD -libdecoder_plugins_a_SOURCES += src/decoder/faad_decoder_plugin.c +if HAVE_WAVPACK +libdecoder_plugins_a_SOURCES += \ + src/decoder/WavpackDecoderPlugin.cxx \ + src/decoder/WavpackDecoderPlugin.hxx endif -if HAVE_MP4 -libdecoder_plugins_a_SOURCES += src/decoder/mp4ff_decoder_plugin.c +if HAVE_ADPLUG +libdecoder_plugins_a_SOURCES += \ + src/decoder/AdPlugDecoderPlugin.cxx \ + src/decoder/AdPlugDecoderPlugin.h endif -if HAVE_OGG_COMMON -libdecoder_plugins_a_SOURCES += src/decoder/_ogg_common.c +if HAVE_FAAD +libdecoder_plugins_a_SOURCES += src/decoder/faad_decoder_plugin.c endif -if HAVE_FLAC_COMMON +if HAVE_XIPH libdecoder_plugins_a_SOURCES += \ - src/decoder/flac_metadata.c \ - src/decoder/flac_pcm.c \ - src/decoder/_flac_common.c + src/decoder/XiphTags.c src/decoder/XiphTags.h \ + src/decoder/OggCodec.cxx src/decoder/OggCodec.hxx endif if ENABLE_VORBIS_DECODER libdecoder_plugins_a_SOURCES += \ - src/decoder/vorbis_comments.c \ - src/decoder/vorbis_comments.h \ - src/decoder/vorbis_decoder_plugin.c + src/decoder/VorbisComments.cxx src/decoder/VorbisComments.hxx \ + src/decoder/VorbisDecoderPlugin.cxx src/decoder/VorbisDecoderPlugin.h endif if HAVE_FLAC -libdecoder_plugins_a_SOURCES += src/decoder/flac_decoder_plugin.c +libdecoder_plugins_a_SOURCES += \ + src/decoder/FLACInput.cxx src/decoder/FLACInput.hxx \ + src/decoder/FLACIOHandle.cxx src/decoder/FLACIOHandle.hxx \ + src/decoder/FLACMetaData.cxx src/decoder/FLACMetaData.hxx \ + src/decoder/FLAC_PCM.cxx src/decoder/FLAC_PCM.hxx \ + src/decoder/FLACCommon.cxx src/decoder/FLACCommon.hxx \ + src/decoder/FLACDecoderPlugin.cxx \ + src/decoder/FLACDecoderPlugin.h endif if HAVE_AUDIOFILE @@ -603,7 +587,6 @@ endif if ENABLE_SIDPLAY libdecoder_plugins_a_SOURCES += src/decoder/sidplay_decoder_plugin.cxx -DECODER_SRC += src/dummy.cxx endif if ENABLE_FLUIDSYNTH @@ -616,9 +599,10 @@ endif if HAVE_FFMPEG libdecoder_plugins_a_SOURCES += \ - src/decoder/ffmpeg_metadata.c \ - src/decoder/ffmpeg_metadata.h \ - src/decoder/ffmpeg_decoder_plugin.c + src/decoder/FfmpegMetaData.cxx \ + src/decoder/FfmpegMetaData.hxx \ + src/decoder/FfmpegDecoderPlugin.cxx \ + src/decoder/FfmpegDecoderPlugin.hxx endif if ENABLE_SNDFILE @@ -639,6 +623,7 @@ libencoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(LAME_CFLAGS) \ $(TWOLAME_CFLAGS) \ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \ + $(OPUS_CFLAGS) \ $(VORBISENC_CFLAGS) ENCODER_LIBS = \ @@ -646,19 +631,28 @@ ENCODER_LIBS = \ $(LAME_LIBS) \ $(TWOLAME_LIBS) \ $(FLAC_LIBS) \ + $(OPUS_LIBS) \ $(VORBISENC_LIBS) -libencoder_plugins_a_SOURCES = - -libencoder_plugins_a_SOURCES += src/encoder_list.c -libencoder_plugins_a_SOURCES += src/encoder/null_encoder.c +libencoder_plugins_a_SOURCES = \ + src/encoder/OggStream.hxx \ + src/encoder/null_encoder.c \ + src/encoder_list.c if ENABLE_WAVE_ENCODER libencoder_plugins_a_SOURCES += src/encoder/wave_encoder.c endif if ENABLE_VORBIS_ENCODER -libencoder_plugins_a_SOURCES += src/encoder/vorbis_encoder.c +libencoder_plugins_a_SOURCES += \ + src/encoder/VorbisEncoderPlugin.cxx \ + src/encoder/VorbisEncoderPlugin.hxx +endif + +if HAVE_OPUS +libencoder_plugins_a_SOURCES += \ + src/encoder/OpusEncoderPlugin.cxx \ + src/encoder/OpusEncoderPlugin.hxx endif if ENABLE_LAME_ENCODER @@ -679,14 +673,16 @@ endif if HAVE_ZEROCONF -src_mpd_SOURCES += src/zeroconf.c +src_mpd_SOURCES += \ + src/ZeroconfInternal.hxx \ + src/ZeroconfGlue.cxx src/ZeroconfGlue.hxx if HAVE_AVAHI -src_mpd_SOURCES += src/zeroconf-avahi.c +src_mpd_SOURCES += src/ZeroconfAvahi.cxx src/ZeroconfAvahi.hxx endif if HAVE_BONJOUR -src_mpd_SOURCES += src/zeroconf-bonjour.c +src_mpd_SOURCES += src/ZeroconfBonjour.cxx src/ZeroconfBonjour.hxx endif endif @@ -695,12 +691,13 @@ endif # libinput_a_SOURCES = \ - src/input_init.c \ - src/input_registry.c \ - src/input_stream.c \ - src/input_internal.c src/input_internal.h \ - src/input/rewind_input_plugin.c \ - src/input/file_input_plugin.c + src/InputInit.cxx src/InputInit.hxx \ + src/InputRegistry.cxx src/InputRegistry.hxx \ + src/InputStream.cxx src/InputStream.hxx \ + src/InputPlugin.hxx \ + src/InputInternal.cxx src/InputInternal.hxx \ + src/input/RewindInputPlugin.cxx src/input/RewindInputPlugin.hxx \ + src/input/FileInputPlugin.cxx src/input/FileInputPlugin.hxx libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(CURL_CFLAGS) \ @@ -720,30 +717,36 @@ INPUT_LIBS = \ $(MMS_LIBS) if ENABLE_CURL -libinput_a_SOURCES += src/input/curl_input_plugin.c \ - src/icy_metadata.c +libinput_a_SOURCES += \ + src/input/CurlInputPlugin.cxx src/input/CurlInputPlugin.hxx \ + src/IcyMetaDataParser.cxx src/IcyMetaDataParser.hxx endif if ENABLE_SOUP libinput_a_SOURCES += \ - src/input/soup_input_plugin.c \ - src/input/soup_input_plugin.h + src/input/SoupInputPlugin.cxx src/input/SoupInputPlugin.hxx endif if ENABLE_CDIO_PARANOIA -libinput_a_SOURCES += src/input/cdio_paranoia_input_plugin.c +libinput_a_SOURCES += \ + src/input/CdioParanoiaInputPlugin.cxx \ + src/input/CdioParanoiaInputPlugin.hxx endif if HAVE_FFMPEG -libinput_a_SOURCES += src/input/ffmpeg_input_plugin.c +libinput_a_SOURCES += \ + src/input/FfmpegInputPlugin.cxx src/input/FfmpegInputPlugin.hxx endif if ENABLE_MMS -libinput_a_SOURCES += src/input/mms_input_plugin.c +libinput_a_SOURCES += \ + src/input/MmsInputPlugin.cxx src/input/MmsInputPlugin.hxx endif if ENABLE_DESPOTIFY -libinput_a_SOURCES += src/input/despotify_input_plugin.c +libinput_a_SOURCES += \ + src/input/DespotifyInputPlugin.cxx \ + src/input/DespotifyInputPlugin.hxx endif @@ -770,47 +773,51 @@ OUTPUT_LIBS = \ $(SHOUT_LIBS) OUTPUT_API_SRC = \ - src/output_list.c \ - src/output_all.c \ - src/output_thread.c \ - src/output_control.c \ - src/output_state.c \ - src/output_print.c \ - src/output_command.c \ - src/output_plugin.c src/output_plugin.h \ - src/output_finish.c \ - src/output_init.c + src/OutputList.cxx src/OutputList.hxx \ + src/OutputAll.cxx src/OutputAll.hxx \ + src/OutputThread.cxx src/OutputThread.hxx \ + src/OutputError.hxx \ + src/OutputControl.cxx src/OutputControl.hxx \ + src/OutputState.cxx src/OutputState.hxx \ + src/OutputPrint.cxx src/OutputPrint.hxx \ + src/OutputCommand.cxx src/OutputCommand.hxx \ + src/OutputPlugin.cxx src/output_plugin.h \ + src/OutputFinish.cxx \ + src/OutputInit.cxx liboutput_plugins_a_SOURCES = \ - src/output/null_output_plugin.h \ - src/output/null_output_plugin.c + src/output/NullOutputPlugin.cxx \ + src/output/NullOutputPlugin.hxx MIXER_LIBS = \ libmixer_plugins.a \ $(PULSE_LIBS) MIXER_API_SRC = \ - src/mixer_control.c \ - src/mixer_type.c \ - src/mixer_all.c \ - src/mixer_api.c + src/MixerList.hxx \ + src/MixerControl.cxx src/MixerControl.hxx \ + src/MixerType.cxx src/MixerType.hxx \ + src/MixerAll.cxx src/MixerAll.hxx \ + src/MixerInternal.cxx src/MixerInternal.hxx libmixer_plugins_a_SOURCES = \ - src/mixer/software_mixer_plugin.c + src/mixer/SoftwareMixerPlugin.cxx \ + src/mixer/SoftwareMixerPlugin.hxx libmixer_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(ALSA_CFLAGS) \ $(PULSE_CFLAGS) if HAVE_ALSA liboutput_plugins_a_SOURCES += \ - src/output/alsa_output_plugin.c src/output/alsa_output_plugin.h -libmixer_plugins_a_SOURCES += src/mixer/alsa_mixer_plugin.c + src/output/AlsaOutputPlugin.cxx \ + src/output/AlsaOutputPlugin.hxx +libmixer_plugins_a_SOURCES += src/mixer/AlsaMixerPlugin.cxx endif if HAVE_ROAR liboutput_plugins_a_SOURCES += \ - src/output/roar_output_plugin.c src/output/roar_output_plugin.h -libmixer_plugins_a_SOURCES += src/mixer/roar_mixer_plugin.c + src/output/RoarOutputPlugin.cxx src/output/RoarOutputPlugin.hxx +libmixer_plugins_a_SOURCES += src/mixer/RoarMixerPlugin.cxx endif if ENABLE_FFADO_OUTPUT @@ -845,8 +852,9 @@ endif if HAVE_OSS liboutput_plugins_a_SOURCES += \ - src/output/oss_output_plugin.c src/output/oss_output_plugin.h -libmixer_plugins_a_SOURCES += src/mixer/oss_mixer_plugin.c + src/output/OssOutputPlugin.cxx \ + src/output/OssOutputPlugin.hxx +libmixer_plugins_a_SOURCES += src/mixer/OssMixerPlugin.cxx endif if HAVE_OPENAL @@ -856,13 +864,15 @@ endif if HAVE_OSX liboutput_plugins_a_SOURCES += \ - src/output/osx_output_plugin.c src/output/osx_output_plugin.h + src/output/OSXOutputPlugin.cxx \ + src/output/OSXOutputPlugin.hxx endif if HAVE_PULSE liboutput_plugins_a_SOURCES += \ src/output/pulse_output_plugin.c src/output/pulse_output_plugin.h -libmixer_plugins_a_SOURCES += src/mixer/pulse_mixer_plugin.c +libmixer_plugins_a_SOURCES += \ + src/mixer/PulseMixerPlugin.cxx src/mixer/PulseMixerPlugin.h endif if HAVE_SHOUT @@ -877,9 +887,10 @@ endif if ENABLE_HTTPD_OUTPUT liboutput_plugins_a_SOURCES += \ - src/icy_server.c \ - src/output/httpd_client.c \ - src/output/httpd_output_plugin.c src/output/httpd_output_plugin.h + src/IcyMetaDataServer.cxx src/IcyMetaDataServer.hxx \ + src/output/HttpdInternal.hxx \ + src/output/HttpdClient.cxx src/output/HttpdClient.hxx \ + src/output/HttpdOutputPlugin.cxx src/output/HttpdOutputPlugin.hxx endif if ENABLE_SOLARIS_OUTPUT @@ -889,8 +900,8 @@ endif if ENABLE_WINMM_OUTPUT liboutput_plugins_a_SOURCES += \ - src/output/winmm_output_plugin.c src/output/winmm_output_plugin.h -libmixer_plugins_a_SOURCES += src/mixer/winmm_mixer_plugin.c + src/output/WinmmOutputPlugin.cxx src/output/WinmmOutputPlugin.hxx +libmixer_plugins_a_SOURCES += src/mixer/WinmmMixerPlugin.cxx endif @@ -899,16 +910,26 @@ endif # libplaylist_plugins_a_SOURCES = \ - src/playlist/extm3u_playlist_plugin.c \ - src/playlist/m3u_playlist_plugin.c \ - src/playlist/pls_playlist_plugin.c \ - src/playlist/xspf_playlist_plugin.c \ - src/playlist/asx_playlist_plugin.c \ - src/playlist/rss_playlist_plugin.c \ - src/playlist/cue_playlist_plugin.c \ - src/playlist/embcue_playlist_plugin.c \ - src/playlist/embcue_playlist_plugin.h \ - src/playlist_list.c + src/PlaylistPlugin.hxx \ + src/playlist/MemoryPlaylistProvider.cxx \ + src/playlist/MemoryPlaylistProvider.hxx \ + src/playlist/ExtM3uPlaylistPlugin.cxx \ + src/playlist/ExtM3uPlaylistPlugin.hxx \ + src/playlist/M3uPlaylistPlugin.cxx \ + src/playlist/M3uPlaylistPlugin.hxx \ + src/playlist/PlsPlaylistPlugin.cxx \ + src/playlist/PlsPlaylistPlugin.hxx \ + src/playlist/XspfPlaylistPlugin.cxx \ + src/playlist/XspfPlaylistPlugin.hxx \ + src/playlist/AsxPlaylistPlugin.cxx \ + src/playlist/AsxPlaylistPlugin.hxx \ + src/playlist/RssPlaylistPlugin.cxx \ + src/playlist/RssPlaylistPlugin.hxx \ + src/playlist/CuePlaylistPlugin.cxx \ + src/playlist/CuePlaylistPlugin.hxx \ + src/playlist/EmbeddedCuePlaylistPlugin.cxx \ + src/playlist/EmbeddedCuePlaylistPlugin.hxx \ + src/PlaylistRegistry.cxx src/PlaylistRegistry.hxx libplaylist_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(YAJL_CFLAGS) \ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) @@ -918,17 +939,21 @@ PLAYLIST_LIBS = \ $(FLAC_LIBS) if ENABLE_LASTFM -libplaylist_plugins_a_SOURCES += src/playlist/lastfm_playlist_plugin.c +libplaylist_plugins_a_SOURCES += \ + src/playlist/LastFMPlaylistPlugin.cxx \ + src/playlist/LastFMPlaylistPlugin.hxx endif if ENABLE_DESPOTIFY -libplaylist_plugins_a_SOURCES += src/playlist/despotify_playlist_plugin.c +libplaylist_plugins_a_SOURCES += \ + src/playlist/DespotifyPlaylistPlugin.cxx \ + src/playlist/DespotifyPlaylistPlugin.hxx endif if ENABLE_SOUNDCLOUD libplaylist_plugins_a_SOURCES += \ - src/playlist/soundcloud_playlist_plugin.h \ - src/playlist/soundcloud_playlist_plugin.c + src/playlist/SoundCloudPlaylistPlugin.cxx \ + src/playlist/SoundCloudPlaylistPlugin.hxx PLAYLIST_LIBS += $(YAJL_LIBS) endif @@ -937,14 +962,19 @@ endif # libfilter_plugins_a_SOURCES = \ - src/filter/null_filter_plugin.c \ - src/filter/chain_filter_plugin.c \ - src/filter/autoconvert_filter_plugin.c \ - src/filter/convert_filter_plugin.c \ - src/filter/route_filter_plugin.c \ - src/filter/normalize_filter_plugin.c \ - src/filter/replay_gain_filter_plugin.c \ - src/filter/volume_filter_plugin.c + src/filter/NullFilterPlugin.cxx \ + src/filter/ChainFilterPlugin.cxx \ + src/filter/ChainFilterPlugin.hxx \ + src/filter/AutoConvertFilterPlugin.cxx \ + src/filter/AutoConvertFilterPlugin.hxx \ + src/filter/ConvertFilterPlugin.cxx \ + src/filter/ConvertFilterPlugin.hxx \ + src/filter/RouteFilterPlugin.cxx \ + src/filter/NormalizeFilterPlugin.cxx \ + src/filter/ReplayGainFilterPlugin.cxx \ + src/filter/ReplayGainFilterPlugin.hxx \ + src/filter/VolumeFilterPlugin.cxx \ + src/filter/VolumeFilterPlugin.hxx FILTER_LIBS = \ libfilter_plugins.a \ @@ -998,6 +1028,7 @@ noinst_PROGRAMS = \ $(C_TESTS) \ test/read_conf \ test/run_resolver \ + test/DumpDatabase \ test/run_input \ test/dump_text_file \ test/dump_playlist \ @@ -1009,6 +1040,10 @@ noinst_PROGRAMS = \ test/run_normalize \ test/software_volume +if ENABLE_ARCHIVE +noinst_PROGRAMS += test/visit_archive +endif + if HAVE_ID3TAG noinst_PROGRAMS += test/dump_rva2 endif @@ -1019,36 +1054,88 @@ noinst_PROGRAMS += test/read_mixer endif test_read_conf_LDADD = \ + libconf.a \ + libfs.a \ $(GLIB_LIBS) -test_read_conf_SOURCES = test/read_conf.c \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c +test_read_conf_SOURCES = test/read_conf.cxx \ + src/tokenizer.c src/utils.c src/string_util.c test_run_resolver_LDADD = \ $(GLIB_LIBS) test_run_resolver_SOURCES = test/run_resolver.c \ src/resolver.c +test_DumpDatabase_LDADD = \ + $(DB_LIBS) \ + libconf.a \ + libutil.a \ + libfs.a \ + $(GLIB_LIBS) +test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \ + src/DatabaseRegistry.cxx \ + src/DatabaseSelection.cxx \ + src/Directory.cxx src/DirectorySave.cxx \ + src/PlaylistVector.cxx src/PlaylistDatabase.cxx \ + src/DatabaseLock.cxx src/DatabaseSave.cxx \ + src/Song.cxx src/song_sort.c src/SongSave.cxx \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx src/TagSave.cxx \ + src/SongFilter.cxx \ + src/TextFile.cxx \ + src/tokenizer.c src/utils.c src/string_util.c + test_run_input_LDADD = \ $(INPUT_LIBS) \ $(ARCHIVE_LIBS) \ + libconf.a \ + libevent.a \ + libfs.a \ $(GLIB_LIBS) -test_run_input_SOURCES = test/run_input.c \ +test_run_input_SOURCES = test/run_input.cxx \ test/stdbin.h \ - src/io_thread.c src/io_thread.h \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ - src/tag.c src/tag_pool.c src/tag_save.c \ + src/IOThread.cxx \ + src/tokenizer.c src/utils.c src/string_util.c\ + src/Tag.cxx src/TagNames.c src/TagPool.cxx src/TagSave.cxx \ + src/uri.c \ src/fd_util.c +if ENABLE_ARCHIVE + +test_visit_archive_LDADD = \ + $(INPUT_LIBS) \ + $(ARCHIVE_LIBS) \ + libconf.a \ + libevent.a \ + libfs.a \ + $(GLIB_LIBS) +test_visit_archive_SOURCES = test/visit_archive.cxx \ + src/IOThread.cxx \ + src/InputStream.cxx \ + src/tokenizer.c src/utils.c src/string_util.c \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx \ + src/uri.c \ + src/fd_util.c + +if ENABLE_DESPOTIFY +test_visit_archive_SOURCES += src/DespotifyUtils.cxx +endif + +endif + test_dump_text_file_LDADD = \ $(INPUT_LIBS) \ $(ARCHIVE_LIBS) \ + libconf.a \ + libevent.a \ + libfs.a \ + libutil.a \ $(GLIB_LIBS) -test_dump_text_file_SOURCES = test/dump_text_file.c \ +test_dump_text_file_SOURCES = test/dump_text_file.cxx \ test/stdbin.h \ - src/io_thread.c src/io_thread.h \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ - src/tag.c src/tag_pool.c \ - src/text_input_stream.c src/fifo_buffer.c \ + src/IOThread.cxx \ + src/tokenizer.c src/utils.c src/string_util.c\ + src/Tag.cxx src/TagNames.c src/TagPool.cxx \ + src/text_input_stream.c \ + src/uri.c \ src/fd_util.c test_dump_playlist_LDADD = \ @@ -1058,24 +1145,27 @@ test_dump_playlist_LDADD = \ $(ARCHIVE_LIBS) \ $(DECODER_LIBS) \ $(TAG_LIBS) \ + libconf.a \ + libevent.a \ + libfs.a \ libutil.a \ $(GLIB_LIBS) -test_dump_playlist_SOURCES = test/dump_playlist.c \ +test_dump_playlist_SOURCES = test/dump_playlist.cxx \ $(DECODER_SRC) \ - src/io_thread.c src/io_thread.h \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ + src/IOThread.cxx \ + src/tokenizer.c src/utils.c src/string_util.c\ src/uri.c \ - src/song.c src/tag.c src/tag_pool.c src/tag_save.c \ - src/tag_handler.c src/tag_file.c \ + src/Song.cxx src/Tag.cxx src/TagNames.c src/TagPool.cxx src/TagSave.cxx \ + src/tag_handler.c src/TagFile.cxx \ src/audio_check.c src/pcm_buffer.c \ - src/text_input_stream.c src/fifo_buffer.c \ + src/text_input_stream.c \ src/cue/cue_parser.c src/cue/cue_parser.h \ src/fd_util.c if HAVE_FLAC test_dump_playlist_SOURCES += \ - src/replay_gain_info.c \ - src/decoder/flac_metadata.c + src/ReplayGainInfo.cxx \ + src/decoder/FLACMetaData.cxx endif test_run_decoder_LDADD = \ @@ -1084,14 +1174,17 @@ test_run_decoder_LDADD = \ $(INPUT_LIBS) \ $(ARCHIVE_LIBS) \ $(TAG_LIBS) \ + libconf.a \ + libevent.a \ + libfs.a \ libutil.a \ $(GLIB_LIBS) -test_run_decoder_SOURCES = test/run_decoder.c \ +test_run_decoder_SOURCES = test/run_decoder.cxx \ test/stdbin.h \ - src/io_thread.c src/io_thread.h \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ - src/tag.c src/tag_pool.c src/tag_handler.c \ - src/replay_gain_info.c \ + src/IOThread.cxx \ + src/tokenizer.c src/utils.c src/string_util.c \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx src/tag_handler.c \ + src/ReplayGainInfo.cxx \ src/uri.c \ src/fd_util.c \ src/audio_check.c \ @@ -1107,13 +1200,16 @@ test_read_tags_LDADD = \ $(INPUT_LIBS) \ $(ARCHIVE_LIBS) \ $(TAG_LIBS) \ + libconf.a \ + libevent.a \ + libfs.a \ libutil.a \ $(GLIB_LIBS) -test_read_tags_SOURCES = test/read_tags.c \ - src/io_thread.c src/io_thread.h \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ - src/tag.c src/tag_pool.c src/tag_handler.c \ - src/replay_gain_info.c \ +test_read_tags_SOURCES = test/read_tags.cxx \ + src/IOThread.cxx \ + src/tokenizer.c src/utils.c src/string_util.c \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx src/tag_handler.c \ + src/ReplayGainInfo.cxx \ src/uri.c \ src/fd_util.c \ src/audio_check.c \ @@ -1132,93 +1228,89 @@ endif test_run_filter_LDADD = \ $(FILTER_LIBS) \ + libconf.a \ + libfs.a \ $(GLIB_LIBS) -test_run_filter_SOURCES = test/run_filter.c \ +test_run_filter_SOURCES = test/run_filter.cxx \ + test/FakeReplayGainConfig.cxx \ test/stdbin.h \ - src/filter_plugin.c \ - src/filter_registry.c \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c \ + src/FilterPlugin.cxx src/FilterRegistry.cxx \ + src/tokenizer.c src/utils.c src/string_util.c \ src/audio_check.c \ src/audio_format.c \ - src/audio_parser.c \ - src/replay_gain_config.c \ - src/replay_gain_info.c \ + src/AudioParser.cxx \ + src/ReplayGainInfo.cxx \ src/AudioCompress/compress.c if ENABLE_DESPOTIFY -test_read_tags_SOURCES += \ - src/despotify_utils.c -test_run_input_SOURCES += \ - src/despotify_utils.c -test_dump_text_file_SOURCES += \ - src/despotify_utils.c -test_dump_playlist_SOURCES += \ - src/despotify_utils.c -test_run_decoder_SOURCES += \ - src/despotify_utils.c +test_read_tags_SOURCES += src/DespotifyUtils.cxx +test_run_input_SOURCES += src/DespotifyUtils.cxx +test_dump_text_file_SOURCES += src/DespotifyUtils.cxx +test_dump_playlist_SOURCES += src/DespotifyUtils.cxx +test_run_decoder_SOURCES += src/DespotifyUtils.cxx endif if ENABLE_ENCODER noinst_PROGRAMS += test/run_encoder -test_run_encoder_SOURCES = test/run_encoder.c \ +test_run_encoder_SOURCES = test/run_encoder.cxx \ test/stdbin.h \ - src/fifo_buffer.c src/growing_fifo.c \ - src/conf.c src/tokenizer.c \ - src/utils.c src/string_util.c \ - src/tag.c src/tag_pool.c \ + src/tokenizer.c src/utils.c src/string_util.c \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx \ src/audio_check.c \ src/audio_format.c \ - src/audio_parser.c + src/AudioParser.cxx test_run_encoder_LDADD = \ $(ENCODER_LIBS) \ - libpcm.a \ $(TAG_LIBS) \ + libconf.a \ + libpcm.a \ + libfs.a \ + libutil.a \ $(GLIB_LIBS) endif if ENABLE_VORBIS_ENCODER noinst_PROGRAMS += test/test_vorbis_encoder -test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.c \ +test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.cxx \ test/stdbin.h \ - src/conf.c src/tokenizer.c \ - src/utils.c \ - src/string_util.c \ - src/tag.c src/tag_pool.c \ + src/tokenizer.c src/utils.c src/string_util.c \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx \ src/audio_check.c \ src/audio_format.c \ - src/audio_parser.c \ + src/AudioParser.cxx \ src/pcm_buffer.c \ - src/fifo_buffer.c src/growing_fifo.c \ $(ENCODER_SRC) test_test_vorbis_encoder_CPPFLAGS = $(AM_CPPFLAGS) \ $(ENCODER_CFLAGS) test_test_vorbis_encoder_LDADD = $(MPD_LIBS) \ $(ENCODER_LIBS) \ + libconf.a \ + libfs.a \ + libutil.a \ $(GLIB_LIBS) endif -test_software_volume_SOURCES = test/software_volume.c \ +test_software_volume_SOURCES = test/software_volume.cxx \ test/stdbin.h \ src/audio_check.c \ - src/audio_parser.c + src/AudioParser.cxx test_software_volume_LDADD = \ $(PCM_LIBS) \ $(GLIB_LIBS) -test_run_normalize_SOURCES = test/run_normalize.c \ +test_run_normalize_SOURCES = test/run_normalize.cxx \ test/stdbin.h \ src/audio_check.c \ - src/audio_parser.c \ + src/AudioParser.cxx \ src/AudioCompress/compress.c test_run_normalize_LDADD = \ $(GLIB_LIBS) -test_run_convert_SOURCES = test/run_convert.c \ +test_run_convert_SOURCES = test/run_convert.cxx \ src/dsd2pcm/dsd2pcm.c \ - src/fifo_buffer.c \ src/audio_format.c \ src/audio_check.c \ - src/audio_parser.c + src/AudioParser.cxx test_run_convert_LDADD = \ $(PCM_LIBS) \ libutil.a \ @@ -1229,44 +1321,49 @@ test_run_output_LDADD = $(MPD_LIBS) \ $(ENCODER_LIBS) \ libmixer_plugins.a \ $(FILTER_LIBS) \ + libconf.a \ + libevent.a \ + libfs.a \ libutil.a \ $(GLIB_LIBS) -test_run_output_SOURCES = test/run_output.c \ +test_run_output_SOURCES = test/run_output.cxx \ + test/FakeReplayGainConfig.cxx \ test/stdbin.h \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ - src/io_thread.c src/io_thread.h \ + src/tokenizer.c src/utils.c src/string_util.c \ + src/IOThread.cxx \ src/audio_check.c \ src/audio_format.c \ - src/audio_parser.c \ + src/AudioParser.cxx \ src/timer.c src/clock.c \ - src/tag.c src/tag_pool.c \ - src/fifo_buffer.c src/growing_fifo.c \ - src/page.c \ - src/socket_util.c \ + src/Tag.cxx src/TagNames.c src/TagPool.cxx \ + src/Page.cxx \ + src/SocketUtil.cxx \ src/resolver.c \ - src/output_init.c src/output_finish.c src/output_list.c \ - src/output_plugin.c \ - src/mixer_api.c \ - src/mixer_control.c \ - src/mixer_type.c \ - src/filter_plugin.c \ - src/filter_config.c \ + src/OutputInit.cxx src/OutputFinish.cxx src/OutputList.cxx \ + src/OutputPlugin.cxx \ + src/MixerInternal.cxx \ + src/MixerControl.cxx \ + src/MixerType.cxx \ + src/FilterPlugin.cxx \ + src/FilterConfig.cxx \ src/AudioCompress/compress.c \ - src/replay_gain_info.c \ - src/replay_gain_config.c \ - src/fd_util.c \ - src/server_socket.c + src/ReplayGainInfo.cxx \ + src/fd_util.c test_read_mixer_LDADD = \ libpcm.a \ libmixer_plugins.a \ $(OUTPUT_LIBS) \ + libconf.a \ + libevent.a \ + libfs.a \ $(GLIB_LIBS) -test_read_mixer_SOURCES = test/read_mixer.c \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ - src/mixer_control.c src/mixer_api.c \ - src/filter_plugin.c \ - src/filter/volume_filter_plugin.c \ +test_read_mixer_SOURCES = test/read_mixer.cxx \ + src/tokenizer.c src/utils.c src/string_util.c \ + src/MixerControl.cxx \ + src/MixerInternal.cxx \ + src/FilterPlugin.cxx \ + src/filter/VolumeFilterPlugin.cxx \ src/fd_util.c if ENABLE_BZIP2_TEST @@ -1283,11 +1380,13 @@ endif if ENABLE_INOTIFY noinst_PROGRAMS += test/run_inotify -test_run_inotify_SOURCES = test/run_inotify.c \ +test_run_inotify_SOURCES = test/run_inotify.cxx \ src/fd_util.c \ - src/fifo_buffer.c \ - src/inotify_source.c -test_run_inotify_LDADD = $(GLIB_LIBS) + src/InotifySource.cxx +test_run_inotify_LDADD = \ + libevent.a \ + libutil.a \ + $(GLIB_LIBS) endif test_test_byte_reverse_SOURCES = \ @@ -1297,24 +1396,28 @@ test_test_byte_reverse_LDADD = \ $(GLIB_LIBS) test_test_pcm_SOURCES = \ - test/test_pcm_dither.c \ - test/test_pcm_pack.c \ - test/test_pcm_channels.c \ - test/test_pcm_volume.c \ - test/test_pcm_all.h \ - test/test_pcm_main.c + test/test_pcm_util.hxx \ + test/test_pcm_dither.cxx \ + test/test_pcm_pack.cxx \ + test/test_pcm_channels.cxx \ + test/test_pcm_format.cxx \ + test/test_pcm_volume.cxx \ + test/test_pcm_mix.cxx \ + test/test_pcm_all.hxx \ + test/test_pcm_main.cxx test_test_pcm_LDADD = \ $(PCM_LIBS) \ libutil.a \ $(GLIB_LIBS) test_test_queue_priority_SOURCES = \ - src/queue.c \ - test/test_queue_priority.c + src/Queue.cxx \ + src/fd_util.c \ + test/test_queue_priority.cxx test_test_queue_priority_LDADD = \ + libutil.a \ $(GLIB_LIBS) -if HAVE_CXX noinst_PROGRAMS += src/dsd2pcm/dsd2pcm src_dsd2pcm_dsd2pcm_SOURCES = \ @@ -1322,7 +1425,6 @@ src_dsd2pcm_dsd2pcm_SOURCES = \ src/dsd2pcm/noiseshape.c src/dsd2pcm/noiseshape.h \ src/dsd2pcm/main.cpp src_dsd2pcm_dsd2pcm_LDADD = libutil.a -endif endif @@ -1,3 +1,20 @@ +ver 0.18 (2012/??/??) +* decoder: + - adplug: new decoder plugin using libadplug + - flac: require libFLAC 1.2 or newer + - flac: support FLAC files inside archives + - opus: new decoder plugin for the Opus codec + - vorbis: skip 16 bit quantisation, provide float samples + - mp4ff: obsolete plugin removed +* encoder: + - opus: new encoder plugin for the Opus codec + - vorbis: accept floating point input samples +* output: + - new option "tags" may be used to disable sending tags to output + - alsa: workaround for noise after manual song change +* improved decoder/output error reporting + + ver 0.17.4 (2013/??/??) * protocol: - allow to omit END in ranges (START:END) @@ -7,7 +24,6 @@ ver 0.17.4 (2013/??/??) - implement missing "idle" events on output errors * clock: fix build failure - ver 0.17.3 (2013/01/06) * output: - osx: fix pops during playback diff --git a/configure.ac b/configure.ac index 05f28882..19246980 100644 --- a/configure.ac +++ b/configure.ac @@ -1,13 +1,13 @@ AC_PREREQ(2.60) -AC_INIT(mpd, 0.17.4~git, musicpd-dev-team@lists.sourceforge.net) +AC_INIT(mpd, 0.18~git, musicpd-dev-team@lists.sourceforge.net) VERSION_MAJOR=0 -VERSION_MINOR=17 +VERSION_MINOR=18 VERSION_REVISION=0 VERSION_EXTRA=0 -AC_CONFIG_SRCDIR([src/main.c]) +AC_CONFIG_SRCDIR([src/Main.cxx]) AM_INIT_AUTOMAKE([foreign 1.11 dist-bzip2 subdir-objects]) AM_SILENT_RULES AC_CONFIG_HEADERS(config.h) @@ -23,22 +23,6 @@ AC_PROG_CC_C99 AC_PROG_CXX AC_PROG_RANLIB -HAVE_CXX=yes -if test x$CXX = xg++; then - # CXX=g++ probably means that autoconf hasn't found any C++ - # compiler; to be sure, we check again - AC_PATH_PROG(CXX, $CXX, no) - if test x$CXX = xno; then - # no, we don't have C++ - the following hack is - # required because automake insists on using $(CXX) - # for linking the MPD binary - AC_MSG_NOTICE([Disabling C++ support]) - CXX="$CC" - HAVE_CXX=no - fi -fi -AM_CONDITIONAL(HAVE_CXX, test x$HAVE_CXX = xyes) - AC_PROG_INSTALL AC_PROG_MAKE_SET PKG_PROG_PKG_CONFIG @@ -82,7 +66,8 @@ mingw32* | windows*) src/win/mpd_win32_rc.rc ]) AC_CHECK_TOOL(WINDRES, windres) - AM_CPPFLAGS="$AM_CPPFLAGS -DWINVER=0x0501" + AM_CPPFLAGS="$AM_CPPFLAGS -DWIN32_LEAN_AND_MEAN" + AM_CPPFLAGS="$AM_CPPFLAGS -DWINVER=0x0600 -D_WIN32_WINNT=0x0600" LIBS="$LIBS -lws2_32" HAVE_WINDOWS=1 ;; @@ -126,6 +111,19 @@ if test -z "$prefix" || test "x$prefix" = xNONE; then fi dnl --------------------------------------------------------------------------- +dnl Language Checks +dnl --------------------------------------------------------------------------- + +AC_CXX_COMPILE_STDCXX_0X +if test "$ax_cv_cxx_compile_cxx0x_native" != yes; then + if test "$ax_cv_cxx_compile_cxx0x_gxx" = yes; then + AM_CXXFLAGS="$AM_CXXFLAGS -std=gnu++0x" + elif test "$ax_cv_cxx_compile_cxx0x_cxx" = yes; then + AM_CXXFLAGS="$AM_CXXFLAGS -std=c++0x" + fi +fi + +dnl --------------------------------------------------------------------------- dnl Header/Library Checks dnl --------------------------------------------------------------------------- AC_CHECK_FUNCS(daemon fork) @@ -136,7 +134,9 @@ AC_SEARCH_LIBS([syslog], [bsd socket inet], AC_SEARCH_LIBS([socket], [socket]) AC_SEARCH_LIBS([gethostbyname], [nsl]) -AC_CHECK_FUNCS(pipe2 accept4) +AC_CHECK_FUNCS(pipe2 accept4 eventfd) + +AC_CHECK_FUNCS(strnlen strndup) AC_SEARCH_LIBS([exp], [m],, [AC_MSG_ERROR([exp() not found])]) @@ -147,6 +147,17 @@ AC_CHECK_HEADERS(valgrind/memcheck.h) dnl --------------------------------------------------------------------------- dnl Allow tools to be specifically built dnl --------------------------------------------------------------------------- + +AC_ARG_ENABLE(mpdclient, + AS_HELP_STRING([--enable-libmpdclient], + [enable support for the MPD client]),, + enable_libmpdclient=auto) + +AC_ARG_ENABLE(adplug, + AS_HELP_STRING([--enable-adplug], + [enable the AdPlug decoder plugin (default: auto)]),, + enable_adplug=auto) + AC_ARG_ENABLE(alsa, AS_HELP_STRING([--enable-alsa], [enable ALSA support]),, [enable_alsa=auto]) @@ -326,6 +337,11 @@ AC_ARG_ENABLE(openal, [enable OpenAL support (default: disable)]),, enable_openal=no) +AC_ARG_ENABLE(opus, + AS_HELP_STRING([--enable-opus], + [enable Opus codec support (default: auto)]),, + enable_opus=auto) + AC_ARG_ENABLE(oss, AS_HELP_STRING([--disable-oss], [disable OSS support (default: enable)]),, @@ -456,8 +472,8 @@ AC_ARG_WITH(tremor-includes, dnl --------------------------------------------------------------------------- dnl Mandatory Libraries dnl --------------------------------------------------------------------------- -PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.16 gthread-2.0],, - [AC_MSG_ERROR([GLib 2.16 is required])]) +PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.24 gthread-2.0],, + [AC_MSG_ERROR([GLib 2.24 is required])]) if test x$GCC = xyes; then # suppress warnings in the GLib headers @@ -537,6 +553,15 @@ dnl --------------------------------------------------------------------------- dnl Miscellaneous Libraries dnl --------------------------------------------------------------------------- +dnl -------------------------------- libmpdclient -------------------------------- +MPD_AUTO_PKG(libmpdclient, LIBMPDCLIENT, [libmpdclient >= 2.2], + [MPD client library], [libmpdclient not found]) +if test x$enable_libmpdclient = xyes; then + AC_DEFINE(HAVE_LIBMPDCLIENT, 1, [Define to use libmpdclient]) +fi + +AM_CONDITIONAL(HAVE_LIBMPDCLIENT, test x$enable_libmpdclient = xyes) + dnl --------------------------------- inotify --------------------------------- AC_CHECK_FUNCS(inotify_init inotify_init1) @@ -552,6 +577,27 @@ AM_CONDITIONAL(ENABLE_INOTIFY, test x$enable_inotify = xyes) dnl --------------------------------- libwrap --------------------------------- if test x$enable_libwrap != xno; then AC_CHECK_LIBWRAP(found_libwrap=yes, found_libwrap=no) + + if test x$found_libwrap = xyes; then + dnl See if libwrap is compatible with C++; it is + dnl broken on many systems + AC_MSG_CHECKING(whether libwrap is compatible with C++) + AC_LANG_PUSH([C++]) + AC_COMPILE_IFELSE([AC_LANG_SOURCE([ + #include <tcpd.h> + bool CheckLibWrap(int fd, const char &progname) { + struct request_info req; + request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); + fromhost(&req); + return hosts_access(&req); + } + ])], + AC_MSG_RESULT([yes]), + [found_libwrap=no; AC_MSG_RESULT([no]); + AC_MSG_WARN([Your version of libwrap is broken with C++])]) + AC_LANG_POP + fi + MPD_AUTO_RESULT(libwrap, libwrap, [libwrap not found]) fi @@ -669,7 +715,7 @@ dnl Input Plugins dnl --------------------------------------------------------------------------- dnl ----------------------------------- CURL ---------------------------------- -MPD_AUTO_PKG(curl, CURL, [libcurl], +MPD_AUTO_PKG(curl, CURL, [libcurl >= 7.18], [libcurl HTTP streaming], [libcurl not found]) if test x$enable_curl = xyes; then AC_DEFINE(ENABLE_CURL, 1, [Define when libcurl is used for HTTP streaming]) @@ -806,6 +852,14 @@ dnl --------------------------------------------------------------------------- dnl Decoder Plugins dnl --------------------------------------------------------------------------- +dnl -------------------------------- libadplug -------------------------------- +MPD_AUTO_PKG(adplug, ADPLUG, [adplug], + [AdPlug decoder plugin], [libadplug not found]) +if test x$enable_adplug = xyes; then + AC_DEFINE(HAVE_ADPLUG, 1, [Define to use libadplug]) +fi +AM_CONDITIONAL(HAVE_ADPLUG, test x$enable_adplug = xyes) + dnl -------------------------------- audiofile -------------------------------- MPD_AUTO_PKG(audiofile, AUDIOFILE, [audiofile >= 0.1.7], [audiofile decoder plugin], [libaudiofile not found]) @@ -818,10 +872,9 @@ dnl ----------------------------------- FAAD ---------------------------------- AM_PATH_FAAD() AM_CONDITIONAL(HAVE_FAAD, test x$enable_aac = xyes) -AM_CONDITIONAL(HAVE_MP4, test x$enable_mp4 = xyes) dnl ---------------------------------- ffmpeg --------------------------------- -MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 52.31 libavcodec >= 52.20 libavutil >= 49.15], +MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 53.2 libavcodec >= 53.5 libavutil >= 51.7], [ffmpeg decoder library], [libavformat+libavcodec+libavutil not found]) if test x$enable_ffmpeg = xyes; then @@ -832,7 +885,7 @@ AM_CONDITIONAL(HAVE_FFMPEG, test x$enable_ffmpeg = xyes) dnl ----------------------------------- FLAC ---------------------------------- -MPD_AUTO_PKG(flac, FLAC, [flac >= 1.1], +MPD_AUTO_PKG(flac, FLAC, [flac >= 1.2], [FLAC decoder], [libFLAC not found]) if test x$enable_flac = xyes; then @@ -899,9 +952,6 @@ fi AM_CONDITIONAL(ENABLE_MIKMOD_DECODER, test x$enable_mikmod = xyes) dnl -------------------------------- libmodplug ------------------------------- -found_modplug=$HAVE_CXX -MPD_AUTO_PRE(modplug, [modplug decoder plugin], [No C++ compiler found]) - MPD_AUTO_PKG(modplug, MODPLUG, [libmodplug], [modplug decoder plugin], [libmodplug not found]) @@ -910,6 +960,14 @@ if test x$enable_modplug = xyes; then fi AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes) +dnl -------------------------------- libopus ---------------------------------- +MPD_AUTO_PKG(opus, OPUS, [opus ogg], + [opus decoder plugin], [libopus not found]) +if test x$enable_opus = xyes; then + AC_DEFINE(HAVE_OPUS, 1, [Define to use libopus]) +fi +AM_CONDITIONAL(HAVE_OPUS, test x$enable_opus = xyes) + dnl -------------------------------- libsndfile ------------------------------- dnl See above test, which may disable this. MPD_AUTO_PKG(sndfile, SNDFILE, [sndfile], @@ -1015,9 +1073,6 @@ fi AM_CONDITIONAL(ENABLE_VORBIS_DECODER, test x$enable_vorbis = xyes || test x$enable_tremor = xyes) dnl --------------------------------- sidplay --------------------------------- -found_sidplay=$HAVE_CXX -MPD_AUTO_PRE(sidplay, [sidplay decoder plugin], [No C++ compiler found]) - if test x$enable_sidplay != xno; then # we're not using pkg-config here # because libsidplay2's .pc file requires libtool @@ -1092,9 +1147,9 @@ if test x$enable_mad = xno && test x$enable_mikmod = xno; then test x$enable_modplug = xno && - test x$enable_mp4 = xno && test x$enable_mpc = xno && test x$enable_mpg123 = xno && + test x$enable_opus = xno && test x$enable_sidplay = xno && test x$enable_tremor = xno && test x$enable_vorbis = xno && @@ -1104,11 +1159,8 @@ if AC_MSG_ERROR([No input plugins supported!]) fi -AM_CONDITIONAL(HAVE_OGG_COMMON, - test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_flac = xyes) - -AM_CONDITIONAL(HAVE_FLAC_COMMON, - test x$enable_flac = xyes) +AM_CONDITIONAL(HAVE_XIPH, + test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_flac = xyes || test x$enable_opus = xyes) dnl --------------------------------------------------------------------------- dnl Encoders for Streaming Audio Output Plugins @@ -1196,6 +1248,7 @@ fi dnl --------------------------- encoder plugins test -------------------------- if test x$enable_vorbis_encoder != xno || + test x$enable_opus != xno || test x$enable_lame_encoder != xno || test x$enable_twolame_encoder != xno || test x$enable_flac_encoder != xno || @@ -1499,6 +1552,22 @@ dnl --------------------------------------------------------------------------- dnl ---------------------------------- debug ---------------------------------- if test "x$enable_debug" = xno; then AM_CPPFLAGS="$AM_CPPFLAGS -DNDEBUG" + + AX_APPEND_COMPILE_FLAGS([-ffunction-sections]) + AX_APPEND_COMPILE_FLAGS([-fdata-sections]) + AX_APPEND_COMPILE_FLAGS([-fvisibility=hidden]) + + AC_LANG_PUSH([C++]) + AX_APPEND_COMPILE_FLAGS([-ffunction-sections]) + AX_APPEND_COMPILE_FLAGS([-fdata-sections]) + AX_APPEND_COMPILE_FLAGS([-fvisibility=hidden]) + AX_APPEND_COMPILE_FLAGS([-fno-threadsafe-statics]) + AX_APPEND_COMPILE_FLAGS([-fmerge-all-constants]) + AX_APPEND_COMPILE_FLAGS([-fno-exceptions]) + AX_APPEND_COMPILE_FLAGS([-fno-rtti]) + AC_LANG_POP + + AX_APPEND_LINK_FLAGS([-Wl,--gc-sections]) fi dnl ----------------------------------- GCC ----------------------------------- @@ -1513,6 +1582,17 @@ then AX_APPEND_COMPILE_FLAGS([-Wcast-qual]) AX_APPEND_COMPILE_FLAGS([-Wwrite-strings]) AX_APPEND_COMPILE_FLAGS([-pedantic]) + + AC_LANG_PUSH([C++]) + AX_APPEND_COMPILE_FLAGS([-Wall]) + AX_APPEND_COMPILE_FLAGS([-Wextra]) + AX_APPEND_COMPILE_FLAGS([-Wmissing-declarations]) + AX_APPEND_COMPILE_FLAGS([-Wshadow]) + AX_APPEND_COMPILE_FLAGS([-Wpointer-arith]) + AX_APPEND_COMPILE_FLAGS([-Wcast-qual]) + AX_APPEND_COMPILE_FLAGS([-Wwrite-strings]) + AX_APPEND_COMPILE_FLAGS([-Wsign-compare]) + AC_LANG_POP fi dnl ---------------------------- warnings as errors --------------------------- @@ -1545,20 +1625,21 @@ results(un,[UNIX Domain Sockets]) printf '\nFile format support:\n\t' results(aac, [AAC]) +results(adplug, [AdPlug]) results(sidplay, [C64 SID]) results(ffmpeg, [FFMPEG]) results(flac, [FLAC]) results(fluidsynth, [FluidSynth]) results(gme, [GME]) -results(sndfile, [libsndfile]) printf '\n\t' +results(sndfile, [libsndfile]) results(mikmod, [MikMod]) results(modplug, [MODPLUG]) results(mad, [MAD]) results(mpg123, [MPG123]) -results(mp4, [MP4]) results(mpc, [Musepack]) printf '\n\t' +results(opus, [Opus]) results(tremor, [OggTremor]) results(vorbis, [OggVorbis]) results(audiofile, [WAVE]) @@ -1567,6 +1648,7 @@ results(wildmidi, [WildMidi]) printf '\nOther features:\n\t' results(lsr, [libsamplerate]) +results(libmpdclient, [libmpdclient]) results(inotify, [inotify]) results(sqlite, [SQLite]) @@ -1602,6 +1684,7 @@ if results(flac_encoder, [FLAC]) results(lame_encoder, [LAME]) results(vorbis_encoder, [Ogg Vorbis]) + results(opus, [Opus]) results(twolame_encoder, [TwoLAME]) results(wave_encoder, [WAVE]) fi diff --git a/doc/user.xml b/doc/user.xml index 38d8a9d8..c83c625a 100644 --- a/doc/user.xml +++ b/doc/user.xml @@ -165,6 +165,53 @@ systemctl start mpd.socket</programlisting> </section> <section> + <title>Configuring database plugins</title> + + <para> + If a music directory is configured, one database plugin is + used. To configure this plugin, add a + <varname>database</varname> block to + <filename>mpd.conf</filename>: + </para> + + <programlisting>database { + plugin "simple" + path "/var/lib/mpd/db" +} + </programlisting> + + <para> + The following table lists the <varname>database</varname> + options valid for all plugins: + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry> + Name + </entry> + <entry> + Description + </entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>plugin</varname> + </entry> + <entry> + The name of the plugin. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> <title>Configuring input plugins</title> <para> @@ -391,6 +438,18 @@ systemctl start mpd.socket</programlisting> </row> <row> <entry> + <varname>tags</varname> + <parameter>yes|no</parameter> + </entry> + <entry> + If set to "no", then MPD will not send tags to this + output. This is only useful for output plugins that + can receive tags, for example the + <varname>httpd</varname> output plugin. + </entry> + </row> + <row> + <entry> <varname>always_on</varname> <parameter>yes|no</parameter> </entry> @@ -618,6 +677,78 @@ systemctl start mpd.socket</programlisting> <title>Plugin reference</title> <section> + <title>Database plugins</title> + + <section> + <title><varname>simple</varname></title> + + <para> + The default plugin. Stores a copy of the database in + memory. A file is used for permanent storage. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>path</varname> + </entry> + <entry> + The path of the database file. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> + <title><varname>proxy</varname></title> + + <para> + Provides access to the database of another MPD instance + using <filename>libmpdclient</filename>. Experimental! + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>host</varname> + </entry> + <entry> + The host name of the "master" MPD instance. + </entry> + </row> + <row> + <entry> + <varname>port</varname> + </entry> + <entry> + The port number of the "master" MPD instance. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + </section> + + <section> <title>Input plugins</title> <section> diff --git a/m4/ax_append_link_flags.m4 b/m4/ax_append_link_flags.m4 new file mode 100644 index 00000000..4fc43370 --- /dev/null +++ b/m4/ax_append_link_flags.m4 @@ -0,0 +1,61 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_append_link_flags.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_APPEND_LINK_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS]) +# +# DESCRIPTION +# +# For every FLAG1, FLAG2 it is checked whether the linker works with the +# flag. If it does, the flag is added FLAGS-VARIABLE +# +# If FLAGS-VARIABLE is not specified, the linker's flags (LDFLAGS) is +# used. During the check the flag is always added to the linker's flags. +# +# If EXTRA-FLAGS is defined, it is added to the linker's default flags +# when the check is done. The check is thus made with the flags: "LDFLAGS +# EXTRA-FLAGS FLAG". This can for example be used to force the linker to +# issue an error when a bad flag is given. +# +# NOTE: This macro depends on the AX_APPEND_FLAG and AX_CHECK_LINK_FLAG. +# Please keep this macro in sync with AX_APPEND_COMPILE_FLAGS. +# +# LICENSE +# +# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 2 + +AC_DEFUN([AX_APPEND_LINK_FLAGS], +[for flag in $1; do + AX_CHECK_LINK_FLAG([$flag], [AX_APPEND_FLAG([$flag], [m4_default([$2], [LDFLAGS])])], [], [$3]) +done +])dnl AX_APPEND_LINK_FLAGS diff --git a/m4/ax_check_link_flag.m4 b/m4/ax_check_link_flag.m4 new file mode 100644 index 00000000..e2d0d363 --- /dev/null +++ b/m4/ax_check_link_flag.m4 @@ -0,0 +1,71 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_link_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_LINK_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the linker or gives an error. +# (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the linker's default flags +# when the check is done. The check is thus made with the flags: "LDFLAGS +# EXTRA-FLAGS FLAG". This can for example be used to force the linker to +# issue an error when a bad flag is given. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,COMPILE}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de> +# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 2 + +AC_DEFUN([AX_CHECK_LINK_FLAG], +[AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_ldflags_$4_$1])dnl +AC_CACHE_CHECK([whether the linker accepts $1], CACHEVAR, [ + ax_check_save_flags=$LDFLAGS + LDFLAGS="$LDFLAGS $4 $1" + AC_LINK_IFELSE([AC_LANG_PROGRAM()], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + LDFLAGS=$ax_check_save_flags]) +AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes], + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_LINK_FLAGS diff --git a/m4/ax_cxx_compile_stdcxx_0x.m4 b/m4/ax_cxx_compile_stdcxx_0x.m4 new file mode 100644 index 00000000..a4e556ff --- /dev/null +++ b/m4/ax_cxx_compile_stdcxx_0x.m4 @@ -0,0 +1,107 @@ +# ============================================================================ +# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_0x.html +# ============================================================================ +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX_0X +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the C++0x +# standard. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 7 + +AU_ALIAS([AC_CXX_COMPILE_STDCXX_0X], [AX_CXX_COMPILE_STDCXX_0X]) +AC_DEFUN([AX_CXX_COMPILE_STDCXX_0X], [ + AC_CACHE_CHECK(if g++ supports C++0x features without additional flags, + ax_cv_cxx_compile_cxx0x_native, + [AC_LANG_SAVE + AC_LANG_CPLUSPLUS + AC_TRY_COMPILE([ + template <typename T> + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + typedef check<check<bool>> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check<int> check_type; + check_type c; + check_type&& cr = static_cast<check_type&&>(c);],, + ax_cv_cxx_compile_cxx0x_native=yes, ax_cv_cxx_compile_cxx0x_native=no) + AC_LANG_RESTORE + ]) + + AC_CACHE_CHECK(if g++ supports C++0x features with -std=c++0x, + ax_cv_cxx_compile_cxx0x_cxx, + [AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -std=c++0x" + AC_TRY_COMPILE([ + template <typename T> + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + typedef check<check<bool>> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check<int> check_type; + check_type c; + check_type&& cr = static_cast<check_type&&>(c);],, + ax_cv_cxx_compile_cxx0x_cxx=yes, ax_cv_cxx_compile_cxx0x_cxx=no) + CXXFLAGS="$ac_save_CXXFLAGS" + AC_LANG_RESTORE + ]) + + AC_CACHE_CHECK(if g++ supports C++0x features with -std=gnu++0x, + ax_cv_cxx_compile_cxx0x_gxx, + [AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -std=gnu++0x" + AC_TRY_COMPILE([ + template <typename T> + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + typedef check<check<bool>> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check<int> check_type; + check_type c; + check_type&& cr = static_cast<check_type&&>(c);],, + ax_cv_cxx_compile_cxx0x_gxx=yes, ax_cv_cxx_compile_cxx0x_gxx=no) + CXXFLAGS="$ac_save_CXXFLAGS" + AC_LANG_RESTORE + ]) + + if test "$ax_cv_cxx_compile_cxx0x_native" = yes || + test "$ax_cv_cxx_compile_cxx0x_cxx" = yes || + test "$ax_cv_cxx_compile_cxx0x_gxx" = yes; then + AC_DEFINE(HAVE_STDCXX_0X,,[Define if g++ supports C++0x features. ]) + fi +]) @@ -8,38 +8,14 @@ AC_ARG_ENABLE(aac, [disable AAC support (default: enable)]),, enable_aac=yes) -AC_ARG_WITH(faad, - AS_HELP_STRING([--with-faad=PFX], - [prefix where faad2 is installed (optional)]),, - faad_prefix="") -AC_ARG_WITH(faad-libraries, - AS_HELP_STRING([--with-faad-libraries=DIR], - [directory where faad2 library is installed (optional)]),, - faad_libraries="") -AC_ARG_WITH(faad-includes, - AS_HELP_STRING([--with-faad-includes=DIR], - [directory where faad2 header files are installed (optional)]),, - faad_includes="") - if test x$enable_aac = xyes; then - if test "x$faad_libraries" != "x" ; then - FAAD_LIBS="-L$faad_libraries" - elif test "x$faad_prefix" != "x" ; then - FAAD_LIBS="-L$faad_prefix/lib" - fi - - FAAD_LIBS="$FAAD_LIBS -lfaad" - - if test "x$faad_includes" != "x" ; then - FAAD_CFLAGS="-I$faad_includes" - elif test "x$faad_prefix" != "x" ; then - FAAD_CFLAGS="-I$faad_prefix/include" - fi + FAAD_LIBS="-lfaad" + FAAD_CFLAGS="" oldcflags=$CFLAGS oldlibs=$LIBS oldcppflags=$CPPFLAGS - CFLAGS="$CFLAGS $FAAD_CFLAGS -I." + CFLAGS="$CFLAGS $FAAD_CFLAGS" LIBS="$LIBS $FAAD_LIBS" CPPFLAGS=$CFLAGS AC_CHECK_HEADER(faad.h,,enable_aac=no) @@ -47,77 +23,36 @@ if test x$enable_aac = xyes; then AC_CHECK_DECL(FAAD2_VERSION,,enable_aac=no,[#include <faad.h>]) fi if test x$enable_aac = xyes; then - AC_CHECK_DECL(faacDecInit2,,enable_aac=no,[#include <faad.h>]) - fi - if test x$enable_aac = xyes; then - AC_CHECK_LIB(faad,faacDecInit2,,enable_aac=no) - if test x$enable_aac = xno; then - enable_aac=yes - AC_CHECK_LIB(faad,NeAACDecInit2,,enable_aac=no) - fi + AC_CHECK_LIB(faad,NeAACDecInit2,,enable_aac=no) fi if test x$enable_aac = xyes; then - AC_MSG_CHECKING(that FAAD2 uses buffer and bufferlen) - AC_COMPILE_IFELSE([AC_LANG_SOURCE([ -#include <faad.h> - -int main() { - char buffer; - long bufferlen = 0; - faacDecHandle decoder; - faacDecFrameInfo frameInfo; - faacDecConfigurationPtr config; - unsigned char channels; - long sampleRate; - mp4AudioSpecificConfig mp4ASC; - - decoder = faacDecOpen(); - config = faacDecGetCurrentConfiguration(decoder); - config->outputFormat = FAAD_FMT_16BIT; - faacDecSetConfiguration(decoder,config); - AudioSpecificConfig(&buffer, bufferlen, &mp4ASC); - faacDecInit(decoder,&buffer,bufferlen,&sampleRate,&channels); - faacDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels); - faacDecDecode(decoder,&frameInfo,&buffer,bufferlen); - - return 0; -} -])],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no); AC_MSG_CHECKING(that FAAD2 can even be used) AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include <faad.h> int main() { char buffer; - faacDecHandle decoder; - faacDecFrameInfo frameInfo; - faacDecConfigurationPtr config; + NeAACDecHandle decoder; + NeAACDecFrameInfo frameInfo; + NeAACDecConfigurationPtr config; unsigned char channels; long sampleRate; long bufferlen = 0; - unsigned long dummy1_32; - unsigned char dummy2_8, dummy3_8, dummy4_8, dummy5_8, dummy6_8, - dummy7_8, dummy8_8; - decoder = faacDecOpen(); - config = faacDecGetCurrentConfiguration(decoder); + decoder = NeAACDecOpen(); + config = NeAACDecGetCurrentConfiguration(decoder); config->outputFormat = FAAD_FMT_16BIT; - faacDecSetConfiguration(decoder,config); - AudioSpecificConfig(&buffer,&dummy1_32,&dummy2_8, - &dummy3_8,&dummy4_8,&dummy5_8, - &dummy6_8,&dummy7_8,&dummy8_8); - faacDecInit(decoder,&buffer,&sampleRate,&channels); - faacDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels); - faacDecDecode(decoder,&frameInfo,&buffer); - faacDecClose(decoder); + NeAACDecSetConfiguration(decoder,config); + NeAACDecInit(decoder,&buffer,bufferlen,&sampleRate,&channels); + NeAACDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels); + NeAACDecDecode(decoder,&frameInfo,&buffer,bufferlen); + NeAACDecClose(decoder); return 0; } ])],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no]) - ]) fi if test x$enable_aac = xyes; then - AC_CHECK_MEMBERS([faacDecConfiguration.downMatrix,faacDecConfiguration.dontUpSampleImplicitSBR,faacDecFrameInfo.samplerate],,,[#include <faad.h>]) AC_DEFINE(HAVE_FAAD,1,[Define to use FAAD2 for AAC decoding]) else AC_MSG_WARN([faad2 lib needed for MP4/AAC support -- disabling MP4/AAC support]) @@ -145,7 +80,7 @@ int main() { unsigned char channels; uint32_t sample_rate; - faacDecInit2(NULL, NULL, 0, &sample_rate, &channels); + NeAACDecInit2(NULL, NULL, 0, &sample_rate, &channels); return 0; } ])], @@ -156,40 +91,9 @@ int main() { CFLAGS=$oldcflags LIBS=$oldlibs CPPFLAGS=$oldcppflags -fi - -if test x$enable_aac = xyes; then - enable_mp4=yes - MP4FF_LIBS="-lmp4ff" - - oldcflags=$CFLAGS - oldlibs=$LIBS - oldcppflags=$CPPFLAGS - CFLAGS="$CFLAGS $FAAD_CFLAGS" - LIBS="$LIBS $FAAD_LIBS $MP4FF_LIBS" - CPPFLAGS=$CFLAGS - - AC_CHECK_HEADER(mp4ff.h,,enable_mp4=no) - - if test x$enable_mp4 = xyes; then - AC_CHECK_LIB(mp4ff,mp4ff_open_read,,enable_mp4=no) - fi - - if test x$enable_mp4 = xyes; then - AC_SUBST(MP4FF_LIBS) - AC_DEFINE(HAVE_MP4, 1, [Define to use FAAD2+mp4ff for MP4 decoding]) - else - AC_MSG_WARN([libmp4ff needed for MP4 support -- disabling MP4 support]) - unset MP4FF_LIBS - fi - - CFLAGS=$oldcflags - LIBS=$oldlibs - CPPFLAGS=$oldcppflags else - enable_mp4=no - FAAD_CFLAGS="" FAAD_LIBS="" + FAAD_CFLAGS="" fi AC_SUBST(FAAD_CFLAGS) diff --git a/src/AllCommands.cxx b/src/AllCommands.cxx new file mode 100644 index 00000000..58dcf4db --- /dev/null +++ b/src/AllCommands.cxx @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "AllCommands.hxx" +#include "command.h" +#include "QueueCommands.hxx" +#include "PlayerCommands.hxx" +#include "PlaylistCommands.hxx" +#include "DatabaseCommands.hxx" +#include "OutputCommands.hxx" +#include "MessageCommands.hxx" +#include "OtherCommands.hxx" +#include "Permission.hxx" +#include "tag.h" +#include "protocol/Result.hxx" +#include "Client.hxx" + +extern "C" { +#include "tokenizer.h" +} + +#ifdef ENABLE_SQLITE +#include "StickerCommands.hxx" +#include "StickerDatabase.hxx" +#endif + +#include <assert.h> +#include <string.h> + +/* + * The most we ever use is for search/find, and that limits it to the + * number of tags we can have. Add one for the command, and one extra + * to catch errors clients may send us + */ +#define COMMAND_ARGV_MAX (2+(TAG_NUM_OF_ITEM_TYPES*2)) + +/* if min: -1 don't check args * + * if max: -1 no max args */ +struct command { + const char *cmd; + unsigned permission; + int min; + int max; + enum command_return (*handler)(Client *client, int argc, char **argv); +}; + +/* don't be fooled, this is the command handler for "commands" command */ +static enum command_return +handle_commands(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]); + +static enum command_return +handle_not_commands(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]); + +/** + * The command registry. + * + * This array must be sorted! + */ +static const struct command commands[] = { + { "add", PERMISSION_ADD, 1, 1, handle_add }, + { "addid", PERMISSION_ADD, 1, 2, handle_addid }, + { "channels", PERMISSION_READ, 0, 0, handle_channels }, + { "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, + { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, + { "close", PERMISSION_NONE, -1, -1, handle_close }, + { "commands", PERMISSION_NONE, 0, 0, handle_commands }, + { "config", PERMISSION_ADMIN, 0, 0, handle_config }, + { "consume", PERMISSION_CONTROL, 1, 1, handle_consume }, + { "count", PERMISSION_READ, 2, -1, handle_count }, + { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade }, + { "currentsong", PERMISSION_READ, 0, 0, handle_currentsong }, + { "decoders", PERMISSION_READ, 0, 0, handle_decoders }, + { "delete", PERMISSION_CONTROL, 1, 1, handle_delete }, + { "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid }, + { "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput }, + { "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput }, + { "find", PERMISSION_READ, 2, -1, handle_find }, + { "findadd", PERMISSION_READ, 2, -1, handle_findadd}, + { "idle", PERMISSION_READ, 0, -1, handle_idle }, + { "kill", PERMISSION_ADMIN, -1, -1, handle_kill }, + { "list", PERMISSION_READ, 1, -1, handle_list }, + { "listall", PERMISSION_READ, 0, 1, handle_listall }, + { "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo }, + { "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist }, + { "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo }, + { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists }, + { "load", PERMISSION_ADD, 1, 2, handle_load }, + { "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo }, + { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb }, + { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay }, + { "move", PERMISSION_CONTROL, 2, 2, handle_move }, + { "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid }, + { "next", PERMISSION_CONTROL, 0, 0, handle_next }, + { "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands }, + { "outputs", PERMISSION_READ, 0, 0, handle_devices }, + { "password", PERMISSION_NONE, 1, 1, handle_password }, + { "pause", PERMISSION_CONTROL, 0, 1, handle_pause }, + { "ping", PERMISSION_NONE, 0, 0, handle_ping }, + { "play", PERMISSION_CONTROL, 0, 1, handle_play }, + { "playid", PERMISSION_CONTROL, 0, 1, handle_playid }, + { "playlist", PERMISSION_READ, 0, 0, handle_playlist }, + { "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd }, + { "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear }, + { "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete }, + { "playlistfind", PERMISSION_READ, 2, -1, handle_playlistfind }, + { "playlistid", PERMISSION_READ, 0, 1, handle_playlistid }, + { "playlistinfo", PERMISSION_READ, 0, 1, handle_playlistinfo }, + { "playlistmove", PERMISSION_CONTROL, 3, 3, handle_playlistmove }, + { "playlistsearch", PERMISSION_READ, 2, -1, handle_playlistsearch }, + { "plchanges", PERMISSION_READ, 1, 1, handle_plchanges }, + { "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid }, + { "previous", PERMISSION_CONTROL, 0, 0, handle_previous }, + { "prio", PERMISSION_CONTROL, 2, -1, handle_prio }, + { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid }, + { "random", PERMISSION_CONTROL, 1, 1, handle_random }, + { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages }, + { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, + { "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat }, + { "replay_gain_mode", PERMISSION_CONTROL, 1, 1, + handle_replay_gain_mode }, + { "replay_gain_status", PERMISSION_READ, 0, 0, + handle_replay_gain_status }, + { "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan }, + { "rm", PERMISSION_CONTROL, 1, 1, handle_rm }, + { "save", PERMISSION_CONTROL, 1, 1, handle_save }, + { "search", PERMISSION_READ, 2, -1, handle_search }, + { "searchadd", PERMISSION_ADD, 2, -1, handle_searchadd }, + { "searchaddpl", PERMISSION_CONTROL, 3, -1, handle_searchaddpl }, + { "seek", PERMISSION_CONTROL, 2, 2, handle_seek }, + { "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur }, + { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid }, + { "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message }, + { "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol }, + { "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle }, + { "single", PERMISSION_CONTROL, 1, 1, handle_single }, + { "stats", PERMISSION_READ, 0, 0, handle_stats }, + { "status", PERMISSION_READ, 0, 0, handle_status }, +#ifdef ENABLE_SQLITE + { "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker }, +#endif + { "stop", PERMISSION_CONTROL, 0, 0, handle_stop }, + { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe }, + { "swap", PERMISSION_CONTROL, 2, 2, handle_swap }, + { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid }, + { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, + { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe }, + { "update", PERMISSION_CONTROL, 0, 1, handle_update }, + { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, +}; + +static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]); + +static bool +command_available(G_GNUC_UNUSED const struct command *cmd) +{ +#ifdef ENABLE_SQLITE + if (strcmp(cmd->cmd, "sticker") == 0) + return sticker_enabled(); +#endif + + return true; +} + +/* don't be fooled, this is the command handler for "commands" command */ +static enum command_return +handle_commands(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + const unsigned permission = client_get_permission(client); + const struct command *cmd; + + for (unsigned i = 0; i < num_commands; ++i) { + cmd = &commands[i]; + + if (cmd->permission == (permission & cmd->permission) && + command_available(cmd)) + client_printf(client, "command: %s\n", cmd->cmd); + } + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_not_commands(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + const unsigned permission = client_get_permission(client); + const struct command *cmd; + + for (unsigned i = 0; i < num_commands; ++i) { + cmd = &commands[i]; + + if (cmd->permission != (permission & cmd->permission)) + client_printf(client, "command: %s\n", cmd->cmd); + } + + return COMMAND_RETURN_OK; +} + +void command_init(void) +{ +#ifndef NDEBUG + /* ensure that the command list is sorted */ + for (unsigned i = 0; i < num_commands - 1; ++i) + assert(strcmp(commands[i].cmd, commands[i + 1].cmd) < 0); +#endif +} + +void command_finish(void) +{ +} + +static const struct command * +command_lookup(const char *name) +{ + unsigned a = 0, b = num_commands, i; + int cmp; + + /* binary search */ + do { + i = (a + b) / 2; + + cmp = strcmp(name, commands[i].cmd); + if (cmp == 0) + return &commands[i]; + else if (cmp < 0) + b = i; + else if (cmp > 0) + a = i + 1; + } while (a < b); + + return NULL; +} + +static bool +command_check_request(const struct command *cmd, Client *client, + unsigned permission, int argc, char *argv[]) +{ + int min = cmd->min + 1; + int max = cmd->max + 1; + + if (cmd->permission != (permission & cmd->permission)) { + if (client != NULL) + command_error(client, ACK_ERROR_PERMISSION, + "you don't have permission for \"%s\"", + cmd->cmd); + return false; + } + + if (min == 0) + return true; + + if (min == max && max != argc) { + if (client != NULL) + command_error(client, ACK_ERROR_ARG, + "wrong number of arguments for \"%s\"", + argv[0]); + return false; + } else if (argc < min) { + if (client != NULL) + command_error(client, ACK_ERROR_ARG, + "too few arguments for \"%s\"", argv[0]); + return false; + } else if (argc > max && max /* != 0 */ ) { + if (client != NULL) + command_error(client, ACK_ERROR_ARG, + "too many arguments for \"%s\"", argv[0]); + return false; + } else + return true; +} + +static const struct command * +command_checked_lookup(Client *client, unsigned permission, + int argc, char *argv[]) +{ + const struct command *cmd; + + current_command = ""; + + if (argc == 0) + return NULL; + + cmd = command_lookup(argv[0]); + if (cmd == NULL) { + if (client != NULL) + command_error(client, ACK_ERROR_UNKNOWN, + "unknown command \"%s\"", argv[0]); + return NULL; + } + + current_command = cmd->cmd; + + if (!command_check_request(cmd, client, permission, argc, argv)) + return NULL; + + return cmd; +} + +enum command_return +command_process(Client *client, unsigned num, char *line) +{ + GError *error = NULL; + int argc; + char *argv[COMMAND_ARGV_MAX] = { NULL }; + const struct command *cmd; + enum command_return ret = COMMAND_RETURN_ERROR; + + command_list_num = num; + + /* get the command name (first word on the line) */ + + argv[0] = tokenizer_next_word(&line, &error); + if (argv[0] == NULL) { + current_command = ""; + if (*line == 0) + command_error(client, ACK_ERROR_UNKNOWN, + "No command given"); + else { + command_error(client, ACK_ERROR_UNKNOWN, + "%s", error->message); + g_error_free(error); + } + current_command = NULL; + + return COMMAND_RETURN_ERROR; + } + + argc = 1; + + /* now parse the arguments (quoted or unquoted) */ + + while (argc < (int)G_N_ELEMENTS(argv) && + (argv[argc] = + tokenizer_next_param(&line, &error)) != NULL) + ++argc; + + /* some error checks; we have to set current_command because + command_error() expects it to be set */ + + current_command = argv[0]; + + if (argc >= (int)G_N_ELEMENTS(argv)) { + command_error(client, ACK_ERROR_ARG, "Too many arguments"); + current_command = NULL; + return COMMAND_RETURN_ERROR; + } + + if (*line != 0) { + command_error(client, ACK_ERROR_ARG, + "%s", error->message); + current_command = NULL; + g_error_free(error); + return COMMAND_RETURN_ERROR; + } + + /* look up and invoke the command handler */ + + cmd = command_checked_lookup(client, client_get_permission(client), + argc, argv); + if (cmd) + ret = cmd->handler(client, argc, argv); + + current_command = NULL; + command_list_num = 0; + + return ret; +} diff --git a/src/db_internal.h b/src/AllCommands.hxx index a3335152..a55eb5a3 100644 --- a/src/db_internal.h +++ b/src/AllCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,19 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DB_INTERNAL_H -#define MPD_DB_INTERNAL_H +#ifndef MPD_ALL_COMMANDS_HXX +#define MPD_ALL_COMMANDS_HXX -#include "db_plugin.h" +#include "command.h" -#include <assert.h> +class Client; -static inline void -db_base_init(struct db *db, const struct db_plugin *plugin) -{ - assert(plugin != NULL); +void command_init(void); - db->plugin = plugin; -} +void command_finish(void); + +enum command_return +command_process(Client *client, unsigned num, char *line); #endif diff --git a/src/ArchiveFile.hxx b/src/ArchiveFile.hxx new file mode 100644 index 00000000..c7933ebd --- /dev/null +++ b/src/ArchiveFile.hxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_FILE_HXX +#define MPD_ARCHIVE_FILE_HXX + +class ArchiveFile { +public: + const struct archive_plugin &plugin; + + ArchiveFile(const struct archive_plugin &_plugin) + :plugin(_plugin) {} + +protected: + /** + * Use Close() instead of delete. + */ + ~ArchiveFile() {} + +public: + virtual void Close() = 0; + + /** + * Visit all entries inside this archive. + */ + virtual void Visit(ArchiveVisitor &visitor) = 0; + + /** + * Opens an input_stream of a file within the archive. + * + * @param path the path within the archive + * @param error_r location to store the error occurring, or + * NULL to ignore errors + */ + virtual input_stream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + GError **error_r) = 0; +}; + +#endif diff --git a/src/archive_list.c b/src/ArchiveList.cxx index e23567bd..02b19ce7 100644 --- a/src/archive_list.c +++ b/src/ArchiveList.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,12 +18,12 @@ */ #include "config.h" -#include "archive_list.h" -#include "archive_plugin.h" +#include "ArchiveList.hxx" +#include "ArchivePlugin.hxx" #include "string_util.h" -#include "archive/bz2_archive_plugin.h" -#include "archive/iso9660_archive_plugin.h" -#include "archive/zzip_archive_plugin.h" +#include "archive/Bzip2ArchivePlugin.hxx" +#include "archive/Iso9660ArchivePlugin.hxx" +#include "archive/ZzipArchivePlugin.hxx" #include <string.h> #include <glib.h> diff --git a/src/archive_list.h b/src/ArchiveList.hxx index f944583e..057c351d 100644 --- a/src/archive_list.h +++ b/src/ArchiveList.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_ARCHIVE_LIST_H -#define MPD_ARCHIVE_LIST_H +#ifndef MPD_ARCHIVE_LIST_HXX +#define MPD_ARCHIVE_LIST_HXX struct archive_plugin; diff --git a/src/archive_api.c b/src/ArchiveLookup.cxx index be3c35f7..747f5c7e 100644 --- a/src/archive_api.c +++ b/src/ArchiveLookup.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" /* must be first for large file support */ -#include "archive_api.h" +#include "ArchiveLookup.hxx" #include <stdio.h> diff --git a/src/archive_api.h b/src/ArchiveLookup.hxx index 4e0f603f..6e7669cb 100644 --- a/src/archive_api.h +++ b/src/ArchiveLookup.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_ARCHIVE_API_H -#define MPD_ARCHIVE_API_H +#ifndef MPD_ARCHIVE_LOOKUP_HXX +#define MPD_ARCHIVE_LOOKUP_HXX /* * This is the public API which is used by archive plugins to @@ -26,12 +26,6 @@ * */ -#include "archive_internal.h" -#include "archive_plugin.h" -#include "input_stream.h" - -#include <stdbool.h> - bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix); #endif diff --git a/src/ArchivePlugin.cxx b/src/ArchivePlugin.cxx new file mode 100644 index 00000000..7c516422 --- /dev/null +++ b/src/ArchivePlugin.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" + +#include <assert.h> + +ArchiveFile * +archive_file_open(const struct archive_plugin *plugin, const char *path, + GError **error_r) +{ + assert(plugin != NULL); + assert(plugin->open != NULL); + assert(path != NULL); + assert(error_r == NULL || *error_r == NULL); + + ArchiveFile *file = plugin->open(path, error_r); + + if (file != NULL) { + assert(error_r == NULL || *error_r == NULL); + } else { + assert(error_r == NULL || *error_r != NULL); + } + + return file; +} diff --git a/src/archive_plugin.h b/src/ArchivePlugin.hxx index b7b92446..13952940 100644 --- a/src/archive_plugin.h +++ b/src/ArchivePlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,15 +17,16 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_ARCHIVE_PLUGIN_H -#define MPD_ARCHIVE_PLUGIN_H +#ifndef MPD_ARCHIVE_PLUGIN_HXX +#define MPD_ARCHIVE_PLUGIN_HXX -#include <glib.h> - -#include <stdbool.h> +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "gerror.h" struct input_stream; -struct archive_file; +class ArchiveFile; +class ArchiveVisitor; struct archive_plugin { const char *name; @@ -48,38 +49,7 @@ struct archive_plugin { * returns pointer to handle used is all operations with this archive * or NULL when opening fails */ - struct archive_file *(*open)(const char *path_fs, GError **error_r); - - /** - * reset routine will move current read index in archive to default - * position and then the filenames from archives can be read - * via scan_next routine - */ - void (*scan_reset)(struct archive_file *); - - /** - * the read method will return corresponding files from archive - * (as pathnames) and move read index to next file. When there is no - * next file it return NULL. - */ - char *(*scan_next)(struct archive_file *); - - /** - * Opens an input_stream of a file within the archive. - * - * @param path the path within the archive - * @param error_r location to store the error occurring, or - * NULL to ignore errors - */ - struct input_stream *(*open_stream)(struct archive_file *af, - const char *path, - GMutex *mutex, GCond *cond, - GError **error_r); - - /** - * closes archive file. - */ - void (*close)(struct archive_file *); + ArchiveFile *(*open)(const char *path_fs, GError **error_r); /** * suffixes handled by this plugin. @@ -88,22 +58,8 @@ struct archive_plugin { const char *const*suffixes; }; -struct archive_file * +ArchiveFile * archive_file_open(const struct archive_plugin *plugin, const char *path, GError **error_r); -void -archive_file_close(struct archive_file *file); - -void -archive_file_scan_reset(struct archive_file *file); - -char * -archive_file_scan_next(struct archive_file *file); - -struct input_stream * -archive_file_open_stream(struct archive_file *file, const char *path, - GMutex *mutex, GCond *cond, - GError **error_r); - #endif diff --git a/src/ArchiveVisitor.hxx b/src/ArchiveVisitor.hxx new file mode 100644 index 00000000..e951cb5e --- /dev/null +++ b/src/ArchiveVisitor.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_VISITOR_HXX +#define MPD_ARCHIVE_VISITOR_HXX + +class ArchiveVisitor { +public: + virtual void VisitArchiveEntry(const char *path_utf8) = 0; +}; + +#endif diff --git a/src/AudioCompress/compress.h b/src/AudioCompress/compress.h index 073d4af9..8556d16b 100644 --- a/src/AudioCompress/compress.h +++ b/src/AudioCompress/compress.h @@ -19,6 +19,10 @@ struct CompressorConfig { struct Compressor; +#ifdef __cplusplus +extern "C" { +#endif + //! Create a new compressor (use history value of 0 for default) struct Compressor *Compressor_new(unsigned int history); @@ -34,7 +38,12 @@ struct CompressorConfig *Compressor_getConfig(struct Compressor *); //! Process 16-bit signed data void Compressor_Process_int16(struct Compressor *, int16_t *data, unsigned int count); +#ifdef __cplusplus +} +#endif + //! TODO: Compressor_Process_int32, Compressor_Process_float, others as needed //! TODO: functions for getting at the peak/gain/clip history buffers (for monitoring) + #endif diff --git a/src/audio_config.c b/src/AudioConfig.cxx index 72869c38..e546aed2 100644 --- a/src/audio_config.c +++ b/src/AudioConfig.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,20 +18,12 @@ */ #include "config.h" -#include "audio_config.h" +#include "AudioConfig.hxx" #include "audio_format.h" -#include "audio_parser.h" -#include "output_internal.h" -#include "output_plugin.h" -#include "output_all.h" +#include "AudioParser.hxx" #include "conf.h" #include "mpd_error.h" -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> - static struct audio_format configured_audio_format; void getOutputAudioFormat(const struct audio_format *inAudioFormat, @@ -53,7 +45,6 @@ void initAudioConfig(void) ret = audio_format_parse(&configured_audio_format, param->value, true, &error); if (!ret) - MPD_ERROR("error parsing \"%s\" at line %i: %s", - CONF_AUDIO_OUTPUT_FORMAT, param->line, - error->message); + MPD_ERROR("error parsing line %i: %s", + param->line, error->message); } diff --git a/src/audio_config.h b/src/AudioConfig.hxx index 85143247..717a8e2e 100644 --- a/src/audio_config.h +++ b/src/AudioConfig.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_AUDIO_CONFIG_H -#define MPD_AUDIO_CONFIG_H - -#include <stdbool.h> +#ifndef MPD_AUDIO_CONFIG_HXX +#define MPD_AUDIO_CONFIG_HXX struct audio_format; diff --git a/src/audio_parser.c b/src/AudioParser.cxx index 152eab5d..9178c3e1 100644 --- a/src/audio_parser.c +++ b/src/AudioParser.cxx @@ -23,7 +23,7 @@ */ #include "config.h" -#include "audio_parser.h" +#include "AudioParser.hxx" #include "audio_format.h" #include "audio_check.h" #include "gcc.h" diff --git a/src/audio_parser.h b/src/AudioParser.hxx index a963eb46..f7855e8e 100644 --- a/src/audio_parser.h +++ b/src/AudioParser.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,12 +22,10 @@ * Parser functions for audio related objects. */ -#ifndef AUDIO_PARSER_H -#define AUDIO_PARSER_H +#ifndef MPD_AUDIO_PARSER_HXX +#define MPD_AUDIO_PARSER_HXX -#include <glib.h> - -#include <stdbool.h> +#include "gerror.h" struct audio_format; diff --git a/src/client.c b/src/Client.cxx index 3fa2c9be..be121dfe 100644 --- a/src/client.c +++ b/src/Client.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,24 +18,19 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" -bool client_is_expired(const struct client *client) -{ - return client->channel == NULL; -} - -int client_get_uid(const struct client *client) +int client_get_uid(const Client *client) { return client->uid; } -unsigned client_get_permission(const struct client *client) +unsigned client_get_permission(const Client *client) { return client->permission; } -void client_set_permission(struct client *client, unsigned permission) +void client_set_permission(Client *client, unsigned permission) { client->permission = permission; } diff --git a/src/client.h b/src/Client.hxx index 0302a2e0..1456f1b7 100644 --- a/src/client.h +++ b/src/Client.hxx @@ -20,60 +20,60 @@ #ifndef MPD_CLIENT_H #define MPD_CLIENT_H -#include <glib.h> -#include <stdbool.h> +#include "gcc.h" + #include <stddef.h> #include <stdarg.h> -struct client; struct sockaddr; -struct player_control; +class EventLoop; +struct Partition; +class Client; void client_manager_init(void); -void client_manager_deinit(void); - -void client_new(struct player_control *player_control, - int fd, const struct sockaddr *sa, size_t sa_length, int uid); -G_GNUC_PURE -bool client_is_expired(const struct client *client); +void +client_new(EventLoop &loop, Partition &partition, + int fd, const struct sockaddr *sa, size_t sa_length, int uid); /** * returns the uid of the client process, or a negative value if the * uid is unknown */ -G_GNUC_PURE -int client_get_uid(const struct client *client); +gcc_pure +int client_get_uid(const Client *client); /** * Is this client running on the same machine, connected with a local * (UNIX domain) socket? */ -G_GNUC_PURE +gcc_pure static inline bool -client_is_local(const struct client *client) +client_is_local(const Client *client) { return client_get_uid(client) > 0; } -G_GNUC_PURE -unsigned client_get_permission(const struct client *client); +gcc_pure +unsigned client_get_permission(const Client *client); -void client_set_permission(struct client *client, unsigned permission); +void client_set_permission(Client *client, unsigned permission); /** * Write a C string to the client. */ -void client_puts(struct client *client, const char *s); +void client_puts(Client *client, const char *s); /** * Write a printf-like formatted string to the client. */ -void client_vprintf(struct client *client, const char *fmt, va_list args); +void client_vprintf(Client *client, const char *fmt, va_list args); /** * Write a printf-like formatted string to the client. */ -G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...); +gcc_fprintf +void +client_printf(Client *client, const char *fmt, ...); #endif diff --git a/src/song_print.h b/src/ClientEvent.cxx index 8f1f0cc6..905cf0c0 100644 --- a/src/song_print.h +++ b/src/ClientEvent.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,17 +17,20 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_SONG_PRINT_H -#define MPD_SONG_PRINT_H - -struct client; -struct song; -struct songvec; +#include "config.h" +#include "ClientInternal.hxx" void -song_print_info(struct client *client, struct song *song); +Client::OnSocketError(GError *error) +{ + g_warning("error on client %d: %s", num, error->message); + g_error_free(error); -void -song_print_uri(struct client *client, struct song *song); + SetExpired(); +} -#endif +void +Client::OnSocketClosed() +{ + SetExpired(); +} diff --git a/src/db_lock.c b/src/ClientExpire.cxx index 53759673..6bb0a43a 100644 --- a/src/db_lock.c +++ b/src/ClientExpire.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,16 +18,26 @@ */ #include "config.h" -#include "db_lock.h" -#include "gcc.h" +#include "ClientInternal.hxx" -#if GCC_CHECK_VERSION(4, 2) -/* workaround for a warning caused by G_STATIC_MUTEX_INIT */ -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif +void +Client::SetExpired() +{ + if (IsExpired()) + return; -GStaticMutex db_mutex = G_STATIC_MUTEX_INIT; + FullyBufferedSocket::Close(); + TimeoutMonitor::Schedule(0); +} -#ifndef NDEBUG -GThread *db_mutex_holder; -#endif +bool +Client::OnTimeout() +{ + if (!IsExpired()) { + assert(!idle_waiting); + g_debug("[%u] timeout", num); + } + + Close(); + return false; +} diff --git a/src/client_file.c b/src/ClientFile.cxx index 2ee43330..ca5acb22 100644 --- a/src/client_file.c +++ b/src/ClientFile.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,9 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "client_file.h" -#include "client.h" +#include "ClientFile.hxx" +#include "Client.hxx" #include "ack.h" +#include "io_error.h" #include <sys/stat.h> #include <sys/types.h> @@ -27,7 +28,7 @@ #include <unistd.h> bool -client_allow_file(const struct client *client, const char *path_fs, +client_allow_file(const Client *client, const char *path_fs, GError **error_r) { #ifdef WIN32 @@ -53,8 +54,7 @@ client_allow_file(const struct client *client, const char *path_fs, struct stat st; if (stat(path_fs, &st) < 0) { - g_set_error(error_r, g_file_error_quark(), errno, - "%s", g_strerror(errno)); + set_error_errno(error_r); return false; } diff --git a/src/client_file.h b/src/ClientFile.hxx index bc64bd04..48e00c44 100644 --- a/src/client_file.h +++ b/src/ClientFile.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_CLIENT_FILE_H -#define MPD_CLIENT_FILE_H +#ifndef MPD_CLIENT_FILE_HXX +#define MPD_CLIENT_FILE_HXX + +#include "gerror.h" -#include <glib.h> #include <stdbool.h> -struct client; +class Client; /** * Is this client allowed to use the specified local file? @@ -36,7 +37,7 @@ struct client; * @return true if access is allowed */ bool -client_allow_file(const struct client *client, const char *path_fs, +client_allow_file(const Client *client, const char *path_fs, GError **error_r); #endif diff --git a/src/client_global.c b/src/ClientGlobal.cxx index adf3b2f9..6115a785 100644 --- a/src/client_global.c +++ b/src/ClientGlobal.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,18 +18,16 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" +#include "ClientList.hxx" #include "conf.h" #include <assert.h> #define CLIENT_TIMEOUT_DEFAULT (60) -#define CLIENT_MAX_CONNECTIONS_DEFAULT (10) #define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024) #define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024) -/* set this to zero to indicate we have no possible clients */ -unsigned int client_max_connections; int client_timeout; size_t client_max_command_list_size; size_t client_max_output_buffer_size; @@ -38,9 +36,6 @@ void client_manager_init(void) { client_timeout = config_get_positive(CONF_CONN_TIMEOUT, CLIENT_TIMEOUT_DEFAULT); - client_max_connections = - config_get_positive(CONF_MAX_CONN, - CLIENT_MAX_CONNECTIONS_DEFAULT); client_max_command_list_size = config_get_positive(CONF_MAX_COMMAND_LIST_SIZE, CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024) @@ -51,23 +46,3 @@ void client_manager_init(void) CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024) * 1024; } - -static void client_close_all(void) -{ - while (!client_list_is_empty()) { - struct client *client = client_list_get_first(); - - client_close(client); - } - - assert(client_list_is_empty()); -} - -void client_manager_deinit(void) -{ - client_close_all(); - - client_max_connections = 0; - - client_deinit_expire(); -} diff --git a/src/ClientIdle.cxx b/src/ClientIdle.cxx new file mode 100644 index 00000000..71443812 --- /dev/null +++ b/src/ClientIdle.cxx @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" +#include "Idle.hxx" + +#include <assert.h> + +void +Client::IdleNotify() +{ + assert(idle_waiting); + assert(idle_flags != 0); + + unsigned flags = idle_flags; + idle_flags = 0; + idle_waiting = false; + + const char *const*idle_names = idle_get_names(); + for (unsigned i = 0; idle_names[i]; ++i) { + if (flags & (1 << i) & idle_subscriptions) + client_printf(this, "changed: %s\n", + idle_names[i]); + } + + client_puts(this, "OK\n"); + + TimeoutMonitor::ScheduleSeconds(client_timeout); +} + +void +Client::IdleAdd(unsigned flags) +{ + if (IsExpired()) + return; + + idle_flags |= flags; + if (idle_waiting && (idle_flags & idle_subscriptions)) + IdleNotify(); +} + +bool +Client::IdleWait(unsigned flags) +{ + assert(!idle_waiting); + + idle_waiting = true; + idle_subscriptions = flags; + + if (idle_flags & idle_subscriptions) { + IdleNotify(); + return true; + } else { + /* disable timeouts while in "idle" */ + TimeoutMonitor::Cancel(); + return false; + } +} diff --git a/src/ClientInternal.hxx b/src/ClientInternal.hxx new file mode 100644 index 00000000..c3111868 --- /dev/null +++ b/src/ClientInternal.hxx @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_INTERNAL_HXX +#define MPD_CLIENT_INTERNAL_HXX + +#include "check.h" +#include "Client.hxx" +#include "ClientMessage.hxx" +#include "CommandListBuilder.hxx" +#include "event/FullyBufferedSocket.hxx" +#include "event/TimeoutMonitor.hxx" +#include "command.h" + +#include <set> +#include <string> +#include <list> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "client" + +enum { + CLIENT_MAX_SUBSCRIPTIONS = 16, + CLIENT_MAX_MESSAGES = 64, +}; + +struct Partition; + +class Client final : private FullyBufferedSocket, TimeoutMonitor { +public: + Partition &partition; + struct playlist &playlist; + struct player_control *player_control; + + unsigned permission; + + /** the uid of the client process, or -1 if unknown */ + int uid; + + CommandListBuilder cmd_list; + + unsigned int num; /* client number */ + + /** is this client waiting for an "idle" response? */ + bool idle_waiting; + + /** idle flags pending on this client, to be sent as soon as + the client enters "idle" */ + unsigned idle_flags; + + /** idle flags that the client wants to receive */ + unsigned idle_subscriptions; + + /** + * A list of channel names this client is subscribed to. + */ + std::set<std::string> subscriptions; + + /** + * The number of subscriptions in #subscriptions. Used to + * limit the number of subscriptions. + */ + unsigned num_subscriptions; + + /** + * A list of messages this client has received. + */ + std::list<ClientMessage> messages; + + Client(EventLoop &loop, Partition &partition, + int fd, int uid, int num); + + bool IsConnected() const { + return FullyBufferedSocket::IsDefined(); + } + + gcc_pure + bool IsSubscribed(const char *channel_name) const { + return subscriptions.find(channel_name) != subscriptions.end(); + } + + gcc_pure + bool IsExpired() const { + return !FullyBufferedSocket::IsDefined(); + } + + void Close(); + void SetExpired(); + + using FullyBufferedSocket::Write; + + /** + * Send "idle" response to this client. + */ + void IdleNotify(); + void IdleAdd(unsigned flags); + bool IdleWait(unsigned flags); + +private: + /* virtual methods from class BufferedSocket */ + virtual InputResult OnSocketInput(const void *data, + size_t length) override; + virtual void OnSocketError(GError *error) override; + virtual void OnSocketClosed() override; + + /* virtual methods from class TimeoutMonitor */ + virtual bool OnTimeout() override; +}; + +extern int client_timeout; +extern size_t client_max_command_list_size; +extern size_t client_max_output_buffer_size; + +enum command_return +client_process_line(Client *client, char *line); + +#endif diff --git a/src/ClientList.cxx b/src/ClientList.cxx new file mode 100644 index 00000000..37e6f128 --- /dev/null +++ b/src/ClientList.cxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientList.hxx" +#include "ClientInternal.hxx" + +#include <algorithm> + +#include <assert.h> + +void +ClientList::Remove(Client &client) +{ + assert(size > 0); + assert(!list.empty()); + + auto i = std::find(list.begin(), list.end(), &client); + assert(i != list.end()); + list.erase(i); + --size; +} + +void +ClientList::CloseAll() +{ + while (!list.empty()) + list.front()->Close(); + + assert(size == 0); +} + +void +ClientList::IdleAdd(unsigned flags) +{ + assert(flags != 0); + + for (const auto &client : list) + client->IdleAdd(flags); +} diff --git a/src/ClientList.hxx b/src/ClientList.hxx new file mode 100644 index 00000000..e8560af7 --- /dev/null +++ b/src/ClientList.hxx @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_LIST_HXX +#define MPD_CLIENT_LIST_HXX + +#include <list> + +class Client; + +class ClientList { + const unsigned max_size; + + unsigned size; + std::list<Client *> list; + +public: + ClientList(unsigned _max_size) + :max_size(_max_size), size(0) {} + + std::list<Client *>::iterator begin() { + return list.begin(); + } + + std::list<Client *>::iterator end() { + return list.end(); + } + + bool IsFull() const { + return size >= max_size; + } + + void Add(Client &client) { + list.push_front(&client); + ++size; + } + + void Remove(Client &client); + + void CloseAll(); + + void IdleAdd(unsigned flags); +}; + +#endif diff --git a/src/ClientMessage.cxx b/src/ClientMessage.cxx new file mode 100644 index 00000000..619964b3 --- /dev/null +++ b/src/ClientMessage.cxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ClientMessage.hxx" + +#include <glib.h> + +G_GNUC_PURE +static bool +valid_channel_char(const char ch) +{ + return g_ascii_isalnum(ch) || + ch == '_' || ch == '-' || ch == '.' || ch == ':'; +} + +bool +client_message_valid_channel_name(const char *name) +{ + do { + if (!valid_channel_char(*name)) + return false; + } while (*++name != 0); + + return true; +} diff --git a/src/exclude.h b/src/ClientMessage.hxx index 5b1229e2..2a929d44 100644 --- a/src/exclude.h +++ b/src/ClientMessage.hxx @@ -17,35 +17,36 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -/* - * The .mpdignore backend code. - * - */ +#ifndef MPD_CLIENT_MESSAGE_H +#define MPD_CLIENT_MESSAGE_H -#ifndef MPD_EXCLUDE_H -#define MPD_EXCLUDE_H +#include "gcc.h" -#include <glib.h> - -#include <stdbool.h> +#include <string> /** - * Loads and parses a .mpdignore file. + * A client-to-client message. */ -GSList * -exclude_list_load(const char *path_fs); +class ClientMessage { + std::string channel, message; -/** - * Frees a list returned by exclude_list_load(). - */ -void -exclude_list_free(GSList *list); +public: + template<typename T, typename U> + ClientMessage(T &&_channel, U &&_message) + :channel(std::forward<T>(_channel)), + message(std::forward<U>(_message)) {} -/** - * Checks whether one of the patterns in the .mpdignore file matches - * the specified file name. - */ + const char *GetChannel() const { + return channel.c_str(); + } + + const char *GetMessage() const { + return message.c_str(); + } +}; + +gcc_pure bool -exclude_list_check(GSList *list, const char *name_fs); +client_message_valid_channel_name(const char *name); #endif diff --git a/src/client_new.c b/src/ClientNew.cxx index cf28c43c..a416c1f8 100644 --- a/src/client_new.c +++ b/src/ClientNew.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,12 +18,15 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" +#include "ClientList.hxx" +#include "Partition.hxx" +#include "Main.hxx" #include "fd_util.h" -#include "fifo_buffer.h" +extern "C" { #include "resolver.h" -#include "permission.h" -#include "glib_socket.h" +} +#include "Permission.hxx" #include <assert.h> #include <sys/types.h> @@ -43,15 +46,28 @@ 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(struct player_control *player_control, +client_new(EventLoop &loop, Partition &partition, int fd, const struct sockaddr *sa, size_t sa_length, int uid) { static unsigned int next_client_num; - struct client *client; char *remote; - assert(player_control != NULL); assert(fd >= 0); #ifdef HAVE_LIBWRAP @@ -79,53 +95,18 @@ client_new(struct player_control *player_control, } #endif /* HAVE_WRAP */ - if (client_list_is_full()) { + if (client_list->IsFull()) { g_warning("Max Connections Reached!"); close_socket(fd); return; } - client = g_new0(struct client, 1); - client->player_control = player_control; - - client->channel = g_io_channel_new_socket(fd); - /* GLib is responsible for closing the file descriptor */ - g_io_channel_set_close_on_unref(client->channel, true); - /* NULL encoding means the stream is binary safe; the MPD - protocol is UTF-8 only, but we are doing this call anyway - to prevent GLib from messing around with the stream */ - g_io_channel_set_encoding(client->channel, NULL, NULL); - /* we prefer to do buffering */ - g_io_channel_set_buffered(client->channel, false); - - client->source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - client_in_event, client); - - client->input = fifo_buffer_new(4096); - - client->permission = getDefaultPermissions(); - client->uid = uid; - - client->last_activity = g_timer_new(); - - client->cmd_list = NULL; - client->cmd_list_OK = -1; - client->cmd_list_size = 0; - - client->deferred_send = g_queue_new(); - client->deferred_bytes = 0; - client->num = next_client_num++; - - client->send_buf_used = 0; - - client->subscriptions = NULL; - client->messages = NULL; - client->num_messages = 0; + Client *client = new Client(loop, partition, fd, uid, + next_client_num++); (void)send(fd, GREETING, sizeof(GREETING) - 1, 0); - client_list_add(client); + client_list->Add(*client); remote = sockaddr_to_string(sa, sa_length, NULL); g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, @@ -133,33 +114,13 @@ client_new(struct player_control *player_control, g_free(remote); } -static void -deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct deferred_buffer *buffer = data; - g_free(buffer); -} - void -client_close(struct client *client) +Client::Close() { - client_list_remove(client); - - client_set_expired(client); + client_list->Remove(*this); - g_timer_destroy(client->last_activity); + SetExpired(); - if (client->cmd_list) { - free_cmd_list(client->cmd_list); - client->cmd_list = NULL; - } - - g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL); - g_queue_free(client->deferred_send); - - fifo_buffer_free(client->input); - - g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, - "[%u] closed", client->num); - g_free(client); + g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, "[%u] closed", num); + delete this; } diff --git a/src/client_process.c b/src/ClientProcess.cxx index 57a8a782..bcd20d1b 100644 --- a/src/client_process.c +++ b/src/ClientProcess.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,9 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" +#include "protocol/Result.hxx" +#include "AllCommands.hxx" #include <string.h> @@ -27,19 +29,20 @@ #define CLIENT_LIST_MODE_END "command_list_end" static enum command_return -client_process_command_list(struct client *client, bool list_ok, GSList *list) +client_process_command_list(Client *client, bool list_ok, + std::list<std::string> &&list) { enum command_return ret = COMMAND_RETURN_OK; unsigned num = 0; - for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) { - char *cmd = cur->data; + for (auto &&i : list) { + char *cmd = &*i.begin(); g_debug("command_process_list: process command \"%s\"", cmd); ret = command_process(client, num++, cmd); g_debug("command_process_list: command returned %i", ret); - if (ret != COMMAND_RETURN_OK || client_is_expired(client)) + if (ret != COMMAND_RETURN_OK || client->IsExpired()) break; else if (list_ok) client_puts(client, "list_OK\n"); @@ -49,7 +52,7 @@ client_process_command_list(struct client *client, bool list_ok, GSList *list) } enum command_return -client_process_line(struct client *client, char *line) +client_process_line(Client *client, char *line) { enum command_return ret; @@ -58,7 +61,6 @@ client_process_line(struct client *client, char *line) /* send empty idle response and leave idle mode */ client->idle_waiting = false; command_success(client); - client_write_output(client); } /* do nothing if the client wasn't idling: the client @@ -74,55 +76,44 @@ client_process_line(struct client *client, char *line) return COMMAND_RETURN_CLOSE; } - if (client->cmd_list_OK >= 0) { + if (client->cmd_list.IsActive()) { if (strcmp(line, CLIENT_LIST_MODE_END) == 0) { g_debug("[%u] process command list", client->num); - /* for scalability reasons, we have prepended - each new command; now we have to reverse it - to restore the correct order */ - client->cmd_list = g_slist_reverse(client->cmd_list); + auto &&cmd_list = client->cmd_list.Commit(); ret = client_process_command_list(client, - client->cmd_list_OK, - client->cmd_list); + client->cmd_list.IsOKMode(), + std::move(cmd_list)); g_debug("[%u] process command " "list returned %i", client->num, ret); if (ret == COMMAND_RETURN_CLOSE || - client_is_expired(client)) + client->IsExpired()) return COMMAND_RETURN_CLOSE; if (ret == COMMAND_RETURN_OK) command_success(client); - client_write_output(client); - free_cmd_list(client->cmd_list); - client->cmd_list = NULL; - client->cmd_list_OK = -1; + client->cmd_list.Reset(); } else { - size_t len = strlen(line) + 1; - client->cmd_list_size += len; - if (client->cmd_list_size > - client_max_command_list_size) { - g_warning("[%u] command list size (%lu) " + if (!client->cmd_list.Add(line)) { + g_warning("[%u] command list size " "is larger than the max (%lu)", client->num, - (unsigned long)client->cmd_list_size, (unsigned long)client_max_command_list_size); return COMMAND_RETURN_CLOSE; } - new_cmd_list_ptr(client, line); ret = COMMAND_RETURN_OK; } } else { if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) { - client->cmd_list_OK = 0; + client->cmd_list.Begin(false); ret = COMMAND_RETURN_OK; } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) { - client->cmd_list_OK = 1; + client->cmd_list.Begin(true); ret = COMMAND_RETURN_OK; } else { g_debug("[%u] process command \"%s\"", @@ -132,13 +123,11 @@ client_process_line(struct client *client, char *line) client->num, ret); if (ret == COMMAND_RETURN_CLOSE || - client_is_expired(client)) + client->IsExpired()) return COMMAND_RETURN_CLOSE; if (ret == COMMAND_RETURN_OK) command_success(client); - - client_write_output(client); } } diff --git a/src/ClientRead.cxx b/src/ClientRead.cxx new file mode 100644 index 00000000..49c698bc --- /dev/null +++ b/src/ClientRead.cxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" +#include "Main.hxx" +#include "event/Loop.hxx" + +#include <assert.h> +#include <string.h> + +BufferedSocket::InputResult +Client::OnSocketInput(const void *data, size_t length) +{ + const char *p = (const char *)data; + const char *newline = (const char *)memchr(p, '\n', length); + if (newline == NULL) + return InputResult::MORE; + + TimeoutMonitor::ScheduleSeconds(client_timeout); + + char *line = g_strndup(p, newline - p); + BufferedSocket::ConsumeInput(newline + 1 - p); + + enum command_return result = client_process_line(this, line); + g_free(line); + + switch (result) { + case COMMAND_RETURN_OK: + case COMMAND_RETURN_IDLE: + case COMMAND_RETURN_ERROR: + break; + + case COMMAND_RETURN_KILL: + Close(); + main_loop->Break(); + return InputResult::CLOSED; + + case COMMAND_RETURN_CLOSE: + Close(); + return InputResult::CLOSED; + } + + if (IsExpired()) { + Close(); + return InputResult::CLOSED; + } + + return InputResult::AGAIN; +} diff --git a/src/ClientSubscribe.cxx b/src/ClientSubscribe.cxx new file mode 100644 index 00000000..918a621d --- /dev/null +++ b/src/ClientSubscribe.cxx @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientSubscribe.hxx" +#include "ClientInternal.hxx" +#include "Idle.hxx" + +#include <assert.h> +#include <string.h> + +enum client_subscribe_result +client_subscribe(Client *client, const char *channel) +{ + assert(client != NULL); + assert(channel != NULL); + + if (!client_message_valid_channel_name(channel)) + return CLIENT_SUBSCRIBE_INVALID; + + if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) + return CLIENT_SUBSCRIBE_FULL; + + auto r = client->subscriptions.insert(channel); + if (!r.second) + return CLIENT_SUBSCRIBE_ALREADY; + + ++client->num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + return CLIENT_SUBSCRIBE_OK; +} + +bool +client_unsubscribe(Client *client, const char *channel) +{ + const auto i = client->subscriptions.find(channel); + if (i == client->subscriptions.end()) + return false; + + assert(client->num_subscriptions > 0); + + client->subscriptions.erase(i); + --client->num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + assert((client->num_subscriptions == 0) == + client->subscriptions.empty()); + + return true; +} + +void +client_unsubscribe_all(Client *client) +{ + client->subscriptions.clear(); + client->num_subscriptions = 0; +} + +bool +client_push_message(Client *client, const ClientMessage &msg) +{ + assert(client != NULL); + + if (client->messages.size() >= CLIENT_MAX_MESSAGES || + !client->IsSubscribed(msg.GetChannel())) + return false; + + if (client->messages.empty()) + client->IdleAdd(IDLE_MESSAGE); + + client->messages.push_back(msg); + return true; +} diff --git a/src/client_subscribe.h b/src/ClientSubscribe.hxx index 09f86441..83c234db 100644 --- a/src/client_subscribe.h +++ b/src/ClientSubscribe.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,14 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_CLIENT_SUBSCRIBE_H -#define MPD_CLIENT_SUBSCRIBE_H +#ifndef MPD_CLIENT_SUBSCRIBE_HXX +#define MPD_CLIENT_SUBSCRIBE_HXX -#include <stdbool.h> -#include <glib.h> +#include "gcc.h" -struct client; -struct client_message; +class Client; +class ClientMessage; enum client_subscribe_result { /** success */ @@ -41,19 +40,15 @@ enum client_subscribe_result { }; enum client_subscribe_result -client_subscribe(struct client *client, const char *channel); +client_subscribe(Client *client, const char *channel); bool -client_unsubscribe(struct client *client, const char *channel); +client_unsubscribe(Client *client, const char *channel); void -client_unsubscribe_all(struct client *client); +client_unsubscribe_all(Client *client); bool -client_push_message(struct client *client, const struct client_message *msg); - -G_GNUC_MALLOC -GSList * -client_read_messages(struct client *client); +client_push_message(Client *client, const ClientMessage &msg); #endif diff --git a/src/ClientWrite.cxx b/src/ClientWrite.cxx new file mode 100644 index 00000000..23b515a3 --- /dev/null +++ b/src/ClientWrite.cxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" + +#include <string.h> +#include <stdio.h> + +/** + * Write a block of data to the client. + */ +static void +client_write(Client *client, const char *data, size_t length) +{ + /* if the client is going to be closed, do nothing */ + if (client->IsExpired() || length == 0) + return; + + client->Write(data, length); +} + +void +client_puts(Client *client, const char *s) +{ + client_write(client, s, strlen(s)); +} + +void +client_vprintf(Client *client, const char *fmt, va_list args) +{ +#ifndef G_OS_WIN32 + va_list tmp; + int length; + + va_copy(tmp, args); + length = vsnprintf(NULL, 0, fmt, tmp); + va_end(tmp); + + if (length <= 0) + /* wtf.. */ + return; + + char *buffer = (char *)g_malloc(length + 1); + vsnprintf(buffer, length + 1, fmt, args); + client_write(client, buffer, length); + g_free(buffer); +#else + /* On mingw32, snprintf() expects a 64 bit integer instead of + a "long int" for "%li". This is not consistent with our + expectation, so we're using plain sprintf() here, hoping + the static buffer is large enough. Sorry for this hack, + but WIN32 development is so painful, I'm not in the mood to + do it properly now. */ + + static char buffer[4096]; + vsprintf(buffer, fmt, args); + client_write(client, buffer, strlen(buffer)); +#endif +} + +G_GNUC_PRINTF(2, 3) +void +client_printf(Client *client, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + client_vprintf(client, fmt, args); + va_end(args); +} diff --git a/src/CommandError.cxx b/src/CommandError.cxx new file mode 100644 index 00000000..7e777d82 --- /dev/null +++ b/src/CommandError.cxx @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CommandError.hxx" +#include "db_error.h" +#include "io_error.h" +#include "protocol/Result.hxx" + +#include <assert.h> +#include <errno.h> + +enum command_return +print_playlist_result(Client *client, enum playlist_result result) +{ + switch (result) { + case PLAYLIST_RESULT_SUCCESS: + return COMMAND_RETURN_OK; + + case PLAYLIST_RESULT_ERRNO: + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(errno)); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_DENIED: + command_error(client, ACK_ERROR_PERMISSION, "Access denied"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NO_SUCH_SONG: + command_error(client, ACK_ERROR_NO_EXIST, "No such song"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NO_SUCH_LIST: + command_error(client, ACK_ERROR_NO_EXIST, "No such playlist"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_LIST_EXISTS: + command_error(client, ACK_ERROR_EXIST, + "Playlist already exists"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_BAD_NAME: + command_error(client, ACK_ERROR_ARG, + "playlist name is invalid: " + "playlist names may not contain slashes," + " newlines or carriage returns"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_BAD_RANGE: + command_error(client, ACK_ERROR_ARG, "Bad song index"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NOT_PLAYING: + command_error(client, ACK_ERROR_PLAYER_SYNC, "Not playing"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_TOO_LARGE: + command_error(client, ACK_ERROR_PLAYLIST_MAX, + "playlist is at the max size"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_DISABLED: + command_error(client, ACK_ERROR_UNKNOWN, + "stored playlist support is disabled"); + return COMMAND_RETURN_ERROR; + } + + assert(0); + return COMMAND_RETURN_ERROR; +} + +/** + * Send the GError to the client and free the GError. + */ +enum command_return +print_error(Client *client, GError *error) +{ + assert(client != NULL); + assert(error != NULL); + + g_warning("%s", error->message); + + if (error->domain == playlist_quark()) { + enum playlist_result result = (playlist_result)error->code; + g_error_free(error); + return print_playlist_result(client, result); + } else if (error->domain == ack_quark()) { + command_error(client, (ack)error->code, "%s", error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } else if (error->domain == db_quark()) { + switch ((enum db_error)error->code) { + case DB_DISABLED: + command_error(client, ACK_ERROR_NO_EXIST, "%s", + error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + + case DB_NOT_FOUND: + g_error_free(error); + command_error(client, ACK_ERROR_NO_EXIST, "Not found"); + return COMMAND_RETURN_ERROR; + } + } else if (error->domain == errno_quark()) { + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(error->code)); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } else if (error->domain == g_file_error_quark()) { + command_error(client, ACK_ERROR_SYSTEM, "%s", error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } + + g_error_free(error); + command_error(client, ACK_ERROR_UNKNOWN, "error"); + return COMMAND_RETURN_ERROR; +} diff --git a/src/CommandError.hxx b/src/CommandError.hxx new file mode 100644 index 00000000..5fb02122 --- /dev/null +++ b/src/CommandError.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_COMMAND_ERROR_HXX +#define MPD_COMMAND_ERROR_HXX + +#include "command.h" +#include "playlist_error.h" +#include "gerror.h" + +class Client; + +enum command_return +print_playlist_result(Client *client, enum playlist_result result); + +/** + * Send the GError to the client and free the GError. + */ +enum command_return +print_error(Client *client, GError *error); + +#endif diff --git a/src/cmdline.c b/src/CommandLine.cxx index cb7eff36..0de211fd 100644 --- a/src/cmdline.c +++ b/src/CommandLine.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,21 +18,21 @@ */ #include "config.h" -#include "cmdline.h" -#include "path.h" -#include "log.h" +#include "CommandLine.hxx" +#include "ls.hxx" +#include "Log.hxx" #include "conf.h" -#include "decoder_list.h" +#include "DecoderList.hxx" #include "decoder_plugin.h" -#include "output_list.h" +#include "OutputList.hxx" #include "output_plugin.h" -#include "input_registry.h" -#include "input_plugin.h" -#include "playlist_list.h" -#include "playlist_plugin.h" -#include "ls.h" +#include "InputRegistry.hxx" +#include "InputPlugin.hxx" +#include "PlaylistRegistry.hxx" +#include "PlaylistPlugin.hxx" #include "mpd_error.h" -#include "glib_compat.h" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" #ifdef ENABLE_ENCODER #include "encoder_list.h" @@ -40,8 +40,8 @@ #endif #ifdef ENABLE_ARCHIVE -#include "archive_list.h" -#include "archive_plugin.h" +#include "ArchiveList.hxx" +#include "ArchivePlugin.hxx" #endif #include <glib.h> @@ -134,6 +134,16 @@ static void version(void) static const char *summary = "Music Player Daemon - a daemon for playing music."; +gcc_pure +static Path +PathBuildChecked(const Path &a, Path::const_pointer b) +{ + if (a.IsNull()) + return Path::Null(); + + return Path::Build(a, b); +} + bool parse_cmdline(int argc, char **argv, struct options *options, GError **error_r) @@ -159,7 +169,7 @@ parse_cmdline(int argc, char **argv, struct options *options, "verbose logging", NULL }, { "version", 'V', 0, G_OPTION_ARG_NONE, &option_version, "print version number", NULL }, - { .long_name = NULL } + { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } }; options->kill = false; @@ -192,59 +202,44 @@ parse_cmdline(int argc, char **argv, struct options *options, return true; } else if (argc <= 1) { /* default configuration file path */ - char *path1; #ifdef G_OS_WIN32 - path1 = g_build_filename(g_get_user_config_dir(), - CONFIG_FILE_LOCATION, NULL); - if (g_file_test(path1, G_FILE_TEST_IS_REGULAR)) - ret = config_read_file(path1, error_r); - else { - int i = 0; - char *system_path = NULL; - const char * const *system_config_dirs; - - system_config_dirs = g_get_system_config_dirs(); - - while(system_config_dirs[i] != NULL) { - system_path = g_build_filename(system_config_dirs[i], - CONFIG_FILE_LOCATION, - NULL); - if(g_file_test(system_path, - G_FILE_TEST_IS_REGULAR)) { - ret = config_read_file(system_path,error_r); - g_free(system_path); - break; - } else - g_free(system_path); - ++i; - } + Path path = PathBuildChecked(Path::FromUTF8(g_get_user_config_dir()), + CONFIG_FILE_LOCATION); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error_r); + + const char *const*system_config_dirs = + g_get_system_config_dirs(); + + for (unsigned i = 0; system_config_dirs[i] != nullptr; ++i) { + path = PathBuildChecked(Path::FromUTF8(system_config_dirs[i]), + CONFIG_FILE_LOCATION); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error_r); } #else /* G_OS_WIN32 */ - char *path2; - path1 = g_build_filename(g_get_home_dir(), - USER_CONFIG_FILE_LOCATION1, NULL); - path2 = g_build_filename(g_get_home_dir(), - USER_CONFIG_FILE_LOCATION2, NULL); - if (g_file_test(path1, G_FILE_TEST_IS_REGULAR)) - ret = config_read_file(path1, error_r); - else if (g_file_test(path2, G_FILE_TEST_IS_REGULAR)) - ret = config_read_file(path2, error_r); - else if (g_file_test(SYSTEM_CONFIG_FILE_LOCATION, - G_FILE_TEST_IS_REGULAR)) - ret = config_read_file(SYSTEM_CONFIG_FILE_LOCATION, - error_r); + Path path = PathBuildChecked(Path::FromUTF8(g_get_home_dir()), + USER_CONFIG_FILE_LOCATION1); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error_r); + + path = PathBuildChecked(Path::FromUTF8(g_get_home_dir()), + USER_CONFIG_FILE_LOCATION2); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error_r); + + path = Path::FromUTF8(SYSTEM_CONFIG_FILE_LOCATION); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error_r); #endif - g_free(path1); -#ifndef G_OS_WIN32 - g_free(path2); -#endif - - return ret; + g_set_error(error_r, cmdline_quark(), 0, + "No configuration file found"); + return false; } else if (argc == 2) { /* specified configuration file */ - return config_read_file(argv[1], error_r); + return ReadConfigFile(Path::FromFS(argv[1]), error_r); } else { g_set_error(error_r, cmdline_quark(), 0, "too many arguments"); diff --git a/src/cmdline.h b/src/CommandLine.hxx index 68f625a6..7a8731f8 100644 --- a/src/cmdline.h +++ b/src/CommandLine.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef CMDLINE_H -#define CMDLINE_H +#ifndef MPD_COMMAND_LINE_HXX +#define MPD_COMMAND_LINE_HXX #include <glib.h> -#include <stdbool.h> - struct options { gboolean kill; gboolean daemon; diff --git a/src/CommandListBuilder.cxx b/src/CommandListBuilder.cxx new file mode 100644 index 00000000..cc10f720 --- /dev/null +++ b/src/CommandListBuilder.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CommandListBuilder.hxx" +#include "ClientInternal.hxx" + +#include <string.h> + +void +CommandListBuilder::Reset() +{ + list.clear(); + mode = Mode::DISABLED; +} + +bool +CommandListBuilder::Add(const char *cmd) +{ + size_t len = strlen(cmd) + 1; + size += len; + if (size > client_max_command_list_size) + return false; + + list.emplace_back(cmd); + return true; +} diff --git a/src/CommandListBuilder.hxx b/src/CommandListBuilder.hxx new file mode 100644 index 00000000..a112ac33 --- /dev/null +++ b/src/CommandListBuilder.hxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_COMMAND_LIST_BUILDER_HXX +#define MPD_COMMAND_LIST_BUILDER_HXX + +#include <list> +#include <string> + +#include <assert.h> + +class CommandListBuilder { + /** + * print OK after each command execution + */ + enum class Mode { + /** + * Not active. + */ + DISABLED = -1, + + /** + * Enabled in normal list mode. + */ + ENABLED = false, + + /** + * Enabled in "list_OK" mode. + */ + OK = true, + } mode; + + /** + * for when in list mode + */ + std::list<std::string> list; + + /** + * Memory consumed by the list. + */ + size_t size; + +public: + CommandListBuilder() + :mode(Mode::DISABLED), size(0) {} + + /** + * Is a command list currently being built? + */ + bool IsActive() const { + return mode != Mode::DISABLED; + } + + /** + * Is the object in "list_OK" mode? + */ + bool IsOKMode() const { + assert(IsActive()); + + return (bool)mode; + } + + /** + * Reset the object: delete the list and clear the mode. + */ + void Reset(); + + /** + * Begin building a command list. + */ + void Begin(bool ok) { + assert(list.empty()); + assert(mode == Mode::DISABLED); + + mode = (Mode)ok; + } + + /** + * @return false if the list is full + */ + bool Add(const char *cmd); + + /** + * Finishes the list and returns it. + */ + std::list<std::string> &&Commit() { + assert(IsActive()); + + return std::move(list); + } +}; + +#endif diff --git a/src/ConfigData.cxx b/src/ConfigData.cxx new file mode 100644 index 00000000..48e9612d --- /dev/null +++ b/src/ConfigData.cxx @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ConfigData.hxx" +#include "ConfigParser.hxx" +#include "mpd_error.h" + +extern "C" { +#include "utils.h" +} + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +config_param::config_param(const char *_value, int _line) + :next(nullptr), value(g_strdup(_value)), line(_line) {} + +config_param::~config_param() +{ + delete next; + g_free(value); +} + +const block_param * +config_param::GetBlockParam(const char *name) const +{ + for (const auto &i : block_params) { + if (i.name == name) { + i.used = true; + return &i; + } + } + + return NULL; +} + +const char * +config_get_block_string(const struct config_param *param, const char *name, + const char *default_value) +{ + if (param == nullptr) + return default_value; + + const block_param *bp = param->GetBlockParam(name); + if (bp == NULL) + return default_value; + + return bp->value.c_str(); +} + +char * +config_dup_block_string(const struct config_param *param, const char *name, + const char *default_value) +{ + return g_strdup(config_get_block_string(param, name, default_value)); +} + +char * +config_dup_block_path(const struct config_param *param, const char *name, + GError **error_r) +{ + assert(error_r != NULL); + assert(*error_r == NULL); + + if (param == nullptr) + return nullptr; + + const block_param *bp = param->GetBlockParam(name); + if (bp == NULL) + return NULL; + + char *path = parsePath(bp->value.c_str(), error_r); + if (G_UNLIKELY(path == NULL)) + g_prefix_error(error_r, + "Invalid path in \"%s\" at line %i: ", + name, bp->line); + + return path; +} + +unsigned +config_get_block_unsigned(const struct config_param *param, const char *name, + unsigned default_value) +{ + if (param == nullptr) + return default_value; + + const block_param *bp = param->GetBlockParam(name); + if (bp == NULL) + return default_value; + + char *endptr; + long value = strtol(bp->value.c_str(), &endptr, 0); + if (*endptr != 0) + MPD_ERROR("Not a valid number in line %i", bp->line); + + if (value < 0) + MPD_ERROR("Not a positive number in line %i", bp->line); + + return (unsigned)value; +} + +bool +config_get_block_bool(const struct config_param *param, const char *name, + bool default_value) +{ + if (param == nullptr) + return default_value; + + const block_param *bp = param->GetBlockParam(name); + bool success, value; + + if (bp == NULL) + return default_value; + + success = get_bool(bp->value.c_str(), &value); + if (!success) + MPD_ERROR("%s is not a boolean value (yes, true, 1) or " + "(no, false, 0) on line %i\n", + name, bp->line); + + return value; +} diff --git a/src/ConfigData.hxx b/src/ConfigData.hxx new file mode 100644 index 00000000..f3e661b2 --- /dev/null +++ b/src/ConfigData.hxx @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIG_DATA_HXX +#define MPD_CONFIG_DATA_HXX + +#include "ConfigOption.hxx" +#include "gerror.h" +#include "gcc.h" + +#ifdef __cplusplus +#include <string> +#include <array> +#include <vector> +#endif + +#include <stdbool.h> + +#ifdef __cplusplus + +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) {} +}; + +#endif + +struct config_param { + /** + * The next config_param with the same name. The destructor + * deletes the whole chain. + */ + struct config_param *next; + + char *value; + unsigned int line; + +#ifdef __cplusplus + std::vector<block_param> block_params; + + /** + * This flag is false when nobody has queried the value of + * this option yet. + */ + bool used; + + config_param(int _line=-1) + :next(nullptr), value(nullptr), line(_line), used(false) {} + + gcc_nonnull_all + config_param(const char *_value, int _line=-1); + + config_param(const config_param &) = delete; + + ~config_param(); + + config_param &operator=(const config_param &) = delete; + + 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; +#endif +}; + +#ifdef __cplusplus + +struct ConfigData { + std::array<config_param *, std::size_t(CONF_MAX)> params; +}; + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +gcc_pure +const char * +config_get_block_string(const struct config_param *param, const char *name, + const char *default_value); + +gcc_malloc +char * +config_dup_block_string(const struct config_param *param, const char *name, + const char *default_value); + +/** + * Same as config_dup_path(), but looks up the setting in the + * specified block. + */ +gcc_malloc +char * +config_dup_block_path(const struct config_param *param, const char *name, + GError **error_r); + +gcc_pure +unsigned +config_get_block_unsigned(const struct config_param *param, const char *name, + unsigned default_value); + +gcc_pure +bool +config_get_block_bool(const struct config_param *param, const char *name, + bool default_value); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/ConfigFile.cxx b/src/ConfigFile.cxx new file mode 100644 index 00000000..e94f3f23 --- /dev/null +++ b/src/ConfigFile.cxx @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ConfigFile.hxx" +#include "ConfigQuark.hxx" +#include "ConfigData.hxx" +#include "ConfigTemplates.hxx" +#include "conf.h" + +extern "C" { +#include "string_util.h" +#include "tokenizer.h" +} + +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "config" + +#define MAX_STRING_SIZE MPD_PATH_MAX+80 + +#define CONF_COMMENT '#' + +static bool +config_read_name_value(struct config_param *param, char *input, unsigned line, + GError **error_r) +{ + const char *name = tokenizer_next_word(&input, error_r); + if (name == NULL) { + assert(*input != 0); + return false; + } + + const char *value = tokenizer_next_string(&input, error_r); + if (value == NULL) { + if (*input == 0) { + assert(error_r == NULL || *error_r == NULL); + g_set_error(error_r, config_quark(), 0, + "Value missing"); + } else { + assert(error_r == NULL || *error_r != NULL); + } + + return false; + } + + if (*input != 0 && *input != CONF_COMMENT) { + g_set_error(error_r, config_quark(), 0, + "Unknown tokens after value"); + return false; + } + + const struct block_param *bp = param->GetBlockParam(name); + if (bp != NULL) { + g_set_error(error_r, config_quark(), 0, + "\"%s\" is duplicate, first defined on line %i", + name, bp->line); + return false; + } + + param->AddBlockParam(name, value, line); + return true; +} + +static struct config_param * +config_read_block(FILE *fp, int *count, char *string, GError **error_r) +{ + struct config_param *ret = new config_param(*count); + GError *error = NULL; + + while (true) { + char *line; + + line = fgets(string, MAX_STRING_SIZE, fp); + if (line == NULL) { + delete ret; + g_set_error(error_r, config_quark(), 0, + "Expected '}' before end-of-file"); + return NULL; + } + + (*count)++; + line = strchug_fast(line); + if (*line == 0 || *line == CONF_COMMENT) + continue; + + if (*line == '}') { + /* end of this block; return from the function + (and from this "while" loop) */ + + line = strchug_fast(line + 1); + if (*line != 0 && *line != CONF_COMMENT) { + delete ret; + g_set_error(error_r, config_quark(), 0, + "line %i: Unknown tokens after '}'", + *count); + return nullptr; + } + + return ret; + } + + /* parse name and value */ + + if (!config_read_name_value(ret, line, *count, &error)) { + assert(*line != 0); + delete ret; + g_propagate_prefixed_error(error_r, error, + "line %i: ", *count); + return NULL; + } + } +} + +gcc_nonnull_all +static void +Append(config_param *&head, config_param *p) +{ + assert(p->next == nullptr); + + config_param **i = &head; + while (*i != nullptr) + i = &(*i)->next; + + *i = p; +} + +static bool +ReadConfigFile(ConfigData &config_data, FILE *fp, GError **error_r) +{ + assert(fp != nullptr); + + char string[MAX_STRING_SIZE + 1]; + int count = 0; + struct config_param *param; + + while (fgets(string, MAX_STRING_SIZE, fp)) { + char *line; + const char *name, *value; + GError *error = NULL; + + count++; + + line = strchug_fast(string); + if (*line == 0 || *line == CONF_COMMENT) + continue; + + /* the first token in each line is the name, followed + by either the value or '{' */ + + name = tokenizer_next_word(&line, &error); + if (name == NULL) { + assert(*line != 0); + g_propagate_prefixed_error(error_r, error, + "line %i: ", count); + return false; + } + + /* get the definition of that option, and check the + "repeatable" flag */ + + const ConfigOption o = ParseConfigOptionName(name); + if (o == CONF_MAX) { + g_set_error(error_r, config_quark(), 0, + "unrecognized parameter in config file at " + "line %i: %s\n", count, name); + return false; + } + + const unsigned i = unsigned(o); + const ConfigTemplate &option = config_templates[i]; + config_param *&head = config_data.params[i]; + + if (head != nullptr && !option.repeatable) { + param = head; + g_set_error(error_r, config_quark(), 0, + "config parameter \"%s\" is first defined " + "on line %i and redefined on line %i\n", + name, param->line, count); + return false; + } + + /* now parse the block or the value */ + + if (option.block) { + /* it's a block, call config_read_block() */ + + if (*line != '{') { + g_set_error(error_r, config_quark(), 0, + "line %i: '{' expected", count); + return false; + } + + line = strchug_fast(line + 1); + if (*line != 0 && *line != CONF_COMMENT) { + g_set_error(error_r, config_quark(), 0, + "line %i: Unknown tokens after '{'", + count); + return false; + } + + param = config_read_block(fp, &count, string, error_r); + if (param == NULL) { + return false; + } + } else { + /* a string value */ + + value = tokenizer_next_string(&line, &error); + if (value == NULL) { + if (*line == 0) + g_set_error(error_r, config_quark(), 0, + "line %i: Value missing", + count); + else { + g_set_error(error_r, config_quark(), 0, + "line %i: %s", count, + error->message); + g_error_free(error); + } + + return false; + } + + if (*line != 0 && *line != CONF_COMMENT) { + g_set_error(error_r, config_quark(), 0, + "line %i: Unknown tokens after value", + count); + return false; + } + + param = new config_param(value, count); + } + + Append(head, param); + } + + return true; +} + +bool +ReadConfigFile(ConfigData &config_data, const Path &path, GError **error_r) +{ + assert(!path.IsNull()); + const std::string path_utf8 = path.ToUTF8(); + + g_debug("loading file %s", path_utf8.c_str()); + + FILE *fp = FOpen(path, FOpenMode::ReadText); + if (fp == nullptr) { + g_set_error(error_r, config_quark(), errno, + "Failed to open %s: %s", + path_utf8.c_str(), g_strerror(errno)); + return false; + } + + bool result = ReadConfigFile(config_data, fp, error_r); + fclose(fp); + return result; +} diff --git a/src/ConfigFile.hxx b/src/ConfigFile.hxx new file mode 100644 index 00000000..49c0d31e --- /dev/null +++ b/src/ConfigFile.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIG_FILE_HXX +#define MPD_CONFIG_FILE_HXX + +#include "gerror.h" + +class Path; +struct ConfigData; + +bool +ReadConfigFile(ConfigData &data, const Path &path, GError **error_r); + +#endif diff --git a/src/ConfigGlobal.cxx b/src/ConfigGlobal.cxx new file mode 100644 index 00000000..9786690d --- /dev/null +++ b/src/ConfigGlobal.cxx @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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" + +extern "C" { +#include "utils.h" +} + +#include "mpd_error.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "config" + +static ConfigData config_data; + +void config_global_finish(void) +{ + for (auto i : config_data.params) + delete i; +} + +void config_global_init(void) +{ +} + +bool +ReadConfigFile(const Path &path, GError **error_r) +{ + return ReadConfigFile(config_data, path, error_r); +} + +static void +Check(const config_param *param) +{ + if (!param->used) + /* this whole config_param was not queried at all - + the feature might be disabled at compile time? + Silently ignore it here. */ + return; + + for (const auto &i : param->block_params) { + if (!i.used) + g_warning("option '%s' on line %i was not recognized", + i.name.c_str(), i.line); + } +} + +void config_global_check(void) +{ + for (auto i : config_data.params) + for (const config_param *p = i; p != nullptr; p = p->next) + Check(p); +} + +const struct config_param * +config_get_next_param(ConfigOption option, const struct config_param * last) +{ + config_param *param = last != nullptr + ? last->next + : config_data.params[unsigned(option)]; + if (param != nullptr) + param->used = true; + return param; +} + +const char * +config_get_string(ConfigOption option, const char *default_value) +{ + const struct config_param *param = config_get_param(option); + + if (param == NULL) + return default_value; + + return param->value; +} + +char * +config_dup_path(ConfigOption option, GError **error_r) +{ + assert(error_r != NULL); + assert(*error_r == NULL); + + const struct config_param *param = config_get_param(option); + if (param == NULL) + return NULL; + + char *path = parsePath(param->value, error_r); + if (G_UNLIKELY(path == NULL)) + g_prefix_error(error_r, + "Invalid path at line %i: ", + param->line); + + return path; +} + +unsigned +config_get_unsigned(ConfigOption option, unsigned default_value) +{ + const struct config_param *param = config_get_param(option); + long value; + char *endptr; + + if (param == NULL) + return default_value; + + value = strtol(param->value, &endptr, 0); + if (*endptr != 0 || value < 0) + MPD_ERROR("Not a valid non-negative number in line %i", + param->line); + + return (unsigned)value; +} + +unsigned +config_get_positive(ConfigOption option, unsigned default_value) +{ + const struct config_param *param = config_get_param(option); + long value; + char *endptr; + + if (param == NULL) + return default_value; + + value = strtol(param->value, &endptr, 0); + if (*endptr != 0) + MPD_ERROR("Not a valid number in line %i", param->line); + + if (value <= 0) + MPD_ERROR("Not a positive number in line %i", param->line); + + return (unsigned)value; +} + +bool +config_get_bool(ConfigOption option, bool default_value) +{ + const struct config_param *param = config_get_param(option); + bool success, value; + + if (param == NULL) + return default_value; + + success = get_bool(param->value, &value); + if (!success) + MPD_ERROR("Expected boolean value (yes, true, 1) or " + "(no, false, 0) on line %i\n", + param->line); + + return value; +} diff --git a/src/ConfigGlobal.hxx b/src/ConfigGlobal.hxx new file mode 100644 index 00000000..9abfb2b5 --- /dev/null +++ b/src/ConfigGlobal.hxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIG_GLOBAL_HXX +#define MPD_CONFIG_GLOBAL_HXX + +#include "ConfigOption.hxx" +#include "gerror.h" +#include "gcc.h" + +#include <stdbool.h> +#include <stddef.h> + +#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16) +#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false + +#ifdef __cplusplus +class Path; +#endif + +void config_global_init(void); +void config_global_finish(void); + +/** + * Call this function after all configuration has been evaluated. It + * checks for unused parameters, and logs warnings. + */ +void config_global_check(void); + +#ifdef __cplusplus + +bool +ReadConfigFile(const Path &path, GError **error_r); + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* don't free the returned value + set _last_ to NULL to get first entry */ +gcc_pure +const struct config_param * +config_get_next_param(enum ConfigOption option, + const struct config_param *last); + +gcc_pure +static inline const struct config_param * +config_get_param(enum ConfigOption option) +{ + return config_get_next_param(option, NULL); +} + +/* Note on gcc_pure: Some of the functions declared pure are not + really pure in strict sense. They have side effect such that they + validate parameter's value and signal an error if it's invalid. + However, if the argument was already validated or we don't care + about the argument at all, this may be ignored so in the end, we + should be fine with calling those functions pure. */ + +gcc_pure +const char * +config_get_string(enum ConfigOption option, const char *default_value); + +/** + * Returns an optional configuration variable which contains an + * absolute path. If there is a tilde prefix, it is expanded. + * Returns NULL if the value is not present. If the path could not be + * parsed, returns NULL and sets the error. + * + * The return value must be freed with g_free(). + */ +gcc_malloc +char * +config_dup_path(enum ConfigOption option, GError **error_r); + +gcc_pure +unsigned +config_get_unsigned(enum ConfigOption option, unsigned default_value); + +gcc_pure +unsigned +config_get_positive(enum ConfigOption option, unsigned default_value); + +gcc_pure +bool config_get_bool(enum ConfigOption option, bool default_value); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/ConfigOption.hxx b/src/ConfigOption.hxx new file mode 100644 index 00000000..21a3a02e --- /dev/null +++ b/src/ConfigOption.hxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIG_OPTION_HXX +#define MPD_CONFIG_OPTION_HXX + +#include "gcc.h" + +enum ConfigOption { + CONF_MUSIC_DIR, + CONF_PLAYLIST_DIR, + CONF_FOLLOW_INSIDE_SYMLINKS, + CONF_FOLLOW_OUTSIDE_SYMLINKS, + CONF_DB_FILE, + CONF_STICKER_FILE, + CONF_LOG_FILE, + CONF_PID_FILE, + CONF_STATE_FILE, + CONF_RESTORE_PAUSED, + CONF_USER, + CONF_GROUP, + CONF_BIND_TO_ADDRESS, + CONF_PORT, + CONF_LOG_LEVEL, + CONF_ZEROCONF_NAME, + CONF_ZEROCONF_ENABLED, + CONF_PASSWORD, + CONF_DEFAULT_PERMS, + CONF_AUDIO_OUTPUT, + CONF_AUDIO_OUTPUT_FORMAT, + CONF_MIXER_TYPE, + CONF_REPLAYGAIN, + CONF_REPLAYGAIN_PREAMP, + CONF_REPLAYGAIN_MISSING_PREAMP, + CONF_REPLAYGAIN_LIMIT, + CONF_VOLUME_NORMALIZATION, + CONF_SAMPLERATE_CONVERTER, + CONF_AUDIO_BUFFER_SIZE, + CONF_BUFFER_BEFORE_PLAY, + CONF_HTTP_PROXY_HOST, + CONF_HTTP_PROXY_PORT, + CONF_HTTP_PROXY_USER, + CONF_HTTP_PROXY_PASSWORD, + CONF_CONN_TIMEOUT, + CONF_MAX_CONN, + CONF_MAX_PLAYLIST_LENGTH, + CONF_MAX_COMMAND_LIST_SIZE, + CONF_MAX_OUTPUT_BUFFER_SIZE, + CONF_FS_CHARSET, + CONF_ID3V1_ENCODING, + CONF_METADATA_TO_USE, + CONF_SAVE_ABSOLUTE_PATHS, + CONF_DECODER, + CONF_INPUT, + CONF_GAPLESS_MP3_PLAYBACK, + CONF_PLAYLIST_PLUGIN, + CONF_AUTO_UPDATE, + CONF_AUTO_UPDATE_DEPTH, + CONF_DESPOTIFY_USER, + CONF_DESPOTIFY_PASSWORD, + CONF_DESPOTIFY_HIGH_BITRATE, + CONF_AUDIO_FILTER, + CONF_DATABASE, + CONF_MAX +}; + +/** + * @return #CONF_MAX if not found + */ +gcc_pure +enum ConfigOption +ParseConfigOptionName(const char *name); + +#endif diff --git a/src/ConfigParser.cxx b/src/ConfigParser.cxx new file mode 100644 index 00000000..9798b6ed --- /dev/null +++ b/src/ConfigParser.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ConfigParser.hxx" + +extern "C" { +#include "string_util.h" +} + +bool +get_bool(const char *value, bool *value_r) +{ + static const char *t[] = { "yes", "true", "1", nullptr }; + static const char *f[] = { "no", "false", "0", nullptr }; + + if (string_array_contains(t, value)) { + *value_r = true; + return true; + } + + if (string_array_contains(f, value)) { + *value_r = false; + return true; + } + + return false; +} diff --git a/src/ConfigParser.hxx b/src/ConfigParser.hxx new file mode 100644 index 00000000..00fd42fe --- /dev/null +++ b/src/ConfigParser.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIG_PARSER_HXX +#define MPD_CONFIG_PARSER_HXX + +bool +get_bool(const char *value, bool *value_r); + +#endif diff --git a/src/ConfigQuark.hxx b/src/ConfigQuark.hxx new file mode 100644 index 00000000..11594f99 --- /dev/null +++ b/src/ConfigQuark.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIG_QUARK_HXX +#define MPD_CONFIG_QUARK_HXX + +#include <glib.h> + +/** + * A GQuark for GError instances, resulting from malformed + * configuration. + */ +G_GNUC_CONST +static inline GQuark +config_quark(void) +{ + return g_quark_from_static_string("config"); +} + +#endif diff --git a/src/ConfigTemplates.cxx b/src/ConfigTemplates.cxx new file mode 100644 index 00000000..6c6bf168 --- /dev/null +++ b/src/ConfigTemplates.cxx @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ConfigTemplates.hxx" +#include "ConfigOption.hxx" + +#include <string.h> + +const ConfigTemplate config_templates[] = { + { "music_directory", false, false }, + { "playlist_directory", false, false }, + { "follow_inside_symlinks", false, false }, + { "follow_outside_symlinks", false, false }, + { "db_file", false, false }, + { "sticker_file", false, false }, + { "log_file", false, false }, + { "pid_file", false, false }, + { "state_file", false, false }, + { "restore_paused", false, false }, + { "user", false, false }, + { "group", false, false }, + { "bind_to_address", true, false }, + { "port", false, false }, + { "log_level", false, false }, + { "zeroconf_name", false, false }, + { "zeroconf_enabled", false, false }, + { "password", true, false }, + { "default_permissions", false, false }, + { "audio_output", true, true }, + { "audio_output_format", false, false }, + { "mixer_type", false, false }, + { "replaygain", false, false }, + { "replaygain_preamp", false, false }, + { "replaygain_missing_preamp", false, false }, + { "replaygain_limit", false, false }, + { "volume_normalization", false, false }, + { "samplerate_converter", false, false }, + { "audio_buffer_size", false, false }, + { "buffer_before_play", false, false }, + { "http_proxy_host", false, false }, + { "http_proxy_port", false, false }, + { "http_proxy_user", false, false }, + { "http_proxy_password", false, false }, + { "connection_timeout", false, false }, + { "max_connections", false, false }, + { "max_playlist_length", false, false }, + { "max_command_list_size", false, false }, + { "max_output_buffer_size", false, false }, + { "filesystem_charset", false, false }, + { "id3v1_encoding", false, false }, + { "metadata_to_use", false, false }, + { "save_absolute_paths_in_playlists", false, false }, + { "decoder", true, true }, + { "input", true, true }, + { "gapless_mp3_playback", false, false }, + { "playlist_plugin", true, true }, + { "auto_update", false, false }, + { "auto_update_depth", false, false }, + { "despotify_user", false, false }, + { "despotify_password", false, false}, + { "despotify_high_bitrate", false, false }, + { "filter", true, true }, + { "database", false, true }, +}; + +static constexpr unsigned n_config_templates = + sizeof(config_templates) / sizeof(config_templates[0]); + +static_assert(n_config_templates == unsigned(CONF_MAX), + "Wrong number of config_templates"); + +ConfigOption +ParseConfigOptionName(const char *name) +{ + for (unsigned i = 0; i < n_config_templates; ++i) + if (strcmp(config_templates[i].name, name) == 0) + return ConfigOption(i); + + return CONF_MAX; +} diff --git a/src/ConfigTemplates.hxx b/src/ConfigTemplates.hxx new file mode 100644 index 00000000..4f546046 --- /dev/null +++ b/src/ConfigTemplates.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIG_TEMPLATES_HXX +#define MPD_CONFIG_TEMPLATES_HXX + +#include "ConfigOption.hxx" + +struct ConfigTemplate { + const char *const name; + const bool repeatable; + const bool block; +}; + +extern const ConfigTemplate config_templates[]; + +#endif diff --git a/src/crossfade.c b/src/CrossFade.cxx index 46a0dff3..0bdcc43d 100644 --- a/src/crossfade.c +++ b/src/CrossFade.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,13 @@ */ #include "config.h" -#include "crossfade.h" -#include "chunk.h" +#include "CrossFade.hxx" +#include "MusicChunk.hxx" #include "audio_format.h" #include "tag.h" +#include <cmath> + #include <assert.h> #include <string.h> #include <stdlib.h> @@ -82,9 +84,8 @@ static float mixramp_interpolate(char *ramp_list, float required_db) } /* If required db < any stored value, use the least. */ - if (isnan(last_db)) { + if (std::isnan(last_db)) return secs; - } /* Finally, interpolate linearly. */ secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db); @@ -114,13 +115,14 @@ unsigned cross_fade_calc(float duration, float total_time, chunks_f = (float)audio_format_time_to_size(af) / (float)CHUNK_SIZE; - if (isnan(mixramp_delay) || !(mixramp_start) || !(mixramp_prev_end)) { + if (std::isnan(mixramp_delay) || !mixramp_start || !mixramp_prev_end) { chunks = (chunks_f * duration + 0.5); } else { /* Calculate mixramp overlap. */ mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db - replay_gain_db) + mixramp_interpolate(mixramp_prev_end, mixramp_db - replay_gain_prev_db); - if (!isnan(mixramp_overlap) && (mixramp_delay <= mixramp_overlap)) { + if (!std::isnan(mixramp_overlap) && + mixramp_delay <= mixramp_overlap) { chunks = (chunks_f * (mixramp_overlap - mixramp_delay)); g_debug("will overlap %d chunks, %fs", chunks, mixramp_overlap - mixramp_delay); diff --git a/src/crossfade.h b/src/CrossFade.hxx index d581dbfe..1c467075 100644 --- a/src/crossfade.h +++ b/src/CrossFade.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_CROSSFADE_H -#define MPD_CROSSFADE_H +#ifndef MPD_CROSSFADE_HXX +#define MPD_CROSSFADE_HXX struct audio_format; struct music_chunk; diff --git a/src/DatabaseCommands.cxx b/src/DatabaseCommands.cxx new file mode 100644 index 00000000..bd5a48b3 --- /dev/null +++ b/src/DatabaseCommands.cxx @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseCommands.hxx" +#include "DatabaseQueue.hxx" +#include "DatabasePlaylist.hxx" +#include "DatabasePrint.hxx" +#include "DatabaseSelection.hxx" +#include "CommandError.hxx" +#include "ClientInternal.hxx" +#include "tag.h" +#include "uri.h" +#include "SongFilter.hxx" +#include "protocol/Result.hxx" + +#include <assert.h> +#include <string.h> + +enum command_return +handle_lsinfo2(Client *client, int argc, char *argv[]) +{ + const char *uri; + + if (argc == 2) + uri = argv[1]; + else + /* default is root directory */ + uri = ""; + + const DatabaseSelection selection(uri, false); + + GError *error = NULL; + if (!db_selection_print(client, selection, true, &error)) + return print_error(client, error); + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_match(Client *client, int argc, char *argv[], bool fold_case) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + const DatabaseSelection selection("", true, &filter); + + GError *error = NULL; + return db_selection_print(client, selection, true, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_find(Client *client, int argc, char *argv[]) +{ + return handle_match(client, argc, argv, false); +} + +enum command_return +handle_search(Client *client, int argc, char *argv[]) +{ + return handle_match(client, argc, argv, true); +} + +static enum command_return +handle_match_add(Client *client, int argc, char *argv[], bool fold_case) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + const DatabaseSelection selection("", true, &filter); + GError *error = NULL; + return AddFromDatabase(client->partition, selection, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_findadd(Client *client, int argc, char *argv[]) +{ + return handle_match_add(client, argc, argv, false); +} + +enum command_return +handle_searchadd(Client *client, int argc, char *argv[]) +{ + return handle_match_add(client, argc, argv, true); +} + +enum command_return +handle_searchaddpl(Client *client, int argc, char *argv[]) +{ + const char *playlist = argv[1]; + + SongFilter filter; + if (!filter.Parse(argc - 2, argv + 2, true)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + GError *error = NULL; + return search_add_to_playlist("", playlist, &filter, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_count(Client *client, int argc, char *argv[]) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, false)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + GError *error = NULL; + return searchStatsForSongsIn(client, "", &filter, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_listall(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *directory = ""; + + if (argc == 2) + directory = argv[1]; + + GError *error = NULL; + return printAllIn(client, directory, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_list(Client *client, int argc, char *argv[]) +{ + unsigned tagType = locate_parse_type(argv[1]); + + if (tagType == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]); + return COMMAND_RETURN_ERROR; + } + + if (tagType == LOCATE_TAG_ANY_TYPE) { + command_error(client, ACK_ERROR_ARG, + "\"any\" is not a valid return tag type"); + return COMMAND_RETURN_ERROR; + } + + /* for compatibility with < 0.12.0 */ + SongFilter *filter; + if (argc == 3) { + if (tagType != TAG_ALBUM) { + command_error(client, ACK_ERROR_ARG, + "should be \"%s\" for 3 arguments", + tag_item_names[TAG_ALBUM]); + return COMMAND_RETURN_ERROR; + } + + filter = new SongFilter((unsigned)TAG_ARTIST, argv[2]); + } else if (argc > 2) { + filter = new SongFilter(); + if (!filter->Parse(argc - 2, argv + 2, false)) { + delete filter; + command_error(client, ACK_ERROR_ARG, + "not able to parse args"); + return COMMAND_RETURN_ERROR; + } + } else + filter = nullptr; + + GError *error = NULL; + enum command_return ret = + listAllUniqueTags(client, tagType, filter, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); + + delete filter; + + return ret; +} + +enum command_return +handle_listallinfo(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *directory = ""; + + if (argc == 2) + directory = argv[1]; + + GError *error = NULL; + return printInfoForAllIn(client, directory, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} diff --git a/src/DatabaseCommands.hxx b/src/DatabaseCommands.hxx new file mode 100644 index 00000000..335adc4d --- /dev/null +++ b/src/DatabaseCommands.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_COMMANDS_HXX +#define MPD_DATABASE_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_lsinfo2(Client *client, int argc, char *argv[]); + +enum command_return +handle_find(Client *client, int argc, char *argv[]); + +enum command_return +handle_findadd(Client *client, int argc, char *argv[]); + +enum command_return +handle_search(Client *client, int argc, char *argv[]); + +enum command_return +handle_searchadd(Client *client, int argc, char *argv[]); + +enum command_return +handle_searchaddpl(Client *client, int argc, char *argv[]); + +enum command_return +handle_count(Client *client, int argc, char *argv[]); + +enum command_return +handle_listall(Client *client, int argc, char *argv[]); + +enum command_return +handle_list(Client *client, int argc, char *argv[]); + +enum command_return +handle_listallinfo(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/database.c b/src/DatabaseGlue.cxx index 8c903bb4..db7d4b9f 100644 --- a/src/database.c +++ b/src/DatabaseGlue.cxx @@ -18,17 +18,20 @@ */ #include "config.h" -#include "database.h" +#include "DatabaseGlue.hxx" +#include "DatabaseSimple.hxx" +#include "DatabaseRegistry.hxx" +#include "DatabaseSave.hxx" +#include "Directory.hxx" +#include "conf.h" + +extern "C" { #include "db_error.h" -#include "db_save.h" -#include "db_selection.h" -#include "db_visitor.h" -#include "db_plugin.h" -#include "db/simple_db_plugin.h" -#include "directory.h" #include "stats.h" -#include "conf.h" -#include "glib_compat.h" +} + +#include "DatabasePlugin.hxx" +#include "db/SimpleDatabasePlugin.hxx" #include <glib.h> @@ -42,97 +45,89 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "database" -static struct db *db; +static Database *db; static bool db_is_open; +static bool is_simple; bool -db_init(const struct config_param *path, GError **error_r) +DatabaseGlobalInit(const config_param *param, GError **error_r) { assert(db == NULL); assert(!db_is_open); - if (path == NULL) - return true; + const char *plugin_name = + config_get_block_string(param, "plugin", "simple"); + is_simple = strcmp(plugin_name, "simple") == 0; - struct config_param *param = config_new_param("database", path->line); - config_add_block_param(param, "path", path->value, path->line); - - db = db_plugin_new(&simple_db_plugin, param, error_r); - - config_param_free(param); + const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name); + if (plugin == NULL) { + g_set_error(error_r, db_quark(), 0, + "No such database plugin: %s", plugin_name); + return false; + } + db = plugin->create(param, error_r); return db != NULL; } void -db_finish(void) +DatabaseGlobalDeinit(void) { if (db_is_open) - db_plugin_close(db); + db->Close(); if (db != NULL) - db_plugin_free(db); + delete db; } -struct directory * -db_get_root(void) +const Database * +GetDatabase() { - assert(db != NULL); + assert(db == NULL || db_is_open); - return simple_db_get_root(db); + return db; } -struct directory * -db_get_directory(const char *name) +const Database * +GetDatabase(GError **error_r) { - if (db == NULL) - return NULL; + assert(db == nullptr || db_is_open); - struct directory *music_root = db_get_root(); - if (name == NULL) - return music_root; + if (db == nullptr) + g_set_error_literal(error_r, db_quark(), DB_DISABLED, + "No database"); - struct directory *directory = - directory_lookup_directory(music_root, name); - return directory; + return db; } -struct song * -db_get_song(const char *file) +bool +db_is_simple(void) { - assert(file != NULL); + assert(db == NULL || db_is_open); - g_debug("get song: %s", file); - - if (db == NULL) - return NULL; - - return db_plugin_get_song(db, file, NULL); + return is_simple; } -bool -db_visit(const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r) +Directory * +db_get_root(void) { - if (db == NULL) { - g_set_error_literal(error_r, db_quark(), DB_DISABLED, - "No database"); - return false; - } + assert(db != NULL); + assert(db_is_simple()); - return db_plugin_visit(db, selection, visitor, ctx, error_r); + return ((SimpleDatabase *)db)->GetRoot(); } -bool -db_walk(const char *uri, - const struct db_visitor *visitor, void *ctx, - GError **error_r) +Directory * +db_get_directory(const char *name) { - struct db_selection selection; - db_selection_init(&selection, uri, true); + if (db == NULL) + return NULL; + + Directory *music_root = db_get_root(); + if (name == NULL) + return music_root; - return db_visit(&selection, visitor, ctx, error_r); + return music_root->LookupDirectory(name); } bool @@ -140,17 +135,18 @@ db_save(GError **error_r) { assert(db != NULL); assert(db_is_open); + assert(db_is_simple()); - return simple_db_save(db, error_r); + return ((SimpleDatabase *)db)->Save(error_r); } bool -db_load(GError **error) +DatabaseGlobalOpen(GError **error) { assert(db != NULL); assert(!db_is_open); - if (!db_plugin_open(db, error)) + if (!db->Open(error)) return false; db_is_open = true; @@ -165,6 +161,7 @@ db_get_mtime(void) { assert(db != NULL); assert(db_is_open); + assert(db_is_simple()); - return simple_db_get_mtime(db); + return ((SimpleDatabase *)db)->GetLastModified(); } diff --git a/src/DatabaseGlue.hxx b/src/DatabaseGlue.hxx new file mode 100644 index 00000000..ea26f324 --- /dev/null +++ b/src/DatabaseGlue.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_GLUE_HXX +#define MPD_DATABASE_GLUE_HXX + +#include "gcc.h" +#include "gerror.h" + +struct config_param; +class Database; + +/** + * Initialize the database library. + * + * @param param the database configuration block + */ +bool +DatabaseGlobalInit(const config_param *param, GError **error_r); + +void +DatabaseGlobalDeinit(void); + +bool +DatabaseGlobalOpen(GError **error); + +/** + * Returns the global #Database instance. May return NULL if this MPD + * configuration has no database (no music_directory was configured). + */ +gcc_pure +const Database * +GetDatabase(); + +/** + * Returns the global #Database instance. May return NULL if this MPD + * configuration has no database (no music_directory was configured). + */ +gcc_pure +const Database * +GetDatabase(GError **error_r); + +#endif diff --git a/src/DatabaseHelpers.cxx b/src/DatabaseHelpers.cxx new file mode 100644 index 00000000..dc31a4bc --- /dev/null +++ b/src/DatabaseHelpers.cxx @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "DatabaseHelpers.hxx" +#include "DatabasePlugin.hxx" +#include "song.h" +#include "tag.h" + +#include <functional> +#include <set> + +#include <string.h> + +struct StringLess { + gcc_pure + bool operator()(const char *a, const char *b) const { + return strcmp(a, b) < 0; + } +}; + +typedef std::set<const char *, StringLess> StringSet; + +static bool +CollectTags(StringSet &set, enum tag_type tag_type, song &song) +{ + struct tag *tag = song.tag; + if (tag == nullptr) + return true; + + bool found = false; + for (unsigned i = 0; i < tag->num_items; ++i) { + if (tag->items[i]->type == tag_type) { + set.insert(tag->items[i]->value); + found = true; + } + } + + if (!found) + set.insert(""); + + return true; +} + +bool +VisitUniqueTags(const Database &db, const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r) +{ + StringSet set; + + using namespace std::placeholders; + const auto f = std::bind(CollectTags, std::ref(set), tag_type, _1); + if (!db.Visit(selection, f, error_r)) + return false; + + for (auto value : set) + if (!visit_string(value, error_r)) + return false; + + return true; +} + +static void +StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums, + const struct tag &tag) +{ + if (tag.time > 0) + stats.total_duration += tag.time; + + for (unsigned i = 0; i < tag.num_items; ++i) { + const struct tag_item &item = *tag.items[i]; + + switch (item.type) { + case TAG_ARTIST: + artists.insert(item.value); + break; + + case TAG_ALBUM: + albums.insert(item.value); + break; + + default: + break; + } + } +} + +static bool +StatsVisitSong(DatabaseStats &stats, StringSet &artists, StringSet &albums, + song &song) +{ + ++stats.song_count; + + if (song.tag != nullptr) + StatsVisitTag(stats, artists, albums, *song.tag); + + return true; +} + +bool +GetStats(const Database &db, const DatabaseSelection &selection, + DatabaseStats &stats, GError **error_r) +{ + stats.Clear(); + + StringSet artists, albums; + using namespace std::placeholders; + const auto f = std::bind(StatsVisitSong, + std::ref(stats), std::ref(artists), + std::ref(albums), _1); + if (!db.Visit(selection, f, error_r)) + return false; + + stats.artist_count = artists.size(); + stats.album_count = albums.size(); + return true; +} diff --git a/src/DatabaseHelpers.hxx b/src/DatabaseHelpers.hxx new file mode 100644 index 00000000..cfcc94ac --- /dev/null +++ b/src/DatabaseHelpers.hxx @@ -0,0 +1,41 @@ +/* + * 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_MEMORY_DATABASE_PLUGIN_HXX +#define MPD_MEMORY_DATABASE_PLUGIN_HXX + +#include "DatabaseVisitor.hxx" +#include "tag.h" +#include "gcc.h" + +class Database; +struct DatabaseSelection; +struct DatabaseStats; + +bool +VisitUniqueTags(const Database &db, const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r); + +bool +GetStats(const Database &db, const DatabaseSelection &selection, + DatabaseStats &stats, GError **error_r); + +#endif diff --git a/src/DatabaseLock.cxx b/src/DatabaseLock.cxx new file mode 100644 index 00000000..398e5aeb --- /dev/null +++ b/src/DatabaseLock.cxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseLock.hxx" +#include "gcc.h" + +Mutex db_mutex; + +#ifndef NDEBUG +GThread *db_mutex_holder; +#endif diff --git a/src/db_lock.h b/src/DatabaseLock.hxx index 4640502f..371a7d7b 100644 --- a/src/db_lock.h +++ b/src/DatabaseLock.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,16 +23,16 @@ * multi-threading. */ -#ifndef MPD_DB_LOCK_H -#define MPD_DB_LOCK_H +#ifndef MPD_DB_LOCK_HXX +#define MPD_DB_LOCK_HXX #include "check.h" +#include "thread/Mutex.hxx" #include <glib.h> #include <assert.h> -#include <stdbool.h> -extern GStaticMutex db_mutex; +extern Mutex db_mutex; #ifndef NDEBUG @@ -59,7 +59,7 @@ db_lock(void) { assert(!holding_db_lock()); - g_static_mutex_lock(&db_mutex); + db_mutex.lock(); assert(db_mutex_holder == NULL); #ifndef NDEBUG @@ -78,7 +78,22 @@ db_unlock(void) db_mutex_holder = NULL; #endif - g_static_mutex_unlock(&db_mutex); + db_mutex.unlock(); } +#ifdef __cplusplus + +class ScopeDatabaseLock { +public: + ScopeDatabaseLock() { + db_lock(); + } + + ~ScopeDatabaseLock() { + db_unlock(); + } +}; + +#endif + #endif diff --git a/src/DatabasePlaylist.cxx b/src/DatabasePlaylist.cxx new file mode 100644 index 00000000..fb477e83 --- /dev/null +++ b/src/DatabasePlaylist.cxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabasePlaylist.hxx" +#include "DatabaseSelection.hxx" +#include "PlaylistFile.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +#include <functional> + +static bool +AddSong(const char *playlist_path_utf8, + song &song, GError **error_r) +{ + return spl_append_song(playlist_path_utf8, &song, error_r); +} + +bool +search_add_to_playlist(const char *uri, const char *playlist_path_utf8, + const SongFilter *filter, + GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + const DatabaseSelection selection(uri, true, filter); + + using namespace std::placeholders; + const auto f = std::bind(AddSong, playlist_path_utf8, _1, _2); + return db->Visit(selection, f, error_r); +} diff --git a/src/db_save.h b/src/DatabasePlaylist.hxx index e760ec88..7c6952ff 100644 --- a/src/db_save.h +++ b/src/DatabasePlaylist.hxx @@ -17,19 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DB_SAVE_H -#define MPD_DB_SAVE_H +#ifndef MPD_DATABASE_PLAYLIST_HXX +#define MPD_DATABASE_PLAYLIST_HXX -#include <glib.h> -#include <stdbool.h> -#include <stdio.h> +#include "gcc.h" +#include "gerror.h" -struct directory; - -void -db_save_internal(FILE *file, const struct directory *root); +class SongFilter; +gcc_nonnull(1,2) bool -db_load_internal(FILE *file, struct directory *root, GError **error); +search_add_to_playlist(const char *uri, const char *path_utf8, + const SongFilter *filter, + GError **error_r); #endif diff --git a/src/DatabasePlugin.hxx b/src/DatabasePlugin.hxx new file mode 100644 index 00000000..a175b3cd --- /dev/null +++ b/src/DatabasePlugin.hxx @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "gcc.h" + +extern "C" { +#include "tag.h" +} + +struct config_param; +struct DatabaseSelection; +struct db_visitor; + +struct DatabaseStats { + /** + * Number of songs. + */ + unsigned song_count; + + /** + * Total duration of all songs (in seconds). + */ + unsigned long total_duration; + + /** + * Number of distinct artist names. + */ + unsigned artist_count; + + /** + * Number of distinct album names. + */ + unsigned album_count; + + void Clear() { + song_count = 0; + total_duration = 0; + artist_count = album_count = 0; + } +}; + +class Database { +public: + /** + * Free instance data. + */ + virtual ~Database() {} + + /** + * Open the database. Read it into memory if applicable. + */ + virtual bool Open(gcc_unused GError **error_r) { + return true; + } + + /** + * Close the database, free allocated memory. + */ + virtual void Close() {} + + /** + * Look up a song (including tag data) in the database. When + * you don't need this anymore, call ReturnSong(). + * + * @param uri_utf8 the URI of the song within the music + * directory (UTF-8) + */ + virtual struct song *GetSong(const char *uri_utf8, + GError **error_r) const = 0; + + /** + * Mark the song object as "unused". Call this on objects + * returned by GetSong(). + */ + virtual void ReturnSong(struct song *song) const = 0; + + /** + * Visit the selected entities. + */ + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const = 0; + + bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + GError **error_r) const { + return Visit(selection, visit_directory, visit_song, + VisitPlaylist(), error_r); + } + + bool Visit(const DatabaseSelection &selection, VisitSong visit_song, + GError **error_r) const { + return Visit(selection, VisitDirectory(), visit_song, error_r); + } + + /** + * Visit all unique tag values. + */ + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r) const = 0; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + GError **error_r) const = 0; +}; + +struct DatabasePlugin { + const char *name; + + /** + * Allocates and configures a database. + */ + Database *(*create)(const struct config_param *param, + GError **error_r); +}; + +#endif diff --git a/src/DatabasePrint.cxx b/src/DatabasePrint.cxx new file mode 100644 index 00000000..2384d5c1 --- /dev/null +++ b/src/DatabasePrint.cxx @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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.h" + +extern "C" { +#include "song.h" +} + +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +#include <functional> + +static bool +PrintDirectory(Client *client, const Directory &directory) +{ + if (!directory.IsRoot()) + client_printf(client, "directory: %s\n", directory.GetPath()); + + return true; +} + +static void +print_playlist_in_directory(Client *client, + const Directory &directory, + const char *name_utf8) +{ + if (directory.IsRoot()) + client_printf(client, "playlist: %s\n", name_utf8); + else + client_printf(client, "playlist: %s/%s\n", + directory.GetPath(), name_utf8); +} + +static bool +PrintSongBrief(Client *client, song &song) +{ + assert(song.parent != NULL); + + song_print_uri(client, &song); + + if (song.tag != NULL && song.tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, *song.parent, song.uri); + + return true; +} + +static bool +PrintSongFull(Client *client, song &song) +{ + assert(song.parent != NULL); + + song_print_info(client, &song); + + if (song.tag != NULL && song.tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, *song.parent, song.uri); + + return true; +} + +static bool +PrintPlaylistBrief(Client *client, + const PlaylistInfo &playlist, + const Directory &directory) +{ + print_playlist_in_directory(client, directory, playlist.name.c_str()); + return true; +} + +static bool +PrintPlaylistFull(Client *client, + const PlaylistInfo &playlist, + const Directory &directory) +{ + print_playlist_in_directory(client, directory, playlist.name.c_str()); + + if (playlist.mtime > 0) + time_print(client, "Last-Modified", playlist.mtime); + + return true; +} + +bool +db_selection_print(Client *client, const DatabaseSelection &selection, + bool full, GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + using namespace std::placeholders; + const auto d = selection.filter == nullptr + ? std::bind(PrintDirectory, client, _1) + : VisitDirectory(); + const auto s = std::bind(full ? PrintSongFull : PrintSongBrief, + client, _1); + const auto p = selection.filter == nullptr + ? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief, + client, _1, _2) + : VisitPlaylist(); + + return db->Visit(selection, d, s, p, error_r); +} + +struct SearchStats { + int numberOfSongs; + unsigned long playTime; +}; + +static void printSearchStats(Client *client, SearchStats *stats) +{ + client_printf(client, "songs: %i\n", stats->numberOfSongs); + client_printf(client, "playtime: %li\n", stats->playTime); +} + +static bool +stats_visitor_song(SearchStats &stats, song &song) +{ + stats.numberOfSongs++; + stats.playTime += song_get_duration(&song); + + return true; +} + +bool +searchStatsForSongsIn(Client *client, const char *name, + const SongFilter *filter, + GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + const DatabaseSelection selection(name, true, filter); + + SearchStats stats; + stats.numberOfSongs = 0; + stats.playTime = 0; + + using namespace std::placeholders; + const auto f = std::bind(stats_visitor_song, std::ref(stats), + _1); + if (!db->Visit(selection, f, error_r)) + return false; + + printSearchStats(client, &stats); + return true; +} + +bool +printAllIn(Client *client, const char *uri_utf8, GError **error_r) +{ + const DatabaseSelection selection(uri_utf8, true); + return db_selection_print(client, selection, false, error_r); +} + +bool +printInfoForAllIn(Client *client, const char *uri_utf8, + GError **error_r) +{ + const DatabaseSelection selection(uri_utf8, true); + return db_selection_print(client, selection, true, error_r); +} + +static bool +PrintSongURIVisitor(Client *client, song &song) +{ + song_print_uri(client, &song); + + return true; +} + +static bool +PrintUniqueTag(Client *client, enum tag_type tag_type, + const char *value) +{ + client_printf(client, "%s: %s\n", tag_item_names[tag_type], value); + return true; +} + +bool +listAllUniqueTags(Client *client, int type, + const SongFilter *filter, + GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + const DatabaseSelection selection("", true, filter); + + if (type == LOCATE_TAG_FILE_TYPE) { + using namespace std::placeholders; + const auto f = std::bind(PrintSongURIVisitor, client, _1); + return db->Visit(selection, f, error_r); + } else { + using namespace std::placeholders; + const auto f = std::bind(PrintUniqueTag, client, + (enum tag_type)type, _1); + return db->VisitUniqueTags(selection, (enum tag_type)type, + f, error_r); + } +} diff --git a/src/db_print.h b/src/DatabasePrint.hxx index 1b957da1..68551b63 100644 --- a/src/db_print.h +++ b/src/DatabasePrint.hxx @@ -21,51 +21,37 @@ #define MPD_DB_PRINT_H #include "gcc.h" +#include "gerror.h" -#include <glib.h> -#include <stdbool.h> - -struct client; -struct locate_item_list; -struct db_selection; +class SongFilter; +struct DatabaseSelection; struct db_visitor; +class Client; -gcc_nonnull(1,2) +gcc_nonnull(1) bool -db_selection_print(struct client *client, const struct db_selection *selection, +db_selection_print(Client *client, const DatabaseSelection &selection, bool full, GError **error_r); gcc_nonnull(1,2) bool -printAllIn(struct client *client, const char *uri_utf8, GError **error_r); +printAllIn(Client *client, const char *uri_utf8, GError **error_r); gcc_nonnull(1,2) bool -printInfoForAllIn(struct client *client, const char *uri_utf8, +printInfoForAllIn(Client *client, const char *uri_utf8, GError **error_r); -gcc_nonnull(1,2,3) -bool -searchForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r); - -gcc_nonnull(1,2,3) -bool -findSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r); - -gcc_nonnull(1,2,3) +gcc_nonnull(1,2) bool -searchStatsForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, +searchStatsForSongsIn(Client *client, const char *name, + const SongFilter *filter, GError **error_r); -gcc_nonnull(1,3) +gcc_nonnull(1) bool -listAllUniqueTags(struct client *client, int type, - const struct locate_item_list *criteria, +listAllUniqueTags(Client *client, int type, + const SongFilter *filter, GError **error_r); #endif diff --git a/src/DatabaseQueue.cxx b/src/DatabaseQueue.cxx new file mode 100644 index 00000000..e22144c0 --- /dev/null +++ b/src/DatabaseQueue.cxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseQueue.hxx" +#include "DatabaseSelection.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "Partition.hxx" + +#include <functional> + +static bool +AddToQueue(Partition &partition, song &song, GError **error_r) +{ + enum playlist_result result = + partition.playlist.AppendSong(partition.pc, &song, NULL); + if (result != PLAYLIST_RESULT_SUCCESS) { + g_set_error(error_r, playlist_quark(), result, + "Playlist error"); + return false; + } + + return true; +} + +bool +AddFromDatabase(Partition &partition, const DatabaseSelection &selection, + GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + using namespace std::placeholders; + const auto f = std::bind(AddToQueue, std::ref(partition), _1, _2); + return db->Visit(selection, f, error_r); +} diff --git a/src/inotify_queue.h b/src/DatabaseQueue.hxx index cfc28ebf..bae5b1f0 100644 --- a/src/inotify_queue.h +++ b/src/DatabaseQueue.hxx @@ -17,16 +17,16 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INOTIFY_QUEUE_H -#define MPD_INOTIFY_QUEUE_H +#ifndef MPD_DATABASE_QUEUE_HXX +#define MPD_DATABASE_QUEUE_HXX -void -mpd_inotify_queue_init(void); +#include "gerror.h" -void -mpd_inotify_queue_finish(void); +struct Partition; +struct DatabaseSelection; -void -mpd_inotify_enqueue(char *uri_utf8); +bool +AddFromDatabase(Partition &partition, const DatabaseSelection &selection, + GError **error_r); #endif diff --git a/src/DatabaseRegistry.cxx b/src/DatabaseRegistry.cxx new file mode 100644 index 00000000..cf01decd --- /dev/null +++ b/src/DatabaseRegistry.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseRegistry.hxx" +#include "db/SimpleDatabasePlugin.hxx" +#include "db/ProxyDatabasePlugin.hxx" + +#include <string.h> + +const DatabasePlugin *const database_plugins[] = { + &simple_db_plugin, +#ifdef HAVE_LIBMPDCLIENT + &proxy_db_plugin, +#endif + NULL +}; + +const DatabasePlugin * +GetDatabasePluginByName(const char *name) +{ + for (auto i = database_plugins; *i != nullptr; ++i) + if (strcmp((*i)->name, name) == 0) + return *i; + + return nullptr; +} diff --git a/src/DatabaseRegistry.hxx b/src/DatabaseRegistry.hxx new file mode 100644 index 00000000..4be58157 --- /dev/null +++ b/src/DatabaseRegistry.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_REGISTRY_HXX +#define MPD_DATABASE_REGISTRY_HXX + +#include "gcc.h" + +struct DatabasePlugin; + +/** + * NULL terminated list of all database plugins which were enabled at + * compile time. + */ +extern const DatabasePlugin *const database_plugins[]; + +gcc_pure +const DatabasePlugin * +GetDatabasePluginByName(const char *name); + +#endif diff --git a/src/db_save.c b/src/DatabaseSave.cxx index 4af9d58b..dc87c8dd 100644 --- a/src/db_save.c +++ b/src/DatabaseSave.cxx @@ -18,15 +18,15 @@ */ #include "config.h" -#include "db_save.h" -#include "db_lock.h" -#include "directory.h" -#include "directory_save.h" +#include "DatabaseSave.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" +#include "DirectorySave.hxx" #include "song.h" -#include "path.h" -#include "text_file.h" +#include "TextFile.hxx" +#include "TagInternal.hxx" #include "tag.h" -#include "tag_internal.h" +#include "fs/Path.hxx" #include <glib.h> @@ -55,14 +55,15 @@ db_quark(void) } void -db_save_internal(FILE *fp, const struct directory *music_root) +db_save_internal(FILE *fp, const Directory *music_root) { assert(music_root != NULL); fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN); fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT); fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION); - fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, path_get_fs_charset()); + fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, + Path::GetFSCharset().c_str()); for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) if (!ignore_tag_items[i]) @@ -74,9 +75,8 @@ db_save_internal(FILE *fp, const struct directory *music_root) } bool -db_load_internal(FILE *fp, struct directory *music_root, GError **error) +db_load_internal(TextFile &file, Directory *music_root, GError **error) { - GString *buffer = g_string_sized_new(1024); char *line; int format = 0; bool found_charset = false, found_version = false; @@ -86,16 +86,15 @@ db_load_internal(FILE *fp, struct directory *music_root, GError **error) assert(music_root != NULL); /* get initial info */ - line = read_text_line(fp, buffer); + line = file.ReadLine(); if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) { g_set_error(error, db_quark(), 0, "Database corrupted"); - g_string_free(buffer, true); return false; } memset(tags, false, sizeof(tags)); - while ((line = read_text_line(fp, buffer)) != NULL && + while ((line = file.ReadLine()) != NULL && strcmp(line, DIRECTORY_INFO_END) != 0) { if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) { format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1); @@ -103,33 +102,30 @@ db_load_internal(FILE *fp, struct directory *music_root, GError **error) if (found_version) { g_set_error(error, db_quark(), 0, "Duplicate version line"); - g_string_free(buffer, true); return false; } found_version = true; } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) { - const char *new_charset, *old_charset; + const char *new_charset; if (found_charset) { g_set_error(error, db_quark(), 0, "Duplicate charset line"); - g_string_free(buffer, true); return false; } found_charset = true; new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1; - old_charset = path_get_fs_charset(); - if (old_charset != NULL - && strcmp(new_charset, old_charset)) { + const std::string &old_charset = Path::GetFSCharset(); + if (!old_charset.empty() + && strcmp(new_charset, old_charset.c_str())) { g_set_error(error, db_quark(), 0, "Existing database has charset " "\"%s\" instead of \"%s\"; " "discarding database file", - new_charset, old_charset); - g_string_free(buffer, true); + new_charset, old_charset.c_str()); return false; } } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) { @@ -147,7 +143,6 @@ db_load_internal(FILE *fp, struct directory *music_root, GError **error) } else { g_set_error(error, db_quark(), 0, "Malformed line: %s", line); - g_string_free(buffer, true); return false; } } @@ -171,9 +166,8 @@ db_load_internal(FILE *fp, struct directory *music_root, GError **error) g_debug("reading DB"); db_lock(); - success = directory_load(fp, music_root, buffer, error); + success = directory_load(file, music_root, error); db_unlock(); - g_string_free(buffer, true); return success; } diff --git a/src/DatabaseSave.hxx b/src/DatabaseSave.hxx new file mode 100644 index 00000000..40048f26 --- /dev/null +++ b/src/DatabaseSave.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_SAVE_HXX +#define MPD_DATABASE_SAVE_HXX + +#include "gerror.h" + +#include <stdio.h> + +struct Directory; +class TextFile; + +void +db_save_internal(FILE *file, const Directory *root); + +bool +db_load_internal(TextFile &file, Directory *root, GError **error); + +#endif diff --git a/src/decoder_print.h b/src/DatabaseSelection.cxx index 31713d5d..bd756f5f 100644 --- a/src/decoder_print.h +++ b/src/DatabaseSelection.cxx @@ -17,12 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DECODER_PRINT_H -#define MPD_DECODER_PRINT_H +#include "DatabaseSelection.hxx" +#include "SongFilter.hxx" -struct client; - -void -decoder_list_print(struct client *client); - -#endif +bool +DatabaseSelection::Match(const song &song) const +{ + return filter == nullptr || filter->Match(song); +} diff --git a/src/db_selection.h b/src/DatabaseSelection.hxx index 2cebb490..3a81c01e 100644 --- a/src/db_selection.h +++ b/src/DatabaseSelection.hxx @@ -17,17 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DB_SELECTION_H -#define MPD_DB_SELECTION_H +#ifndef MPD_DATABASE_SELECTION_HXX +#define MPD_DATABASE_SELECTION_HXX #include "gcc.h" #include <assert.h> +#include <stddef.h> -struct directory; +class SongFilter; struct song; -struct db_selection { +struct DatabaseSelection { /** * The base URI of the search (UTF-8). Must not begin or end * with a slash. NULL or an empty string searches the whole @@ -39,18 +40,17 @@ struct db_selection { * Recursively search all sub directories? */ bool recursive; -}; -gcc_nonnull(1,2) -static inline void -db_selection_init(struct db_selection *selection, - const char *uri, bool recursive) -{ - assert(selection != NULL); - assert(uri != NULL); - - selection->uri = uri; - selection->recursive = recursive; -} + const SongFilter *filter; + + DatabaseSelection(const char *_uri, bool _recursive, + const SongFilter *_filter=nullptr) + :uri(_uri), recursive(_recursive), filter(_filter) { + assert(uri != NULL); + } + + gcc_pure + bool Match(const song &song) const; +}; #endif diff --git a/src/database.h b/src/DatabaseSimple.hxx index f877b74d..c387a64f 100644 --- a/src/database.h +++ b/src/DatabaseSimple.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,79 +17,64 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DATABASE_H -#define MPD_DATABASE_H +#ifndef MPD_DATABASE_SIMPLE_HXX +#define MPD_DATABASE_SIMPLE_HXX #include "gcc.h" - -#include <glib.h> +#include "gerror.h" #include <sys/time.h> -#include <stdbool.h> struct config_param; -struct directory; +struct Directory; struct db_selection; struct db_visitor; /** - * Initialize the database library. - * - * @param path the absolute path of the database file + * Check whether the default #SimpleDatabasePlugin is used. This + * allows using db_get_root(), db_save(), db_get_mtime() and + * db_exists(). */ bool -db_init(const struct config_param *path, GError **error_r); - -void -db_finish(void); +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. */ -G_GNUC_PURE -struct directory * +gcc_pure +Directory * db_get_root(void); /** * Caller must lock the #db_mutex. */ gcc_nonnull(1) -G_GNUC_PURE -struct directory * +gcc_pure +Directory * db_get_directory(const char *name); -gcc_nonnull(1) -G_GNUC_PURE -struct song * -db_get_song(const char *file); - -gcc_nonnull(1,2) -bool -db_visit(const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r); - -gcc_nonnull(1,2) -bool -db_walk(const char *uri, - const struct db_visitor *visitor, void *ctx, - GError **error_r); - +/** + * May only be used if db_is_simple() returns true. + */ bool db_save(GError **error_r); -bool -db_load(GError **error); - -G_GNUC_PURE +/** + * May only be used if db_is_simple() returns true. + */ +gcc_pure time_t db_get_mtime(void); /** * Returns true if there is a valid database file on the disk. + * + * May only be used if db_is_simple() returns true. */ -G_GNUC_PURE +gcc_pure static inline bool db_exists(void) { diff --git a/src/input_internal.h b/src/DatabaseVisitor.hxx index d95142e4..c9044141 100644 --- a/src/input_internal.h +++ b/src/DatabaseVisitor.hxx @@ -17,27 +17,22 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INPUT_INTERNAL_H -#define MPD_INPUT_INTERNAL_H +#ifndef MPD_DATABASE_VISITOR_HXX +#define MPD_DATABASE_VISITOR_HXX -#include "check.h" +#include "gerror.h" -#include <glib.h> +#include <functional> -struct input_stream; -struct input_plugin; +struct Directory; +struct song; +struct PlaylistInfo; -void -input_stream_init(struct input_stream *is, const struct input_plugin *plugin, - const char *uri, GMutex *mutex, GCond *cond); +typedef std::function<bool(const Directory &, GError **)> VisitDirectory; +typedef std::function<bool(struct song &, GError **)> VisitSong; +typedef std::function<bool(const PlaylistInfo &, const Directory &, + GError **)> VisitPlaylist; -void -input_stream_deinit(struct input_stream *is); - -void -input_stream_signal_client(struct input_stream *is); - -void -input_stream_set_ready(struct input_stream *is); +typedef std::function<bool(const char *, GError **)> VisitString; #endif diff --git a/src/decoder_api.c b/src/DecoderAPI.cxx index a45d0f1e..d86b93fb 100644 --- a/src/decoder_api.c +++ b/src/DecoderAPI.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,14 +19,15 @@ #include "config.h" #include "decoder_api.h" -#include "decoder_internal.h" -#include "decoder_control.h" -#include "audio_config.h" -#include "song.h" -#include "buffer.h" -#include "pipe.h" -#include "chunk.h" +#include "AudioConfig.hxx" #include "replay_gain_config.h" +#include "MusicChunk.hxx" +#include "MusicBuffer.hxx" +#include "MusicPipe.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "song.h" +#include "InputStream.hxx" #include <glib.h> @@ -60,10 +61,10 @@ decoder_initialized(struct decoder *decoder, dc->seekable = seekable; dc->total_time = total_time; - decoder_lock(dc); + dc->Lock(); dc->state = DECODE_STATE_DECODE; - g_cond_signal(dc->client_cond); - decoder_unlock(dc); + dc->client_cond.signal(); + dc->Unlock(); g_debug("audio_format=%s, seekable=%s", audio_format_to_string(&dc->in_audio_format, &af_string), @@ -151,7 +152,7 @@ decoder_command_finished(struct decoder *decoder) { struct decoder_control *dc = decoder->dc; - decoder_lock(dc); + dc->Lock(); assert(dc->command != DECODE_COMMAND_NONE || decoder->initial_seek_running); @@ -167,7 +168,7 @@ decoder_command_finished(struct decoder *decoder) decoder->initial_seek_running = false; decoder->timestamp = dc->start_ms / 1000.; - decoder_unlock(dc); + dc->Unlock(); return; } @@ -187,8 +188,8 @@ decoder_command_finished(struct decoder *decoder) } dc->command = DECODE_COMMAND_NONE; - g_cond_signal(dc->client_cond); - decoder_unlock(dc); + dc->client_cond.signal(); + dc->Unlock(); } double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder) @@ -280,7 +281,7 @@ size_t decoder_read(struct decoder *decoder, if (input_stream_available(is)) break; - g_cond_wait(is->cond, is->mutex); + is->cond.wait(is->mutex); } nbytes = input_stream_read(is, buffer, length, &error); @@ -319,7 +320,7 @@ do_send_tag(struct decoder *decoder, const struct tag *tag) /* there is a partial chunk - flush it, we want the tag in a new chunk */ decoder_flush_chunk(decoder); - g_cond_signal(decoder->dc->client_cond); + decoder->dc->client_cond.signal(); } assert(decoder->chunk == NULL); @@ -362,11 +363,10 @@ update_stream_tag(struct decoder *decoder, struct input_stream *is) enum decoder_command decoder_data(struct decoder *decoder, struct input_stream *is, - const void *_data, size_t length, + const void *data, size_t length, uint16_t kbit_rate) { struct decoder_control *dc = decoder->dc; - const char *data = _data; GError *error = NULL; enum decoder_command cmd; @@ -374,9 +374,9 @@ decoder_data(struct decoder *decoder, assert(dc->pipe != NULL); assert(length % audio_format_frame_size(&dc->in_audio_format) == 0); - decoder_lock(dc); + dc->Lock(); cmd = decoder_get_virtual_command(decoder); - decoder_unlock(dc); + dc->Unlock(); if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK || length == 0) @@ -402,10 +402,11 @@ decoder_data(struct decoder *decoder, } if (!audio_format_equals(&dc->in_audio_format, &dc->out_audio_format)) { - data = pcm_convert(&decoder->conv_state, - &dc->in_audio_format, data, length, - &dc->out_audio_format, &length, - &error); + data = decoder->conv_state.Convert(&dc->in_audio_format, + data, length, + &dc->out_audio_format, + &length, + &error); if (data == NULL) { /* the PCM conversion has failed - stop playback, since we have no better way to @@ -417,7 +418,6 @@ decoder_data(struct decoder *decoder, while (length > 0) { struct music_chunk *chunk; - char *dest; size_t nbytes; bool full; @@ -427,14 +427,14 @@ decoder_data(struct decoder *decoder, return dc->command; } - dest = music_chunk_write(chunk, &dc->out_audio_format, - decoder->timestamp - - dc->song->start_ms / 1000.0, - kbit_rate, &nbytes); + void *dest = chunk->Write(dc->out_audio_format, + decoder->timestamp - + dc->song->start_ms / 1000.0, + kbit_rate, &nbytes); if (dest == NULL) { /* the chunk is full, flush it */ decoder_flush_chunk(decoder); - g_cond_signal(dc->client_cond); + dc->client_cond.signal(); continue; } @@ -449,14 +449,14 @@ decoder_data(struct decoder *decoder, /* expand the music pipe chunk */ - full = music_chunk_expand(chunk, &dc->out_audio_format, nbytes); + full = chunk->Expand(dc->out_audio_format, nbytes); if (full) { /* the chunk is full, flush it */ decoder_flush_chunk(decoder); - g_cond_signal(dc->client_cond); + dc->client_cond.signal(); } - data += nbytes; + data = (const uint8_t *)data + nbytes; length -= nbytes; decoder->timestamp += (double)nbytes / @@ -517,11 +517,10 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, return cmd; } -float +void decoder_replay_gain(struct decoder *decoder, const struct replay_gain_info *replay_gain_info) { - float return_db = 0; assert(decoder != NULL); if (replay_gain_info != NULL) { @@ -530,9 +529,13 @@ decoder_replay_gain(struct decoder *decoder, serial = 1; if (REPLAY_GAIN_OFF != replay_gain_mode) { - return_db = 20.0 * log10f( + enum replay_gain_mode rgm = replay_gain_mode; + if (rgm != REPLAY_GAIN_ALBUM) + rgm = REPLAY_GAIN_TRACK; + + decoder->dc->replay_gain_db = 20.0 * log10f( replay_gain_tuple_scale( - &replay_gain_info->tuples[replay_gain_get_real_mode()], + &replay_gain_info->tuples[rgm], replay_gain_preamp, replay_gain_missing_preamp, replay_gain_limit)); } @@ -545,23 +548,20 @@ decoder_replay_gain(struct decoder *decoder, replay gain values affect the following samples */ decoder_flush_chunk(decoder); - g_cond_signal(decoder->dc->client_cond); + decoder->dc->client_cond.signal(); } } else decoder->replay_gain_serial = 0; - - return return_db; } void -decoder_mixramp(struct decoder *decoder, float replay_gain_db, +decoder_mixramp(struct decoder *decoder, char *mixramp_start, char *mixramp_end) { assert(decoder != NULL); struct decoder_control *dc = decoder->dc; assert(dc != NULL); - dc->replay_gain_db = replay_gain_db; - dc_mixramp_start(dc, mixramp_start); - dc_mixramp_end(dc, mixramp_end); + dc->MixRampStart(mixramp_start); + dc->MixRampEnd(mixramp_end); } diff --git a/src/DecoderControl.cxx b/src/DecoderControl.cxx new file mode 100644 index 00000000..c2331105 --- /dev/null +++ b/src/DecoderControl.cxx @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DecoderControl.hxx" +#include "MusicPipe.hxx" +#include "song.h" + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "decoder_control" + +decoder_control::decoder_control() + :thread(nullptr), + state(DECODE_STATE_STOP), + command(DECODE_COMMAND_NONE), + song(nullptr), + replay_gain_db(0), replay_gain_prev_db(0), + mixramp_start(nullptr), mixramp_end(nullptr), + mixramp_prev_end(nullptr) {} + +decoder_control::~decoder_control() +{ + ClearError(); + + if (song != NULL) + song_free(song); + + g_free(mixramp_start); + g_free(mixramp_end); + g_free(mixramp_prev_end); +} + +static void +dc_command_wait_locked(struct decoder_control *dc) +{ + while (dc->command != DECODE_COMMAND_NONE) + dc->WaitForDecoder(); +} + +static void +dc_command_locked(struct decoder_control *dc, enum decoder_command cmd) +{ + dc->command = cmd; + dc->Signal(); + dc_command_wait_locked(dc); +} + +static void +dc_command(struct decoder_control *dc, enum decoder_command cmd) +{ + dc->Lock(); + dc->ClearError(); + dc_command_locked(dc, cmd); + dc->Unlock(); +} + +static void +dc_command_async(struct decoder_control *dc, enum decoder_command cmd) +{ + dc->Lock(); + + dc->command = cmd; + dc->Signal(); + + dc->Unlock(); +} + +bool +decoder_control::IsCurrentSong(const struct song *_song) const +{ + assert(_song != NULL); + + switch (state) { + case DECODE_STATE_STOP: + case DECODE_STATE_ERROR: + return false; + + case DECODE_STATE_START: + case DECODE_STATE_DECODE: + return song_equals(song, _song); + } + + assert(false); + return false; +} + +void +decoder_control::Start(struct song *_song, + unsigned _start_ms, unsigned _end_ms, + music_buffer *_buffer, music_pipe *_pipe) +{ + assert(_song != NULL); + assert(_buffer != NULL); + assert(_pipe != NULL); + assert(music_pipe_empty(_pipe)); + + if (song != nullptr) + song_free(song); + + song = _song; + start_ms = _start_ms; + end_ms = _end_ms; + buffer = _buffer; + pipe = _pipe; + + dc_command(this, DECODE_COMMAND_START); +} + +void +decoder_control::Stop() +{ + Lock(); + + if (command != DECODE_COMMAND_NONE) + /* Attempt to cancel the current command. If it's too + late and the decoder thread is already executing + the old command, we'll call STOP again in this + function (see below). */ + dc_command_locked(this, DECODE_COMMAND_STOP); + + if (state != DECODE_STATE_STOP && state != DECODE_STATE_ERROR) + dc_command_locked(this, DECODE_COMMAND_STOP); + + Unlock(); +} + +bool +decoder_control::Seek(double where) +{ + assert(state != DECODE_STATE_START); + assert(where >= 0.0); + + if (state == DECODE_STATE_STOP || + state == DECODE_STATE_ERROR || !seekable) + return false; + + seek_where = where; + seek_error = false; + dc_command(this, DECODE_COMMAND_SEEK); + + return !seek_error; +} + +void +decoder_control::Quit() +{ + assert(thread != nullptr); + + quit = true; + dc_command_async(this, DECODE_COMMAND_STOP); + + g_thread_join(thread); + thread = nullptr; +} + +void +decoder_control::MixRampStart(char *_mixramp_start) +{ + g_free(mixramp_start); + mixramp_start = _mixramp_start; +} + +void +decoder_control::MixRampEnd(char *_mixramp_end) +{ + g_free(mixramp_end); + mixramp_end = _mixramp_end; +} + +void +decoder_control::MixRampPrevEnd(char *_mixramp_prev_end) +{ + g_free(mixramp_prev_end); + mixramp_prev_end = _mixramp_prev_end; +} diff --git a/src/DecoderControl.hxx b/src/DecoderControl.hxx new file mode 100644 index 00000000..c2d7b33a --- /dev/null +++ b/src/DecoderControl.hxx @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_CONTROL_HXX +#define MPD_DECODER_CONTROL_HXX + +#include "decoder_command.h" +#include "audio_format.h" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +#include <glib.h> + +#include <assert.h> + +enum decoder_state { + DECODE_STATE_STOP = 0, + DECODE_STATE_START, + DECODE_STATE_DECODE, + + /** + * The last "START" command failed, because there was an I/O + * error or because no decoder was able to decode the file. + * This state will only come after START; once the state has + * turned to DECODE, by definition no such error can occur. + */ + DECODE_STATE_ERROR, +}; + +struct decoder_control { + /** the handle of the decoder thread, or NULL if the decoder + thread isn't running */ + GThread *thread; + + /** + * This lock protects #state and #command. + */ + mutable Mutex mutex; + + /** + * Trigger this object after you have modified #command. This + * is also used by the decoder thread to notify the caller + * when it has finished a command. + */ + Cond cond; + + /** + * The trigger of this object's client. It is signalled + * whenever an event occurs. + */ + Cond client_cond; + + enum decoder_state state; + enum decoder_command command; + + /** + * The error that occurred in the decoder thread. This + * attribute is only valid if #state is #DECODE_STATE_ERROR. + * The object must be freed when this object transitions to + * any other state (usually #DECODE_STATE_START). + */ + GError *error; + + bool quit; + bool seek_error; + bool seekable; + double seek_where; + + /** the format of the song file */ + struct audio_format in_audio_format; + + /** the format being sent to the music pipe */ + struct audio_format out_audio_format; + + /** + * The song currently being decoded. This attribute is set by + * the player thread, when it sends the #DECODE_COMMAND_START + * command. + * + * This is a duplicate, and must be freed when this attribute + * is cleared. + */ + struct song *song; + + /** + * The initial seek position (in milliseconds), e.g. to the + * start of a sub-track described by a CUE file. + * + * This attribute is set by dc_start(). + */ + unsigned start_ms; + + /** + * The decoder will stop when it reaches this position (in + * milliseconds). 0 means don't stop before the end of the + * file. + * + * This attribute is set by dc_start(). + */ + unsigned end_ms; + + float total_time; + + /** the #music_chunk allocator */ + struct music_buffer *buffer; + + /** + * The destination pipe for decoded chunks. The caller thread + * owns this object, and is responsible for freeing it. + */ + struct music_pipe *pipe; + + float replay_gain_db; + float replay_gain_prev_db; + char *mixramp_start; + char *mixramp_end; + char *mixramp_prev_end; + + decoder_control(); + ~decoder_control(); + + /** + * Locks the object. + */ + void Lock() const { + mutex.lock(); + } + + /** + * Unlocks the object. + */ + void Unlock() const { + mutex.unlock(); + } + + /** + * Signals the object. This function is only valid in the + * player thread. The object should be locked prior to + * calling this function. + */ + void Signal() { + cond.signal(); + } + + /** + * Waits for a signal on the #decoder_control object. This function + * is only valid in the decoder thread. The object must be locked + * prior to calling this function. + */ + void Wait() { + cond.wait(mutex); + } + + /** + * Waits for a signal from the decoder thread. This object + * must be locked prior to calling this function. This method + * is only valid in the player thread. + */ + void WaitForDecoder() { + client_cond.wait(mutex); + } + + bool IsIdle() const { + return state == DECODE_STATE_STOP || + state == DECODE_STATE_ERROR; + } + + gcc_pure + bool LockIsIdle() const { + Lock(); + bool result = IsIdle(); + Unlock(); + return result; + } + + bool IsStarting() const { + return state == DECODE_STATE_START; + } + + gcc_pure + bool LockIsStarting() const { + Lock(); + bool result = IsStarting(); + Unlock(); + return result; + } + + bool HasFailed() const { + assert(command == DECODE_COMMAND_NONE); + + return state == DECODE_STATE_ERROR; + } + + gcc_pure + bool LockHasFailed() const { + Lock(); + bool result = HasFailed(); + Unlock(); + return result; + } + + /** + * Checks whether an error has occurred, and if so, returns a newly + * allocated copy of the #GError object. + * + * Caller must lock the object. + */ + GError *GetError() const { + assert(command == DECODE_COMMAND_NONE); + assert(state != DECODE_STATE_ERROR || error != nullptr); + + return state == DECODE_STATE_ERROR + ? g_error_copy(error) + : nullptr; + } + + /** + * Like dc_get_error(), but locks and unlocks the object. + */ + GError *LockGetError() const { + Lock(); + GError *result = GetError(); + Unlock(); + return result; + } + + /** + * Clear the error condition and free the #GError object (if any). + * + * Caller must lock the object. + */ + void ClearError() { + if (state == DECODE_STATE_ERROR) { + g_error_free(error); + state = DECODE_STATE_STOP; + } + } + + /** + * Check if the specified song is currently being decoded. If the + * decoder is not running currently (or being started), then this + * function returns false in any case. + * + * Caller must lock the object. + */ + gcc_pure + bool IsCurrentSong(const struct song *_song) const; + + gcc_pure + bool LockIsCurrentSong(const struct song *_song) const { + Lock(); + const bool result = IsCurrentSong(_song); + Unlock(); + return result; + } + + /** + * Start the decoder. + * + * @param song the song to be decoded; the given instance will be + * owned and freed by the decoder + * @param start_ms see #decoder_control + * @param end_ms see #decoder_control + * @param pipe the pipe which receives the decoded chunks (owned by + * the caller) + */ + void Start(struct song *song, unsigned start_ms, unsigned end_ms, + music_buffer *buffer, music_pipe *pipe); + + void Stop(); + + bool Seek(double where); + + void Quit(); + + void MixRampStart(char *_mixramp_start); + void MixRampEnd(char *_mixramp_end); + void MixRampPrevEnd(char *_mixramp_prev_end); +}; + +#endif diff --git a/src/decoder_internal.c b/src/DecoderInternal.cxx index bc349f2f..e390fdfd 100644 --- a/src/decoder_internal.c +++ b/src/DecoderInternal.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,15 +18,30 @@ */ #include "config.h" -#include "decoder_internal.h" -#include "decoder_control.h" -#include "pipe.h" -#include "input_stream.h" -#include "buffer.h" -#include "chunk.h" +#include "DecoderInternal.hxx" +#include "DecoderControl.hxx" +#include "MusicPipe.hxx" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "tag.h" #include <assert.h> +decoder::~decoder() +{ + /* caller must flush the chunk */ + assert(chunk == nullptr); + + if (song_tag != nullptr) + tag_free(song_tag); + + if (stream_tag != nullptr) + tag_free(stream_tag); + + if (decoder_tag != nullptr) + tag_free(decoder_tag); +} + /** * All chunks are full of decoded data; wait for the player to free * one. @@ -39,8 +54,8 @@ need_chunks(struct decoder_control *dc, bool do_wait) return dc->command; if (do_wait) { - decoder_wait(dc); - g_cond_signal(dc->client_cond); + dc->Wait(); + dc->client_cond.signal(); return dc->command; } @@ -71,9 +86,9 @@ decoder_get_chunk(struct decoder *decoder) return decoder->chunk; } - decoder_lock(dc); + dc->Lock(); cmd = need_chunks(dc, true); - decoder_unlock(dc); + dc->Unlock(); } while (cmd == DECODE_COMMAND_NONE); return NULL; @@ -87,7 +102,7 @@ decoder_flush_chunk(struct decoder *decoder) assert(decoder != NULL); assert(decoder->chunk != NULL); - if (music_chunk_is_empty(decoder->chunk)) + if (decoder->chunk->IsEmpty()) music_buffer_return(dc->buffer, decoder->chunk); else music_pipe_push(dc->pipe, decoder->chunk); diff --git a/src/decoder_internal.h b/src/DecoderInternal.hxx index d89e68cf..3423e3f9 100644 --- a/src/decoder_internal.h +++ b/src/DecoderInternal.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DECODER_INTERNAL_H -#define MPD_DECODER_INTERNAL_H +#ifndef MPD_DECODER_INTERNAL_HXX +#define MPD_DECODER_INTERNAL_HXX #include "decoder_command.h" -#include "pcm_convert.h" +#include "PcmConvert.hxx" #include "replay_gain_info.h" struct input_stream; @@ -29,7 +29,7 @@ struct input_stream; struct decoder { struct decoder_control *dc; - struct pcm_convert_state conv_state; + PcmConvert conv_state; /** * The time stamp of the next data chunk, in seconds. @@ -80,6 +80,20 @@ struct decoder { * has changed since the last check. */ unsigned replay_gain_serial; + + decoder(decoder_control *_dc, bool _initial_seek_pending, + struct 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(); }; /** diff --git a/src/decoder_list.c b/src/DecoderList.cxx index 177b632a..da9c3fcc 100644 --- a/src/decoder_list.c +++ b/src/DecoderList.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,19 @@ */ #include "config.h" -#include "decoder_list.h" +#include "DecoderList.hxx" #include "decoder_plugin.h" -#include "utils.h" #include "conf.h" #include "mpd_error.h" #include "decoder/pcm_decoder_plugin.h" #include "decoder/dsdiff_decoder_plugin.h" #include "decoder/dsf_decoder_plugin.h" +#include "decoder/FLACDecoderPlugin.h" +#include "decoder/OpusDecoderPlugin.h" +#include "decoder/VorbisDecoderPlugin.h" +#include "decoder/AdPlugDecoderPlugin.h" +#include "decoder/WavpackDecoderPlugin.hxx" +#include "decoder/FfmpegDecoderPlugin.hxx" #include <glib.h> @@ -33,21 +38,15 @@ extern const struct decoder_plugin mad_decoder_plugin; extern const struct decoder_plugin mpg123_decoder_plugin; -extern const struct decoder_plugin vorbis_decoder_plugin; -extern const struct decoder_plugin flac_decoder_plugin; -extern const struct decoder_plugin oggflac_decoder_plugin; extern const struct decoder_plugin sndfile_decoder_plugin; extern const struct decoder_plugin audiofile_decoder_plugin; -extern const struct decoder_plugin mp4ff_decoder_plugin; extern const struct decoder_plugin faad_decoder_plugin; extern const struct decoder_plugin mpcdec_decoder_plugin; -extern const struct decoder_plugin wavpack_decoder_plugin; extern const struct decoder_plugin modplug_decoder_plugin; extern const struct decoder_plugin mikmod_decoder_plugin; extern const struct decoder_plugin sidplay_decoder_plugin; extern const struct decoder_plugin wildmidi_decoder_plugin; extern const struct decoder_plugin fluidsynth_decoder_plugin; -extern const struct decoder_plugin ffmpeg_decoder_plugin; extern const struct decoder_plugin gme_decoder_plugin; const struct decoder_plugin *const decoder_plugins[] = { @@ -66,6 +65,9 @@ const struct decoder_plugin *const decoder_plugins[] = { #ifdef HAVE_FLAC &flac_decoder_plugin, #endif +#ifdef HAVE_OPUS + &opus_decoder_plugin, +#endif #ifdef ENABLE_SNDFILE &sndfile_decoder_plugin, #endif @@ -77,9 +79,6 @@ const struct decoder_plugin *const decoder_plugins[] = { #ifdef HAVE_FAAD &faad_decoder_plugin, #endif -#ifdef HAVE_MP4 - &mp4ff_decoder_plugin, -#endif #ifdef HAVE_MPCDEC &mpcdec_decoder_plugin, #endif @@ -101,6 +100,9 @@ const struct decoder_plugin *const decoder_plugins[] = { #ifdef ENABLE_FLUIDSYNTH &fluidsynth_decoder_plugin, #endif +#ifdef HAVE_ADPLUG + &adplug_decoder_plugin, +#endif #ifdef HAVE_FFMPEG &ffmpeg_decoder_plugin, #endif diff --git a/src/decoder_list.h b/src/DecoderList.hxx index d0a6ade7..8dab8724 100644 --- a/src/decoder_list.h +++ b/src/DecoderList.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DECODER_LIST_H -#define MPD_DECODER_LIST_H - -#include <stdbool.h> +#ifndef MPD_DECODER_LIST_HXX +#define MPD_DECODER_LIST_HXX struct decoder_plugin; diff --git a/src/decoder_print.c b/src/DecoderPrint.cxx index e14477ed..719a499e 100644 --- a/src/decoder_print.c +++ b/src/DecoderPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,15 +18,15 @@ */ #include "config.h" -#include "decoder_print.h" -#include "decoder_list.h" +#include "DecoderPrint.hxx" +#include "DecoderList.hxx" #include "decoder_plugin.h" -#include "client.h" +#include "Client.hxx" #include <assert.h> static void -decoder_plugin_print(struct client *client, +decoder_plugin_print(Client *client, const struct decoder_plugin *plugin) { const char *const*p; @@ -46,7 +46,7 @@ decoder_plugin_print(struct client *client, } void -decoder_list_print(struct client *client) +decoder_list_print(Client *client) { decoder_plugins_for_each_enabled(plugin) decoder_plugin_print(client, plugin); diff --git a/src/DecoderPrint.hxx b/src/DecoderPrint.hxx new file mode 100644 index 00000000..d94ba2ce --- /dev/null +++ b/src/DecoderPrint.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_PRINT_HXX +#define MPD_DECODER_PRINT_HXX + +class Client; + +void +decoder_list_print(Client *client); + +#endif diff --git a/src/decoder_thread.c b/src/DecoderThread.cxx index af80ed45..b7b0bf78 100644 --- a/src/decoder_thread.c +++ b/src/DecoderThread.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,21 +18,24 @@ */ #include "config.h" -#include "decoder_thread.h" -#include "decoder_control.h" -#include "decoder_internal.h" -#include "decoder_list.h" +#include "DecoderThread.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "decoder_error.h" #include "decoder_plugin.h" -#include "decoder_api.h" -#include "replay_gain_ape.h" -#include "input_stream.h" -#include "pipe.h" #include "song.h" +#include "mpd_error.h" +#include "Mapper.hxx" +#include "fs/Path.hxx" +#include "decoder_api.h" #include "tag.h" -#include "mapper.h" -#include "path.h" +#include "InputStream.hxx" +#include "DecoderList.hxx" + +extern "C" { +#include "replay_gain_ape.h" #include "uri.h" -#include "mpd_error.h" +} #include <glib.h> @@ -55,7 +58,7 @@ decoder_command_finished_locked(struct decoder_control *dc) dc->command = DECODE_COMMAND_NONE; - g_cond_signal(dc->client_cond); + dc->client_cond.signal(); } /** @@ -88,18 +91,18 @@ decoder_input_stream_open(struct decoder_control *dc, const char *uri) /* wait for the input stream to become ready; its metadata will be available then */ - decoder_lock(dc); + dc->Lock(); input_stream_update(is); while (!is->ready && dc->command != DECODE_COMMAND_STOP) { - decoder_wait(dc); + dc->Wait(); input_stream_update(is); } if (!input_stream_check(is, &error)) { - decoder_unlock(dc); + dc->Unlock(); g_warning("%s", error->message); g_error_free(error); @@ -107,7 +110,7 @@ decoder_input_stream_open(struct decoder_control *dc, const char *uri) return NULL; } - decoder_unlock(dc); + dc->Unlock(); return is; } @@ -134,11 +137,11 @@ decoder_stream_decode(const struct decoder_plugin *plugin, /* rewind the stream, so each plugin gets a fresh start */ input_stream_seek(input_stream, 0, SEEK_SET, NULL); - decoder_unlock(decoder->dc); + decoder->dc->Unlock(); decoder_plugin_stream_decode(plugin, decoder, input_stream); - decoder_lock(decoder->dc); + decoder->dc->Lock(); assert(decoder->dc->state == DECODE_STATE_START || decoder->dc->state == DECODE_STATE_DECODE); @@ -164,11 +167,11 @@ decoder_file_decode(const struct decoder_plugin *plugin, if (decoder->dc->command == DECODE_COMMAND_STOP) return true; - decoder_unlock(decoder->dc); + decoder->dc->Unlock(); decoder_plugin_file_decode(plugin, decoder, path); - decoder_lock(decoder->dc); + decoder->dc->Lock(); assert(decoder->dc->state == DECODE_STATE_START || decoder->dc->state == DECODE_STATE_DECODE); @@ -182,12 +185,7 @@ decoder_file_decode(const struct decoder_plugin *plugin, static inline gpointer deconst_plugin(const struct decoder_plugin *plugin) { - union { - const struct decoder_plugin *in; - gpointer out; - } u = { .in = plugin }; - - return u.out; + return const_cast<struct decoder_plugin *>(plugin); } /** @@ -204,10 +202,11 @@ decoder_run_stream_mime_type(struct decoder *decoder, struct input_stream *is, const struct decoder_plugin *plugin; unsigned int next = 0; - if (is->mime == NULL) + if (is->mime.empty()) return false; - while ((plugin = decoder_plugin_from_mime_type(is->mime, next++))) { + while ((plugin = decoder_plugin_from_mime_type(is->mime.c_str(), + next++))) { if (plugin->stream_decode == NULL) continue; @@ -282,15 +281,15 @@ decoder_run_stream(struct decoder *decoder, const char *uri) struct input_stream *input_stream; bool success; - decoder_unlock(dc); + dc->Unlock(); input_stream = decoder_input_stream_open(dc, uri); if (input_stream == NULL) { - decoder_lock(dc); + dc->Lock(); return false; } - decoder_lock(dc); + dc->Lock(); GSList *tried = NULL; @@ -307,9 +306,9 @@ decoder_run_stream(struct decoder *decoder, const char *uri) g_slist_free(tried); - decoder_unlock(dc); + dc->Unlock(); input_stream_close(input_stream); - decoder_lock(dc); + dc->Lock(); return success; } @@ -339,18 +338,18 @@ decoder_run_file(struct decoder *decoder, const char *path_fs) if (suffix == NULL) return false; - decoder_unlock(dc); + dc->Unlock(); decoder_load_replay_gain(decoder, path_fs); while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) { if (plugin->file_decode != NULL) { - decoder_lock(dc); + dc->Lock(); if (decoder_file_decode(plugin, decoder, path_fs)) return true; - decoder_unlock(dc); + dc->Unlock(); } else if (plugin->stream_decode != NULL) { struct input_stream *input_stream; bool success; @@ -359,23 +358,23 @@ decoder_run_file(struct decoder *decoder, const char *path_fs) if (input_stream == NULL) continue; - decoder_lock(dc); + dc->Lock(); success = decoder_stream_decode(plugin, decoder, input_stream); - decoder_unlock(dc); + dc->Unlock(); input_stream_close(input_stream); if (success) { - decoder_lock(dc); + dc->Lock(); return true; } } } - decoder_lock(dc); + dc->Lock(); return false; } @@ -383,69 +382,66 @@ static void decoder_run_song(struct decoder_control *dc, const struct song *song, const char *uri) { - struct decoder decoder = { - .dc = dc, - .initial_seek_pending = dc->start_ms > 0, - .initial_seek_running = false, - }; + decoder decoder(dc, dc->start_ms > 0, + song->tag != NULL && song_is_file(song) + ? tag_dup(song->tag) : nullptr); int ret; - decoder.timestamp = 0.0; - decoder.seeking = false; - decoder.song_tag = song->tag != NULL && song_is_file(song) - ? tag_dup(song->tag) : NULL; - decoder.stream_tag = NULL; - decoder.decoder_tag = NULL; - decoder.chunk = NULL; - dc->state = DECODE_STATE_START; decoder_command_finished_locked(dc); - pcm_convert_init(&decoder.conv_state); - ret = song_is_file(song) ? decoder_run_file(&decoder, uri) : decoder_run_stream(&decoder, uri); - decoder_unlock(dc); - - pcm_convert_deinit(&decoder.conv_state); + dc->Unlock(); /* flush the last chunk */ if (decoder.chunk != NULL) decoder_flush_chunk(&decoder); - if (decoder.song_tag != NULL) - tag_free(decoder.song_tag); + dc->Lock(); - if (decoder.stream_tag != NULL) - tag_free(decoder.stream_tag); + if (ret) + dc->state = DECODE_STATE_STOP; + else { + dc->state = DECODE_STATE_ERROR; - if (decoder.decoder_tag != NULL) - tag_free(decoder.decoder_tag); + const char *error_uri = song->uri; + char *allocated = uri_remove_auth(error_uri); + if (allocated != NULL) + error_uri = allocated; - decoder_lock(dc); + dc->error = g_error_new(decoder_quark(), 0, + "Failed to decode %s", error_uri); + g_free(allocated); + } - dc->state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR; + dc->client_cond.signal(); } static void decoder_run(struct decoder_control *dc) { + dc->ClearError(); + const struct song *song = dc->song; char *uri; assert(song != NULL); if (song_is_file(song)) - uri = map_song_fs(song); + uri = map_song_fs(song).Steal(); else uri = song_get_uri(song); if (uri == NULL) { dc->state = DECODE_STATE_ERROR; + dc->error = g_error_new(decoder_quark(), 0, + "Failed to map song"); + decoder_command_finished_locked(dc); return; } @@ -458,9 +454,9 @@ decoder_run(struct decoder_control *dc) static gpointer decoder_task(gpointer arg) { - struct decoder_control *dc = arg; + struct decoder_control *dc = (struct decoder_control *)arg; - decoder_lock(dc); + dc->Lock(); do { assert(dc->state == DECODE_STATE_STOP || @@ -468,8 +464,8 @@ decoder_task(gpointer arg) switch (dc->command) { case DECODE_COMMAND_START: - dc_mixramp_start(dc, NULL); - dc_mixramp_prev_end(dc, dc->mixramp_end); + dc->MixRampStart(nullptr); + dc->MixRampPrevEnd(dc->mixramp_end); dc->mixramp_end = NULL; /* Don't free, it's copied above. */ dc->replay_gain_prev_db = dc->replay_gain_db; dc->replay_gain_db = 0; @@ -485,12 +481,12 @@ decoder_task(gpointer arg) break; case DECODE_COMMAND_NONE: - decoder_wait(dc); + dc->Wait(); break; } } while (dc->command != DECODE_COMMAND_NONE || !dc->quit); - decoder_unlock(dc); + dc->Unlock(); return NULL; } diff --git a/src/decoder_thread.h b/src/DecoderThread.hxx index 78f12a54..8efaa2fc 100644 --- a/src/decoder_thread.h +++ b/src/DecoderThread.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DECODER_THREAD_H -#define MPD_DECODER_THREAD_H +#ifndef MPD_DECODER_THREAD_HXX +#define MPD_DECODER_THREAD_HXX struct decoder_control; diff --git a/src/despotify_utils.c b/src/DespotifyUtils.cxx index 7555d105..c9a1edf0 100644 --- a/src/despotify_utils.c +++ b/src/DespotifyUtils.cxx @@ -1,9 +1,31 @@ -#include <glib.h> -#include <despotify.h> - +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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.h" #include "conf.h" -#include "despotify_utils.h" + +#include <glib.h> + +extern "C" { +#include <despotify.h> +} static struct despotify_session *g_session; static void (*registered_callbacks[8])(struct despotify_session *, @@ -97,25 +119,26 @@ struct despotify_session *mpd_despotify_get_session(void) if (user == NULL || passwd == NULL) { g_debug("disabling despotify because account is not configured"); - return NULL; + return nullptr; } - if (!despotify_init()) { + + if (!despotify_init()) { g_debug("Can't initialize despotify\n"); - return false; + return nullptr; } g_session = despotify_init_client(callback, NULL, - high_bitrate, true); + high_bitrate, true); if (!g_session) { g_debug("Can't initialize despotify client\n"); - return false; + return nullptr; } - if (!despotify_authenticate(g_session, user, passwd)) { - g_debug("Can't authenticate despotify session\n"); - despotify_exit(g_session); - return false; - } + if (!despotify_authenticate(g_session, user, passwd)) { + g_debug("Can't authenticate despotify session\n"); + despotify_exit(g_session); + return nullptr; + } return g_session; } diff --git a/src/despotify_utils.h b/src/DespotifyUtils.hxx index 7e35edc3..7e35edc3 100644 --- a/src/despotify_utils.h +++ b/src/DespotifyUtils.hxx diff --git a/src/Directory.cxx b/src/Directory.cxx new file mode 100644 index 00000000..2b1a34cb --- /dev/null +++ b/src/Directory.cxx @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Directory.hxx" +#include "SongFilter.hxx" +#include "PlaylistVector.hxx" +#include "DatabaseLock.hxx" + +extern "C" { +#include "song.h" +#include "song_sort.h" +#include "util/list_sort.h" +} + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +inline Directory * +Directory::Allocate(const char *path) +{ + assert(path != NULL); + + const size_t path_size = strlen(path) + 1; + Directory *directory = + (Directory *)g_malloc0(sizeof(*directory) + - sizeof(directory->path) + + path_size); + new(directory) Directory(path); + + return directory; +} + +Directory::Directory() +{ + INIT_LIST_HEAD(&children); + INIT_LIST_HEAD(&songs); + + path[0] = 0; +} + +Directory::Directory(const char *_path) +{ + INIT_LIST_HEAD(&children); + INIT_LIST_HEAD(&songs); + + strcpy(path, _path); +} + +Directory::~Directory() +{ + struct song *song, *ns; + directory_for_each_song_safe(song, ns, this) + song_free(song); + + Directory *child, *n; + directory_for_each_child_safe(child, n, this) + child->Free(); +} + +Directory * +Directory::NewGeneric(const char *path, Directory *parent) +{ + assert(path != NULL); + assert((*path == 0) == (parent == NULL)); + + Directory *directory = Allocate(path); + + directory->parent = parent; + + return directory; +} + +void +Directory::Free() +{ + this->Directory::~Directory(); + g_free(this); +} + +void +Directory::Delete() +{ + assert(holding_db_lock()); + assert(parent != nullptr); + + list_del(&siblings); + Free(); +} + +const char * +Directory::GetName() const +{ + assert(!IsRoot()); + assert(path != nullptr); + + const char *slash = strrchr(path, '/'); + assert((slash == nullptr) == parent->IsRoot()); + + return slash != NULL + ? slash + 1 + : path; +} + +Directory * +Directory::CreateChild(const char *name_utf8) +{ + assert(holding_db_lock()); + assert(name_utf8 != NULL); + assert(*name_utf8 != 0); + + char *allocated; + const char *path_utf8; + if (IsRoot()) { + allocated = NULL; + path_utf8 = name_utf8; + } else { + allocated = g_strconcat(GetPath(), + "/", name_utf8, NULL); + path_utf8 = allocated; + } + + Directory *child = NewGeneric(path_utf8, this); + g_free(allocated); + + list_add_tail(&child->siblings, &children); + return child; +} + +const Directory * +Directory::FindChild(const char *name) const +{ + assert(holding_db_lock()); + + const Directory *child; + directory_for_each_child(child, this) + if (strcmp(child->GetName(), name) == 0) + return child; + + return NULL; +} + +void +Directory::PruneEmpty() +{ + assert(holding_db_lock()); + + Directory *child, *n; + directory_for_each_child_safe(child, n, this) { + child->PruneEmpty(); + + if (child->IsEmpty()) + child->Delete(); + } +} + +Directory * +Directory::LookupDirectory(const char *uri) +{ + assert(holding_db_lock()); + assert(uri != NULL); + + if (isRootDirectory(uri)) + return this; + + char *duplicated = g_strdup(uri), *name = duplicated; + + Directory *d = this; + while (1) { + char *slash = strchr(name, '/'); + if (slash == name) { + d = NULL; + break; + } + + if (slash != NULL) + *slash = '\0'; + + d = d->FindChild(name); + if (d == NULL || slash == NULL) + break; + + name = slash + 1; + } + + g_free(duplicated); + + return d; +} + +void +Directory::AddSong(struct song *song) +{ + assert(holding_db_lock()); + assert(song != NULL); + assert(song->parent == this); + + list_add_tail(&song->siblings, &songs); +} + +void +Directory::RemoveSong(struct song *song) +{ + assert(holding_db_lock()); + assert(song != NULL); + assert(song->parent == this); + + list_del(&song->siblings); +} + +const song * +Directory::FindSong(const char *name_utf8) const +{ + assert(holding_db_lock()); + assert(name_utf8 != NULL); + + struct song *song; + directory_for_each_song(song, this) { + assert(song->parent == this); + + if (strcmp(song->uri, name_utf8) == 0) + return song; + } + + return NULL; +} + +struct song * +Directory::LookupSong(const char *uri) +{ + char *duplicated, *base; + + assert(holding_db_lock()); + assert(uri != NULL); + + duplicated = g_strdup(uri); + base = strrchr(duplicated, '/'); + + Directory *d = this; + if (base != NULL) { + *base++ = 0; + d = d->LookupDirectory(duplicated); + if (d == nullptr) { + g_free(duplicated); + return NULL; + } + } else + base = duplicated; + + struct song *song = d->FindSong(base); + assert(song == NULL || song->parent == d); + + g_free(duplicated); + return song; + +} + +static int +directory_cmp(G_GNUC_UNUSED void *priv, + struct list_head *_a, struct list_head *_b) +{ + const Directory *a = (const Directory *)_a; + const Directory *b = (const Directory *)_b; + return g_utf8_collate(a->path, b->path); +} + +void +Directory::Sort() +{ + assert(holding_db_lock()); + + list_sort(NULL, &children, directory_cmp); + song_list_sort(&songs); + + Directory *child; + directory_for_each_child(child, this) + child->Sort(); +} + +bool +Directory::Walk(bool recursive, const SongFilter *filter, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const +{ + assert(error_r == NULL || *error_r == NULL); + + if (visit_song) { + struct song *song; + directory_for_each_song(song, this) + if ((filter == nullptr || filter->Match(*song)) && + !visit_song(*song, error_r)) + return false; + } + + if (visit_playlist) { + for (const PlaylistInfo &p : playlists) + if (!visit_playlist(p, *this, error_r)) + return false; + } + + Directory *child; + directory_for_each_child(child, this) { + if (visit_directory && + !visit_directory(*child, error_r)) + return false; + + if (recursive && + !child->Walk(recursive, filter, + visit_directory, visit_song, visit_playlist, + error_r)) + return false; + } + + return true; +} diff --git a/src/Directory.hxx b/src/Directory.hxx new file mode 100644 index 00000000..8fa5c235 --- /dev/null +++ b/src/Directory.hxx @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DIRECTORY_HXX +#define MPD_DIRECTORY_HXX + +#include "check.h" +#include "util/list.h" +#include "gcc.h" +#include "DatabaseVisitor.hxx" +#include "PlaylistVector.hxx" +#include "gerror.h" + +#include <stdbool.h> +#include <sys/types.h> + +#define DEVICE_INARCHIVE (dev_t)(-1) +#define DEVICE_CONTAINER (dev_t)(-2) + +#define directory_for_each_child(pos, directory) \ + list_for_each_entry(pos, &directory->children, siblings) + +#define directory_for_each_child_safe(pos, n, directory) \ + list_for_each_entry_safe(pos, n, &directory->children, siblings) + +#define directory_for_each_song(pos, directory) \ + list_for_each_entry(pos, &directory->songs, siblings) + +#define directory_for_each_song_safe(pos, n, directory) \ + list_for_each_entry_safe(pos, n, &directory->songs, siblings) + +struct song; +struct db_visitor; +class SongFilter; + +struct Directory { + /** + * Pointers to the siblings of this directory within the + * parent directory. It is unused (undefined) in the root + * directory. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + struct list_head siblings; + + /** + * A doubly linked list of child directories. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + struct list_head children; + + /** + * A doubly linked list of songs within this directory. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + struct list_head songs; + + PlaylistVector playlists; + + Directory *parent; + time_t mtime; + ino_t inode; + dev_t device; + bool have_stat; /* not needed if ino_t == dev_t == 0 is impossible */ + char path[sizeof(long)]; + +protected: + Directory(const char *path); + + gcc_malloc gcc_nonnull_all + static Directory *Allocate(const char *path); + +public: + /** + * Default constructor, needed for #detached_root. + */ + Directory(); + ~Directory(); + + /** + * Generic constructor for #Directory object. + */ + gcc_malloc + static Directory *NewGeneric(const char *path_utf8, Directory *parent); + + /** + * Create a new root #Directory object. + */ + gcc_malloc + static Directory *NewRoot() { + return NewGeneric("", nullptr); + } + + /** + * Free this #Directory object (and the whole object tree within it), + * assuming it was already removed from the parent. + */ + void Free(); + + /** + * Remove this #Directory object from its parent and free it. This + * must not be called with the root Directory. + * + * Caller must lock the #db_mutex. + */ + void Delete(); + + /** + * Create a new #Directory object as a child of the given one. + * + * Caller must lock the #db_mutex. + * + * @param name_utf8 the UTF-8 encoded name of the new sub directory + */ + gcc_malloc + Directory *CreateChild(const char *name_utf8); + + /** + * Caller must lock the #db_mutex. + */ + gcc_pure + const Directory *FindChild(const char *name) const; + + gcc_pure + Directory *FindChild(const char *name) { + const Directory *cthis = this; + return const_cast<Directory *>(cthis->FindChild(name)); + } + + /** + * Look up a sub directory, and create the object if it does not + * exist. + * + * Caller must lock the #db_mutex. + */ + Directory *MakeChild(const char *name_utf8) { + Directory *child = FindChild(name_utf8); + if (child == nullptr) + child = CreateChild(name_utf8); + return child; + } + + /** + * Looks up a directory by its relative URI. + * + * @param uri the relative URI + * @return the Directory, or NULL if none was found + */ + gcc_pure + Directory *LookupDirectory(const char *uri); + + gcc_pure + bool IsEmpty() const { + return list_empty(&children) && + list_empty(&songs) && + playlists.empty(); + } + + gcc_pure + const char *GetPath() const { + return path; + } + + /** + * Returns the base name of the directory. + */ + gcc_pure + const char *GetName() const; + + /** + * Is this the root directory of the music database? + */ + gcc_pure + bool IsRoot() const { + return parent == NULL; + } + + /** + * Look up a song in this directory by its name. + * + * Caller must lock the #db_mutex. + */ + gcc_pure + const song *FindSong(const char *name_utf8) const; + + gcc_pure + song *FindSong(const char *name_utf8) { + const Directory *cthis = this; + return const_cast<song *>(cthis->FindSong(name_utf8)); + } + + /** + * Looks up a song by its relative URI. + * + * Caller must lock the #db_mutex. + * + * @param uri the relative URI + * @return the song, or NULL if none was found + */ + gcc_pure + song *LookupSong(const char *uri); + + /** + * Add a song object to this directory. Its "parent" attribute must + * be set already. + */ + void AddSong(song *song); + + /** + * Remove a song object from this directory (which effectively + * invalidates the song object, because the "parent" attribute becomes + * stale), but does not free it. + */ + void RemoveSong(song *song); + + /** + * Caller must lock the #db_mutex. + */ + void PruneEmpty(); + + /** + * Sort all directory entries recursively. + * + * Caller must lock the #db_mutex. + */ + void Sort(); + + /** + * Caller must lock #db_mutex. + */ + bool Walk(bool recursive, const SongFilter *match, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const; +}; + +static inline bool +isRootDirectory(const char *name) +{ + return name[0] == 0 || (name[0] == '/' && name[1] == 0); +} + +#endif diff --git a/src/directory_save.c b/src/DirectorySave.cxx index de1df050..6a5efb05 100644 --- a/src/directory_save.c +++ b/src/DirectorySave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,12 +18,12 @@ */ #include "config.h" -#include "directory_save.h" -#include "directory.h" +#include "DirectorySave.hxx" +#include "Directory.hxx" #include "song.h" -#include "text_file.h" -#include "song_save.h" -#include "playlist_database.h" +#include "SongSave.hxx" +#include "PlaylistDatabase.hxx" +#include "TextFile.hxx" #include <assert.h> #include <string.h> @@ -43,17 +43,16 @@ directory_quark(void) } void -directory_save(FILE *fp, const struct directory *directory) +directory_save(FILE *fp, const Directory *directory) { - if (!directory_is_root(directory)) { + if (!directory->IsRoot()) { fprintf(fp, DIRECTORY_MTIME "%lu\n", (unsigned long)directory->mtime); - fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, - directory_get_path(directory)); + fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, directory->GetPath()); } - struct directory *cur; + Directory *cur; directory_for_each_child(cur, directory) { char *base = g_path_get_basename(cur->path); @@ -70,33 +69,31 @@ directory_save(FILE *fp, const struct directory *directory) directory_for_each_song(song, directory) song_save(fp, song); - playlist_vector_save(fp, &directory->playlists); + playlist_vector_save(fp, directory->playlists); - if (!directory_is_root(directory)) - fprintf(fp, DIRECTORY_END "%s\n", - directory_get_path(directory)); + if (!directory->IsRoot()) + fprintf(fp, DIRECTORY_END "%s\n", directory->GetPath()); } -static struct directory * -directory_load_subdir(FILE *fp, struct directory *parent, const char *name, - GString *buffer, GError **error_r) +static Directory * +directory_load_subdir(TextFile &file, Directory *parent, const char *name, + GError **error_r) { - const char *line; bool success; - if (directory_get_child(parent, name) != NULL) { + if (parent->FindChild(name) != nullptr) { g_set_error(error_r, directory_quark(), 0, "Duplicate subdirectory '%s'", name); return NULL; } - struct directory *directory = directory_new_child(parent, name); + Directory *directory = parent->CreateChild(name); - line = read_text_line(fp, buffer); + const char *line = file.ReadLine(); if (line == NULL) { g_set_error(error_r, directory_quark(), 0, "Unexpected end of file"); - directory_delete(directory); + directory->Delete(); return NULL; } @@ -105,11 +102,11 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name, g_ascii_strtoull(line + sizeof(DIRECTORY_MTIME) - 1, NULL, 10); - line = read_text_line(fp, buffer); + line = file.ReadLine(); if (line == NULL) { g_set_error(error_r, directory_quark(), 0, "Unexpected end of file"); - directory_delete(directory); + directory->Delete(); return NULL; } } @@ -117,13 +114,13 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name, if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) { g_set_error(error_r, directory_quark(), 0, "Malformed line: %s", line); - directory_delete(directory); + directory->Delete(); return NULL; } - success = directory_load(fp, directory, buffer, error_r); + success = directory_load(file, directory, error_r); if (!success) { - directory_delete(directory); + directory->Delete(); return NULL; } @@ -131,44 +128,42 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name, } bool -directory_load(FILE *fp, struct directory *directory, - GString *buffer, GError **error) +directory_load(TextFile &file, Directory *directory, GError **error) { const char *line; - while ((line = read_text_line(fp, buffer)) != NULL && + while ((line = file.ReadLine()) != NULL && !g_str_has_prefix(line, DIRECTORY_END)) { if (g_str_has_prefix(line, DIRECTORY_DIR)) { - struct directory *subdir = - directory_load_subdir(fp, directory, + Directory *subdir = + directory_load_subdir(file, directory, line + sizeof(DIRECTORY_DIR) - 1, - buffer, error); + error); if (subdir == NULL) return false; } else if (g_str_has_prefix(line, SONG_BEGIN)) { const char *name = line + sizeof(SONG_BEGIN) - 1; struct song *song; - if (directory_get_song(directory, name) != NULL) { + if (directory->FindSong(name) != nullptr) { g_set_error(error, directory_quark(), 0, "Duplicate song '%s'", name); - return NULL; + return false; } - song = song_load(fp, directory, name, - buffer, error); + song = song_load(file, directory, name, error); if (song == NULL) return false; - directory_add_song(directory, song); + 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(fp, &directory->playlists, - name, buffer, error)) { + if (!playlist_metadata_load(file, directory->playlists, + name, error)) { g_free(name); return false; } diff --git a/src/directory_save.h b/src/DirectorySave.hxx index 2d005683..f4b4816f 100644 --- a/src/directory_save.h +++ b/src/DirectorySave.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,21 +17,20 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DIRECTORY_SAVE_H -#define MPD_DIRECTORY_SAVE_H +#ifndef MPD_DIRECTORY_SAVE_HXX +#define MPD_DIRECTORY_SAVE_HXX -#include <glib.h> +#include "gerror.h" -#include <stdbool.h> #include <stdio.h> -struct directory; +struct Directory; +class TextFile; void -directory_save(FILE *fp, const struct directory *directory); +directory_save(FILE *fp, const Directory *directory); bool -directory_load(FILE *fp, struct directory *directory, - GString *buffer, GError **error); +directory_load(TextFile &file, Directory *directory, GError **error); #endif diff --git a/src/exclude.c b/src/ExcludeList.cxx index 438039d3..0daa432d 100644 --- a/src/exclude.c +++ b/src/ExcludeList.cxx @@ -23,35 +23,30 @@ */ #include "config.h" -#include "exclude.h" -#include "path.h" +#include "ExcludeList.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" #include <assert.h> #include <string.h> -#include <stdio.h> #include <errno.h> -GSList * -exclude_list_load(const char *path_fs) +bool +ExcludeList::LoadFile(const Path &path_fs) { - FILE *file; - char line[1024]; - GSList *list = NULL; - - assert(path_fs != NULL); - - file = fopen(path_fs, "r"); + FILE *file = FOpen(path_fs, FOpenMode::ReadText); if (file == NULL) { if (errno != ENOENT) { - char *path_utf8 = fs_charset_to_utf8(path_fs); + const char *msg = g_strerror(errno); + const auto path_utf8 = path_fs.ToUTF8(); g_debug("Failed to open %s: %s", - path_utf8, g_strerror(errno)); - g_free(path_utf8); + path_utf8.c_str(), msg); } - return NULL; + return false; } + char line[1024]; while (fgets(line, sizeof(line), file) != NULL) { char *p = strchr(line, '#'); if (p != NULL) @@ -59,37 +54,24 @@ exclude_list_load(const char *path_fs) p = g_strstrip(line); if (*p != 0) - list = g_slist_prepend(list, g_pattern_spec_new(p)); + patterns.emplace_front(p); } fclose(file); - return list; -} - -void -exclude_list_free(GSList *list) -{ - while (list != NULL) { - GPatternSpec *pattern = list->data; - g_pattern_spec_free(pattern); - list = g_slist_remove(list, list->data); - } + return true; } bool -exclude_list_check(GSList *list, const char *name_fs) +ExcludeList::Check(const char *name_fs) const { assert(name_fs != NULL); /* XXX include full path name in check */ - for (; list != NULL; list = list->next) { - GPatternSpec *pattern = list->data; - - if (g_pattern_match_string(pattern, name_fs)) + for (const auto &i : patterns) + if (i.Check(name_fs)) return true; - } return false; } diff --git a/src/ExcludeList.hxx b/src/ExcludeList.hxx new file mode 100644 index 00000000..f3dc1f05 --- /dev/null +++ b/src/ExcludeList.hxx @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * The .mpdignore backend code. + * + */ + +#ifndef MPD_EXCLUDE_H +#define MPD_EXCLUDE_H + +#include "gcc.h" + +#include <forward_list> + +#include <glib.h> + +class Path; + +class ExcludeList { + class Pattern { + GPatternSpec *pattern; + + public: + Pattern(const char *_pattern) + :pattern(g_pattern_spec_new(_pattern)) {} + + Pattern(Pattern &&other) + :pattern(other.pattern) { + other.pattern = nullptr; + } + + ~Pattern() { + g_pattern_spec_free(pattern); + } + + gcc_pure + bool Check(const char *name_fs) const { + return g_pattern_match_string(pattern, name_fs); + } + }; + + std::forward_list<Pattern> patterns; + +public: + gcc_pure + bool IsEmpty() const { + return patterns.empty(); + } + + /** + * Loads and parses a .mpdignore file. + */ + bool LoadFile(const Path &path_fs); + + /** + * Checks whether one of the patterns in the .mpdignore file matches + * the specified file name. + */ + bool Check(const char *name_fs) const; +}; + + +#endif diff --git a/src/filter_config.c b/src/FilterConfig.cxx index ab9bdb0c..e56c5a98 100644 --- a/src/filter_config.c +++ b/src/FilterConfig.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,16 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "filter_config.h" #include "config.h" +#include "FilterConfig.hxx" #include "conf.h" -#include "filter/chain_filter_plugin.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" +#include "filter/ChainFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" -#include <string.h> +#include <glib.h> +#include <string.h> static GQuark filter_quark(void) @@ -77,37 +78,38 @@ filter_plugin_config(const char *filter_template_name, GError **error_r) * @return the number of filters which were successfully added */ unsigned int -filter_chain_parse(struct filter *chain, const char *spec, GError **error_r) +filter_chain_parse(Filter &chain, const char *spec, GError **error_r) { // Split on comma gchar** tokens = g_strsplit_set(spec, ",", 255); - int added_filters = 0; + unsigned added_filters = 0; // Add each name to the filter chain by instantiating an actual filter char **template_names = tokens; while (*template_names != NULL) { - struct filter *f; - const struct config_param *cfg; - // Squeeze whitespace g_strstrip(*template_names); - cfg = filter_plugin_config(*template_names, error_r); + const struct config_param *cfg = + filter_plugin_config(*template_names, error_r); if (cfg == NULL) { // The error has already been set, just stop. break; } // Instantiate one of those filter plugins with the template name as a hint - f = filter_configured_new(cfg, error_r); + Filter *f = filter_configured_new(cfg, error_r); if (f == NULL) { // The error has already been set, just stop. break; } - filter_chain_append(chain, f); + const char *plugin_name = + config_get_block_string(cfg, "plugin", "unknown"); + + filter_chain_append(chain, plugin_name, f); ++added_filters; ++template_names; diff --git a/src/filter_config.h b/src/FilterConfig.hxx index 920cbc07..bad18635 100644 --- a/src/filter_config.h +++ b/src/FilterConfig.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,15 +22,12 @@ * Utility functions for filter configuration */ -#ifndef MPD_FILTER_CONFIG_H -#define MPD_FILTER_CONFIG_H +#ifndef MPD_FILTER_CONFIG_HXX +#define MPD_FILTER_CONFIG_HXX -#include "conf.h" -#include "filter/chain_filter_plugin.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" +#include "gerror.h" +class Filter; /** * Builds a filter chain from a configuration string on the form @@ -42,6 +39,6 @@ * @return the number of filters which were successfully added */ unsigned int -filter_chain_parse(struct filter *chain, const char *spec, GError **error_r); +filter_chain_parse(Filter &chain, const char *spec, GError **error_r); #endif diff --git a/src/FilterInternal.hxx b/src/FilterInternal.hxx new file mode 100644 index 00000000..cdc2d0ea --- /dev/null +++ b/src/FilterInternal.hxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Internal stuff for the filter core and filter plugins. + */ + +#ifndef MPD_FILTER_INTERNAL_HXX +#define MPD_FILTER_INTERNAL_HXX + +struct audio_format; + +class Filter { +public: + virtual ~Filter() {} + + /** + * Opens the filter, preparing it for FilterPCM(). + * + * @param filter the filter object + * @param audio_format the audio format of incoming data; the + * plugin may modify the object to enforce another input + * format + * @param error location to store the error occurring, or NULL + * to ignore errors. + * @return the format of outgoing data + */ + virtual const audio_format *Open(audio_format &af, + GError **error_r) = 0; + + /** + * Closes the filter. After that, you may call Open() again. + */ + virtual void Close() = 0; + + /** + * Filters a block of PCM data. + * + * @param filter the filter object + * @param src the input buffer + * @param src_size the size of #src_buffer in bytes + * @param dest_size_r the size of the returned buffer + * @param error location to store the error occurring, or NULL + * to ignore errors. + * @return the destination buffer on success (will be + * invalidated by filter_close() or filter_filter()), NULL on + * error + */ + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, + GError **error_r) = 0; +}; + +#endif diff --git a/src/filter_plugin.c b/src/FilterPlugin.cxx index 7173134b..953f404e 100644 --- a/src/filter_plugin.c +++ b/src/FilterPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,18 +18,15 @@ */ #include "config.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" #include "conf.h" - -#ifndef NDEBUG -#include "audio_format.h" -#endif +#include "ConfigQuark.hxx" #include <assert.h> -struct filter * +Filter * filter_new(const struct filter_plugin *plugin, const struct config_param *param, GError **error_r) { @@ -39,7 +36,7 @@ filter_new(const struct filter_plugin *plugin, return plugin->init(param, error_r); } -struct filter * +Filter * filter_configured_new(const struct config_param *param, GError **error_r) { const char *plugin_name; @@ -64,53 +61,3 @@ filter_configured_new(const struct config_param *param, GError **error_r) return filter_new(plugin, param, error_r); } - -void -filter_free(struct filter *filter) -{ - assert(filter != NULL); - - filter->plugin->finish(filter); -} - -const struct audio_format * -filter_open(struct filter *filter, struct audio_format *audio_format, - GError **error_r) -{ - const struct audio_format *out_audio_format; - - assert(filter != NULL); - assert(audio_format != NULL); - assert(audio_format_valid(audio_format)); - assert(error_r == NULL || *error_r == NULL); - - out_audio_format = filter->plugin->open(filter, audio_format, error_r); - - assert(out_audio_format == NULL || audio_format_valid(audio_format)); - assert(out_audio_format == NULL || - audio_format_valid(out_audio_format)); - - return out_audio_format; -} - -void -filter_close(struct filter *filter) -{ - assert(filter != NULL); - - filter->plugin->close(filter); -} - -const void * -filter_filter(struct filter *filter, const void *src, size_t src_size, - size_t *dest_size_r, - GError **error_r) -{ - assert(filter != NULL); - assert(src != NULL); - assert(src_size > 0); - assert(dest_size_r != NULL); - assert(error_r == NULL || *error_r == NULL); - - return filter->plugin->filter(filter, src, src_size, dest_size_r, error_r); -} diff --git a/src/FilterPlugin.hxx b/src/FilterPlugin.hxx new file mode 100644 index 00000000..80bb8a0b --- /dev/null +++ b/src/FilterPlugin.hxx @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This header declares the filter_plugin class. It describes a + * plugin API for objects which filter raw PCM data. + */ + +#ifndef MPD_FILTER_PLUGIN_HXX +#define MPD_FILTER_PLUGIN_HXX + +#include "gerror.h" + +#include <stddef.h> + +struct config_param; +class Filter; + +struct filter_plugin { + const char *name; + + /** + * Allocates and configures a filter. + */ + Filter *(*init)(const struct config_param *param, GError **error_r); +}; + +/** + * Creates a new instance of the specified filter plugin. + * + * @param plugin the filter plugin + * @param param optional configuration section + * @param error location to store the error occurring, or NULL to + * ignore errors. + * @return a new filter object, or NULL on error + */ +Filter * +filter_new(const struct filter_plugin *plugin, + const struct config_param *param, GError **error_r); + +/** + * Creates a new filter, loads configuration and the plugin name from + * the specified configuration section. + * + * @param param the configuration section + * @param error location to store the error occurring, or NULL to + * ignore errors. + * @return a new filter object, or NULL on error + */ +Filter * +filter_configured_new(const struct config_param *param, GError **error_r); + +#endif diff --git a/src/filter_registry.c b/src/FilterRegistry.cxx index dc188939..c8aff829 100644 --- a/src/filter_registry.c +++ b/src/FilterRegistry.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,8 @@ */ #include "config.h" -#include "filter_registry.h" -#include "filter_plugin.h" +#include "FilterRegistry.hxx" +#include "FilterPlugin.hxx" #include <stddef.h> #include <string.h> diff --git a/src/filter_registry.h b/src/FilterRegistry.hxx index d3949c7c..c21d8a59 100644 --- a/src/filter_registry.h +++ b/src/FilterRegistry.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 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 @@ * compile time. */ -#ifndef MPD_FILTER_REGISTRY_H -#define MPD_FILTER_REGISTRY_H +#ifndef MPD_FILTER_REGISTRY_HXX +#define MPD_FILTER_REGISTRY_HXX extern const struct filter_plugin null_filter_plugin; extern const struct filter_plugin chain_filter_plugin; diff --git a/src/GlobalEvents.cxx b/src/GlobalEvents.cxx new file mode 100644 index 00000000..e4f335c9 --- /dev/null +++ b/src/GlobalEvents.cxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "GlobalEvents.hxx" +#include "event/WakeFD.hxx" +#include "mpd_error.h" + +#include <atomic> + +#include <assert.h> +#include <glib.h> +#include <string.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "global_events" + +namespace GlobalEvents { + static WakeFD wake_fd; + static guint source_id; + static std::atomic_uint flags; + static Handler handlers[MAX]; +} + +/** + * Invoke the callback for a certain event. + */ +static void +InvokeGlobalEvent(GlobalEvents::Event event) +{ + assert((unsigned)event < GlobalEvents::MAX); + assert(GlobalEvents::handlers[event] != NULL); + + GlobalEvents::handlers[event](); +} + +static gboolean +GlobalEventCallback(G_GNUC_UNUSED GIOChannel *source, + G_GNUC_UNUSED GIOCondition condition, + G_GNUC_UNUSED gpointer data) +{ + if (!GlobalEvents::wake_fd.Read()) + return true; + + const unsigned flags = GlobalEvents::flags.fetch_and(0); + + for (unsigned i = 0; i < GlobalEvents::MAX; ++i) + if (flags & (1u << i)) + /* invoke the event handler */ + InvokeGlobalEvent(GlobalEvents::Event(i)); + + return true; +} + +void +GlobalEvents::Initialize() +{ + if (!wake_fd.Create()) + MPD_ERROR("Couldn't open pipe: %s", strerror(errno)); + +#ifndef G_OS_WIN32 + GIOChannel *channel = g_io_channel_unix_new(wake_fd.Get()); +#else + GIOChannel *channel = g_io_channel_win32_new_socket(wake_fd.Get()); +#endif + + source_id = g_io_add_watch(channel, G_IO_IN, + GlobalEventCallback, NULL); + g_io_channel_unref(channel); +} + +void +GlobalEvents::Deinitialize() +{ + g_source_remove(source_id); + + wake_fd.Destroy(); +} + +void +GlobalEvents::Register(Event event, Handler callback) +{ + assert((unsigned)event < MAX); + assert(handlers[event] == NULL); + + handlers[event] = callback; +} + +void +GlobalEvents::Emit(Event event) +{ + assert((unsigned)event < MAX); + + const unsigned mask = 1u << unsigned(event); + if ((GlobalEvents::flags.fetch_or(mask) & mask) == 0) + wake_fd.Write(); +} diff --git a/src/GlobalEvents.hxx b/src/GlobalEvents.hxx new file mode 100644 index 00000000..2e549305 --- /dev/null +++ b/src/GlobalEvents.hxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_GLOBAL_EVENTS_HXX +#define MPD_GLOBAL_EVENTS_HXX + +#ifdef WIN32 +/* DELETE is a WIN32 macro that poisons our namespace; this is a + kludge to allow us to use it anyway */ +#ifdef DELETE +#undef DELETE +#endif +#endif + +namespace GlobalEvents { + enum Event { + /** database update was finished */ + UPDATE, + + /** during database update, a song was deleted */ + DELETE, + + /** an idle event was emitted */ + IDLE, + + /** must call playlist_sync() */ + PLAYLIST, + + /** the current song's tag has changed */ + TAG, + + /** SIGHUP received: reload configuration, roll log file */ + RELOAD, + + /** a hardware mixer plugin has detected a change */ + MIXER, + + /** shutdown requested */ + SHUTDOWN, + + MAX + }; + + typedef void (*Handler)(); + + void Initialize(); + + void Deinitialize(); + + void Register(Event event, Handler handler); + + void Emit(Event event); +} + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/io_thread.c b/src/IOThread.cxx index 7c080adc..192d4cc4 100644 --- a/src/io_thread.c +++ b/src/IOThread.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,16 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "io_thread.h" +#include "config.h" +#include "IOThread.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "event/Loop.hxx" #include <assert.h> static struct { - GMutex *mutex; - GCond *cond; + Mutex mutex; + Cond cond; - GMainContext *context; - GMainLoop *loop; + EventLoop *loop; GThread *thread; } io; @@ -34,10 +37,9 @@ void io_thread_run(void) { assert(io_thread_inside()); - assert(io.context != NULL); assert(io.loop != NULL); - g_main_loop_run(io.loop); + io.loop->Run(); } static gpointer @@ -45,8 +47,8 @@ io_thread_func(G_GNUC_UNUSED gpointer arg) { /* lock+unlock to synchronize with io_thread_start(), to be sure that io.thread is set */ - g_mutex_lock(io.mutex); - g_mutex_unlock(io.mutex); + io.mutex.lock(); + io.mutex.unlock(); io_thread_run(); return NULL; @@ -55,26 +57,21 @@ io_thread_func(G_GNUC_UNUSED gpointer arg) void io_thread_init(void) { - assert(io.context == NULL); assert(io.loop == NULL); assert(io.thread == NULL); - io.mutex = g_mutex_new(); - io.cond = g_cond_new(); - io.context = g_main_context_new(); - io.loop = g_main_loop_new(io.context, false); + io.loop = new EventLoop(); } bool io_thread_start(GError **error_r) { - assert(io.context != NULL); assert(io.loop != NULL); assert(io.thread == NULL); - g_mutex_lock(io.mutex); + io.mutex.lock(); io.thread = g_thread_create(io_thread_func, NULL, true, error_r); - g_mutex_unlock(io.mutex); + io.mutex.unlock(); if (io.thread == NULL) return false; @@ -86,7 +83,7 @@ io_thread_quit(void) { assert(io.loop != NULL); - g_main_loop_quit(io.loop); + io.loop->Break(); } void @@ -98,20 +95,15 @@ io_thread_deinit(void) g_thread_join(io.thread); } - if (io.loop != NULL) - g_main_loop_unref(io.loop); - - if (io.context != NULL) - g_main_context_unref(io.context); - - g_cond_free(io.cond); - g_mutex_free(io.mutex); + delete io.loop; } -GMainContext * -io_thread_context(void) +EventLoop & +io_thread_get() { - return io.context; + assert(io.loop != nullptr); + + return *io.loop; } bool @@ -120,35 +112,6 @@ io_thread_inside(void) return io.thread != NULL && g_thread_self() == io.thread; } -guint -io_thread_idle_add(GSourceFunc function, gpointer data) -{ - GSource *source = g_idle_source_new(); - g_source_set_callback(source, function, data, NULL); - guint id = g_source_attach(source, io.context); - g_source_unref(source); - return id; -} - -GSource * -io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data) -{ - GSource *source = g_timeout_source_new(interval_ms); - g_source_set_callback(source, function, data, NULL); - g_source_attach(source, io.context); - return source; -} - -GSource * -io_thread_timeout_add_seconds(guint interval, - GSourceFunc function, gpointer data) -{ - GSource *source = g_timeout_source_new_seconds(interval); - g_source_set_callback(source, function, data, NULL); - g_source_attach(source, io.context); - return source; -} - struct call_data { GThreadFunc function; gpointer data; @@ -159,15 +122,15 @@ struct call_data { static gboolean io_thread_call_func(gpointer _data) { - struct call_data *data = _data; + struct call_data *data = (struct call_data *)_data; gpointer result = data->function(data->data); - g_mutex_lock(io.mutex); + io.mutex.lock(); data->done = true; data->result = result; - g_cond_broadcast(io.cond); - g_mutex_unlock(io.mutex); + io.cond.broadcast(); + io.mutex.unlock(); return false; } @@ -183,17 +146,18 @@ io_thread_call(GThreadFunc function, gpointer _data) return function(_data); struct call_data data = { - .function = function, - .data = _data, - .done = false, + function, + _data, + false, + nullptr, }; - io_thread_idle_add(io_thread_call_func, &data); + io.loop->AddIdle(io_thread_call_func, &data); - g_mutex_lock(io.mutex); + io.mutex.lock(); while (!data.done) - g_cond_wait(io.cond, io.mutex); - g_mutex_unlock(io.mutex); + io.cond.wait(io.mutex); + io.mutex.unlock(); return data.result; } diff --git a/src/io_thread.h b/src/IOThread.hxx index 8ff5a71e..a9401dc7 100644 --- a/src/io_thread.h +++ b/src/IOThread.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_IO_THREAD_H -#define MPD_IO_THREAD_H +#ifndef MPD_IO_THREAD_HXX +#define MPD_IO_THREAD_HXX + +#include "gcc.h" #include <glib.h> -#include <stdbool.h> + +class EventLoop; void io_thread_init(void); @@ -48,29 +51,17 @@ io_thread_quit(void); void io_thread_deinit(void); -G_GNUC_PURE -GMainContext * -io_thread_context(void); +gcc_pure +EventLoop & +io_thread_get(); /** * Is the current thread the I/O thread? */ -G_GNUC_PURE +gcc_pure bool io_thread_inside(void); -guint -io_thread_idle_add(GSourceFunc function, gpointer data); - -G_GNUC_MALLOC -GSource * -io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data); - -G_GNUC_MALLOC -GSource * -io_thread_timeout_add_seconds(guint interval, - GSourceFunc function, gpointer data); - /** * Call a function synchronously in the I/O thread. */ diff --git a/src/icy_metadata.c b/src/IcyMetaDataParser.cxx index 32953e69..cda63da4 100644 --- a/src/icy_metadata.c +++ b/src/IcyMetaDataParser.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "icy_metadata.h" +#include "IcyMetaDataParser.hxx" #include "tag.h" #include <glib.h> @@ -30,46 +30,37 @@ #define G_LOG_DOMAIN "icy_metadata" void -icy_deinit(struct icy_metadata *im) +IcyMetaDataParser::Reset() { - if (!icy_defined(im)) + if (!IsDefined()) return; - if (im->data_rest == 0 && im->meta_size > 0) - g_free(im->meta_data); + if (data_rest == 0 && meta_size > 0) + g_free(meta_data); - if (im->tag != NULL) - tag_free(im->tag); -} - -void -icy_reset(struct icy_metadata *im) -{ - if (!icy_defined(im)) - return; - - icy_deinit(im); + if (tag != nullptr) + tag_free(tag); - im->data_rest = im->data_size; - im->meta_size = 0; + data_rest = data_size; + meta_size = 0; } size_t -icy_data(struct icy_metadata *im, size_t length) +IcyMetaDataParser::Data(size_t length) { assert(length > 0); - if (!icy_defined(im)) + if (!IsDefined()) return length; - if (im->data_rest == 0) + if (data_rest == 0) return 0; - if (length >= im->data_rest) { - length = im->data_rest; - im->data_rest = 0; + if (length >= data_rest) { + length = data_rest; + data_rest = 0; } else - im->data_rest -= length; + data_rest -= length; return length; } @@ -94,7 +85,7 @@ icy_parse_tag_item(struct tag *tag, const char *item) { gchar **p = g_strsplit(item, "=", 0); - if (p[0] != NULL && p[1] != NULL) { + if (p[0] != nullptr && p[1] != nullptr) { if (strcmp(p[0], "StreamTitle") == 0) icy_add_item(tag, TAG_TITLE, p[1]); else @@ -110,7 +101,7 @@ icy_parse_tag(const char *p) struct tag *tag = tag_new(); gchar **items = g_strsplit(p, ";", 0); - for (unsigned i = 0; items[i] != NULL; ++i) + for (unsigned i = 0; items[i] != nullptr; ++i) icy_parse_tag_item(tag, items[i]); g_strfreev(items); @@ -119,21 +110,21 @@ icy_parse_tag(const char *p) } size_t -icy_meta(struct icy_metadata *im, const void *data, size_t length) +IcyMetaDataParser::Meta(const void *data, size_t length) { - const unsigned char *p = data; + const unsigned char *p = (const unsigned char *)data; - assert(icy_defined(im)); - assert(im->data_rest == 0); + assert(IsDefined()); + assert(data_rest == 0); assert(length > 0); - if (im->meta_size == 0) { + if (meta_size == 0) { /* read meta_size from the first byte of a meta block */ - im->meta_size = *p++ * 16; - if (im->meta_size == 0) { + meta_size = *p++ * 16; + if (meta_size == 0) { /* special case: no metadata */ - im->data_rest = im->data_size; + data_rest = data_size; return 1; } @@ -143,39 +134,39 @@ icy_meta(struct icy_metadata *im, const void *data, size_t length) /* initialize metadata reader, allocate enough memory (+1 for the null terminator) */ - im->meta_position = 0; - im->meta_data = g_malloc(im->meta_size + 1); + meta_position = 0; + meta_data = (char *)g_malloc(meta_size + 1); } - assert(im->meta_position < im->meta_size); + assert(meta_position < meta_size); - if (length > im->meta_size - im->meta_position) - length = im->meta_size - im->meta_position; + if (length > meta_size - meta_position) + length = meta_size - meta_position; - memcpy(im->meta_data + im->meta_position, p, length); - im->meta_position += length; + memcpy(meta_data + meta_position, p, length); + meta_position += length; if (p != data) /* re-add the first byte (which contained meta_size) */ ++length; - if (im->meta_position == im->meta_size) { + if (meta_position == meta_size) { /* null-terminate the string */ - im->meta_data[im->meta_size] = 0; + meta_data[meta_size] = 0; /* parse */ - if (im->tag != NULL) - tag_free(im->tag); + if (tag != nullptr) + tag_free(tag); - im->tag = icy_parse_tag(im->meta_data); - g_free(im->meta_data); + tag = icy_parse_tag(meta_data); + g_free(meta_data); /* change back to normal data mode */ - im->meta_size = 0; - im->data_rest = im->data_size; + meta_size = 0; + data_rest = data_size; } return length; diff --git a/src/IcyMetaDataParser.hxx b/src/IcyMetaDataParser.hxx new file mode 100644 index 00000000..6ccc73f5 --- /dev/null +++ b/src/IcyMetaDataParser.hxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ICY_META_DATA_PARSER_HXX +#define MPD_ICY_META_DATA_PARSER_HXX + +#include <stddef.h> + +class IcyMetaDataParser { + size_t data_size, data_rest; + + size_t meta_size, meta_position; + char *meta_data; + + struct tag *tag; + +public: + IcyMetaDataParser():data_size(0) {} + ~IcyMetaDataParser() { + Reset(); + } + + /** + * Initialize an enabled icy_metadata object with the specified + * data_size (from the icy-metaint HTTP response header). + */ + void Start(size_t _data_size) { + data_size = data_rest = _data_size; + meta_size = 0; + tag = nullptr; + } + + /** + * Resets the icy_metadata. Call this after rewinding the stream. + */ + void Reset(); + + /** + * Checks whether the icy_metadata object is enabled. + */ + bool IsDefined() const { + return data_size > 0; + } + + /** + * Evaluates data. Returns the number of bytes of normal data which + * can be read by the caller, but not more than "length". If the + * return value is smaller than "length", the caller should invoke + * icy_meta(). + */ + size_t Data(size_t length); + + /** + * Reads metadata from the stream. Returns the number of bytes + * consumed. If the return value is smaller than "length", the caller + * should invoke icy_data(). + */ + size_t Meta(const void *data, size_t length); + + struct tag *ReadTag() { + struct tag *result = tag; + tag = nullptr; + return result; + } +}; + +#endif diff --git a/src/icy_server.c b/src/IcyMetaDataServer.cxx index b6c89eaf..b1d07558 100644 --- a/src/icy_server.c +++ b/src/IcyMetaDataServer.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,9 @@ */ #include "config.h" -#include "icy_server.h" +#include "IcyMetaDataServer.hxx" +#include "Page.hxx" +#include "tag.h" #include <glib.h> @@ -84,15 +86,13 @@ icy_server_metadata_string(const char *stream_title, const char* stream_url) return icy_metadata; } -struct page* -icy_server_metadata_page(const struct tag *tag, ...) +Page * +icy_server_metadata_page(const struct tag *tag, const enum tag_type *types) { - va_list args; const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES]; gint last_item, item; guint position; gchar *icy_string; - struct page *icy_metadata; gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata - // "StreamTitle='';StreamUrl='';" // = 4081 - 28 @@ -100,22 +100,11 @@ icy_server_metadata_page(const struct tag *tag, ...) last_item = -1; - va_start(args, tag); - while (1) { - enum tag_type type; - const gchar *tag_item; - - type = va_arg(args, enum tag_type); - - if (type == TAG_NUM_OF_ITEM_TYPES) - break; - - tag_item = tag_get_value(tag, type); - + while (*types != TAG_NUM_OF_ITEM_TYPES) { + const gchar *tag_item = tag_get_value(tag, *types); if (tag_item) tag_items[++last_item] = tag_item; } - va_end(args); position = item = 0; while (position < sizeof(stream_title) && item <= last_item) { @@ -141,7 +130,7 @@ icy_server_metadata_page(const struct tag *tag, ...) if (icy_string == NULL) return NULL; - icy_metadata = page_new_copy(icy_string, (icy_string[0] * 16) + 1); + Page *icy_metadata = Page::Copy(icy_string, (icy_string[0] * 16) + 1); g_free(icy_string); diff --git a/src/icy_server.h b/src/IcyMetaDataServer.hxx index 04f21d2a..b344c61f 100644 --- a/src/icy_server.h +++ b/src/IcyMetaDataServer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,20 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef ICY_SERVER_H -#define ICY_SERVER_H +#ifndef MPD_ICY_META_DATA_SERVER_HXX +#define MPD_ICY_META_DATA_SERVER_HXX -#include "page.h" #include "tag.h" -#include <stdarg.h> +class Page; char* icy_server_metadata_header(const char *name, const char *genre, const char *url, const char *content_type, int metaint); -struct page* -icy_server_metadata_page(const struct tag *tag, ...); +Page * +icy_server_metadata_page(const struct tag *tag, const enum tag_type *types); #endif diff --git a/src/IdTable.hxx b/src/IdTable.hxx new file mode 100644 index 00000000..8925fe8a --- /dev/null +++ b/src/IdTable.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ID_TABLE_HXX +#define MPD_ID_TABLE_HXX + +#include "gcc.h" + +#include <algorithm> + +#include <assert.h> + +/** + * A table that maps id numbers to position numbers. + */ +class IdTable { + unsigned size; + + unsigned next; + + int *data; + +public: + IdTable(unsigned _size):size(_size), next(1), data(new int[size]) { + std::fill(data, data + size, -1); + } + + ~IdTable() { + delete[] data; + } + + int IdToPosition(unsigned id) const { + return id < size + ? data[id] + : -1; + } + + unsigned GenerateId() { + assert(next > 0); + assert(next < size); + + while (true) { + unsigned id = next; + + ++next; + if (next == size) + next = 1; + + if (data[id] < 0) + return id; + } + } + + unsigned Insert(unsigned position) { + unsigned id = GenerateId(); + data[id] = position; + return id; + } + + void Move(unsigned id, unsigned position) { + assert(id < size); + assert(data[id] >= 0); + + data[id] = position; + } + + void Erase(unsigned id) { + assert(id < size); + assert(data[id] >= 0); + + data[id] = -1; + } +}; + +#endif diff --git a/src/idle.c b/src/Idle.cxx index 2d174d78..fefbd2fe 100644 --- a/src/idle.c +++ b/src/Idle.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,14 +23,14 @@ */ #include "config.h" -#include "idle.h" -#include "event_pipe.h" +#include "Idle.hxx" +#include "GlobalEvents.hxx" + +#include <atomic> #include <assert.h> -#include <glib.h> -static unsigned idle_flags; -static GMutex *idle_mutex = NULL; +static std::atomic_uint idle_flags; static const char *const idle_names[] = { "database", @@ -44,47 +44,24 @@ static const char *const idle_names[] = { "update", "subscription", "message", - NULL + nullptr }; void -idle_init(void) -{ - g_assert(idle_mutex == NULL); - idle_mutex = g_mutex_new(); -} - -void -idle_deinit(void) -{ - g_assert(idle_mutex != NULL); - g_mutex_free(idle_mutex); - idle_mutex = NULL; -} - -void idle_add(unsigned flags) { assert(flags != 0); - g_mutex_lock(idle_mutex); - idle_flags |= flags; - g_mutex_unlock(idle_mutex); + unsigned old_flags = idle_flags.fetch_or(flags); - event_pipe_emit(PIPE_EVENT_IDLE); + if ((old_flags & flags) != flags) + GlobalEvents::Emit(GlobalEvents::IDLE); } unsigned idle_get(void) { - unsigned flags; - - g_mutex_lock(idle_mutex); - flags = idle_flags; - idle_flags = 0; - g_mutex_unlock(idle_mutex); - - return flags; + return idle_flags.fetch_and(0); } const char*const* diff --git a/src/idle.h b/src/Idle.hxx index 0156933c..6e4fbf78 100644 --- a/src/idle.h +++ b/src/Idle.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,8 +22,8 @@ * */ -#ifndef MPD_IDLE_H -#define MPD_IDLE_H +#ifndef MPD_IDLE_HXX +#define MPD_IDLE_HXX enum { /** song database has been updated*/ @@ -62,18 +62,6 @@ enum { }; /** - * Initialize the mutex - */ -void -idle_init(void); - -/** - * Destroy the mutex - */ -void -idle_deinit(void); - -/** * Adds idle flag (with bitwise "or") and queues notifications to all * clients. */ diff --git a/src/inotify_queue.c b/src/InotifyQueue.cxx index d5e2228c..3212f95f 100644 --- a/src/inotify_queue.c +++ b/src/InotifyQueue.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,9 @@ */ #include "config.h" -#include "inotify_queue.h" -#include "update.h" +#include "InotifyQueue.hxx" +#include "UpdateGlue.hxx" +#include "event/Loop.hxx" #include <glib.h> @@ -37,37 +38,13 @@ enum { INOTIFY_UPDATE_DELAY_S = 5, }; -static GSList *inotify_queue; -static guint queue_source_id; - -void -mpd_inotify_queue_init(void) -{ -} - -static void -free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - g_free(data); -} - -void -mpd_inotify_queue_finish(void) -{ - if (queue_source_id != 0) - g_source_remove(queue_source_id); - - g_slist_foreach(inotify_queue, free_callback, NULL); - g_slist_free(inotify_queue); -} - -static gboolean -mpd_inotify_run_update(G_GNUC_UNUSED gpointer data) +bool +InotifyQueue::OnTimeout() { unsigned id; - while (inotify_queue != NULL) { - char *uri_utf8 = inotify_queue->data; + while (!queue.empty()) { + const char *uri_utf8 = queue.front().c_str(); id = update_enqueue(uri_utf8, false); if (id == 0) @@ -76,13 +53,10 @@ mpd_inotify_run_update(G_GNUC_UNUSED gpointer data) g_debug("updating '%s' job=%u", uri_utf8, id); - g_free(uri_utf8); - inotify_queue = g_slist_delete_link(inotify_queue, - inotify_queue); + queue.pop_front(); } /* done, remove the timer event by returning false */ - queue_source_id = 0; return false; } @@ -97,39 +71,25 @@ path_in(const char *path, const char *possible_parent) } void -mpd_inotify_enqueue(char *uri_utf8) +InotifyQueue::Enqueue(const char *uri_utf8) { - GSList *old_queue = inotify_queue; - - if (queue_source_id != 0) - g_source_remove(queue_source_id); - queue_source_id = g_timeout_add_seconds(INOTIFY_UPDATE_DELAY_S, - mpd_inotify_run_update, NULL); + ScheduleSeconds(INOTIFY_UPDATE_DELAY_S); - inotify_queue = NULL; - while (old_queue != NULL) { - char *current_uri = old_queue->data; + for (auto i = queue.begin(), end = queue.end(); i != end;) { + const char *current_uri = i->c_str(); - if (path_in(uri_utf8, current_uri)) { + if (path_in(uri_utf8, current_uri)) /* already enqueued */ - g_free(uri_utf8); - inotify_queue = g_slist_concat(inotify_queue, - old_queue); return; - } - - old_queue = g_slist_delete_link(old_queue, old_queue); if (path_in(current_uri, uri_utf8)) /* existing path is a sub-path of the new path; we can dequeue the existing path and update the new path instead */ - g_free(current_uri); + i = queue.erase(i); else - /* move the existing path to the new queue */ - inotify_queue = g_slist_prepend(inotify_queue, - current_uri); + ++i; } - inotify_queue = g_slist_prepend(inotify_queue, uri_utf8); + queue.emplace_back(uri_utf8); } diff --git a/src/filter_internal.h b/src/InotifyQueue.hxx index 4e94599a..761df574 100644 --- a/src/filter_internal.h +++ b/src/InotifyQueue.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,22 +17,25 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -/** \file - * - * Internal stuff for the filter core and filter plugins. - */ +#ifndef MPD_INOTIFY_QUEUE_HXX +#define MPD_INOTIFY_QUEUE_HXX -#ifndef MPD_FILTER_INTERNAL_H -#define MPD_FILTER_INTERNAL_H +#include "event/TimeoutMonitor.hxx" +#include "gcc.h" -struct filter { - const struct filter_plugin *plugin; -}; +#include <list> +#include <string> + +class InotifyQueue final : private TimeoutMonitor { + std::list<std::string> queue; -static inline void -filter_init(struct filter *filter, const struct filter_plugin *plugin) -{ - filter->plugin = plugin; -} +public: + InotifyQueue(EventLoop &_loop):TimeoutMonitor(_loop) {} + + void Enqueue(const char *uri_utf8); + +private: + virtual bool OnTimeout() override; +}; #endif diff --git a/src/inotify_source.c b/src/InotifySource.cxx index e415f5e7..5552ed57 100644 --- a/src/inotify_source.c +++ b/src/InotifySource.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,35 +18,20 @@ */ #include "config.h" -#include "inotify_source.h" -#include "fifo_buffer.h" +#include "InotifySource.hxx" +#include "util/fifo_buffer.h" #include "fd_util.h" #include "mpd_error.h" +#include <glib.h> + #include <sys/inotify.h> #include <unistd.h> #include <errno.h> -#include <stdbool.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "inotify" -struct mpd_inotify_source { - int fd; - - GIOChannel *channel; - - /** - * The channel's source id in the GLib main loop. - */ - guint id; - - struct fifo_buffer *buffer; - - mpd_inotify_callback_t callback; - void *callback_ctx; -}; - /** * A GQuark for GError instances. */ @@ -56,34 +41,32 @@ mpd_inotify_quark(void) return g_quark_from_static_string("inotify"); } -static gboolean -mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source, - G_GNUC_UNUSED GIOCondition condition, - gpointer data) +bool +InotifySource::OnSocketReady(gcc_unused unsigned flags) { - struct mpd_inotify_source *source = data; void *dest; size_t length; ssize_t nbytes; - const struct inotify_event *event; - dest = fifo_buffer_write(source->buffer, &length); + dest = fifo_buffer_write(buffer, &length); if (dest == NULL) MPD_ERROR("buffer full"); - nbytes = read(source->fd, dest, length); + nbytes = read(Get(), dest, length); if (nbytes < 0) MPD_ERROR("failed to read from inotify: %s", g_strerror(errno)); if (nbytes == 0) MPD_ERROR("end of file from inotify"); - fifo_buffer_append(source->buffer, nbytes); + fifo_buffer_append(buffer, nbytes); while (true) { const char *name; - event = fifo_buffer_read(source->buffer, &length); + const struct inotify_event *event = + (const struct inotify_event *) + fifo_buffer_read(buffer, &length); if (event == NULL || length < sizeof(*event) || length < sizeof(*event) + event->len) break; @@ -93,59 +76,50 @@ mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source, else name = NULL; - source->callback(event->wd, event->mask, name, - source->callback_ctx); - fifo_buffer_consume(source->buffer, - sizeof(*event) + event->len); + callback(event->wd, event->mask, name, callback_ctx); + fifo_buffer_consume(buffer, sizeof(*event) + event->len); } return true; } -struct mpd_inotify_source * -mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx, - GError **error_r) +inline +InotifySource::InotifySource(EventLoop &_loop, + mpd_inotify_callback_t _callback, void *_ctx, + int _fd) + :SocketMonitor(_fd, _loop), + callback(_callback), callback_ctx(_ctx), + buffer(fifo_buffer_new(4096)) { - struct mpd_inotify_source *source = - g_new(struct mpd_inotify_source, 1); + ScheduleRead(); - source->fd = inotify_init_cloexec(); - if (source->fd < 0) { +} + +InotifySource * +InotifySource::Create(EventLoop &loop, + mpd_inotify_callback_t callback, void *callback_ctx, + GError **error_r) +{ + int fd = inotify_init_cloexec(); + if (fd < 0) { g_set_error(error_r, mpd_inotify_quark(), errno, "inotify_init() has failed: %s", g_strerror(errno)); - g_free(source); return NULL; } - source->buffer = fifo_buffer_new(4096); - - source->channel = g_io_channel_unix_new(source->fd); - source->id = g_io_add_watch(source->channel, G_IO_IN, - mpd_inotify_in_event, source); - - source->callback = callback; - source->callback_ctx = callback_ctx; - - return source; + return new InotifySource(loop, callback, callback_ctx, fd); } -void -mpd_inotify_source_free(struct mpd_inotify_source *source) +InotifySource::~InotifySource() { - g_source_remove(source->id); - g_io_channel_unref(source->channel); - fifo_buffer_free(source->buffer); - close(source->fd); - g_free(source); + fifo_buffer_free(buffer); } int -mpd_inotify_source_add(struct mpd_inotify_source *source, - const char *path_fs, unsigned mask, - GError **error_r) +InotifySource::Add(const char *path_fs, unsigned mask, GError **error_r) { - int wd = inotify_add_watch(source->fd, path_fs, mask); + int wd = inotify_add_watch(Get(), path_fs, mask); if (wd < 0) g_set_error(error_r, mpd_inotify_quark(), errno, "inotify_add_watch() has failed: %s", @@ -155,9 +129,9 @@ mpd_inotify_source_add(struct mpd_inotify_source *source, } void -mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd) +InotifySource::Remove(unsigned wd) { - int ret = inotify_rm_watch(source->fd, wd); + int ret = inotify_rm_watch(Get(), wd); if (ret < 0 && errno != EINVAL) g_warning("inotify_rm_watch() has failed: %s", g_strerror(errno)); diff --git a/src/InotifySource.hxx b/src/InotifySource.hxx new file mode 100644 index 00000000..e8f9ff03 --- /dev/null +++ b/src/InotifySource.hxx @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INOTIFY_SOURCE_HXX +#define MPD_INOTIFY_SOURCE_HXX + +#include "event/SocketMonitor.hxx" +#include "gerror.h" +#include "gcc.h" + +typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask, + const char *name, void *ctx); + +class InotifySource final : private SocketMonitor { + mpd_inotify_callback_t callback; + void *callback_ctx; + + struct fifo_buffer *buffer; + + InotifySource(EventLoop &_loop, + mpd_inotify_callback_t callback, void *ctx, int fd); + +public: + /** + * Creates a new inotify source and registers it in the GLib main + * loop. + * + * @param a callback invoked for events received from the kernel + */ + static InotifySource *Create(EventLoop &_loop, + mpd_inotify_callback_t callback, + void *ctx, + GError **error_r); + + ~InotifySource(); + + + /** + * Adds a path to the notify list. + * + * @return a watch descriptor or -1 on error + */ + int Add(const char *path_fs, unsigned mask, GError **error_r); + + /** + * Removes a path from the notify list. + * + * @param wd the watch descriptor returned by mpd_inotify_source_add() + */ + void Remove(unsigned wd); + +private: + virtual bool OnSocketReady(unsigned flags) override; +}; + +#endif diff --git a/src/inotify_update.c b/src/InotifyUpdate.cxx index 3f4a8c0c..2de3bf62 100644 --- a/src/inotify_update.c +++ b/src/InotifyUpdate.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,17 +18,21 @@ */ #include "config.h" /* must be first for large file support */ -#include "inotify_update.h" -#include "inotify_source.h" -#include "inotify_queue.h" -#include "database.h" -#include "mapper.h" -#include "path.h" +#include "InotifyUpdate.hxx" +#include "InotifySource.hxx" +#include "InotifyQueue.hxx" +#include "Mapper.hxx" +#include "Main.hxx" +#include "fs/Path.hxx" + +#include <glib.h> + +#include <map> +#include <forward_list> #include <assert.h> #include <sys/inotify.h> #include <sys/stat.h> -#include <stdbool.h> #include <string.h> #include <dirent.h> #include <errno.h> @@ -44,57 +48,73 @@ enum { #endif }; -struct watch_directory { - struct watch_directory *parent; +struct WatchDirectory { + WatchDirectory *parent; char *name; int descriptor; - GList *children; + std::forward_list<WatchDirectory> children; + + WatchDirectory(WatchDirectory *_parent, const char *_name, + int _descriptor) + :parent(_parent), name(g_strdup(_name)), + descriptor(_descriptor) {} + + WatchDirectory(const WatchDirectory &) = delete; + WatchDirectory &operator=(const WatchDirectory &) = delete; + + ~WatchDirectory() { + g_free(name); + } }; -static struct mpd_inotify_source *inotify_source; +static InotifySource *inotify_source; +static InotifyQueue *inotify_queue; static unsigned inotify_max_depth; -static struct watch_directory inotify_root; -static GTree *inotify_directories; +static WatchDirectory *inotify_root; +static std::map<int, WatchDirectory *> inotify_directories; -static gint -compare(gconstpointer a, gconstpointer b) +static void +tree_add_watch_directory(WatchDirectory *directory) { - if (a < b) - return -1; - else if (a > b) - return 1; - else - return 0; + inotify_directories.insert(std::make_pair(directory->descriptor, + directory)); } static void -tree_add_watch_directory(struct watch_directory *directory) +tree_remove_watch_directory(WatchDirectory *directory) { - g_tree_insert(inotify_directories, - GINT_TO_POINTER(directory->descriptor), directory); + auto i = inotify_directories.find(directory->descriptor); + assert(i != inotify_directories.end()); + inotify_directories.erase(i); } -static void -tree_remove_watch_directory(struct watch_directory *directory) +static WatchDirectory * +tree_find_watch_directory(int wd) { - G_GNUC_UNUSED - bool found = g_tree_remove(inotify_directories, - GINT_TO_POINTER(directory->descriptor)); - assert(found); + auto i = inotify_directories.find(wd); + if (i == inotify_directories.end()) + return nullptr; + + return i->second; } -static struct watch_directory * -tree_find_watch_directory(int wd) +static void +disable_watch_directory(WatchDirectory &directory) { - return g_tree_lookup(inotify_directories, GINT_TO_POINTER(wd)); + tree_remove_watch_directory(&directory); + + for (WatchDirectory &child : directory.children) + disable_watch_directory(child); + + inotify_source->Remove(directory.descriptor); } static void -remove_watch_directory(struct watch_directory *directory) +remove_watch_directory(WatchDirectory *directory) { assert(directory != NULL); @@ -104,23 +124,16 @@ remove_watch_directory(struct watch_directory *directory) return; } - assert(directory->parent->children != NULL); - - tree_remove_watch_directory(directory); + disable_watch_directory(*directory); - while (directory->children != NULL) - remove_watch_directory(directory->children->data); - - directory->parent->children = - g_list_remove(directory->parent->children, directory); - - mpd_inotify_source_rm(inotify_source, directory->descriptor); - g_free(directory->name); - g_slice_free(struct watch_directory, directory); + /* remove it from the parent, which effectively deletes it */ + directory->parent->children.remove_if([directory](const WatchDirectory &child){ + return &child == directory; + }); } static char * -watch_directory_get_uri_fs(const struct watch_directory *directory) +watch_directory_get_uri_fs(const WatchDirectory *directory) { char *parent_uri, *uri; @@ -146,7 +159,7 @@ static bool skip_path(const char *path) } static void -recursive_watch_subdirectories(struct watch_directory *directory, +recursive_watch_subdirectories(WatchDirectory *directory, const char *path_fs, unsigned depth) { GError *error = NULL; @@ -173,7 +186,6 @@ recursive_watch_subdirectories(struct watch_directory *directory, char *child_path_fs; struct stat st; int ret; - struct watch_directory *child; if (skip_path(ent->d_name)) continue; @@ -192,8 +204,7 @@ recursive_watch_subdirectories(struct watch_directory *directory, continue; } - ret = mpd_inotify_source_add(inotify_source, child_path_fs, - IN_MASK, &error); + ret = inotify_source->Add(child_path_fs, IN_MASK, &error); if (ret < 0) { g_warning("Failed to register %s: %s", child_path_fs, error->message); @@ -203,21 +214,15 @@ recursive_watch_subdirectories(struct watch_directory *directory, continue; } - child = tree_find_watch_directory(ret); + WatchDirectory *child = tree_find_watch_directory(ret); if (child != NULL) { /* already being watched */ g_free(child_path_fs); continue; } - child = g_slice_new(struct watch_directory); - child->parent = directory; - child->name = g_strdup(ent->d_name); - child->descriptor = ret; - child->children = NULL; - - directory->children = g_list_prepend(directory->children, - child); + directory->children.emplace_front(directory, ent->d_name, ret); + child = &directory->children.front(); tree_add_watch_directory(child); @@ -230,7 +235,7 @@ recursive_watch_subdirectories(struct watch_directory *directory, G_GNUC_PURE static unsigned -watch_directory_depth(const struct watch_directory *d) +watch_directory_depth(const WatchDirectory *d) { assert(d != NULL); @@ -245,7 +250,7 @@ static void mpd_inotify_callback(int wd, unsigned mask, G_GNUC_UNUSED const char *name, G_GNUC_UNUSED void *ctx) { - struct watch_directory *directory; + WatchDirectory *directory; char *uri_fs; /*g_debug("wd=%d mask=0x%x name='%s'", wd, mask, name);*/ @@ -266,7 +271,7 @@ mpd_inotify_callback(int wd, unsigned mask, (mask & IN_ISDIR) != 0) { /* a sub directory was changed: register those in inotify */ - const char *root = mapper_get_music_directory_fs(); + const char *root = mapper_get_music_directory_fs().c_str(); const char *path_fs; char *allocated = NULL; @@ -288,14 +293,14 @@ mpd_inotify_callback(int wd, unsigned mask, (mask & (IN_CREATE|IN_ISDIR)) == (IN_CREATE|IN_ISDIR))) { /* a file was changed, or a directory was moved/deleted: queue a database update */ - char *uri_utf8 = uri_fs != NULL - ? fs_charset_to_utf8(uri_fs) - : g_strdup(""); - - if (uri_utf8 != NULL) - /* this function will take care of freeing - uri_utf8 */ - mpd_inotify_enqueue(uri_utf8); + + if (uri_fs != nullptr) { + const std::string uri_utf8 = Path::ToUTF8(uri_fs); + if (!uri_utf8.empty()) + inotify_queue->Enqueue(uri_utf8.c_str()); + } + else + inotify_queue->Enqueue(""); } g_free(uri_fs); @@ -308,14 +313,15 @@ mpd_inotify_init(unsigned max_depth) g_debug("initializing inotify"); - const char *path = mapper_get_music_directory_fs(); - if (path == NULL) { + const Path &path = mapper_get_music_directory_fs(); + if (path.IsNull()) { g_debug("no music directory configured"); return; } - inotify_source = mpd_inotify_source_new(mpd_inotify_callback, NULL, - &error); + inotify_source = InotifySource::Create(*main_loop, + mpd_inotify_callback, nullptr, + &error); if (inotify_source == NULL) { g_warning("%s", error->message); g_error_free(error); @@ -324,40 +330,24 @@ mpd_inotify_init(unsigned max_depth) inotify_max_depth = max_depth; - inotify_root.name = g_strdup(path); - inotify_root.descriptor = mpd_inotify_source_add(inotify_source, path, - IN_MASK, &error); - if (inotify_root.descriptor < 0) { + int descriptor = inotify_source->Add(path.c_str(), IN_MASK, &error); + if (descriptor < 0) { g_warning("%s", error->message); g_error_free(error); - mpd_inotify_source_free(inotify_source); + delete inotify_source; inotify_source = NULL; return; } - inotify_directories = g_tree_new(compare); - tree_add_watch_directory(&inotify_root); - - recursive_watch_subdirectories(&inotify_root, path, 0); + inotify_root = new WatchDirectory(nullptr, path.c_str(), descriptor); - mpd_inotify_queue_init(); + tree_add_watch_directory(inotify_root); - g_debug("watching music directory"); -} - -static gboolean -free_watch_directory(G_GNUC_UNUSED gpointer key, gpointer value, - G_GNUC_UNUSED gpointer data) -{ - struct watch_directory *directory = value; + recursive_watch_subdirectories(inotify_root, path.c_str(), 0); - g_free(directory->name); - g_list_free(directory->children); + inotify_queue = new InotifyQueue(*main_loop); - if (directory != &inotify_root) - g_slice_free(struct watch_directory, directory); - - return false; + g_debug("watching music directory"); } void @@ -366,9 +356,8 @@ mpd_inotify_finish(void) if (inotify_source == NULL) return; - mpd_inotify_queue_finish(); - mpd_inotify_source_free(inotify_source); - - g_tree_foreach(inotify_directories, free_watch_directory, NULL); - g_tree_destroy(inotify_directories); + delete inotify_queue; + delete inotify_source; + delete inotify_root; + inotify_directories.clear(); } diff --git a/src/inotify_update.h b/src/InotifyUpdate.hxx index ca75c0f4..ceb42155 100644 --- a/src/inotify_update.h +++ b/src/InotifyUpdate.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INOTIFY_UPDATE_H -#define MPD_INOTIFY_UPDATE_H +#ifndef MPD_INOTIFY_UPDATE_HXX +#define MPD_INOTIFY_UPDATE_HXX #include "check.h" diff --git a/src/input_init.c b/src/InputInit.cxx index 771d648d..64cdfc7a 100644 --- a/src/input_init.c +++ b/src/InputInit.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,9 @@ */ #include "config.h" -#include "input_init.h" -#include "input_plugin.h" -#include "input_registry.h" +#include "InputInit.hxx" +#include "InputRegistry.hxx" +#include "InputPlugin.hxx" #include "conf.h" #include <assert.h> diff --git a/src/input_init.h b/src/InputInit.hxx index ad92cda0..9d503e5a 100644 --- a/src/input_init.h +++ b/src/InputInit.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INPUT_INIT_H -#define MPD_INPUT_INIT_H +#ifndef MPD_INPUT_INIT_HXX +#define MPD_INPUT_INIT_HXX -#include "check.h" - -#include <glib.h> -#include <stdbool.h> +#include "gerror.h" /** * Initializes this library and all input_stream implementations. diff --git a/src/InputInternal.cxx b/src/InputInternal.cxx new file mode 100644 index 00000000..e110ebf0 --- /dev/null +++ b/src/InputInternal.cxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "InputInternal.hxx" +#include "InputStream.hxx" + +void +input_stream_signal_client(struct input_stream *is) +{ + is->cond.broadcast(); +} + +void +input_stream_set_ready(struct input_stream *is) +{ + const ScopeLock protect(is->mutex); + + if (!is->ready) { + is->ready = true; + input_stream_signal_client(is); + } +} diff --git a/src/InputInternal.hxx b/src/InputInternal.hxx new file mode 100644 index 00000000..019ac09c --- /dev/null +++ b/src/InputInternal.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_INTERNAL_HXX +#define MPD_INPUT_INTERNAL_HXX + +#include "check.h" + +struct input_stream; + +void +input_stream_signal_client(struct input_stream *is); + +void +input_stream_set_ready(struct input_stream *is); + +#endif diff --git a/src/input_plugin.h b/src/InputPlugin.hxx index 6b0c77c8..c1660081 100644 --- a/src/input_plugin.h +++ b/src/InputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INPUT_PLUGIN_H -#define MPD_INPUT_PLUGIN_H +#ifndef MPD_INPUT_PLUGIN_HXX +#define MPD_INPUT_PLUGIN_HXX #include "input_stream.h" #include <stddef.h> -#include <stdbool.h> #include <sys/types.h> struct config_param; @@ -49,7 +48,7 @@ struct input_plugin { void (*finish)(void); struct input_stream *(*open)(const char *uri, - GMutex *mutex, GCond *cond, + Mutex &mutex, Cond &cond, GError **error_r); void (*close)(struct input_stream *is); diff --git a/src/input_registry.c b/src/InputRegistry.cxx index 5987d5da..342720d2 100644 --- a/src/input_registry.c +++ b/src/InputRegistry.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,35 +18,35 @@ */ #include "config.h" -#include "input_registry.h" -#include "input/file_input_plugin.h" +#include "InputRegistry.hxx" +#include "input/FileInputPlugin.hxx" #ifdef ENABLE_ARCHIVE -#include "input/archive_input_plugin.h" +#include "input/ArchiveInputPlugin.hxx" #endif #ifdef ENABLE_CURL -#include "input/curl_input_plugin.h" +#include "input/CurlInputPlugin.hxx" #endif #ifdef ENABLE_SOUP -#include "input/soup_input_plugin.h" +#include "input/SoupInputPlugin.hxx" #endif #ifdef HAVE_FFMPEG -#include "input/ffmpeg_input_plugin.h" +#include "input/FfmpegInputPlugin.hxx" #endif #ifdef ENABLE_MMS -#include "input/mms_input_plugin.h" +#include "input/MmsInputPlugin.hxx" #endif #ifdef ENABLE_CDIO_PARANOIA -#include "input/cdio_paranoia_input_plugin.h" +#include "input/CdioParanoiaInputPlugin.hxx" #endif #ifdef ENABLE_DESPOTIFY -#include "input/despotify_input_plugin.h" +#include "input/DespotifyInputPlugin.hxx" #endif #include <glib.h> diff --git a/src/input_registry.h b/src/InputRegistry.hxx index 4f5fff8d..a080d108 100644 --- a/src/input_registry.h +++ b/src/InputRegistry.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INPUT_REGISTRY_H -#define MPD_INPUT_REGISTRY_H +#ifndef MPD_INPUT_REGISTRY_HXX +#define MPD_INPUT_REGISTRY_HXX #include "check.h" -#include <stdbool.h> - /** * NULL terminated list of all input plugins which were enabled at * compile time. diff --git a/src/input_stream.c b/src/InputStream.cxx index e445dca6..913fcf63 100644 --- a/src/input_stream.c +++ b/src/InputStream.cxx @@ -18,10 +18,14 @@ */ #include "config.h" -#include "input_stream.h" -#include "input_registry.h" -#include "input_plugin.h" -#include "input/rewind_input_plugin.h" +#include "InputStream.hxx" +#include "InputRegistry.hxx" +#include "InputPlugin.hxx" +#include "input/RewindInputPlugin.hxx" + +extern "C" { +#include "uri.h" +} #include <glib.h> #include <assert.h> @@ -34,12 +38,11 @@ input_quark(void) struct input_stream * input_stream_open(const char *url, - GMutex *mutex, GCond *cond, + Mutex &mutex, Cond &cond, GError **error_r) { GError *error = NULL; - assert(mutex != NULL); assert(error_r == NULL || *error_r == NULL); input_plugins_for_each_enabled(plugin) { @@ -47,11 +50,10 @@ input_stream_open(const char *url, is = plugin->open(url, mutex, cond, &error); if (is != NULL) { - assert(is->plugin != NULL); - assert(is->plugin->close != NULL); - assert(is->plugin->read != NULL); - assert(is->plugin->eof != NULL); - assert(!is->seekable || is->plugin->seek != NULL); + assert(is->plugin.close != NULL); + assert(is->plugin.read != NULL); + assert(is->plugin.eof != NULL); + assert(!is->seekable || is->plugin.seek != NULL); is = input_rewind_open(is); @@ -70,35 +72,31 @@ bool input_stream_check(struct input_stream *is, GError **error_r) { assert(is != NULL); - assert(is->plugin != NULL); - return is->plugin->check == NULL || - is->plugin->check(is, error_r); + return is->plugin.check == NULL || + is->plugin.check(is, error_r); } void input_stream_update(struct input_stream *is) { assert(is != NULL); - assert(is->plugin != NULL); - if (is->plugin->update != NULL) - is->plugin->update(is); + if (is->plugin.update != NULL) + is->plugin.update(is); } void input_stream_wait_ready(struct input_stream *is) { assert(is != NULL); - assert(is->mutex != NULL); - assert(is->cond != NULL); while (true) { input_stream_update(is); if (is->ready) break; - g_cond_wait(is->cond, is->mutex); + is->cond.wait(is->mutex); } } @@ -106,12 +104,60 @@ void input_stream_lock_wait_ready(struct input_stream *is) { assert(is != NULL); - assert(is->mutex != NULL); - assert(is->cond != NULL); - g_mutex_lock(is->mutex); + const ScopeLock protect(is->mutex); input_stream_wait_ready(is); - g_mutex_unlock(is->mutex); +} + +const char * +input_stream_get_mime_type(const struct input_stream *is) +{ + assert(is != NULL); + assert(is->ready); + + return is->mime.empty() ? nullptr : is->mime.c_str(); +} + +void +input_stream_override_mime_type(struct input_stream *is, const char *mime) +{ + assert(is != NULL); + assert(is->ready); + + is->mime = mime; +} + +goffset +input_stream_get_size(const struct input_stream *is) +{ + assert(is != NULL); + assert(is->ready); + + return is->size; +} + +goffset +input_stream_get_offset(const struct input_stream *is) +{ + assert(is != NULL); + assert(is->ready); + + return is->offset; +} + +bool +input_stream_is_seekable(const struct input_stream *is) +{ + assert(is != NULL); + assert(is->ready); + + return is->seekable; +} + +bool +input_stream_cheap_seeking(const struct input_stream *is) +{ + return is->seekable && !uri_has_scheme(is->uri.c_str()); } bool @@ -119,12 +165,11 @@ input_stream_seek(struct input_stream *is, goffset offset, int whence, GError **error_r) { assert(is != NULL); - assert(is->plugin != NULL); - if (is->plugin->seek == NULL) + if (is->plugin.seek == NULL) return false; - return is->plugin->seek(is, offset, whence, error_r); + return is->plugin.seek(is, offset, whence, error_r); } bool @@ -132,29 +177,21 @@ input_stream_lock_seek(struct input_stream *is, goffset offset, int whence, GError **error_r) { assert(is != NULL); - assert(is->plugin != NULL); - if (is->plugin->seek == NULL) + if (is->plugin.seek == NULL) return false; - if (is->mutex == NULL) - /* no locking */ - return input_stream_seek(is, offset, whence, error_r); - - g_mutex_lock(is->mutex); - bool success = input_stream_seek(is, offset, whence, error_r); - g_mutex_unlock(is->mutex); - return success; + const ScopeLock protect(is->mutex); + return input_stream_seek(is, offset, whence, error_r); } struct tag * input_stream_tag(struct input_stream *is) { assert(is != NULL); - assert(is->plugin != NULL); - return is->plugin->tag != NULL - ? is->plugin->tag(is) + return is->plugin.tag != NULL + ? is->plugin.tag(is) : NULL; } @@ -162,29 +199,21 @@ struct tag * input_stream_lock_tag(struct input_stream *is) { assert(is != NULL); - assert(is->plugin != NULL); - if (is->plugin->tag == NULL) - return false; - - if (is->mutex == NULL) - /* no locking */ - return input_stream_tag(is); + if (is->plugin.tag == NULL) + return nullptr; - g_mutex_lock(is->mutex); - struct tag *tag = input_stream_tag(is); - g_mutex_unlock(is->mutex); - return tag; + const ScopeLock protect(is->mutex); + return input_stream_tag(is); } bool input_stream_available(struct input_stream *is) { assert(is != NULL); - assert(is->plugin != NULL); - return is->plugin->available != NULL - ? is->plugin->available(is) + return is->plugin.available != NULL + ? is->plugin.available(is) : true; } @@ -195,7 +224,7 @@ input_stream_read(struct input_stream *is, void *ptr, size_t size, assert(ptr != NULL); assert(size > 0); - return is->plugin->read(is, ptr, size, error_r); + return is->plugin.read(is, ptr, size, error_r); } size_t @@ -205,39 +234,26 @@ input_stream_lock_read(struct input_stream *is, void *ptr, size_t size, assert(ptr != NULL); assert(size > 0); - if (is->mutex == NULL) - /* no locking */ - return input_stream_read(is, ptr, size, error_r); - - g_mutex_lock(is->mutex); - size_t nbytes = input_stream_read(is, ptr, size, error_r); - g_mutex_unlock(is->mutex); - return nbytes; + const ScopeLock protect(is->mutex); + return input_stream_read(is, ptr, size, error_r); } void input_stream_close(struct input_stream *is) { - is->plugin->close(is); + is->plugin.close(is); } bool input_stream_eof(struct input_stream *is) { - return is->plugin->eof(is); + return is->plugin.eof(is); } bool input_stream_lock_eof(struct input_stream *is) { assert(is != NULL); - assert(is->plugin != NULL); - - if (is->mutex == NULL) - /* no locking */ - return input_stream_eof(is); - g_mutex_lock(is->mutex); - bool eof = input_stream_eof(is); - g_mutex_unlock(is->mutex); - return eof; + const ScopeLock protect(is->mutex); + return input_stream_eof(is); } diff --git a/src/InputStream.hxx b/src/InputStream.hxx new file mode 100644 index 00000000..96172723 --- /dev/null +++ b/src/InputStream.hxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_STREAM_HXX +#define MPD_INPUT_STREAM_HXX + +#include "input_stream.h" +#include "check.h" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "gcc.h" + +#include <string> + +#include <assert.h> + +struct input_stream { + /** + * the plugin which implements this input stream + */ + const struct input_plugin &plugin; + + /** + * The absolute URI which was used to open this stream. + */ + std::string uri; + + /** + * A mutex that protects the mutable attributes of this object + * and its implementation. It must be locked before calling + * any of the public methods. + * + * This object is allocated by the client, and the client is + * responsible for freeing it. + */ + Mutex &mutex; + + /** + * A cond that gets signalled when the state of this object + * changes from the I/O thread. The client of this object may + * wait on it. Optional, may be NULL. + * + * This object is allocated by the client, and the client is + * responsible for freeing it. + */ + Cond &cond; + + /** + * indicates whether the stream is ready for reading and + * whether the other attributes in this struct are valid + */ + bool ready; + + /** + * if true, then the stream is fully seekable + */ + bool seekable; + + /** + * the size of the resource, or -1 if unknown + */ + goffset size; + + /** + * the current offset within the stream + */ + goffset offset; + + /** + * the MIME content type of the resource, or empty if unknown. + */ + std::string mime; + + input_stream(const input_plugin &_plugin, + const char *_uri, Mutex &_mutex, Cond &_cond) + :plugin(_plugin), uri(_uri), + mutex(_mutex), cond(_cond), + ready(false), seekable(false), + size(-1), offset(0) { + assert(_uri != NULL); + } +}; + +gcc_nonnull(1) +static inline void +input_stream_lock(struct input_stream *is) +{ + is->mutex.lock(); +} + +gcc_nonnull(1) +static inline void +input_stream_unlock(struct input_stream *is) +{ + is->mutex.unlock(); +} + +#endif diff --git a/src/listen.c b/src/Listen.cxx index 90e13b9c..26c9e100 100644 --- a/src/listen.c +++ b/src/Listen.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,11 @@ */ #include "config.h" -#include "listen.h" -#include "server_socket.h" -#include "client.h" +#include "Listen.hxx" +#include "Main.hxx" +#include "Client.hxx" #include "conf.h" -#include "main.h" +#include "event/ServerSocket.hxx" #include <string.h> #include <assert.h> @@ -36,15 +36,20 @@ #define DEFAULT_PORT 6600 -static struct server_socket *listen_socket; -int listen_port; +class ClientListener final : public ServerSocket { +public: + ClientListener():ServerSocket(*main_loop) {} -static void -listen_callback(int fd, const struct sockaddr *address, - size_t address_length, int uid, G_GNUC_UNUSED void *ctx) -{ - client_new(global_player_control, fd, address, address_length, uid); -} +private: + virtual void OnAccept(int fd, const sockaddr &address, + size_t address_length, int uid) { + client_new(*main_loop, *global_partition, + fd, &address, address_length, uid); + } +}; + +static ClientListener *listen_socket; +int listen_port; static bool listen_add_config_param(unsigned int port, @@ -54,13 +59,11 @@ listen_add_config_param(unsigned int port, assert(param != NULL); if (0 == strcmp(param->value, "any")) { - return server_socket_add_port(listen_socket, port, error_r); + return listen_socket->AddPort(port, error_r); } else if (param->value[0] == '/') { - return server_socket_add_path(listen_socket, param->value, - error_r); + return listen_socket->AddPath(param->value, error_r); } else { - return server_socket_add_host(listen_socket, param->value, - port, error_r); + return listen_socket->AddHost(param->value, port, error_r); } } @@ -78,7 +81,7 @@ listen_systemd_activation(GError **error_r) for (int i = SD_LISTEN_FDS_START, end = SD_LISTEN_FDS_START + n; i != end; ++i) - if (!server_socket_add_fd(listen_socket, i, error_r)) + if (!listen_socket->AddFD(i, error_r)) return false; return true; @@ -91,13 +94,15 @@ listen_systemd_activation(GError **error_r) bool listen_global_init(GError **error_r) { + assert(main_loop != nullptr); + int port = config_get_positive(CONF_PORT, DEFAULT_PORT); const struct config_param *param = config_get_next_param(CONF_BIND_TO_ADDRESS, NULL); bool success; GError *error = NULL; - listen_socket = server_socket_new(listen_callback, NULL); + listen_socket = new ClientListener(); if (listen_systemd_activation(&error)) return true; @@ -114,6 +119,7 @@ listen_global_init(GError **error_r) do { success = listen_add_config_param(port, param, &error); if (!success) { + delete listen_socket; g_propagate_prefixed_error(error_r, error, "Failed to listen on %s (line %i): ", param->value, param->line); @@ -127,8 +133,9 @@ listen_global_init(GError **error_r) /* no "bind_to_address" configured, bind the configured port on all interfaces */ - success = server_socket_add_port(listen_socket, port, error_r); + success = listen_socket->AddPort(port, error_r); if (!success) { + delete listen_socket; g_propagate_prefixed_error(error_r, error, "Failed to listen on *:%d: ", port); @@ -136,8 +143,10 @@ listen_global_init(GError **error_r) } } - if (!server_socket_open(listen_socket, error_r)) + if (!listen_socket->Open(error_r)) { + delete listen_socket; return false; + } listen_port = port; return true; @@ -149,5 +158,5 @@ void listen_global_finish(void) assert(listen_socket != NULL); - server_socket_free(listen_socket); + delete listen_socket; } diff --git a/src/listen.h b/src/Listen.hxx index 246e8370..fd553477 100644 --- a/src/listen.h +++ b/src/Listen.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_LISTEN_H -#define MPD_LISTEN_H +#ifndef MPD_LISTEN_HXX +#define MPD_LISTEN_HXX -#include <glib.h> +#include "gerror.h" #include <stdbool.h> @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,8 @@ */ #include "config.h" -#include "log.h" +#include "Log.hxx" #include "conf.h" -#include "utils.h" #include "fd_util.h" #include "mpd_error.h" @@ -262,8 +261,7 @@ log_init(bool verbose, bool use_stdout, GError **error_r) return true; #else g_set_error(error_r, log_quark(), 0, - "config parameter \"%s\" not found", - CONF_LOG_FILE); + "config parameter 'log_file' not found"); return false; #endif #ifdef HAVE_SYSLOG @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_LOG_H -#define MPD_LOG_H +#ifndef MPD_LOG_HXX +#define MPD_LOG_HXX #include <glib.h> #include <stdbool.h> diff --git a/src/main.c b/src/Main.cxx index 12f8d86f..9b6630d6 100644 --- a/src/main.c +++ b/src/Main.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,55 +18,59 @@ */ #include "config.h" -#include "main.h" -#include "daemon.h" -#include "io_thread.h" -#include "client.h" -#include "client_idle.h" -#include "idle.h" -#include "command.h" -#include "playlist.h" -#include "stored_playlist.h" -#include "database.h" -#include "update.h" -#include "player_thread.h" -#include "listen.h" -#include "cmdline.h" +#include "Main.hxx" +#include "CommandLine.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistGlobal.hxx" +#include "UpdateGlue.hxx" +#include "MusicChunk.hxx" +#include "StateFile.hxx" +#include "PlayerThread.hxx" +#include "Mapper.hxx" +#include "DatabaseGlue.hxx" +#include "DatabaseSimple.hxx" +#include "Permission.hxx" +#include "Listen.hxx" +#include "Client.hxx" +#include "ClientList.hxx" +#include "AllCommands.hxx" +#include "Partition.hxx" +#include "Volume.hxx" +#include "OutputAll.hxx" +#include "tag.h" #include "conf.h" -#include "path.h" -#include "mapper.h" -#include "chunk.h" -#include "player_control.h" +#include "replay_gain_config.h" +#include "Idle.hxx" +#include "SignalHandlers.hxx" +#include "Log.hxx" +#include "GlobalEvents.hxx" +#include "InputInit.hxx" +#include "event/Loop.hxx" +#include "IOThread.hxx" +#include "fs/Path.hxx" +#include "PlaylistRegistry.hxx" +#include "ZeroconfGlue.hxx" +#include "DecoderList.hxx" +#include "AudioConfig.hxx" + +extern "C" { +#include "daemon.h" #include "stats.h" -#include "sig_handlers.h" -#include "audio_config.h" -#include "output_all.h" -#include "volume.h" -#include "log.h" -#include "permission.h" #include "pcm_resample.h" -#include "replay_gain_config.h" -#include "decoder_list.h" -#include "input_init.h" -#include "playlist_list.h" -#include "state_file.h" -#include "tag.h" -#include "dbUtils.h" -#include "zeroconf.h" -#include "event_pipe.h" -#include "tag_pool.h" +} + #include "mpd_error.h" #ifdef ENABLE_INOTIFY -#include "inotify_update.h" +#include "InotifyUpdate.hxx" #endif #ifdef ENABLE_SQLITE -#include "sticker.h" +#include "StickerDatabase.hxx" #endif #ifdef ENABLE_ARCHIVE -#include "archive_list.h" +#include "ArchiveList.hxx" #endif #include <glib.h> @@ -91,11 +95,18 @@ enum { }; GThread *main_task; -GMainLoop *main_loop; +EventLoop *main_loop; + +ClientList *client_list; -GCond *main_cond; +Partition *global_partition; -struct player_control *global_player_control; +static StateFile *state_file; + +static inline GQuark main_quark() +{ + return g_quark_from_static_string ("main"); +} static bool glue_daemonize_init(const struct options *options, GError **error_r) @@ -138,7 +149,10 @@ glue_mapper_init(GError **error_r) if (music_dir == NULL) music_dir = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC)); - mapper_init(music_dir, playlist_dir); + if (!mapper_init(music_dir, playlist_dir, &error)) { + g_propagate_error(error_r, error); + return false; + } g_free(music_dir); g_free(playlist_dir); @@ -153,31 +167,44 @@ glue_mapper_init(GError **error_r) static bool glue_db_init_and_load(void) { + const struct config_param *param = config_get_param(CONF_DATABASE); const struct config_param *path = config_get_param(CONF_DB_FILE); + if (param != NULL && path != NULL) + g_message("Found both 'database' and 'db_file' setting - ignoring the latter"); + GError *error = NULL; bool ret; if (!mapper_has_music_directory()) { + if (param != NULL) + g_message("Found database setting without " + "music_directory - disabling database"); if (path != NULL) - g_message("Found " CONF_DB_FILE " setting without " - CONF_MUSIC_DIR " - disabling database"); - db_init(NULL, NULL); + g_message("Found db_file setting without " + "music_directory - disabling database"); return true; } - if (path == NULL) - MPD_ERROR(CONF_DB_FILE " setting missing"); + struct config_param *allocated = NULL; + + if (param == NULL && path != NULL) { + allocated = new config_param("database", path->line); + allocated->AddBlockParam("path", path->value, path->line); + param = allocated; + } - if (!db_init(path, &error)) + if (!DatabaseGlobalInit(param, &error)) MPD_ERROR("%s", error->message); - ret = db_load(&error); + delete allocated; + + ret = DatabaseGlobalOpen(&error); if (!ret) MPD_ERROR("%s", error->message); /* run database update after daemonization? */ - return db_exists(); + return !db_is_simple() || db_exists(); } /** @@ -205,14 +232,29 @@ glue_state_file_init(GError **error_r) GError *error = NULL; char *path = config_dup_path(CONF_STATE_FILE, &error); - if (path == NULL && error != NULL) { - g_propagate_error(error_r, error); + if (path == nullptr) { + if (error != nullptr) { + g_propagate_error(error_r, error); + return false; + } + + return true; + } + + Path path_fs = Path::FromUTF8(path); + + if (path_fs.IsNull()) { + g_free(path); + g_set_error(error_r, main_quark(), 0, + "Failed to convert state file path to FS encoding"); return false; } - state_file_init(path, global_player_control); + state_file = new StateFile(std::move(path_fs), path, + *global_partition, *main_loop); g_free(path); + state_file->Read(); return true; } @@ -285,29 +327,35 @@ initialize_decoder_and_player(void) if (buffered_before_play > buffered_chunks) buffered_before_play = buffered_chunks; - global_player_control = pc_new(buffered_chunks, buffered_before_play); + const unsigned max_length = + config_get_positive(CONF_MAX_PLAYLIST_LENGTH, + DEFAULT_PLAYLIST_MAX_LENGTH); + + global_partition = new Partition(max_length, + buffered_chunks, + buffered_before_play); } /** - * event_pipe callback function for PIPE_EVENT_IDLE + * Handler for GlobalEvents::IDLE. */ static void idle_event_emitted(void) { - /* send "idle" notificaions to all subscribed + /* send "idle" notifications to all subscribed clients */ unsigned flags = idle_get(); if (flags != 0) - client_manager_idle_add(flags); + client_list->IdleAdd(flags); } /** - * event_pipe callback function for PIPE_EVENT_SHUTDOWN + * Handler for GlobalEvents::SHUTDOWN. */ static void shutdown_event_emitted(void) { - g_main_loop_quit(main_loop); + main_loop->Break(); } int main(int argc, char *argv[]) @@ -341,8 +389,6 @@ int mpd_main(int argc, char *argv[]) io_thread_init(); winsock_init(); - idle_init(); - tag_pool_init(); config_global_init(); success = parse_cmdline(argc, argv, &options, &error); @@ -367,6 +413,12 @@ int mpd_main(int argc, char *argv[]) return EXIT_FAILURE; } + main_task = g_thread_self(); + main_loop = new EventLoop(EventLoop::Default()); + + const unsigned max_clients = config_get_positive(CONF_MAX_CONN, 10); + client_list = new ClientList(max_clients); + success = listen_global_init(&error); if (!success) { g_warning("%s", error->message); @@ -376,15 +428,11 @@ int mpd_main(int argc, char *argv[]) daemonize_set_user(); - main_task = g_thread_self(); - main_loop = g_main_loop_new(NULL, FALSE); - main_cond = g_cond_new(); - - event_pipe_init(); - event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted); - event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_event_emitted); + GlobalEvents::Initialize(); + GlobalEvents::Register(GlobalEvents::IDLE, idle_event_emitted); + GlobalEvents::Register(GlobalEvents::SHUTDOWN, shutdown_event_emitted); - path_global_init(); + Path::GlobalInit(); if (!glue_mapper_init(&error)) { g_warning("%s", error->message); @@ -416,7 +464,7 @@ int mpd_main(int argc, char *argv[]) initialize_decoder_and_player(); volume_init(); initAudioConfig(); - audio_output_all_init(global_player_control); + audio_output_all_init(&global_partition->pc); client_manager_init(); replay_gain_global_init(); @@ -440,9 +488,9 @@ int mpd_main(int argc, char *argv[]) return EXIT_FAILURE; } - initZeroconf(); + ZeroconfInit(*main_loop); - player_create(global_player_control); + player_create(&global_partition->pc); if (create_db) { /* the database failed to load: recreate the @@ -458,6 +506,8 @@ int mpd_main(int argc, char *argv[]) return EXIT_FAILURE; } + audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(global_partition->playlist.queue.random)); + success = config_get_bool(CONF_AUTO_UPDATE, false); #ifdef ENABLE_INOTIFY if (success && mapper_has_music_directory()) @@ -472,14 +522,14 @@ int mpd_main(int argc, char *argv[]) /* enable all audio outputs (if not already done by playlist_state_restore() */ - pc_update_audio(global_player_control); + global_partition->pc.UpdateAudio(); #ifdef WIN32 win32_app_started(); #endif /* run the main loop */ - g_main_loop_run(main_loop); + main_loop->Run(); #ifdef WIN32 win32_app_stopping(); @@ -487,21 +537,22 @@ int mpd_main(int argc, char *argv[]) /* cleanup */ - g_main_loop_unref(main_loop); - #ifdef ENABLE_INOTIFY mpd_inotify_finish(); #endif - state_file_finish(global_player_control); - pc_kill(global_player_control); - finishZeroconf(); - client_manager_deinit(); + if (state_file != nullptr) { + state_file->Write(); + delete state_file; + } + + global_partition->pc.Kill(); + ZeroconfDeinit(); listen_global_finish(); - playlist_global_finish(); + delete client_list; start = clock(); - db_finish(); + DatabaseGlobalDeinit(); g_debug("db_finish took %f seconds", ((float)(clock()-start))/CLOCKS_PER_SEC); @@ -509,17 +560,14 @@ int mpd_main(int argc, char *argv[]) sticker_global_finish(); #endif - g_cond_free(main_cond); - event_pipe_deinit(); + GlobalEvents::Deinitialize(); playlist_list_global_finish(); input_stream_global_finish(); audio_output_all_finish(); volume_finish(); mapper_finish(); - path_global_finish(); - finishPermissions(); - pc_free(global_player_control); + delete global_partition; command_finish(); update_global_finish(); decoder_plugin_deinit_all(); @@ -527,10 +575,9 @@ int mpd_main(int argc, char *argv[]) archive_plugin_deinit_all(); #endif config_global_finish(); - tag_pool_deinit(); - idle_deinit(); stats_global_finish(); io_thread_deinit(); + delete main_loop; daemonize_finish(); #ifdef WIN32 WSACleanup(); diff --git a/src/main.h b/src/Main.hxx index 2a7d7591..b2768600 100644 --- a/src/main.h +++ b/src/Main.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,18 +17,20 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MAIN_H -#define MAIN_H +#ifndef MPD_MAIN_HXX +#define MPD_MAIN_HXX #include <glib.h> +class EventLoop; + extern GThread *main_task; -extern GMainLoop *main_loop; +extern EventLoop *main_loop; -extern GCond *main_cond; +extern class ClientList *client_list; -extern struct player_control *global_player_control; +extern struct Partition *global_partition; /** * A entry point for application. @@ -52,7 +54,7 @@ win32_main(int argc, char *argv[]); * When running as a service reports to service control manager * that our service is started. * When running as a console application enables console handler that will - * trigger PIPE_EVENT_SHUTDOWN when user closes console window + * trigger GlobalEvents::SHUTDOWN when user closes console window * or presses Ctrl+C. * This function should be called just before entering main loop. */ diff --git a/src/Mapper.cxx b/src/Mapper.cxx new file mode 100644 index 00000000..559df0d2 --- /dev/null +++ b/src/Mapper.cxx @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Maps directory and song objects to file system paths. + */ + +#include "config.h" +#include "Mapper.hxx" +#include "Directory.hxx" +#include "song.h" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "fs/DirectoryReader.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <dirent.h> + +/** + * The absolute path of the music directory encoded in UTF-8. + */ +static char *music_dir_utf8; +static size_t music_dir_utf8_length; + +/** + * The absolute path of the music directory encoded in the filesystem + * character set. + */ +static Path music_dir_fs = Path::Null(); +static size_t music_dir_fs_length; + +/** + * The absolute path of the playlist directory encoded in the + * filesystem character set. + */ +static Path playlist_dir_fs = Path::Null(); + +static inline GQuark +mapper_quark() +{ + return g_quark_from_static_string ("mapper"); +} + +/** + * Duplicate a string, chop all trailing slashes. + */ +static char * +strdup_chop_slash(const char *path_fs) +{ + size_t length = strlen(path_fs); + + while (length > 0 && path_fs[length - 1] == G_DIR_SEPARATOR) + --length; + + return g_strndup(path_fs, length); +} + +static void +check_directory(const char *path_utf8, const Path &path_fs) +{ + struct stat st; + if (!StatFile(path_fs, st)) { + g_warning("Failed to stat directory \"%s\": %s", + path_utf8, g_strerror(errno)); + return; + } + + if (!S_ISDIR(st.st_mode)) { + g_warning("Not a directory: %s", path_utf8); + return; + } + +#ifndef WIN32 + const Path x = Path::Build(path_fs, "."); + if (!StatFile(x, st) && errno == EACCES) + g_warning("No permission to traverse (\"execute\") directory: %s", + path_utf8); +#endif + + const DirectoryReader reader(path_fs); + if (reader.Failed() && errno == EACCES) + g_warning("No permission to read directory: %s", path_utf8); +} + +static bool +mapper_set_music_dir(const char *path_utf8, GError **error_r) +{ + music_dir_fs = Path::FromUTF8(path_utf8); + if (music_dir_fs.IsNull()) { + g_set_error(error_r, mapper_quark(), 0, + "Failed to convert music path to FS encoding"); + return false; + } + + music_dir_fs_length = music_dir_fs.length(); + + music_dir_utf8 = strdup_chop_slash(path_utf8); + music_dir_utf8_length = strlen(music_dir_utf8); + + check_directory(path_utf8, music_dir_fs); + + return true; +} + +static bool +mapper_set_playlist_dir(const char *path_utf8, GError **error_r) +{ + playlist_dir_fs = Path::FromUTF8(path_utf8); + if (playlist_dir_fs.IsNull()) { + g_set_error(error_r, mapper_quark(), 0, + "Failed to convert playlist path to FS encoding"); + return false; + } + + check_directory(path_utf8, playlist_dir_fs); + return true; +} + +bool mapper_init(const char *_music_dir, const char *_playlist_dir, + GError **error_r) +{ + if (_music_dir != NULL) + if (!mapper_set_music_dir(_music_dir, error_r)) + return false; + + if (_playlist_dir != NULL) + if (!mapper_set_playlist_dir(_playlist_dir, error_r)) + return false; + + return true; +} + +void mapper_finish(void) +{ + g_free(music_dir_utf8); +} + +const char * +mapper_get_music_directory_utf8(void) +{ + return music_dir_utf8; +} + +const Path & +mapper_get_music_directory_fs(void) +{ + return music_dir_fs; +} + +const char * +map_to_relative_path(const char *path_utf8) +{ + return music_dir_utf8 != NULL && + memcmp(path_utf8, music_dir_utf8, + music_dir_utf8_length) == 0 && + G_IS_DIR_SEPARATOR(path_utf8[music_dir_utf8_length]) + ? path_utf8 + music_dir_utf8_length + 1 + : path_utf8; +} + +Path +map_uri_fs(const char *uri) +{ + assert(uri != NULL); + assert(*uri != '/'); + + if (music_dir_fs.IsNull()) + return Path::Null(); + + const Path uri_fs = Path::FromUTF8(uri); + if (uri_fs.IsNull()) + return Path::Null(); + + return Path::Build(music_dir_fs, uri_fs); +} + +Path +map_directory_fs(const Directory *directory) +{ + assert(music_dir_utf8 != NULL); + assert(!music_dir_fs.IsNull()); + + if (directory->IsRoot()) + return music_dir_fs; + + return map_uri_fs(directory->GetPath()); +} + +Path +map_directory_child_fs(const Directory *directory, const char *name) +{ + assert(music_dir_utf8 != NULL); + assert(!music_dir_fs.IsNull()); + + /* check for invalid or unauthorized base names */ + if (*name == 0 || strchr(name, '/') != NULL || + strcmp(name, ".") == 0 || strcmp(name, "..") == 0) + return Path::Null(); + + const Path parent_fs = map_directory_fs(directory); + if (parent_fs.IsNull()) + return Path::Null(); + + const Path name_fs = Path::FromUTF8(name); + if (name_fs.IsNull()) + return Path::Null(); + + return Path::Build(parent_fs, name_fs); +} + +/** + * Map a song object that was created by song_dup_detached(). It does + * not have a real parent directory, only the dummy object + * #detached_root. + */ +static Path +map_detached_song_fs(const char *uri_utf8) +{ + Path uri_fs = Path::FromUTF8(uri_utf8); + if (uri_fs.IsNull()) + return Path::Null(); + + return Path::Build(music_dir_fs, uri_fs); +} + +Path +map_song_fs(const struct song *song) +{ + assert(song_is_file(song)); + + if (song_in_database(song)) + return song_is_detached(song) + ? map_detached_song_fs(song->uri) + : map_directory_child_fs(song->parent, song->uri); + else + return Path::FromUTF8(song->uri); +} + +char * +map_fs_to_utf8(const char *path_fs) +{ + if (!music_dir_fs.IsNull() && + strncmp(path_fs, music_dir_fs.c_str(), music_dir_fs_length) == 0 && + G_IS_DIR_SEPARATOR(path_fs[music_dir_fs_length])) + /* remove musicDir prefix */ + path_fs += music_dir_fs_length + 1; + else if (G_IS_DIR_SEPARATOR(path_fs[0])) + /* not within musicDir */ + return NULL; + + while (path_fs[0] == G_DIR_SEPARATOR) + ++path_fs; + + const std::string path_utf8 = Path::ToUTF8(path_fs); + if (path_utf8.empty()) + return nullptr; + + return g_strdup(path_utf8.c_str()); +} + +const Path & +map_spl_path(void) +{ + return playlist_dir_fs; +} + +Path +map_spl_utf8_to_fs(const char *name) +{ + if (playlist_dir_fs.IsNull()) + return Path::Null(); + + char *filename_utf8 = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL); + const Path filename_fs = Path::FromUTF8(filename_utf8); + g_free(filename_utf8); + if (filename_fs.IsNull()) + return Path::Null(); + + return Path::Build(playlist_dir_fs, filename_fs); +} diff --git a/src/mapper.h b/src/Mapper.hxx index d6184a17..af6c84cc 100644 --- a/src/mapper.h +++ b/src/Mapper.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,25 +21,27 @@ * Maps directory and song objects to file system paths. */ -#ifndef MPD_MAPPER_H -#define MPD_MAPPER_H +#ifndef MPD_MAPPER_HXX +#define MPD_MAPPER_HXX -#include <glib.h> -#include <stdbool.h> +#include "gcc.h" +#include "gerror.h" #define PLAYLIST_FILE_SUFFIX ".m3u" -struct directory; +class Path; +struct Directory; struct song; -void mapper_init(const char *_music_dir, const char *_playlist_dir); +bool mapper_init(const char *_music_dir, const char *_playlist_dir, + GError **error_r); void mapper_finish(void); /** * Return the absolute path of the music directory encoded in UTF-8. */ -G_GNUC_CONST +gcc_const const char * mapper_get_music_directory_utf8(void); @@ -47,18 +49,18 @@ mapper_get_music_directory_utf8(void); * Return the absolute path of the music directory encoded in the * filesystem character set. */ -G_GNUC_CONST -const char * +gcc_const +const Path & mapper_get_music_directory_fs(void); /** * Returns true if a music directory was configured. */ -G_GNUC_CONST +gcc_const static inline bool mapper_has_music_directory(void) { - return mapper_get_music_directory_utf8() != NULL; + return mapper_get_music_directory_utf8() != nullptr; } /** @@ -66,7 +68,7 @@ mapper_has_music_directory(void) * this function converts it to a relative path. If not, it returns * the unmodified string pointer. */ -G_GNUC_PURE +gcc_pure const char * map_to_relative_path(const char *path_utf8); @@ -75,19 +77,19 @@ map_to_relative_path(const char *path_utf8); * is basically done by converting the URI to the file system charset * and prepending the music directory. */ -G_GNUC_MALLOC -char * +gcc_pure +Path map_uri_fs(const char *uri); /** * Determines the file system path of a directory object. * * @param directory the directory object - * @return the path in file system encoding, or NULL if mapping failed + * @return the path in file system encoding, or nullptr if mapping failed */ -G_GNUC_MALLOC -char * -map_directory_fs(const struct directory *directory); +gcc_pure +Path +map_directory_fs(const Directory *directory); /** * Determines the file system path of a directory's child (may be a @@ -95,21 +97,21 @@ map_directory_fs(const struct directory *directory); * * @param directory the parent directory object * @param name the child's name in UTF-8 - * @return the path in file system encoding, or NULL if mapping failed + * @return the path in file system encoding, or nullptr if mapping failed */ -G_GNUC_MALLOC -char * -map_directory_child_fs(const struct directory *directory, const char *name); +gcc_pure +Path +map_directory_child_fs(const Directory *directory, const char *name); /** * Determines the file system path of a song. This must not be a * remote song. * * @param song the song object - * @return the path in file system encoding, or NULL if mapping failed + * @return the path in file system encoding, or nullptr if mapping failed */ -G_GNUC_MALLOC -char * +gcc_pure +Path map_song_fs(const struct song *song); /** @@ -117,17 +119,17 @@ map_song_fs(const struct song *song); * absolute) to a relative path in UTF-8 encoding. * * @param path_fs a path in file system encoding - * @return the relative path in UTF-8, or NULL if mapping failed + * @return the relative path in UTF-8, or nullptr if mapping failed */ -G_GNUC_MALLOC +gcc_malloc char * map_fs_to_utf8(const char *path_fs); /** * Returns the playlist directory. */ -G_GNUC_CONST -const char * +gcc_const +const Path & map_spl_path(void); /** @@ -135,10 +137,10 @@ map_spl_path(void); * path. The return value is allocated on the heap and must be freed * with g_free(). * - * @return the path in file system encoding, or NULL if mapping failed + * @return the path in file system encoding, or nullptr if mapping failed */ -G_GNUC_PURE -char * +gcc_pure +Path map_spl_utf8_to_fs(const char *name); #endif diff --git a/src/MessageCommands.cxx b/src/MessageCommands.cxx new file mode 100644 index 00000000..f19a1b5d --- /dev/null +++ b/src/MessageCommands.cxx @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MessageCommands.hxx" +#include "ClientSubscribe.hxx" +#include "ClientInternal.hxx" +#include "ClientList.hxx" +#include "Main.hxx" +#include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" + +#include <set> +#include <string> + +#include <assert.h> + +enum command_return +handle_subscribe(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + assert(argc == 2); + + switch (client_subscribe(client, argv[1])) { + case CLIENT_SUBSCRIBE_OK: + return COMMAND_RETURN_OK; + + case CLIENT_SUBSCRIBE_INVALID: + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return COMMAND_RETURN_ERROR; + + case CLIENT_SUBSCRIBE_ALREADY: + command_error(client, ACK_ERROR_EXIST, + "already subscribed to this channel"); + return COMMAND_RETURN_ERROR; + + case CLIENT_SUBSCRIBE_FULL: + command_error(client, ACK_ERROR_EXIST, + "subscription list is full"); + return COMMAND_RETURN_ERROR; + } + + /* unreachable */ + return COMMAND_RETURN_OK; +} + +enum command_return +handle_unsubscribe(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + assert(argc == 2); + + if (client_unsubscribe(client, argv[1])) + return COMMAND_RETURN_OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "not subscribed to this channel"); + return COMMAND_RETURN_ERROR; + } +} + +enum command_return +handle_channels(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 1); + + std::set<std::string> channels; + for (const auto &c : *client_list) + channels.insert(c->subscriptions.begin(), + c->subscriptions.end()); + + for (const auto &channel : channels) + client_printf(client, "channel: %s\n", channel.c_str()); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_read_messages(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 1); + + while (!client->messages.empty()) { + const ClientMessage &msg = client->messages.front(); + + client_printf(client, "channel: %s\nmessage: %s\n", + msg.GetChannel(), msg.GetMessage()); + client->messages.pop_front(); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_send_message(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 3); + + if (!client_message_valid_channel_name(argv[1])) { + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return COMMAND_RETURN_ERROR; + } + + bool sent = false; + const ClientMessage msg(argv[1], argv[2]); + for (const auto &c : *client_list) + if (client_push_message(c, msg)) + sent = true; + + if (sent) + return COMMAND_RETURN_OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "nobody is subscribed to this channel"); + return COMMAND_RETURN_ERROR; + } +} diff --git a/src/MessageCommands.hxx b/src/MessageCommands.hxx new file mode 100644 index 00000000..b10f3d8e --- /dev/null +++ b/src/MessageCommands.hxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MESSAGE_COMMANDS_HXX +#define MPD_MESSAGE_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_subscribe(Client *client, int argc, char *argv[]); + +enum command_return +handle_unsubscribe(Client *client, int argc, char *argv[]); + +enum command_return +handle_channels(Client *client, int argc, char *argv[]); + +enum command_return +handle_read_messages(Client *client, int argc, char *argv[]); + +enum command_return +handle_send_message(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/mixer_all.c b/src/MixerAll.cxx index 95ba9079..00343a1a 100644 --- a/src/mixer_all.c +++ b/src/MixerAll.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,16 @@ */ #include "config.h" -#include "mixer_all.h" -#include "mixer_control.h" -#include "output_all.h" -#include "output_plugin.h" +#include "MixerAll.hxx" +#include "MixerControl.hxx" +#include "MixerInternal.hxx" +#include "MixerList.hxx" +#include "OutputAll.hxx" +#include "PcmVolume.hxx" + +extern "C" { #include "output_internal.h" -#include "pcm_volume.h" -#include "mixer_api.h" -#include "mixer_list.h" +} #include <glib.h> diff --git a/src/mixer_all.h b/src/MixerAll.hxx index fe873e71..23350a84 100644 --- a/src/mixer_all.h +++ b/src/MixerAll.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,10 +22,8 @@ * Functions which affect the mixers of all audio outputs. */ -#ifndef MPD_MIXER_ALL_H -#define MPD_MIXER_ALL_H - -#include <stdbool.h> +#ifndef MPD_MIXER_ALL_HXX +#define MPD_MIXER_ALL_HXX /** * Returns the average volume of all available mixers (range 0..100). diff --git a/src/mixer_control.c b/src/MixerControl.cxx index 3e984dd0..4ca1c76e 100644 --- a/src/mixer_control.c +++ b/src/MixerControl.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,8 @@ */ #include "config.h" -#include "mixer_control.h" -#include "mixer_api.h" +#include "MixerControl.hxx" +#include "MixerInternal.hxx" #include <assert.h> #include <stddef.h> diff --git a/src/mixer_control.h b/src/MixerControl.hxx index 6c3468ac..ee1e959d 100644 --- a/src/mixer_control.h +++ b/src/MixerControl.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,17 +22,19 @@ * Functions which manipulate a #mixer object. */ -#ifndef MPD_MIXER_CONTROL_H -#define MPD_MIXER_CONTROL_H +#ifndef MPD_MIXER_CONTROL_HXX +#define MPD_MIXER_CONTROL_HXX -#include <glib.h> - -#include <stdbool.h> +#include "gerror.h" struct mixer; struct mixer_plugin; struct config_param; +#ifdef __cplusplus +extern "C" { +#endif + struct mixer * mixer_new(const struct mixer_plugin *plugin, void *ao, const struct config_param *param, @@ -60,4 +62,8 @@ mixer_get_volume(struct mixer *mixer, GError **error_r); bool mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/mixer_api.c b/src/MixerInternal.cxx index c85916c9..b8729fc7 100644 --- a/src/mixer_api.c +++ b/src/MixerInternal.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "mixer_api.h" +#include "MixerInternal.hxx" #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "mixer" diff --git a/src/mixer_api.h b/src/MixerInternal.hxx index 29c1e00c..9c39e3e3 100644 --- a/src/mixer_api.h +++ b/src/MixerInternal.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_MIXER_H -#define MPD_MIXER_H +#ifndef MPD_MIXER_INTERNAL_HXX +#define MPD_MIXER_INTERNAL_HXX -#include "mixer_plugin.h" -#include "mixer_list.h" +#include "MixerPlugin.hxx" +#include "MixerList.hxx" #include <glib.h> diff --git a/src/mixer_list.h b/src/MixerList.hxx index 078358ec..440f442b 100644 --- a/src/mixer_list.h +++ b/src/MixerList.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,8 +22,8 @@ * This header provides "extern" declarations for all mixer plugins. */ -#ifndef MPD_MIXER_LIST_H -#define MPD_MIXER_LIST_H +#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; diff --git a/src/mixer_plugin.h b/src/MixerPlugin.hxx index 9532b95c..1fbdfbbc 100644 --- a/src/mixer_plugin.h +++ b/src/MixerPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 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 @@ /** \file * * This header declares the mixer_plugin class. It should not be - * included directly; use mixer_api.h instead in mixer + * included directly; use MixerInternal.hxx instead in mixer * implementations. */ -#ifndef MPD_MIXER_PLUGIN_H -#define MPD_MIXER_PLUGIN_H +#ifndef MPD_MIXER_PLUGIN_HXX +#define MPD_MIXER_PLUGIN_HXX -#include <glib.h> +#include "gerror.h" #include <stdbool.h> diff --git a/src/mixer_type.c b/src/MixerType.cxx index a479caf1..43507979 100644 --- a/src/mixer_type.c +++ b/src/MixerType.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "mixer_type.h" +#include "MixerType.hxx" #include <assert.h> #include <string.h> diff --git a/src/mixer_type.h b/src/MixerType.hxx index 15d136b5..320a36c0 100644 --- a/src/mixer_type.h +++ b/src/MixerType.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_MIXER_TYPE_H -#define MPD_MIXER_TYPE_H +#ifndef MPD_MIXER_TYPE_HXX +#define MPD_MIXER_TYPE_HXX enum mixer_type { /** parser error */ diff --git a/src/MusicBuffer.cxx b/src/MusicBuffer.cxx new file mode 100644 index 00000000..ea03fc0b --- /dev/null +++ b/src/MusicBuffer.cxx @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "thread/Mutex.hxx" +#include "util/SliceBuffer.hxx" +#include "mpd_error.h" + +#include <assert.h> + +struct music_buffer : public SliceBuffer<music_chunk> { + /** a mutex which protects #available */ + Mutex mutex; + + music_buffer(unsigned num_chunks) + :SliceBuffer(num_chunks) { + if (IsOOM()) + MPD_ERROR("Failed to allocate buffer"); + } +}; + +struct music_buffer * +music_buffer_new(unsigned num_chunks) +{ + return new music_buffer(num_chunks); +} + +void +music_buffer_free(struct music_buffer *buffer) +{ + delete buffer; +} + +unsigned +music_buffer_size(const struct music_buffer *buffer) +{ + return buffer->GetCapacity(); +} + +struct music_chunk * +music_buffer_allocate(struct music_buffer *buffer) +{ + const ScopeLock protect(buffer->mutex); + return buffer->Allocate(); +} + +void +music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk) +{ + assert(buffer != NULL); + assert(chunk != NULL); + + const ScopeLock protect(buffer->mutex); + + if (chunk->other != nullptr) { + assert(chunk->other->other == nullptr); + buffer->Free(chunk->other); + } + + buffer->Free(chunk); +} diff --git a/src/buffer.h b/src/MusicBuffer.hxx index f860231e..cc03dfcb 100644 --- a/src/buffer.h +++ b/src/MusicBuffer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_MUSIC_BUFFER_H -#define MPD_MUSIC_BUFFER_H +#ifndef MPD_MUSIC_BUFFER_HXX +#define MPD_MUSIC_BUFFER_HXX /** * An allocator for #music_chunk objects. diff --git a/src/MusicChunk.cxx b/src/MusicChunk.cxx new file mode 100644 index 00000000..eefda24b --- /dev/null +++ b/src/MusicChunk.cxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MusicChunk.hxx" +#include "audio_format.h" +#include "tag.h" + +#include <assert.h> + +music_chunk::~music_chunk() +{ + if (tag != NULL) + tag_free(tag); +} + +#ifndef NDEBUG +bool +music_chunk::CheckFormat(const struct audio_format &other_format) const +{ + assert(audio_format_valid(&other_format)); + + return length == 0 || + audio_format_equals(&audio_format, &other_format); +} +#endif + +void * +music_chunk::Write(const struct audio_format &af, + float data_time, uint16_t _bit_rate, + size_t *max_length_r) +{ + assert(CheckFormat(af)); + assert(length == 0 || audio_format_valid(&audio_format)); + + if (length == 0) { + /* if the chunk is empty, nobody has set bitRate and + times yet */ + + bit_rate = _bit_rate; + times = data_time; + } + + const size_t frame_size = audio_format_frame_size(&af); + size_t num_frames = (sizeof(data) - length) / frame_size; + if (num_frames == 0) + return NULL; + +#ifndef NDEBUG + audio_format = af; +#endif + + *max_length_r = num_frames * frame_size; + return data + length; +} + +bool +music_chunk::Expand(const struct audio_format &af, size_t _length) +{ + const size_t frame_size = audio_format_frame_size(&af); + + assert(length + _length <= sizeof(data)); + assert(audio_format_equals(&audio_format, &af)); + + length += _length; + + return length + frame_size > sizeof(data); +} diff --git a/src/chunk.h b/src/MusicChunk.hxx index a06a203e..c03e4551 100644 --- a/src/chunk.h +++ b/src/MusicChunk.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_CHUNK_H -#define MPD_CHUNK_H +#ifndef MPD_MUSIC_CHUNK_HXX +#define MPD_MUSIC_CHUNK_HXX #include "replay_gain_info.h" @@ -26,7 +26,6 @@ #include "audio_format.h" #endif -#include <stdbool.h> #include <stdint.h> #include <stddef.h> @@ -92,60 +91,63 @@ struct music_chunk { #ifndef NDEBUG struct audio_format audio_format; #endif -}; -void -music_chunk_init(struct music_chunk *chunk); + music_chunk() + :other(nullptr), + length(0), + tag(nullptr), + replay_gain_serial(0) {} -void -music_chunk_free(struct music_chunk *chunk); + ~music_chunk(); -static inline bool -music_chunk_is_empty(const struct music_chunk *chunk) -{ - return chunk->length == 0 && chunk->tag == NULL; -} + bool IsEmpty() const { + return length == 0 && tag == nullptr; + } #ifndef NDEBUG -/** - * Checks if the audio format if the chunk is equal to the specified - * audio_format. - */ -bool -music_chunk_check_format(const struct music_chunk *chunk, - const struct audio_format *audio_format); + /** + * Checks if the audio format if the chunk is equal to the + * specified audio_format. + */ + gcc_pure + bool CheckFormat(const struct audio_format &audio_format) const; #endif -/** - * Prepares appending to the music chunk. Returns a buffer where you - * may write into. After you are finished, call music_chunk_expand(). - * - * @param chunk the music_chunk object - * @param audio_format the audio format for the appended data; must - * stay the same for the life cycle of this chunk - * @param data_time the time within the song - * @param bit_rate the current bit rate of the source file - * @param max_length_r the maximum write length is returned here - * @return a writable buffer, or NULL if the chunk is full - */ -void * -music_chunk_write(struct music_chunk *chunk, - const struct audio_format *audio_format, - float data_time, uint16_t bit_rate, - size_t *max_length_r); + /** + * Prepares appending to the music chunk. Returns a buffer + * where you may write into. After you are finished, call + * music_chunk_expand(). + * + * @param chunk the music_chunk object + * @param audio_format the audio format for the appended data; + * must stay the same for the life cycle of this chunk + * @param data_time the time within the song + * @param bit_rate the current bit rate of the source file + * @param max_length_r the maximum write length is returned + * here + * @return a writable buffer, or NULL if the chunk is full + */ + void *Write(const struct audio_format &af, + float data_time, uint16_t bit_rate, + size_t *max_length_r); -/** - * Increases the length of the chunk after the caller has written to - * the buffer returned by music_chunk_write(). - * - * @param chunk the music_chunk object - * @param audio_format the audio format for the appended data; must - * stay the same for the life cycle of this chunk - * @param length the number of bytes which were appended - * @return true if the chunk is full - */ -bool -music_chunk_expand(struct music_chunk *chunk, - const struct audio_format *audio_format, size_t length); + /** + * Increases the length of the chunk after the caller has written to + * the buffer returned by music_chunk_write(). + * + * @param chunk the music_chunk object + * @param audio_format the audio format for the appended data; must + * stay the same for the life cycle of this chunk + * @param length the number of bytes which were appended + * @return true if the chunk is full + */ + bool Expand(const struct audio_format &af, size_t length); +}; + +void +music_chunk_init(struct music_chunk *chunk); + +void +music_chunk_free(struct music_chunk *chunk); #endif diff --git a/src/pipe.c b/src/MusicPipe.cxx index d8131432..6f25eff8 100644 --- a/src/pipe.c +++ b/src/MusicPipe.cxx @@ -18,9 +18,10 @@ */ #include "config.h" -#include "pipe.h" -#include "buffer.h" -#include "chunk.h" +#include "MusicPipe.hxx" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "thread/Mutex.hxx" #include <glib.h> @@ -37,38 +38,35 @@ struct music_pipe { unsigned size; /** a mutex which protects #head and #tail_r */ - GMutex *mutex; + mutable Mutex mutex; #ifndef NDEBUG struct audio_format audio_format; #endif + + music_pipe() + :head(nullptr), tail_r(&head), size(0) { +#ifndef NDEBUG + audio_format_clear(&audio_format); +#endif + } + + ~music_pipe() { + assert(head == nullptr); + assert(tail_r == &head); + } }; struct music_pipe * music_pipe_new(void) { - struct music_pipe *mp = g_new(struct music_pipe, 1); - - mp->head = NULL; - mp->tail_r = &mp->head; - mp->size = 0; - mp->mutex = g_mutex_new(); - -#ifndef NDEBUG - audio_format_clear(&mp->audio_format); -#endif - - return mp; + return new music_pipe(); } void music_pipe_free(struct music_pipe *mp) { - assert(mp->head == NULL); - assert(mp->tail_r == &mp->head); - - g_mutex_free(mp->mutex); - g_free(mp); + delete mp; } #ifndef NDEBUG @@ -88,17 +86,12 @@ bool music_pipe_contains(const struct music_pipe *mp, const struct music_chunk *chunk) { - g_mutex_lock(mp->mutex); + const ScopeLock protect(mp->mutex); for (const struct music_chunk *i = mp->head; - i != NULL; i = i->next) { - if (i == chunk) { - g_mutex_unlock(mp->mutex); + i != NULL; i = i->next) + if (i == chunk) return true; - } - } - - g_mutex_unlock(mp->mutex); return false; } @@ -114,13 +107,11 @@ music_pipe_peek(const struct music_pipe *mp) struct music_chunk * music_pipe_shift(struct music_pipe *mp) { - struct music_chunk *chunk; - - g_mutex_lock(mp->mutex); + const ScopeLock protect(mp->mutex); - chunk = mp->head; + struct music_chunk *chunk = mp->head; if (chunk != NULL) { - assert(!music_chunk_is_empty(chunk)); + assert(!chunk->IsEmpty()); mp->head = chunk->next; --mp->size; @@ -137,15 +128,13 @@ music_pipe_shift(struct music_pipe *mp) #ifndef NDEBUG /* poison the "next" reference */ - chunk->next = (void*)0x01010101; + chunk->next = (struct music_chunk *)(void *)0x01010101; if (mp->size == 0) audio_format_clear(&mp->audio_format); #endif } - g_mutex_unlock(mp->mutex); - return chunk; } @@ -161,14 +150,14 @@ music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer) void music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk) { - assert(!music_chunk_is_empty(chunk)); + assert(!chunk->IsEmpty()); assert(chunk->length == 0 || audio_format_valid(&chunk->audio_format)); - g_mutex_lock(mp->mutex); + const ScopeLock protect(mp->mutex); assert(mp->size > 0 || !audio_format_defined(&mp->audio_format)); assert(!audio_format_defined(&mp->audio_format) || - music_chunk_check_format(chunk, &mp->audio_format)); + chunk->CheckFormat(mp->audio_format)); #ifndef NDEBUG if (!audio_format_defined(&mp->audio_format) && chunk->length > 0) @@ -180,15 +169,11 @@ music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk) mp->tail_r = &chunk->next; ++mp->size; - - g_mutex_unlock(mp->mutex); } unsigned music_pipe_size(const struct music_pipe *mp) { - g_mutex_lock(mp->mutex); - unsigned size = mp->size; - g_mutex_unlock(mp->mutex); - return size; + const ScopeLock protect(mp->mutex); + return mp->size; } diff --git a/src/pipe.h b/src/MusicPipe.hxx index 84b9869e..99561ca6 100644 --- a/src/pipe.h +++ b/src/MusicPipe.hxx @@ -20,8 +20,7 @@ #ifndef MPD_PIPE_H #define MPD_PIPE_H -#include <glib.h> -#include <stdbool.h> +#include "gcc.h" #ifndef NDEBUG struct audio_format; @@ -39,7 +38,7 @@ struct music_pipe; /** * Creates a new #music_pipe object. It is empty. */ -G_GNUC_MALLOC +gcc_malloc struct music_pipe * music_pipe_new(void); @@ -72,7 +71,7 @@ music_pipe_contains(const struct music_pipe *mp, * Returns the first #music_chunk from the pipe. Returns NULL if the * pipe is empty. */ -G_GNUC_PURE +gcc_pure const struct music_chunk * music_pipe_peek(const struct music_pipe *mp); @@ -99,11 +98,11 @@ music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk); /** * Returns the number of chunks currently in this pipe. */ -G_GNUC_PURE +gcc_pure unsigned music_pipe_size(const struct music_pipe *mp); -G_GNUC_PURE +gcc_pure static inline bool music_pipe_empty(const struct music_pipe *mp) { diff --git a/src/OtherCommands.cxx b/src/OtherCommands.cxx new file mode 100644 index 00000000..4909f37f --- /dev/null +++ b/src/OtherCommands.cxx @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OtherCommands.hxx" +#include "DatabaseCommands.hxx" +#include "CommandError.hxx" +#include "UpdateGlue.hxx" +#include "Directory.hxx" +#include "song.h" +#include "SongPrint.hxx" +#include "TagPrint.hxx" +#include "TimePrint.hxx" +#include "Mapper.hxx" +#include "DecoderPrint.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "ls.hxx" +#include "Volume.hxx" + +extern "C" { +#include "uri.h" +#include "stats.h" +} + +#include "Permission.hxx" +#include "PlaylistFile.hxx" +#include "ClientFile.hxx" +#include "ClientInternal.hxx" +#include "Idle.hxx" + +#ifdef ENABLE_SQLITE +#include "StickerDatabase.hxx" +#endif + +#include <assert.h> +#include <string.h> + +static void +print_spl_list(Client *client, const PlaylistVector &list) +{ + for (const auto &i : list) { + client_printf(client, "playlist: %s\n", i.name.c_str()); + + if (i.mtime > 0) + time_print(client, "Last-Modified", i.mtime); + } +} + +enum command_return +handle_urlhandlers(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + if (client_is_local(client)) + client_puts(client, "handler: file://\n"); + print_supported_uri_schemes(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_decoders(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + decoder_list_print(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_tagtypes(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + tag_print_types(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_kill(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + return COMMAND_RETURN_KILL; +} + +enum command_return +handle_close(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + return COMMAND_RETURN_CLOSE; +} + +enum command_return +handle_lsinfo(Client *client, int argc, char *argv[]) +{ + const char *uri; + + if (argc == 2) + uri = argv[1]; + else + /* default is root directory */ + uri = ""; + + if (strncmp(uri, "file:///", 8) == 0) { + /* print information about an arbitrary local file */ + const char *path_utf8 = uri + 7; + + GError *error = NULL; + if (!client_allow_file(client, path_utf8, &error)) + return print_error(client, error); + + struct song *song = song_file_load(path_utf8, NULL); + if (song == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such file"); + return COMMAND_RETURN_ERROR; + } + + song_print_info(client, song); + song_free(song); + return COMMAND_RETURN_OK; + } + + enum command_return result = handle_lsinfo2(client, argc, argv); + if (result != COMMAND_RETURN_OK) + return result; + + if (isRootDirectory(uri)) { + const auto &list = ListPlaylistFiles(NULL); + print_spl_list(client, list); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_update(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *path = NULL; + unsigned ret; + + assert(argc <= 2); + if (argc == 2) { + path = argv[1]; + + if (*path == 0 || strcmp(path, "/") == 0) + /* backwards compatibility with MPD 0.15 */ + path = NULL; + else if (!uri_safe_local(path)) { + command_error(client, ACK_ERROR_ARG, + "Malformed path"); + return COMMAND_RETURN_ERROR; + } + } + + ret = update_enqueue(path, false); + if (ret > 0) { + client_printf(client, "updating_db: %i\n", ret); + return COMMAND_RETURN_OK; + } else { + command_error(client, ACK_ERROR_UPDATE_ALREADY, + "already updating"); + return COMMAND_RETURN_ERROR; + } +} + +enum command_return +handle_rescan(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *path = NULL; + unsigned ret; + + assert(argc <= 2); + if (argc == 2) { + path = argv[1]; + + if (!uri_safe_local(path)) { + command_error(client, ACK_ERROR_ARG, + "Malformed path"); + return COMMAND_RETURN_ERROR; + } + } + + ret = update_enqueue(path, true); + if (ret > 0) { + client_printf(client, "updating_db: %i\n", ret); + return COMMAND_RETURN_OK; + } else { + command_error(client, ACK_ERROR_UPDATE_ALREADY, + "already updating"); + return COMMAND_RETURN_ERROR; + } +} + +enum command_return +handle_setvol(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned level; + bool success; + + if (!check_unsigned(client, &level, argv[1])) + return COMMAND_RETURN_ERROR; + + if (level > 100) { + command_error(client, ACK_ERROR_ARG, "Invalid volume value"); + return COMMAND_RETURN_ERROR; + } + + success = volume_level_change(level); + if (!success) { + command_error(client, ACK_ERROR_SYSTEM, + "problems setting volume"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_stats(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + stats_print(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_ping(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + return COMMAND_RETURN_OK; +} + +enum command_return +handle_password(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned permission = 0; + + if (getPermissionFromPassword(argv[1], &permission) < 0) { + command_error(client, ACK_ERROR_PASSWORD, "incorrect password"); + return COMMAND_RETURN_ERROR; + } + + client_set_permission(client, permission); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_config(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + if (!client_is_local(client)) { + command_error(client, ACK_ERROR_PERMISSION, + "Command only permitted to local clients"); + return COMMAND_RETURN_ERROR; + } + + const char *path = mapper_get_music_directory_utf8(); + if (path != NULL) + client_printf(client, "music_directory: %s\n", path); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_idle(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + unsigned flags = 0, j; + int i; + const char *const* idle_names; + + idle_names = idle_get_names(); + for (i = 1; i < argc; ++i) { + if (!argv[i]) + continue; + + for (j = 0; idle_names[j]; ++j) { + if (!g_ascii_strcasecmp(argv[i], idle_names[j])) { + flags |= (1 << j); + } + } + } + + /* No argument means that the client wants to receive everything */ + if (flags == 0) + flags = ~0; + + /* enable "idle" mode on this client */ + client->IdleWait(flags); + + return COMMAND_RETURN_IDLE; +} diff --git a/src/OtherCommands.hxx b/src/OtherCommands.hxx new file mode 100644 index 00000000..564ad38e --- /dev/null +++ b/src/OtherCommands.hxx @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OTHER_COMMANDS_HXX +#define MPD_OTHER_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_urlhandlers(Client *client, int argc, char *argv[]); + +enum command_return +handle_decoders(Client *client, int argc, char *argv[]); + +enum command_return +handle_tagtypes(Client *client, int argc, char *argv[]); + +enum command_return +handle_kill(Client *client, int argc, char *argv[]); + +enum command_return +handle_close(Client *client, int argc, char *argv[]); + +enum command_return +handle_lsinfo(Client *client, int argc, char *argv[]); + +enum command_return +handle_update(Client *client, int argc, char *argv[]); + +enum command_return +handle_rescan(Client *client, int argc, char *argv[]); + +enum command_return +handle_setvol(Client *client, int argc, char *argv[]); + +enum command_return +handle_stats(Client *client, int argc, char *argv[]); + +enum command_return +handle_ping(Client *client, int argc, char *argv[]); + +enum command_return +handle_password(Client *client, int argc, char *argv[]); + +enum command_return +handle_config(Client *client, int argc, char *argv[]); + +enum command_return +handle_idle(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/output_all.c b/src/OutputAll.cxx index f56cd04e..4cdcc84e 100644 --- a/src/output_all.c +++ b/src/OutputAll.cxx @@ -18,20 +18,21 @@ */ #include "config.h" -#include "output_all.h" +#include "OutputAll.hxx" + +extern "C" { #include "output_internal.h" -#include "output_control.h" -#include "chunk.h" -#include "conf.h" -#include "pipe.h" -#include "buffer.h" -#include "player_control.h" -#include "mpd_error.h" -#include "notify.h" +} -#ifndef NDEBUG -#include "chunk.h" -#endif +#include "PlayerControl.hxx" +#include "OutputControl.hxx" +#include "OutputError.hxx" +#include "MusicBuffer.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" +#include "mpd_error.h" +#include "conf.h" +#include "notify.hxx" #include <assert.h> #include <string.h> @@ -109,8 +110,6 @@ audio_output_all_init(struct player_control *pc) unsigned int i; GError *error = NULL; - notify_init(&audio_output_client_notify); - num_audio_outputs = audio_output_config_count(); audio_outputs = g_new(struct audio_output *, num_audio_outputs); @@ -157,8 +156,6 @@ audio_output_all_finish(void) g_free(audio_outputs); audio_outputs = NULL; num_audio_outputs = 0; - - notify_deinit(&audio_output_client_notify); } void @@ -207,7 +204,7 @@ audio_output_all_finished(void) static void audio_output_wait_all(void) { while (!audio_output_all_finished()) - notify_wait(&audio_output_client_notify); + audio_output_client_notify.Wait(); } /** @@ -269,8 +266,15 @@ audio_output_all_update(void) return ret; } +void +audio_output_all_set_replay_gain_mode(enum replay_gain_mode mode) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_set_replay_gain_mode(audio_outputs[i], mode); +} + bool -audio_output_all_play(struct music_chunk *chunk) +audio_output_all_play(struct music_chunk *chunk, GError **error_r) { bool ret; unsigned int i; @@ -278,11 +282,15 @@ audio_output_all_play(struct music_chunk *chunk) assert(g_music_buffer != NULL); assert(g_mp != NULL); assert(chunk != NULL); - assert(music_chunk_check_format(chunk, &input_audio_format)); + assert(chunk->CheckFormat(input_audio_format)); ret = audio_output_all_update(); - if (!ret) + if (!ret) { + /* TODO: obtain real error */ + g_set_error(error_r, output_quark(), 0, + "Failed to open audio output"); return false; + } music_pipe_push(g_mp, chunk); @@ -294,7 +302,8 @@ audio_output_all_play(struct music_chunk *chunk) bool audio_output_all_open(const struct audio_format *audio_format, - struct music_buffer *buffer) + struct music_buffer *buffer, + GError **error_r) { bool ret = false, enabled = false; unsigned int i; @@ -334,7 +343,12 @@ audio_output_all_open(const struct audio_format *audio_format, } if (!enabled) - g_warning("All audio outputs are disabled"); + g_set_error(error_r, output_quark(), 0, + "All audio outputs are disabled"); + else if (!ret) + /* TODO: obtain real error */ + g_set_error(error_r, output_quark(), 0, + "Failed to open audio output"); if (!ret) /* close all devices if there was an error */ @@ -467,15 +481,15 @@ audio_output_all_check(void) bool audio_output_all_wait(struct player_control *pc, unsigned threshold) { - player_lock(pc); + pc->Lock(); if (audio_output_all_check() < threshold) { - player_unlock(pc); + pc->Unlock(); return true; } - player_wait(pc); - player_unlock(pc); + pc->Wait(); + pc->Unlock(); return audio_output_all_check() < threshold; } diff --git a/src/output_all.h b/src/OutputAll.hxx index 4eeb94f1..becf4b69 100644 --- a/src/output_all.h +++ b/src/OutputAll.hxx @@ -26,6 +26,9 @@ #ifndef OUTPUT_ALL_H #define OUTPUT_ALL_H +#include "replay_gain_info.h" +#include "gerror.h" + #include <stdbool.h> #include <stddef.h> @@ -84,7 +87,8 @@ audio_output_all_enable_disable(void); */ bool audio_output_all_open(const struct audio_format *audio_format, - struct music_buffer *buffer); + struct music_buffer *buffer, + GError **error_r); /** * Closes all audio outputs. @@ -99,6 +103,9 @@ audio_output_all_close(void); void audio_output_all_release(void); +void +audio_output_all_set_replay_gain_mode(enum replay_gain_mode mode); + /** * Enqueue a #music_chunk object for playing, i.e. pushes it to a * #music_pipe. @@ -108,7 +115,7 @@ audio_output_all_release(void); * (all closed then) */ bool -audio_output_all_play(struct music_chunk *chunk); +audio_output_all_play(struct music_chunk *chunk, GError **error_r); /** * Checks if the output devices have drained their music pipe, and diff --git a/src/output_command.c b/src/OutputCommand.cxx index 3988f350..beb44f0e 100644 --- a/src/output_command.c +++ b/src/OutputCommand.cxx @@ -25,13 +25,16 @@ */ #include "config.h" -#include "output_command.h" -#include "output_all.h" +#include "OutputCommand.hxx" +#include "OutputAll.hxx" +#include "PlayerControl.hxx" +#include "MixerControl.hxx" +#include "Idle.hxx" + +extern "C" { #include "output_internal.h" #include "output_plugin.h" -#include "mixer_control.h" -#include "player_control.h" -#include "idle.h" +} extern unsigned audio_output_state_version; @@ -50,7 +53,7 @@ audio_output_enable_index(unsigned idx) ao->enabled = true; idle_add(IDLE_OUTPUT); - pc_update_audio(ao->player_control); + ao->player_control->UpdateAudio(); ++audio_output_state_version; @@ -79,7 +82,7 @@ audio_output_disable_index(unsigned idx) idle_add(IDLE_MIXER); } - pc_update_audio(ao->player_control); + ao->player_control->UpdateAudio(); ++audio_output_state_version; diff --git a/src/output_command.h b/src/OutputCommand.hxx index eda30acc..74eaf8f1 100644 --- a/src/output_command.h +++ b/src/OutputCommand.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,10 +24,8 @@ * */ -#ifndef OUTPUT_COMMAND_H -#define OUTPUT_COMMAND_H - -#include <stdbool.h> +#ifndef MPD_OUTPUT_COMMAND_HXX +#define MPD_OUTPUT_COMMAND_HXX /** * Enables an audio output. Returns false if the specified output diff --git a/src/OutputCommands.cxx b/src/OutputCommands.cxx new file mode 100644 index 00000000..7d626477 --- /dev/null +++ b/src/OutputCommands.cxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputCommands.hxx" +#include "OutputPrint.hxx" +#include "OutputCommand.hxx" +#include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" + +#include <string.h> + +enum command_return +handle_enableoutput(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned device; + bool ret; + + if (!check_unsigned(client, &device, argv[1])) + return COMMAND_RETURN_ERROR; + + ret = audio_output_enable_index(device); + if (!ret) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such audio output"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_disableoutput(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned device; + bool ret; + + if (!check_unsigned(client, &device, argv[1])) + return COMMAND_RETURN_ERROR; + + ret = audio_output_disable_index(device); + if (!ret) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such audio output"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_devices(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + printAudioDevices(client); + + return COMMAND_RETURN_OK; +} diff --git a/src/OutputCommands.hxx b/src/OutputCommands.hxx new file mode 100644 index 00000000..4f7082bf --- /dev/null +++ b/src/OutputCommands.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_COMMANDS_HXX +#define MPD_OUTPUT_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_enableoutput(Client *client, int argc, char *argv[]); + +enum command_return +handle_disableoutput(Client *client, int argc, char *argv[]); + +enum command_return +handle_devices(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/output_control.c b/src/OutputControl.cxx index 7b95be49..c451938f 100644 --- a/src/output_control.c +++ b/src/OutputControl.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,19 @@ */ #include "config.h" -#include "output_control.h" +#include "OutputControl.hxx" +#include "OutputThread.hxx" #include "output_api.h" + +extern "C" { #include "output_internal.h" -#include "output_thread.h" -#include "mixer_control.h" -#include "mixer_plugin.h" -#include "filter_plugin.h" -#include "notify.h" +} + +#include "MixerPlugin.hxx" +#include "MixerControl.hxx" +#include "notify.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" +#include "FilterPlugin.hxx" #include <assert.h> #include <stdlib.h> @@ -47,7 +52,7 @@ static void ao_command_wait(struct audio_output *ao) { while (ao->command != AO_COMMAND_NONE) { g_mutex_unlock(ao->mutex); - notify_wait(&audio_output_client_notify); + audio_output_client_notify.Wait(); g_mutex_lock(ao->mutex); } } @@ -92,6 +97,14 @@ ao_lock_command(struct audio_output *ao, enum audio_output_command cmd) } void +audio_output_set_replay_gain_mode(struct audio_output *ao, + enum replay_gain_mode mode) +{ + if (ao->replay_gain_filter != NULL) + replay_gain_filter_set_mode(ao->replay_gain_filter, mode); +} + +void audio_output_enable(struct audio_output *ao) { if (ao->thread == NULL) { diff --git a/src/output_control.h b/src/OutputControl.hxx index 874a5351..2ff09539 100644 --- a/src/output_control.h +++ b/src/OutputControl.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_OUTPUT_CONTROL_H -#define MPD_OUTPUT_CONTROL_H +#ifndef MPD_OUTPUT_CONTROL_HXX +#define MPD_OUTPUT_CONTROL_HXX -#include <glib.h> +#include "replay_gain_info.h" #include <stddef.h> -#include <stdbool.h> struct audio_output; struct audio_format; @@ -31,11 +30,9 @@ struct config_param; struct music_pipe; struct player_control; -static inline GQuark -audio_output_quark(void) -{ - return g_quark_from_static_string("audio_output"); -} +void +audio_output_set_replay_gain_mode(struct audio_output *ao, + enum replay_gain_mode mode); /** * Enables the device. diff --git a/src/OutputError.hxx b/src/OutputError.hxx new file mode 100644 index 00000000..451df985 --- /dev/null +++ b/src/OutputError.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_ERROR_HXX +#define MPD_OUTPUT_ERROR_HXX + +#include <glib.h> + +/** + * Quark for GError.domain. + */ +G_GNUC_CONST +static inline GQuark +output_quark(void) +{ + return g_quark_from_static_string("output"); +} + +#endif diff --git a/src/output_finish.c b/src/OutputFinish.cxx index e11b4367..559a5cf2 100644 --- a/src/output_finish.c +++ b/src/OutputFinish.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,14 @@ */ #include "config.h" + +extern "C" { #include "output_internal.h" #include "output_plugin.h" -#include "mixer_control.h" -#include "filter_plugin.h" +} + +#include "MixerControl.hxx" +#include "FilterInternal.hxx" #include <assert.h> @@ -38,13 +42,9 @@ ao_base_finish(struct audio_output *ao) g_cond_free(ao->cond); g_mutex_free(ao->mutex); - if (ao->replay_gain_filter != NULL) - filter_free(ao->replay_gain_filter); - - if (ao->other_replay_gain_filter != NULL) - filter_free(ao->other_replay_gain_filter); - - filter_free(ao->filter); + delete ao->replay_gain_filter; + delete ao->other_replay_gain_filter; + delete ao->filter; pcm_buffer_deinit(&ao->cross_fade_buffer); } diff --git a/src/output_init.c b/src/OutputInit.cxx index c3b808e9..de686925 100644 --- a/src/output_init.c +++ b/src/OutputInit.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,21 +18,26 @@ */ #include "config.h" -#include "output_control.h" +#include "OutputControl.hxx" +#include "OutputList.hxx" +#include "OutputError.hxx" +#include "FilterConfig.hxx" #include "output_api.h" +#include "AudioParser.hxx" + +extern "C" { #include "output_internal.h" -#include "output_list.h" -#include "audio_parser.h" -#include "mixer_control.h" -#include "mixer_type.h" -#include "mixer_list.h" -#include "mixer/software_mixer_plugin.h" -#include "filter_plugin.h" -#include "filter_registry.h" -#include "filter_config.h" -#include "filter/chain_filter_plugin.h" -#include "filter/autoconvert_filter_plugin.h" -#include "filter/replay_gain_filter_plugin.h" +} + +#include "MixerList.hxx" +#include "MixerType.hxx" +#include "MixerControl.hxx" +#include "mixer/SoftwareMixerPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterRegistry.hxx" +#include "filter/AutoConvertFilterPlugin.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" +#include "filter/ChainFilterPlugin.hxx" #include <glib.h> @@ -61,7 +66,7 @@ audio_output_detect(GError **error) return plugin; } - g_set_error(error, audio_output_quark(), 0, + g_set_error(error, output_quark(), 0, "Unable to detect an audio device"); return NULL; } @@ -87,14 +92,15 @@ audio_output_mixer_type(const struct config_param *param) /* fall back to the global "mixer_type" setting (also deprecated) */ - return mixer_type_parse(config_get_string("mixer_type", "hardware")); + return mixer_type_parse(config_get_string(CONF_MIXER_TYPE, + "hardware")); } static struct mixer * audio_output_load_mixer(struct audio_output *ao, const struct config_param *param, const struct mixer_plugin *plugin, - struct filter *filter_chain, + Filter &filter_chain, GError **error_r) { struct mixer *mixer; @@ -114,7 +120,7 @@ audio_output_load_mixer(struct audio_output *ao, mixer = mixer_new(&software_mixer_plugin, NULL, NULL, NULL); assert(mixer != NULL); - filter_chain_append(filter_chain, + filter_chain_append(filter_chain, "software_mixer", software_mixer_get_filter(mixer)); return mixer; } @@ -143,7 +149,7 @@ ao_base_init(struct audio_output *ao, ao->name = config_get_block_string(param, AUDIO_OUTPUT_NAME, NULL); if (ao->name == NULL) { - g_set_error(error_r, audio_output_quark(), 0, + g_set_error(error_r, output_quark(), 0, "Missing \"name\" configuration"); return false; } @@ -165,6 +171,7 @@ ao_base_init(struct audio_output *ao, } ao->plugin = plugin; + ao->tags = config_get_block_bool(param, "tags", true); ao->always_on = config_get_block_bool(param, "always_on", false); ao->enabled = config_get_block_bool(param, "enabled", true); ao->really_enabled = false; @@ -183,15 +190,15 @@ ao_base_init(struct audio_output *ao, /* create the normalization filter (if configured) */ if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) { - struct filter *normalize_filter = + Filter *normalize_filter = filter_new(&normalize_filter_plugin, NULL, NULL); assert(normalize_filter != NULL); - filter_chain_append(ao->filter, + filter_chain_append(*ao->filter, "normalize", autoconvert_filter_new(normalize_filter)); } - filter_chain_parse(ao->filter, + filter_chain_parse(*ao->filter, config_get_block_string(param, AUDIO_FILTERS, ""), &error ); @@ -251,7 +258,7 @@ audio_output_setup(struct audio_output *ao, const struct config_param *param, GError *error = NULL; ao->mixer = audio_output_load_mixer(ao, param, ao->plugin->mixer_plugin, - ao->filter, &error); + *ao->filter, &error); if (ao->mixer == NULL && error != NULL) { g_warning("Failed to initialize hardware mixer for '%s': %s", ao->name, error->message); @@ -268,7 +275,7 @@ audio_output_setup(struct audio_output *ao, const struct config_param *param, g_warning("No such mixer for output '%s'", ao->name); } else if (strcmp(replay_gain_handler, "software") != 0 && ao->replay_gain_filter != NULL) { - g_set_error(error_r, audio_output_quark(), 0, + g_set_error(error_r, output_quark(), 0, "Invalid \"replay_gain_handler\" value"); return false; } @@ -278,7 +285,7 @@ audio_output_setup(struct audio_output *ao, const struct config_param *param, ao->convert_filter = filter_new(&convert_filter_plugin, NULL, NULL); assert(ao->convert_filter != NULL); - filter_chain_append(ao->filter, ao->convert_filter); + filter_chain_append(*ao->filter, "convert", ao->convert_filter); return true; } @@ -295,24 +302,23 @@ audio_output_new(const struct config_param *param, p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL); if (p == NULL) { - g_set_error(error_r, audio_output_quark(), 0, + g_set_error(error_r, output_quark(), 0, "Missing \"type\" configuration"); - return false; + return nullptr; } plugin = audio_output_plugin_get(p); if (plugin == NULL) { - g_set_error(error_r, audio_output_quark(), 0, + g_set_error(error_r, output_quark(), 0, "No such audio output plugin: %s", p); - return false; + return nullptr; } } else { - g_warning("No \"%s\" defined in config file\n", - CONF_AUDIO_OUTPUT); + g_warning("No 'audio_output' defined in config file\n"); plugin = audio_output_detect(error_r); if (plugin == NULL) - return false; + return nullptr; g_message("Successfully detected a %s audio device", plugin->name); diff --git a/src/output_list.c b/src/OutputList.cxx index 835c02bb..dc4e2319 100644 --- a/src/output_list.c +++ b/src/OutputList.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,26 +18,26 @@ */ #include "config.h" -#include "output_list.h" +#include "OutputList.hxx" #include "output_api.h" -#include "output/alsa_output_plugin.h" +#include "output/AlsaOutputPlugin.hxx" #include "output/ao_output_plugin.h" #include "output/ffado_output_plugin.h" #include "output/fifo_output_plugin.h" -#include "output/httpd_output_plugin.h" +#include "output/HttpdOutputPlugin.hxx" #include "output/jack_output_plugin.h" #include "output/mvp_output_plugin.h" -#include "output/null_output_plugin.h" +#include "output/NullOutputPlugin.hxx" #include "output/openal_output_plugin.h" -#include "output/oss_output_plugin.h" -#include "output/osx_output_plugin.h" +#include "output/OssOutputPlugin.hxx" +#include "output/OSXOutputPlugin.hxx" #include "output/pipe_output_plugin.h" #include "output/pulse_output_plugin.h" #include "output/recorder_output_plugin.h" -#include "output/roar_output_plugin.h" +#include "output/RoarOutputPlugin.hxx" #include "output/shout_output_plugin.h" #include "output/solaris_output_plugin.h" -#include "output/winmm_output_plugin.h" +#include "output/WinmmOutputPlugin.hxx" const struct audio_output_plugin *const audio_output_plugins[] = { #ifdef HAVE_SHOUT diff --git a/src/output_list.h b/src/OutputList.hxx index 185ada71..b7716c67 100644 --- a/src/output_list.h +++ b/src/OutputList.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_OUTPUT_LIST_H -#define MPD_OUTPUT_LIST_H +#ifndef MPD_OUTPUT_LIST_HXX +#define MPD_OUTPUT_LIST_HXX extern const struct audio_output_plugin *const audio_output_plugins[]; diff --git a/src/output_plugin.c b/src/OutputPlugin.cxx index 221570c1..9aa0f779 100644 --- a/src/output_plugin.c +++ b/src/OutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,11 @@ */ #include "config.h" + +extern "C" { #include "output_plugin.h" +} + #include "output_internal.h" struct audio_output * diff --git a/src/output_print.c b/src/OutputPrint.cxx index 483648ca..240ea967 100644 --- a/src/output_print.c +++ b/src/OutputPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,15 +23,15 @@ */ #include "config.h" -#include "output_print.h" +#include "OutputPrint.hxx" +#include "OutputAll.hxx" #include "output_internal.h" -#include "output_all.h" -#include "client.h" +#include "Client.hxx" void -printAudioDevices(struct client *client) +printAudioDevices(Client *client) { - unsigned n = audio_output_count(); + const unsigned n = audio_output_count(); for (unsigned i = 0; i < n; ++i) { const struct audio_output *ao = audio_output_get(i); diff --git a/src/output_print.h b/src/OutputPrint.hxx index e02f4e9f..78717d0a 100644 --- a/src/output_print.h +++ b/src/OutputPrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,12 +22,12 @@ * */ -#ifndef OUTPUT_PRINT_H -#define OUTPUT_PRINT_H +#ifndef MPD_OUTPUT_PRINT_HXX +#define MPD_OUTPUT_PRINT_HXX -struct client; +class Client; void -printAudioDevices(struct client *client); +printAudioDevices(Client *client); #endif diff --git a/src/output_state.c b/src/OutputState.cxx index 7bcafb36..27fa34f8 100644 --- a/src/output_state.c +++ b/src/OutputState.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,9 +23,9 @@ */ #include "config.h" -#include "output_state.h" +#include "OutputState.hxx" +#include "OutputAll.hxx" #include "output_internal.h" -#include "output_all.h" #include <glib.h> diff --git a/src/output_state.h b/src/OutputState.hxx index 320a3520..5ab765ba 100644 --- a/src/output_state.h +++ b/src/OutputState.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,10 +22,9 @@ * */ -#ifndef OUTPUT_STATE_H -#define OUTPUT_STATE_H +#ifndef MPD_OUTPUT_STATE_HXX +#define MPD_OUTPUT_STATE_HXX -#include <stdbool.h> #include <stdio.h> bool diff --git a/src/output_thread.c b/src/OutputThread.cxx index 4eef2ccd..f1ffe876 100644 --- a/src/output_thread.c +++ b/src/OutputThread.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,18 +18,23 @@ */ #include "config.h" -#include "output_thread.h" +#include "OutputThread.hxx" #include "output_api.h" +#include "PcmMix.hxx" + +extern "C" { #include "output_internal.h" -#include "chunk.h" -#include "pipe.h" -#include "player_control.h" -#include "pcm_mix.h" -#include "filter_plugin.h" -#include "filter/convert_filter_plugin.h" -#include "filter/replay_gain_filter_plugin.h" +} + +#include "notify.hxx" +#include "FilterInternal.hxx" +#include "filter/ConvertFilterPlugin.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" +#include "PlayerControl.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" + #include "mpd_error.h" -#include "notify.h" #include "gcc.h" #include <glib.h> @@ -47,7 +52,7 @@ static void ao_command_finished(struct audio_output *ao) ao->command = AO_COMMAND_NONE; g_mutex_unlock(ao->mutex); - notify_signal(&audio_output_client_notify); + audio_output_client_notify.Signal(); g_mutex_lock(ao->mutex); } @@ -93,26 +98,24 @@ ao_disable(struct audio_output *ao) } static const struct audio_format * -ao_filter_open(struct audio_output *ao, - struct audio_format *audio_format, +ao_filter_open(struct audio_output *ao, audio_format &format, GError **error_r) { - assert(audio_format_valid(audio_format)); + assert(audio_format_valid(&format)); /* the replay_gain filter cannot fail here */ if (ao->replay_gain_filter != NULL) - filter_open(ao->replay_gain_filter, audio_format, error_r); + ao->replay_gain_filter->Open(format, error_r); if (ao->other_replay_gain_filter != NULL) - filter_open(ao->other_replay_gain_filter, audio_format, - error_r); + ao->other_replay_gain_filter->Open(format, error_r); const struct audio_format *af - = filter_open(ao->filter, audio_format, error_r); + = ao->filter->Open(format, error_r); if (af == NULL) { if (ao->replay_gain_filter != NULL) - filter_close(ao->replay_gain_filter); + ao->replay_gain_filter->Close(); if (ao->other_replay_gain_filter != NULL) - filter_close(ao->other_replay_gain_filter); + ao->other_replay_gain_filter->Close(); } return af; @@ -122,11 +125,11 @@ static void ao_filter_close(struct audio_output *ao) { if (ao->replay_gain_filter != NULL) - filter_close(ao->replay_gain_filter); + ao->replay_gain_filter->Close(); if (ao->other_replay_gain_filter != NULL) - filter_close(ao->other_replay_gain_filter); + ao->other_replay_gain_filter->Close(); - filter_close(ao->filter); + ao->filter->Close(); } static void @@ -134,7 +137,6 @@ ao_open(struct audio_output *ao) { bool success; GError *error = NULL; - const struct audio_format *filter_audio_format; struct audio_format_string af_string; assert(!ao->open); @@ -159,7 +161,8 @@ ao_open(struct audio_output *ao) /* open the filter */ - filter_audio_format = ao_filter_open(ao, &ao->in_audio_format, &error); + const audio_format *filter_audio_format = + ao_filter_open(ao, ao->in_audio_format, &error); if (filter_audio_format == NULL) { g_warning("Failed to open filter for \"%s\" [%s]: %s", ao->name, ao->plugin->name, error->message); @@ -191,7 +194,7 @@ ao_open(struct audio_output *ao) return; } - convert_filter_set(ao->convert_filter, &ao->out_audio_format); + convert_filter_set(ao->convert_filter, ao->out_audio_format); ao->open = true; @@ -239,7 +242,7 @@ ao_reopen_filter(struct audio_output *ao) GError *error = NULL; ao_filter_close(ao); - filter_audio_format = ao_filter_open(ao, &ao->in_audio_format, &error); + filter_audio_format = ao_filter_open(ao, ao->in_audio_format, &error); if (filter_audio_format == NULL) { g_warning("Failed to open filter for \"%s\" [%s]: %s", ao->name, ao->plugin->name, error->message); @@ -262,7 +265,7 @@ ao_reopen_filter(struct audio_output *ao) return; } - convert_filter_set(ao->convert_filter, &ao->out_audio_format); + convert_filter_set(ao->convert_filter, ao->out_audio_format); } static void @@ -315,17 +318,17 @@ ao_wait(struct audio_output *ao) } } -static const char * +static const void * ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, - struct filter *replay_gain_filter, + Filter *replay_gain_filter, unsigned *replay_gain_serial_p, size_t *length_r) { assert(chunk != NULL); - assert(!music_chunk_is_empty(chunk)); - assert(music_chunk_check_format(chunk, &ao->in_audio_format)); + assert(!chunk->IsEmpty()); + assert(chunk->CheckFormat(ao->in_audio_format)); - const char *data = chunk->data; + const void *data = chunk->data; size_t length = chunk->length; (void)ao; @@ -342,8 +345,8 @@ ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, } GError *error = NULL; - data = filter_filter(replay_gain_filter, data, length, - &length, &error); + data = replay_gain_filter->FilterPCM(data, length, + &length, &error); if (data == NULL) { g_warning("\"%s\" [%s] failed to filter: %s", ao->name, ao->plugin->name, error->message); @@ -356,14 +359,14 @@ ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, return data; } -static const char * +static const void * ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, size_t *length_r) { GError *error = NULL; size_t length; - const char *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter, + const void *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter, &ao->replay_gain_serial, &length); if (data == NULL) return NULL; @@ -378,7 +381,7 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, if (chunk->other != NULL) { size_t other_length; - const char *other_data = + const void *other_data = ao_chunk_data(ao, chunk->other, ao->other_replay_gain_filter, &ao->other_replay_gain_serial, @@ -399,13 +402,14 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, if (length > other_length) length = other_length; - char *dest = pcm_buffer_get(&ao->cross_fade_buffer, + void *dest = pcm_buffer_get(&ao->cross_fade_buffer, other_length); memcpy(dest, other_data, other_length); - if (!pcm_mix(dest, data, length, ao->in_audio_format.format, + if (!pcm_mix(dest, data, length, + sample_format(ao->in_audio_format.format), 1.0 - chunk->mix_ratio)) { g_warning("Cannot cross-fade format %s", - sample_format_to_string(ao->in_audio_format.format)); + sample_format_to_string(sample_format(ao->in_audio_format.format))); return NULL; } @@ -415,7 +419,7 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, /* apply filter chain */ - data = filter_filter(ao->filter, data, length, &length, &error); + data = ao->filter->FilterPCM(data, length, &length, &error); if (data == NULL) { g_warning("\"%s\" [%s] failed to filter: %s", ao->name, ao->plugin->name, error->message); @@ -435,7 +439,7 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) assert(ao != NULL); assert(ao->filter != NULL); - if (chunk->tag != NULL) { + if (ao->tags && gcc_unlikely(chunk->tag != NULL)) { g_mutex_unlock(ao->mutex); ao_plugin_send_tag(ao, chunk->tag); g_mutex_lock(ao->mutex); @@ -446,7 +450,7 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) /* workaround -Wmaybe-uninitialized false positive */ size = 0; #endif - const char *data = ao_filter_chunk(ao, chunk, &size); + const char *data = (const char *)ao_filter_chunk(ao, chunk, &size); if (data == NULL) { ao_close(ao, false); @@ -542,7 +546,7 @@ ao_play(struct audio_output *ao) ao->chunk_finished = true; g_mutex_unlock(ao->mutex); - player_lock_signal(ao->player_control); + ao->player_control->LockSignal(); g_mutex_lock(ao->mutex); return true; @@ -578,7 +582,7 @@ static void ao_pause(struct audio_output *ao) static gpointer audio_output_task(gpointer arg) { - struct audio_output *ao = arg; + struct audio_output *ao = (struct audio_output *)arg; g_mutex_lock(ao->mutex); diff --git a/src/output_thread.h b/src/OutputThread.hxx index 5ad9a752..1a793216 100644 --- a/src/output_thread.h +++ b/src/OutputThread.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_OUTPUT_THREAD_H -#define MPD_OUTPUT_THREAD_H +#ifndef MPD_OUTPUT_THREAD_HXX +#define MPD_OUTPUT_THREAD_HXX struct audio_output; diff --git a/src/client_list.c b/src/Page.cxx index 2c7f37af..bf30376e 100644 --- a/src/client_list.c +++ b/src/Page.cxx @@ -18,52 +18,53 @@ */ #include "config.h" -#include "client_internal.h" +#include "Page.hxx" -#include <assert.h> +#include <glib.h> -static GList *clients; -static unsigned num_clients; +#include <new> -bool -client_list_is_empty(void) -{ - return num_clients == 0; -} +#include <assert.h> +#include <string.h> -bool -client_list_is_full(void) +Page * +Page::Create(size_t size) { - return num_clients >= client_max_connections; + void *p = g_malloc(sizeof(Page) + size - + sizeof(Page::data)); + return ::new(p) Page(size); } -struct client * -client_list_get_first(void) +Page * +Page::Copy(const void *data, size_t size) { - assert(clients != NULL); + assert(data != nullptr); - return clients->data; + Page *page = Create(size); + memcpy(page->data, data, size); + return page; } -void -client_list_add(struct client *client) +Page * +Page::Concat(const Page &a, const Page &b) { - clients = g_list_prepend(clients, client); - ++num_clients; -} + Page *page = Create(a.size + b.size); -void -client_list_foreach(GFunc func, gpointer user_data) -{ - g_list_foreach(clients, func, user_data); + memcpy(page->data, a.data, a.size); + memcpy(page->data + a.size, b.data, b.size); + + return page; } -void -client_list_remove(struct client *client) +bool +Page::Unref() { - assert(num_clients > 0); - assert(clients != NULL); + bool unused = ref.Decrement(); + + if (unused) { + this->Page::~Page(); + g_free(this); + } - clients = g_list_remove(clients, client); - --num_clients; + return unused; } diff --git a/src/page.h b/src/Page.hxx index 8a3aaf39..2bc9a6ac 100644 --- a/src/page.h +++ b/src/Page.hxx @@ -22,73 +22,83 @@ * This is a library which manages reference counted buffers. */ -#ifndef MPD_PAGE_H -#define MPD_PAGE_H +#ifndef MPD_PAGE_HXX +#define MPD_PAGE_HXX + +#include "util/RefCount.hxx" + +#include <algorithm> #include <stddef.h> -#include <stdbool.h> /** * A dynamically allocated buffer which keeps track of its reference * count. This is useful for passing buffers around, when several * instances hold references to one buffer. */ -struct page { +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. */ - int ref; + RefCount ref; +public: /** * The size of this buffer in bytes. */ - size_t size; + const size_t size; /** * Dynamic array containing the buffer data. */ unsigned char data[sizeof(long)]; -}; -/** - * Creates a new #page object, and copies data from the specified - * buffer. It is initialized with a reference count of 1. - * - * @param data the source buffer - * @param size the size of the source buffer - * @return the new #page object - */ -struct page * -page_new_copy(const void *data, size_t size); +protected: + Page(size_t _size):size(_size) {} + ~Page() = default; -/** - * Concatenates two pages to a new page. - * - * @param a the first page - * @param b the second page, which is appended - */ -struct page * -page_new_concat(const struct page *a, const struct page *b); + /** + * Allocates a new #Page object, without filling the data + * element. + */ + static Page *Create(size_t size); -/** - * Increases the reference counter. - * - * @param page the #page object - */ -void -page_ref(struct page *page); +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); -/** - * Decreases the reference counter. If it reaches zero, the #page is - * freed. - * - * @param page the #page object - * @return true if the #page has been freed - */ -bool -page_unref(struct page *page); + /** + * Concatenates two pages to a new page. + * + * @param a the first page + * @param b the second page, which is appended + */ + static Page *Concat(const Page &a, const Page &b); + + /** + * Increases the reference counter. + */ + void Ref() { + ref.Increment(); + } + + /** + * Decreases the reference counter. If it reaches zero, the #page is + * freed. + * + * @return true if the #page has been freed + */ + bool Unref(); +}; #endif diff --git a/src/Partition.hxx b/src/Partition.hxx new file mode 100644 index 00000000..776f74e2 --- /dev/null +++ b/src/Partition.hxx @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PARTITION_HXX +#define MPD_PARTITION_HXX + +#include "Playlist.hxx" +#include "PlayerControl.hxx" + +/** + * A partition of the Music Player Daemon. It is a separate unit with + * a playlist, a player, outputs etc. + */ +struct Partition { + struct playlist playlist; + + player_control pc; + + Partition(unsigned max_length, + unsigned buffer_chunks, + unsigned buffered_before_play) + :playlist(max_length), + pc(buffer_chunks, buffered_before_play) { + } + + void ClearQueue() { + playlist.Clear(pc); + } + + enum playlist_result AppendFile(const char *path_utf8, + unsigned *added_id=nullptr) { + return playlist.AppendFile(pc, path_utf8, added_id); + } + + enum playlist_result AppendURI(const char *uri_utf8, + unsigned *added_id=nullptr) { + return playlist.AppendURI(pc, uri_utf8, added_id); + } + + enum playlist_result DeletePosition(unsigned position) { + return playlist.DeletePosition(pc, position); + } + + enum playlist_result DeleteId(unsigned id) { + return playlist.DeleteId(pc, id); + } + + /** + * Deletes a range of songs from the playlist. + * + * @param start the position of the first song to delete + * @param end the position after the last song to delete + */ + enum playlist_result DeleteRange(unsigned start, unsigned end) { + return playlist.DeleteRange(pc, start, end); + } + + void DeleteSong(const song &song) { + playlist.DeleteSong(pc, song); + } + + void Shuffle(unsigned start, unsigned end) { + playlist.Shuffle(pc, start, end); + } + + enum playlist_result MoveRange(unsigned start, unsigned end, int to) { + return playlist.MoveRange(pc, start, end, to); + } + + enum playlist_result MoveId(unsigned id, int to) { + return playlist.MoveId(pc, id, to); + } + + enum playlist_result SwapPositions(unsigned song1, unsigned song2) { + return playlist.SwapPositions(pc, song1, song2); + } + + enum playlist_result SwapIds(unsigned id1, unsigned id2) { + return playlist.SwapIds(pc, id1, id2); + } + + enum playlist_result SetPriorityRange(unsigned start_position, + unsigned end_position, + uint8_t priority) { + return playlist.SetPriorityRange(pc, + start_position, end_position, + priority); + } + + enum playlist_result SetPriorityId(unsigned song_id, + uint8_t priority) { + return playlist.SetPriorityId(pc, song_id, priority); + } + + void Stop() { + playlist.Stop(pc); + } + + enum playlist_result PlayPosition(int position) { + return playlist.PlayPosition(pc, position); + } + + enum playlist_result PlayId(int id) { + return playlist.PlayId(pc, id); + } + + void PlayNext() { + return playlist.PlayNext(pc); + } + + void PlayPrevious() { + return playlist.PlayPrevious(pc); + } + + enum playlist_result SeekSongPosition(unsigned song_position, + float seek_time) { + return playlist.SeekSongPosition(pc, song_position, seek_time); + } + + enum playlist_result SeekSongId(unsigned song_id, float seek_time) { + return playlist.SeekSongId(pc, song_id, seek_time); + } + + enum playlist_result SeekCurrent(float seek_time, bool relative) { + return playlist.SeekCurrent(pc, seek_time, relative); + } + + void SetRepeat(bool new_value) { + playlist.SetRepeat(pc, new_value); + } + + bool GetRandom() const { + return playlist.GetRandom(); + } + + void SetRandom(bool new_value) { + playlist.SetRandom(pc, new_value); + } + + void SetSingle(bool new_value) { + playlist.SetSingle(pc, new_value); + } + + void SetConsume(bool new_value) { + playlist.SetConsume(new_value); + } +}; + +#endif diff --git a/src/pcm_channels.c b/src/PcmChannels.cxx index ec2bd69a..eca6b250 100644 --- a/src/pcm_channels.c +++ b/src/PcmChannels.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,23 +18,23 @@ */ #include "config.h" -#include "pcm_channels.h" +#include "PcmChannels.hxx" #include "pcm_buffer.h" -#include "pcm_utils.h" +#include "PcmUtils.hxx" #include <assert.h> +template<typename D, typename S> static void -pcm_convert_channels_16_1_to_2(int16_t *restrict dest, - const int16_t *restrict src, - const int16_t *restrict src_end) +MonoToStereo(D dest, S src, S end) { - while (src < src_end) { - int16_t value = *src++; + while (src != end) { + const auto value = *src++; *dest++ = value; *dest++ = value; } + } static void @@ -84,11 +84,11 @@ pcm_convert_channels_16(struct pcm_buffer *buffer, size_t dest_size = src_size / src_channels * dest_channels; *dest_size_r = dest_size; - int16_t *dest = pcm_buffer_get(buffer, dest_size); + int16_t *dest = (int16_t *)pcm_buffer_get(buffer, dest_size); const int16_t *src_end = pcm_end_pointer(src, src_size); if (src_channels == 1 && dest_channels == 2) - pcm_convert_channels_16_1_to_2(dest, src, src_end); + 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) @@ -101,19 +101,6 @@ pcm_convert_channels_16(struct pcm_buffer *buffer, } static void -pcm_convert_channels_24_1_to_2(int32_t *restrict dest, - const int32_t *restrict src, - const int32_t *restrict src_end) -{ - while (src < src_end) { - int32_t value = *src++; - - *dest++ = value; - *dest++ = value; - } -} - -static void pcm_convert_channels_24_2_to_1(int32_t *restrict dest, const int32_t *restrict src, const int32_t *restrict src_end) @@ -160,11 +147,12 @@ pcm_convert_channels_24(struct pcm_buffer *buffer, size_t dest_size = src_size / src_channels * dest_channels; *dest_size_r = dest_size; - int32_t *dest = pcm_buffer_get(buffer, dest_size); - const int32_t *src_end = pcm_end_pointer(src, src_size); + int32_t *dest = (int32_t *)pcm_buffer_get(buffer, dest_size); + const int32_t *src_end = (const int32_t *) + pcm_end_pointer(src, src_size); if (src_channels == 1 && dest_channels == 2) - pcm_convert_channels_24_1_to_2(dest, src, src_end); + 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) @@ -177,13 +165,6 @@ pcm_convert_channels_24(struct pcm_buffer *buffer, } static void -pcm_convert_channels_32_1_to_2(int32_t *dest, const int32_t *src, - const int32_t *src_end) -{ - pcm_convert_channels_24_1_to_2(dest, src, src_end); -} - -static void pcm_convert_channels_32_2_to_1(int32_t *restrict dest, const int32_t *restrict src, const int32_t *restrict src_end) @@ -229,11 +210,12 @@ pcm_convert_channels_32(struct pcm_buffer *buffer, size_t dest_size = src_size / src_channels * dest_channels; *dest_size_r = dest_size; - int32_t *dest = pcm_buffer_get(buffer, dest_size); - const int32_t *src_end = pcm_end_pointer(src, src_size); + int32_t *dest = (int32_t *)pcm_buffer_get(buffer, dest_size); + const int32_t *src_end = (const int32_t *) + pcm_end_pointer(src, src_size); if (src_channels == 1 && dest_channels == 2) - pcm_convert_channels_32_1_to_2(dest, src, src_end); + MonoToStereo(dest, src, src_end); else if (src_channels == 2 && dest_channels == 1) pcm_convert_channels_32_2_to_1(dest, src, src_end); else if (dest_channels == 2) @@ -244,3 +226,65 @@ pcm_convert_channels_32(struct pcm_buffer *buffer, return dest; } + +static void +pcm_convert_channels_float_2_to_1(float *restrict dest, + const float *restrict src, + const float *restrict src_end) +{ + while (src < src_end) { + double a = *src++, b = *src++; + + *dest++ = (a + b) / 2; + } +} + +static void +pcm_convert_channels_float_n_to_2(float *dest, + unsigned src_channels, const float *src, + const float *src_end) +{ + unsigned c; + + assert(src_channels > 0); + + while (src < src_end) { + double sum = 0; + float value; + + for (c = 0; c < src_channels; ++c) + sum += *src++; + value = sum / (double)src_channels; + + /* XXX this is actually only mono ... */ + *dest++ = value; + *dest++ = value; + } +} + +const float * +pcm_convert_channels_float(struct pcm_buffer *buffer, + unsigned dest_channels, + unsigned src_channels, const float *src, + size_t src_size, size_t *dest_size_r) +{ + assert(src_size % (sizeof(*src) * src_channels) == 0); + + size_t dest_size = src_size / src_channels * dest_channels; + *dest_size_r = dest_size; + + float *dest = (float *)pcm_buffer_get(buffer, dest_size); + const float *src_end = (const float *)pcm_end_pointer(src, src_size); + + if (src_channels == 1 && dest_channels == 2) + MonoToStereo(dest, src, src_end); + else if (src_channels == 2 && dest_channels == 1) + pcm_convert_channels_float_2_to_1(dest, src, src_end); + else if (dest_channels == 2) + pcm_convert_channels_float_n_to_2(dest, src_channels, src, + src_end); + else + return NULL; + + return dest; +} diff --git a/src/pcm_channels.h b/src/PcmChannels.hxx index 1e4a0991..ede49cd8 100644 --- a/src/pcm_channels.h +++ b/src/PcmChannels.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PCM_CHANNELS_H -#define MPD_PCM_CHANNELS_H +#ifndef MPD_PCM_CHANNELS_HXX +#define MPD_PCM_CHANNELS_HXX #include <stdint.h> #include <stddef.h> @@ -77,4 +77,21 @@ pcm_convert_channels_32(struct pcm_buffer *buffer, unsigned src_channels, const int32_t *src, size_t src_size, size_t *dest_size_r); +/** + * Changes the number of channels in 32 bit float PCM data. + * + * @param buffer the destination pcm_buffer object + * @param dest_channels the number of channels requested + * @param src_channels the number of channels in the source buffer + * @param src the source PCM buffer + * @param src_size the number of bytes in #src + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const float * +pcm_convert_channels_float(struct pcm_buffer *buffer, + unsigned dest_channels, + unsigned src_channels, const float *src, + size_t src_size, size_t *dest_size_r); + #endif diff --git a/src/PcmConvert.cxx b/src/PcmConvert.cxx new file mode 100644 index 00000000..9618b964 --- /dev/null +++ b/src/PcmConvert.cxx @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PcmConvert.hxx" +#include "PcmChannels.hxx" +#include "PcmFormat.hxx" +#include "pcm_pack.h" +#include "audio_format.h" + +#include <assert.h> +#include <string.h> +#include <math.h> +#include <glib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "pcm" + +PcmConvert::PcmConvert() +{ + memset(this, 0, sizeof(*this)); + + pcm_dsd_init(&dsd); + pcm_resample_init(&resample); + + pcm_buffer_init(&format_buffer); + pcm_buffer_init(&channels_buffer); +} + +PcmConvert::~PcmConvert() +{ + pcm_dsd_deinit(&dsd); + pcm_resample_deinit(&resample); + + pcm_buffer_deinit(&format_buffer); + pcm_buffer_deinit(&channels_buffer); +} + +void +PcmConvert::Reset() +{ + pcm_dsd_reset(&dsd); + pcm_resample_reset(&resample); +} + +inline const int16_t * +PcmConvert::Convert16(const audio_format *src_format, + const void *src_buffer, size_t src_size, + const audio_format *dest_format, size_t *dest_size_r, + GError **error_r) +{ + const int16_t *buf; + size_t len; + + assert(dest_format->format == SAMPLE_FORMAT_S16); + + buf = pcm_convert_to_16(&format_buffer, dither, + sample_format(src_format->format), + src_buffer, src_size, + &len); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %s to 16 bit is not implemented", + sample_format_to_string(sample_format(src_format->format))); + return NULL; + } + + if (src_format->channels != dest_format->channels) { + buf = pcm_convert_channels_16(&channels_buffer, + dest_format->channels, + src_format->channels, + buf, len, &len); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %u to %u channels " + "is not implemented", + src_format->channels, + dest_format->channels); + return NULL; + } + } + + if (src_format->sample_rate != dest_format->sample_rate) { + buf = pcm_resample_16(&resample, + dest_format->channels, + src_format->sample_rate, buf, len, + dest_format->sample_rate, &len, + error_r); + if (buf == NULL) + return NULL; + } + + *dest_size_r = len; + return buf; +} + +inline const int32_t * +PcmConvert::Convert24(const audio_format *src_format, + const void *src_buffer, size_t src_size, + const audio_format *dest_format, size_t *dest_size_r, + GError **error_r) +{ + const int32_t *buf; + size_t len; + + assert(dest_format->format == SAMPLE_FORMAT_S24_P32); + + buf = pcm_convert_to_24(&format_buffer, + sample_format(src_format->format), + src_buffer, src_size, &len); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %s to 24 bit is not implemented", + sample_format_to_string(sample_format(src_format->format))); + return NULL; + } + + if (src_format->channels != dest_format->channels) { + buf = pcm_convert_channels_24(&channels_buffer, + dest_format->channels, + src_format->channels, + buf, len, &len); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %u to %u channels " + "is not implemented", + src_format->channels, + dest_format->channels); + return NULL; + } + } + + if (src_format->sample_rate != dest_format->sample_rate) { + buf = pcm_resample_24(&resample, + dest_format->channels, + src_format->sample_rate, buf, len, + dest_format->sample_rate, &len, + error_r); + if (buf == NULL) + return NULL; + } + + *dest_size_r = len; + return buf; +} + +inline const int32_t * +PcmConvert::Convert32(const audio_format *src_format, + const void *src_buffer, size_t src_size, + const audio_format *dest_format, size_t *dest_size_r, + GError **error_r) +{ + const int32_t *buf; + size_t len; + + assert(dest_format->format == SAMPLE_FORMAT_S32); + + buf = pcm_convert_to_32(&format_buffer, + sample_format(src_format->format), + src_buffer, src_size, &len); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %s to 32 bit is not implemented", + sample_format_to_string(sample_format(src_format->format))); + return NULL; + } + + if (src_format->channels != dest_format->channels) { + buf = pcm_convert_channels_32(&channels_buffer, + dest_format->channels, + src_format->channels, + buf, len, &len); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %u to %u channels " + "is not implemented", + src_format->channels, + dest_format->channels); + return NULL; + } + } + + if (src_format->sample_rate != dest_format->sample_rate) { + buf = pcm_resample_32(&resample, + dest_format->channels, + src_format->sample_rate, buf, len, + dest_format->sample_rate, &len, + error_r); + if (buf == NULL) + return buf; + } + + *dest_size_r = len; + return buf; +} + +inline const float * +PcmConvert::ConvertFloat(const audio_format *src_format, + const void *src_buffer, size_t src_size, + const audio_format *dest_format, size_t *dest_size_r, + GError **error_r) +{ + const float *buffer = (const float *)src_buffer; + size_t size = src_size; + + assert(dest_format->format == SAMPLE_FORMAT_FLOAT); + + /* convert to float now */ + + buffer = pcm_convert_to_float(&format_buffer, + sample_format(src_format->format), + buffer, size, &size); + if (buffer == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %s to float is not implemented", + sample_format_to_string(sample_format(src_format->format))); + return NULL; + } + + /* convert channels */ + + if (src_format->channels != dest_format->channels) { + buffer = pcm_convert_channels_float(&channels_buffer, + dest_format->channels, + src_format->channels, + buffer, size, &size); + if (buffer == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %u to %u channels " + "is not implemented", + src_format->channels, + dest_format->channels); + return NULL; + } + } + + /* resample with float, because this is the best format for + libsamplerate */ + + if (src_format->sample_rate != dest_format->sample_rate) { + buffer = pcm_resample_float(&resample, + dest_format->channels, + src_format->sample_rate, + buffer, size, + dest_format->sample_rate, &size, + error_r); + if (buffer == NULL) + return NULL; + } + + *dest_size_r = size; + return buffer; +} + +const void * +PcmConvert::Convert(const audio_format *src_format, + const void *src, size_t src_size, + const audio_format *dest_format, + size_t *dest_size_r, + GError **error_r) +{ + struct audio_format float_format; + if (src_format->format == SAMPLE_FORMAT_DSD) { + size_t f_size; + const float *f = pcm_dsd_to_float(&dsd, + src_format->channels, + false, (const uint8_t *)src, + src_size, &f_size); + if (f == NULL) { + g_set_error_literal(error_r, pcm_convert_quark(), 0, + "DSD to PCM conversion failed"); + return NULL; + } + + float_format = *src_format; + float_format.format = SAMPLE_FORMAT_FLOAT; + + src_format = &float_format; + src = f; + src_size = f_size; + } + + switch (sample_format(dest_format->format)) { + case SAMPLE_FORMAT_S16: + return Convert16(src_format, src, src_size, + dest_format, dest_size_r, + error_r); + + case SAMPLE_FORMAT_S24_P32: + return Convert24(src_format, src, src_size, + dest_format, dest_size_r, + error_r); + + case SAMPLE_FORMAT_S32: + return Convert32(src_format, src, src_size, + dest_format, dest_size_r, + error_r); + + case SAMPLE_FORMAT_FLOAT: + return ConvertFloat(src_format, src, src_size, + dest_format, dest_size_r, + error_r); + + default: + g_set_error(error_r, pcm_convert_quark(), 0, + "PCM conversion to %s is not implemented", + sample_format_to_string(sample_format(dest_format->format))); + return NULL; + } +} diff --git a/src/PcmConvert.hxx b/src/PcmConvert.hxx new file mode 100644 index 00000000..f08188a9 --- /dev/null +++ b/src/PcmConvert.hxx @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef PCM_CONVERT_HXX +#define PCM_CONVERT_HXX + +#include "PcmDither.hxx" + +extern "C" { +#include "pcm_dsd.h" +#include "pcm_resample.h" +#include "pcm_buffer.h" +} + +#include <glib.h> + +struct audio_format; + +/** + * This object is statically allocated (within another struct), and + * holds buffer allocations and the state for all kinds of PCM + * conversions. + */ +class PcmConvert { + struct pcm_dsd dsd; + + struct pcm_resample_state resample; + + PcmDither dither; + + /** the buffer for converting the sample format */ + struct pcm_buffer format_buffer; + + /** the buffer for converting the channel count */ + struct pcm_buffer channels_buffer; + +public: + PcmConvert(); + ~PcmConvert(); + + + /** + * Reset the pcm_convert_state object. Use this at the + * boundary between two distinct songs and each time the + * format changes. + */ + void Reset(); + + /** + * Converts PCM data between two audio formats. + * + * @param src_format the source audio format + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_format the requested destination audio format + * @param dest_size_r returns the number of bytes of the destination buffer + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return the destination buffer, or NULL on error + */ + const void *Convert(const audio_format *src_format, + const void *src, size_t src_size, + const audio_format *dest_format, + size_t *dest_size_r, + GError **error_r); + +private: + const int16_t *Convert16(const audio_format *src_format, + const void *src_buffer, size_t src_size, + const audio_format *dest_format, + size_t *dest_size_r, + GError **error_r); + + const int32_t *Convert24(const audio_format *src_format, + const void *src_buffer, size_t src_size, + const audio_format *dest_format, + size_t *dest_size_r, + GError **error_r); + + const int32_t *Convert32(const audio_format *src_format, + const void *src_buffer, size_t src_size, + const audio_format *dest_format, + size_t *dest_size_r, + GError **error_r); + + const float *ConvertFloat(const audio_format *src_format, + const void *src_buffer, size_t src_size, + const audio_format *dest_format, + size_t *dest_size_r, + GError **error_r); +}; + +static inline GQuark +pcm_convert_quark(void) +{ + return g_quark_from_static_string("pcm_convert"); +} + +#endif diff --git a/src/PcmDither.cxx b/src/PcmDither.cxx new file mode 100644 index 00000000..98d0d443 --- /dev/null +++ b/src/PcmDither.cxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PcmDither.hxx" +#include "PcmPrng.hxx" + +inline int16_t +PcmDither::Dither24To16(int_fast32_t sample) +{ + constexpr unsigned from_bits = 24; + constexpr unsigned to_bits = 16; + constexpr unsigned scale_bits = from_bits - to_bits; + constexpr int_fast32_t round = 1 << (scale_bits - 1); + constexpr int_fast32_t mask = (1 << scale_bits) - 1; + constexpr int_fast32_t ONE = 1 << (from_bits - 1); + constexpr int_fast32_t MIN = -ONE; + constexpr int_fast32_t MAX = ONE - 1; + + sample += error[0] - error[1] + error[2]; + + error[2] = error[1]; + error[1] = error[0] / 2; + + /* round */ + int_fast32_t output = sample + round; + + int_fast32_t rnd = pcm_prng(random); + output += (rnd & mask) - (random & mask); + + random = rnd; + + /* clip */ + if (output > MAX) { + output = MAX; + + if (sample > MAX) + sample = MAX; + } else if (output < MIN) { + output = MIN; + + if (sample < MIN) + sample = MIN; + } + + output &= ~mask; + + error[0] = sample - output; + + return (int16_t)(output >> scale_bits); +} + +void +PcmDither::Dither24To16(int16_t *dest, const int32_t *src, + const int32_t *src_end) +{ + while (src < src_end) + *dest++ = Dither24To16(*src++); +} + +inline int16_t +PcmDither::Dither32To16(int_fast32_t sample) +{ + return Dither24To16(sample >> 8); +} + +void +PcmDither::Dither32To16(int16_t *dest, const int32_t *src, + const int32_t *src_end) +{ + while (src < src_end) + *dest++ = Dither32To16(*src++); +} diff --git a/src/pcm_dither.h b/src/PcmDither.hxx index 046dea21..10638230 100644 --- a/src/pcm_dither.h +++ b/src/PcmDither.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,29 +17,28 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PCM_DITHER_H -#define MPD_PCM_DITHER_H +#ifndef MPD_PCM_DITHER_HXX +#define MPD_PCM_DITHER_HXX #include <stdint.h> -struct pcm_dither { +class PcmDither { int32_t error[3]; int32_t random; -}; -static inline void -pcm_dither_24_init(struct pcm_dither *dither) -{ - dither->error[0] = dither->error[1] = dither->error[2] = 0; - dither->random = 0; -} +public: + constexpr PcmDither() + :error{0, 0, 0}, random(0) {} + + void Dither24To16(int16_t *dest, const int32_t *src, + const int32_t *src_end); -void -pcm_dither_24_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, const int32_t *src_end); + void Dither32To16(int16_t *dest, const int32_t *src, + const int32_t *src_end); -void -pcm_dither_32_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, const int32_t *src_end); +private: + int16_t Dither24To16(int_fast32_t sample); + int16_t Dither32To16(int_fast32_t sample); +}; #endif diff --git a/src/pcm_format.c b/src/PcmFormat.cxx index d3ea3acb..1385d161 100644 --- a/src/pcm_format.c +++ b/src/PcmFormat.cxx @@ -18,11 +18,21 @@ */ #include "config.h" -#include "pcm_format.h" -#include "pcm_dither.h" +#include "PcmFormat.hxx" +#include "PcmDither.hxx" #include "pcm_buffer.h" #include "pcm_pack.h" -#include "pcm_utils.h" +#include "PcmUtils.hxx" + +#include <type_traits> + +template<typename S> +struct DefaultSampleBits { + typedef decltype(*S()) T; + typedef typename std::remove_reference<T>::type U; + + static constexpr auto value = sizeof(U) * 8; +}; static void pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end) @@ -33,65 +43,90 @@ pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end) } static void -pcm_convert_24_to_16(struct pcm_dither *dither, +pcm_convert_24_to_16(PcmDither &dither, int16_t *out, const int32_t *in, const int32_t *in_end) { - pcm_dither_24_to_16(dither, out, in, in_end); + dither.Dither24To16(out, in, in_end); } static void -pcm_convert_32_to_16(struct pcm_dither *dither, +pcm_convert_32_to_16(PcmDither &dither, int16_t *out, const int32_t *in, const int32_t *in_end) { - pcm_dither_32_to_16(dither, out, in, in_end); + dither.Dither32To16(out, in, in_end); } +template<typename S, unsigned bits=DefaultSampleBits<S>::value> static void -pcm_convert_float_to_16(int16_t *out, const float *in, const float *in_end) +ConvertFromFloat(S dest, const float *src, const float *end) { - const unsigned OUT_BITS = 16; - const float factor = 1 << (OUT_BITS - 1); + typedef decltype(*S()) T; + typedef typename std::remove_reference<T>::type U; - while (in < in_end) { - int sample = *in++ * factor; - *out++ = pcm_clamp_16(sample); + const float factor = 1 << (bits - 1); + + while (src != end) { + int sample(*src++ * factor); + *dest++ = PcmClamp<U, int, bits>(sample); } } +template<typename S, unsigned bits=DefaultSampleBits<S>::value> +static void +ConvertFromFloat(S dest, const float *src, size_t size) +{ + ConvertFromFloat<S, bits>(dest, src, pcm_end_pointer(src, size)); +} + +template<typename S, unsigned bits=sizeof(S)*8> +static S * +AllocateFromFloat(pcm_buffer &buffer, const float *src, size_t src_size, + size_t *dest_size_r) +{ + constexpr size_t src_sample_size = sizeof(*src); + assert(src_size % src_sample_size == 0); + + const size_t num_samples = src_size / src_sample_size; + *dest_size_r = num_samples * sizeof(S); + S *dest = (S *)pcm_buffer_get(&buffer, *dest_size_r); + ConvertFromFloat<S *, bits>(dest, src, src_size); + return dest; +} + static int16_t * pcm_allocate_8_to_16(struct pcm_buffer *buffer, const int8_t *src, size_t src_size, size_t *dest_size_r) { int16_t *dest; *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); + dest = (int16_t *)pcm_buffer_get(buffer, *dest_size_r); pcm_convert_8_to_16(dest, src, pcm_end_pointer(src, src_size)); return dest; } static int16_t * -pcm_allocate_24p32_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, +pcm_allocate_24p32_to_16(struct pcm_buffer *buffer, PcmDither &dither, const int32_t *src, size_t src_size, size_t *dest_size_r) { int16_t *dest; *dest_size_r = src_size / 2; assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); + dest = (int16_t *)pcm_buffer_get(buffer, *dest_size_r); pcm_convert_24_to_16(dither, dest, src, pcm_end_pointer(src, src_size)); return dest; } static int16_t * -pcm_allocate_32_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, +pcm_allocate_32_to_16(struct pcm_buffer *buffer, PcmDither &dither, const int32_t *src, size_t src_size, size_t *dest_size_r) { int16_t *dest; *dest_size_r = src_size / 2; assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); + dest = (int16_t *)pcm_buffer_get(buffer, *dest_size_r); pcm_convert_32_to_16(dither, dest, src, pcm_end_pointer(src, src_size)); return dest; @@ -102,17 +137,11 @@ pcm_allocate_float_to_16(struct pcm_buffer *buffer, const float *src, size_t src_size, size_t *dest_size_r) { - int16_t *dest; - *dest_size_r = src_size / 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_float_to_16(dest, src, - pcm_end_pointer(src, src_size)); - return dest; + return AllocateFromFloat<int16_t>(*buffer, src, src_size, dest_size_r); } const int16_t * -pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, +pcm_convert_to_16(struct pcm_buffer *buffer, PcmDither &dither, enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r) { @@ -125,22 +154,26 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, case SAMPLE_FORMAT_S8: return pcm_allocate_8_to_16(buffer, - src, src_size, dest_size_r); + (const int8_t *)src, src_size, + dest_size_r); case SAMPLE_FORMAT_S16: *dest_size_r = src_size; - return src; + return (const int16_t *)src; case SAMPLE_FORMAT_S24_P32: - return pcm_allocate_24p32_to_16(buffer, dither, src, src_size, + return pcm_allocate_24p32_to_16(buffer, dither, + (const int32_t *)src, src_size, dest_size_r); case SAMPLE_FORMAT_S32: - return pcm_allocate_32_to_16(buffer, dither, src, src_size, + return pcm_allocate_32_to_16(buffer, dither, + (const int32_t *)src, src_size, dest_size_r); case SAMPLE_FORMAT_FLOAT: - return pcm_allocate_float_to_16(buffer, src, src_size, + return pcm_allocate_float_to_16(buffer, + (const float *)src, src_size, dest_size_r); } @@ -170,25 +203,13 @@ pcm_convert_32_to_24(int32_t *restrict out, *out++ = *in++ >> 8; } -static void -pcm_convert_float_to_24(int32_t *out, const float *in, const float *in_end) -{ - const unsigned OUT_BITS = 24; - const float factor = 1 << (OUT_BITS - 1); - - while (in < in_end) { - int sample = *in++ * factor; - *out++ = pcm_clamp_24(sample); - } -} - static int32_t * pcm_allocate_8_to_24(struct pcm_buffer *buffer, const int8_t *src, size_t src_size, size_t *dest_size_r) { int32_t *dest; *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); + dest = (int32_t *)pcm_buffer_get(buffer, *dest_size_r); pcm_convert_8_to_24(dest, src, pcm_end_pointer(src, src_size)); return dest; } @@ -200,7 +221,7 @@ pcm_allocate_16_to_24(struct pcm_buffer *buffer, int32_t *dest; *dest_size_r = src_size * 2; assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); + dest = (int32_t *)pcm_buffer_get(buffer, *dest_size_r); pcm_convert_16_to_24(dest, src, pcm_end_pointer(src, src_size)); return dest; } @@ -210,7 +231,7 @@ pcm_allocate_32_to_24(struct pcm_buffer *buffer, const int32_t *src, size_t src_size, size_t *dest_size_r) { *dest_size_r = src_size; - int32_t *dest = pcm_buffer_get(buffer, *dest_size_r); + int32_t *dest = (int32_t *)pcm_buffer_get(buffer, *dest_size_r); pcm_convert_32_to_24(dest, src, pcm_end_pointer(src, src_size)); return dest; } @@ -220,10 +241,8 @@ pcm_allocate_float_to_24(struct pcm_buffer *buffer, const float *src, size_t src_size, size_t *dest_size_r) { - *dest_size_r = src_size; - int32_t *dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_float_to_24(dest, src, pcm_end_pointer(src, src_size)); - return dest; + return AllocateFromFloat<int32_t, 24>(*buffer, src, src_size, + dest_size_r); } const int32_t * @@ -240,22 +259,26 @@ pcm_convert_to_24(struct pcm_buffer *buffer, case SAMPLE_FORMAT_S8: return pcm_allocate_8_to_24(buffer, - src, src_size, dest_size_r); + (const int8_t *)src, src_size, + dest_size_r); case SAMPLE_FORMAT_S16: return pcm_allocate_16_to_24(buffer, - src, src_size, dest_size_r); + (const int16_t *)src, src_size, + dest_size_r); case SAMPLE_FORMAT_S24_P32: *dest_size_r = src_size; - return src; + return (const int32_t *)src; case SAMPLE_FORMAT_S32: - return pcm_allocate_32_to_24(buffer, src, src_size, + return pcm_allocate_32_to_24(buffer, + (const int32_t *)src, src_size, dest_size_r); case SAMPLE_FORMAT_FLOAT: - return pcm_allocate_float_to_24(buffer, src, src_size, + return pcm_allocate_float_to_24(buffer, + (const float *)src, src_size, dest_size_r); } @@ -291,7 +314,7 @@ pcm_allocate_8_to_32(struct pcm_buffer *buffer, { int32_t *dest; *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); + dest = (int32_t *)pcm_buffer_get(buffer, *dest_size_r); pcm_convert_8_to_32(dest, src, pcm_end_pointer(src, src_size)); return dest; } @@ -303,7 +326,7 @@ pcm_allocate_16_to_32(struct pcm_buffer *buffer, int32_t *dest; *dest_size_r = src_size * 2; assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); + dest = (int32_t *)pcm_buffer_get(buffer, *dest_size_r); pcm_convert_16_to_32(dest, src, pcm_end_pointer(src, src_size)); return dest; } @@ -314,7 +337,7 @@ pcm_allocate_24p32_to_32(struct pcm_buffer *buffer, size_t *dest_size_r) { *dest_size_r = src_size; - int32_t *dest = pcm_buffer_get(buffer, *dest_size_r); + int32_t *dest = (int32_t *)pcm_buffer_get(buffer, *dest_size_r); pcm_convert_24_to_32(dest, src, pcm_end_pointer(src, src_size)); return dest; } @@ -346,63 +369,63 @@ pcm_convert_to_32(struct pcm_buffer *buffer, break; case SAMPLE_FORMAT_S8: - return pcm_allocate_8_to_32(buffer, src, src_size, + return pcm_allocate_8_to_32(buffer, + (const int8_t *)src, src_size, dest_size_r); case SAMPLE_FORMAT_S16: - return pcm_allocate_16_to_32(buffer, src, src_size, + return pcm_allocate_16_to_32(buffer, + (const int16_t *)src, src_size, dest_size_r); case SAMPLE_FORMAT_S24_P32: - return pcm_allocate_24p32_to_32(buffer, src, src_size, + return pcm_allocate_24p32_to_32(buffer, + (const int32_t *)src, src_size, dest_size_r); case SAMPLE_FORMAT_S32: *dest_size_r = src_size; - return src; + return (const int32_t *)src; case SAMPLE_FORMAT_FLOAT: - return pcm_allocate_float_to_32(buffer, src, src_size, + return pcm_allocate_float_to_32(buffer, + (const float *)src, src_size, dest_size_r); } return NULL; } +template<typename S, unsigned bits=DefaultSampleBits<S>::value> static void -pcm_convert_8_to_float(float *out, const int8_t *in, const int8_t *in_end) +ConvertToFloat(float *dest, S src, S end) { - enum { in_bits = sizeof(*in) * 8 }; - static const float factor = 2.0f / (1 << in_bits); - while (in < in_end) - *out++ = (float)*in++ * factor; -} + constexpr float factor = 0.5 / (1 << (bits - 2)); + while (src != end) + *dest++ = float(*src++) * factor; -static void -pcm_convert_16_to_float(float *out, const int16_t *in, const int16_t *in_end) -{ - enum { in_bits = sizeof(*in) * 8 }; - static const float factor = 2.0f / (1 << in_bits); - while (in < in_end) - *out++ = (float)*in++ * factor; } +template<typename S, unsigned bits=DefaultSampleBits<S>::value> static void -pcm_convert_24_to_float(float *out, const int32_t *in, const int32_t *in_end) +ConvertToFloat(float *dest, S src, size_t size) { - enum { in_bits = 24 }; - static const float factor = 2.0f / (1 << in_bits); - while (in < in_end) - *out++ = (float)*in++ * factor; + ConvertToFloat<S, bits>(dest, src, pcm_end_pointer(src, size)); } -static void -pcm_convert_32_to_float(float *out, const int32_t *in, const int32_t *in_end) +template<typename S, unsigned bits=DefaultSampleBits<S>::value> +static float * +AllocateToFloat(pcm_buffer &buffer, S src, size_t src_size, + size_t *dest_size_r) { - enum { in_bits = sizeof(*in) * 8 }; - static const float factor = 0.5f / (1 << (in_bits - 2)); - while (in < in_end) - *out++ = (float)*in++ * factor; + constexpr size_t src_sample_size = sizeof(*S()); + assert(src_size % src_sample_size == 0); + + const size_t num_samples = src_size / src_sample_size; + *dest_size_r = num_samples * sizeof(float); + float *dest = (float *)pcm_buffer_get(&buffer, *dest_size_r); + ConvertToFloat<S, bits>(dest, src, src_size); + return dest; } static float * @@ -410,11 +433,7 @@ pcm_allocate_8_to_float(struct pcm_buffer *buffer, const int8_t *src, size_t src_size, size_t *dest_size_r) { - float *dest; - *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_8_to_float(dest, src, pcm_end_pointer(src, src_size)); - return dest; + return AllocateToFloat(*buffer, src, src_size, dest_size_r); } static float * @@ -422,23 +441,16 @@ pcm_allocate_16_to_float(struct pcm_buffer *buffer, const int16_t *src, size_t src_size, size_t *dest_size_r) { - float *dest; - *dest_size_r = src_size * 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_16_to_float(dest, src, pcm_end_pointer(src, src_size)); - return dest; + return AllocateToFloat(*buffer, src, src_size, dest_size_r); } static float * pcm_allocate_24p32_to_float(struct pcm_buffer *buffer, - const int32_t *src, size_t src_size, - size_t *dest_size_r) + const int32_t *src, size_t src_size, + size_t *dest_size_r) { - *dest_size_r = src_size; - float *dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_24_to_float(dest, src, pcm_end_pointer(src, src_size)); - return dest; + return AllocateToFloat<decltype(src), 24> + (*buffer, src, src_size, dest_size_r); } static float * @@ -446,10 +458,7 @@ pcm_allocate_32_to_float(struct pcm_buffer *buffer, const int32_t *src, size_t src_size, size_t *dest_size_r) { - *dest_size_r = src_size; - float *dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_32_to_float(dest, src, pcm_end_pointer(src, src_size)); - return dest; + return AllocateToFloat(*buffer, src, src_size, dest_size_r); } const float * @@ -464,23 +473,27 @@ pcm_convert_to_float(struct pcm_buffer *buffer, case SAMPLE_FORMAT_S8: return pcm_allocate_8_to_float(buffer, - src, src_size, dest_size_r); + (const int8_t *)src, src_size, + dest_size_r); case SAMPLE_FORMAT_S16: return pcm_allocate_16_to_float(buffer, - src, src_size, dest_size_r); + (const int16_t *)src, src_size, + dest_size_r); case SAMPLE_FORMAT_S24_P32: return pcm_allocate_24p32_to_float(buffer, - src, src_size, dest_size_r); + (const int32_t *)src, src_size, + dest_size_r); case SAMPLE_FORMAT_S32: return pcm_allocate_32_to_float(buffer, - src, src_size, dest_size_r); + (const int32_t *)src, src_size, + dest_size_r); case SAMPLE_FORMAT_FLOAT: *dest_size_r = src_size; - return src; + return (const float *)src; } return NULL; diff --git a/src/pcm_format.h b/src/PcmFormat.hxx index 48bcd066..a5970b2d 100644 --- a/src/pcm_format.h +++ b/src/PcmFormat.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef PCM_FORMAT_H -#define PCM_FORMAT_H +#ifndef MPD_PCM_FORMAT_HXX +#define MPD_PCM_FORMAT_HXX #include "audio_format.h" @@ -26,7 +26,7 @@ #include <stddef.h> struct pcm_buffer; -struct pcm_dither; +class PcmDither; /** * Converts PCM samples to 16 bit. If the source format is 24 bit, @@ -41,7 +41,7 @@ struct pcm_dither; * @return the destination buffer */ const int16_t * -pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, +pcm_convert_to_16(struct pcm_buffer *buffer, PcmDither &dither, enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r); diff --git a/src/PcmMix.cxx b/src/PcmMix.cxx new file mode 100644 index 00000000..8435c0c2 --- /dev/null +++ b/src/PcmMix.cxx @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PcmMix.hxx" +#include "PcmVolume.hxx" +#include "PcmUtils.hxx" +#include "audio_format.h" + +#include <math.h> + +template<typename T, typename U, unsigned bits> +static T +PcmAddVolume(T _a, T _b, int volume1, int volume2) +{ + U a(_a), b(_b); + + U c = ((a * volume1 + b * volume2) + + pcm_volume_dither() + PCM_VOLUME_1 / 2) + / PCM_VOLUME_1; + + return PcmClamp<T, U, bits>(c); +} + +template<typename T, typename U, unsigned bits> +static void +PcmAddVolume(T *a, const T *b, unsigned n, int volume1, int volume2) +{ + for (size_t i = 0; i != n; ++i) + a[i] = PcmAddVolume<T, U, bits>(a[i], b[i], volume1, volume2); +} + +template<typename T, typename U, unsigned bits> +static void +PcmAddVolumeVoid(void *a, const void *b, size_t size, int volume1, int volume2) +{ + constexpr size_t sample_size = sizeof(T); + assert(size % sample_size == 0); + + PcmAddVolume<T, U, bits>((T *)a, (const T *)b, size / sample_size, + volume1, volume2); +} + +static void +pcm_add_vol_float(float *buffer1, const float *buffer2, + unsigned num_samples, float volume1, float volume2) +{ + while (num_samples > 0) { + float sample1 = *buffer1; + float sample2 = *buffer2++; + + sample1 = (sample1 * volume1 + sample2 * volume2); + *buffer1++ = sample1; + --num_samples; + } +} + +static bool +pcm_add_vol(void *buffer1, const void *buffer2, size_t size, + int vol1, int vol2, + enum sample_format format) +{ + switch (format) { + case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: + /* not implemented */ + return false; + + case SAMPLE_FORMAT_S8: + PcmAddVolumeVoid<int8_t, int32_t, 8>(buffer1, buffer2, size, + vol1, vol2); + return true; + + case SAMPLE_FORMAT_S16: + PcmAddVolumeVoid<int16_t, int32_t, 16>(buffer1, buffer2, size, + vol1, vol2); + return true; + + case SAMPLE_FORMAT_S24_P32: + PcmAddVolumeVoid<int32_t, int64_t, 24>(buffer1, buffer2, size, + vol1, vol2); + return true; + + case SAMPLE_FORMAT_S32: + PcmAddVolumeVoid<int32_t, int64_t, 32>(buffer1, buffer2, size, + vol1, vol2); + return true; + + case SAMPLE_FORMAT_FLOAT: + pcm_add_vol_float((float *)buffer1, (const float *)buffer2, + size / 4, + pcm_volume_to_float(vol1), + pcm_volume_to_float(vol2)); + return true; + } + + /* unreachable */ + assert(false); + return false; +} + +template<typename T, typename U, unsigned bits> +static T +PcmAdd(T _a, T _b) +{ + U a(_a), b(_b); + return PcmClamp<T, U, bits>(a + b); +} + +template<typename T, typename U, unsigned bits> +static void +PcmAdd(T *a, const T *b, unsigned n) +{ + for (size_t i = 0; i != n; ++i) + a[i] = PcmAdd<T, U, bits>(a[i], b[i]); +} + +template<typename T, typename U, unsigned bits> +static void +PcmAddVoid(void *a, const void *b, size_t size) +{ + constexpr size_t sample_size = sizeof(T); + assert(size % sample_size == 0); + + PcmAdd<T, U, bits>((T *)a, (const T *)b, size / sample_size); +} + +static void +pcm_add_float(float *buffer1, const float *buffer2, unsigned num_samples) +{ + while (num_samples > 0) { + float sample1 = *buffer1; + float sample2 = *buffer2++; + *buffer1++ = sample1 + sample2; + --num_samples; + } +} + +static bool +pcm_add(void *buffer1, const void *buffer2, size_t size, + enum sample_format format) +{ + switch (format) { + case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: + /* not implemented */ + return false; + + case SAMPLE_FORMAT_S8: + PcmAddVoid<int8_t, int32_t, 8>(buffer1, buffer2, size); + return true; + + case SAMPLE_FORMAT_S16: + PcmAddVoid<int16_t, int32_t, 16>(buffer1, buffer2, size); + return true; + + case SAMPLE_FORMAT_S24_P32: + PcmAddVoid<int32_t, int64_t, 24>(buffer1, buffer2, size); + return true; + + case SAMPLE_FORMAT_S32: + PcmAddVoid<int32_t, int64_t, 32>(buffer1, buffer2, size); + return true; + + case SAMPLE_FORMAT_FLOAT: + pcm_add_float((float *)buffer1, (const float *)buffer2, + size / 4); + return true; + } + + /* unreachable */ + assert(false); + return false; +} + +bool +pcm_mix(void *buffer1, const void *buffer2, size_t size, + enum sample_format format, float portion1) +{ + int vol1; + float s; + + /* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses NaN + * to signal mixing rather than fading */ + if (isnan(portion1)) + return pcm_add(buffer1, buffer2, size, format); + + s = sin(M_PI_2 * portion1); + s *= s; + + vol1 = s * PCM_VOLUME_1 + 0.5; + vol1 = vol1 > PCM_VOLUME_1 ? PCM_VOLUME_1 : (vol1 < 0 ? 0 : vol1); + + return pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format); +} diff --git a/src/pcm_mix.h b/src/PcmMix.hxx index 0e58d01e..bb7110d0 100644 --- a/src/pcm_mix.h +++ b/src/PcmMix.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,12 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef PCM_MIX_H -#define PCM_MIX_H +#ifndef MPD_PCM_MIX_HXX +#define MPD_PCM_MIX_HXX #include "audio_format.h" +#include "gcc.h" -#include <stdbool.h> #include <stddef.h> /* @@ -41,7 +41,7 @@ * * @return true on success, false if the format is not supported */ -G_GNUC_WARN_UNUSED_RESULT +gcc_warn_unused_result bool pcm_mix(void *buffer1, const void *buffer2, size_t size, enum sample_format format, float portion1); diff --git a/src/pcm_prng.h b/src/PcmPrng.hxx index 457ba4b6..0c823250 100644 --- a/src/pcm_prng.h +++ b/src/PcmPrng.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef PCM_PRNG_H -#define PCM_PRNG_H +#ifndef MPD_PCM_PRNG_HXX +#define MPD_PCM_PRNG_HXX /** * A very simple linear congruential PRNG. It's good enough for PCM diff --git a/src/PcmUtils.hxx b/src/PcmUtils.hxx new file mode 100644 index 00000000..d77c4194 --- /dev/null +++ b/src/PcmUtils.hxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_UTILS_H +#define MPD_PCM_UTILS_H + +#include "gcc.h" + +#include <limits> + +#include <stdint.h> + +/** + * Add a byte count to the specified pointer. This is a utility + * function to convert a source pointer and a byte count to an "end" + * pointer for use in loops. + */ +template<typename T> +static inline const T * +pcm_end_pointer(const T *p, size_t size) +{ + return (const T *)((const uint8_t *)p + size); +} + +/** + * Check if the value is within the range of the provided bit size, + * and caps it if necessary. + */ +template<typename T, typename U, unsigned bits> +gcc_const +static inline T +PcmClamp(U x) +{ + constexpr U MIN_VALUE = -(U(1) << (bits - 1)); + constexpr U MAX_VALUE = (U(1) << (bits - 1)) - 1; + + typedef std::numeric_limits<T> limits; + static_assert(MIN_VALUE >= limits::min(), "out of range"); + static_assert(MAX_VALUE <= limits::max(), "out of range"); + + if (gcc_unlikely(x < MIN_VALUE)) + return T(MIN_VALUE); + + if (gcc_unlikely(x > MAX_VALUE)) + return T(MAX_VALUE); + + return T(x); +} + +#endif diff --git a/src/pcm_volume.c b/src/PcmVolume.cxx index 49c86026..556ab992 100644 --- a/src/pcm_volume.c +++ b/src/PcmVolume.cxx @@ -18,8 +18,8 @@ */ #include "config.h" -#include "pcm_volume.h" -#include "pcm_utils.h" +#include "PcmVolume.hxx" +#include "PcmUtils.hxx" #include "audio_format.h" #include <glib.h> @@ -40,7 +40,7 @@ pcm_volume_change_8(int8_t *buffer, const int8_t *end, int volume) PCM_VOLUME_1 / 2) / PCM_VOLUME_1; - *buffer++ = pcm_range(sample, 8); + *buffer++ = PcmClamp<int8_t, int16_t, 8>(sample); } } @@ -54,7 +54,7 @@ pcm_volume_change_16(int16_t *buffer, const int16_t *end, int volume) PCM_VOLUME_1 / 2) / PCM_VOLUME_1; - *buffer++ = pcm_range(sample, 16); + *buffer++ = PcmClamp<int16_t, int32_t, 16>(sample); } } @@ -107,7 +107,7 @@ pcm_volume_change_24(int32_t *buffer, const int32_t *end, int volume) PCM_VOLUME_1 / 2) / PCM_VOLUME_1; #endif - *buffer++ = pcm_range(sample, 24); + *buffer++ = PcmClamp<int32_t, int32_t, 24>(sample); } } @@ -127,7 +127,7 @@ pcm_volume_change_32(int32_t *buffer, const int32_t *end, int volume) sample = (sample * volume + pcm_volume_dither() + PCM_VOLUME_1 / 2) / PCM_VOLUME_1; - *buffer++ = pcm_range_64(sample, 32); + *buffer++ = PcmClamp<int32_t, int64_t, 32>(sample); #endif } } @@ -163,23 +163,27 @@ pcm_volume(void *buffer, size_t length, return false; case SAMPLE_FORMAT_S8: - pcm_volume_change_8(buffer, end, volume); + pcm_volume_change_8((int8_t *)buffer, (const int8_t *)end, + volume); return true; case SAMPLE_FORMAT_S16: - pcm_volume_change_16(buffer, end, volume); + pcm_volume_change_16((int16_t *)buffer, (const int16_t *)end, + volume); return true; case SAMPLE_FORMAT_S24_P32: - pcm_volume_change_24(buffer, end, volume); + pcm_volume_change_24((int32_t *)buffer, (const int32_t *)end, + volume); return true; case SAMPLE_FORMAT_S32: - pcm_volume_change_32(buffer, end, volume); + pcm_volume_change_32((int32_t *)buffer, (const int32_t *)end, + volume); return true; case SAMPLE_FORMAT_FLOAT: - pcm_volume_change_float(buffer, end, + pcm_volume_change_float((float *)buffer, (const float *)end, pcm_volume_to_float(volume)); return true; } diff --git a/src/pcm_volume.h b/src/PcmVolume.hxx index 64e3c764..d3e6a553 100644 --- a/src/pcm_volume.h +++ b/src/PcmVolume.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,14 +17,15 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef PCM_VOLUME_H -#define PCM_VOLUME_H +#ifndef MPD_PCM_VOLUME_HXX +#define MPD_PCM_VOLUME_HXX -#include "pcm_prng.h" +#include "PcmPrng.hxx" #include "audio_format.h" #include <stdint.h> #include <stdbool.h> +#include <stddef.h> enum { /** this value means "100% volume" */ diff --git a/src/permission.c b/src/Permission.cxx index cd52b9c8..e6cf5cfb 100644 --- a/src/permission.c +++ b/src/Permission.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,12 @@ */ #include "config.h" -#include "permission.h" -#include "conf.h" +#include "Permission.hxx" #include "mpd_error.h" +#include "conf.h" + +#include <map> +#include <string> #include <glib.h> @@ -35,7 +38,7 @@ #define PERMISSION_CONTROL_STRING "control" #define PERMISSION_ADMIN_STRING "admin" -static GHashTable *permission_passwords; +static std::map<std::string, unsigned> permission_passwords; static unsigned permission_default; @@ -75,9 +78,6 @@ void initPermissions(void) unsigned permission; const struct config_param *param; - permission_passwords = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, NULL); - permission_default = PERMISSION_READ | PERMISSION_ADD | PERMISSION_CONTROL | PERMISSION_ADMIN; @@ -101,9 +101,8 @@ void initPermissions(void) permission = parsePermissions(separator + 1); - g_hash_table_replace(permission_passwords, - password, - GINT_TO_POINTER(permission)); + permission_passwords.insert(std::make_pair(password, + permission)); } while ((param = config_get_next_param(CONF_PASSWORD, param))); } @@ -115,23 +114,14 @@ void initPermissions(void) int getPermissionFromPassword(char const* password, unsigned* permission) { - bool found; - gpointer key, value; - - found = g_hash_table_lookup_extended(permission_passwords, - password, &key, &value); - if (!found) + auto i = permission_passwords.find(password); + if (i == permission_passwords.end()) return -1; - *permission = GPOINTER_TO_INT(value); + *permission = i->second; return 0; } -void finishPermissions(void) -{ - g_hash_table_destroy(permission_passwords); -} - unsigned getDefaultPermissions(void) { return permission_default; diff --git a/src/permission.h b/src/Permission.hxx index 6c377136..4ff3850e 100644 --- a/src/permission.h +++ b/src/Permission.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PERMISSION_H -#define MPD_PERMISSION_H +#ifndef MPD_PERMISSION_HXX +#define MPD_PERMISSION_HXX #define PERMISSION_NONE 0 #define PERMISSION_READ 1 @@ -29,8 +29,6 @@ int getPermissionFromPassword(char const* password, unsigned* permission); -void finishPermissions(void); - unsigned getDefaultPermissions(void); void initPermissions(void); diff --git a/src/PlayerCommands.cxx b/src/PlayerCommands.cxx new file mode 100644 index 00000000..32cd16d9 --- /dev/null +++ b/src/PlayerCommands.cxx @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlayerCommands.hxx" +#include "CommandError.hxx" +#include "Playlist.hxx" +#include "PlaylistPrint.hxx" +#include "UpdateGlue.hxx" +#include "ClientInternal.hxx" +#include "Volume.hxx" +#include "OutputAll.hxx" +#include "Partition.hxx" +#include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" + +extern "C" { +#include "audio_format.h" +} + +#include "replay_gain_config.h" + +#include <errno.h> + +#define COMMAND_STATUS_STATE "state" +#define COMMAND_STATUS_REPEAT "repeat" +#define COMMAND_STATUS_SINGLE "single" +#define COMMAND_STATUS_CONSUME "consume" +#define COMMAND_STATUS_RANDOM "random" +#define COMMAND_STATUS_PLAYLIST "playlist" +#define COMMAND_STATUS_PLAYLIST_LENGTH "playlistlength" +#define COMMAND_STATUS_SONG "song" +#define COMMAND_STATUS_SONGID "songid" +#define COMMAND_STATUS_NEXTSONG "nextsong" +#define COMMAND_STATUS_NEXTSONGID "nextsongid" +#define COMMAND_STATUS_TIME "time" +#define COMMAND_STATUS_BITRATE "bitrate" +#define COMMAND_STATUS_ERROR "error" +#define COMMAND_STATUS_CROSSFADE "xfade" +#define COMMAND_STATUS_MIXRAMPDB "mixrampdb" +#define COMMAND_STATUS_MIXRAMPDELAY "mixrampdelay" +#define COMMAND_STATUS_AUDIO "audio" +#define COMMAND_STATUS_UPDATING_DB "updating_db" + +enum command_return +handle_play(Client *client, int argc, char *argv[]) +{ + int song = -1; + + if (argc == 2 && !check_int(client, &song, argv[1])) + return COMMAND_RETURN_ERROR; + enum playlist_result result = client->partition.PlayPosition(song); + return print_playlist_result(client, result); +} + +enum command_return +handle_playid(Client *client, int argc, char *argv[]) +{ + int id = -1; + + if (argc == 2 && !check_int(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = client->partition.PlayId(id); + return print_playlist_result(client, result); +} + +enum command_return +handle_stop(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + client->partition.Stop(); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_currentsong(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist_print_current(client, &client->playlist); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_pause(Client *client, + int argc, char *argv[]) +{ + if (argc == 2) { + bool pause_flag; + if (!check_bool(client, &pause_flag, argv[1])) + return COMMAND_RETURN_ERROR; + + client->player_control->SetPause(pause_flag); + } else + client->player_control->Pause(); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_status(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + const char *state = NULL; + int updateJobId; + int song; + + const auto player_status = client->player_control->GetStatus(); + + switch (player_status.state) { + case PLAYER_STATE_STOP: + state = "stop"; + break; + case PLAYER_STATE_PAUSE: + state = "pause"; + break; + case PLAYER_STATE_PLAY: + state = "play"; + break; + } + + const playlist &playlist = client->playlist; + client_printf(client, + "volume: %i\n" + COMMAND_STATUS_REPEAT ": %i\n" + COMMAND_STATUS_RANDOM ": %i\n" + COMMAND_STATUS_SINGLE ": %i\n" + COMMAND_STATUS_CONSUME ": %i\n" + COMMAND_STATUS_PLAYLIST ": %li\n" + COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" + COMMAND_STATUS_CROSSFADE ": %i\n" + COMMAND_STATUS_MIXRAMPDB ": %f\n" + COMMAND_STATUS_MIXRAMPDELAY ": %f\n" + COMMAND_STATUS_STATE ": %s\n", + volume_level_get(), + playlist.GetRepeat(), + playlist.GetRandom(), + playlist.GetSingle(), + playlist.GetConsume(), + (unsigned long)playlist.GetVersion(), + playlist.GetLength(), + (int)(client->player_control->GetCrossFade() + 0.5), + client->player_control->GetMixRampDb(), + client->player_control->GetMixRampDelay(), + state); + + song = playlist.GetCurrentPosition(); + if (song >= 0) { + client_printf(client, + COMMAND_STATUS_SONG ": %i\n" + COMMAND_STATUS_SONGID ": %u\n", + song, playlist.PositionToId(song)); + } + + if (player_status.state != PLAYER_STATE_STOP) { + struct audio_format_string af_string; + + client_printf(client, + COMMAND_STATUS_TIME ": %i:%i\n" + "elapsed: %1.3f\n" + COMMAND_STATUS_BITRATE ": %u\n" + COMMAND_STATUS_AUDIO ": %s\n", + (int)(player_status.elapsed_time + 0.5), + (int)(player_status.total_time + 0.5), + player_status.elapsed_time, + player_status.bit_rate, + audio_format_to_string(&player_status.audio_format, + &af_string)); + } + + if ((updateJobId = isUpdatingDB())) { + client_printf(client, + COMMAND_STATUS_UPDATING_DB ": %i\n", + updateJobId); + } + + char *error = client->player_control->GetErrorMessage(); + if (error != NULL) { + client_printf(client, + COMMAND_STATUS_ERROR ": %s\n", + error); + g_free(error); + } + + song = playlist.GetNextPosition(); + if (song >= 0) { + client_printf(client, + COMMAND_STATUS_NEXTSONG ": %i\n" + COMMAND_STATUS_NEXTSONGID ": %u\n", + song, playlist.PositionToId(song)); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_next(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist &playlist = client->playlist; + + /* single mode is not considered when this is user who + * wants to change song. */ + const bool single = playlist.queue.single; + playlist.queue.single = false; + + client->partition.PlayNext(); + + playlist.queue.single = single; + return COMMAND_RETURN_OK; +} + +enum command_return +handle_previous(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + client->partition.PlayPrevious(); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_repeat(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.SetRepeat(status); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_single(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.SetSingle(status); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_consume(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.SetConsume(status); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_random(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.SetRandom(status); + audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client->partition.GetRandom())); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_clearerror(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + client->player_control->ClearError(); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_seek(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned song, seek_time; + + if (!check_unsigned(client, &song, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &seek_time, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SeekSongPosition(song, seek_time); + return print_playlist_result(client, result); +} + +enum command_return +handle_seekid(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned id, seek_time; + + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &seek_time, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SeekSongId(id, seek_time); + return print_playlist_result(client, result); +} + +enum command_return +handle_seekcur(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *p = argv[1]; + bool relative = *p == '+' || *p == '-'; + int seek_time; + if (!check_int(client, &seek_time, p)) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SeekCurrent(seek_time, relative); + return print_playlist_result(client, result); +} + +enum command_return +handle_crossfade(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned xfade_time; + + if (!check_unsigned(client, &xfade_time, argv[1])) + return COMMAND_RETURN_ERROR; + client->player_control->SetCrossFade(xfade_time); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_mixrampdb(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + float db; + + if (!check_float(client, &db, argv[1])) + return COMMAND_RETURN_ERROR; + client->player_control->SetMixRampDb(db); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_mixrampdelay(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + float delay_secs; + + if (!check_float(client, &delay_secs, argv[1])) + return COMMAND_RETURN_ERROR; + client->player_control->SetMixRampDelay(delay_secs); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_replay_gain_mode(Client *client, + G_GNUC_UNUSED int argc, char *argv[]) +{ + if (!replay_gain_set_mode_string(argv[1])) { + command_error(client, ACK_ERROR_ARG, + "Unrecognized replay gain mode"); + return COMMAND_RETURN_ERROR; + } + + audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client->playlist.queue.random)); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_replay_gain_status(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + client_printf(client, "replay_gain_mode: %s\n", + replay_gain_get_mode_string()); + return COMMAND_RETURN_OK; +} diff --git a/src/PlayerCommands.hxx b/src/PlayerCommands.hxx new file mode 100644 index 00000000..a2fed585 --- /dev/null +++ b/src/PlayerCommands.hxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYER_COMMANDS_HXX +#define MPD_PLAYER_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_play(Client *client, int argc, char *argv[]); + +enum command_return +handle_playid(Client *client, int argc, char *argv[]); + +enum command_return +handle_stop(Client *client, int argc, char *argv[]); + +enum command_return +handle_currentsong(Client *client, int argc, char *argv[]); + +enum command_return +handle_pause(Client *client, int argc, char *argv[]); + +enum command_return +handle_status(Client *client, int argc, char *argv[]); + +enum command_return +handle_next(Client *client, int argc, char *argv[]); + +enum command_return +handle_previous(Client *client, int argc, char *avg[]); + +enum command_return +handle_repeat(Client *client, int argc, char *argv[]); + +enum command_return +handle_single(Client *client, int argc, char *argv[]); + +enum command_return +handle_consume(Client *client, int argc, char *argv[]); + +enum command_return +handle_random(Client *client, int argc, char *argv[]); + +enum command_return +handle_clearerror(Client *client, int argc, char *argv[]); + +enum command_return +handle_seek(Client *client, int argc, char *argv[]); + +enum command_return +handle_seekid(Client *client, int argc, char *argv[]); + +enum command_return +handle_seekcur(Client *client, int argc, char *argv[]); + +enum command_return +handle_crossfade(Client *client, int argc, char *argv[]); + +enum command_return +handle_mixrampdb(Client *client, int argc, char *argv[]); + +enum command_return +handle_mixrampdelay(Client *client, int argc, char *argv[]); + +enum command_return +handle_replay_gain_mode(Client *client, int argc, char *argv[]); + +enum command_return +handle_replay_gain_status(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/PlayerControl.cxx b/src/PlayerControl.cxx new file mode 100644 index 00000000..d3e8c7d0 --- /dev/null +++ b/src/PlayerControl.cxx @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlayerControl.hxx" +#include "Idle.hxx" +#include "song.h" +#include "DecoderControl.hxx" +#include "Main.hxx" + +#include <cmath> + +#include <assert.h> +#include <stdio.h> + +static void +pc_enqueue_song_locked(struct player_control *pc, struct song *song); + +player_control::player_control(unsigned _buffer_chunks, + unsigned _buffered_before_play) + :buffer_chunks(_buffer_chunks), + buffered_before_play(_buffered_before_play), + thread(nullptr), + command(PLAYER_COMMAND_NONE), + state(PLAYER_STATE_STOP), + error_type(PLAYER_ERROR_NONE), + error(nullptr), + next_song(nullptr), + cross_fade_seconds(0), + mixramp_db(0), +#if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_C99_MATH_TR1) + /* workaround: on MacPorts, this option is disabled on gcc47, + and therefore std::nanf() is not available */ + mixramp_delay_seconds(nanf("")), +#else + mixramp_delay_seconds(std::nanf("")), +#endif + total_play_time(0), + border_pause(false) +{ +} + +player_control::~player_control() +{ + if (next_song != nullptr) + song_free(next_song); +} + +static void +player_command_wait_locked(struct player_control *pc) +{ + while (pc->command != PLAYER_COMMAND_NONE) + pc->ClientWait(); +} + +static void +player_command_locked(struct player_control *pc, enum player_command cmd) +{ + assert(pc->command == PLAYER_COMMAND_NONE); + + pc->command = cmd; + pc->Signal(); + player_command_wait_locked(pc); +} + +static void +player_command(struct player_control *pc, enum player_command cmd) +{ + pc->Lock(); + player_command_locked(pc, cmd); + pc->Unlock(); +} + +void +player_control::Play(struct song *song) +{ + assert(song != NULL); + + Lock(); + + if (state != PLAYER_STATE_STOP) + player_command_locked(this, PLAYER_COMMAND_STOP); + + assert(next_song == nullptr); + + pc_enqueue_song_locked(this, song); + + assert(next_song == nullptr); + + Unlock(); + + idle_add(IDLE_PLAYER); +} + +void +player_control::Cancel() +{ + player_command(this, PLAYER_COMMAND_CANCEL); + assert(next_song == NULL); +} + +void +player_control::Stop() +{ + player_command(this, PLAYER_COMMAND_CLOSE_AUDIO); + assert(next_song == nullptr); + + idle_add(IDLE_PLAYER); +} + +void +player_control::UpdateAudio() +{ + player_command(this, PLAYER_COMMAND_UPDATE_AUDIO); +} + +void +player_control::Kill() +{ + assert(thread != NULL); + + player_command(this, PLAYER_COMMAND_EXIT); + g_thread_join(thread); + thread = NULL; + + idle_add(IDLE_PLAYER); +} + +void +player_control::Pause() +{ + Lock(); + + if (state != PLAYER_STATE_STOP) { + player_command_locked(this, PLAYER_COMMAND_PAUSE); + idle_add(IDLE_PLAYER); + } + + Unlock(); +} + +static void +pc_pause_locked(struct player_control *pc) +{ + if (pc->state != PLAYER_STATE_STOP) { + player_command_locked(pc, PLAYER_COMMAND_PAUSE); + idle_add(IDLE_PLAYER); + } +} + +void +player_control::SetPause(bool pause_flag) +{ + Lock(); + + switch (state) { + case PLAYER_STATE_STOP: + break; + + case PLAYER_STATE_PLAY: + if (pause_flag) + pc_pause_locked(this); + break; + + case PLAYER_STATE_PAUSE: + if (!pause_flag) + pc_pause_locked(this); + break; + } + + Unlock(); +} + +void +player_control::SetBorderPause(bool _border_pause) +{ + Lock(); + border_pause = _border_pause; + Unlock(); +} + +player_status +player_control::GetStatus() +{ + player_status status; + + Lock(); + player_command_locked(this, PLAYER_COMMAND_REFRESH); + + status.state = state; + + if (state != PLAYER_STATE_STOP) { + status.bit_rate = bit_rate; + status.audio_format = audio_format; + status.total_time = total_time; + status.elapsed_time = elapsed_time; + } + + Unlock(); + + return status; +} + +void +player_control::SetError(player_error type, GError *_error) +{ + assert(type != PLAYER_ERROR_NONE); + assert(_error != NULL); + + if (error_type != PLAYER_ERROR_NONE) + g_error_free(error); + + error_type = type; + error = _error; +} + +void +player_control::ClearError() +{ + Lock(); + + if (error_type != PLAYER_ERROR_NONE) { + error_type = PLAYER_ERROR_NONE; + g_error_free(error); + } + + Unlock(); +} + +char * +player_control::GetErrorMessage() const +{ + Lock(); + char *message = error_type != PLAYER_ERROR_NONE + ? g_strdup(error->message) + : NULL; + Unlock(); + return message; +} + +static void +pc_enqueue_song_locked(struct player_control *pc, struct song *song) +{ + assert(song != NULL); + assert(pc->next_song == NULL); + + pc->next_song = song; + player_command_locked(pc, PLAYER_COMMAND_QUEUE); +} + +void +player_control::EnqueueSong(struct song *song) +{ + assert(song != NULL); + + Lock(); + pc_enqueue_song_locked(this, song); + Unlock(); +} + +bool +player_control::Seek(struct song *song, float seek_time) +{ + assert(song != NULL); + + Lock(); + + if (next_song != nullptr) + song_free(next_song); + + next_song = song; + seek_where = seek_time; + player_command_locked(this, PLAYER_COMMAND_SEEK); + Unlock(); + + assert(next_song == nullptr); + + idle_add(IDLE_PLAYER); + + return true; +} + +void +player_control::SetCrossFade(float _cross_fade_seconds) +{ + if (_cross_fade_seconds < 0) + _cross_fade_seconds = 0; + cross_fade_seconds = _cross_fade_seconds; + + idle_add(IDLE_OPTIONS); +} + +void +player_control::SetMixRampDb(float _mixramp_db) +{ + mixramp_db = _mixramp_db; + + idle_add(IDLE_OPTIONS); +} + +void +player_control::SetMixRampDelay(float _mixramp_delay_seconds) +{ + mixramp_delay_seconds = _mixramp_delay_seconds; + + idle_add(IDLE_OPTIONS); +} diff --git a/src/PlayerControl.hxx b/src/PlayerControl.hxx new file mode 100644 index 00000000..de05e17a --- /dev/null +++ b/src/PlayerControl.hxx @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYER_H +#define MPD_PLAYER_H + +#include "audio_format.h" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +#include <glib.h> + +#include <stdint.h> + +struct decoder_control; + +enum player_state { + PLAYER_STATE_STOP = 0, + PLAYER_STATE_PAUSE, + PLAYER_STATE_PLAY +}; + +enum player_command { + PLAYER_COMMAND_NONE = 0, + PLAYER_COMMAND_EXIT, + PLAYER_COMMAND_STOP, + PLAYER_COMMAND_PAUSE, + PLAYER_COMMAND_SEEK, + PLAYER_COMMAND_CLOSE_AUDIO, + + /** + * At least one audio_output.enabled flag has been modified; + * commit those changes to the output threads. + */ + PLAYER_COMMAND_UPDATE_AUDIO, + + /** player_control.next_song has been updated */ + PLAYER_COMMAND_QUEUE, + + /** + * cancel pre-decoding player_control.next_song; if the player + * has already started playing this song, it will completely + * stop + */ + PLAYER_COMMAND_CANCEL, + + /** + * Refresh status information in the #player_control struct, + * e.g. elapsed_time. + */ + PLAYER_COMMAND_REFRESH, +}; + +enum player_error { + PLAYER_ERROR_NONE = 0, + + /** + * The decoder has failed to decode the song. + */ + PLAYER_ERROR_DECODER, + + /** + * The audio output has failed. + */ + PLAYER_ERROR_OUTPUT, +}; + +struct player_status { + enum player_state state; + uint16_t bit_rate; + struct audio_format audio_format; + float total_time; + float elapsed_time; +}; + +struct player_control { + unsigned buffer_chunks; + + unsigned int buffered_before_play; + + /** the handle of the player thread, or NULL if the player + thread isn't running */ + GThread *thread; + + /** + * This lock protects #command, #state, #error. + */ + mutable Mutex mutex; + + /** + * Trigger this object after you have modified #command. + */ + Cond cond; + + /** + * This object gets signalled when the player thread has + * finished the #command. It wakes up the client that waits + * (i.e. the main thread). + */ + Cond client_cond; + + enum player_command command; + enum player_state state; + + enum player_error error_type; + + /** + * The error that occurred in the player thread. This + * attribute is only valid if #error is not + * #PLAYER_ERROR_NONE. The object must be freed when this + * object transitions back to #PLAYER_ERROR_NONE. + */ + GError *error; + + uint16_t bit_rate; + struct audio_format audio_format; + float total_time; + float elapsed_time; + + /** + * The next queued song. + * + * This is a duplicate, and must be freed when this attribute + * is cleared. + */ + struct song *next_song; + + double seek_where; + float cross_fade_seconds; + float mixramp_db; + float mixramp_delay_seconds; + double total_play_time; + + /** + * If this flag is set, then the player will be auto-paused at + * the end of the song, before the next song starts to play. + * + * This is a copy of the queue's "single" flag most of the + * time. + */ + bool border_pause; + + player_control(unsigned buffer_chunks, + unsigned buffered_before_play); + ~player_control(); + + /** + * Locks the object. + */ + void Lock() const { + mutex.lock(); + } + + /** + * Unlocks the object. + */ + void Unlock() const { + mutex.unlock(); + } + + /** + * Signals the object. The object should be locked prior to + * calling this function. + */ + void Signal() { + cond.signal(); + } + + /** + * Signals the object. The object is temporarily locked by + * this function. + */ + void LockSignal() { + Lock(); + Signal(); + Unlock(); + } + + /** + * Waits for a signal on the object. This function is only + * valid in the player thread. The object must be locked + * prior to calling this function. + */ + void Wait() { + assert(thread == g_thread_self()); + + cond.wait(mutex); + } + + /** + * Wake up the client waiting for command completion. + * + * Caller must lock the object. + */ + void ClientSignal() { + assert(thread == g_thread_self()); + + client_cond.signal(); + } + + /** + * The client calls this method to wait for command + * completion. + * + * Caller must lock the object. + */ + void ClientWait() { + assert(thread != g_thread_self()); + + client_cond.wait(mutex); + } + + /** + * @param song the song to be queued; the given instance will + * be owned and freed by the player + */ + void Play(struct song *song); + + /** + * see PLAYER_COMMAND_CANCEL + */ + void Cancel(); + + void SetPause(bool pause_flag); + + void Pause(); + + /** + * Set the player's #border_pause flag. + */ + void SetBorderPause(bool border_pause); + + void Kill(); + + gcc_pure + player_status GetStatus(); + + player_state GetState() const { + return state; + } + + /** + * Set the error. Discards any previous error condition. + * + * Caller must lock the object. + * + * @param type the error type; must not be #PLAYER_ERROR_NONE + * @param error detailed error information; must not be NULL; the + * #player_control takes over ownership of this #GError instance + */ + void SetError(player_error type, GError *error); + + void ClearError(); + + /** + * Returns the human-readable message describing the last + * error during playback, NULL if no error occurred. The + * caller has to free the returned string. + */ + char *GetErrorMessage() const; + + player_error GetErrorType() const { + return error_type; + } + + void Stop(); + + void UpdateAudio(); + + /** + * @param song the song to be queued; the given instance will be owned + * and freed by the player + */ + void EnqueueSong(struct song *song); + + /** + * Makes the player thread seek the specified song to a position. + * + * @param song the song to be queued; the given instance will be owned + * and freed by the player + * @return true on success, false on failure (e.g. if MPD isn't + * playing currently) + */ + bool Seek(struct song *song, float seek_time); + + void SetCrossFade(float cross_fade_seconds); + + float GetCrossFade() const { + return cross_fade_seconds; + } + + void SetMixRampDb(float mixramp_db); + + float GetMixRampDb() const { + return mixramp_db; + } + + void SetMixRampDelay(float mixramp_delay_seconds); + + float GetMixRampDelay() const { + return mixramp_delay_seconds; + } + + double GetTotalPlayTime() const { + return total_play_time; + } +}; + +#endif diff --git a/src/player_thread.c b/src/PlayerThread.cxx index 593788ca..599df833 100644 --- a/src/player_thread.c +++ b/src/PlayerThread.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,23 +18,23 @@ */ #include "config.h" -#include "player_thread.h" -#include "player_control.h" -#include "decoder_control.h" -#include "decoder_thread.h" -#include "output_all.h" -#include "pcm_volume.h" -#include "path.h" -#include "event_pipe.h" -#include "crossfade.h" +#include "PlayerThread.hxx" +#include "DecoderThread.hxx" +#include "DecoderControl.hxx" +#include "MusicPipe.hxx" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" #include "song.h" -#include "tag.h" -#include "pipe.h" -#include "chunk.h" -#include "idle.h" -#include "main.h" -#include "buffer.h" +#include "Main.hxx" #include "mpd_error.h" +#include "CrossFade.hxx" +#include "PlayerControl.hxx" +#include "OutputAll.hxx" +#include "tag.h" +#include "Idle.hxx" +#include "GlobalEvents.hxx" + +#include <cmath> #include <glib.h> @@ -123,6 +123,20 @@ struct player { * precisely. */ float elapsed_time; + + player(player_control *_pc, decoder_control *_dc) + :pc(_pc), dc(_dc), + buffering(false), + decoder_starting(false), + paused(false), + queued(true), + output_open(false), + song(NULL), + xfade(XFADE_UNKNOWN), + cross_fading(false), + cross_fade_chunks(0), + cross_fade_tag(NULL), + elapsed_time(0.0) {} }; static struct music_buffer *player_buffer; @@ -133,15 +147,15 @@ player_command_finished_locked(struct player_control *pc) assert(pc->command != PLAYER_COMMAND_NONE); pc->command = PLAYER_COMMAND_NONE; - g_cond_signal(main_cond); + pc->ClientSignal(); } static void player_command_finished(struct player_control *pc) { - player_lock(pc); + pc->Lock(); player_command_finished_locked(pc); - player_unlock(pc); + pc->Unlock(); } /** @@ -162,9 +176,9 @@ player_dc_start(struct player *player, struct music_pipe *pipe) if (pc->command == PLAYER_COMMAND_SEEK) start_ms += (unsigned)(pc->seek_where * 1000); - dc_start(dc, pc->next_song, - start_ms, pc->next_song->end_ms, - player_buffer, pipe); + dc->Start(song_dup_detached(pc->next_song), + start_ms, pc->next_song->end_ms, + player_buffer, pipe); } /** @@ -203,7 +217,7 @@ player_dc_stop(struct player *player) { struct decoder_control *dc = player->dc; - dc_stop(dc); + dc->Stop(); if (dc->pipe != NULL) { /* clear and free the decoder pipe */ @@ -235,16 +249,22 @@ player_wait_for_decoder(struct player *player) player->queued = false; - if (decoder_lock_has_failed(dc)) { - player_lock(pc); - pc->errored_song = dc->song; - pc->error = PLAYER_ERROR_FILE; + GError *error = dc->LockGetError(); + if (error != NULL) { + pc->Lock(); + pc->SetError(PLAYER_ERROR_DECODER, error); + + song_free(pc->next_song); pc->next_song = NULL; - player_unlock(pc); + + pc->Unlock(); return false; } + if (player->song != NULL) + song_free(player->song); + player->song = pc->next_song; player->elapsed_time = 0.0; @@ -252,7 +272,7 @@ player_wait_for_decoder(struct player *player) player_check_decoder_startup() */ player->decoder_starting = true; - player_lock(pc); + pc->Lock(); /* update player_control's song information */ pc->total_time = song_get_duration(pc->next_song); @@ -262,10 +282,10 @@ player_wait_for_decoder(struct player *player) /* clear the queued song */ pc->next_song = NULL; - player_unlock(pc); + pc->Unlock(); /* call syncPlaylistWithQueue() in the main thread */ - event_pipe_emit(PIPE_EVENT_PLAYLIST); + GlobalEvents::Emit(GlobalEvents::PLAYLIST); return true; } @@ -305,26 +325,30 @@ player_open_output(struct player *player) assert(pc->state == PLAYER_STATE_PLAY || pc->state == PLAYER_STATE_PAUSE); - if (audio_output_all_open(&player->play_audio_format, player_buffer)) { + GError *error = NULL; + if (audio_output_all_open(&player->play_audio_format, player_buffer, + &error)) { player->output_open = true; player->paused = false; - player_lock(pc); + pc->Lock(); pc->state = PLAYER_STATE_PLAY; - player_unlock(pc); + pc->Unlock(); return true; } else { + g_warning("%s", error->message); + player->output_open = false; /* pause: the user may resume playback as soon as an audio output becomes available */ player->paused = true; - player_lock(pc); - pc->error = PLAYER_ERROR_AUDIO; + pc->Lock(); + pc->SetError(PLAYER_ERROR_OUTPUT, error); pc->state = PLAYER_STATE_PAUSE; - player_unlock(pc); + pc->Unlock(); idle_add(IDLE_PLAYER); @@ -347,22 +371,22 @@ player_check_decoder_startup(struct player *player) assert(player->decoder_starting); - decoder_lock(dc); + dc->Lock(); - if (decoder_has_failed(dc)) { + GError *error = dc->GetError(); + if (error != NULL) { /* the decoder failed */ - decoder_unlock(dc); + dc->Unlock(); - player_lock(pc); - pc->errored_song = dc->song; - pc->error = PLAYER_ERROR_FILE; - player_unlock(pc); + pc->Lock(); + pc->SetError(PLAYER_ERROR_DECODER, error); + pc->Unlock(); return false; - } else if (!decoder_is_starting(dc)) { + } else if (!dc->IsStarting()) { /* the decoder is ready and ok */ - decoder_unlock(dc); + dc->Unlock(); if (player->output_open && !audio_output_all_wait(pc, 1)) @@ -370,10 +394,10 @@ player_check_decoder_startup(struct player *player) all chunks yet - wait for that */ return true; - player_lock(pc); + pc->Lock(); pc->total_time = real_song_duration(dc->song, dc->total_time); pc->audio_format = dc->in_audio_format; - player_unlock(pc); + pc->Unlock(); player->play_audio_format = dc->out_audio_format; player->decoder_starting = false; @@ -391,8 +415,8 @@ player_check_decoder_startup(struct player *player) } else { /* the decoder is not yet ready; wait some more */ - player_wait_decoder(pc, dc); - decoder_unlock(dc); + dc->WaitForDecoder(); + dc->Unlock(); return true; } @@ -431,7 +455,11 @@ player_send_silence(struct player *player) chunk->length = num_frames * frame_size; memset(chunk->data, 0, chunk->length); - if (!audio_output_all_play(chunk)) { + GError *error = NULL; + if (!audio_output_all_play(chunk, &error)) { + g_warning("%s", error->message); + g_error_free(error); + music_buffer_return(player_buffer, chunk); return false; } @@ -454,7 +482,7 @@ static bool player_seek_decoder(struct player *player) const unsigned start_ms = song->start_ms; - if (decoder_current_song(dc) != song) { + if (!dc->LockIsCurrentSong(song)) { /* the decoder is already decoding the "next" song - stop it and start the previous song again */ @@ -480,6 +508,7 @@ static bool player_seek_decoder(struct player *player) player->pipe = dc->pipe; } + song_free(pc->next_song); pc->next_song = NULL; player->queued = false; } @@ -502,7 +531,7 @@ static bool player_seek_decoder(struct player *player) if (where < 0.0) where = 0.0; - if (!dc_seek(dc, where + start_ms / 1000.0)) { + if (!dc->Seek(where + start_ms / 1000.0)) { /* decoder failure */ player_command_finished(pc); return false; @@ -538,9 +567,9 @@ static void player_process_command(struct player *player) break; case PLAYER_COMMAND_UPDATE_AUDIO: - player_unlock(pc); + pc->Unlock(); audio_output_all_enable_disable(); - player_lock(pc); + pc->Lock(); player_command_finished_locked(pc); break; @@ -554,33 +583,33 @@ static void player_process_command(struct player *player) break; case PLAYER_COMMAND_PAUSE: - player_unlock(pc); + pc->Unlock(); player->paused = !player->paused; if (player->paused) { audio_output_all_pause(); - player_lock(pc); + pc->Lock(); pc->state = PLAYER_STATE_PAUSE; } else if (!audio_format_defined(&player->play_audio_format)) { /* the decoder hasn't provided an audio format yet - don't open the audio device yet */ - player_lock(pc); + pc->Lock(); pc->state = PLAYER_STATE_PLAY; } else { player_open_output(player); - player_lock(pc); + pc->Lock(); } player_command_finished_locked(pc); break; case PLAYER_COMMAND_SEEK: - player_unlock(pc); + pc->Unlock(); player_seek_decoder(player); - player_lock(pc); + pc->Lock(); break; case PLAYER_COMMAND_CANCEL: @@ -595,11 +624,12 @@ static void player_process_command(struct player *player) if (player_dc_at_next_song(player)) { /* the decoder is already decoding the song - stop it and reset the position */ - player_unlock(pc); + pc->Unlock(); player_dc_stop(player); - player_lock(pc); + pc->Lock(); } + song_free(pc->next_song); pc->next_song = NULL; player->queued = false; player_command_finished_locked(pc); @@ -607,9 +637,9 @@ static void player_process_command(struct player *player) case PLAYER_COMMAND_REFRESH: if (player->output_open && !player->paused) { - player_unlock(pc); + pc->Unlock(); audio_output_all_check(); - player_lock(pc); + pc->Lock(); } pc->elapsed_time = audio_output_all_get_elapsed_time(); @@ -637,7 +667,7 @@ update_song_tag(struct song *song, const struct tag *new_tag) /* the main thread will update the playlist version when he receives this event */ - event_pipe_emit(PIPE_EVENT_TAG); + GlobalEvents::Emit(GlobalEvents::TAG); /* notify all clients that the tag of the current song has changed */ @@ -654,9 +684,10 @@ update_song_tag(struct song *song, const struct tag *new_tag) static bool play_chunk(struct player_control *pc, struct song *song, struct music_chunk *chunk, - const struct audio_format *format) + const struct audio_format *format, + GError **error_r) { - assert(music_chunk_check_format(chunk, format)); + assert(chunk->CheckFormat(*format)); if (chunk->tag != NULL) update_song_tag(song, chunk->tag); @@ -666,13 +697,13 @@ play_chunk(struct player_control *pc, return true; } - player_lock(pc); + pc->Lock(); pc->bit_rate = chunk->bit_rate; - player_unlock(pc); + pc->Unlock(); /* send the chunk to the audio outputs */ - if (!audio_output_all_play(chunk)) + if (!audio_output_all_play(chunk, error_r)) return false; pc->total_play_time += (double)chunk->length / @@ -729,14 +760,14 @@ play_next_chunk(struct player *player) other_chunk->tag); other_chunk->tag = NULL; - if (isnan(pc->mixramp_delay_seconds)) { + if (std::isnan(pc->mixramp_delay_seconds)) { chunk->mix_ratio = ((float)cross_fade_position) / player->cross_fade_chunks; } else { chunk->mix_ratio = nan(""); } - if (music_chunk_is_empty(other_chunk)) { + if (other_chunk->IsEmpty()) { /* the "other" chunk was a music_chunk which had only a tag, but no music data - we cannot cross-fade that; @@ -753,19 +784,19 @@ play_next_chunk(struct player *player) } else { /* there are not enough decoded chunks yet */ - decoder_lock(dc); + dc->Lock(); - if (decoder_is_idle(dc)) { + if (dc->IsIdle()) { /* the decoder isn't running, abort cross fading */ - decoder_unlock(dc); + dc->Unlock(); player->xfade = XFADE_DISABLED; } else { /* wait for the decoder */ - decoder_signal(dc); - player_wait_decoder(pc, dc); - decoder_unlock(dc); + dc->Signal(); + dc->WaitForDecoder(); + dc->Unlock(); return true; } @@ -787,20 +818,23 @@ play_next_chunk(struct player *player) /* play the current chunk */ + GError *error = NULL; if (!play_chunk(player->pc, player->song, chunk, - &player->play_audio_format)) { + &player->play_audio_format, &error)) { + g_warning("%s", error->message); + music_buffer_return(player_buffer, chunk); - player_lock(pc); + pc->Lock(); - pc->error = PLAYER_ERROR_AUDIO; + pc->SetError(PLAYER_ERROR_OUTPUT, error); /* pause: the user may resume playback as soon as an audio output becomes available */ pc->state = PLAYER_STATE_PAUSE; player->paused = true; - player_unlock(pc); + pc->Unlock(); idle_add(IDLE_PLAYER); @@ -810,12 +844,12 @@ play_next_chunk(struct player *player) /* this formula should prevent that the decoder gets woken up with each chunk; it is more efficient to make it decode a larger block at a time */ - decoder_lock(dc); - if (!decoder_is_idle(dc) && + dc->Lock(); + if (!dc->IsIdle() && music_pipe_size(dc->pipe) <= (pc->buffered_before_play + music_buffer_size(player_buffer) * 3) / 4) - decoder_signal(dc); - decoder_unlock(dc); + dc->Signal(); + dc->Unlock(); return true; } @@ -847,7 +881,7 @@ player_song_border(struct player *player) return false; struct player_control *const pc = player->pc; - player_lock(pc); + pc->Lock(); const bool border_pause = pc->border_pause; if (border_pause) { @@ -855,7 +889,7 @@ player_song_border(struct player *player) pc->state = PLAYER_STATE_PAUSE; } - player_unlock(pc); + pc->Unlock(); if (border_pause) idle_add(IDLE_PLAYER); @@ -870,37 +904,25 @@ player_song_border(struct player *player) */ static void do_play(struct player_control *pc, struct decoder_control *dc) { - struct player player = { - .pc = pc, - .dc = dc, - .buffering = true, - .decoder_starting = false, - .paused = false, - .queued = true, - .output_open = false, - .song = NULL, - .xfade = XFADE_UNKNOWN, - .cross_fading = false, - .cross_fade_chunks = 0, - .cross_fade_tag = NULL, - .elapsed_time = 0.0, - }; - - player_unlock(pc); + player player(pc, dc); + + pc->Unlock(); player.pipe = music_pipe_new(); player_dc_start(&player, player.pipe); if (!player_wait_for_decoder(&player)) { + assert(player.song == NULL); + player_dc_stop(&player); player_command_finished(pc); music_pipe_free(player.pipe); - event_pipe_emit(PIPE_EVENT_PLAYLIST); - player_lock(pc); + GlobalEvents::Emit(GlobalEvents::PLAYLIST); + pc->Lock(); return; } - player_lock(pc); + pc->Lock(); pc->state = PLAYER_STATE_PLAY; if (pc->command == PLAYER_COMMAND_SEEK) @@ -913,12 +935,12 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) if (pc->command == PLAYER_COMMAND_STOP || pc->command == PLAYER_COMMAND_EXIT || pc->command == PLAYER_COMMAND_CLOSE_AUDIO) { - player_unlock(pc); + pc->Unlock(); audio_output_all_cancel(); break; } - player_unlock(pc); + pc->Unlock(); if (player.buffering) { /* buffering at the start of the song - wait @@ -926,7 +948,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) prevent stuttering on slow machines */ if (music_pipe_size(player.pipe) < pc->buffered_before_play && - !decoder_lock_is_idle(dc)) { + !dc->LockIsIdle()) { /* not enough decoded buffer space yet */ if (!player.paused && @@ -935,11 +957,11 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) !player_send_silence(&player)) break; - decoder_lock(dc); + dc->Lock(); /* XXX race condition: check decoder again */ - player_wait_decoder(pc, dc); - decoder_unlock(dc); - player_lock(pc); + dc->WaitForDecoder(); + dc->Unlock(); + pc->Lock(); continue; } else { /* buffering is complete */ @@ -953,7 +975,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) if (!player_check_decoder_startup(&player)) break; - player_lock(pc); + pc->Lock(); continue; } @@ -965,7 +987,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) */ #endif - if (decoder_lock_is_idle(dc) && player.queued && + if (dc->LockIsIdle() && player.queued && dc->pipe == player.pipe) { /* the decoder has finished the current song; make it decode the next song */ @@ -980,7 +1002,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) !pc->border_pause && player_dc_at_next_song(&player) && player.xfade == XFADE_UNKNOWN && - !decoder_lock_is_starting(dc)) { + !dc->LockIsStarting()) { /* enable cross fading in this song? if yes, calculate how many chunks will be required for it */ @@ -1006,10 +1028,10 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) } if (player.paused) { - player_lock(pc); + pc->Lock(); if (pc->command == PLAYER_COMMAND_NONE) - player_wait(pc); + pc->Wait(); continue; } else if (!music_pipe_empty(player.pipe)) { /* at least one music chunk is ready - send it @@ -1028,7 +1050,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) if (!player_song_border(&player)) break; - } else if (decoder_lock_is_idle(dc)) { + } else if (dc->LockIsIdle()) { /* check the size of the pipe again, because the decoder thread may have added something since we last checked */ @@ -1046,7 +1068,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) break; } - player_lock(pc); + pc->Lock(); } player_dc_stop(&player); @@ -1057,33 +1079,37 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) if (player.cross_fade_tag != NULL) tag_free(player.cross_fade_tag); - player_lock(pc); + if (player.song != NULL) + song_free(player.song); + + pc->Lock(); if (player.queued) { assert(pc->next_song != NULL); + song_free(pc->next_song); pc->next_song = NULL; } pc->state = PLAYER_STATE_STOP; - player_unlock(pc); + pc->Unlock(); - event_pipe_emit(PIPE_EVENT_PLAYLIST); + GlobalEvents::Emit(GlobalEvents::PLAYLIST); - player_lock(pc); + pc->Lock(); } static gpointer player_task(gpointer arg) { - struct player_control *pc = arg; + struct player_control *pc = (struct player_control *)arg; - struct decoder_control *dc = dc_new(pc->cond); + struct decoder_control *dc = new decoder_control(); decoder_thread_start(dc); player_buffer = music_buffer_new(pc->buffer_chunks); - player_lock(pc); + pc->Lock(); while (1) { switch (pc->command) { @@ -1095,23 +1121,27 @@ player_task(gpointer arg) break; case PLAYER_COMMAND_STOP: - player_unlock(pc); + pc->Unlock(); audio_output_all_cancel(); - player_lock(pc); + pc->Lock(); /* fall through */ case PLAYER_COMMAND_PAUSE: - pc->next_song = NULL; + if (pc->next_song != NULL) { + song_free(pc->next_song); + pc->next_song = NULL; + } + player_command_finished_locked(pc); break; case PLAYER_COMMAND_CLOSE_AUDIO: - player_unlock(pc); + pc->Unlock(); audio_output_all_release(); - player_lock(pc); + pc->Lock(); player_command_finished_locked(pc); #ifndef NDEBUG @@ -1125,17 +1155,17 @@ player_task(gpointer arg) break; case PLAYER_COMMAND_UPDATE_AUDIO: - player_unlock(pc); + pc->Unlock(); audio_output_all_enable_disable(); - player_lock(pc); + pc->Lock(); player_command_finished_locked(pc); break; case PLAYER_COMMAND_EXIT: - player_unlock(pc); + pc->Unlock(); - dc_quit(dc); - dc_free(dc); + dc->Quit(); + delete dc; audio_output_all_close(); music_buffer_free(player_buffer); @@ -1143,7 +1173,11 @@ player_task(gpointer arg) return NULL; case PLAYER_COMMAND_CANCEL: - pc->next_song = NULL; + if (pc->next_song != NULL) { + song_free(pc->next_song); + pc->next_song = NULL; + } + player_command_finished_locked(pc); break; @@ -1153,7 +1187,7 @@ player_task(gpointer arg) break; case PLAYER_COMMAND_NONE: - player_wait(pc); + pc->Wait(); break; } } diff --git a/src/player_thread.h b/src/PlayerThread.hxx index 7373eb43..197669cd 100644 --- a/src/player_thread.h +++ b/src/PlayerThread.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -34,8 +34,8 @@ * #music_chunk instances around in #music_pipe objects. */ -#ifndef MPD_PLAYER_THREAD_H -#define MPD_PLAYER_THREAD_H +#ifndef MPD_PLAYER_THREAD_HXX +#define MPD_PLAYER_THREAD_HXX struct player_control; diff --git a/src/Playlist.cxx b/src/Playlist.cxx new file mode 100644 index 00000000..89bdac63 --- /dev/null +++ b/src/Playlist.cxx @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlayerControl.hxx" +#include "song.h" +#include "Idle.hxx" + +#include <glib.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "playlist" + +void +playlist::FullIncrementVersions() +{ + queue.ModifyAll(); + idle_add(IDLE_PLAYLIST); +} + +void +playlist::TagChanged() +{ + if (!playing) + return; + + assert(current >= 0); + + queue.ModifyAtOrder(current); + idle_add(IDLE_PLAYLIST); +} + +/** + * Queue a song, addressed by its order number. + */ +static void +playlist_queue_song_order(struct playlist *playlist, struct player_control *pc, + unsigned order) +{ + char *uri; + + assert(playlist->queue.IsValidOrder(order)); + + playlist->queued = order; + + struct song *song = + song_dup_detached(playlist->queue.GetOrder(order)); + + uri = song_get_uri(song); + g_debug("queue song %i:\"%s\"", playlist->queued, uri); + g_free(uri); + + pc->EnqueueSong(song); +} + +/** + * Called if the player thread has started playing the "queued" song. + */ +static void +playlist_song_started(struct playlist *playlist, struct player_control *pc) +{ + assert(pc->next_song == NULL); + assert(playlist->queued >= -1); + + /* queued song has started: copy queued to current, + and notify the clients */ + + int current = playlist->current; + playlist->current = playlist->queued; + playlist->queued = -1; + + if(playlist->queue.consume) + playlist->DeleteOrder(*pc, current); + + idle_add(IDLE_PLAYER); +} + +const struct song * +playlist::GetQueuedSong() const +{ + return playing && queued >= 0 + ? queue.GetOrder(queued) + : nullptr; +} + +void +playlist::UpdateQueuedSong(player_control &pc, const song *prev) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + assert((queued < 0) == (prev == NULL)); + + const int next_order = current >= 0 + ? queue.GetNextOrder(current) + : 0; + + if (next_order == 0 && queue.random && !queue.single) { + /* shuffle the song order again, so we get a different + order each time the playlist is played + completely */ + const unsigned current_position = + queue.OrderToPosition(current); + + queue.ShuffleOrder(); + + /* make sure that the current still points to + the current song, after the song order has been + shuffled */ + current = queue.PositionToOrder(current_position); + } + + const struct song *const next_song = next_order >= 0 + ? queue.GetOrder(next_order) + : nullptr; + + if (prev != NULL && next_song != prev) { + /* clear the currently queued song */ + pc.Cancel(); + queued = -1; + } + + if (next_order >= 0) { + if (next_song != prev) + playlist_queue_song_order(this, &pc, next_order); + else + queued = next_order; + } +} + +void +playlist::PlayOrder(player_control &pc, int order) +{ + playing = true; + queued = -1; + + struct song *song = song_dup_detached(queue.GetOrder(order)); + + char *uri = song_get_uri(song); + g_debug("play %i:\"%s\"", order, uri); + g_free(uri); + + pc.Play(song); + current = order; +} + +static void +playlist_resume_playback(struct playlist *playlist, struct player_control *pc); + +void +playlist::SyncWithPlayer(player_control &pc) +{ + if (!playing) + /* this event has reached us out of sync: we aren't + playing anymore; ignore the event */ + return; + + pc.Lock(); + const player_state pc_state = pc.GetState(); + const song *pc_next_song = pc.next_song; + pc.Unlock(); + + if (pc_state == PLAYER_STATE_STOP) + /* the player thread has stopped: check if playback + should be restarted with the next song. That can + happen if the playlist isn't filling the queue fast + enough */ + playlist_resume_playback(this, &pc); + else { + /* check if the player thread has already started + playing the queued song */ + if (pc_next_song == nullptr && queued != -1) + playlist_song_started(this, &pc); + + pc.Lock(); + pc_next_song = pc.next_song; + pc.Unlock(); + + /* make sure the queued song is always set (if + possible) */ + if (pc_next_song == nullptr && queued < 0) + UpdateQueuedSong(pc, nullptr); + } +} + +/** + * The player has stopped for some reason. Check the error, and + * decide whether to re-start playback + */ +static void +playlist_resume_playback(struct playlist *playlist, struct player_control *pc) +{ + assert(playlist->playing); + assert(pc->GetState() == PLAYER_STATE_STOP); + + const auto error = pc->GetErrorType(); + if (error == PLAYER_ERROR_NONE) + playlist->error_count = 0; + else + ++playlist->error_count; + + if ((playlist->stop_on_error && error != PLAYER_ERROR_NONE) || + error == PLAYER_ERROR_OUTPUT || + playlist->error_count >= playlist->queue.GetLength()) + /* too many errors, or critical error: stop + playback */ + playlist->Stop(*pc); + else + /* continue playback at the next song */ + playlist->PlayNext(*pc); +} + +void +playlist::SetRepeat(player_control &pc, bool status) +{ + if (status == queue.repeat) + return; + + queue.repeat = status; + + pc.SetBorderPause(queue.single && !queue.repeat); + + /* if the last song is currently being played, the "next song" + might change when repeat mode is toggled */ + UpdateQueuedSong(pc, GetQueuedSong()); + + idle_add(IDLE_OPTIONS); +} + +static void +playlist_order(struct playlist *playlist) +{ + if (playlist->current >= 0) + /* update playlist.current, order==position now */ + playlist->current = playlist->queue.OrderToPosition(playlist->current); + + playlist->queue.RestoreOrder(); +} + +void +playlist::SetSingle(player_control &pc, bool status) +{ + if (status == queue.single) + return; + + queue.single = status; + + pc.SetBorderPause(queue.single && !queue.repeat); + + /* if the last song is currently being played, the "next song" + might change when single mode is toggled */ + UpdateQueuedSong(pc, GetQueuedSong()); + + idle_add(IDLE_OPTIONS); +} + +void +playlist::SetConsume(bool status) +{ + if (status == queue.consume) + return; + + queue.consume = status; + idle_add(IDLE_OPTIONS); +} + +void +playlist::SetRandom(player_control &pc, bool status) +{ + if (status == queue.random) + return; + + const struct song *const queued_song = GetQueuedSong(); + + queue.random = status; + + if (queue.random) { + /* shuffle the queue order, but preserve current */ + + const int current_position = GetCurrentPosition(); + + queue.ShuffleOrder(); + + if (current_position >= 0) { + /* make sure the current song is the first in + the order list, so the whole rest of the + playlist is played after that */ + unsigned current_order = + queue.PositionToOrder(current_position); + queue.SwapOrders(0, current_order); + current = 0; + } else + current = -1; + } else + playlist_order(this); + + UpdateQueuedSong(pc, queued_song); + + idle_add(IDLE_OPTIONS); +} + +int +playlist::GetCurrentPosition() const +{ + return current >= 0 + ? queue.OrderToPosition(current) + : -1; +} + +int +playlist::GetNextPosition() const +{ + if (current < 0) + return -1; + + if (queue.single && queue.repeat) + return queue.OrderToPosition(current); + else if (queue.IsValidOrder(current + 1)) + return queue.OrderToPosition(current + 1); + else if (queue.repeat) + return queue.OrderToPosition(0); + + return -1; +} diff --git a/src/Playlist.hxx b/src/Playlist.hxx new file mode 100644 index 00000000..c0181332 --- /dev/null +++ b/src/Playlist.hxx @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_HXX +#define MPD_PLAYLIST_HXX + +#include "Queue.hxx" +#include "playlist_error.h" + +#include <stdbool.h> + +struct player_control; + +struct playlist { + /** + * The song queue - it contains the "real" playlist. + */ + struct queue queue; + + /** + * This value is true if the player is currently playing (or + * should be playing). + */ + bool playing; + + /** + * If true, then any error is fatal; if false, MPD will + * attempt to play the next song on non-fatal errors. During + * seeking, this flag is set. + */ + bool stop_on_error; + + /** + * Number of errors since playback was started. If this + * number exceeds the length of the playlist, MPD gives up, + * because all songs have been tried. + */ + unsigned error_count; + + /** + * The "current song pointer". This is the song which is + * played when we get the "play" command. It is also the song + * which is currently being played. + */ + int current; + + /** + * The "next" song to be played, when the current one + * finishes. The decoder thread may start decoding and + * buffering it, while the "current" song is still playing. + * + * This variable is only valid if #playing is true. + */ + int queued; + + 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 struct song *GetQueuedSong() const; + + /** + * This is the "PLAYLIST" event handler. It is invoked by the + * player thread whenever it requests a new queued song, or + * when it exits. + */ + void SyncWithPlayer(player_control &pc); + +protected: + /** + * Called by all editing methods after a modification. + * Updates the queue version and emits #IDLE_PLAYLIST. + */ + void OnModified(); + + /** + * Updates the "queued song". Calculates the next song + * according to the current one (if MPD isn't playing, it + * takes the first song), and queues this song. Clears the + * old queued song if there was one. + * + * @param prev the song which was previously queued, as + * determined by playlist_get_queued_song() + */ + void UpdateQueuedSong(player_control &pc, const song *prev); + +public: + void Clear(player_control &pc); + + void TagChanged(); + + void FullIncrementVersions(); + + enum playlist_result AppendSong(player_control &pc, + struct song *song, + unsigned *added_id=nullptr); + + /** + * Appends a local file (outside the music database) to the + * playlist. + * + * Note: the caller is responsible for checking permissions. + */ + enum playlist_result AppendFile(player_control &pc, + const char *path_utf8, + unsigned *added_id=nullptr); + + enum playlist_result AppendURI(player_control &pc, + const char *uri_utf8, + unsigned *added_id=nullptr); + +protected: + void DeleteInternal(player_control &pc, + unsigned song, const struct song **queued_p); + +public: + enum playlist_result DeletePosition(player_control &pc, + unsigned position); + + enum playlist_result DeleteOrder(player_control &pc, + unsigned order) { + return DeletePosition(pc, queue.OrderToPosition(order)); + } + + enum playlist_result DeleteId(player_control &pc, unsigned id); + + /** + * Deletes a range of songs from the playlist. + * + * @param start the position of the first song to delete + * @param end the position after the last song to delete + */ + enum playlist_result DeleteRange(player_control &pc, + unsigned start, unsigned end); + + void DeleteSong(player_control &pc, const song &song); + + void Shuffle(player_control &pc, unsigned start, unsigned end); + + enum playlist_result MoveRange(player_control &pc, + unsigned start, unsigned end, int to); + + enum playlist_result MoveId(player_control &pc, unsigned id, int to); + + enum playlist_result SwapPositions(player_control &pc, + unsigned song1, unsigned song2); + + enum playlist_result SwapIds(player_control &pc, + unsigned id1, unsigned id2); + + enum playlist_result SetPriorityRange(player_control &pc, + unsigned start_position, + unsigned end_position, + uint8_t priority); + + enum playlist_result SetPriorityId(player_control &pc, + unsigned song_id, uint8_t priority); + + void Stop(player_control &pc); + + enum playlist_result PlayPosition(player_control &pc, int position); + + void PlayOrder(player_control &pc, int order); + + enum playlist_result PlayId(player_control &pc, int id); + + void PlayNext(player_control &pc); + + void PlayPrevious(player_control &pc); + + enum playlist_result SeekSongPosition(player_control &pc, + unsigned song_position, + float seek_time); + + enum playlist_result SeekSongId(player_control &pc, + unsigned song_id, float seek_time); + + /** + * Seek within the current song. Fails if MPD is not currently + * playing. + * + * @param time the time in seconds + * @param relative if true, then the specified time is relative to the + * current position + */ + enum playlist_result SeekCurrent(player_control &pc, + float seek_time, bool relative); + + bool GetRepeat() const { + return queue.repeat; + } + + void SetRepeat(player_control &pc, bool new_value); + + bool GetRandom() const { + return queue.random; + } + + void SetRandom(player_control &pc, bool new_value); + + bool GetSingle() const { + return queue.single; + } + + void SetSingle(player_control &pc, bool new_value); + + bool GetConsume() const { + return queue.consume; + } + + void SetConsume(bool new_value); +}; + +#endif diff --git a/src/playlist_any.c b/src/PlaylistAny.cxx index 450ca593..3f6733f0 100644 --- a/src/playlist_any.c +++ b/src/PlaylistAny.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,16 +18,19 @@ */ #include "config.h" -#include "playlist_any.h" -#include "playlist_list.h" -#include "playlist_mapper.h" -#include "uri.h" +#include "PlaylistAny.hxx" +#include "PlaylistMapper.hxx" +#include "PlaylistRegistry.hxx" #include "input_stream.h" +extern "C" { +#include "uri.h" +} + #include <assert.h> static struct playlist_provider * -playlist_open_remote(const char *uri, GMutex *mutex, GCond *cond, +playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond, struct input_stream **is_r) { assert(uri_has_scheme(uri)); @@ -62,7 +65,7 @@ playlist_open_remote(const char *uri, GMutex *mutex, GCond *cond, } struct playlist_provider * -playlist_open_any(const char *uri, GMutex *mutex, GCond *cond, +playlist_open_any(const char *uri, Mutex &mutex, Cond &cond, struct input_stream **is_r) { return uri_has_scheme(uri) diff --git a/src/playlist_any.h b/src/PlaylistAny.hxx index 310913de..d69087b3 100644 --- a/src/playlist_any.h +++ b/src/PlaylistAny.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_ANY_H -#define MPD_PLAYLIST_ANY_H +#ifndef MPD_PLAYLIST_ANY_HXX +#define MPD_PLAYLIST_ANY_HXX -#include <glib.h> +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" struct playlist_provider; struct input_stream; @@ -35,7 +36,7 @@ struct input_stream; * freed */ struct playlist_provider * -playlist_open_any(const char *uri, GMutex *mutex, GCond *cond, +playlist_open_any(const char *uri, Mutex &mutex, Cond &cond, struct input_stream **is_r); #endif diff --git a/src/PlaylistCommands.cxx b/src/PlaylistCommands.cxx new file mode 100644 index 00000000..dc3b3e0d --- /dev/null +++ b/src/PlaylistCommands.cxx @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlaylistCommands.hxx" +#include "DatabasePlaylist.hxx" +#include "CommandError.hxx" +#include "PlaylistPrint.hxx" +#include "PlaylistSave.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistVector.hxx" +#include "PlaylistQueue.hxx" +#include "TimePrint.hxx" +#include "ClientInternal.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "ls.hxx" +#include "Playlist.hxx" + +extern "C" { +#include "uri.h" +} + +#include <assert.h> +#include <stdlib.h> + +static void +print_spl_list(Client *client, const PlaylistVector &list) +{ + for (const auto &i : list) { + client_printf(client, "playlist: %s\n", i.name.c_str()); + + if (i.mtime > 0) + time_print(client, "Last-Modified", i.mtime); + } +} + +enum command_return +handle_save(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + enum playlist_result result; + + result = spl_save_playlist(argv[1], &client->playlist); + return print_playlist_result(client, result); +} + +enum command_return +handle_load(Client *client, int argc, char *argv[]) +{ + unsigned start_index, end_index; + + if (argc < 3) { + start_index = 0; + end_index = G_MAXUINT; + } else if (!check_range(client, &start_index, &end_index, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result; + + result = playlist_open_into_queue(argv[1], + start_index, end_index, + &client->playlist, + client->player_control, true); + if (result != PLAYLIST_RESULT_NO_SUCH_LIST) + return print_playlist_result(client, result); + + GError *error = NULL; + if (playlist_load_spl(&client->playlist, client->player_control, + argv[1], start_index, end_index, + &error)) + return COMMAND_RETURN_OK; + + if (error->domain == playlist_quark() && + error->code == PLAYLIST_RESULT_BAD_NAME) + /* the message for BAD_NAME is confusing when the + client wants to load a playlist file from the music + directory; patch the GError object to show "no such + playlist" instead */ + error->code = PLAYLIST_RESULT_NO_SUCH_LIST; + + return print_error(client, error); +} + +enum command_return +handle_listplaylist(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + if (playlist_file_print(client, argv[1], false)) + return COMMAND_RETURN_OK; + + GError *error = NULL; + return spl_print(client, argv[1], false, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_listplaylistinfo(Client *client, + G_GNUC_UNUSED int argc, char *argv[]) +{ + if (playlist_file_print(client, argv[1], true)) + return COMMAND_RETURN_OK; + + GError *error = NULL; + return spl_print(client, argv[1], true, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_rm(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + GError *error = NULL; + return spl_delete(argv[1], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_rename(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + GError *error = NULL; + return spl_rename(argv[1], argv[2], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistdelete(Client *client, + G_GNUC_UNUSED int argc, char *argv[]) { + char *playlist = argv[1]; + unsigned from; + + if (!check_unsigned(client, &from, argv[2])) + return COMMAND_RETURN_ERROR; + + GError *error = NULL; + return spl_remove_index(playlist, from, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistmove(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + char *playlist = argv[1]; + unsigned from, to; + + if (!check_unsigned(client, &from, argv[2])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &to, argv[3])) + return COMMAND_RETURN_ERROR; + + GError *error = NULL; + return spl_move_index(playlist, from, to, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistclear(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + GError *error = NULL; + return spl_clear(argv[1], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistadd(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + char *playlist = argv[1]; + char *uri = argv[2]; + + bool success; + GError *error = NULL; + if (uri_has_scheme(uri)) { + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return COMMAND_RETURN_ERROR; + } + + success = spl_append_uri(argv[1], playlist, &error); + } else + success = search_add_to_playlist(uri, playlist, nullptr, + &error); + + if (!success && error == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "directory or file not found"); + return COMMAND_RETURN_ERROR; + } + + return success ? COMMAND_RETURN_OK : print_error(client, error); +} + +enum command_return +handle_listplaylists(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + GError *error = NULL; + const auto list = ListPlaylistFiles(&error); + if (list.empty() && error != NULL) + return print_error(client, error); + + print_spl_list(client, list); + return COMMAND_RETURN_OK; +} diff --git a/src/PlaylistCommands.hxx b/src/PlaylistCommands.hxx new file mode 100644 index 00000000..067f428b --- /dev/null +++ b/src/PlaylistCommands.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_COMMANDS_HXX +#define MPD_PLAYLIST_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_save(Client *client, int argc, char *argv[]); + +enum command_return +handle_load(Client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylist(Client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylistinfo(Client *client, int argc, char *argv[]); + +enum command_return +handle_rm(Client *client, int argc, char *argv[]); + +enum command_return +handle_rename(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistdelete(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistmove(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistclear(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistadd(Client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylists(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/PlaylistControl.cxx b/src/PlaylistControl.cxx new file mode 100644 index 00000000..3db61cc7 --- /dev/null +++ b/src/PlaylistControl.cxx @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Functions for controlling playback on the playlist level. + * + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlayerControl.hxx" +#include "song.h" + +#include <glib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "playlist" + +void +playlist::Stop(player_control &pc) +{ + if (!playing) + return; + + assert(current >= 0); + + g_debug("stop"); + pc.Stop(); + queued = -1; + playing = false; + + if (queue.random) { + /* shuffle the playlist, so the next playback will + result in a new random order */ + + unsigned current_position = queue.OrderToPosition(current); + + queue.ShuffleOrder(); + + /* make sure that "current" stays valid, and the next + "play" command plays the same song again */ + current = queue.PositionToOrder(current_position); + } +} + +enum playlist_result +playlist::PlayPosition(player_control &pc, int song) +{ + pc.ClearError(); + + unsigned i = song; + if (song == -1) { + /* play any song ("current" song, or the first song */ + + if (queue.IsEmpty()) + return PLAYLIST_RESULT_SUCCESS; + + if (playing) { + /* already playing: unpause playback, just in + case it was paused, and return */ + pc.SetPause(false); + return PLAYLIST_RESULT_SUCCESS; + } + + /* select a song: "current" song, or the first one */ + i = current >= 0 + ? current + : 0; + } else if (!queue.IsValidPosition(song)) + return PLAYLIST_RESULT_BAD_RANGE; + + if (queue.random) { + if (song >= 0) + /* "i" is currently the song position (which + would be equal to the order number in + no-random mode); convert it to a order + number, because random mode is enabled */ + i = queue.PositionToOrder(song); + + if (!playing) + current = 0; + + /* swap the new song with the previous "current" one, + so playback continues as planned */ + queue.SwapOrders(i, current); + i = current; + } + + stop_on_error = false; + error_count = 0; + + PlayOrder(pc, i); + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::PlayId(player_control &pc, int id) +{ + if (id == -1) + return PlayPosition(pc, id); + + int song = queue.IdToPosition(id); + if (song < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return PlayPosition(pc, song); +} + +void +playlist::PlayNext(player_control &pc) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + assert(queue.IsValidOrder(current)); + + const int old_current = current; + stop_on_error = false; + + /* determine the next song from the queue's order list */ + + const int next_order = queue.GetNextOrder(current); + if (next_order < 0) { + /* no song after this one: stop playback */ + Stop(pc); + + /* reset "current song" */ + current = -1; + } + else + { + if (next_order == 0 && queue.random) { + /* The queue told us that the next song is the first + song. This means we are in repeat mode. Shuffle + the queue order, so this time, the user hears the + songs in a different than before */ + assert(queue.repeat); + + queue.ShuffleOrder(); + + /* note that current and queued are + now invalid, but playlist_play_order() will + discard them anyway */ + } + + PlayOrder(pc, next_order); + } + + /* Consume mode removes each played songs. */ + if (queue.consume) + DeleteOrder(pc, old_current); +} + +void +playlist::PlayPrevious(player_control &pc) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + + int order; + if (current > 0) { + /* play the preceding song */ + order = current - 1; + } else if (queue.repeat) { + /* play the last song in "repeat" mode */ + order = queue.GetLength() - 1; + } else { + /* re-start playing the current song if it's + the first one */ + order = current; + } + + PlayOrder(pc, order); +} + +enum playlist_result +playlist::SeekSongPosition(player_control &pc, unsigned song, float seek_time) +{ + if (!queue.IsValidPosition(song)) + return PLAYLIST_RESULT_BAD_RANGE; + + const struct 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; + } + + struct song *the_song = song_dup_detached(queue.GetOrder(i)); + if (!pc.Seek(the_song, seek_time)) { + UpdateQueuedSong(pc, queued_song); + + return PLAYLIST_RESULT_NOT_PLAYING; + } + + queued = -1; + UpdateQueuedSong(pc, NULL); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::SeekSongId(player_control &pc, unsigned id, float seek_time) +{ + int song = queue.IdToPosition(id); + if (song < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return SeekSongPosition(pc, song, seek_time); +} + +enum playlist_result +playlist::SeekCurrent(player_control &pc, float seek_time, bool relative) +{ + if (!playing) + return PLAYLIST_RESULT_NOT_PLAYING; + + if (relative) { + const auto status = pc.GetStatus(); + + if (status.state != PLAYER_STATE_PLAY && + status.state != PLAYER_STATE_PAUSE) + return PLAYLIST_RESULT_NOT_PLAYING; + + seek_time += (int)status.elapsed_time; + } + + if (seek_time < 0) + seek_time = 0; + + return SeekSongPosition(pc, current, seek_time); +} diff --git a/src/playlist_database.c b/src/PlaylistDatabase.cxx index 6b9d8715..edc6a281 100644 --- a/src/playlist_database.c +++ b/src/PlaylistDatabase.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,13 @@ */ #include "config.h" -#include "playlist_database.h" -#include "playlist_vector.h" -#include "text_file.h" +#include "PlaylistDatabase.hxx" +#include "PlaylistVector.hxx" +#include "TextFile.hxx" + +extern "C" { #include "string_util.h" +} #include <string.h> #include <stdlib.h> @@ -33,27 +36,25 @@ playlist_database_quark(void) } void -playlist_vector_save(FILE *fp, const struct list_head *pv) +playlist_vector_save(FILE *fp, const PlaylistVector &pv) { - struct playlist_metadata *pm; - playlist_vector_for_each(pm, pv) + for (const PlaylistInfo &pi : pv) fprintf(fp, PLAYLIST_META_BEGIN "%s\n" "mtime: %li\n" "playlist_end\n", - pm->name, (long)pm->mtime); + pi.name.c_str(), (long)pi.mtime); } bool -playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name, - GString *buffer, GError **error_r) +playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name, + GError **error_r) { - struct playlist_metadata pm = { - .mtime = 0, - }; + PlaylistInfo pm(name, 0); + char *line, *colon; const char *value; - while ((line = read_text_line(fp, buffer)) != NULL && + while ((line = file.ReadLine()) != NULL && strcmp(line, "playlist_end") != 0) { colon = strchr(line, ':'); if (colon == NULL || colon == line) { @@ -74,6 +75,6 @@ playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name, } } - playlist_vector_update_or_add(pv, name, pm.mtime); + pv.UpdateOrInsert(std::move(pm)); return true; } diff --git a/src/playlist_database.h b/src/PlaylistDatabase.hxx index 3238fa06..a08d623f 100644 --- a/src/playlist_database.h +++ b/src/PlaylistDatabase.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,24 +17,24 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_DATABASE_H -#define MPD_PLAYLIST_DATABASE_H +#ifndef MPD_PLAYLIST_DATABASE_HXX +#define MPD_PLAYLIST_DATABASE_HXX #include "check.h" +#include "gerror.h" -#include <stdbool.h> #include <stdio.h> -#include <glib.h> #define PLAYLIST_META_BEGIN "playlist_begin: " -struct list_head; +class PlaylistVector; +class TextFile; void -playlist_vector_save(FILE *fp, const struct list_head *pv); +playlist_vector_save(FILE *fp, const PlaylistVector &pv); bool -playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name, - GString *buffer, GError **error_r); +playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name, + GError **error_r); #endif diff --git a/src/PlaylistEdit.cxx b/src/PlaylistEdit.cxx new file mode 100644 index 00000000..df38c3da --- /dev/null +++ b/src/PlaylistEdit.cxx @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Functions for editing the playlist (adding, removing, reordering + * songs in the queue). + * + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlayerControl.hxx" + +extern "C" { +#include "uri.h" +#include "song.h" +} + +#include "Idle.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +#include <stdlib.h> + +void +playlist::OnModified() +{ + queue.IncrementVersion(); + + idle_add(IDLE_PLAYLIST); +} + +void +playlist::Clear(player_control &pc) +{ + Stop(pc); + + queue.Clear(); + current = -1; + + OnModified(); +} + +enum playlist_result +playlist::AppendFile(struct player_control &pc, + const char *path_utf8, unsigned *added_id) +{ + struct song *song = song_file_load(path_utf8, NULL); + if (song == NULL) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return AppendSong(pc, song, added_id); +} + +enum playlist_result +playlist::AppendSong(struct player_control &pc, + struct song *song, unsigned *added_id) +{ + unsigned id; + + if (queue.IsFull()) + return PLAYLIST_RESULT_TOO_LARGE; + + const struct song *const queued_song = GetQueuedSong(); + + id = queue.Append(song, 0); + + if (queue.random) { + /* shuffle the new song into the list of remaining + songs to play */ + + unsigned start; + if (queued >= 0) + start = queued + 1; + else + start = current + 1; + if (start < queue.GetLength()) + queue.ShuffleOrderLast(start, queue.GetLength()); + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + if (added_id) + *added_id = id; + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::AppendURI(struct player_control &pc, + const char *uri, unsigned *added_id) +{ + g_debug("add to playlist: %s", uri); + + const Database *db = nullptr; + struct song *song; + if (uri_has_scheme(uri)) { + song = song_remote_new(uri); + } else { + db = GetDatabase(nullptr); + if (db == nullptr) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + song = db->GetSong(uri, nullptr); + if (song == nullptr) + return PLAYLIST_RESULT_NO_SUCH_SONG; + } + + enum playlist_result result = AppendSong(pc, song, added_id); + if (db != nullptr) + db->ReturnSong(song); + + return result; +} + +enum playlist_result +playlist::SwapPositions(player_control &pc, unsigned song1, unsigned song2) +{ + if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2)) + return PLAYLIST_RESULT_BAD_RANGE; + + const struct song *const queued_song = GetQueuedSong(); + + queue.SwapPositions(song1, song2); + + if (queue.random) { + /* update the queue order, so that current + still points to the current song order */ + + queue.SwapOrders(queue.PositionToOrder(song1), + queue.PositionToOrder(song2)); + } else { + /* correct the "current" song order */ + + if (current == (int)song1) + current = song2; + else if (current == (int)song2) + current = song1; + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::SwapIds(player_control &pc, unsigned id1, unsigned id2) +{ + int song1 = queue.IdToPosition(id1); + int song2 = queue.IdToPosition(id2); + + if (song1 < 0 || song2 < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return SwapPositions(pc, song1, song2); +} + +enum playlist_result +playlist::SetPriorityRange(player_control &pc, + unsigned start, unsigned end, + uint8_t priority) +{ + if (start >= GetLength()) + return PLAYLIST_RESULT_BAD_RANGE; + + if (end > GetLength()) + end = GetLength(); + + if (start >= end) + return PLAYLIST_RESULT_SUCCESS; + + /* remember "current" and "queued" */ + + const int current_position = GetCurrentPosition(); + const struct song *const queued_song = GetQueuedSong(); + + /* apply the priority changes */ + + queue.SetPriorityRange(start, end, priority, current); + + /* restore "current" and choose a new "queued" */ + + if (current_position >= 0) + current = queue.PositionToOrder(current_position); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::SetPriorityId(struct player_control &pc, + unsigned song_id, uint8_t priority) +{ + int song_position = queue.IdToPosition(song_id); + if (song_position < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return SetPriorityRange(pc, song_position, song_position + 1, + priority); + +} + +void +playlist::DeleteInternal(player_control &pc, + unsigned song, const struct song **queued_p) +{ + assert(song < GetLength()); + + unsigned songOrder = queue.PositionToOrder(song); + + if (playing && current == (int)songOrder) { + const bool paused = pc.GetState() == PLAYER_STATE_PAUSE; + + /* the current song is going to be deleted: stop the player */ + + pc.Stop(); + playing = false; + + /* see which song is going to be played instead */ + + current = queue.GetNextOrder(current); + if (current == (int)songOrder) + current = -1; + + if (current >= 0 && !paused) + /* play the song after the deleted one */ + PlayOrder(pc, current); + else + /* no songs left to play, stop playback + completely */ + Stop(pc); + + *queued_p = NULL; + } else if (current == (int)songOrder) + /* there's a "current song" but we're not playing + currently - clear "current" */ + current = -1; + + /* now do it: remove the song */ + + queue.DeletePosition(song); + + /* update the "current" and "queued" variables */ + + if (current > (int)songOrder) + current--; +} + +enum playlist_result +playlist::DeletePosition(struct player_control &pc, unsigned song) +{ + if (song >= queue.GetLength()) + return PLAYLIST_RESULT_BAD_RANGE; + + const struct song *queued_song = GetQueuedSong(); + + DeleteInternal(pc, song, &queued_song); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::DeleteRange(struct player_control &pc, unsigned start, unsigned end) +{ + if (start >= queue.GetLength()) + return PLAYLIST_RESULT_BAD_RANGE; + + if (end > queue.GetLength()) + end = queue.GetLength(); + + if (start >= end) + return PLAYLIST_RESULT_SUCCESS; + + const struct song *queued_song = GetQueuedSong(); + + do { + DeleteInternal(pc, --end, &queued_song); + } while (end != start); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::DeleteId(struct player_control &pc, unsigned id) +{ + int song = queue.IdToPosition(id); + if (song < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return DeletePosition(pc, song); +} + +void +playlist::DeleteSong(struct player_control &pc, const struct song &song) +{ + for (int i = queue.GetLength() - 1; i >= 0; --i) + // TODO: compare URI instead of pointer + if (&song == queue.Get(i)) + DeletePosition(pc, i); +} + +enum playlist_result +playlist::MoveRange(player_control &pc, unsigned start, unsigned end, int to) +{ + if (!queue.IsValidPosition(start) || !queue.IsValidPosition(end - 1)) + return PLAYLIST_RESULT_BAD_RANGE; + + if ((to >= 0 && to + end - start - 1 >= GetLength()) || + (to < 0 && unsigned(abs(to)) > GetLength())) + return PLAYLIST_RESULT_BAD_RANGE; + + if ((int)start == to) + /* nothing happens */ + return PLAYLIST_RESULT_SUCCESS; + + const struct 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 && currentSong >= 0) { + if (start <= (unsigned)currentSong && (unsigned)currentSong < end) + /* no-op, can't be moved to offset of itself */ + return PLAYLIST_RESULT_SUCCESS; + to = (currentSong + abs(to)) % GetLength(); + if (start < (unsigned)to) + to--; + } + + queue.MoveRange(start, end, to); + + if (!queue.random) { + /* update current/queued */ + if ((int)start <= current && (unsigned)current < end) + current += to - start; + else if (current >= (int)end && current <= to) + current -= end - start; + else if (current >= to && current < (int)start) + current += end - start; + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::MoveId(player_control &pc, unsigned id1, int to) +{ + int song = queue.IdToPosition(id1); + if (song < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return MoveRange(pc, song, song + 1, to); +} + +void +playlist::Shuffle(player_control &pc, unsigned start, unsigned end) +{ + if (end > GetLength()) + /* correct the "end" offset */ + end = GetLength(); + + if (start + 1 >= end) + /* needs at least two entries. */ + return; + + const struct 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/stored_playlist.c b/src/PlaylistFile.cxx index 39ba2bac..a879f70a 100644 --- a/src/stored_playlist.c +++ b/src/PlaylistFile.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,17 +18,24 @@ */ #include "config.h" -#include "stored_playlist.h" -#include "playlist_save.h" -#include "text_file.h" +#include "PlaylistFile.hxx" +#include "PlaylistSave.hxx" +#include "PlaylistInfo.hxx" +#include "PlaylistVector.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" #include "song.h" -#include "mapper.h" -#include "path.h" -#include "uri.h" -#include "database.h" -#include "idle.h" +#include "io_error.h" +#include "Mapper.hxx" +#include "TextFile.hxx" #include "conf.h" -#include "glib_compat.h" +#include "Idle.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" + +extern "C" { +#include "uri.h" +} #include <assert.h> #include <sys/types.h> @@ -76,13 +83,13 @@ spl_valid_name(const char *name_utf8) static const char * spl_map(GError **error_r) { - const char *path_fs = map_spl_path(); - if (path_fs == NULL) + const Path &path_fs = map_spl_path(); + if (path_fs.IsNull()) g_set_error_literal(error_r, playlist_quark(), PLAYLIST_RESULT_DISABLED, "Stored playlists are disabled"); - return path_fs; + return path_fs.c_str(); } static bool @@ -98,15 +105,15 @@ spl_check_name(const char *name_utf8, GError **error_r) return true; } -static char * +static Path spl_map_to_fs(const char *name_utf8, GError **error_r) { if (spl_map(error_r) == NULL || !spl_check_name(name_utf8, error_r)) - return NULL; + return Path::Null(); - char *path_fs = map_spl_utf8_to_fs(name_utf8); - if (path_fs == NULL) + Path path_fs = map_spl_utf8_to_fs(name_utf8); + if (path_fs.IsNull()) g_set_error_literal(error_r, playlist_quark(), PLAYLIST_RESULT_BAD_NAME, "Bad playlist name"); @@ -128,147 +135,115 @@ playlist_errno(GError **error_r) break; default: - g_set_error_literal(error_r, g_file_error_quark(), errno, - g_strerror(errno)); + set_error_errno(error_r); break; } } -static struct stored_playlist_info * -load_playlist_info(const char *parent_path_fs, const char *name_fs) +static bool +LoadPlaylistFileInfo(PlaylistInfo &info, + const char *parent_path_fs, const char *name_fs) { size_t name_length = strlen(name_fs); - char *path_fs, *name, *name_utf8; - int ret; - struct stat st; - struct stored_playlist_info *playlist; if (name_length < sizeof(PLAYLIST_FILE_SUFFIX) || memchr(name_fs, '\n', name_length) != NULL) - return NULL; + return false; if (!g_str_has_suffix(name_fs, PLAYLIST_FILE_SUFFIX)) - return NULL; + return false; - path_fs = g_build_filename(parent_path_fs, name_fs, NULL); - ret = stat(path_fs, &st); + char *path_fs = g_build_filename(parent_path_fs, name_fs, NULL); + struct stat st; + int ret = stat(path_fs, &st); g_free(path_fs); if (ret < 0 || !S_ISREG(st.st_mode)) - return NULL; + return false; - name = g_strndup(name_fs, - name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX)); - name_utf8 = fs_charset_to_utf8(name); + char *name = g_strndup(name_fs, + name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX)); + std::string name_utf8 = Path::ToUTF8(name); g_free(name); - if (name_utf8 == NULL) - return NULL; + if (name_utf8.empty()) + return false; - playlist = g_new(struct stored_playlist_info, 1); - playlist->name = name_utf8; - playlist->mtime = st.st_mtime; - return playlist; + info.name = std::move(name_utf8); + info.mtime = st.st_mtime; + return true; } -GPtrArray * -spl_list(GError **error_r) +PlaylistVector +ListPlaylistFiles(GError **error_r) { - const char *parent_path_fs = spl_map(error_r); - DIR *dir; - struct dirent *ent; - GPtrArray *list; - struct stored_playlist_info *playlist; + PlaylistVector list; + const char *parent_path_fs = spl_map(error_r); if (parent_path_fs == NULL) - return NULL; + return list; - dir = opendir(parent_path_fs); + DIR *dir = opendir(parent_path_fs); if (dir == NULL) { - g_set_error_literal(error_r, g_file_error_quark(), errno, - g_strerror(errno)); - return NULL; + set_error_errno(error_r); + return list; } - list = g_ptr_array_new(); - + PlaylistInfo info; + struct dirent *ent; while ((ent = readdir(dir)) != NULL) { - playlist = load_playlist_info(parent_path_fs, ent->d_name); - if (playlist != NULL) - g_ptr_array_add(list, playlist); + if (LoadPlaylistFileInfo(info, parent_path_fs, ent->d_name)) + list.push_back(std::move(info)); } closedir(dir); return list; } -void -spl_list_free(GPtrArray *list) -{ - for (unsigned i = 0; i < list->len; ++i) { - struct stored_playlist_info *playlist = - g_ptr_array_index(list, i); - g_free(playlist->name); - g_free(playlist); - } - - g_ptr_array_free(list, true); -} - static bool -spl_save(GPtrArray *list, const char *utf8path, GError **error_r) +SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path, + GError **error_r) { - FILE *file; - assert(utf8path != NULL); if (spl_map(error_r) == NULL) return false; - char *path_fs = spl_map_to_fs(utf8path, error_r); - if (path_fs == NULL) + const Path path_fs = spl_map_to_fs(utf8path, error_r); + if (path_fs.IsNull()) return false; - file = fopen(path_fs, "w"); - g_free(path_fs); + FILE *file = FOpen(path_fs, FOpenMode::WriteText); if (file == NULL) { playlist_errno(error_r); return false; } - for (unsigned i = 0; i < list->len; ++i) { - const char *uri = g_ptr_array_index(list, i); - playlist_print_uri(file, uri); - } + for (const auto &uri_utf8 : contents) + playlist_print_uri(file, uri_utf8.c_str()); fclose(file); return true; } -GPtrArray * -spl_load(const char *utf8path, GError **error_r) +PlaylistFileContents +LoadPlaylistFile(const char *utf8path, GError **error_r) { - FILE *file; - GPtrArray *list; - char *path_fs; + PlaylistFileContents contents; if (spl_map(error_r) == NULL) - return NULL; + return contents; - path_fs = spl_map_to_fs(utf8path, error_r); - if (path_fs == NULL) - return NULL; + const Path path_fs = spl_map_to_fs(utf8path, error_r); + if (path_fs.IsNull()) + return contents; - file = fopen(path_fs, "r"); - g_free(path_fs); - if (file == NULL) { + TextFile file(path_fs); + if (file.HasFailed()) { playlist_errno(error_r); - return NULL; + return contents; } - list = g_ptr_array_new(); - - GString *buffer = g_string_sized_new(1024); char *s; - while ((s = read_text_line(file, buffer)) != NULL) { + while ((s = file.ReadLine()) != NULL) { if (*s == 0 || *s == PLAYLIST_COMMENT) continue; @@ -283,80 +258,45 @@ spl_load(const char *utf8path, GError **error_r) } else s = g_strdup(s); - g_ptr_array_add(list, s); - - if (list->len >= playlist_max_length) + contents.emplace_back(s); + if (contents.size() >= playlist_max_length) break; } - fclose(file); - return list; -} - -void -spl_free(GPtrArray *list) -{ - for (unsigned i = 0; i < list->len; ++i) { - char *uri = g_ptr_array_index(list, i); - g_free(uri); - } - - g_ptr_array_free(list, true); -} - -static char * -spl_remove_index_internal(GPtrArray *list, unsigned idx) -{ - char *uri; - - assert(idx < list->len); - - uri = g_ptr_array_remove_index(list, idx); - assert(uri != NULL); - return uri; -} - -static void -spl_insert_index_internal(GPtrArray *list, unsigned idx, char *uri) -{ - assert(idx <= list->len); - - g_ptr_array_add(list, uri); - - memmove(list->pdata + idx + 1, list->pdata + idx, - (list->len - idx - 1) * sizeof(list->pdata[0])); - g_ptr_array_index(list, idx) = uri; + return contents; } bool spl_move_index(const char *utf8path, unsigned src, unsigned dest, GError **error_r) { - char *uri; - if (src == dest) /* this doesn't check whether the playlist exists, but what the hell.. */ return true; - GPtrArray *list = spl_load(utf8path, error_r); - if (list == NULL) + GError *error = nullptr; + auto contents = LoadPlaylistFile(utf8path, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); return false; + } - if (src >= list->len || dest >= list->len) { - spl_free(list); + if (src >= contents.size() || dest >= contents.size()) { g_set_error_literal(error_r, playlist_quark(), PLAYLIST_RESULT_BAD_RANGE, "Bad range"); return false; } - uri = spl_remove_index_internal(list, src); - spl_insert_index_internal(list, dest, uri); + const auto src_i = std::next(contents.begin(), src); + auto value = std::move(*src_i); + contents.erase(src_i); - bool result = spl_save(list, utf8path, error_r); + const auto dest_i = std::next(contents.begin(), dest); + contents.insert(dest_i, std::move(value)); - spl_free(list); + bool result = SavePlaylistFile(contents, utf8path, error_r); idle_add(IDLE_STORED_PLAYLIST); return result; @@ -365,17 +305,14 @@ spl_move_index(const char *utf8path, unsigned src, unsigned dest, bool spl_clear(const char *utf8path, GError **error_r) { - FILE *file; - if (spl_map(error_r) == NULL) return false; - char *path_fs = spl_map_to_fs(utf8path, error_r); - if (path_fs == NULL) + const Path path_fs = spl_map_to_fs(utf8path, error_r); + if (path_fs.IsNull()) return false; - file = fopen(path_fs, "w"); - g_free(path_fs); + FILE *file = FOpen(path_fs, FOpenMode::WriteText); if (file == NULL) { playlist_errno(error_r); return false; @@ -390,16 +327,11 @@ spl_clear(const char *utf8path, GError **error_r) bool spl_delete(const char *name_utf8, GError **error_r) { - char *path_fs; - int ret; - - path_fs = spl_map_to_fs(name_utf8, error_r); - if (path_fs == NULL) + const Path path_fs = spl_map_to_fs(name_utf8, error_r); + if (path_fs.IsNull()) return false; - ret = unlink(path_fs); - g_free(path_fs); - if (ret < 0) { + if (!RemoveFile(path_fs)) { playlist_errno(error_r); return false; } @@ -411,25 +343,23 @@ spl_delete(const char *name_utf8, GError **error_r) bool spl_remove_index(const char *utf8path, unsigned pos, GError **error_r) { - char *uri; - - GPtrArray *list = spl_load(utf8path, error_r); - if (list == NULL) + GError *error = nullptr; + auto contents = LoadPlaylistFile(utf8path, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); return false; + } - if (pos >= list->len) { - spl_free(list); + if (pos >= contents.size()) { g_set_error_literal(error_r, playlist_quark(), PLAYLIST_RESULT_BAD_RANGE, "Bad range"); return false; } - uri = spl_remove_index_internal(list, pos); - g_free(uri); - bool result = spl_save(list, utf8path, error_r); + contents.erase(std::next(contents.begin(), pos)); - spl_free(list); + bool result = SavePlaylistFile(contents, utf8path, error_r); idle_add(IDLE_STORED_PLAYLIST); return result; @@ -438,23 +368,20 @@ spl_remove_index(const char *utf8path, unsigned pos, GError **error_r) bool spl_append_song(const char *utf8path, struct song *song, GError **error_r) { - FILE *file; - struct stat st; - if (spl_map(error_r) == NULL) return false; - char *path_fs = spl_map_to_fs(utf8path, error_r); - if (path_fs == NULL) + const Path path_fs = spl_map_to_fs(utf8path, error_r); + if (path_fs.IsNull()) return false; - file = fopen(path_fs, "a"); - g_free(path_fs); + FILE *file = FOpen(path_fs, FOpenMode::AppendText); if (file == NULL) { playlist_errno(error_r); return false; } + struct stat st; if (fstat(fileno(file), &st) < 0) { playlist_errno(error_r); fclose(file); @@ -480,45 +407,45 @@ spl_append_song(const char *utf8path, struct song *song, GError **error_r) bool spl_append_uri(const char *url, const char *utf8file, GError **error_r) { - struct song *song; - if (uri_has_scheme(url)) { - song = song_remote_new(url); + struct song *song = song_remote_new(url); bool success = spl_append_song(utf8file, song, error_r); song_free(song); return success; } else { - song = db_get_song(url); - if (song == NULL) { - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_NO_SUCH_SONG, - "No such song"); + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + song *song = db->GetSong(url, error_r); + if (song == nullptr) return false; - } - return spl_append_song(utf8file, song, error_r); + bool success = spl_append_song(utf8file, song, error_r); + db->ReturnSong(song); + return success; } } static bool -spl_rename_internal(const char *from_path_fs, const char *to_path_fs, +spl_rename_internal(const Path &from_path_fs, const Path &to_path_fs, GError **error_r) { - if (!g_file_test(from_path_fs, G_FILE_TEST_IS_REGULAR)) { + if (!FileExists(from_path_fs)) { g_set_error_literal(error_r, playlist_quark(), PLAYLIST_RESULT_NO_SUCH_LIST, "No such playlist"); return false; } - if (g_file_test(to_path_fs, G_FILE_TEST_EXISTS)) { + if (FileExists(to_path_fs)) { g_set_error_literal(error_r, playlist_quark(), PLAYLIST_RESULT_LIST_EXISTS, "Playlist exists already"); return false; } - if (rename(from_path_fs, to_path_fs) < 0) { + if (!RenameFile(from_path_fs, to_path_fs)) { playlist_errno(error_r); return false; } @@ -533,20 +460,13 @@ spl_rename(const char *utf8from, const char *utf8to, GError **error_r) if (spl_map(error_r) == NULL) return false; - char *from_path_fs = spl_map_to_fs(utf8from, error_r); - if (from_path_fs == NULL) + Path from_path_fs = spl_map_to_fs(utf8from, error_r); + if (from_path_fs.IsNull()) return false; - char *to_path_fs = spl_map_to_fs(utf8to, error_r); - if (to_path_fs == NULL) { - g_free(from_path_fs); + Path to_path_fs = spl_map_to_fs(utf8to, error_r); + if (to_path_fs.IsNull()) return false; - } - - bool success = spl_rename_internal(from_path_fs, to_path_fs, error_r); - - g_free(from_path_fs); - g_free(to_path_fs); - return success; + return spl_rename_internal(from_path_fs, to_path_fs, error_r); } diff --git a/src/stored_playlist.h b/src/PlaylistFile.hxx index cfe49633..a9aeaa23 100644 --- a/src/stored_playlist.h +++ b/src/PlaylistFile.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,20 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_STORED_PLAYLIST_H -#define MPD_STORED_PLAYLIST_H +#ifndef MPD_PLAYLIST_FILE_HXX +#define MPD_PLAYLIST_FILE_HXX -#include <glib.h> -#include <stdbool.h> -#include <time.h> +#include "gerror.h" -struct song; +#include <vector> +#include <string> -struct stored_playlist_info { - char *name; +struct song; +struct PlaylistInfo; +class PlaylistVector; - time_t mtime; -}; +typedef std::vector<std::string> PlaylistFileContents; extern bool playlist_saveAbsolutePaths; @@ -40,6 +39,8 @@ extern bool playlist_saveAbsolutePaths; void spl_global_init(void); +#ifdef __cplusplus + /** * Determines whether the specified string is a valid name for a * stored playlist. @@ -51,17 +52,11 @@ spl_valid_name(const char *name_utf8); * Returns a list of stored_playlist_info struct pointers. Returns * NULL if an error occurred. */ -GPtrArray * -spl_list(GError **error_r); - -void -spl_list_free(GPtrArray *list); +PlaylistVector +ListPlaylistFiles(GError **error_r); -GPtrArray * -spl_load(const char *utf8path, GError **error_r); - -void -spl_free(GPtrArray *list); +PlaylistFileContents +LoadPlaylistFile(const char *utf8path, GError **error_r); bool spl_move_index(const char *utf8path, unsigned src, unsigned dest, @@ -86,3 +81,5 @@ bool spl_rename(const char *utf8from, const char *utf8to, GError **error_r); #endif + +#endif diff --git a/src/playlist_global.c b/src/PlaylistGlobal.cxx index 650b88bb..9dd971d3 100644 --- a/src/playlist_global.c +++ b/src/PlaylistGlobal.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,36 +23,27 @@ */ #include "config.h" -#include "playlist.h" -#include "playlist_state.h" -#include "event_pipe.h" -#include "main.h" - -struct playlist g_playlist; +#include "PlaylistGlobal.hxx" +#include "Playlist.hxx" +#include "Main.hxx" +#include "Partition.hxx" +#include "GlobalEvents.hxx" static void playlist_tag_event(void) { - playlist_tag_changed(&g_playlist); + global_partition->playlist.TagChanged(); } static void playlist_event(void) { - playlist_sync(&g_playlist, global_player_control); -} - -void -playlist_global_init(void) -{ - playlist_init(&g_playlist); - - event_pipe_register(PIPE_EVENT_TAG, playlist_tag_event); - event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event); + global_partition->playlist.SyncWithPlayer(global_partition->pc); } void -playlist_global_finish(void) +playlist_global_init() { - playlist_finish(&g_playlist); + GlobalEvents::Register(GlobalEvents::TAG, playlist_tag_event); + GlobalEvents::Register(GlobalEvents::PLAYLIST, playlist_event); } diff --git a/src/PlaylistGlobal.hxx b/src/PlaylistGlobal.hxx new file mode 100644 index 00000000..4397292d --- /dev/null +++ b/src/PlaylistGlobal.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_GLOBAL_HXX +#define MPD_PLAYLIST_GLOBAL_HXX + +void +playlist_global_init(); + +#endif diff --git a/src/PlaylistInfo.hxx b/src/PlaylistInfo.hxx new file mode 100644 index 00000000..96e4f6db --- /dev/null +++ b/src/PlaylistInfo.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_INFO_HXX +#define MPD_PLAYLIST_INFO_HXX + +#include "check.h" +#include "gcc.h" + +#include <string> + +#include <sys/time.h> + +/** + * A directory entry pointing to a playlist file. + */ +struct PlaylistInfo { + /** + * The UTF-8 encoded name of the playlist file. + */ + std::string name; + + time_t mtime; + + class CompareName { + const char *const name; + + public: + constexpr CompareName(const char *_name):name(_name) {} + + gcc_pure + bool operator()(const PlaylistInfo &pi) const { + return pi.name.compare(name) == 0; + } + }; + + PlaylistInfo() = default; + + template<typename N> + PlaylistInfo(N &&_name, time_t _mtime) + :name(std::forward<N>(_name)), mtime(_mtime) {} + + PlaylistInfo(const PlaylistInfo &other) = delete; + PlaylistInfo(PlaylistInfo &&) = default; +}; + +#endif diff --git a/src/playlist_mapper.c b/src/PlaylistMapper.cxx index 13adb80d..85f47e44 100644 --- a/src/playlist_mapper.c +++ b/src/PlaylistMapper.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,16 +18,20 @@ */ #include "config.h" -#include "playlist_mapper.h" -#include "playlist_list.h" -#include "stored_playlist.h" -#include "mapper.h" +#include "PlaylistMapper.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistRegistry.hxx" +#include "Mapper.hxx" +#include "fs/Path.hxx" + +extern "C" { #include "uri.h" +} #include <assert.h> static struct playlist_provider * -playlist_open_path(const char *path_fs, GMutex *mutex, GCond *cond, +playlist_open_path(const char *path_fs, Mutex &mutex, Cond &cond, struct input_stream **is_r) { struct playlist_provider *playlist; @@ -45,18 +49,18 @@ playlist_open_path(const char *path_fs, GMutex *mutex, GCond *cond, * Load a playlist from the configured playlist directory. */ static struct playlist_provider * -playlist_open_in_playlist_dir(const char *uri, GMutex *mutex, GCond *cond, +playlist_open_in_playlist_dir(const char *uri, Mutex &mutex, Cond &cond, struct input_stream **is_r) { char *path_fs; assert(spl_valid_name(uri)); - const char *playlist_directory_fs = map_spl_path(); - if (playlist_directory_fs == NULL) + const Path &playlist_directory_fs = map_spl_path(); + if (playlist_directory_fs.IsNull()) return NULL; - path_fs = g_build_filename(playlist_directory_fs, uri, NULL); + path_fs = g_build_filename(playlist_directory_fs.c_str(), uri, NULL); struct playlist_provider *playlist = playlist_open_path(path_fs, mutex, cond, is_r); @@ -69,26 +73,20 @@ playlist_open_in_playlist_dir(const char *uri, GMutex *mutex, GCond *cond, * Load a playlist from the configured music directory. */ static struct playlist_provider * -playlist_open_in_music_dir(const char *uri, GMutex *mutex, GCond *cond, +playlist_open_in_music_dir(const char *uri, Mutex &mutex, Cond &cond, struct input_stream **is_r) { - char *path_fs; - assert(uri_safe_local(uri)); - path_fs = map_uri_fs(uri); - if (path_fs == NULL) + Path path = map_uri_fs(uri); + if (path.IsNull()) return NULL; - struct playlist_provider *playlist = - playlist_open_path(path_fs, mutex, cond, is_r); - g_free(path_fs); - - return playlist; + return playlist_open_path(path.c_str(), mutex, cond, is_r); } struct playlist_provider * -playlist_mapper_open(const char *uri, GMutex *mutex, GCond *cond, +playlist_mapper_open(const char *uri, Mutex &mutex, Cond &cond, struct input_stream **is_r) { struct playlist_provider *playlist; diff --git a/src/playlist_mapper.h b/src/PlaylistMapper.hxx index 9a7187d9..abfdb548 100644 --- a/src/playlist_mapper.h +++ b/src/PlaylistMapper.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_MAPPER_H -#define MPD_PLAYLIST_MAPPER_H +#ifndef MPD_PLAYLIST_MAPPER_HXX +#define MPD_PLAYLIST_MAPPER_HXX -#include <glib.h> +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" struct input_stream; @@ -33,7 +34,7 @@ struct input_stream; * freed */ struct playlist_provider * -playlist_mapper_open(const char *uri, GMutex *mutex, GCond *cond, +playlist_mapper_open(const char *uri, Mutex &mutex, Cond &cond, struct input_stream **is_r); #endif diff --git a/src/playlist_plugin.h b/src/PlaylistPlugin.hxx index a27f651c..d422106b 100644 --- a/src/playlist_plugin.h +++ b/src/PlaylistPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,12 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_PLUGIN_H +#ifndef MPD_PLAYLIST_PLUGIN_HXX +#define MPD_PLAYLIST_PLUGIN_HXX -#include <glib.h> +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" -#include <stdbool.h> #include <stddef.h> struct config_param; @@ -67,7 +67,7 @@ struct playlist_plugin { * either matched one of the schemes or one of the suffixes. */ struct playlist_provider *(*open_uri)(const char *uri, - GMutex *mutex, GCond *cond); + Mutex &mutex, Cond &cond); /** * Opens the playlist in the specified input stream. It has @@ -114,7 +114,7 @@ playlist_plugin_finish(const struct playlist_plugin *plugin) static inline struct playlist_provider * playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri, - GMutex *mutex, GCond *cond) + Mutex &mutex, Cond &cond) { return plugin->open_uri(uri, mutex, cond); } diff --git a/src/playlist_print.c b/src/PlaylistPrint.cxx index 59c42f96..e79e8773 100644 --- a/src/playlist_print.c +++ b/src/PlaylistPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,37 +18,41 @@ */ #include "config.h" -#include "playlist_print.h" -#include "playlist_list.h" -#include "playlist_plugin.h" -#include "playlist_any.h" -#include "playlist_song.h" -#include "playlist.h" -#include "queue_print.h" -#include "stored_playlist.h" -#include "song_print.h" -#include "song.h" -#include "database.h" -#include "client.h" +#include "PlaylistPrint.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistAny.hxx" +#include "PlaylistSong.hxx" +#include "Playlist.hxx" +#include "PlaylistRegistry.hxx" +#include "PlaylistPlugin.hxx" +#include "QueuePrint.hxx" +#include "SongPrint.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "Client.hxx" #include "input_stream.h" +extern "C" { +#include "song.h" +} + void -playlist_print_uris(struct client *client, const struct playlist *playlist) +playlist_print_uris(Client *client, const struct playlist *playlist) { const struct queue *queue = &playlist->queue; - queue_print_uris(client, queue, 0, queue_length(queue)); + queue_print_uris(client, queue, 0, queue->GetLength()); } bool -playlist_print_info(struct client *client, const struct playlist *playlist, +playlist_print_info(Client *client, const struct playlist *playlist, unsigned start, unsigned end) { const struct queue *queue = &playlist->queue; - if (end > queue_length(queue)) + if (end > queue->GetLength()) /* correct the "end" offset */ - end = queue_length(queue); + end = queue->GetLength(); if (start > end) /* an invalid "start" offset is fatal */ @@ -59,13 +63,13 @@ playlist_print_info(struct client *client, const struct playlist *playlist, } bool -playlist_print_id(struct client *client, const struct playlist *playlist, +playlist_print_id(Client *client, const struct playlist *playlist, unsigned id) { const struct queue *queue = &playlist->queue; int position; - position = queue_id_to_position(queue, id); + position = queue->IdToPosition(id); if (position < 0) /* no such song */ return false; @@ -74,10 +78,9 @@ playlist_print_id(struct client *client, const struct playlist *playlist, } bool -playlist_print_current(struct client *client, const struct playlist *playlist) +playlist_print_current(Client *client, const struct playlist *playlist) { - int current_position = playlist_get_current_song(playlist); - + int current_position = playlist->GetCurrentPosition(); if (current_position < 0) return false; @@ -87,21 +90,14 @@ playlist_print_current(struct client *client, const struct playlist *playlist) } void -playlist_print_find(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list) +playlist_print_find(Client *client, const struct playlist *playlist, + const SongFilter &filter) { - queue_find(client, &playlist->queue, list); + queue_find(client, &playlist->queue, filter); } void -playlist_print_search(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list) -{ - queue_search(client, &playlist->queue, list); -} - -void -playlist_print_changes_info(struct client *client, +playlist_print_changes_info(Client *client, const struct playlist *playlist, uint32_t version) { @@ -109,46 +105,51 @@ playlist_print_changes_info(struct client *client, } void -playlist_print_changes_position(struct client *client, +playlist_print_changes_position(Client *client, const struct playlist *playlist, uint32_t version) { queue_print_changes_position(client, &playlist->queue, version); } +static bool +PrintSongDetails(Client *client, const char *uri_utf8) +{ + const Database *db = GetDatabase(nullptr); + if (db == nullptr) + return false; + + song *song = db->GetSong(uri_utf8, nullptr); + if (song == nullptr) + return false; + + song_print_info(client, song); + db->ReturnSong(song); + return true; +} + bool -spl_print(struct client *client, const char *name_utf8, bool detail, +spl_print(Client *client, const char *name_utf8, bool detail, GError **error_r) { - GPtrArray *list; - - list = spl_load(name_utf8, error_r); - if (list == NULL) + GError *error = NULL; + PlaylistFileContents contents = LoadPlaylistFile(name_utf8, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); return false; + } - for (unsigned i = 0; i < list->len; ++i) { - const char *temp = g_ptr_array_index(list, i); - bool wrote = false; - - if (detail) { - struct song *song = db_get_song(temp); - if (song) { - song_print_info(client, song); - wrote = true; - } - } - - if (!wrote) { - client_printf(client, SONG_FILE "%s\n", temp); - } + for (const auto &uri_utf8 : contents) { + if (!detail || !PrintSongDetails(client, uri_utf8.c_str())) + client_printf(client, SONG_FILE "%s\n", + uri_utf8.c_str()); } - spl_free(list); return true; } static void -playlist_provider_print(struct client *client, const char *uri, +playlist_provider_print(Client *client, const char *uri, struct playlist_provider *playlist, bool detail) { struct song *song; @@ -164,27 +165,23 @@ playlist_provider_print(struct client *client, const char *uri, else song_print_uri(client, song); - if (!song_in_database(song)) - song_free(song); + song_free(song); } g_free(base_uri); } bool -playlist_file_print(struct client *client, const char *uri, bool detail) +playlist_file_print(Client *client, const char *uri, bool detail) { - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); + Mutex mutex; + Cond cond; struct input_stream *is; struct playlist_provider *playlist = playlist_open_any(uri, mutex, cond, &is); - if (playlist == NULL) { - g_cond_free(cond); - g_mutex_free(mutex); + if (playlist == NULL) return false; - } playlist_provider_print(client, uri, playlist, detail); playlist_plugin_close(playlist); @@ -192,8 +189,5 @@ playlist_file_print(struct client *client, const char *uri, bool detail) if (is != NULL) input_stream_close(is); - g_cond_free(cond); - g_mutex_free(mutex); - return true; } diff --git a/src/playlist_print.h b/src/PlaylistPrint.hxx index d4f1911d..16bee9b8 100644 --- a/src/playlist_print.h +++ b/src/PlaylistPrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,22 +17,22 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef PLAYLIST_PRINT_H -#define PLAYLIST_PRINT_H +#ifndef MPD_PLAYLIST_PRINT_HXX +#define MPD_PLAYLIST_PRINT_HXX + +#include "gerror.h" -#include <glib.h> -#include <stdbool.h> #include <stdint.h> -struct client; struct playlist; -struct locate_item_list; +class SongFilter; +class Client; /** * Sends the whole playlist to the client, song URIs only. */ void -playlist_print_uris(struct client *client, const struct playlist *playlist); +playlist_print_uris(Client *client, const struct playlist *playlist); /** * Sends a range of the playlist to the client, including all known @@ -41,7 +41,7 @@ playlist_print_uris(struct client *client, const struct playlist *playlist); * This function however fails when the start offset is invalid. */ bool -playlist_print_info(struct client *client, const struct playlist *playlist, +playlist_print_info(Client *client, const struct playlist *playlist, unsigned start, unsigned end); /** @@ -50,7 +50,7 @@ playlist_print_info(struct client *client, const struct playlist *playlist, * @return true on suite, false if there is no such song */ bool -playlist_print_id(struct client *client, const struct playlist *playlist, +playlist_print_id(Client *client, const struct playlist *playlist, unsigned id); /** @@ -59,27 +59,20 @@ playlist_print_id(struct client *client, const struct playlist *playlist, * @return true on success, false if there is no current song */ bool -playlist_print_current(struct client *client, const struct playlist *playlist); +playlist_print_current(Client *client, const struct playlist *playlist); /** * Find songs in the playlist. */ void -playlist_print_find(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list); - -/** - * Search for songs in the playlist. - */ -void -playlist_print_search(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list); +playlist_print_find(Client *client, const struct playlist *playlist, + const SongFilter &filter); /** * Print detailed changes since the specified playlist version. */ void -playlist_print_changes_info(struct client *client, +playlist_print_changes_info(Client *client, const struct playlist *playlist, uint32_t version); @@ -87,7 +80,7 @@ playlist_print_changes_info(struct client *client, * Print changes since the specified playlist version, position only. */ void -playlist_print_changes_position(struct client *client, +playlist_print_changes_position(Client *client, const struct playlist *playlist, uint32_t version); @@ -100,7 +93,7 @@ playlist_print_changes_position(struct client *client, * @return true on success, false if the playlist does not exist */ bool -spl_print(struct client *client, const char *name_utf8, bool detail, +spl_print(Client *client, const char *name_utf8, bool detail, GError **error_r); /** @@ -112,6 +105,6 @@ spl_print(struct client *client, const char *name_utf8, bool detail, * @return true on success, false if the playlist does not exist */ bool -playlist_file_print(struct client *client, const char *uri, bool detail); +playlist_file_print(Client *client, const char *uri, bool detail); #endif diff --git a/src/playlist_queue.c b/src/PlaylistQueue.cxx index aada9498..c52f49a9 100644 --- a/src/playlist_queue.c +++ b/src/PlaylistQueue.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,17 @@ */ #include "config.h" -#include "playlist_queue.h" -#include "playlist_plugin.h" -#include "playlist_any.h" -#include "playlist_song.h" -#include "playlist.h" -#include "song.h" +#include "PlaylistQueue.hxx" +#include "PlaylistPlugin.hxx" +#include "PlaylistAny.hxx" +#include "PlaylistSong.hxx" +#include "Playlist.hxx" #include "input_stream.h" +extern "C" { +#include "song.h" +} + enum playlist_result playlist_load_into_queue(const char *uri, struct playlist_provider *source, unsigned start_index, unsigned end_index, @@ -41,8 +44,7 @@ playlist_load_into_queue(const char *uri, struct playlist_provider *source, ++i) { if (i < start_index) { /* skip songs before the start index */ - if (!song_in_database(song)) - song_free(song); + song_free(song); continue; } @@ -50,10 +52,9 @@ playlist_load_into_queue(const char *uri, struct playlist_provider *source, if (song == NULL) continue; - result = playlist_append_song(dest, pc, song, NULL); + result = dest->AppendSong(*pc, song); + song_free(song); if (result != PLAYLIST_RESULT_SUCCESS) { - if (!song_in_database(song)) - song_free(song); g_free(base_uri); return result; } @@ -70,17 +71,14 @@ playlist_open_into_queue(const char *uri, struct playlist *dest, struct player_control *pc, bool secure) { - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); + Mutex mutex; + Cond cond; struct input_stream *is; struct playlist_provider *playlist = playlist_open_any(uri, mutex, cond, &is); - if (playlist == NULL) { - g_cond_free(cond); - g_mutex_free(mutex); + if (playlist == NULL) return PLAYLIST_RESULT_NO_SUCH_LIST; - } enum playlist_result result = playlist_load_into_queue(uri, playlist, start_index, end_index, @@ -90,8 +88,5 @@ playlist_open_into_queue(const char *uri, if (is != NULL) input_stream_close(is); - g_cond_free(cond); - g_mutex_free(mutex); - return result; } diff --git a/src/playlist_queue.h b/src/PlaylistQueue.hxx index 24a851aa..cda77c81 100644 --- a/src/playlist_queue.h +++ b/src/PlaylistQueue.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,13 +21,11 @@ * \brief Glue between playlist plugin and the play queue */ -#ifndef MPD_PLAYLIST_QUEUE_H -#define MPD_PLAYLIST_QUEUE_H +#ifndef MPD_PLAYLIST_QUEUE_HXX +#define MPD_PLAYLIST_QUEUE_HXX #include "playlist_error.h" -#include <stdbool.h> - struct playlist_provider; struct playlist; struct player_control; diff --git a/src/playlist_list.c b/src/PlaylistRegistry.cxx index 68d24fe4..6dc94034 100644 --- a/src/playlist_list.c +++ b/src/PlaylistRegistry.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,21 +18,25 @@ */ #include "config.h" -#include "playlist_list.h" -#include "playlist_plugin.h" -#include "playlist/extm3u_playlist_plugin.h" -#include "playlist/m3u_playlist_plugin.h" -#include "playlist/xspf_playlist_plugin.h" -#include "playlist/lastfm_playlist_plugin.h" -#include "playlist/despotify_playlist_plugin.h" -#include "playlist/soundcloud_playlist_plugin.h" -#include "playlist/pls_playlist_plugin.h" -#include "playlist/asx_playlist_plugin.h" -#include "playlist/rss_playlist_plugin.h" -#include "playlist/cue_playlist_plugin.h" -#include "playlist/embcue_playlist_plugin.h" +#include "PlaylistRegistry.hxx" +#include "PlaylistPlugin.hxx" +#include "playlist/ExtM3uPlaylistPlugin.hxx" +#include "playlist/M3uPlaylistPlugin.hxx" +#include "playlist/XspfPlaylistPlugin.hxx" +#include "playlist/LastFMPlaylistPlugin.hxx" +#include "playlist/DespotifyPlaylistPlugin.hxx" +#include "playlist/SoundCloudPlaylistPlugin.hxx" +#include "playlist/PlsPlaylistPlugin.hxx" +#include "playlist/AsxPlaylistPlugin.hxx" +#include "playlist/RssPlaylistPlugin.hxx" +#include "playlist/CuePlaylistPlugin.hxx" +#include "playlist/EmbeddedCuePlaylistPlugin.hxx" #include "input_stream.h" + +extern "C" { #include "uri.h" +} + #include "string_util.h" #include "conf.h" #include "mpd_error.h" @@ -121,7 +125,7 @@ playlist_list_global_finish(void) } static struct playlist_provider * -playlist_list_open_uri_scheme(const char *uri, GMutex *mutex, GCond *cond, +playlist_list_open_uri_scheme(const char *uri, Mutex &mutex, Cond &cond, bool *tried) { char *scheme; @@ -155,7 +159,7 @@ playlist_list_open_uri_scheme(const char *uri, GMutex *mutex, GCond *cond, } static struct playlist_provider * -playlist_list_open_uri_suffix(const char *uri, GMutex *mutex, GCond *cond, +playlist_list_open_uri_suffix(const char *uri, Mutex &mutex, Cond &cond, const bool *tried) { const char *suffix; @@ -184,7 +188,7 @@ playlist_list_open_uri_suffix(const char *uri, GMutex *mutex, GCond *cond, } struct playlist_provider * -playlist_list_open_uri(const char *uri, GMutex *mutex, GCond *cond) +playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond) { struct playlist_provider *playlist; /** this array tracks which plugins have already been tried by @@ -229,19 +233,19 @@ playlist_list_open_stream_mime2(struct input_stream *is, const char *mime) } static struct playlist_provider * -playlist_list_open_stream_mime(struct input_stream *is) +playlist_list_open_stream_mime(struct input_stream *is, const char *full_mime) { - assert(is->mime != NULL); + assert(full_mime != NULL); - const char *semicolon = strchr(is->mime, ';'); + const char *semicolon = strchr(full_mime, ';'); if (semicolon == NULL) - return playlist_list_open_stream_mime2(is, is->mime); + return playlist_list_open_stream_mime2(is, full_mime); - if (semicolon == is->mime) + if (semicolon == full_mime) return NULL; /* probe only the portion before the semicolon*/ - char *mime = g_strndup(is->mime, semicolon - is->mime); + char *mime = g_strndup(full_mime, semicolon - full_mime); struct playlist_provider *playlist = playlist_list_open_stream_mime2(is, mime); g_free(mime); @@ -281,8 +285,9 @@ playlist_list_open_stream(struct input_stream *is, const char *uri) input_stream_lock_wait_ready(is); - if (is->mime != NULL) { - playlist = playlist_list_open_stream_mime(is); + const char *const mime = input_stream_get_mime_type(is); + if (mime != NULL) { + playlist = playlist_list_open_stream_mime(is, mime); if (playlist != NULL) return playlist; } @@ -312,7 +317,7 @@ playlist_suffix_supported(const char *suffix) } struct playlist_provider * -playlist_list_open_path(const char *path_fs, GMutex *mutex, GCond *cond, +playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond, struct input_stream **is_r) { GError *error = NULL; diff --git a/src/playlist_list.h b/src/PlaylistRegistry.hxx index c3967d5a..7c34c156 100644 --- a/src/playlist_list.h +++ b/src/PlaylistRegistry.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,12 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_LIST_H -#define MPD_PLAYLIST_LIST_H +#ifndef MPD_PLAYLIST_REGISTRY_HXX +#define MPD_PLAYLIST_REGISTRY_HXX -#include <glib.h> - -#include <stdbool.h> +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" struct playlist_provider; struct input_stream; @@ -51,7 +50,7 @@ playlist_list_global_finish(void); * Opens a playlist by its URI. */ struct playlist_provider * -playlist_list_open_uri(const char *uri, GMutex *mutex, GCond *cond); +playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond); /** * Opens a playlist from an input stream. @@ -79,7 +78,7 @@ playlist_suffix_supported(const char *suffix); * @return a playlist, or NULL on error */ struct playlist_provider * -playlist_list_open_path(const char *path_fs, GMutex *mutex, GCond *cond, +playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond, struct input_stream **is_r); #endif diff --git a/src/playlist_save.c b/src/PlaylistSave.cxx index 334159e0..6ca6740f 100644 --- a/src/playlist_save.c +++ b/src/PlaylistSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,17 +18,18 @@ */ #include "config.h" -#include "playlist_save.h" -#include "playlist.h" -#include "stored_playlist.h" -#include "queue.h" +#include "PlaylistSave.hxx" +#include "PlaylistFile.hxx" +#include "Playlist.hxx" #include "song.h" -#include "mapper.h" -#include "path.h" +#include "Mapper.hxx" +#include "Idle.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" + +extern "C" { #include "uri.h" -#include "database.h" -#include "idle.h" -#include "glib_compat.h" +} #include <glib.h> @@ -36,68 +37,54 @@ void playlist_print_song(FILE *file, const struct song *song) { if (playlist_saveAbsolutePaths && song_in_database(song)) { - char *path = map_song_fs(song); - if (path != NULL) { - fprintf(file, "%s\n", path); - g_free(path); - } + const Path path = map_song_fs(song); + if (!path.IsNull()) + fprintf(file, "%s\n", path.c_str()); } else { - char *uri = song_get_uri(song), *uri_fs; - - uri_fs = utf8_to_fs_charset(uri); + char *uri = song_get_uri(song); + const Path uri_fs = Path::FromUTF8(uri); g_free(uri); - fprintf(file, "%s\n", uri_fs); - g_free(uri_fs); + if (!uri_fs.IsNull()) + fprintf(file, "%s\n", uri_fs.c_str()); } } void playlist_print_uri(FILE *file, const char *uri) { - char *s; + Path path = playlist_saveAbsolutePaths && !uri_has_scheme(uri) && + !g_path_is_absolute(uri) + ? map_uri_fs(uri) + : Path::FromUTF8(uri); - if (playlist_saveAbsolutePaths && !uri_has_scheme(uri) && - !g_path_is_absolute(uri)) - s = map_uri_fs(uri); - else - s = utf8_to_fs_charset(uri); - - if (s != NULL) { - fprintf(file, "%s\n", s); - g_free(s); - } + if (!path.IsNull()) + fprintf(file, "%s\n", path.c_str()); } enum playlist_result spl_save_queue(const char *name_utf8, const struct queue *queue) { - char *path_fs; - FILE *file; - - if (map_spl_path() == NULL) + if (map_spl_path().IsNull()) return PLAYLIST_RESULT_DISABLED; if (!spl_valid_name(name_utf8)) return PLAYLIST_RESULT_BAD_NAME; - path_fs = map_spl_utf8_to_fs(name_utf8); - if (path_fs == NULL) + const Path path_fs = map_spl_utf8_to_fs(name_utf8); + if (path_fs.IsNull()) return PLAYLIST_RESULT_BAD_NAME; - if (g_file_test(path_fs, G_FILE_TEST_EXISTS)) { - g_free(path_fs); + if (FileExists(path_fs)) return PLAYLIST_RESULT_LIST_EXISTS; - } - file = fopen(path_fs, "w"); - g_free(path_fs); + FILE *file = FOpen(path_fs, FOpenMode::WriteText); if (file == NULL) return PLAYLIST_RESULT_ERRNO; - for (unsigned i = 0; i < queue_length(queue); i++) - playlist_print_song(file, queue_get(queue, i)); + for (unsigned i = 0; i < queue->GetLength(); i++) + playlist_print_song(file, queue->Get(i)); fclose(file); @@ -117,34 +104,35 @@ playlist_load_spl(struct playlist *playlist, struct player_control *pc, unsigned start_index, unsigned end_index, GError **error_r) { - GPtrArray *list; - - list = spl_load(name_utf8, error_r); - if (list == NULL) + GError *error = NULL; + PlaylistFileContents contents = LoadPlaylistFile(name_utf8, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); return false; + } - if (list->len < end_index) - end_index = list->len; + if (end_index > contents.size()) + end_index = contents.size(); for (unsigned i = start_index; i < end_index; ++i) { - const char *temp = g_ptr_array_index(list, i); - if ((playlist_append_uri(playlist, pc, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { + const auto &uri_utf8 = contents[i]; + + if ((playlist->AppendURI(*pc, uri_utf8.c_str())) != PLAYLIST_RESULT_SUCCESS) { /* for windows compatibility, convert slashes */ - char *temp2 = g_strdup(temp); + char *temp2 = g_strdup(uri_utf8.c_str()); char *p = temp2; while (*p) { if (*p == '\\') *p = '/'; p++; } - if ((playlist_append_uri(playlist, pc, temp2, - NULL)) != PLAYLIST_RESULT_SUCCESS) { + + if (playlist->AppendURI(*pc, temp2) != PLAYLIST_RESULT_SUCCESS) g_warning("can't add file \"%s\"", temp2); - } + g_free(temp2); } } - spl_free(list); return true; } diff --git a/src/playlist_save.h b/src/PlaylistSave.hxx index a6c31a9a..ff5f0c49 100644 --- a/src/playlist_save.h +++ b/src/PlaylistSave.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,7 +22,6 @@ #include "playlist_error.h" -#include <stdbool.h> #include <stdio.h> struct song; diff --git a/src/playlist_song.c b/src/PlaylistSong.cxx index a3d9ab4d..4df4d22d 100644 --- a/src/playlist_song.c +++ b/src/PlaylistSong.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,20 @@ */ #include "config.h" -#include "playlist_song.h" -#include "database.h" -#include "mapper.h" +#include "PlaylistSong.hxx" +#include "Mapper.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" +#include "ls.hxx" +#include "tag.h" +#include "fs/Path.hxx" + +extern "C" { #include "song.h" #include "uri.h" -#include "path.h" -#include "ls.h" -#include "tag.h" +} + +#include <glib.h> #include <assert.h> #include <string.h> @@ -59,18 +65,15 @@ apply_song_metadata(struct song *dest, const struct song *src) return dest; if (song_in_database(dest)) { - char *path_fs = map_song_fs(dest); - if (path_fs == NULL) + const Path &path_fs = map_song_fs(dest); + if (path_fs.IsNull()) return dest; - char *path_utf8 = fs_charset_to_utf8(path_fs); - if (path_utf8 != NULL) - g_free(path_fs); - else - path_utf8 = path_fs; + std::string path_utf8 = path_fs.ToUTF8(); + if (path_utf8.empty()) + path_utf8 = path_fs.c_str(); - tmp = song_file_new(path_utf8, NULL); - g_free(path_utf8); + tmp = song_file_new(path_utf8.c_str(), NULL); merge_song_metadata(tmp, dest, src); } else { @@ -86,9 +89,7 @@ apply_song_metadata(struct song *dest, const struct song *src) (e.g. last track on a CUE file); fix it up here */ tmp->tag->time = dest->tag->time - src->start_ms / 1000; - if (!song_in_database(dest)) - song_free(dest); - + song_free(dest); return tmp; } @@ -104,10 +105,17 @@ playlist_check_load_song(const struct song *song, const char *uri, bool secure) if (dest == NULL) return NULL; } else { - dest = db_get_song(uri); - if (dest == NULL) + const Database *db = GetDatabase(nullptr); + if (db == nullptr) + return nullptr; + + struct song *tmp = db->GetSong(uri, nullptr); + if (tmp == NULL) /* not found in database */ return NULL; + + dest = song_dup_detached(tmp); + db->ReturnSong(tmp); } return apply_song_metadata(dest, song); diff --git a/src/playlist_song.h b/src/PlaylistSong.hxx index ea878691..117ee133 100644 --- a/src/playlist_song.h +++ b/src/PlaylistSong.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_SONG_H -#define MPD_PLAYLIST_SONG_H - -#include <stdbool.h> +#ifndef MPD_PLAYLIST_SONG_HXX +#define MPD_PLAYLIST_SONG_HXX /** * Verifies the song, returns NULL if it is unsafe. Translate the diff --git a/src/playlist_state.c b/src/PlaylistState.cxx index 4aa2c2c9..d03de0a1 100644 --- a/src/playlist_state.c +++ b/src/PlaylistState.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,12 +23,11 @@ */ #include "config.h" -#include "playlist_state.h" -#include "playlist.h" -#include "player_control.h" -#include "queue_save.h" -#include "path.h" -#include "text_file.h" +#include "PlaylistState.hxx" +#include "Playlist.hxx" +#include "QueueSave.hxx" +#include "TextFile.hxx" +#include "PlayerControl.hxx" #include "conf.h" #include <string.h> @@ -57,9 +56,7 @@ void playlist_state_save(FILE *fp, const struct playlist *playlist, struct player_control *pc) { - struct player_status player_status; - - pc_get_status(pc, &player_status); + const auto player_status = pc->GetStatus(); fputs(PLAYLIST_STATE_FILE_STATE, fp); @@ -72,8 +69,7 @@ playlist_state_save(FILE *fp, const struct playlist *playlist, fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp); } fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", - queue_order_to_position(&playlist->queue, - playlist->current)); + playlist->queue.OrderToPosition(playlist->current)); fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n", (int)player_status.elapsed_time); } else { @@ -81,8 +77,7 @@ playlist_state_save(FILE *fp, const struct playlist *playlist, if (playlist->current >= 0) fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", - queue_order_to_position(&playlist->queue, - playlist->current)); + playlist->queue.OrderToPosition(playlist->current)); } fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist->queue.random); @@ -91,29 +86,29 @@ playlist_state_save(FILE *fp, const struct playlist *playlist, fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n", playlist->queue.consume); fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n", - (int)(pc_get_cross_fade(pc))); + (int)pc->GetCrossFade()); fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", - pc_get_mixramp_db(pc)); + pc->GetMixRampDb()); fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", - pc_get_mixramp_delay(pc)); + 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(FILE *fp, GString *buffer, struct playlist *playlist) +playlist_state_load(TextFile &file, struct playlist *playlist) { - const char *line = read_text_line(fp, buffer); + const char *line = file.ReadLine(); if (line == NULL) { g_warning("No playlist in state file"); return; } while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { - queue_load_song(fp, buffer, line, &playlist->queue); + queue_load_song(file, line, &playlist->queue); - line = read_text_line(fp, buffer); + line = file.ReadLine(); if (line == NULL) { g_warning("'" PLAYLIST_STATE_FILE_PLAYLIST_END "' not found in state file"); @@ -121,11 +116,11 @@ playlist_state_load(FILE *fp, GString *buffer, struct playlist *playlist) } } - queue_increment_version(&playlist->queue); + playlist->queue.IncrementVersion(); } bool -playlist_state_restore(const char *line, FILE *fp, GString *buffer, +playlist_state_restore(const char *line, TextFile &file, struct playlist *playlist, struct player_control *pc) { int current = -1; @@ -143,40 +138,27 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0) state = PLAYER_STATE_PAUSE; - while ((line = read_text_line(fp, buffer)) != NULL) { + while ((line = file.ReadLine()) != NULL) { if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_TIME)) { seek_time = atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_REPEAT)) { - if (strcmp - (&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), - "1") == 0) { - playlist_set_repeat(playlist, pc, true); - } else - playlist_set_repeat(playlist, pc, false); + playlist->SetRepeat(*pc, + strcmp(&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), + "1") == 0); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) { - if (strcmp - (&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), - "1") == 0) { - playlist_set_single(playlist, pc, true); - } else - playlist_set_single(playlist, pc, false); + playlist->SetSingle(*pc, + strcmp(&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), + "1") == 0); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) { - if (strcmp - (&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), - "1") == 0) { - playlist_set_consume(playlist, true); - } else - playlist_set_consume(playlist, false); + playlist->SetConsume(strcmp(&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), + "1") == 0); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CROSSFADE)) { - pc_set_cross_fade(pc, - atoi(line + strlen(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_set_mixramp_db(pc, - atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); + pc->SetMixRampDb(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { - pc_set_mixramp_delay(pc, - atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY))); + pc->SetMixRampDelay(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY))); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) { random_mode = strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), @@ -187,18 +169,18 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, (PLAYLIST_STATE_FILE_CURRENT)])); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { - playlist_state_load(fp, buffer, playlist); + playlist_state_load(file, playlist); } } - playlist_set_random(playlist, pc, random_mode); + playlist->SetRandom(*pc, random_mode); - if (!queue_is_empty(&playlist->queue)) { - if (!queue_valid_position(&playlist->queue, current)) + if (!playlist->queue.IsEmpty()) { + if (!playlist->queue.IsValidPosition(current)) current = 0; if (state == PLAYER_STATE_PLAY && - config_get_bool("restore_paused", false)) + config_get_bool(CONF_RESTORE_PAUSED, false)) /* the user doesn't want MPD to auto-start playback after startup; fall back to "pause" */ @@ -208,17 +190,17 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, called here, after the audio output states were restored, before playback begins */ if (state != PLAYER_STATE_STOP) - pc_update_audio(pc); + pc->UpdateAudio(); if (state == PLAYER_STATE_STOP /* && config_option */) playlist->current = current; else if (seek_time == 0) - playlist_play(playlist, pc, current); + playlist->PlayPosition(*pc, current); else - playlist_seek_song(playlist, pc, current, seek_time); + playlist->SeekSongPosition(*pc, current, seek_time); if (state == PLAYER_STATE_PAUSE) - pc_pause(pc); + pc->Pause(); } return true; @@ -228,19 +210,16 @@ unsigned playlist_state_get_hash(const struct playlist *playlist, struct player_control *pc) { - struct player_status player_status; - - pc_get_status(pc, &player_status); + const auto player_status = pc->GetStatus(); return playlist->queue.version ^ (player_status.state != PLAYER_STATE_STOP ? ((int)player_status.elapsed_time << 8) : 0) ^ (playlist->current >= 0 - ? (queue_order_to_position(&playlist->queue, - playlist->current) << 16) + ? (playlist->queue.OrderToPosition(playlist->current) << 16) : 0) ^ - ((int)pc_get_cross_fade(pc) << 20) ^ + ((int)pc->GetCrossFade() << 20) ^ (player_status.state << 24) ^ (playlist->queue.random << 27) ^ (playlist->queue.repeat << 28) ^ diff --git a/src/playlist_state.h b/src/PlaylistState.hxx index f67d01d2..7e994478 100644 --- a/src/playlist_state.h +++ b/src/PlaylistState.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,22 +22,21 @@ * */ -#ifndef PLAYLIST_STATE_H -#define PLAYLIST_STATE_H +#ifndef MPD_PLAYLIST_STATE_HXX +#define MPD_PLAYLIST_STATE_HXX -#include <glib.h> -#include <stdbool.h> #include <stdio.h> struct playlist; struct player_control; +class TextFile; void playlist_state_save(FILE *fp, const struct playlist *playlist, struct player_control *pc); bool -playlist_state_restore(const char *line, FILE *fp, GString *buffer, +playlist_state_restore(const char *line, TextFile &file, struct playlist *playlist, struct player_control *pc); /** diff --git a/src/PlaylistVector.cxx b/src/PlaylistVector.cxx new file mode 100644 index 00000000..06c7b9ff --- /dev/null +++ b/src/PlaylistVector.cxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlaylistVector.hxx" +#include "DatabaseLock.hxx" + +#include <algorithm> + +#include <assert.h> +#include <string.h> +#include <glib.h> + +PlaylistVector::iterator +PlaylistVector::find(const char *name) +{ + assert(holding_db_lock()); + assert(name != NULL); + + return std::find_if(begin(), end(), + PlaylistInfo::CompareName(name)); +} + +bool +PlaylistVector::UpdateOrInsert(PlaylistInfo &&pi) +{ + assert(holding_db_lock()); + + auto i = find(pi.name.c_str()); + if (i != end()) { + if (pi.mtime == i->mtime) + return false; + + i->mtime = pi.mtime; + } else + push_back(std::move(pi)); + + return true; +} + +bool +PlaylistVector::erase(const char *name) +{ + assert(holding_db_lock()); + + auto i = find(name); + if (i == end()) + return false; + + erase(i); + return true; +} diff --git a/src/PlaylistVector.hxx b/src/PlaylistVector.hxx new file mode 100644 index 00000000..d10c90fd --- /dev/null +++ b/src/PlaylistVector.hxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_VECTOR_HXX +#define MPD_PLAYLIST_VECTOR_HXX + +#include "PlaylistInfo.hxx" +#include "gcc.h" + +#include <list> + +class PlaylistVector : protected std::list<PlaylistInfo> { +protected: + /** + * Caller must lock the #db_mutex. + */ + gcc_pure + iterator find(const char *name); + +public: + using std::list<PlaylistInfo>::empty; + using std::list<PlaylistInfo>::begin; + using std::list<PlaylistInfo>::end; + using std::list<PlaylistInfo>::push_back; + using std::list<PlaylistInfo>::erase; + + /** + * Caller must lock the #db_mutex. + * + * @return true if the vector or one of its items was modified + */ + bool UpdateOrInsert(PlaylistInfo &&pi); + + /** + * Caller must lock the #db_mutex. + */ + bool erase(const char *name); +}; + +#endif /* SONGVEC_H */ diff --git a/src/Queue.cxx b/src/Queue.cxx new file mode 100644 index 00000000..3fdb9ed1 --- /dev/null +++ b/src/Queue.cxx @@ -0,0 +1,499 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Queue.hxx" +#include "song.h" + +#include <stdlib.h> + +queue::queue(unsigned _max_length) + :max_length(_max_length), length(0), + version(1), + items(new Item[max_length]), + order(new unsigned[max_length]), + id_table(max_length * HASH_MULT), + repeat(false), + single(false), + consume(false), + random(false) +{ +} + +queue::~queue() +{ + Clear(); + + delete[] items; + delete[] order; +} + +int +queue::GetNextOrder(unsigned _order) const +{ + assert(_order < length); + + if (single && repeat && !consume) + return _order; + else if (_order + 1 < length) + return _order + 1; + else if (repeat && (_order > 0 || !consume)) + /* restart at first song */ + return 0; + else + /* end of queue */ + return -1; +} + +void +queue::IncrementVersion() +{ + static unsigned long max = ((uint32_t) 1 << 31) - 1; + + version++; + + if (version >= max) { + for (unsigned i = 0; i < length; i++) + items[i].version = 0; + + version = 1; + } +} + +void +queue::ModifyAtOrder(unsigned _order) +{ + assert(_order < length); + + unsigned position = order[_order]; + items[position].version = version; + + IncrementVersion(); +} + +void +queue::ModifyAll() +{ + for (unsigned i = 0; i < length; i++) + items[i].version = version; + + IncrementVersion(); +} + +unsigned +queue::Append(struct 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_dup_detached(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); + + struct song *song = Get(position); + assert(!song_in_database(song) || song_is_detached(song)); + song_free(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]; + + assert(!song_in_database(item->song) || + song_is_detached(item->song)); + song_free(item->song); + + id_table.Erase(item->id); + } + + length = 0; +} + +static void +queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end) +{ + assert(queue != NULL); + assert(queue->random); + assert(start <= end); + assert(end <= queue->length); + + auto cmp = [queue](unsigned a_pos, unsigned b_pos){ + const queue::Item &a = queue->items[a_pos]; + const queue::Item &b = queue->items[b_pos]; + + return a.priority > b.priority; + }; + + std::stable_sort(queue->order + start, queue->order + end, cmp); +} + +void +queue::ShuffleOrderRange(unsigned start, unsigned end) +{ + assert(random); + assert(start <= end); + assert(end <= length); + + rand.AutoCreate(); + std::shuffle(order + start, order + end, rand); +} + +/** + * Sort the "order" of items by priority, and then shuffle each + * priority group. + */ +void +queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end) +{ + assert(random); + assert(start <= end); + assert(end <= length); + + if (start == end) + return; + + /* first group the range by priority */ + queue_sort_order_by_priority(this, start, end); + + /* now shuffle each priority group */ + unsigned group_start = start; + uint8_t group_priority = GetOrderPriority(start); + + for (unsigned i = start + 1; i < end; ++i) { + const uint8_t priority = GetOrderPriority(i); + assert(priority <= group_priority); + + if (priority != group_priority) { + /* start of a new group - shuffle the one that + has just ended */ + ShuffleOrderRange(group_start, i); + group_start = i; + group_priority = priority; + } + } + + /* shuffle the last group */ + ShuffleOrderRange(group_start, end); +} + +void +queue::ShuffleOrder() +{ + ShuffleOrderRangeWithPriority(0, length); +} + +void +queue::ShuffleOrderFirst(unsigned start, unsigned end) +{ + rand.AutoCreate(); + + std::uniform_int_distribution<unsigned> distribution(start, end - 1); + SwapOrders(start, distribution(rand)); +} + +void +queue::ShuffleOrderLast(unsigned start, unsigned end) +{ + rand.AutoCreate(); + + std::uniform_int_distribution<unsigned> distribution(start, end - 1); + SwapOrders(end - 1, distribution(rand)); +} + +void +queue::ShuffleRange(unsigned start, unsigned end) +{ + assert(start <= end); + assert(end <= length); + + rand.AutoCreate(); + + for (unsigned i = start; i < end; i++) { + std::uniform_int_distribution<unsigned> distribution(start, + end - 1); + unsigned ri = distribution(rand); + SwapPositions(i, ri); + } +} + +unsigned +queue::FindPriorityOrder(unsigned start_order, uint8_t priority, + unsigned exclude_order) const +{ + assert(random); + assert(start_order <= length); + + for (unsigned i = start_order; i < length; ++i) { + const unsigned position = OrderToPosition(i); + const Item *item = &items[position]; + if (item->priority <= priority && i != exclude_order) + return i; + } + + return length; +} + +unsigned +queue::CountSamePriority(unsigned start_order, uint8_t priority) const +{ + assert(random); + assert(start_order <= length); + + for (unsigned i = start_order; i < length; ++i) { + const unsigned position = OrderToPosition(i); + const Item *item = &items[position]; + if (item->priority != priority) + return i - start_order; + } + + return length - start_order; +} + +bool +queue::SetPriority(unsigned position, uint8_t priority, int after_order) +{ + assert(position < length); + + Item *item = &items[position]; + uint8_t old_priority = item->priority; + if (old_priority == priority) + return false; + + item->version = version; + item->priority = priority; + + if (!random) + /* don't reorder if not in random mode */ + return true; + + unsigned _order = PositionToOrder(position); + if (after_order >= 0) { + if (_order == (unsigned)after_order) + /* don't reorder the current song */ + return true; + + if (_order < (unsigned)after_order) { + /* the specified song has been played already + - enqueue it only if its priority has just + become bigger than the current one's */ + + const unsigned after_position = + OrderToPosition(after_order); + const Item *after_item = + &items[after_position]; + if (old_priority > after_item->priority || + priority <= after_item->priority) + /* priority hasn't become bigger */ + return true; + } + } + + /* move the item to the beginning of the priority group (or + create a new priority group) */ + + const unsigned before_order = + FindPriorityOrder(after_order + 1, priority, _order); + const unsigned new_order = before_order > _order + ? before_order - 1 + : before_order; + MoveOrder(_order, new_order); + + /* shuffle the song within that priority group */ + + const unsigned priority_count = CountSamePriority(new_order, priority); + assert(priority_count >= 1); + ShuffleOrderFirst(new_order, new_order + priority_count); + + return true; +} + +bool +queue::SetPriorityRange(unsigned start_position, unsigned end_position, + uint8_t priority, int after_order) +{ + assert(start_position <= end_position); + assert(end_position <= length); + + bool modified = false; + int after_position = after_order >= 0 + ? (int)OrderToPosition(after_order) + : -1; + for (unsigned i = start_position; i < end_position; ++i) { + after_order = after_position >= 0 + ? (int)PositionToOrder(after_position) + : -1; + + modified |= SetPriority(i, priority, after_order); + } + + return modified; +} diff --git a/src/Queue.hxx b/src/Queue.hxx new file mode 100644 index 00000000..6e7786bc --- /dev/null +++ b/src/Queue.hxx @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_QUEUE_HXX +#define MPD_QUEUE_HXX + +#include "gcc.h" +#include "IdTable.hxx" +#include "util/LazyRandomEngine.hxx" + +#include <algorithm> + +#include <assert.h> +#include <stdint.h> + +/** + * 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 { + struct song *song; + + /** the unique id of this item in the queue */ + unsigned id; + + /** when was this item last changed? */ + uint32_t version; + + /** + * The priority of this item, between 0 and 255. High + * priority value means that this song gets played first in + * "random" mode. + */ + uint8_t priority; + }; + + /** configured maximum length of the queue */ + unsigned max_length; + + /** number of songs in the queue */ + unsigned length; + + /** the current version number */ + uint32_t version; + + /** all songs in "position" order */ + Item *items; + + /** map order numbers to positions */ + unsigned *order; + + /** map song ids to positions */ + IdTable id_table; + + /** repeat playback when the end of the queue has been + reached? */ + bool repeat; + + /** play only current song. */ + bool single; + + /** remove each played files. */ + bool consume; + + /** play back songs in random order? */ + bool random; + + /** random number generator for shuffle and random mode */ + LazyRandomEngine rand; + + queue(unsigned max_length); + + /** + * Deinitializes a queue object. It does not free the queue + * pointer itself. + */ + ~queue(); + + queue(const queue &other) = delete; + queue &operator=(const queue &other) = delete; + + unsigned GetLength() const { + assert(length <= max_length); + + return length; + } + + /** + * Determine if the queue is empty, i.e. there are no songs. + */ + bool IsEmpty() const { + return length == 0; + } + + /** + * Determine if the maximum number of songs has been reached. + */ + bool IsFull() const { + assert(length <= max_length); + + return length >= max_length; + } + + /** + * Is that a valid position number? + */ + bool IsValidPosition(unsigned position) const { + return position < length; + } + + /** + * Is that a valid order number? + */ + bool IsValidOrder(unsigned _order) const { + return _order < length; + } + + int IdToPosition(unsigned id) const { + return id_table.IdToPosition(id); + } + + int PositionToId(unsigned position) const + { + assert(position < length); + + return items[position].id; + } + + gcc_pure + unsigned OrderToPosition(unsigned _order) const { + assert(_order < length); + + return order[_order]; + } + + gcc_pure + unsigned PositionToOrder(unsigned position) const { + assert(position < length); + + for (unsigned i = 0;; ++i) { + assert(i < length); + + if (order[i] == position) + return i; + } + } + + gcc_pure + uint8_t GetPriorityAtPosition(unsigned position) const { + assert(position < length); + + return items[position].priority; + } + + const Item &GetOrderItem(unsigned i) const { + assert(IsValidOrder(i)); + + return items[OrderToPosition(i)]; + } + + uint8_t GetOrderPriority(unsigned i) const { + return GetOrderItem(i).priority; + } + + /** + * Returns the song at the specified position. + */ + struct song *Get(unsigned position) const { + assert(position < length); + + return items[position].song; + } + + /** + * Returns the song at the specified order number. + */ + struct song *GetOrder(unsigned _order) const { + return Get(OrderToPosition(_order)); + } + + /** + * Is the song at the specified position newer than the specified + * version? + */ + bool IsNewerAtPosition(unsigned position, uint32_t _version) const { + assert(position < length); + + return _version > version || + items[position].version >= _version || + items[position].version == 0; + } + + /** + * Returns the order number following the specified one. This takes + * end of queue and "repeat" mode into account. + * + * @return the next order number, or -1 to stop playback + */ + gcc_pure + int GetNextOrder(unsigned order) const; + + /** + * Increments the queue's version number. This handles integer + * overflow well. + */ + void IncrementVersion(); + + /** + * Marks the specified song as "modified" and increments the version + * number. + */ + void ModifyAtOrder(unsigned order); + + /** + * Marks all songs as "modified" and increments the version number. + */ + void ModifyAll(); + + /** + * Appends a song to the queue and returns its position. Prior to + * that, the caller must check if the queue is already full. + * + * If a song is not in the database (determined by + * song_in_database()), it is freed when removed from the queue. + * + * @param priority the priority of this new queue item + */ + unsigned Append(struct song *song, uint8_t priority); + + /** + * Swaps two songs, addressed by their position. + */ + void SwapPositions(unsigned position1, unsigned position2); + + /** + * Swaps two songs, addressed by their order number. + */ + void SwapOrders(unsigned order1, unsigned order2) { + std::swap(order[order1], order[order2]); + } + + /** + * Moves a song to a new position. + */ + void MovePostion(unsigned from, unsigned to); + + /** + * Moves a range of songs to a new position. + */ + void MoveRange(unsigned start, unsigned end, unsigned to); + + /** + * Removes a song from the playlist. + */ + void DeletePosition(unsigned position); + + /** + * Removes all songs from the playlist. + */ + void Clear(); + + /** + * Initializes the "order" array, and restores "normal" order. + */ + void RestoreOrder() { + for (unsigned i = 0; i < length; ++i) + order[i] = i; + } + + /** + * Shuffle the order of items in the specified range, ignoring + * their priorities. + */ + void ShuffleOrderRange(unsigned start, unsigned end); + + /** + * Shuffle the order of items in the specified range, taking their + * priorities into account. + */ + void ShuffleOrderRangeWithPriority(unsigned start, unsigned end); + + /** + * Shuffles the virtual order of songs, but does not move them + * physically. This is used in random mode. + */ + void ShuffleOrder(); + + void ShuffleOrderFirst(unsigned start, unsigned end); + + /** + * Shuffles the virtual order of the last song in the specified + * (order) range. This is used in random mode after a song has been + * appended by queue_append(). + */ + void ShuffleOrderLast(unsigned start, unsigned end); + + /** + * Shuffles a (position) range in the queue. The songs are physically + * shuffled, not by using the "order" mapping. + */ + void ShuffleRange(unsigned start, unsigned end); + + bool SetPriority(unsigned position, uint8_t priority, int after_order); + + bool SetPriorityRange(unsigned start_position, unsigned end_position, + uint8_t priority, int after_order); + +private: + /** + * Moves a song to a new position in the "order" list. + */ + void MoveOrder(unsigned from_order, unsigned to_order); + + void MoveItemTo(unsigned from, unsigned to) { + unsigned from_id = items[from].id; + + items[to] = items[from]; + items[to].version = version; + id_table.Move(from_id, to); + } + + /** + * Find the first item that has this specified priority or + * higher. + */ + gcc_pure + unsigned FindPriorityOrder(unsigned start_order, uint8_t priority, + unsigned exclude_order) const; + + gcc_pure + unsigned CountSamePriority(unsigned start_order, + uint8_t priority) const; +}; + +#endif diff --git a/src/QueueCommands.cxx b/src/QueueCommands.cxx new file mode 100644 index 00000000..4a3e1312 --- /dev/null +++ b/src/QueueCommands.cxx @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "QueueCommands.hxx" +#include "CommandError.hxx" +#include "DatabaseQueue.hxx" +#include "SongFilter.hxx" +#include "DatabaseSelection.hxx" +#include "Playlist.hxx" +#include "PlaylistPrint.hxx" +#include "ClientFile.hxx" +#include "ClientInternal.hxx" +#include "Partition.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "ls.hxx" + +extern "C" { +#include "uri.h" +} + +#include <string.h> + +enum command_return +handle_add(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + char *uri = argv[1]; + enum playlist_result result; + + if (strncmp(uri, "file:///", 8) == 0) { + const char *path = uri + 7; + + GError *error = NULL; + if (!client_allow_file(client, path, &error)) + return print_error(client, error); + + result = client->partition.AppendFile(path); + return print_playlist_result(client, result); + } + + if (uri_has_scheme(uri)) { + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return COMMAND_RETURN_ERROR; + } + + result = client->partition.AppendURI(uri); + return print_playlist_result(client, result); + } + + const DatabaseSelection selection(uri, true); + GError *error = NULL; + return AddFromDatabase(client->partition, selection, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_addid(Client *client, int argc, char *argv[]) +{ + char *uri = argv[1]; + unsigned added_id; + enum playlist_result result; + + if (strncmp(uri, "file:///", 8) == 0) { + const char *path = uri + 7; + + GError *error = NULL; + if (!client_allow_file(client, path, &error)) + return print_error(client, error); + + result = client->partition.AppendFile(path, &added_id); + } else { + if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return COMMAND_RETURN_ERROR; + } + + result = client->partition.AppendURI(uri, &added_id); + } + + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + + if (argc == 3) { + unsigned to; + if (!check_unsigned(client, &to, argv[2])) + return COMMAND_RETURN_ERROR; + result = client->partition.MoveId(added_id, to); + if (result != PLAYLIST_RESULT_SUCCESS) { + enum command_return ret = + print_playlist_result(client, result); + client->partition.DeleteId(added_id); + return ret; + } + } + + client_printf(client, "Id: %u\n", added_id); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_delete(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned start, end; + + if (!check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = client->partition.DeleteRange(start, end); + return print_playlist_result(client, result); +} + +enum command_return +handle_deleteid(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned id; + + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = client->partition.DeleteId(id); + return print_playlist_result(client, result); +} + +enum command_return +handle_playlist(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist_print_uris(client, &client->playlist); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_shuffle(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + unsigned start = 0, end = client->playlist.queue.GetLength(); + if (argc == 2 && !check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.Shuffle(start, end); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_clear(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + client->partition.ClearQueue(); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_plchanges(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + uint32_t version; + + if (!check_uint32(client, &version, argv[1])) + return COMMAND_RETURN_ERROR; + + playlist_print_changes_info(client, &client->playlist, version); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_plchangesposid(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + uint32_t version; + + if (!check_uint32(client, &version, argv[1])) + return COMMAND_RETURN_ERROR; + + playlist_print_changes_position(client, &client->playlist, version); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_playlistinfo(Client *client, int argc, char *argv[]) +{ + unsigned start = 0, end = G_MAXUINT; + bool ret; + + if (argc == 2 && !check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + + ret = playlist_print_info(client, &client->playlist, start, end); + if (!ret) + return print_playlist_result(client, + PLAYLIST_RESULT_BAD_RANGE); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_playlistid(Client *client, int argc, char *argv[]) +{ + if (argc >= 2) { + unsigned id; + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + + bool ret = playlist_print_id(client, &client->playlist, id); + if (!ret) + return print_playlist_result(client, + PLAYLIST_RESULT_NO_SUCH_SONG); + } else { + playlist_print_info(client, &client->playlist, 0, G_MAXUINT); + } + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_playlist_match(Client *client, int argc, char *argv[], + bool fold_case) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + playlist_print_find(client, &client->playlist, filter); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_playlistfind(Client *client, int argc, char *argv[]) +{ + return handle_playlist_match(client, argc, argv, false); +} + +enum command_return +handle_playlistsearch(Client *client, int argc, char *argv[]) +{ + return handle_playlist_match(client, argc, argv, true); +} + +enum command_return +handle_prio(Client *client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return COMMAND_RETURN_ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return COMMAND_RETURN_ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned start_position, end_position; + if (!check_range(client, &start_position, &end_position, + argv[i])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SetPriorityRange(start_position, + end_position, + priority); + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_prioid(Client *client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return COMMAND_RETURN_ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return COMMAND_RETURN_ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned song_id; + if (!check_unsigned(client, &song_id, argv[i])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SetPriorityId(song_id, priority); + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_move(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned start, end; + int to; + + if (!check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_int(client, &to, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.MoveRange(start, end, to); + return print_playlist_result(client, result); +} + +enum command_return +handle_moveid(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned id; + int to; + + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_int(client, &to, argv[2])) + return COMMAND_RETURN_ERROR; + enum playlist_result result = client->partition.MoveId(id, to); + return print_playlist_result(client, result); +} + +enum command_return +handle_swap(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned song1, song2; + + if (!check_unsigned(client, &song1, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &song2, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SwapPositions(song1, song2); + return print_playlist_result(client, result); +} + +enum command_return +handle_swapid(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned id1, id2; + + if (!check_unsigned(client, &id1, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &id2, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = client->partition.SwapIds(id1, id2); + return print_playlist_result(client, result); +} diff --git a/src/QueueCommands.hxx b/src/QueueCommands.hxx new file mode 100644 index 00000000..97b61e21 --- /dev/null +++ b/src/QueueCommands.hxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_QUEUE_COMMANDS_HXX +#define MPD_QUEUE_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_add(Client *client, int argc, char *argv[]); + +enum command_return +handle_addid(Client *client, int argc, char *argv[]); + +enum command_return +handle_delete(Client *client, int argc, char *argv[]); + +enum command_return +handle_deleteid(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlist(Client *client, int argc, char *argv[]); + +enum command_return +handle_shuffle(Client *client, int argc, char *argv[]); + +enum command_return +handle_clear(Client *client, int argc, char *argv[]); + +enum command_return +handle_plchanges(Client *client, int argc, char *argv[]); + +enum command_return +handle_plchangesposid(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistinfo(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistid(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistfind(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistsearch(Client *client, int argc, char *argv[]); + +enum command_return +handle_prio(Client *client, int argc, char *argv[]); + +enum command_return +handle_prioid(Client *client, int argc, char *argv[]); + +enum command_return +handle_move(Client *client, int argc, char *argv[]); + +enum command_return +handle_moveid(Client *client, int argc, char *argv[]); + +enum command_return +handle_swap(Client *client, int argc, char *argv[]); + +enum command_return +handle_swapid(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/QueuePrint.cxx b/src/QueuePrint.cxx new file mode 100644 index 00000000..28abc5a8 --- /dev/null +++ b/src/QueuePrint.cxx @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "QueuePrint.hxx" +#include "Queue.hxx" +#include "SongFilter.hxx" +#include "SongPrint.hxx" +#include "Mapper.hxx" +#include "Client.hxx" + +extern "C" { +#include "song.h" +} + +/** + * Send detailed information about a range of songs in the queue to a + * client. + * + * @param client the client which has requested information + * @param start the index of the first song (including) + * @param end the index of the last song (excluding) + */ +static void +queue_print_song_info(Client *client, const struct queue *queue, + unsigned position) +{ + song_print_info(client, queue->Get(position)); + client_printf(client, "Pos: %u\nId: %u\n", + position, queue->PositionToId(position)); + + uint8_t priority = queue->GetPriorityAtPosition(position); + if (priority != 0) + client_printf(client, "Prio: %u\n", priority); +} + +void +queue_print_info(Client *client, const struct queue *queue, + unsigned start, unsigned end) +{ + assert(start <= end); + assert(end <= queue->GetLength()); + + for (unsigned i = start; i < end; ++i) + queue_print_song_info(client, queue, i); +} + +void +queue_print_uris(Client *client, const struct queue *queue, + unsigned start, unsigned end) +{ + assert(start <= end); + assert(end <= queue->GetLength()); + + for (unsigned i = start; i < end; ++i) { + client_printf(client, "%i:", i); + song_print_uri(client, queue->Get(i)); + } +} + +void +queue_print_changes_info(Client *client, const struct queue *queue, + uint32_t version) +{ + for (unsigned i = 0; i < queue->GetLength(); i++) { + if (queue->IsNewerAtPosition(i, version)) + queue_print_song_info(client, queue, i); + } +} + +void +queue_print_changes_position(Client *client, const struct queue *queue, + uint32_t version) +{ + for (unsigned i = 0; i < queue->GetLength(); i++) + if (queue->IsNewerAtPosition(i, version)) + client_printf(client, "cpos: %i\nId: %i\n", + i, queue->PositionToId(i)); +} + +void +queue_find(Client *client, const struct queue *queue, + const SongFilter &filter) +{ + for (unsigned i = 0; i < queue->GetLength(); i++) { + const struct song *song = queue->Get(i); + + if (filter.Match(*song)) + queue_print_song_info(client, queue, i); + } +} diff --git a/src/queue_print.h b/src/QueuePrint.hxx index 371e2041..6b3a29fb 100644 --- a/src/queue_print.h +++ b/src/QueuePrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,37 +22,33 @@ * client. */ -#ifndef QUEUE_PRINT_H -#define QUEUE_PRINT_H +#ifndef MPD_QUEUE_PRINT_HXX +#define MPD_QUEUE_PRINT_HXX #include <stdint.h> -struct client; struct queue; -struct locate_item_list; +class SongFilter; +class Client; void -queue_print_info(struct client *client, const struct queue *queue, +queue_print_info(Client *client, const struct queue *queue, unsigned start, unsigned end); void -queue_print_uris(struct client *client, const struct queue *queue, +queue_print_uris(Client *client, const struct queue *queue, unsigned start, unsigned end); void -queue_print_changes_info(struct client *client, const struct queue *queue, +queue_print_changes_info(Client *client, const struct queue *queue, uint32_t version); void -queue_print_changes_position(struct client *client, const struct queue *queue, +queue_print_changes_position(Client *client, const struct queue *queue, uint32_t version); void -queue_search(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria); - -void -queue_find(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria); +queue_find(Client *client, const struct queue *queue, + const SongFilter &filter); #endif diff --git a/src/queue_save.c b/src/QueueSave.cxx index 16852d3c..09b0645f 100644 --- a/src/queue_save.c +++ b/src/QueueSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,17 @@ */ #include "config.h" -#include "queue_save.h" -#include "queue.h" +#include "QueueSave.hxx" +#include "Playlist.hxx" #include "song.h" +#include "SongSave.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" +#include "TextFile.hxx" + +extern "C" { #include "uri.h" -#include "database.h" -#include "song_save.h" -#include "text_file.h" +} #include <stdlib.h> @@ -57,48 +61,40 @@ queue_save_song(FILE *fp, int idx, const struct song *song) void queue_save(FILE *fp, const struct queue *queue) { - for (unsigned i = 0; i < queue_length(queue); i++) { - uint8_t prio = queue_get_priority_at_position(queue, i); + 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(queue, i)); + queue_save_song(fp, i, queue->Get(i)); } } -static struct song * -get_song(const char *uri) -{ - return uri_has_scheme(uri) - ? song_remote_new(uri) - : db_get_song(uri); -} - void -queue_load_song(FILE *fp, GString *buffer, const char *line, - struct queue *queue) +queue_load_song(TextFile &file, const char *line, queue *queue) { - struct song *song; - - if (queue_is_full(queue)) + if (queue->IsFull()) return; uint8_t priority = 0; if (g_str_has_prefix(line, PRIO_LABEL)) { priority = strtoul(line + sizeof(PRIO_LABEL) - 1, NULL, 10); - line = read_text_line(fp, buffer); + line = file.ReadLine(); if (line == NULL) return; } + const Database *db = nullptr; + struct song *song; + if (g_str_has_prefix(line, SONG_BEGIN)) { const char *uri = line + sizeof(SONG_BEGIN) - 1; if (!uri_has_scheme(uri) && !g_path_is_absolute(uri)) return; GError *error = NULL; - song = song_load(fp, NULL, uri, buffer, &error); + song = song_load(file, NULL, uri, &error); if (song == NULL) { g_warning("%s", error->message); g_error_free(error); @@ -112,12 +108,23 @@ queue_load_song(FILE *fp, GString *buffer, const char *line, return; } - line = endptr + 1; + const char *uri = endptr + 1; - song = get_song(line); - if (song == NULL) - return; + if (uri_has_scheme(uri)) { + song = song_remote_new(uri); + } else { + db = GetDatabase(nullptr); + if (db == nullptr) + return; + + song = db->GetSong(uri, nullptr); + if (song == nullptr) + return; + } } - queue_append(queue, song, priority); + queue->Append(song, priority); + + if (db != nullptr) + db->ReturnSong(song); } diff --git a/src/queue_save.h b/src/QueueSave.hxx index 5526d615..25d7804f 100644 --- a/src/queue_save.h +++ b/src/QueueSave.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,13 +22,13 @@ * back into memory. */ -#ifndef QUEUE_SAVE_H -#define QUEUE_SAVE_H +#ifndef MPD_QUEUE_SAVE_HXX +#define MPD_QUEUE_SAVE_HXX -#include <glib.h> #include <stdio.h> struct queue; +class TextFile; void queue_save(FILE *fp, const struct queue *queue); @@ -37,7 +37,6 @@ queue_save(FILE *fp, const struct queue *queue); * Loads one song from the state file and appends it to the queue. */ void -queue_load_song(FILE *fp, GString *buffer, const char *line, - struct queue *queue); +queue_load_song(TextFile &file, const char *line, queue *queue); #endif diff --git a/src/replay_gain_config.c b/src/ReplayGainConfig.cxx index 2181387b..d86c7005 100644 --- a/src/replay_gain_config.c +++ b/src/ReplayGainConfig.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 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 "replay_gain_config.h" -#include "playlist.h" +#include "Idle.hxx" #include "conf.h" -#include "idle.h" +#include "Playlist.hxx" #include "mpd_error.h" #include <glib.h> @@ -29,20 +29,14 @@ #include <assert.h> #include <stdlib.h> #include <string.h> -#include <math.h> - -static const char *const replay_gain_mode_names[] = { - [REPLAY_GAIN_ALBUM] = "album", - [REPLAY_GAIN_TRACK] = "track", -}; enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF; -const bool DEFAULT_REPLAYGAIN_LIMIT = true; +static constexpr bool DEFAULT_REPLAYGAIN_LIMIT = true; float replay_gain_preamp = 1.0; float replay_gain_missing_preamp = 1.0; -bool replay_gain_limit; +bool replay_gain_limit = DEFAULT_REPLAYGAIN_LIMIT; const char * replay_gain_get_mode_string(void) @@ -137,14 +131,15 @@ void replay_gain_global_init(void) replay_gain_limit = config_get_bool(CONF_REPLAYGAIN_LIMIT, DEFAULT_REPLAYGAIN_LIMIT); } -enum replay_gain_mode replay_gain_get_real_mode(void) +enum replay_gain_mode +replay_gain_get_real_mode(bool random_mode) { enum replay_gain_mode rgm; rgm = replay_gain_mode; if (rgm == REPLAY_GAIN_AUTO) - rgm = g_playlist.queue.random ? REPLAY_GAIN_TRACK : REPLAY_GAIN_ALBUM; + rgm = random_mode ? REPLAY_GAIN_TRACK : REPLAY_GAIN_ALBUM; return rgm; } diff --git a/src/replay_gain_info.c b/src/ReplayGainInfo.cxx index 1f09e7a1..b9d1b82c 100644 --- a/src/replay_gain_info.c +++ b/src/ReplayGainInfo.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 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/sig_handlers.c b/src/SignalHandlers.cxx index b23f9e77..d438eb70 100644 --- a/src/sig_handlers.c +++ b/src/SignalHandlers.cxx @@ -18,13 +18,14 @@ */ #include "config.h" -#include "sig_handlers.h" +#include "SignalHandlers.hxx" #ifndef WIN32 -#include "log.h" -#include "main.h" -#include "event_pipe.h" +#include "Log.hxx" +#include "Main.hxx" +#include "event/Loop.hxx" +#include "GlobalEvents.hxx" #include "mpd_error.h" #include <glib.h> @@ -35,12 +36,12 @@ static void exit_signal_handler(G_GNUC_UNUSED int signum) { - g_main_loop_quit(main_loop); + GlobalEvents::Emit(GlobalEvents::SHUTDOWN); } static void reload_signal_handler(G_GNUC_UNUSED int signum) { - event_pipe_emit_fast(PIPE_EVENT_RELOAD); + GlobalEvents::Emit(GlobalEvents::RELOAD); } static void @@ -73,7 +74,7 @@ void initSigHandlers(void) x_sigaction(SIGINT, &sa); x_sigaction(SIGTERM, &sa); - event_pipe_register(PIPE_EVENT_RELOAD, handle_reload_event); + GlobalEvents::Register(GlobalEvents::RELOAD, handle_reload_event); sa.sa_handler = reload_signal_handler; x_sigaction(SIGHUP, &sa); #endif diff --git a/src/SignalHandlers.hxx b/src/SignalHandlers.hxx new file mode 100644 index 00000000..99f347fb --- /dev/null +++ b/src/SignalHandlers.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SIGNAL_HANDLERS_HXX +#define MPD_SIGNAL_HANDLERS_HXX + +void initSigHandlers(void); + +#endif diff --git a/src/SocketError.hxx b/src/SocketError.hxx new file mode 100644 index 00000000..53a0c1d8 --- /dev/null +++ b/src/SocketError.hxx @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SOCKET_ERROR_HXX +#define MPD_SOCKET_ERROR_HXX + +#include "gcc.h" + +#include <glib.h> + +#ifdef WIN32 +#include <winsock2.h> +typedef DWORD socket_error_t; +#else +#include <errno.h> +typedef int socket_error_t; +#endif + +/** + * A GQuark for GError for socket I/O errors. The code is an errno + * value (or WSAGetLastError() on Windows). + */ +gcc_const +static inline GQuark +SocketErrorQuark(void) +{ + return g_quark_from_static_string("socket"); +} + +gcc_pure +static inline socket_error_t +GetSocketError() +{ +#ifdef WIN32 + return WSAGetLastError(); +#else + return errno; +#endif +} + +gcc_const +static inline bool +IsSocketErrorAgain(socket_error_t code) +{ +#ifdef WIN32 + return code == WSAEINPROGRESS; +#else + return code == EAGAIN; +#endif +} + +gcc_const +static inline bool +IsSocketErrorInterruped(socket_error_t code) +{ +#ifdef WIN32 + return code == WSAEINTR; +#else + return code == EINTR; +#endif +} + +gcc_const +static inline bool +IsSocketErrorClosed(socket_error_t code) +{ +#ifdef WIN32 + return code == WSAECONNRESET; +#else + return code == EPIPE || code == ECONNRESET; +#endif +} + +/** + * Helper class that formats a socket error message into a + * human-readable string. On Windows, a buffer is necessary for this, + * and this class hosts the buffer. + */ +class SocketErrorMessage { +#ifdef WIN32 + char msg[256]; +#else + const char *const msg; +#endif + +public: +#ifdef WIN32 + explicit SocketErrorMessage(socket_error_t code=GetSocketError()) { + DWORD nbytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, code, 0, + (LPSTR)msg, sizeof(msg), NULL); + if (nbytes == 0) + strcpy(msg, "Unknown error"); + } +#else + explicit SocketErrorMessage(socket_error_t code=GetSocketError()) + :msg(g_strerror(code)) {} +#endif + + operator const char *() const { + return msg; + } +}; + +static inline void +SetSocketError(GError **error_r, socket_error_t code) +{ +#ifdef WIN32 + if (error_r == NULL) + return; +#endif + + const SocketErrorMessage msg(code); + g_set_error_literal(error_r, SocketErrorQuark(), code, msg); +} + +static inline void +SetSocketError(GError **error_r) +{ + SetSocketError(error_r, GetSocketError()); +} + +gcc_malloc +static inline GError * +NewSocketError(socket_error_t code) +{ + const SocketErrorMessage msg(code); + return g_error_new_literal(SocketErrorQuark(), code, msg); +} + +gcc_malloc +static inline GError * +NewSocketError() +{ + return NewSocketError(GetSocketError()); +} + +#endif diff --git a/src/socket_util.c b/src/SocketUtil.cxx index a06a0cbd..dd7eb7dd 100644 --- a/src/socket_util.c +++ b/src/SocketUtil.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,12 @@ */ #include "config.h" -#include "socket_util.h" +#include "SocketUtil.hxx" +#include "SocketError.hxx" #include "fd_util.h" -#include <errno.h> +#include <glib.h> + #include <unistd.h> #ifndef G_OS_WIN32 @@ -35,49 +37,42 @@ #include <string.h> #endif -static GQuark -listen_quark(void) -{ - return g_quark_from_static_string("listen"); -} - int socket_bind_listen(int domain, int type, int protocol, const struct sockaddr *address, size_t address_length, int backlog, - GError **error) + GError **error_r) { int fd, ret; const int reuse = 1; fd = socket_cloexec_nonblock(domain, type, protocol); if (fd < 0) { - g_set_error(error, listen_quark(), errno, - "Failed to create socket: %s", g_strerror(errno)); + SetSocketError(error_r); + g_prefix_error(error_r, "Failed to create socket: "); return -1; } ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *) &reuse, sizeof(reuse)); if (ret < 0) { - g_set_error(error, listen_quark(), errno, - "setsockopt() failed: %s", g_strerror(errno)); + SetSocketError(error_r); + g_prefix_error(error_r, "setsockopt() failed: "); close_socket(fd); return -1; } ret = bind(fd, address, address_length); if (ret < 0) { - g_set_error(error, listen_quark(), errno, - "%s", g_strerror(errno)); + SetSocketError(error_r); close_socket(fd); return -1; } ret = listen(fd, backlog); if (ret < 0) { - g_set_error(error, listen_quark(), errno, - "listen() failed: %s", g_strerror(errno)); + SetSocketError(error_r); + g_prefix_error(error_r, "listen() failed: "); close_socket(fd); return -1; } diff --git a/src/socket_util.h b/src/SocketUtil.hxx index 93bd2736..3d0be78c 100644 --- a/src/socket_util.h +++ b/src/SocketUtil.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,10 +23,12 @@ * */ -#ifndef SOCKET_UTIL_H -#define SOCKET_UTIL_H +#ifndef MPD_SOCKET_UTIL_HXX +#define MPD_SOCKET_UTIL_HXX -#include <glib.h> +#include "gerror.h" + +#include <stddef.h> struct sockaddr; diff --git a/src/song.c b/src/Song.cxx index f5cc7c35..4c820c3f 100644 --- a/src/song.c +++ b/src/Song.cxx @@ -19,26 +19,28 @@ #include "config.h" #include "song.h" -#include "uri.h" -#include "directory.h" +#include "Directory.hxx" #include "tag.h" #include <glib.h> #include <assert.h> +Directory detached_root; + static struct song * -song_alloc(const char *uri, struct directory *parent) +song_alloc(const char *uri, Directory *parent) { size_t uri_length; - struct song *song; assert(uri); uri_length = strlen(uri); assert(uri_length); - song = g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1); - song->tag = NULL; + struct song *song = (struct 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; @@ -50,13 +52,13 @@ song_alloc(const char *uri, struct directory *parent) struct song * song_remote_new(const char *uri) { - return song_alloc(uri, NULL); + return song_alloc(uri, nullptr); } struct song * -song_file_new(const char *path, struct directory *parent) +song_file_new(const char *path, Directory *parent) { - assert((parent == NULL) == (*path == '/')); + assert((parent == nullptr) == (*path == '/')); return song_alloc(path, parent); } @@ -73,6 +75,35 @@ song_replace_uri(struct song *old_song, const char *uri) return new_song; } +struct song * +song_detached_new(const char *uri) +{ + assert(uri != nullptr); + + return song_alloc(uri, &detached_root); +} + +struct song * +song_dup_detached(const struct song *src) +{ + assert(src != nullptr); + + struct song *song; + if (song_in_database(src)) { + char *uri = song_get_uri(src); + song = song_detached_new(uri); + g_free(uri); + } else + song = song_alloc(src->uri, nullptr); + + song->tag = tag_dup(src->tag); + song->mtime = src->mtime; + song->start_ms = src->start_ms; + song->end_ms = src->end_ms; + + return song; +} + void song_free(struct song *song) { @@ -81,17 +112,57 @@ song_free(struct song *song) g_free(song); } +gcc_pure +static inline bool +directory_equals(const Directory &a, const Directory &b) +{ + return strcmp(a.path, b.path) == 0; +} + +gcc_pure +static inline bool +directory_is_same(const Directory *a, const Directory *b) +{ + return a == b || + (a != nullptr && b != nullptr && + directory_equals(*a, *b)); + +} + +bool +song_equals(const struct song *a, const struct song *b) +{ + assert(a != nullptr); + assert(b != nullptr); + + if (a->parent != nullptr && b->parent != nullptr && + !directory_equals(*a->parent, *b->parent) && + (a->parent == &detached_root || b->parent == &detached_root)) { + /* must compare the full URI if one of the objects is + "detached" */ + char *au = song_get_uri(a); + char *bu = song_get_uri(b); + const bool result = strcmp(au, bu) == 0; + g_free(bu); + g_free(au); + return result; + } + + return directory_is_same(a->parent, b->parent) && + strcmp(a->uri, b->uri) == 0; +} + char * song_get_uri(const struct song *song) { - assert(song != NULL); + assert(song != nullptr); assert(*song->uri); - if (!song_in_database(song) || directory_is_root(song->parent)) + if (!song_in_database(song) || song->parent->IsRoot()) return g_strdup(song->uri); else - return g_strconcat(directory_get_path(song->parent), - "/", song->uri, NULL); + return g_strconcat(song->parent->GetPath(), + "/", song->uri, nullptr); } double @@ -100,7 +171,7 @@ song_get_duration(const struct song *song) if (song->end_ms > 0) return (song->end_ms - song->start_ms) / 1000.0; - if (song->tag == NULL) + if (song->tag == nullptr) return 0; return song->tag->time - song->start_ms / 1000.0; diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx new file mode 100644 index 00000000..0e138386 --- /dev/null +++ b/src/SongFilter.cxx @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SongFilter.hxx" +#include "song.h" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> + +#define LOCATE_TAG_FILE_KEY "file" +#define LOCATE_TAG_FILE_KEY_OLD "filename" +#define LOCATE_TAG_ANY_KEY "any" + +unsigned +locate_parse_type(const char *str) +{ + if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY) || + 0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY_OLD)) + return LOCATE_TAG_FILE_TYPE; + + if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY)) + return LOCATE_TAG_ANY_TYPE; + + return tag_name_parse_i(str); +} + +SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case) + :tag(_tag), fold_case(_fold_case), + value(fold_case + ? g_utf8_casefold(_value, -1) + : g_strdup(_value)) +{ +} + +SongFilter::Item::~Item() +{ + g_free(value); +} + +bool +SongFilter::Item::StringMatch(const char *s) const +{ + assert(value != nullptr); + assert(s != nullptr); + + if (fold_case) { + char *p = g_utf8_casefold(s, -1); + const bool result = strstr(p, value) != NULL; + g_free(p); + return result; + } else { + return strcmp(s, value) == 0; + } +} + +bool +SongFilter::Item::Match(const tag_item &item) const +{ + return (tag == LOCATE_TAG_ANY_TYPE || (unsigned)item.type == tag) && + StringMatch(item.value); +} + +bool +SongFilter::Item::Match(const struct tag &_tag) const +{ + bool visited_types[TAG_NUM_OF_ITEM_TYPES]; + std::fill(visited_types, visited_types + TAG_NUM_OF_ITEM_TYPES, false); + + for (unsigned i = 0; i < _tag.num_items; i++) { + visited_types[_tag.items[i]->type] = true; + + if (Match(*_tag.items[i])) + return true; + } + + /** If the search critieron was not visited during the sweep + * through the song's tag, it means this field is absent from + * the tag or empty. Thus, if the searched string is also + * empty (first char is a \0), then it's a match as well and + * we should return true. + */ + if (*value == 0 && tag < TAG_NUM_OF_ITEM_TYPES && + !visited_types[tag]) + return true; + + return false; +} + +bool +SongFilter::Item::Match(const song &song) const +{ + if (tag == LOCATE_TAG_FILE_TYPE || tag == LOCATE_TAG_ANY_TYPE) { + char *uri = song_get_uri(&song); + const bool result = StringMatch(uri); + g_free(uri); + + if (result || tag == LOCATE_TAG_FILE_TYPE) + return result; + } + + return song.tag != NULL && Match(*song.tag); +} + +SongFilter::SongFilter(unsigned tag, const char *value, bool fold_case) +{ + items.push_back(Item(tag, value, fold_case)); +} + +SongFilter::~SongFilter() +{ + /* this destructor exists here just so it won't get inlined */ +} + +bool +SongFilter::Parse(const char *tag_string, const char *value, bool fold_case) +{ + unsigned tag = locate_parse_type(tag_string); + if (tag == TAG_NUM_OF_ITEM_TYPES) + return false; + + items.push_back(Item(tag, value, fold_case)); + return true; +} + +bool +SongFilter::Parse(unsigned argc, char *argv[], bool fold_case) +{ + if (argc == 0 || argc % 2 != 0) + return false; + + for (unsigned i = 0; i < argc; i += 2) + if (!Parse(argv[i], argv[i + 1], fold_case)) + return false; + + return true; +} + +bool +SongFilter::Match(const song &song) const +{ + for (const auto &i : items) + if (!i.Match(song)) + return false; + + return true; +} diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx new file mode 100644 index 00000000..afec8130 --- /dev/null +++ b/src/SongFilter.hxx @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SONG_FILTER_HXX +#define MPD_SONG_FILTER_HXX + +#include "gcc.h" + +#include <list> + +#include <stdint.h> + +#define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10 +#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20 + +struct tag; +struct tag_item; +struct song; + +class SongFilter { + class Item { + uint8_t tag; + + bool fold_case; + + char *value; + + public: + gcc_nonnull(3) + Item(unsigned tag, const char *value, bool fold_case=false); + + Item(const Item &other) = delete; + + Item(Item &&other) + :tag(other.tag), fold_case(other.fold_case), + value(other.value) { + other.value = nullptr; + } + + ~Item(); + + Item &operator=(const Item &other) = delete; + + unsigned GetTag() const { + return tag; + } + + gcc_pure gcc_nonnull(2) + bool StringMatch(const char *s) const; + + gcc_pure + bool Match(const tag_item &tag_item) const; + + gcc_pure + bool Match(const struct tag &tag) const; + + gcc_pure + bool Match(const song &song) const; + }; + + std::list<Item> items; + +public: + SongFilter() = default; + + gcc_nonnull(3) + SongFilter(unsigned tag, const char *value, bool fold_case=false); + + ~SongFilter(); + + gcc_nonnull(2,3) + bool Parse(const char *tag, const char *value, bool fold_case=false); + + gcc_nonnull(3) + bool Parse(unsigned argc, char *argv[], bool fold_case=false); + + gcc_pure + bool Match(const tag &tag) const; + + gcc_pure + bool Match(const song &song) const; +}; + +/** + * @return #TAG_NUM_OF_ITEM_TYPES on error + */ +gcc_pure +unsigned +locate_parse_type(const char *str); + +#endif diff --git a/src/notify.h b/src/SongPointer.hxx index 40821690..c80f96f4 100644 --- a/src/notify.h +++ b/src/SongPointer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,37 +17,47 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_NOTIFY_H -#define MPD_NOTIFY_H +#ifndef MPD_SONG_POINTER_HXX +#define MPD_SONG_POINTER_HXX -#include <glib.h> +#include "song.h" -#include <stdbool.h> +#include <utility> -struct notify { - GMutex *mutex; - GCond *cond; - bool pending; -}; +class SongPointer { + struct song *song; -void notify_init(struct notify *notify); +public: + explicit SongPointer(struct song *_song) + :song(_song) {} -void notify_deinit(struct notify *notify); + SongPointer(const SongPointer &) = delete; -/** - * Wait for a notification. Return immediately if we have already - * been notified since we last returned from notify_wait(). - */ -void notify_wait(struct notify *notify); + SongPointer(SongPointer &&other):song(other.song) { + other.song = nullptr; + } -/** - * Notify the thread. This function never blocks. - */ -void notify_signal(struct notify *notify); + ~SongPointer() { + if (song != nullptr) + song_free(song); + } -/** - * Clears a pending notification. - */ -void notify_clear(struct notify *notify); + SongPointer &operator=(const SongPointer &) = delete; + + SongPointer &operator=(SongPointer &&other) { + std::swap(song, other.song); + return *this; + } + + operator const struct song *() const { + return song; + } + + struct song *Steal() { + auto result = song; + song = nullptr; + return result; + } +}; #endif diff --git a/src/song_print.c b/src/SongPrint.cxx index fb608a8b..126e5b9f 100644 --- a/src/song_print.c +++ b/src/SongPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,20 +18,26 @@ */ #include "config.h" -#include "song_print.h" +#include "SongPrint.hxx" #include "song.h" -#include "directory.h" -#include "tag_print.h" -#include "client.h" +#include "Directory.hxx" +#include "TimePrint.hxx" +#include "TagPrint.hxx" +#include "Mapper.hxx" +#include "Client.hxx" + +extern "C" { #include "uri.h" -#include "mapper.h" +} + +#include <glib.h> void -song_print_uri(struct client *client, struct song *song) +song_print_uri(Client *client, struct song *song) { - if (song_in_database(song) && !directory_is_root(song->parent)) { + if (song_in_database(song) && !song->parent->IsRoot()) { client_printf(client, "%s%s/%s\n", SONG_FILE, - directory_get_path(song->parent), song->uri); + song->parent->GetPath(), song->uri); } else { char *allocated; const char *uri; @@ -48,7 +54,7 @@ song_print_uri(struct client *client, struct song *song) } void -song_print_info(struct client *client, struct song *song) +song_print_info(Client *client, struct song *song) { song_print_uri(client, song); @@ -63,32 +69,8 @@ song_print_info(struct client *client, struct song *song) song->start_ms / 1000, song->start_ms % 1000); - if (song->mtime > 0) { -#ifndef G_OS_WIN32 - struct tm tm; -#endif - const struct tm *tm2; - -#ifdef G_OS_WIN32 - tm2 = gmtime(&song->mtime); -#else - tm2 = gmtime_r(&song->mtime, &tm); -#endif - - if (tm2 != NULL) { - char timestamp[32]; - - strftime(timestamp, sizeof(timestamp), -#ifdef G_OS_WIN32 - "%Y-%m-%dT%H:%M:%SZ", -#else - "%FT%TZ", -#endif - tm2); - client_printf(client, "Last-Modified: %s\n", - timestamp); - } - } + if (song->mtime > 0) + time_print(client, "Last-Modified", song->mtime); if (song->tag) tag_print(client, song->tag); diff --git a/src/SongPrint.hxx b/src/SongPrint.hxx new file mode 100644 index 00000000..49f9478b --- /dev/null +++ b/src/SongPrint.hxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SONG_PRINT_HXX +#define MPD_SONG_PRINT_HXX + +struct song; +class Client; + +void +song_print_info(Client *client, struct song *song); + +void +song_print_uri(Client *client, struct song *song); + +#endif diff --git a/src/song_save.c b/src/SongSave.cxx index 4fcb46e2..2b74d935 100644 --- a/src/song_save.c +++ b/src/SongSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,16 @@ */ #include "config.h" -#include "song_save.h" +#include "SongSave.hxx" #include "song.h" -#include "tag_save.h" -#include "directory.h" +#include "TagSave.hxx" +#include "Directory.hxx" +#include "TextFile.hxx" #include "tag.h" -#include "text_file.h" + +extern "C" { #include "string_util.h" +} #include <glib.h> @@ -60,8 +63,8 @@ song_save(FILE *fp, const struct song *song) } struct song * -song_load(FILE *fp, struct directory *parent, const char *uri, - GString *buffer, GError **error_r) +song_load(TextFile &file, Directory *parent, const char *uri, + GError **error_r) { struct song *song = parent != NULL ? song_file_new(uri, parent) @@ -70,7 +73,7 @@ song_load(FILE *fp, struct directory *parent, const char *uri, enum tag_type type; const char *value; - while ((line = read_text_line(fp, buffer)) != NULL && + while ((line = file.ReadLine()) != NULL && strcmp(line, SONG_END) != 0) { colon = strchr(line, ':'); if (colon == NULL || colon == line) { diff --git a/src/song_save.h b/src/SongSave.hxx index f6ecbbfe..3b0c3319 100644 --- a/src/song_save.h +++ b/src/SongSave.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,17 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_SONG_SAVE_H -#define MPD_SONG_SAVE_H +#ifndef MPD_SONG_SAVE_HXX +#define MPD_SONG_SAVE_HXX -#include <glib.h> +#include "gerror.h" #include <stdio.h> #define SONG_BEGIN "song_begin: " struct song; -struct directory; +struct Directory; +class TextFile; void song_save(FILE *fp, const struct song *song); @@ -41,7 +42,7 @@ song_save(FILE *fp, const struct song *song); * @return true on success, false on error */ struct song * -song_load(FILE *fp, struct directory *parent, const char *uri, - GString *buffer, GError **error_r); +song_load(TextFile &file, Directory *parent, const char *uri, + GError **error_r); #endif diff --git a/src/song_sticker.c b/src/SongSticker.cxx index 78025906..86385fc3 100644 --- a/src/song_sticker.c +++ b/src/SongSticker.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,10 @@ */ #include "config.h" -#include "song_sticker.h" +#include "SongSticker.hxx" +#include "StickerDatabase.hxx" #include "song.h" -#include "directory.h" -#include "sticker.h" +#include "Directory.hxx" #include <glib.h> @@ -109,46 +109,43 @@ sticker_song_get(const struct song *song) } struct sticker_song_find_data { - struct directory *directory; + Directory *directory; const char *base_uri; size_t base_uri_length; void (*func)(struct song *song, const char *value, - gpointer user_data); - gpointer user_data; + void *user_data); + void *user_data; }; static void -sticker_song_find_cb(const char *uri, const char *value, gpointer user_data) +sticker_song_find_cb(const char *uri, const char *value, void *user_data) { - struct sticker_song_find_data *data = user_data; - struct song *song; + 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 = directory_lookup_song(data->directory, - uri + data->base_uri_length); + song *song = data->directory->LookupSong(uri + data->base_uri_length); if (song != NULL) data->func(song, value, data->user_data); } bool -sticker_song_find(struct directory *directory, const char *name, +sticker_song_find(Directory *directory, const char *name, void (*func)(struct song *song, const char *value, - gpointer user_data), - gpointer user_data) + void *user_data), + void *user_data) { - struct sticker_song_find_data data = { - .directory = directory, - .func = func, - .user_data = user_data, - }; - char *allocated; - bool success; + struct sticker_song_find_data data; + data.directory = directory; + data.func = func; + data.user_data = user_data; - data.base_uri = directory_get_path(directory); + char *allocated; + data.base_uri = directory->GetPath(); if (*data.base_uri != 0) /* append slash to base_uri */ data.base_uri = allocated = @@ -159,8 +156,8 @@ sticker_song_find(struct directory *directory, const char *name, data.base_uri_length = strlen(data.base_uri); - success = sticker_find("song", data.base_uri, name, - sticker_song_find_cb, &data); + bool success = sticker_find("song", data.base_uri, name, + sticker_song_find_cb, &data); g_free(allocated); return success; diff --git a/src/song_sticker.h b/src/SongSticker.hxx index 20ae68ce..07539b40 100644 --- a/src/song_sticker.h +++ b/src/SongSticker.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,14 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef SONG_STICKER_H -#define SONG_STICKER_H +#ifndef MPD_SONG_STICKER_HXX +#define MPD_SONG_STICKER_HXX -#include <stdbool.h> -#include <glib.h> +#include "gerror.h" struct song; -struct directory; +struct Directory; struct sticker; /** @@ -76,9 +75,9 @@ sticker_song_get(const struct song *song); * failure */ bool -sticker_song_find(struct directory *directory, const char *name, +sticker_song_find(Directory *directory, const char *name, void (*func)(struct song *song, const char *value, - gpointer user_data), - gpointer user_data); + void *user_data), + void *user_data); #endif diff --git a/src/song_update.c b/src/SongUpdate.cxx index 37f502a2..186512ff 100644 --- a/src/song_update.c +++ b/src/SongUpdate.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,17 +18,26 @@ */ #include "config.h" /* must be first for large file support */ + +extern "C" { #include "song.h" #include "uri.h" -#include "directory.h" -#include "mapper.h" -#include "decoder_list.h" +} + +#include "Directory.hxx" +#include "Mapper.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "tag.h" +#include "input_stream.h" #include "decoder_plugin.h" +#include "DecoderList.hxx" + +extern "C" { #include "tag_ape.h" #include "tag_id3.h" -#include "tag.h" #include "tag_handler.h" -#include "input_stream.h" +} #include <glib.h> @@ -38,16 +47,16 @@ #include <stdio.h> struct song * -song_file_load(const char *path, struct directory *parent) +song_file_load(const char *path_utf8, Directory *parent) { struct song *song; bool ret; - assert((parent == NULL) == g_path_is_absolute(path)); - assert(!uri_has_scheme(path)); - assert(strchr(path, '\n') == NULL); + assert((parent == NULL) == g_path_is_absolute(path_utf8)); + assert(!uri_has_scheme(path_utf8)); + assert(strchr(path_utf8, '\n') == NULL); - song = song_file_new(path, parent); + song = song_file_new(path_utf8, parent); //in archive ? if (parent != NULL && parent->device == DEVICE_INARCHIVE) { @@ -78,7 +87,6 @@ bool song_file_update(struct song *song) { const char *suffix; - char *path_fs; const struct decoder_plugin *plugin; struct stat st; struct input_stream *is = NULL; @@ -95,8 +103,8 @@ song_file_update(struct song *song) if (plugin == NULL) return false; - path_fs = map_song_fs(song); - if (path_fs == NULL) + const Path path_fs = map_song_fs(song); + if (path_fs.IsNull()) return false; if (song->tag != NULL) { @@ -104,25 +112,19 @@ song_file_update(struct song *song) song->tag = NULL; } - if (stat(path_fs, &st) < 0 || !S_ISREG(st.st_mode)) { - g_free(path_fs); + if (!StatFile(path_fs, st) || !S_ISREG(st.st_mode)) { return false; } song->mtime = st.st_mtime; - GMutex *mutex = NULL; - GCond *cond; -#if !GCC_CHECK_VERSION(4, 2) - /* work around "may be used uninitialized in this function" - false positive */ - cond = NULL; -#endif + Mutex mutex; + Cond cond; do { /* load file tag */ song->tag = tag_new(); - if (decoder_plugin_scan_file(plugin, path_fs, + if (decoder_plugin_scan_file(plugin, path_fs.c_str(), &full_tag_handler, song->tag)) break; @@ -134,9 +136,8 @@ song_file_update(struct song *song) /* open the input_stream (if not already open) */ if (is == NULL) { - mutex = g_mutex_new(); - cond = g_cond_new(); - is = input_stream_open(path_fs, mutex, cond, + is = input_stream_open(path_fs.c_str(), + mutex, cond, NULL); } @@ -161,15 +162,10 @@ song_file_update(struct song *song) if (is != NULL) input_stream_close(is); - if (mutex != NULL) { - g_cond_free(cond); - g_mutex_free(mutex); - } - if (song->tag != NULL && tag_is_empty(song->tag)) - tag_scan_fallback(path_fs, &full_tag_handler, song->tag); + tag_scan_fallback(path_fs.c_str(), &full_tag_handler, + song->tag); - g_free(path_fs); return song->tag != NULL; } @@ -187,7 +183,7 @@ song_file_update_inarchive(struct song *song) if (suffix == NULL) return false; - plugin = decoder_plugin_from_suffix(suffix, false); + plugin = decoder_plugin_from_suffix(suffix, nullptr); if (plugin == NULL) return false; diff --git a/src/StateFile.cxx b/src/StateFile.cxx new file mode 100644 index 00000000..a15eb722 --- /dev/null +++ b/src/StateFile.cxx @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "StateFile.hxx" +#include "OutputState.hxx" +#include "PlaylistState.hxx" +#include "TextFile.hxx" +#include "Partition.hxx" +#include "Volume.hxx" +#include "event/Loop.hxx" + +#include <glib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "state_file" + +StateFile::StateFile(Path &&_path, const char *_path_utf8, + Partition &_partition, EventLoop &_loop) + :TimeoutMonitor(_loop), path(std::move(_path)), path_utf8(_path_utf8), + partition(_partition), + prev_volume_version(0), prev_output_version(0), + prev_playlist_version(0) +{ + ScheduleSeconds(5 * 60); +} + +void +StateFile::Write() +{ + g_debug("Saving state file %s", path_utf8.c_str()); + + FILE *fp = FOpen(path, FOpenMode::WriteText); + if (G_UNLIKELY(!fp)) { + g_warning("failed to create %s: %s", + path_utf8.c_str(), g_strerror(errno)); + return; + } + + save_sw_volume_state(fp); + audio_output_state_save(fp); + playlist_state_save(fp, &partition.playlist, &partition.pc); + + fclose(fp); + + prev_volume_version = sw_volume_state_get_hash(); + prev_output_version = audio_output_state_get_version(); + prev_playlist_version = playlist_state_get_hash(&partition.playlist, + &partition.pc); +} + +void +StateFile::Read() +{ + bool success; + + g_debug("Loading state file %s", path_utf8.c_str()); + + TextFile file(path); + if (file.HasFailed()) { + g_warning("failed to open %s: %s", + path_utf8.c_str(), g_strerror(errno)); + return; + } + + const char *line; + while ((line = file.ReadLine()) != NULL) { + success = read_sw_volume_state(line) || + audio_output_state_read(line) || + playlist_state_restore(line, file, &partition.playlist, + &partition.pc); + if (!success) + g_warning("Unrecognized line in state file: %s", line); + } + + prev_volume_version = sw_volume_state_get_hash(); + prev_output_version = audio_output_state_get_version(); + prev_playlist_version = playlist_state_get_hash(&partition.playlist, + &partition.pc); +} + +inline void +StateFile::AutoWrite() +{ + if (prev_volume_version == sw_volume_state_get_hash() && + prev_output_version == audio_output_state_get_version() && + prev_playlist_version == playlist_state_get_hash(&partition.playlist, + &partition.pc)) + /* nothing has changed - don't save the state file, + don't spin up the hard disk */ + return; + + Write(); +} + +/** + * This function is called every 5 minutes by the GLib main loop, and + * saves the state file. + */ +bool +StateFile::OnTimeout() +{ + AutoWrite(); + return true; +} diff --git a/src/state_file.h b/src/StateFile.hxx index 4c4f881c..9ec97d51 100644 --- a/src/state_file.h +++ b/src/StateFile.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,17 +17,40 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_STATE_FILE_H -#define MPD_STATE_FILE_H +#ifndef MPD_STATE_FILE_HXX +#define MPD_STATE_FILE_HXX -struct player_control; +#include "event/TimeoutMonitor.hxx" +#include "fs/Path.hxx" +#include "gcc.h" -void -state_file_init(const char *path, struct player_control *pc); +#include <string> -void -state_file_finish(struct player_control *pc); +struct Partition; -void write_state_file(void); +class StateFile final : private TimeoutMonitor { + Path path; + std::string path_utf8; + + Partition &partition; + + /** + * These version numbers determine whether we need to save the state + * file. If nothing has changed, we won't let the hard drive spin up. + */ + unsigned prev_volume_version, prev_output_version, + prev_playlist_version; + +public: + StateFile(Path &&path, const char *path_utf8, + Partition &partition, EventLoop &loop); + + void Read(); + void Write(); + void AutoWrite(); + +private: + virtual bool OnTimeout() override; +}; #endif /* STATE_FILE_H */ diff --git a/src/Stats.cxx b/src/Stats.cxx new file mode 100644 index 00000000..354d26c5 --- /dev/null +++ b/src/Stats.cxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +extern "C" { +#include "stats.h" +} + +#include "PlayerControl.hxx" +#include "ClientInternal.hxx" +#include "DatabaseSelection.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseSimple.hxx" + +struct stats stats; + +void stats_global_init(void) +{ + stats.timer = g_timer_new(); +} + +void stats_global_finish(void) +{ + g_timer_destroy(stats.timer); +} + +void stats_update(void) +{ + GError *error = nullptr; + + DatabaseStats stats2; + + const DatabaseSelection selection("", true); + if (GetDatabase()->GetStats(selection, stats2, &error)) { + stats.song_count = stats2.song_count; + stats.song_duration = stats2.total_duration; + stats.artist_count = stats2.artist_count; + stats.album_count = stats2.album_count; + } else { + g_warning("%s", error->message); + g_error_free(error); + + stats.song_count = 0; + stats.song_duration = 0; + stats.artist_count = 0; + stats.album_count = 0; + } +} + +void +stats_print(Client *client) +{ + client_printf(client, + "artists: %u\n" + "albums: %u\n" + "songs: %i\n" + "uptime: %li\n" + "playtime: %li\n" + "db_playtime: %li\n", + stats.artist_count, + stats.album_count, + stats.song_count, + (long)g_timer_elapsed(stats.timer, NULL), + (long)(client->player_control->GetTotalPlayTime() + 0.5), + stats.song_duration); + + if (db_is_simple()) + client_printf(client, + "db_update: %li\n", + (long)db_get_mtime()); +} diff --git a/src/StickerCommands.cxx b/src/StickerCommands.cxx new file mode 100644 index 00000000..d13647c3 --- /dev/null +++ b/src/StickerCommands.cxx @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "StickerCommands.hxx" +#include "SongPrint.hxx" +#include "DatabaseLock.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" +#include "DatabaseSimple.hxx" +#include "SongSticker.hxx" +#include "StickerPrint.hxx" +#include "StickerDatabase.hxx" +#include "CommandError.hxx" +#include "protocol/Result.hxx" + +#include <string.h> + +struct sticker_song_find_data { + Client *client; + const char *name; +}; + +static void +sticker_song_find_print_cb(struct song *song, const char *value, + gpointer user_data) +{ + struct sticker_song_find_data *data = + (struct sticker_song_find_data *)user_data; + + song_print_uri(data->client, song); + sticker_print_value(data->client, data->name, value); +} + +static enum command_return +handle_sticker_song(Client *client, int argc, char *argv[]) +{ + GError *error = nullptr; + const Database *db = GetDatabase(&error); + if (db == nullptr) + return print_error(client, error); + + /* get song song_id key */ + if (argc == 5 && strcmp(argv[1], "get") == 0) { + song *song = db->GetSong(argv[3], &error); + if (song == nullptr) + return print_error(client, error); + + char *value = sticker_song_get_value(song, argv[4]); + db->ReturnSong(song); + if (value == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "no such sticker"); + return COMMAND_RETURN_ERROR; + } + + sticker_print_value(client, argv[4], value); + g_free(value); + + return COMMAND_RETURN_OK; + /* list song song_id */ + } else if (argc == 4 && strcmp(argv[1], "list") == 0) { + song *song = db->GetSong(argv[3], &error); + if (song == nullptr) + return print_error(client, error); + + sticker *sticker = sticker_song_get(song); + db->ReturnSong(song); + if (sticker) { + sticker_print(client, sticker); + sticker_free(sticker); + } + + return COMMAND_RETURN_OK; + /* set song song_id id key */ + } else if (argc == 6 && strcmp(argv[1], "set") == 0) { + song *song = db->GetSong(argv[3], &error); + if (song == nullptr) + return print_error(client, error); + + bool ret = sticker_song_set_value(song, argv[4], argv[5]); + db->ReturnSong(song); + if (!ret) { + command_error(client, ACK_ERROR_SYSTEM, + "failed to set sticker value"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; + /* delete song song_id [key] */ + } else if ((argc == 4 || argc == 5) && + strcmp(argv[1], "delete") == 0) { + song *song = db->GetSong(argv[3], &error); + if (song == nullptr) + return print_error(client, error); + + bool ret = argc == 4 + ? sticker_song_delete(song) + : sticker_song_delete_value(song, argv[4]); + db->ReturnSong(song); + if (!ret) { + command_error(client, ACK_ERROR_SYSTEM, + "no such sticker"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; + /* find song dir key */ + } else if (argc == 5 && strcmp(argv[1], "find") == 0) { + /* "sticker find song a/directory name" */ + bool success; + struct sticker_song_find_data data = { + client, + argv[4], + }; + + db_lock(); + Directory *directory = db_get_directory(argv[3]); + if (directory == NULL) { + db_unlock(); + command_error(client, ACK_ERROR_NO_EXIST, + "no such directory"); + return COMMAND_RETURN_ERROR; + } + + success = sticker_song_find(directory, data.name, + sticker_song_find_print_cb, &data); + db_unlock(); + if (!success) { + command_error(client, ACK_ERROR_SYSTEM, + "failed to set search sticker database"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; + } else { + command_error(client, ACK_ERROR_ARG, "bad request"); + return COMMAND_RETURN_ERROR; + } +} + +enum command_return +handle_sticker(Client *client, int argc, char *argv[]) +{ + assert(argc >= 4); + + if (!sticker_enabled()) { + command_error(client, ACK_ERROR_UNKNOWN, + "sticker database is disabled"); + return COMMAND_RETURN_ERROR; + } + + if (strcmp(argv[2], "song") == 0) + return handle_sticker_song(client, argc, argv); + else { + command_error(client, ACK_ERROR_ARG, + "unknown sticker domain"); + return COMMAND_RETURN_ERROR; + } +} diff --git a/src/StickerCommands.hxx b/src/StickerCommands.hxx new file mode 100644 index 00000000..840bd33d --- /dev/null +++ b/src/StickerCommands.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_STICKER_COMMANDS_HXX +#define MPD_STICKER_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_sticker(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/sticker.c b/src/StickerDatabase.cxx index 346a827a..2d77e4b6 100644 --- a/src/sticker.c +++ b/src/StickerDatabase.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,11 @@ */ #include "config.h" -#include "sticker.h" -#include "idle.h" +#include "StickerDatabase.hxx" +#include "Idle.hxx" + +#include <string> +#include <map> #include <glib.h> #include <sqlite3.h> @@ -33,7 +36,7 @@ #endif struct sticker { - GHashTable *table; + std::map<std::string, std::string> table; }; enum sticker_sql { @@ -47,19 +50,19 @@ enum sticker_sql { }; static const char *const sticker_sql[] = { - [STICKER_SQL_GET] = + //[STICKER_SQL_GET] = "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_LIST] = + //[STICKER_SQL_LIST] = "SELECT name,value FROM sticker WHERE type=? AND uri=?", - [STICKER_SQL_UPDATE] = + //[STICKER_SQL_UPDATE] = "UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_INSERT] = + //[STICKER_SQL_INSERT] = "INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)", - [STICKER_SQL_DELETE] = + //[STICKER_SQL_DELETE] = "DELETE FROM sticker WHERE type=? AND uri=?", - [STICKER_SQL_DELETE_VALUE] = + //[STICKER_SQL_DELETE_VALUE] = "DELETE FROM sticker WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_FIND] = + //[STICKER_SQL_FIND] = "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?", }; @@ -226,13 +229,12 @@ sticker_load_value(const char *type, const char *uri, const char *name) } static bool -sticker_list_values(GHashTable *hash, const char *type, const char *uri) +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; - char *name, *value; - assert(hash != NULL); assert(type != NULL); assert(uri != NULL); assert(sticker_enabled()); @@ -256,10 +258,13 @@ sticker_list_values(GHashTable *hash, const char *type, const char *uri) do { ret = sqlite3_step(stmt); switch (ret) { + const char *name, *value; + case SQLITE_ROW: - name = g_strdup((const char*)sqlite3_column_text(stmt, 0)); - value = g_strdup((const char*)sqlite3_column_text(stmt, 1)); - g_hash_table_insert(hash, name, value); + 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; @@ -518,44 +523,20 @@ sticker_delete_value(const char *type, const char *uri, const char *name) return ret > 0; } -static struct sticker * -sticker_new(void) -{ - struct sticker *sticker = g_new(struct sticker, 1); - - sticker->table = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_free); - return sticker; -} - void sticker_free(struct sticker *sticker) { - assert(sticker != NULL); - assert(sticker->table != NULL); - - g_hash_table_destroy(sticker->table); - g_free(sticker); + delete sticker; } const char * sticker_get_value(const struct sticker *sticker, const char *name) { - return g_hash_table_lookup(sticker->table, name); -} - -struct sticker_foreach_data { - void (*func)(const char *name, const char *value, - gpointer user_data); - gpointer user_data; -}; - -static void -sticker_foreach_func(gpointer key, gpointer value, gpointer user_data) -{ - struct sticker_foreach_data *data = user_data; + auto i = sticker->table.find(name); + if (i == sticker->table.end()) + return nullptr; - data->func(key, value, data->user_data); + return i->second.c_str(); } void @@ -564,33 +545,23 @@ sticker_foreach(const struct sticker *sticker, gpointer user_data), gpointer user_data) { - struct sticker_foreach_data data = { - .func = func, - .user_data = user_data, - }; - - g_hash_table_foreach(sticker->table, sticker_foreach_func, &data); + 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) { - struct sticker *sticker = sticker_new(); - bool success; + sticker s; - success = sticker_list_values(sticker->table, type, uri); - if (!success) { - sticker_free(sticker); + if (!sticker_list_values(s.table, type, uri)) return NULL; - } - if (g_hash_table_size(sticker->table) == 0) { + if (s.table.empty()) /* don't return empty sticker objects */ - sticker_free(sticker); return NULL; - } - return sticker; + return new sticker(std::move(s)); } bool diff --git a/src/sticker.h b/src/StickerDatabase.hxx index 5545206a..90ff9b06 100644 --- a/src/sticker.h +++ b/src/StickerDatabase.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -39,12 +39,10 @@ * */ -#ifndef STICKER_H -#define STICKER_H +#ifndef MPD_STICKER_DATABASE_HXX +#define MPD_STICKER_DATABASE_HXX -#include <glib.h> - -#include <stdbool.h> +#include "gerror.h" struct sticker; @@ -127,8 +125,8 @@ sticker_get_value(const struct sticker *sticker, const char *name); void sticker_foreach(const struct sticker *sticker, void (*func)(const char *name, const char *value, - gpointer user_data), - gpointer user_data); + void *user_data), + void *user_data); /** * Loads the sticker for the specified resource. @@ -153,7 +151,7 @@ sticker_load(const char *type, const char *uri); bool sticker_find(const char *type, const char *base_uri, const char *name, void (*func)(const char *uri, const char *value, - gpointer user_data), - gpointer user_data); + void *user_data), + void *user_data); #endif diff --git a/src/sticker_print.c b/src/StickerPrint.cxx index 65e79513..1708ee4f 100644 --- a/src/sticker_print.c +++ b/src/StickerPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,27 +18,27 @@ */ #include "config.h" -#include "sticker_print.h" -#include "sticker.h" -#include "client.h" +#include "StickerPrint.hxx" +#include "StickerDatabase.hxx" +#include "Client.hxx" void -sticker_print_value(struct client *client, +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, gpointer data) +print_sticker_cb(const char *name, const char *value, void *data) { - struct client *client = data; + Client *client = (Client *)data; sticker_print_value(client, name, value); } void -sticker_print(struct client *client, const struct sticker *sticker) +sticker_print(Client *client, const struct sticker *sticker) { sticker_foreach(sticker, print_sticker_cb, client); } diff --git a/src/sticker_print.h b/src/StickerPrint.hxx index 7398c808..27225a49 100644 --- a/src/sticker_print.h +++ b/src/StickerPrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,23 +17,22 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_STICKER_PRINT_H -#define MPD_STICKER_PRINT_H +#ifndef MPD_STICKER_PRINT_HXX +#define MPD_STICKER_PRINT_HXX struct sticker; -struct client; +class Client; /** * Sends one sticker value to the client. */ void -sticker_print_value(struct client *client, - const char *name, const char *value); +sticker_print_value(Client *client, const char *name, const char *value); /** * Sends all sticker values to the client. */ void -sticker_print(struct client *client, const struct sticker *sticker); +sticker_print(Client *client, const struct sticker *sticker); #endif @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 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 "tag.h" -#include "tag_internal.h" -#include "tag_pool.h" +#include "TagInternal.hxx" +#include "TagPool.hxx" #include "conf.h" #include "song.h" #include "mpd_error.h" @@ -43,38 +43,15 @@ static struct { struct tag_item *items[BULK_MAX]; } bulk; -const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = { - [TAG_ARTIST] = "Artist", - [TAG_ARTIST_SORT] = "ArtistSort", - [TAG_ALBUM] = "Album", - [TAG_ALBUM_ARTIST] = "AlbumArtist", - [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort", - [TAG_TITLE] = "Title", - [TAG_TRACK] = "Track", - [TAG_NAME] = "Name", - [TAG_GENRE] = "Genre", - [TAG_DATE] = "Date", - [TAG_COMPOSER] = "Composer", - [TAG_PERFORMER] = "Performer", - [TAG_COMMENT] = "Comment", - [TAG_DISC] = "Disc", - - /* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */ - [TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID", - [TAG_MUSICBRAINZ_ALBUMID] = "MUSICBRAINZ_ALBUMID", - [TAG_MUSICBRAINZ_ALBUMARTISTID] = "MUSICBRAINZ_ALBUMARTISTID", - [TAG_MUSICBRAINZ_TRACKID] = "MUSICBRAINZ_TRACKID", -}; - bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; enum tag_type tag_name_parse(const char *name) { - assert(name != NULL); + assert(name != nullptr); for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - assert(tag_item_names[i] != NULL); + assert(tag_item_names[i] != nullptr); if (strcmp(name, tag_item_names[i]) == 0) return (enum tag_type)i; @@ -86,10 +63,10 @@ tag_name_parse(const char *name) enum tag_type tag_name_parse_i(const char *name) { - assert(name != NULL); + assert(name != nullptr); for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - assert(tag_item_names[i] != NULL); + assert(tag_item_names[i] != nullptr); if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0) return (enum tag_type)i; @@ -117,8 +94,8 @@ void tag_lib_init(void) /* ignore comments by default */ ignore_tag_items[TAG_COMMENT] = true; - value = config_get_string(CONF_METADATA_TO_USE, NULL); - if (value == NULL) + value = config_get_string(CONF_METADATA_TO_USE, nullptr); + if (value == nullptr) return; memset(ignore_tag_items, true, TAG_NUM_OF_ITEM_TYPES); @@ -156,7 +133,7 @@ void tag_lib_init(void) struct tag *tag_new(void) { struct tag *ret = g_new(struct tag, 1); - ret->items = NULL; + ret->items = nullptr; ret->time = -1; ret->has_playlist = false; ret->num_items = 0; @@ -168,9 +145,9 @@ static void tag_delete_item(struct tag *tag, unsigned idx) assert(idx < tag->num_items); tag->num_items--; - g_mutex_lock(tag_pool_lock); + tag_pool_lock.lock(); tag_pool_put_item(tag->items[idx]); - g_mutex_unlock(tag_pool_lock); + tag_pool_lock.unlock(); if (tag->num_items - idx > 0) { memmove(tag->items + idx, tag->items + idx + 1, @@ -178,10 +155,11 @@ static void tag_delete_item(struct tag *tag, unsigned idx) } if (tag->num_items > 0) { - tag->items = g_realloc(tag->items, items_size(tag)); + tag->items = (struct tag_item **) + g_realloc(tag->items, items_size(tag)); } else { g_free(tag->items); - tag->items = NULL; + tag->items = nullptr; } } @@ -200,12 +178,12 @@ void tag_free(struct tag *tag) { int i; - assert(tag != NULL); + assert(tag != nullptr); - g_mutex_lock(tag_pool_lock); + tag_pool_lock.lock(); for (i = tag->num_items; --i >= 0; ) tag_pool_put_item(tag->items[i]); - g_mutex_unlock(tag_pool_lock); + tag_pool_lock.unlock(); if (tag->items == bulk.items) { #ifndef NDEBUG @@ -223,18 +201,20 @@ struct tag *tag_dup(const struct tag *tag) struct tag *ret; if (!tag) - return NULL; + return nullptr; ret = tag_new(); ret->time = tag->time; ret->has_playlist = tag->has_playlist; ret->num_items = tag->num_items; - ret->items = ret->num_items > 0 ? g_malloc(items_size(tag)) : NULL; + ret->items = ret->num_items > 0 + ? (struct tag_item **)g_malloc(items_size(tag)) + : nullptr; - g_mutex_lock(tag_pool_lock); + tag_pool_lock.lock(); for (unsigned i = 0; i < tag->num_items; i++) ret->items[i] = tag_pool_dup_item(tag->items[i]); - g_mutex_unlock(tag_pool_lock); + tag_pool_lock.unlock(); return ret; } @@ -245,17 +225,19 @@ tag_merge(const struct tag *base, const struct tag *add) struct tag *ret; unsigned n; - assert(base != NULL); - assert(add != NULL); + assert(base != nullptr); + assert(add != nullptr); /* allocate new tag object */ ret = tag_new(); ret->time = add->time > 0 ? add->time : base->time; ret->num_items = base->num_items + add->num_items; - ret->items = ret->num_items > 0 ? g_malloc(items_size(ret)) : NULL; + ret->items = ret->num_items > 0 + ? (struct tag_item **)g_malloc(items_size(ret)) + : nullptr; - g_mutex_lock(tag_pool_lock); + tag_pool_lock.lock(); /* copy all items from "add" */ @@ -270,7 +252,7 @@ tag_merge(const struct tag *base, const struct tag *add) if (!tag_has_type(add, base->items[i]->type)) ret->items[n++] = tag_pool_dup_item(base->items[i]); - g_mutex_unlock(tag_pool_lock); + tag_pool_lock.unlock(); assert(n <= ret->num_items); @@ -279,7 +261,8 @@ tag_merge(const struct tag *base, const struct tag *add) assert(n > 0); ret->num_items = n; - ret->items = g_realloc(ret->items, items_size(ret)); + ret->items = (struct tag_item **) + g_realloc(ret->items, items_size(ret)); } return ret; @@ -288,10 +271,10 @@ tag_merge(const struct tag *base, const struct tag *add) struct tag * tag_merge_replace(struct tag *base, struct tag *add) { - if (add == NULL) + if (add == nullptr) return base; - if (base == NULL) + if (base == nullptr) return add; struct tag *tag = tag_merge(base, add); @@ -304,24 +287,24 @@ tag_merge_replace(struct tag *base, struct tag *add) const char * tag_get_value(const struct tag *tag, enum tag_type type) { - assert(tag != NULL); + assert(tag != nullptr); assert(type < TAG_NUM_OF_ITEM_TYPES); for (unsigned i = 0; i < tag->num_items; i++) if (tag->items[i]->type == type) return tag->items[i]->value; - return NULL; + return nullptr; } bool tag_has_type(const struct tag *tag, enum tag_type type) { - return tag_get_value(tag, type) != NULL; + return tag_get_value(tag, type) != nullptr; } bool tag_equal(const struct tag *tag1, const struct tag *tag2) { - if (tag1 == NULL && tag2 == NULL) + if (tag1 == nullptr && tag2 == nullptr) return true; else if (!tag1 || !tag2) return false; @@ -367,16 +350,16 @@ fix_utf8(const char *str, size_t length) char *temp; gsize written; - assert(str != NULL); + assert(str != nullptr); /* check if the string is already valid UTF-8 */ if (g_utf8_validate(str, length, &end)) - return NULL; + return nullptr; /* no, it's not - try to import it from ISO-Latin-1 */ temp = g_convert(str, length, "utf-8", "iso-8859-1", - NULL, &written, NULL); - if (temp != NULL) + nullptr, &written, nullptr); + if (temp != nullptr) /* success! */ return temp; @@ -388,8 +371,8 @@ fix_utf8(const char *str, size_t length) void tag_begin_add(struct tag *tag) { assert(!bulk.busy); - assert(tag != NULL); - assert(tag->items == NULL); + assert(tag != nullptr); + assert(tag->items == nullptr); assert(tag->num_items == 0); #ifndef NDEBUG @@ -406,10 +389,11 @@ void tag_end_add(struct tag *tag) if (tag->num_items > 0) { /* copy the tag items from the bulk list over to a new list (which fits exactly) */ - tag->items = g_malloc(items_size(tag)); + tag->items = (struct tag_item **) + g_malloc(items_size(tag)); memcpy(tag->items, bulk.items, items_size(tag)); } else - tag->items = NULL; + tag->items = nullptr; } #ifndef NDEBUG @@ -430,12 +414,12 @@ find_non_printable(const char *p, size_t length) if (char_is_non_printable(p[i])) return p + i; - return NULL; + return nullptr; } /** * Clears all non-printable characters, convert them to space. - * Returns NULL if nothing needs to be cleared. + * Returns nullptr if nothing needs to be cleared. */ static char * clear_non_printable(const char *p, size_t length) @@ -443,8 +427,8 @@ clear_non_printable(const char *p, size_t length) const char *first = find_non_printable(p, length); char *dest; - if (first == NULL) - return NULL; + if (first == nullptr) + return nullptr; dest = g_strndup(p, length); @@ -461,13 +445,13 @@ fix_tag_value(const char *p, size_t length) char *utf8, *cleared; utf8 = fix_utf8(p, length); - if (utf8 != NULL) { + if (utf8 != nullptr) { p = utf8; length = strlen(p); } cleared = clear_non_printable(p, length); - if (cleared == NULL) + if (cleared == nullptr) cleared = utf8; else g_free(utf8); @@ -483,7 +467,7 @@ tag_add_item_internal(struct tag *tag, enum tag_type type, char *p; p = fix_tag_value(value, len); - if (p != NULL) { + if (p != nullptr) { value = p; len = strlen(value); } @@ -492,19 +476,20 @@ tag_add_item_internal(struct tag *tag, enum tag_type type, if (tag->items != bulk.items) /* bulk mode disabled */ - tag->items = g_realloc(tag->items, items_size(tag)); + tag->items = (struct tag_item **) + g_realloc(tag->items, items_size(tag)); else if (tag->num_items >= BULK_MAX) { /* bulk list already full - switch back to non-bulk */ assert(bulk.busy); - tag->items = g_malloc(items_size(tag)); + tag->items = (struct tag_item **)g_malloc(items_size(tag)); memcpy(tag->items, bulk.items, items_size(tag) - sizeof(struct tag_item *)); } - g_mutex_lock(tag_pool_lock); + tag_pool_lock.lock(); tag->items[i] = tag_pool_get_item(type, value, len); - g_mutex_unlock(tag_pool_lock); + tag_pool_lock.unlock(); g_free(p); } diff --git a/src/tag_file.c b/src/TagFile.cxx index 8d8a0f5f..046778bd 100644 --- a/src/tag_file.c +++ b/src/TagFile.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,13 @@ */ #include "config.h" -#include "tag_file.h" +#include "TagFile.hxx" + +extern "C" { #include "uri.h" -#include "decoder_list.h" +} + +#include "DecoderList.hxx" #include "decoder_plugin.h" #include "input_stream.h" @@ -46,8 +50,8 @@ tag_file_scan(const char *path_fs, return false; struct input_stream *is = NULL; - GMutex *mutex = NULL; - GCond *cond = NULL; + Mutex mutex; + Cond cond; do { /* load file tag */ @@ -59,12 +63,9 @@ tag_file_scan(const char *path_fs, if (plugin->scan_stream != NULL) { /* open the input_stream (if not already open) */ - if (is == NULL) { - mutex = g_mutex_new(); - cond = g_cond_new(); + if (is == nullptr) is = input_stream_open(path_fs, mutex, cond, NULL); - } /* now try the stream_tag() method */ if (is != NULL) { @@ -80,11 +81,8 @@ tag_file_scan(const char *path_fs, plugin = decoder_plugin_from_suffix(suffix, plugin); } while (plugin != NULL); - if (is != NULL) { + if (is != NULL) input_stream_close(is); - g_cond_free(cond); - g_mutex_free(mutex); - } return plugin != NULL; } diff --git a/src/tag_file.h b/src/TagFile.hxx index 8cf1af3c..61f49185 100644 --- a/src/tag_file.h +++ b/src/TagFile.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_TAG_FILE_H -#define MPD_TAG_FILE_H +#ifndef MPD_TAG_FILE_HXX +#define MPD_TAG_FILE_HXX #include "check.h" diff --git a/src/tag_internal.h b/src/TagInternal.hxx index af05cc6b..afce0427 100644 --- a/src/tag_internal.h +++ b/src/TagInternal.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_TAG_INTERNAL_H -#define MPD_TAG_INTERNAL_H +#ifndef MPD_TAG_INTERNAL_HXX +#define MPD_TAG_INTERNAL_HXX #include "tag.h" -#include <stdbool.h> - extern bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; #endif diff --git a/src/TagNames.c b/src/TagNames.c new file mode 100644 index 00000000..eca320af --- /dev/null +++ b/src/TagNames.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "tag.h" + +const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = { + [TAG_ARTIST] = "Artist", + [TAG_ARTIST_SORT] = "ArtistSort", + [TAG_ALBUM] = "Album", + [TAG_ALBUM_ARTIST] = "AlbumArtist", + [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort", + [TAG_TITLE] = "Title", + [TAG_TRACK] = "Track", + [TAG_NAME] = "Name", + [TAG_GENRE] = "Genre", + [TAG_DATE] = "Date", + [TAG_COMPOSER] = "Composer", + [TAG_PERFORMER] = "Performer", + [TAG_COMMENT] = "Comment", + [TAG_DISC] = "Disc", + + /* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */ + [TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID", + [TAG_MUSICBRAINZ_ALBUMID] = "MUSICBRAINZ_ALBUMID", + [TAG_MUSICBRAINZ_ALBUMARTISTID] = "MUSICBRAINZ_ALBUMARTISTID", + [TAG_MUSICBRAINZ_TRACKID] = "MUSICBRAINZ_TRACKID", +}; diff --git a/src/tag_pool.c b/src/TagPool.cxx index eabf3e36..a9b89284 100644 --- a/src/tag_pool.c +++ b/src/TagPool.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,13 @@ */ #include "config.h" -#include "tag_pool.h" +#include "TagPool.hxx" + +#include <glib.h> #include <assert.h> -GMutex *tag_pool_lock = NULL; +Mutex tag_pool_lock; #define NUM_SLOTS 4096 @@ -39,7 +41,7 @@ calc_hash_n(enum tag_type type, const char *p, size_t length) { unsigned hash = 5381; - assert(p != NULL); + assert(p != nullptr); while (length-- > 0) hash = (hash << 5) + hash + *p++; @@ -52,7 +54,7 @@ calc_hash(enum tag_type type, const char *p) { unsigned hash = 5381; - assert(p != NULL); + assert(p != nullptr); while (*p != 0) hash = (hash << 5) + hash + *p++; @@ -72,7 +74,8 @@ static struct slot *slot_alloc(struct slot *next, { struct slot *slot; - slot = g_malloc(sizeof(*slot) - sizeof(slot->item.value) + length + 1); + slot = (struct slot *) + g_malloc(sizeof(*slot) - sizeof(slot->item.value) + length + 1); slot->next = next; slot->ref = 1; slot->item.type = type; @@ -81,26 +84,13 @@ static struct slot *slot_alloc(struct slot *next, return slot; } -void tag_pool_init(void) -{ - g_assert(tag_pool_lock == NULL); - tag_pool_lock = g_mutex_new(); -} - -void tag_pool_deinit(void) -{ - g_assert(tag_pool_lock != NULL); - g_mutex_free(tag_pool_lock); - tag_pool_lock = NULL; -} - struct tag_item * tag_pool_get_item(enum tag_type type, const char *value, size_t length) { struct slot **slot_p, *slot; slot_p = &slots[calc_hash_n(type, value, length) % NUM_SLOTS]; - for (slot = *slot_p; slot != NULL; slot = slot->next) { + for (slot = *slot_p; slot != nullptr; slot = slot->next) { if (slot->item.type == type && length == strlen(slot->item.value) && memcmp(value, slot->item.value, length) == 0 && @@ -153,7 +143,7 @@ void tag_pool_put_item(struct tag_item *item) for (slot_p = &slots[calc_hash(item->type, item->value) % NUM_SLOTS]; *slot_p != slot; slot_p = &(*slot_p)->next) { - assert(*slot_p != NULL); + assert(*slot_p != nullptr); } *slot_p = slot->next; diff --git a/src/tag_pool.h b/src/TagPool.hxx index a96c00d8..649cef6e 100644 --- a/src/tag_pool.h +++ b/src/TagPool.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,21 +17,16 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_TAG_POOL_H -#define MPD_TAG_POOL_H +#ifndef MPD_TAG_POOL_HXX +#define MPD_TAG_POOL_HXX #include "tag.h" +#include "thread/Mutex.hxx" -#include <glib.h> - -extern GMutex *tag_pool_lock; +extern Mutex tag_pool_lock; struct tag_item; -void tag_pool_init(void); - -void tag_pool_deinit(void); - struct tag_item * tag_pool_get_item(enum tag_type type, const char *value, size_t length); diff --git a/src/tag_print.c b/src/TagPrint.cxx index 9a46b247..b3ad07df 100644 --- a/src/tag_print.c +++ b/src/TagPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,13 @@ */ #include "config.h" -#include "tag_print.h" +#include "TagPrint.hxx" #include "tag.h" -#include "tag_internal.h" -#include "client.h" +#include "TagInternal.hxx" #include "song.h" +#include "Client.hxx" -void tag_print_types(struct client *client) +void tag_print_types(Client *client) { int i; @@ -35,7 +35,7 @@ void tag_print_types(struct client *client) } } -void tag_print(struct client *client, const struct tag *tag) +void tag_print(Client *client, const struct tag *tag) { if (tag->time >= 0) client_printf(client, SONG_TIME "%i\n", tag->time); diff --git a/src/tag_print.h b/src/TagPrint.hxx index b9eeeaec..99e1d085 100644 --- a/src/tag_print.h +++ b/src/TagPrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,14 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_TAG_PRINT_H -#define MPD_TAG_PRINT_H +#ifndef MPD_TAG_PRINT_HXX +#define MPD_TAG_PRINT_HXX struct tag; -struct client; +class Client; -void tag_print_types(struct client *client); +void tag_print_types(Client *client); -void tag_print(struct client *client, const struct tag *tag); +void tag_print(Client *client, const struct tag *tag); #endif diff --git a/src/tag_save.c b/src/TagSave.cxx index 2fdaef56..15da9fc4 100644 --- a/src/tag_save.c +++ b/src/TagSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,9 @@ */ #include "config.h" -#include "tag_save.h" +#include "TagSave.hxx" #include "tag.h" -#include "tag_internal.h" +#include "TagInternal.hxx" #include "song.h" void tag_save(FILE *file, const struct tag *tag) diff --git a/src/tag_save.h b/src/TagSave.hxx index 9f6a580c..a7ccfa61 100644 --- a/src/tag_save.h +++ b/src/TagSave.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_TAG_SAVE_H -#define MPD_TAG_SAVE_H +#ifndef MPD_TAG_SAVE_HXX +#define MPD_TAG_SAVE_HXX #include <stdio.h> diff --git a/src/text_file.c b/src/TextFile.cxx index 3674e5ce..4ad59ee4 100644 --- a/src/text_file.c +++ b/src/TextFile.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,27 +18,20 @@ */ #include "config.h" -#include "text_file.h" +#include "TextFile.hxx" #include <assert.h> #include <string.h> char * -read_text_line(FILE *file, GString *buffer) +TextFile::ReadLine() { - enum { - max_length = 512 * 1024, - step = 1024, - }; - gsize length = 0, i; char *p; assert(file != NULL); assert(buffer != NULL); - - if (buffer->allocated_len < step) - g_string_set_size(buffer, step); + assert(buffer->allocated_len >= step); while (buffer->len < max_length) { p = fgets(buffer->str + length, diff --git a/src/TextFile.hxx b/src/TextFile.hxx new file mode 100644 index 00000000..d593e096 --- /dev/null +++ b/src/TextFile.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TEXT_FILE_HXX +#define MPD_TEXT_FILE_HXX + +#include "gcc.h" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" + +#include <glib.h> + +class TextFile { + static constexpr size_t max_length = 512 * 1024; + static constexpr size_t step = 1024; + + FILE *const file; + + GString *const buffer; + +public: + TextFile(const Path &path_fs) + :file(FOpen(path_fs, FOpenMode::ReadText)), + buffer(g_string_sized_new(step)) {} + + TextFile(const TextFile &other) = delete; + + ~TextFile() { + if (file != nullptr) + fclose(file); + + g_string_free(buffer, true); + } + + bool HasFailed() const { + return gcc_unlikely(file == nullptr); + } + + /** + * Reads a line from the input file, and strips trailing + * space. There is a reasonable maximum line length, only to + * prevent denial of service. + * + * @param file the source file, opened in text mode + * @param buffer an allocator for the buffer + * @return a pointer to the line, or NULL on end-of-file or error + */ + char *ReadLine(); +}; + +#endif diff --git a/src/glib_socket.h b/src/TimePrint.cxx index 46fab37d..c5247dd9 100644 --- a/src/glib_socket.h +++ b/src/TimePrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,24 +17,31 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_GLIB_SOCKET_H -#define MPD_GLIB_SOCKET_H +#include "config.h" +#include "TimePrint.hxx" +#include "Client.hxx" #include <glib.h> -/** - * Portable wrapper for g_io_channel_unix_new() or - * g_io_channel_win32_new_socket(). - */ -G_GNUC_MALLOC -static inline GIOChannel * -g_io_channel_new_socket(int fd) +void +time_print(Client *client, const char *name, time_t t) { #ifdef G_OS_WIN32 - return g_io_channel_win32_new_socket(fd); + const struct tm *tm2 = gmtime(&t); #else - return g_io_channel_unix_new(fd); + struct tm tm; + const struct tm *tm2 = gmtime_r(&t, &tm); #endif -} + if (tm2 == NULL) + return; + char buffer[32]; + strftime(buffer, sizeof(buffer), +#ifdef G_OS_WIN32 + "%Y-%m-%dT%H:%M:%SZ", +#else + "%FT%TZ", #endif + tm2); + client_printf(client, "%s: %s\n", name, buffer); +} diff --git a/src/TimePrint.hxx b/src/TimePrint.hxx new file mode 100644 index 00000000..f4510125 --- /dev/null +++ b/src/TimePrint.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TIME_PRINT_HXX +#define MPD_TIME_PRINT_HXX + +#include <time.h> + +class Client; + +/** + * Write a line with a time stamp to the client. + */ +void +time_print(Client *client, const char *name, time_t t); + +#endif diff --git a/src/update_archive.c b/src/UpdateArchive.cxx index 3fb2bc18..133dfd47 100644 --- a/src/update_archive.c +++ b/src/UpdateArchive.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,31 +18,36 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_archive.h" -#include "update_internal.h" -#include "db_lock.h" -#include "directory.h" +#include "UpdateArchive.hxx" +#include "UpdateInternal.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" #include "song.h" -#include "mapper.h" -#include "archive_list.h" -#include "archive_plugin.h" +#include "Mapper.hxx" +#include "fs/Path.hxx" +#include "ArchiveList.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "ArchiveVisitor.hxx" #include <glib.h> #include <string.h> static void -update_archive_tree(struct directory *directory, char *name) +update_archive_tree(Directory *directory, const char *name) { - char *tmp = strchr(name, '/'); + const char *tmp = strchr(name, '/'); if (tmp) { - *tmp = 0; + char *child_name = g_strndup(name, tmp - name); //add dir is not there already db_lock(); - struct directory *subdir = - directory_make_child(directory, name); + Directory *subdir = + directory->MakeChild(child_name); subdir->device = DEVICE_INARCHIVE; db_unlock(); + g_free(child_name); + //create directories first update_archive_tree(subdir, tmp+1); } else { @@ -53,18 +58,18 @@ update_archive_tree(struct directory *directory, char *name) //add file db_lock(); - struct song *song = directory_get_song(directory, name); + struct song *song = directory->FindSong(name); db_unlock(); if (song == NULL) { song = song_file_load(name, directory); if (song != NULL) { db_lock(); - directory_add_song(directory, song); + directory->AddSong(song); db_unlock(); modified = true; g_message("added %s/%s", - directory_get_path(directory), name); + directory->GetPath(), name); } } } @@ -79,12 +84,12 @@ update_archive_tree(struct directory *directory, char *name) * @param plugin the archive plugin which fits this archive type */ static void -update_archive_file2(struct directory *parent, const char *name, +update_archive_file2(Directory *parent, const char *name, const struct stat *st, const struct archive_plugin *plugin) { db_lock(); - struct directory *directory = directory_get_child(parent, name); + Directory *directory = parent->FindChild(name); db_unlock(); if (directory != NULL && directory->mtime == st->st_mtime && @@ -93,25 +98,23 @@ update_archive_file2(struct directory *parent, const char *name, changed since - don't consider updating it */ return; - char *path_fs = map_directory_child_fs(parent, name); + const Path path_fs = map_directory_child_fs(parent, name); /* open archive */ GError *error = NULL; - struct archive_file *file = archive_file_open(plugin, path_fs, &error); + ArchiveFile *file = archive_file_open(plugin, path_fs.c_str(), &error); if (file == NULL) { - g_free(path_fs); g_warning("%s", error->message); g_error_free(error); return; } - g_debug("archive %s opened", path_fs); - g_free(path_fs); + g_debug("archive %s opened", path_fs.c_str()); if (directory == NULL) { g_debug("creating archive directory: %s", name); db_lock(); - directory = directory_new_child(parent, name); + directory = parent->CreateChild(name); /* mark this directory as archive (we use device for this) */ directory->device = DEVICE_INARCHIVE; @@ -120,20 +123,26 @@ update_archive_file2(struct directory *parent, const char *name, directory->mtime = st->st_mtime; - archive_file_scan_reset(file); + class UpdateArchiveVisitor final : public ArchiveVisitor { + Directory *directory; - char *filepath; - while ((filepath = archive_file_scan_next(file)) != NULL) { - /* split name into directory and file */ - g_debug("adding archive file: %s", filepath); - update_archive_tree(directory, filepath); - } + public: + UpdateArchiveVisitor(Directory *_directory) + :directory(_directory) {} + + virtual void VisitArchiveEntry(const char *path_utf8) override { + g_debug("adding archive file: %s", path_utf8); + update_archive_tree(directory, path_utf8); + } + }; - archive_file_close(file); + UpdateArchiveVisitor visitor(directory); + file->Visit(visitor); + file->Close(); } bool -update_archive_file(struct directory *directory, +update_archive_file(Directory *directory, const char *name, const char *suffix, const struct stat *st) { diff --git a/src/update_archive.h b/src/UpdateArchive.hxx index 838697dd..73b363d2 100644 --- a/src/update_archive.h +++ b/src/UpdateArchive.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,33 +17,31 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_ARCHIVE_H -#define MPD_UPDATE_ARCHIVE_H +#ifndef MPD_UPDATE_ARCHIVE_HXX +#define MPD_UPDATE_ARCHIVE_HXX #include "check.h" +#include "gcc.h" -#include <stdbool.h> #include <sys/stat.h> -struct directory; +struct Directory; struct archive_plugin; #ifdef ENABLE_ARCHIVE bool -update_archive_file(struct directory *directory, +update_archive_file(Directory *directory, const char *name, const char *suffix, const struct stat *st); #else -#include <glib.h> - static inline bool -update_archive_file(G_GNUC_UNUSED struct directory *directory, - G_GNUC_UNUSED const char *name, - G_GNUC_UNUSED const char *suffix, - G_GNUC_UNUSED const struct stat *st) +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; } diff --git a/src/update_container.c b/src/UpdateContainer.cxx index bda95dab..27ee89ba 100644 --- a/src/update_container.c +++ b/src/UpdateContainer.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,16 +18,19 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_container.h" -#include "update_internal.h" -#include "update_db.h" -#include "db_lock.h" -#include "directory.h" +#include "UpdateContainer.hxx" +#include "UpdateInternal.hxx" +#include "UpdateDatabase.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" #include "song.h" -#include "mapper.h" #include "decoder_plugin.h" -#include "tag.h" +#include "Mapper.hxx" +#include "fs/Path.hxx" + +extern "C" { #include "tag_handler.h" +} #include <glib.h> @@ -39,17 +42,16 @@ * * The caller must lock the database. */ -static struct directory * -make_directory_if_modified(struct directory *parent, const char *name, +static Directory * +make_directory_if_modified(Directory *parent, const char *name, const struct stat *st) { - struct directory *directory = directory_get_child(parent, name); + Directory *directory = parent->FindChild(name); // directory exists already if (directory != NULL) { if (directory->mtime == st->st_mtime && !walk_discard) { /* not modified */ - db_unlock(); return NULL; } @@ -57,13 +59,13 @@ make_directory_if_modified(struct directory *parent, const char *name, modified = true; } - directory = directory_make_child(parent, name); + directory = parent->MakeChild(name); directory->mtime = st->st_mtime; return directory; } bool -update_container_file(struct directory *directory, +update_container_file(Directory *directory, const char *name, const struct stat *st, const struct decoder_plugin *plugin) @@ -72,8 +74,7 @@ update_container_file(struct directory *directory, return false; db_lock(); - struct directory *contdir = - make_directory_if_modified(directory, name, st); + Directory *contdir = make_directory_if_modified(directory, name, st); if (contdir == NULL) { /* not modified */ db_unlock(); @@ -83,36 +84,33 @@ update_container_file(struct directory *directory, contdir->device = DEVICE_CONTAINER; db_unlock(); - char *const pathname = map_directory_child_fs(directory, name); + const Path pathname = map_directory_child_fs(directory, name); char *vtrack; unsigned int tnum = 0; - while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL) { + while ((vtrack = plugin->container_scan(pathname.c_str(), ++tnum)) != NULL) { struct song *song = song_file_new(vtrack, contdir); // shouldn't be necessary but it's there.. song->mtime = st->st_mtime; - char *child_path_fs = map_directory_child_fs(contdir, vtrack); + const Path child_path_fs = + map_directory_child_fs(contdir, vtrack); song->tag = tag_new(); - decoder_plugin_scan_file(plugin, child_path_fs, + decoder_plugin_scan_file(plugin, child_path_fs.c_str(), &add_tag_handler, song->tag); - g_free(child_path_fs); db_lock(); - directory_add_song(contdir, song); + contdir->AddSong(song); db_unlock(); modified = true; - g_message("added %s/%s", - directory_get_path(directory), vtrack); + g_message("added %s/%s", directory->GetPath(), vtrack); g_free(vtrack); } - g_free(pathname); - if (tnum == 1) { db_lock(); delete_directory(contdir); diff --git a/src/update_container.h b/src/UpdateContainer.hxx index 7f42c80c..0078730d 100644 --- a/src/update_container.h +++ b/src/UpdateContainer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,19 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_CONTAINER_H -#define MPD_UPDATE_CONTAINER_H +#ifndef MPD_UPDATE_CONTAINER_HXX +#define MPD_UPDATE_CONTAINER_HXX #include "check.h" -#include <stdbool.h> #include <sys/stat.h> -struct directory; +struct Directory; struct decoder_plugin; bool -update_container_file(struct directory *directory, +update_container_file(Directory *directory, const char *name, const struct stat *st, const struct decoder_plugin *plugin); diff --git a/src/update_db.c b/src/UpdateDatabase.cxx index 8982a53e..984fb1be 100644 --- a/src/update_db.c +++ b/src/UpdateDatabase.cxx @@ -18,23 +18,23 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_db.h" -#include "update_remove.h" -#include "directory.h" +#include "UpdateDatabase.hxx" +#include "UpdateRemove.hxx" +#include "PlaylistVector.hxx" +#include "Directory.hxx" #include "song.h" -#include "playlist_vector.h" -#include "db_lock.h" +#include "DatabaseLock.hxx" #include <glib.h> #include <assert.h> void -delete_song(struct directory *dir, struct song *del) +delete_song(Directory *dir, struct song *del) { assert(del->parent == dir); /* first, prevent traversers in main task from getting this */ - directory_remove_song(dir, del); + dir->RemoveSong(del); db_unlock(); /* temporary unlock, because update_remove_song() blocks */ @@ -54,9 +54,9 @@ delete_song(struct directory *dir, struct song *del) * Caller must lock the #db_mutex. */ static void -clear_directory(struct directory *directory) +clear_directory(Directory *directory) { - struct directory *child, *n; + Directory *child, *n; directory_for_each_child_safe(child, n, directory) delete_directory(child); @@ -68,35 +68,35 @@ clear_directory(struct directory *directory) } void -delete_directory(struct directory *directory) +delete_directory(Directory *directory) { assert(directory->parent != NULL); clear_directory(directory); - directory_delete(directory); + directory->Delete(); } bool -delete_name_in(struct directory *parent, const char *name) +delete_name_in(Directory *parent, const char *name) { bool modified = false; db_lock(); - struct directory *directory = directory_get_child(parent, name); + Directory *directory = parent->FindChild(name); if (directory != NULL) { delete_directory(directory); modified = true; } - struct song *song = directory_get_song(parent, name); + struct song *song = parent->FindSong(name); if (song != NULL) { delete_song(parent, song); modified = true; } - playlist_vector_remove(&parent->playlists, name); + parent->playlists.erase(name); db_unlock(); diff --git a/src/update_db.h b/src/UpdateDatabase.hxx index 0a9e46b0..7b55ce95 100644 --- a/src/update_db.h +++ b/src/UpdateDatabase.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,21 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_DB_H -#define MPD_UPDATE_DB_H +#ifndef MPD_UPDATE_DATABASE_HXX +#define MPD_UPDATE_DATABASE_HXX #include "check.h" -#include <stdbool.h> - -struct directory; +struct Directory; struct song; /** * Caller must lock the #db_mutex. */ void -delete_song(struct directory *parent, struct song *song); +delete_song(Directory *parent, struct song *song); /** * Recursively free a directory and all its contents. @@ -39,7 +37,7 @@ delete_song(struct directory *parent, struct song *song); * Caller must lock the #db_mutex. */ void -delete_directory(struct directory *directory); +delete_directory(Directory *directory); /** * Caller must NOT lock the #db_mutex. @@ -47,6 +45,6 @@ delete_directory(struct directory *directory); * @return true if the database was modified */ bool -delete_name_in(struct directory *parent, const char *name); +delete_name_in(Directory *parent, const char *name); #endif diff --git a/src/update.c b/src/UpdateGlue.cxx index 12eec40c..66463878 100644 --- a/src/update.c +++ b/src/UpdateGlue.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,19 +18,21 @@ */ #include "config.h" -#include "update.h" -#include "update_queue.h" -#include "update_walk.h" -#include "update_remove.h" -#include "update.h" -#include "database.h" -#include "mapper.h" -#include "playlist.h" -#include "event_pipe.h" -#include "update.h" -#include "idle.h" +#include "UpdateGlue.hxx" +#include "UpdateQueue.hxx" +#include "UpdateWalk.hxx" +#include "UpdateRemove.hxx" +#include "Mapper.hxx" +#include "DatabaseSimple.hxx" +#include "Idle.hxx" +#include "GlobalEvents.hxx" + +extern "C" { #include "stats.h" -#include "main.h" +} + +#include "Main.hxx" +#include "Partition.hxx" #include "mpd_error.h" #include <glib.h> @@ -65,7 +67,7 @@ isUpdatingDB(void) static void * update_task(void *_path) { - const char *path = _path; + const char *path = (const char *)_path; if (path != NULL && *path != 0) g_debug("starting: %s", path); @@ -90,7 +92,7 @@ static void * update_task(void *_path) g_free(_path); progress = UPDATE_PROGRESS_DONE; - event_pipe_emit(PIPE_EVENT_UPDATE); + GlobalEvents::Emit(GlobalEvents::UPDATE); return NULL; } @@ -118,7 +120,7 @@ update_enqueue(const char *path, bool _discard) { assert(g_thread_self() == main_task); - if (!mapper_has_music_directory()) + if (!db_is_simple() || !mapper_has_music_directory()) return 0; if (progress != UPDATE_PROGRESS_IDLE) { @@ -153,7 +155,7 @@ static void update_finished_event(void) if (modified) { /* send "idle" events */ - playlist_increment_version_all(&g_playlist); + global_partition->playlist.FullIncrementVersions(); idle_add(IDLE_DATABASE); } @@ -171,7 +173,7 @@ static void update_finished_event(void) void update_global_init(void) { - event_pipe_register(PIPE_EVENT_UPDATE, update_finished_event); + GlobalEvents::Register(GlobalEvents::UPDATE, update_finished_event); update_remove_global_init(); update_walk_global_init(); @@ -180,5 +182,4 @@ void update_global_init(void) void update_global_finish(void) { update_walk_global_finish(); - update_remove_global_finish(); } diff --git a/src/update.h b/src/UpdateGlue.hxx index 3d586b69..9d546a2a 100644 --- a/src/update.h +++ b/src/UpdateGlue.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_H -#define MPD_UPDATE_H - -#include <stdbool.h> +#ifndef MPD_UPDATE_GLUE_HXX +#define MPD_UPDATE_GLUE_HXX void update_global_init(void); diff --git a/src/UpdateIO.cxx b/src/UpdateIO.cxx new file mode 100644 index 00000000..7e262555 --- /dev/null +++ b/src/UpdateIO.cxx @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "UpdateIO.hxx" +#include "Directory.hxx" +#include "Mapper.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" + +#include <glib.h> + +#include <errno.h> +#include <unistd.h> + +int +stat_directory(const Directory *directory, struct stat *st) +{ + const Path path_fs = map_directory_fs(directory); + if (path_fs.IsNull()) + return -1; + + if (!StatFile(path_fs, *st)) { + int error = errno; + const std::string path_utf8 = path_fs.ToUTF8(); + g_warning("Failed to stat %s: %s", + path_utf8.c_str(), g_strerror(error)); + return -1; + } + + return 0; +} + +int +stat_directory_child(const Directory *parent, const char *name, + struct stat *st) +{ + const Path path_fs = map_directory_child_fs(parent, name); + if (path_fs.IsNull()) + return -1; + + if (!StatFile(path_fs, *st)) { + int error = errno; + const std::string path_utf8 = path_fs.ToUTF8(); + g_warning("Failed to stat %s: %s", + path_utf8.c_str(), g_strerror(error)); + return -1; + } + + return 0; +} + +bool +directory_exists(const Directory *directory) +{ + const Path path_fs = map_directory_fs(directory); + if (path_fs.IsNull()) + /* invalid path: cannot exist */ + return false; + + return directory->device == DEVICE_INARCHIVE || + directory->device == DEVICE_CONTAINER + ? FileExists(path_fs) + : DirectoryExists(path_fs); +} + +bool +directory_child_is_regular(const Directory *directory, + const char *name_utf8) +{ + const Path path_fs = map_directory_child_fs(directory, name_utf8); + if (path_fs.IsNull()) + return false; + + return FileExists(path_fs); +} + +bool +directory_child_access(const Directory *directory, + const char *name, int mode) +{ +#ifdef WIN32 + /* CheckAccess() is useless on WIN32 */ + (void)directory; + (void)name; + (void)mode; + return true; +#else + const Path path = map_directory_child_fs(directory, name); + if (path.IsNull()) + /* something went wrong, but that isn't a permission + problem */ + return true; + + return CheckAccess(path, mode) || errno != EACCES; +#endif +} diff --git a/src/update_io.h b/src/UpdateIO.hxx index 6ff1cceb..ee47b268 100644 --- a/src/update_io.h +++ b/src/UpdateIO.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,35 +17,34 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_IO_H -#define MPD_UPDATE_IO_H +#ifndef MPD_UPDATE_IO_HXX +#define MPD_UPDATE_IO_HXX #include "check.h" -#include <stdbool.h> #include <sys/stat.h> -struct directory; +struct Directory; int -stat_directory(const struct directory *directory, struct stat *st); +stat_directory(const Directory *directory, struct stat *st); int -stat_directory_child(const struct directory *parent, const char *name, +stat_directory_child(const Directory *parent, const char *name, struct stat *st); bool -directory_exists(const struct directory *directory); +directory_exists(const Directory *directory); bool -directory_child_is_regular(const struct directory *directory, +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 struct directory *directory, +directory_child_access(const Directory *directory, const char *name, int mode); #endif diff --git a/src/update_internal.h b/src/UpdateInternal.hxx index 76ab7bed..50443c08 100644 --- a/src/update_internal.h +++ b/src/UpdateInternal.hxx @@ -22,8 +22,6 @@ #include "check.h" -#include <stdbool.h> - extern bool walk_discard; extern bool modified; diff --git a/src/update_queue.c b/src/UpdateQueue.cxx index 2150fa4e..85eb2235 100644 --- a/src/update_queue.c +++ b/src/UpdateQueue.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "update_queue.h" +#include "UpdateQueue.hxx" #include <glib.h> diff --git a/src/update_queue.h b/src/UpdateQueue.hxx index 84ba474b..7de06964 100644 --- a/src/update_queue.h +++ b/src/UpdateQueue.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_QUEUE_H -#define MPD_UPDATE_QUEUE_H +#ifndef MPD_UPDATE_QUEUE_HXX +#define MPD_UPDATE_QUEUE_HXX #include "check.h" -#include <stdbool.h> - unsigned update_queue_push(const char *path, bool discard, unsigned base); diff --git a/src/update_remove.c b/src/UpdateRemove.cxx index f443f5eb..acf8bf46 100644 --- a/src/update_remove.c +++ b/src/UpdateRemove.cxx @@ -18,15 +18,19 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_remove.h" -#include "event_pipe.h" +#include "UpdateRemove.hxx" +#include "Playlist.hxx" +#include "Partition.hxx" +#include "GlobalEvents.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + #include "song.h" -#include "playlist.h" -#include "main.h" +#include "Main.hxx" #ifdef ENABLE_SQLITE -#include "sticker.h" -#include "song_sticker.h" +#include "StickerDatabase.hxx" +#include "SongSticker.hxx" #endif #include <glib.h> @@ -35,8 +39,8 @@ static const struct song *removed_song; -static GMutex *remove_mutex; -static GCond *remove_cond; +static Mutex remove_mutex; +static Cond remove_cond; /** * Safely remove a song from the database. This must be done in the @@ -59,29 +63,19 @@ song_remove_event(void) sticker_song_delete(removed_song); #endif - playlist_delete_song(&g_playlist, global_player_control, removed_song); + global_partition->DeleteSong(*removed_song); /* clear "removed_song" and send signal to update thread */ - g_mutex_lock(remove_mutex); + remove_mutex.lock(); removed_song = NULL; - g_cond_signal(remove_cond); - g_mutex_unlock(remove_mutex); + remove_cond.signal(); + remove_mutex.unlock(); } void update_remove_global_init(void) { - remove_mutex = g_mutex_new(); - remove_cond = g_cond_new(); - - event_pipe_register(PIPE_EVENT_DELETE, song_remove_event); -} - -void -update_remove_global_finish(void) -{ - g_mutex_free(remove_mutex); - g_cond_free(remove_cond); + GlobalEvents::Register(GlobalEvents::DELETE, song_remove_event); } void @@ -91,12 +85,12 @@ update_remove_song(const struct song *song) removed_song = song; - event_pipe_emit(PIPE_EVENT_DELETE); + GlobalEvents::Emit(GlobalEvents::DELETE); - g_mutex_lock(remove_mutex); + remove_mutex.lock(); while (removed_song != NULL) - g_cond_wait(remove_cond, remove_mutex); + remove_cond.wait(remove_mutex); - g_mutex_unlock(remove_mutex); + remove_mutex.unlock(); } diff --git a/src/update_remove.h b/src/UpdateRemove.hxx index 479ef83f..a83e14ae 100644 --- a/src/update_remove.h +++ b/src/UpdateRemove.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_REMOVE_H -#define MPD_UPDATE_REMOVE_H +#ifndef MPD_UPDATE_REMOVE_HXX +#define MPD_UPDATE_REMOVE_HXX #include "check.h" @@ -27,9 +27,6 @@ struct song; void update_remove_global_init(void); -void -update_remove_global_finish(void); - /** * Sends a signal to the main thread which will in turn remove the * song: from the sticker database and from the playlist. This diff --git a/src/update_song.c b/src/UpdateSong.cxx index 1126ad11..676ba48e 100644 --- a/src/update_song.c +++ b/src/UpdateSong.cxx @@ -18,33 +18,33 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_song.h" -#include "update_internal.h" -#include "update_io.h" -#include "update_db.h" -#include "update_container.h" -#include "db_lock.h" -#include "directory.h" +#include "UpdateSong.hxx" +#include "UpdateInternal.hxx" +#include "UpdateIO.hxx" +#include "UpdateDatabase.hxx" +#include "UpdateContainer.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" #include "song.h" -#include "decoder_list.h" #include "decoder_plugin.h" +#include "DecoderList.hxx" #include <glib.h> #include <unistd.h> static void -update_song_file2(struct directory *directory, +update_song_file2(Directory *directory, const char *name, const struct stat *st, const struct decoder_plugin *plugin) { db_lock(); - struct song *song = directory_get_song(directory, name); + struct song *song = directory->FindSong(name); db_unlock(); if (!directory_child_access(directory, name, R_OK)) { g_warning("no read permissions on %s/%s", - directory_get_path(directory), name); + directory->GetPath(), name); if (song != NULL) { db_lock(); delete_song(directory, song); @@ -67,28 +67,27 @@ update_song_file2(struct directory *directory, } if (song == NULL) { - g_debug("reading %s/%s", - directory_get_path(directory), name); + g_debug("reading %s/%s", directory->GetPath(), name); song = song_file_load(name, directory); if (song == NULL) { g_debug("ignoring unrecognized file %s/%s", - directory_get_path(directory), name); + directory->GetPath(), name); return; } db_lock(); - directory_add_song(directory, song); + directory->AddSong(song); db_unlock(); modified = true; g_message("added %s/%s", - directory_get_path(directory), name); + directory->GetPath(), name); } else if (st->st_mtime != song->mtime || walk_discard) { g_message("updating %s/%s", - directory_get_path(directory), name); + directory->GetPath(), name); if (!song_file_update(song)) { g_debug("deleting unrecognized file %s/%s", - directory_get_path(directory), name); + directory->GetPath(), name); db_lock(); delete_song(directory, song); db_unlock(); @@ -99,12 +98,12 @@ update_song_file2(struct directory *directory, } bool -update_song_file(struct directory *directory, +update_song_file(Directory *directory, const char *name, const char *suffix, const struct stat *st) { const struct decoder_plugin *plugin = - decoder_plugin_from_suffix(suffix, false); + decoder_plugin_from_suffix(suffix, nullptr); if (plugin == NULL) return false; diff --git a/src/update_song.h b/src/UpdateSong.hxx index cff63f57..60a532e3 100644 --- a/src/update_song.h +++ b/src/UpdateSong.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,18 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_SONG_H -#define MPD_UPDATE_SONG_H +#ifndef MPD_UPDATE_SONG_HXX +#define MPD_UPDATE_SONG_HXX #include "check.h" -#include <stdbool.h> #include <sys/stat.h> -struct directory; +struct Directory; bool -update_song_file(struct directory *directory, +update_song_file(Directory *directory, const char *name, const char *suffix, const struct stat *st); diff --git a/src/update_walk.c b/src/UpdateWalk.cxx index 8554e8f3..9a2e87d5 100644 --- a/src/update_walk.c +++ b/src/UpdateWalk.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,22 +18,26 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_walk.h" -#include "update_io.h" -#include "update_db.h" -#include "update_song.h" -#include "update_archive.h" -#include "database.h" -#include "db_lock.h" -#include "exclude.h" -#include "directory.h" +#include "UpdateWalk.hxx" +#include "UpdateIO.hxx" +#include "UpdateDatabase.hxx" +#include "UpdateSong.hxx" +#include "UpdateArchive.hxx" +#include "DatabaseLock.hxx" +#include "DatabaseSimple.hxx" +#include "Directory.hxx" #include "song.h" -#include "playlist_vector.h" -#include "uri.h" -#include "mapper.h" -#include "path.h" -#include "playlist_list.h" +#include "PlaylistVector.hxx" +#include "PlaylistRegistry.hxx" +#include "Mapper.hxx" +#include "ExcludeList.hxx" #include "conf.h" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" + +extern "C" { +#include "uri.h" +} #include <glib.h> @@ -84,7 +88,7 @@ update_walk_global_finish(void) } static void -directory_set_stat(struct directory *dir, const struct stat *st) +directory_set_stat(Directory *dir, const struct stat *st) { dir->inode = st->st_ino; dir->device = st->st_dev; @@ -92,43 +96,39 @@ directory_set_stat(struct directory *dir, const struct stat *st) } static void -remove_excluded_from_directory(struct directory *directory, - GSList *exclude_list) +remove_excluded_from_directory(Directory *directory, + const ExcludeList &exclude_list) { db_lock(); - struct directory *child, *n; + Directory *child, *n; directory_for_each_child_safe(child, n, directory) { - char *name_fs = utf8_to_fs_charset(directory_get_name(child)); + const Path name_fs = Path::FromUTF8(child->GetName()); - if (exclude_list_check(exclude_list, name_fs)) { + if (name_fs.IsNull() || exclude_list.Check(name_fs.c_str())) { delete_directory(child); modified = true; } - - g_free(name_fs); } struct song *song, *ns; directory_for_each_song_safe(song, ns, directory) { assert(song->parent == directory); - char *name_fs = utf8_to_fs_charset(song->uri); - if (exclude_list_check(exclude_list, name_fs)) { + const Path name_fs = Path::FromUTF8(song->uri); + if (name_fs.IsNull() || exclude_list.Check(name_fs.c_str())) { delete_song(directory, song); modified = true; } - - g_free(name_fs); } db_unlock(); } static void -purge_deleted_from_directory(struct directory *directory) +purge_deleted_from_directory(Directory *directory) { - struct directory *child, *n; + Directory *child, *n; directory_for_each_child_safe(child, n, directory) { if (directory_exists(child)) continue; @@ -142,33 +142,31 @@ purge_deleted_from_directory(struct directory *directory) struct song *song, *ns; directory_for_each_song_safe(song, ns, directory) { - char *path; - struct stat st; - if ((path = map_song_fs(song)) == NULL || - stat(path, &st) < 0 || !S_ISREG(st.st_mode)) { + const Path path = map_song_fs(song); + if (path.IsNull() || !FileExists(path)) { db_lock(); delete_song(directory, song); db_unlock(); modified = true; } - - g_free(path); } - struct playlist_metadata *pm, *np; - directory_for_each_playlist_safe(pm, np, directory) { - if (!directory_child_is_regular(directory, pm->name)) { + for (auto i = directory->playlists.begin(), + end = directory->playlists.end(); + i != end;) { + if (!directory_child_is_regular(directory, i->name.c_str())) { db_lock(); - playlist_vector_remove(&directory->playlists, pm->name); + i = directory->playlists.erase(i); db_unlock(); - } + } else + ++i; } } #ifndef G_OS_WIN32 static int -update_directory_stat(struct directory *directory) +update_directory_stat(Directory *directory) { struct stat st; if (stat_directory(directory, &st) < 0) @@ -180,7 +178,7 @@ update_directory_stat(struct directory *directory) #endif static int -find_inode_ancestor(struct directory *parent, ino_t inode, dev_t device) +find_inode_ancestor(Directory *parent, ino_t inode, dev_t device) { #ifndef G_OS_WIN32 while (parent) { @@ -204,23 +202,24 @@ find_inode_ancestor(struct directory *parent, ino_t inode, dev_t device) } static bool -update_playlist_file2(struct directory *directory, +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 (playlist_vector_update_or_add(&directory->playlists, name, - st->st_mtime)) + if (directory->playlists.UpdateOrInsert(std::move(pi))) modified = true; db_unlock(); return true; } static bool -update_regular_file(struct directory *directory, +update_regular_file(Directory *directory, const char *name, const struct stat *st) { const char *suffix = uri_get_suffix(name); @@ -233,10 +232,10 @@ update_regular_file(struct directory *directory, } static bool -update_directory(struct directory *directory, const struct stat *st); +update_directory(Directory *directory, const struct stat *st); static void -update_directory_child(struct directory *directory, +update_directory_child(Directory *directory, const char *name, const struct stat *st) { assert(strchr(name, '/') == NULL); @@ -248,8 +247,7 @@ update_directory_child(struct directory *directory, return; db_lock(); - struct directory *subdir = - directory_make_child(directory, name); + Directory *subdir = directory->MakeChild(name); db_unlock(); assert(directory == subdir->parent); @@ -275,16 +273,15 @@ static bool skip_path(const char *path) G_GNUC_PURE static bool -skip_symlink(const struct directory *directory, const char *utf8_name) +skip_symlink(const Directory *directory, const char *utf8_name) { #ifndef WIN32 - char *path_fs = map_directory_child_fs(directory, utf8_name); - if (path_fs == NULL) + const Path path_fs = map_directory_child_fs(directory, utf8_name); + if (path_fs.IsNull()) return true; char buffer[MPD_PATH_MAX]; - ssize_t length = readlink(path_fs, buffer, sizeof(buffer)); - g_free(path_fs); + ssize_t length = readlink(path_fs.c_str(), buffer, sizeof(buffer)); if (length < 0) /* don't skip if this is not a symlink */ return errno != EINVAL; @@ -348,64 +345,54 @@ skip_symlink(const struct directory *directory, const char *utf8_name) } static bool -update_directory(struct directory *directory, const struct stat *st) +update_directory(Directory *directory, const struct stat *st) { assert(S_ISDIR(st->st_mode)); directory_set_stat(directory, st); - char *path_fs = map_directory_fs(directory); - if (path_fs == NULL) + const Path path_fs = map_directory_fs(directory); + if (path_fs.IsNull()) return false; - DIR *dir = opendir(path_fs); + DIR *dir = opendir(path_fs.c_str()); if (!dir) { g_warning("Failed to open directory %s: %s", - path_fs, g_strerror(errno)); - g_free(path_fs); + path_fs.c_str(), g_strerror(errno)); return false; } - char *exclude_path_fs = g_build_filename(path_fs, ".mpdignore", NULL); - GSList *exclude_list = exclude_list_load(exclude_path_fs); - g_free(exclude_path_fs); + ExcludeList exclude_list; + exclude_list.LoadFile(Path::Build(path_fs, ".mpdignore")); - g_free(path_fs); - - if (exclude_list != NULL) + if (!exclude_list.IsEmpty()) remove_excluded_from_directory(directory, exclude_list); purge_deleted_from_directory(directory); struct dirent *ent; while ((ent = readdir(dir))) { - char *utf8; + std::string utf8; struct stat st2; - if (skip_path(ent->d_name) || - exclude_list_check(exclude_list, ent->d_name)) + if (skip_path(ent->d_name) || exclude_list.Check(ent->d_name)) continue; - utf8 = fs_charset_to_utf8(ent->d_name); - if (utf8 == NULL) + utf8 = Path::ToUTF8(ent->d_name); + if (utf8.empty()) continue; - if (skip_symlink(directory, utf8)) { - modified |= delete_name_in(directory, utf8); - g_free(utf8); + if (skip_symlink(directory, utf8.c_str())) { + modified |= delete_name_in(directory, utf8.c_str()); continue; } - if (stat_directory_child(directory, utf8, &st2) == 0) - update_directory_child(directory, utf8, &st2); + 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); - - g_free(utf8); + modified |= delete_name_in(directory, utf8.c_str()); } - exclude_list_free(exclude_list); - closedir(dir); directory->mtime = st->st_mtime; @@ -413,11 +400,11 @@ update_directory(struct directory *directory, const struct stat *st) return true; } -static struct directory * -directory_make_child_checked(struct directory *parent, const char *name_utf8) +static Directory * +directory_make_child_checked(Directory *parent, const char *name_utf8) { db_lock(); - struct directory *directory = directory_get_child(parent, name_utf8); + Directory *directory = parent->FindChild(name_utf8); db_unlock(); if (directory != NULL) @@ -434,21 +421,21 @@ directory_make_child_checked(struct directory *parent, const char *name_utf8) /* if we're adding directory paths, make sure to delete filenames with potentially the same name */ db_lock(); - struct song *conflicting = directory_get_song(parent, name_utf8); + struct song *conflicting = parent->FindSong(name_utf8); if (conflicting) delete_song(parent, conflicting); - directory = directory_new_child(parent, name_utf8); + directory = parent->CreateChild(name_utf8); db_unlock(); directory_set_stat(directory, &st); return directory; } -static struct directory * +static Directory * directory_make_uri_parent_checked(const char *uri) { - struct directory *directory = db_get_root(); + Directory *directory = db_get_root(); char *duplicated = g_strdup(uri); char *name_utf8 = duplicated, *slash; @@ -472,7 +459,7 @@ directory_make_uri_parent_checked(const char *uri) static void update_uri(const char *uri) { - struct directory *parent = directory_make_uri_parent_checked(uri); + Directory *parent = directory_make_uri_parent_checked(uri); if (parent == NULL) return; @@ -497,7 +484,7 @@ update_walk(const char *path, bool discard) if (path != NULL && !isRootDirectory(path)) { update_uri(path); } else { - struct directory *directory = db_get_root(); + Directory *directory = db_get_root(); struct stat st; if (stat_directory(directory, &st) == 0) diff --git a/src/update_walk.h b/src/UpdateWalk.hxx index ab1e41fb..62c0d0a8 100644 --- a/src/update_walk.h +++ b/src/UpdateWalk.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_WALK_H -#define MPD_UPDATE_WALK_H +#ifndef MPD_UPDATE_WALK_HXX +#define MPD_UPDATE_WALK_HXX #include "check.h" -#include <stdbool.h> - void update_walk_global_init(void); diff --git a/src/volume.c b/src/Volume.cxx index d3ce47dd..116f4aa1 100644 --- a/src/volume.c +++ b/src/Volume.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,21 +18,14 @@ */ #include "config.h" -#include "volume.h" -#include "conf.h" -#include "idle.h" -#include "pcm_volume.h" -#include "output_all.h" -#include "mixer_control.h" -#include "mixer_all.h" -#include "mixer_type.h" -#include "event_pipe.h" +#include "Volume.hxx" +#include "MixerAll.hxx" +#include "Idle.hxx" +#include "GlobalEvents.hxx" #include <glib.h> #include <assert.h> -#include <math.h> -#include <string.h> #include <stdlib.h> #undef G_LOG_DOMAIN @@ -48,7 +41,7 @@ static int last_hardware_volume = -1; static GTimer *hardware_volume_timer; /** - * Handler for #PIPE_EVENT_MIXER. + * Handler for #GlobalEvents::MIXER. */ static void mixer_event_callback(void) @@ -69,7 +62,7 @@ void volume_init(void) { hardware_volume_timer = g_timer_new(); - event_pipe_register(PIPE_EVENT_MIXER, mixer_event_callback); + GlobalEvents::Register(GlobalEvents::MIXER, mixer_event_callback); } int volume_level_get(void) diff --git a/src/volume.h b/src/Volume.hxx index b08899a8..024b2840 100644 --- a/src/volume.h +++ b/src/Volume.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_VOLUME_H -#define MPD_VOLUME_H +#ifndef MPD_VOLUME_HXX +#define MPD_VOLUME_HXX -#include <stdbool.h> #include <stdio.h> void volume_init(void); diff --git a/src/main_win32.c b/src/Win32Main.cxx index aac7ad88..0cd5e445 100644 --- a/src/main_win32.c +++ b/src/Win32Main.cxx @@ -18,12 +18,15 @@ */ #include "config.h" -#include "main.h" +#include "Main.hxx" #ifdef WIN32 #include "mpd_error.h" -#include "event_pipe.h" +#include "GlobalEvents.hxx" + +#include <cstdlib> +#include <atomic> #include <glib.h> @@ -32,7 +35,7 @@ static int service_argc; static char **service_argv; static char service_name[] = ""; -static BOOL ignore_console_events; +static std::atomic_bool running; static SERVICE_STATUS_HANDLE service_handle; static void WINAPI @@ -68,7 +71,7 @@ service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type, switch (control) { case SERVICE_CONTROL_SHUTDOWN: case SERVICE_CONTROL_STOP: - event_pipe_emit(PIPE_EVENT_SHUTDOWN); + GlobalEvents::Emit(GlobalEvents::SHUTDOWN); return NO_ERROR; default: return NO_ERROR; @@ -103,8 +106,22 @@ console_handler(DWORD event) switch (event) { case CTRL_C_EVENT: case CTRL_CLOSE_EVENT: - if (!ignore_console_events) - event_pipe_emit(PIPE_EVENT_SHUTDOWN); + 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; @@ -125,8 +142,8 @@ int win32_main(int argc, char *argv[]) error_code = GetLastError(); if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) { /* running as console app */ + running.store(false); SetConsoleTitle("Music Player Daemon"); - ignore_console_events = TRUE; SetConsoleCtrlHandler(console_handler, TRUE); return mpd_main(argc, argv); } @@ -140,7 +157,7 @@ void win32_app_started() if (service_handle != 0) service_notify_status(SERVICE_RUNNING); else - ignore_console_events = FALSE; + running.store(true); } void win32_app_stopping() @@ -148,7 +165,7 @@ void win32_app_stopping() if (service_handle != 0) service_notify_status(SERVICE_STOP_PENDING); else - ignore_console_events = TRUE; + running.store(false); } #endif diff --git a/src/zeroconf-avahi.c b/src/ZeroconfAvahi.cxx index f2cc5359..4764ad75 100644 --- a/src/zeroconf-avahi.c +++ b/src/ZeroconfAvahi.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,10 @@ */ #include "config.h" -#include "zeroconf-internal.h" -#include "listen.h" +#include "ZeroconfAvahi.hxx" +#include "ZeroconfInternal.hxx" +#include "Listen.hxx" +#include "event/Loop.hxx" #include "mpd_error.h" #include <glib.h> @@ -116,7 +118,8 @@ static void avahiRegisterService(AvahiClient * c) * if that's better. */ ret = avahi_entry_group_add_service(avahiGroup, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, - 0, avahiName, SERVICE_TYPE, NULL, + AvahiPublishFlags(0), + avahiName, SERVICE_TYPE, NULL, NULL, listen_port, NULL); if (ret < 0) { g_warning("Failed to add service %s: %s", SERVICE_TYPE, @@ -213,7 +216,8 @@ static void avahiClientCallback(AvahiClient * c, AvahiClientState state, } } -void init_avahi(const char *serviceName) +void +AvahiInit(EventLoop &loop, const char *serviceName) { int error; g_debug("Initializing interface"); @@ -225,7 +229,8 @@ void init_avahi(const char *serviceName) avahiRunning = 1; - avahi_glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT); + avahi_glib_poll = avahi_glib_poll_new(loop.GetContext(), + G_PRIORITY_DEFAULT); avahi_poll = avahi_glib_poll_get(avahi_glib_poll); avahiClient = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL, @@ -234,16 +239,12 @@ void init_avahi(const char *serviceName) if (!avahiClient) { g_warning("Failed to create client: %s", avahi_strerror(error)); - goto fail; + AvahiDeinit(); } - - return; - -fail: - avahi_finish(); } -void avahi_finish(void) +void +AvahiDeinit(void) { g_debug("Shutting down interface"); diff --git a/src/ZeroconfAvahi.hxx b/src/ZeroconfAvahi.hxx new file mode 100644 index 00000000..bb046350 --- /dev/null +++ b/src/ZeroconfAvahi.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ZEROCONF_AVAHI_HXX +#define MPD_ZEROCONF_AVAHI_HXX + +class EventLoop; + +void +AvahiInit(EventLoop &loop, const char *service_name); + +void +AvahiDeinit(); + +#endif diff --git a/src/zeroconf-bonjour.c b/src/ZeroconfBonjour.cxx index 0f216aad..959c9024 100644 --- a/src/zeroconf-bonjour.c +++ b/src/ZeroconfBonjour.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,11 @@ */ #include "config.h" -#include "zeroconf-internal.h" -#include "listen.h" +#include "ZeroconfBonjour.hxx" +#include "ZeroconfInternal.hxx" +#include "Listen.hxx" +#include "event/SocketMonitor.hxx" +#include "gcc.h" #include <glib.h> @@ -28,8 +31,29 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "bonjour" -static DNSServiceRef dnsReference; -static GIOChannel *bonjour_channel; +class BonjourMonitor final : public SocketMonitor { + DNSServiceRef service_ref; + +public: + BonjourMonitor(EventLoop &_loop, DNSServiceRef _service_ref) + :SocketMonitor(DNSServiceRefSockFD(_service_ref), _loop), + service_ref(_service_ref) { + ScheduleRead(); + } + + ~BonjourMonitor() { + Steal(); + DNSServiceRefDeallocate(service_ref); + } + +protected: + virtual bool OnSocketReady(gcc_unused unsigned flags) override { + DNSServiceProcessResult(service_ref); + return false; + } +}; + +static BonjourMonitor *bonjour_monitor; static void dnsRegisterCallback(G_GNUC_UNUSED DNSServiceRef sdRef, @@ -42,26 +66,18 @@ dnsRegisterCallback(G_GNUC_UNUSED DNSServiceRef sdRef, if (errorCode != kDNSServiceErr_NoError) { g_warning("Failed to register zeroconf service."); - bonjour_finish(); + bonjour_monitor->Cancel(); } else { g_debug("Registered zeroconf service with name '%s'", name); } } -static gboolean -bonjour_channel_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - G_GNUC_UNUSED gpointer data) -{ - DNSServiceProcessResult(dnsReference); - - return dnsReference != NULL; -} - -void init_zeroconf_osx(const char *serviceName) +void +BonjourInit(EventLoop &loop, const char *service_name) { + DNSServiceRef dnsReference; DNSServiceErrorType error = DNSServiceRegister(&dnsReference, - 0, 0, serviceName, + 0, 0, service_name, SERVICE_TYPE, NULL, NULL, g_htons(listen_port), 0, NULL, @@ -78,20 +94,11 @@ void init_zeroconf_osx(const char *serviceName) return; } - bonjour_channel = g_io_channel_unix_new(DNSServiceRefSockFD(dnsReference)); - g_io_add_watch(bonjour_channel, G_IO_IN, bonjour_channel_event, NULL); + bonjour_monitor = new BonjourMonitor(loop, dnsReference); } -void bonjour_finish(void) +void +BonjourDeinit() { - if (bonjour_channel != NULL) { - g_io_channel_unref(bonjour_channel); - bonjour_channel = NULL; - } - - if (dnsReference != NULL) { - DNSServiceRefDeallocate(dnsReference); - dnsReference = NULL; - g_debug("Deregistered Zeroconf service."); - } + delete bonjour_monitor; } diff --git a/src/ZeroconfBonjour.hxx b/src/ZeroconfBonjour.hxx new file mode 100644 index 00000000..d91fe9a0 --- /dev/null +++ b/src/ZeroconfBonjour.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ZEROCONF_BONJOUR_HXX +#define MPD_ZEROCONF_BONJOUR_HXX + +class EventLoop; + +void +BonjourInit(EventLoop &loop, const char *service_name); + +void +BonjourDeinit(); + +#endif diff --git a/src/zeroconf.c b/src/ZeroconfGlue.cxx index 4a399e4a..14e1d086 100644 --- a/src/zeroconf.c +++ b/src/ZeroconfGlue.cxx @@ -18,10 +18,12 @@ */ #include "config.h" -#include "zeroconf.h" -#include "zeroconf-internal.h" +#include "ZeroconfGlue.hxx" +#include "ZeroconfAvahi.hxx" +#include "ZeroconfBonjour.hxx" #include "conf.h" -#include "listen.h" +#include "Listen.hxx" +#include "gcc.h" #include <glib.h> @@ -34,7 +36,8 @@ static int zeroconfEnabled; -void initZeroconf(void) +void +ZeroconfInit(gcc_unused EventLoop &loop) { const char *serviceName; @@ -52,24 +55,25 @@ void initZeroconf(void) serviceName = config_get_string(CONF_ZEROCONF_NAME, SERVICE_NAME); #ifdef HAVE_AVAHI - init_avahi(serviceName); + AvahiInit(loop, serviceName); #endif #ifdef HAVE_BONJOUR - init_zeroconf_osx(serviceName); + BonjourInit(loop, serviceName); #endif } -void finishZeroconf(void) +void +ZeroconfDeinit() { if (!zeroconfEnabled) return; #ifdef HAVE_AVAHI - avahi_finish(); + AvahiDeinit(); #endif /* HAVE_AVAHI */ #ifdef HAVE_BONJOUR - bonjour_finish(); + BonjourDeinit(); #endif } diff --git a/src/zeroconf.h b/src/ZeroconfGlue.hxx index 8e33a3d8..2a291ce2 100644 --- a/src/zeroconf.h +++ b/src/ZeroconfGlue.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,20 +17,30 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_ZEROCONF_H -#define MPD_ZEROCONF_H +#ifndef MPD_ZEROCONF_GLUE_HXX +#define MPD_ZEROCONF_GLUE_HXX #include "check.h" +class EventLoop; + #ifdef HAVE_ZEROCONF -void initZeroconf(void); -void finishZeroconf(void); +void +ZeroconfInit(EventLoop &loop); + +void +ZeroconfDeinit(); #else /* ! HAVE_ZEROCONF */ -static void initZeroconf(void) { } -static void finishZeroconf(void) { } +static inline void +ZeroconfInit(EventLoop &) +{} + +static inline void +ZeroconfDeinit() +{} #endif /* ! HAVE_ZEROCONF */ diff --git a/src/zeroconf-internal.h b/src/ZeroconfInternal.hxx index 983e5c55..2eadcff6 100644 --- a/src/zeroconf-internal.h +++ b/src/ZeroconfInternal.hxx @@ -23,12 +23,4 @@ /* The dns-sd service type qualifier to publish */ #define SERVICE_TYPE "_mpd._tcp" -void init_avahi(const char *service_name); - -void avahi_finish(void); - -void init_zeroconf_osx(const char *service_name); - -void bonjour_finish(void); - #endif diff --git a/src/archive/Bzip2ArchivePlugin.cxx b/src/archive/Bzip2ArchivePlugin.cxx new file mode 100644 index 00000000..182b9ccd --- /dev/null +++ b/src/archive/Bzip2ArchivePlugin.cxx @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * single bz2 archive handling (requires libbz2) + */ + +#include "config.h" +#include "Bzip2ArchivePlugin.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "ArchiveVisitor.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "util/RefCount.hxx" + +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include <glib.h> +#include <bzlib.h> + +#ifdef HAVE_OLDER_BZIP2 +#define BZ2_bzDecompressInit bzDecompressInit +#define BZ2_bzDecompress bzDecompress +#endif + +class Bzip2ArchiveFile final : public ArchiveFile { +public: + RefCount ref; + + char *const name; + struct input_stream *const istream; + + Bzip2ArchiveFile(const char *path, input_stream *_is) + :ArchiveFile(bz2_archive_plugin), + name(g_path_get_basename(path)), + istream(_is) { + // remove .bz2 suffix + size_t len = strlen(name); + if (len > 4) + name[len - 4] = 0; + } + + ~Bzip2ArchiveFile() { + input_stream_close(istream); + } + + void Ref() { + ref.Increment(); + } + + void Unref() { + if (!ref.Decrement()) + return; + + g_free(name); + delete this; + } + + virtual void Close() override { + Unref(); + } + + virtual void Visit(ArchiveVisitor &visitor) override { + visitor.VisitArchiveEntry(name); + } + + virtual input_stream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + GError **error_r) override; +}; + +struct Bzip2InputStream { + struct input_stream base; + + Bzip2ArchiveFile *archive; + + bool eof; + + bz_stream bzstream; + + char buffer[5000]; + + Bzip2InputStream(Bzip2ArchiveFile &context, const char *uri, + Mutex &mutex, Cond &cond); + ~Bzip2InputStream(); + + bool Open(GError **error_r); + void Close(); +}; + +extern const struct input_plugin bz2_inputplugin; + +static inline GQuark +bz2_quark(void) +{ + return g_quark_from_static_string("bz2"); +} + +/* single archive handling allocation helpers */ + +inline bool +Bzip2InputStream::Open(GError **error_r) +{ + bzstream.bzalloc = nullptr; + bzstream.bzfree = nullptr; + bzstream.opaque = nullptr; + + bzstream.next_in = (char *)buffer; + bzstream.avail_in = 0; + + int ret = BZ2_bzDecompressInit(&bzstream, 0, 0); + if (ret != BZ_OK) { + g_set_error(error_r, bz2_quark(), ret, + "BZ2_bzDecompressInit() has failed"); + return false; + } + + base.ready = true; + return true; +} + +inline void +Bzip2InputStream::Close() +{ + BZ2_bzDecompressEnd(&bzstream); +} + +/* archive open && listing routine */ + +static ArchiveFile * +bz2_open(const char *pathname, GError **error_r) +{ + static Mutex mutex; + static Cond cond; + input_stream *is = input_stream_open(pathname, mutex, cond, error_r); + if (is == nullptr) + return nullptr; + + return new Bzip2ArchiveFile(pathname, is); +} + +/* single archive handling */ + +Bzip2InputStream::Bzip2InputStream(Bzip2ArchiveFile &_context, const char *uri, + Mutex &mutex, Cond &cond) + :base(bz2_inputplugin, uri, mutex, cond), + archive(&_context), eof(false) +{ + archive->Ref(); +} + +Bzip2InputStream::~Bzip2InputStream() +{ + archive->Unref(); +} + +input_stream * +Bzip2ArchiveFile::OpenStream(const char *path, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + Bzip2InputStream *bis = new Bzip2InputStream(*this, path, mutex, cond); + if (!bis->Open(error_r)) { + delete bis; + return NULL; + } + + return &bis->base; +} + +static void +bz2_is_close(struct input_stream *is) +{ + Bzip2InputStream *bis = (Bzip2InputStream *)is; + + bis->Close(); + delete bis; +} + +static bool +bz2_fillbuffer(Bzip2InputStream *bis, GError **error_r) +{ + size_t count; + bz_stream *bzstream; + + bzstream = &bis->bzstream; + + if (bzstream->avail_in > 0) + return true; + + count = input_stream_read(bis->archive->istream, + bis->buffer, sizeof(bis->buffer), + error_r); + if (count == 0) + return false; + + bzstream->next_in = bis->buffer; + bzstream->avail_in = count; + return true; +} + +static size_t +bz2_is_read(struct input_stream *is, void *ptr, size_t length, + GError **error_r) +{ + Bzip2InputStream *bis = (Bzip2InputStream *)is; + bz_stream *bzstream; + int bz_result; + size_t nbytes = 0; + + if (bis->eof) + return 0; + + bzstream = &bis->bzstream; + bzstream->next_out = (char *)ptr; + bzstream->avail_out = length; + + do { + if (!bz2_fillbuffer(bis, error_r)) + return 0; + + bz_result = BZ2_bzDecompress(bzstream); + + if (bz_result == BZ_STREAM_END) { + bis->eof = true; + break; + } + + if (bz_result != BZ_OK) { + g_set_error(error_r, bz2_quark(), bz_result, + "BZ2_bzDecompress() has failed"); + return 0; + } + } while (bzstream->avail_out == length); + + nbytes = length - bzstream->avail_out; + is->offset += nbytes; + + return nbytes; +} + +static bool +bz2_is_eof(struct input_stream *is) +{ + Bzip2InputStream *bis = (Bzip2InputStream *)is; + + return bis->eof; +} + +/* exported structures */ + +static const char *const bz2_extensions[] = { + "bz2", + NULL +}; + +const struct input_plugin bz2_inputplugin = { + nullptr, + nullptr, + nullptr, + nullptr, + bz2_is_close, + nullptr, + nullptr, + nullptr, + nullptr, + bz2_is_read, + bz2_is_eof, + nullptr, +}; + +const struct archive_plugin bz2_archive_plugin = { + "bz2", + nullptr, + nullptr, + bz2_open, + bz2_extensions, +}; + diff --git a/src/archive/bz2_archive_plugin.h b/src/archive/Bzip2ArchivePlugin.hxx index 46c69a66..a7933a7a 100644 --- a/src/archive/bz2_archive_plugin.h +++ b/src/archive/Bzip2ArchivePlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_ARCHIVE_BZ2_H -#define MPD_ARCHIVE_BZ2_H +#ifndef MPD_ARCHIVE_BZ2_HXX +#define MPD_ARCHIVE_BZ2_HXX extern const struct archive_plugin bz2_archive_plugin; diff --git a/src/archive/Iso9660ArchivePlugin.cxx b/src/archive/Iso9660ArchivePlugin.cxx new file mode 100644 index 00000000..97fd8bd5 --- /dev/null +++ b/src/archive/Iso9660ArchivePlugin.cxx @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * iso archive handling (requires cdio, and iso9660) + */ + +#include "config.h" +#include "Iso9660ArchivePlugin.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "ArchiveVisitor.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "util/RefCount.hxx" + +#include <cdio/cdio.h> +#include <cdio/iso9660.h> + +#include <glib.h> + +#include <stdlib.h> +#include <string.h> + +#define CEILING(x, y) ((x+(y-1))/y) + +class Iso9660ArchiveFile final : public ArchiveFile { +public: + RefCount ref; + + iso9660_t *iso; + + Iso9660ArchiveFile(iso9660_t *_iso) + :ArchiveFile(iso9660_archive_plugin), iso(_iso) {} + + ~Iso9660ArchiveFile() { + iso9660_close(iso); + } + + void Unref() { + if (ref.Decrement()) + delete this; + } + + void Visit(const char *path, ArchiveVisitor &visitor); + + virtual void Close() override { + Unref(); + } + + virtual void Visit(ArchiveVisitor &visitor) override; + + virtual input_stream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + GError **error_r) override; +}; + +extern const struct input_plugin iso9660_input_plugin; + +static inline GQuark +iso9660_quark(void) +{ + return g_quark_from_static_string("iso9660"); +} + +/* archive open && listing routine */ + +inline void +Iso9660ArchiveFile::Visit(const char *psz_path, ArchiveVisitor &visitor) +{ + CdioList_t *entlist; + CdioListNode_t *entnode; + iso9660_stat_t *statbuf; + char pathname[4096]; + + entlist = iso9660_ifs_readdir (iso, psz_path); + if (!entlist) { + return; + } + /* Iterate over the list of nodes that iso9660_ifs_readdir gives */ + _CDIO_LIST_FOREACH (entnode, entlist) { + statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode); + + strcpy(pathname, psz_path); + strcat(pathname, statbuf->filename); + + if (iso9660_stat_s::_STAT_DIR == statbuf->type ) { + if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) { + strcat(pathname, "/"); + Visit(pathname, visitor); + } + } else { + //remove leading / + visitor.VisitArchiveEntry(pathname + 1); + } + } + _cdio_list_free (entlist, true); +} + +static ArchiveFile * +iso9660_archive_open(const char *pathname, GError **error_r) +{ + /* open archive */ + auto iso = iso9660_open(pathname); + if (iso == nullptr) { + g_set_error(error_r, iso9660_quark(), 0, + "Failed to open ISO9660 file %s", pathname); + return NULL; + } + + return new Iso9660ArchiveFile(iso); +} + +void +Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor) +{ + Visit("/", visitor); +} + +/* single archive handling */ + +struct Iso9660InputStream { + struct input_stream base; + + Iso9660ArchiveFile *archive; + + iso9660_stat_t *statbuf; + size_t max_blocks; + + Iso9660InputStream(Iso9660ArchiveFile &_archive, const char *uri, + Mutex &mutex, Cond &cond, + iso9660_stat_t *_statbuf) + :base(iso9660_input_plugin, uri, mutex, cond), + archive(&_archive), statbuf(_statbuf), + max_blocks(CEILING(statbuf->size, ISO_BLOCKSIZE)) { + + base.ready = true; + base.size = statbuf->size; + + archive->ref.Increment(); + } + + ~Iso9660InputStream() { + free(statbuf); + archive->Unref(); + } +}; + +input_stream * +Iso9660ArchiveFile::OpenStream(const char *pathname, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + auto statbuf = iso9660_ifs_stat_translate(iso, pathname); + if (statbuf == nullptr) { + g_set_error(error_r, iso9660_quark(), 0, + "not found in the ISO file: %s", pathname); + return NULL; + } + + Iso9660InputStream *iis = + new Iso9660InputStream(*this, pathname, mutex, cond, + statbuf); + return &iis->base; +} + +static void +iso9660_input_close(struct input_stream *is) +{ + Iso9660InputStream *iis = (Iso9660InputStream *)is; + + delete iis; +} + + +static size_t +iso9660_input_read(struct input_stream *is, void *ptr, size_t size, GError **error_r) +{ + Iso9660InputStream *iis = (Iso9660InputStream *)is; + int toread, readed = 0; + int no_blocks, cur_block; + size_t left_bytes = iis->statbuf->size - is->offset; + + size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE; + + if (left_bytes < size) { + toread = left_bytes; + no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE); + } else { + toread = size; + no_blocks = toread / ISO_BLOCKSIZE; + } + if (no_blocks > 0) { + + cur_block = is->offset / ISO_BLOCKSIZE; + + readed = iso9660_iso_seek_read (iis->archive->iso, ptr, + iis->statbuf->lsn + cur_block, no_blocks); + + if (readed != no_blocks * ISO_BLOCKSIZE) { + g_set_error(error_r, iso9660_quark(), 0, + "error reading ISO file at lsn %lu", + (long unsigned int) cur_block); + return 0; + } + if (left_bytes < size) { + readed = left_bytes; + } + + is->offset += readed; + } + return readed; +} + +static bool +iso9660_input_eof(struct input_stream *is) +{ + return is->offset == is->size; +} + +/* exported structures */ + +static const char *const iso9660_archive_extensions[] = { + "iso", + NULL +}; + +const struct input_plugin iso9660_input_plugin = { + nullptr, + nullptr, + nullptr, + nullptr, + iso9660_input_close, + nullptr, + nullptr, + nullptr, + nullptr, + iso9660_input_read, + iso9660_input_eof, + nullptr, +}; + +const struct archive_plugin iso9660_archive_plugin = { + "iso", + nullptr, + nullptr, + iso9660_archive_open, + iso9660_archive_extensions, +}; diff --git a/src/archive/iso9660_archive_plugin.h b/src/archive/Iso9660ArchivePlugin.hxx index 47dc6e47..6fbab615 100644 --- a/src/archive/iso9660_archive_plugin.h +++ b/src/archive/Iso9660ArchivePlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_ARCHIVE_ISO9660_H -#define MPD_ARCHIVE_ISO9660_H +#ifndef MPD_ARCHIVE_ISO9660_HXX +#define MPD_ARCHIVE_ISO9660_HXX extern const struct archive_plugin iso9660_archive_plugin; diff --git a/src/archive/ZzipArchivePlugin.cxx b/src/archive/ZzipArchivePlugin.cxx new file mode 100644 index 00000000..d0db7aa3 --- /dev/null +++ b/src/archive/ZzipArchivePlugin.cxx @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * zip archive handling (requires zziplib) + */ + +#include "config.h" +#include "ZzipArchivePlugin.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "ArchiveVisitor.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "util/RefCount.hxx" + +#include <zzip/zzip.h> +#include <glib.h> +#include <string.h> + +class ZzipArchiveFile final : public ArchiveFile { +public: + RefCount ref; + + ZZIP_DIR *const dir; + + ZzipArchiveFile(ZZIP_DIR *_dir) + :ArchiveFile(zzip_archive_plugin), dir(_dir) {} + + ~ZzipArchiveFile() { + zzip_dir_close(dir); + } + + void Unref() { + if (ref.Decrement()) + delete this; + } + + virtual void Close() override { + Unref(); + } + + virtual void Visit(ArchiveVisitor &visitor) override; + + virtual input_stream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + GError **error_r) override; +}; + +extern const struct input_plugin zzip_input_plugin; + +static inline GQuark +zzip_quark(void) +{ + return g_quark_from_static_string("zzip"); +} + +/* archive open && listing routine */ + +static ArchiveFile * +zzip_archive_open(const char *pathname, GError **error_r) +{ + ZZIP_DIR *dir = zzip_dir_open(pathname, NULL); + if (dir == nullptr) { + g_set_error(error_r, zzip_quark(), 0, + "Failed to open ZIP file %s", pathname); + return NULL; + } + + return new ZzipArchiveFile(dir); +} + +inline void +ZzipArchiveFile::Visit(ArchiveVisitor &visitor) +{ + zzip_rewinddir(dir); + + ZZIP_DIRENT dirent; + while (zzip_dir_read(dir, &dirent)) + //add only files + if (dirent.st_size > 0) + visitor.VisitArchiveEntry(dirent.d_name); +} + +/* single archive handling */ + +struct ZzipInputStream { + struct input_stream base; + + ZzipArchiveFile *archive; + + ZZIP_FILE *file; + + ZzipInputStream(ZzipArchiveFile &_archive, const char *uri, + Mutex &mutex, Cond &cond, + ZZIP_FILE *_file) + :base(zzip_input_plugin, uri, mutex, cond), + archive(&_archive), file(_file) { + base.ready = true; + //we are seekable (but its not recommendent to do so) + base.seekable = true; + + ZZIP_STAT z_stat; + zzip_file_stat(file, &z_stat); + base.size = z_stat.st_size; + + archive->ref.Increment(); + } + + ~ZzipInputStream() { + zzip_file_close(file); + archive->Unref(); + } +}; + +input_stream * +ZzipArchiveFile::OpenStream(const char *pathname, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + ZZIP_FILE *_file = zzip_file_open(dir, pathname, 0); + if (_file == nullptr) { + g_set_error(error_r, zzip_quark(), 0, + "not found in the ZIP file: %s", pathname); + return NULL; + } + + ZzipInputStream *zis = + new ZzipInputStream(*this, pathname, + mutex, cond, + _file); + return &zis->base; +} + +static void +zzip_input_close(struct input_stream *is) +{ + ZzipInputStream *zis = (ZzipInputStream *)is; + + delete zis; +} + +static size_t +zzip_input_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + ZzipInputStream *zis = (ZzipInputStream *)is; + int ret; + + ret = zzip_file_read(zis->file, ptr, size); + if (ret < 0) { + g_set_error(error_r, zzip_quark(), ret, + "zzip_file_read() has failed"); + return 0; + } + + is->offset = zzip_tell(zis->file); + + return ret; +} + +static bool +zzip_input_eof(struct input_stream *is) +{ + ZzipInputStream *zis = (ZzipInputStream *)is; + + return (goffset)zzip_tell(zis->file) == is->size; +} + +static bool +zzip_input_seek(struct input_stream *is, + goffset offset, int whence, GError **error_r) +{ + ZzipInputStream *zis = (ZzipInputStream *)is; + zzip_off_t ofs = zzip_seek(zis->file, offset, whence); + if (ofs != -1) { + g_set_error(error_r, zzip_quark(), ofs, + "zzip_seek() has failed"); + is->offset = ofs; + return true; + } + return false; +} + +/* exported structures */ + +static const char *const zzip_archive_extensions[] = { + "zip", + NULL +}; + +const struct input_plugin zzip_input_plugin = { + nullptr, + nullptr, + nullptr, + nullptr, + zzip_input_close, + nullptr, + nullptr, + nullptr, + nullptr, + zzip_input_read, + zzip_input_eof, + zzip_input_seek, +}; + +const struct archive_plugin zzip_archive_plugin = { + "zzip", + nullptr, + nullptr, + zzip_archive_open, + zzip_archive_extensions, +}; diff --git a/src/archive/zzip_archive_plugin.h b/src/archive/ZzipArchivePlugin.hxx index 2b2c01e5..4ba16849 100644 --- a/src/archive/zzip_archive_plugin.h +++ b/src/archive/ZzipArchivePlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_ARCHIVE_ZZIP_H -#define MPD_ARCHIVE_ZZIP_H +#ifndef MPD_ARCHIVE_ZZIP_HXX +#define MPD_ARCHIVE_ZZIP_HXX extern const struct archive_plugin zzip_archive_plugin; diff --git a/src/archive/bz2_archive_plugin.c b/src/archive/bz2_archive_plugin.c deleted file mode 100644 index e2420048..00000000 --- a/src/archive/bz2_archive_plugin.c +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * single bz2 archive handling (requires libbz2) - */ - -#include "config.h" -#include "archive/bz2_archive_plugin.h" -#include "archive_api.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "refcount.h" - -#include <stdint.h> -#include <stddef.h> -#include <string.h> -#include <glib.h> -#include <bzlib.h> - -#ifdef HAVE_OLDER_BZIP2 -#define BZ2_bzDecompressInit bzDecompressInit -#define BZ2_bzDecompress bzDecompress -#endif - -struct bz2_archive_file { - struct archive_file base; - - struct refcount ref; - - char *name; - bool reset; - struct input_stream *istream; -}; - -struct bz2_input_stream { - struct input_stream base; - - struct bz2_archive_file *archive; - - bool eof; - - bz_stream bzstream; - - char buffer[5000]; -}; - -static const struct input_plugin bz2_inputplugin; - -static inline GQuark -bz2_quark(void) -{ - return g_quark_from_static_string("bz2"); -} - -/* single archive handling allocation helpers */ - -static bool -bz2_alloc(struct bz2_input_stream *data, GError **error_r) -{ - int ret; - - data->bzstream.bzalloc = NULL; - data->bzstream.bzfree = NULL; - data->bzstream.opaque = NULL; - - data->bzstream.next_in = (void *) data->buffer; - data->bzstream.avail_in = 0; - - ret = BZ2_bzDecompressInit(&data->bzstream, 0, 0); - if (ret != BZ_OK) { - g_free(data); - - g_set_error(error_r, bz2_quark(), ret, - "BZ2_bzDecompressInit() has failed"); - return false; - } - - return true; -} - -static void -bz2_destroy(struct bz2_input_stream *data) -{ - BZ2_bzDecompressEnd(&data->bzstream); -} - -/* archive open && listing routine */ - -#if GCC_CHECK_VERSION(4, 2) -/* workaround for a warning caused by G_STATIC_MUTEX_INIT */ -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif - -static struct archive_file * -bz2_open(const char *pathname, GError **error_r) -{ - struct bz2_archive_file *context; - int len; - - context = g_malloc(sizeof(*context)); - archive_file_init(&context->base, &bz2_archive_plugin); - refcount_init(&context->ref); - - //open archive - static GStaticMutex mutex = G_STATIC_MUTEX_INIT; - context->istream = input_stream_open(pathname, - g_static_mutex_get_mutex(&mutex), - NULL, - error_r); - if (context->istream == NULL) { - g_free(context); - return NULL; - } - - context->name = g_path_get_basename(pathname); - - //remove suffix - len = strlen(context->name); - if (len > 4) { - context->name[len - 4] = 0; //remove .bz2 suffix - } - - return &context->base; -} - -static void -bz2_scan_reset(struct archive_file *file) -{ - struct bz2_archive_file *context = (struct bz2_archive_file *) file; - context->reset = true; -} - -static char * -bz2_scan_next(struct archive_file *file) -{ - struct bz2_archive_file *context = (struct bz2_archive_file *) file; - char *name = NULL; - - if (context->reset) { - name = context->name; - context->reset = false; - } - - return name; -} - -static void -bz2_close(struct archive_file *file) -{ - struct bz2_archive_file *context = (struct bz2_archive_file *) file; - - if (!refcount_dec(&context->ref)) - return; - - g_free(context->name); - - input_stream_close(context->istream); - g_free(context); -} - -/* single archive handling */ - -static struct input_stream * -bz2_open_stream(struct archive_file *file, const char *path, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct bz2_archive_file *context = (struct bz2_archive_file *) file; - struct bz2_input_stream *bis = g_new(struct bz2_input_stream, 1); - - input_stream_init(&bis->base, &bz2_inputplugin, path, - mutex, cond); - - bis->archive = context; - - bis->base.ready = true; - bis->base.seekable = false; - - if (!bz2_alloc(bis, error_r)) { - input_stream_deinit(&bis->base); - g_free(bis); - return NULL; - } - - bis->eof = false; - - refcount_inc(&context->ref); - - return &bis->base; -} - -static void -bz2_is_close(struct input_stream *is) -{ - struct bz2_input_stream *bis = (struct bz2_input_stream *)is; - - bz2_destroy(bis); - - bz2_close(&bis->archive->base); - - input_stream_deinit(&bis->base); - g_free(bis); -} - -static bool -bz2_fillbuffer(struct bz2_input_stream *bis, GError **error_r) -{ - size_t count; - bz_stream *bzstream; - - bzstream = &bis->bzstream; - - if (bzstream->avail_in > 0) - return true; - - count = input_stream_read(bis->archive->istream, - bis->buffer, sizeof(bis->buffer), - error_r); - if (count == 0) - return false; - - bzstream->next_in = bis->buffer; - bzstream->avail_in = count; - return true; -} - -static size_t -bz2_is_read(struct input_stream *is, void *ptr, size_t length, - GError **error_r) -{ - struct bz2_input_stream *bis = (struct bz2_input_stream *)is; - bz_stream *bzstream; - int bz_result; - size_t nbytes = 0; - - if (bis->eof) - return 0; - - bzstream = &bis->bzstream; - bzstream->next_out = ptr; - bzstream->avail_out = length; - - do { - if (!bz2_fillbuffer(bis, error_r)) - return 0; - - bz_result = BZ2_bzDecompress(bzstream); - - if (bz_result == BZ_STREAM_END) { - bis->eof = true; - break; - } - - if (bz_result != BZ_OK) { - g_set_error(error_r, bz2_quark(), bz_result, - "BZ2_bzDecompress() has failed"); - return 0; - } - } while (bzstream->avail_out == length); - - nbytes = length - bzstream->avail_out; - is->offset += nbytes; - - return nbytes; -} - -static bool -bz2_is_eof(struct input_stream *is) -{ - struct bz2_input_stream *bis = (struct bz2_input_stream *)is; - - return bis->eof; -} - -/* exported structures */ - -static const char *const bz2_extensions[] = { - "bz2", - NULL -}; - -static const struct input_plugin bz2_inputplugin = { - .close = bz2_is_close, - .read = bz2_is_read, - .eof = bz2_is_eof, -}; - -const struct archive_plugin bz2_archive_plugin = { - .name = "bz2", - .open = bz2_open, - .scan_reset = bz2_scan_reset, - .scan_next = bz2_scan_next, - .open_stream = bz2_open_stream, - .close = bz2_close, - .suffixes = bz2_extensions -}; - diff --git a/src/archive/iso9660_archive_plugin.c b/src/archive/iso9660_archive_plugin.c deleted file mode 100644 index bb6cb958..00000000 --- a/src/archive/iso9660_archive_plugin.c +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * iso archive handling (requires cdio, and iso9660) - */ - -#include "config.h" -#include "archive/iso9660_archive_plugin.h" -#include "archive_api.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "refcount.h" - -#include <cdio/cdio.h> -#include <cdio/iso9660.h> - -#include <glib.h> -#include <string.h> - -#define CEILING(x, y) ((x+(y-1))/y) - -struct iso9660_archive_file { - struct archive_file base; - - struct refcount ref; - - iso9660_t *iso; - GSList *list; - GSList *iter; -}; - -static const struct input_plugin iso9660_input_plugin; - -static inline GQuark -iso9660_quark(void) -{ - return g_quark_from_static_string("iso9660"); -} - -/* archive open && listing routine */ - -static void -listdir_recur(const char *psz_path, struct iso9660_archive_file *context) -{ - iso9660_t *iso = context->iso; - CdioList_t *entlist; - CdioListNode_t *entnode; - iso9660_stat_t *statbuf; - char pathname[4096]; - - entlist = iso9660_ifs_readdir (iso, psz_path); - if (!entlist) { - return; - } - /* Iterate over the list of nodes that iso9660_ifs_readdir gives */ - _CDIO_LIST_FOREACH (entnode, entlist) { - statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode); - - strcpy(pathname, psz_path); - strcat(pathname, statbuf->filename); - - if (_STAT_DIR == statbuf->type ) { - if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) { - strcat(pathname, "/"); - listdir_recur(pathname, context); - } - } else { - //remove leading / - context->list = g_slist_prepend( context->list, - g_strdup(pathname + 1)); - } - } - _cdio_list_free (entlist, true); -} - -static struct archive_file * -iso9660_archive_open(const char *pathname, GError **error_r) -{ - struct iso9660_archive_file *context = - g_new(struct iso9660_archive_file, 1); - - archive_file_init(&context->base, &iso9660_archive_plugin); - refcount_init(&context->ref); - - context->list = NULL; - - /* open archive */ - context->iso = iso9660_open (pathname); - if (context->iso == NULL) { - g_set_error(error_r, iso9660_quark(), 0, - "Failed to open ISO9660 file %s", pathname); - return NULL; - } - - listdir_recur("/", context); - - return &context->base; -} - -static void -iso9660_archive_scan_reset(struct archive_file *file) -{ - struct iso9660_archive_file *context = - (struct iso9660_archive_file *)file; - - //reset iterator - context->iter = context->list; -} - -static char * -iso9660_archive_scan_next(struct archive_file *file) -{ - struct iso9660_archive_file *context = - (struct iso9660_archive_file *)file; - - char *data = NULL; - if (context->iter != NULL) { - ///fetch data and goto next - data = context->iter->data; - context->iter = g_slist_next(context->iter); - } - return data; -} - -static void -iso9660_archive_close(struct archive_file *file) -{ - struct iso9660_archive_file *context = - (struct iso9660_archive_file *)file; - GSList *tmp; - - if (!refcount_dec(&context->ref)) - return; - - if (context->list) { - //free list - for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp)) - g_free(tmp->data); - g_slist_free(context->list); - } - //close archive - iso9660_close(context->iso); - - g_free(context); -} - -/* single archive handling */ - -struct iso9660_input_stream { - struct input_stream base; - - struct iso9660_archive_file *archive; - - iso9660_stat_t *statbuf; - size_t max_blocks; -}; - -static struct input_stream * -iso9660_archive_open_stream(struct archive_file *file, const char *pathname, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct iso9660_archive_file *context = - (struct iso9660_archive_file *)file; - struct iso9660_input_stream *iis; - - iis = g_new(struct iso9660_input_stream, 1); - input_stream_init(&iis->base, &iso9660_input_plugin, pathname, - mutex, cond); - - iis->archive = context; - iis->statbuf = iso9660_ifs_stat_translate(context->iso, pathname); - if (iis->statbuf == NULL) { - g_free(iis); - g_set_error(error_r, iso9660_quark(), 0, - "not found in the ISO file: %s", pathname); - return NULL; - } - - iis->base.ready = true; - //we are not seekable - iis->base.seekable = false; - - iis->base.size = iis->statbuf->size; - - iis->max_blocks = CEILING(iis->statbuf->size, ISO_BLOCKSIZE); - - refcount_inc(&context->ref); - - return &iis->base; -} - -static void -iso9660_input_close(struct input_stream *is) -{ - struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is; - - g_free(iis->statbuf); - - iso9660_archive_close(&iis->archive->base); - - input_stream_deinit(&iis->base); - g_free(iis); -} - - -static size_t -iso9660_input_read(struct input_stream *is, void *ptr, size_t size, GError **error_r) -{ - struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is; - int toread, readed = 0; - int no_blocks, cur_block; - size_t left_bytes = iis->statbuf->size - is->offset; - - size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE; - - if (left_bytes < size) { - toread = left_bytes; - no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE); - } else { - toread = size; - no_blocks = toread / ISO_BLOCKSIZE; - } - if (no_blocks > 0) { - - cur_block = is->offset / ISO_BLOCKSIZE; - - readed = iso9660_iso_seek_read (iis->archive->iso, ptr, - iis->statbuf->lsn + cur_block, no_blocks); - - if (readed != no_blocks * ISO_BLOCKSIZE) { - g_set_error(error_r, iso9660_quark(), 0, - "error reading ISO file at lsn %lu", - (long unsigned int) cur_block); - return 0; - } - if (left_bytes < size) { - readed = left_bytes; - } - - is->offset += readed; - } - return readed; -} - -static bool -iso9660_input_eof(struct input_stream *is) -{ - return is->offset == is->size; -} - -/* exported structures */ - -static const char *const iso9660_archive_extensions[] = { - "iso", - NULL -}; - -static const struct input_plugin iso9660_input_plugin = { - .close = iso9660_input_close, - .read = iso9660_input_read, - .eof = iso9660_input_eof, -}; - -const struct archive_plugin iso9660_archive_plugin = { - .name = "iso", - .open = iso9660_archive_open, - .scan_reset = iso9660_archive_scan_reset, - .scan_next = iso9660_archive_scan_next, - .open_stream = iso9660_archive_open_stream, - .close = iso9660_archive_close, - .suffixes = iso9660_archive_extensions -}; diff --git a/src/archive/zzip_archive_plugin.c b/src/archive/zzip_archive_plugin.c deleted file mode 100644 index ad96b5f8..00000000 --- a/src/archive/zzip_archive_plugin.c +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * zip archive handling (requires zziplib) - */ - -#include "config.h" -#include "archive/zzip_archive_plugin.h" -#include "archive_api.h" -#include "archive_api.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "refcount.h" - -#include <zzip/zzip.h> -#include <glib.h> -#include <string.h> - -struct zzip_archive { - struct archive_file base; - - struct refcount ref; - - ZZIP_DIR *dir; - GSList *list; - GSList *iter; -}; - -static const struct input_plugin zzip_input_plugin; - -static inline GQuark -zzip_quark(void) -{ - return g_quark_from_static_string("zzip"); -} - -/* archive open && listing routine */ - -static struct archive_file * -zzip_archive_open(const char *pathname, GError **error_r) -{ - struct zzip_archive *context = g_malloc(sizeof(*context)); - ZZIP_DIRENT dirent; - - archive_file_init(&context->base, &zzip_archive_plugin); - refcount_init(&context->ref); - - // open archive - context->list = NULL; - context->dir = zzip_dir_open(pathname, NULL); - if (context->dir == NULL) { - g_set_error(error_r, zzip_quark(), 0, - "Failed to open ZIP file %s", pathname); - return NULL; - } - - while (zzip_dir_read(context->dir, &dirent)) { - //add only files - if (dirent.st_size > 0) { - context->list = g_slist_prepend(context->list, - g_strdup(dirent.d_name)); - } - } - - return &context->base; -} - -static void -zzip_archive_scan_reset(struct archive_file *file) -{ - struct zzip_archive *context = (struct zzip_archive *) file; - //reset iterator - context->iter = context->list; -} - -static char * -zzip_archive_scan_next(struct archive_file *file) -{ - struct zzip_archive *context = (struct zzip_archive *) file; - char *data = NULL; - if (context->iter != NULL) { - ///fetch data and goto next - data = context->iter->data; - context->iter = g_slist_next(context->iter); - } - return data; -} - -static void -zzip_archive_close(struct archive_file *file) -{ - struct zzip_archive *context = (struct zzip_archive *) file; - - if (!refcount_dec(&context->ref)) - return; - - if (context->list) { - //free list - for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp)) - g_free(tmp->data); - g_slist_free(context->list); - } - //close archive - zzip_dir_close (context->dir); - - g_free(context); -} - -/* single archive handling */ - -struct zzip_input_stream { - struct input_stream base; - - struct zzip_archive *archive; - - ZZIP_FILE *file; -}; - -static struct input_stream * -zzip_archive_open_stream(struct archive_file *file, - const char *pathname, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct zzip_archive *context = (struct zzip_archive *) file; - struct zzip_input_stream *zis; - ZZIP_STAT z_stat; - - zis = g_new(struct zzip_input_stream, 1); - input_stream_init(&zis->base, &zzip_input_plugin, pathname, - mutex, cond); - - zis->archive = context; - zis->file = zzip_file_open(context->dir, pathname, 0); - if (zis->file == NULL) { - g_free(zis); - g_set_error(error_r, zzip_quark(), 0, - "not found in the ZIP file: %s", pathname); - return NULL; - } - - zis->base.ready = true; - //we are seekable (but its not recommendent to do so) - zis->base.seekable = true; - - zzip_file_stat(zis->file, &z_stat); - zis->base.size = z_stat.st_size; - - refcount_inc(&context->ref); - - return &zis->base; -} - -static void -zzip_input_close(struct input_stream *is) -{ - struct zzip_input_stream *zis = (struct zzip_input_stream *)is; - - zzip_file_close(zis->file); - zzip_archive_close(&zis->archive->base); - input_stream_deinit(&zis->base); - g_free(zis); -} - -static size_t -zzip_input_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct zzip_input_stream *zis = (struct zzip_input_stream *)is; - int ret; - - ret = zzip_file_read(zis->file, ptr, size); - if (ret < 0) { - g_set_error(error_r, zzip_quark(), ret, - "zzip_file_read() has failed"); - return 0; - } - - is->offset = zzip_tell(zis->file); - - return ret; -} - -static bool -zzip_input_eof(struct input_stream *is) -{ - struct zzip_input_stream *zis = (struct zzip_input_stream *)is; - - return (goffset)zzip_tell(zis->file) == is->size; -} - -static bool -zzip_input_seek(struct input_stream *is, - goffset offset, int whence, GError **error_r) -{ - struct zzip_input_stream *zis = (struct zzip_input_stream *)is; - zzip_off_t ofs = zzip_seek(zis->file, offset, whence); - if (ofs != -1) { - g_set_error(error_r, zzip_quark(), ofs, - "zzip_seek() has failed"); - is->offset = ofs; - return true; - } - return false; -} - -/* exported structures */ - -static const char *const zzip_archive_extensions[] = { - "zip", - NULL -}; - -static const struct input_plugin zzip_input_plugin = { - .close = zzip_input_close, - .read = zzip_input_read, - .eof = zzip_input_eof, - .seek = zzip_input_seek, -}; - -const struct archive_plugin zzip_archive_plugin = { - .name = "zzip", - .open = zzip_archive_open, - .scan_reset = zzip_archive_scan_reset, - .scan_next = zzip_archive_scan_next, - .open_stream = zzip_archive_open_stream, - .close = zzip_archive_close, - .suffixes = zzip_archive_extensions -}; diff --git a/src/archive_internal.h b/src/archive_internal.h deleted file mode 100644 index 0d885e91..00000000 --- a/src/archive_internal.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_INTERNAL_H -#define MPD_ARCHIVE_INTERNAL_H - -struct archive_file { - const struct archive_plugin *plugin; -}; - -static inline void -archive_file_init(struct archive_file *archive_file, - const struct archive_plugin *plugin) -{ - archive_file->plugin = plugin; -} - -#endif diff --git a/src/archive_plugin.c b/src/archive_plugin.c deleted file mode 100644 index cf23e639..00000000 --- a/src/archive_plugin.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "archive_plugin.h" -#include "archive_internal.h" - -#include <assert.h> - -struct archive_file * -archive_file_open(const struct archive_plugin *plugin, const char *path, - GError **error_r) -{ - struct archive_file *file; - - assert(plugin != NULL); - assert(plugin->open != NULL); - assert(path != NULL); - assert(error_r == NULL || *error_r == NULL); - - file = plugin->open(path, error_r); - - if (file != NULL) { - assert(file->plugin != NULL); - assert(file->plugin->close != NULL); - assert(file->plugin->scan_reset != NULL); - assert(file->plugin->scan_next != NULL); - assert(file->plugin->open_stream != NULL); - assert(error_r == NULL || *error_r == NULL); - } else { - assert(error_r == NULL || *error_r != NULL); - } - - return file; -} - -void -archive_file_close(struct archive_file *file) -{ - assert(file != NULL); - assert(file->plugin != NULL); - assert(file->plugin->close != NULL); - - file->plugin->close(file); -} - -void -archive_file_scan_reset(struct archive_file *file) -{ - assert(file != NULL); - assert(file->plugin != NULL); - assert(file->plugin->scan_reset != NULL); - assert(file->plugin->scan_next != NULL); - - file->plugin->scan_reset(file); -} - -char * -archive_file_scan_next(struct archive_file *file) -{ - assert(file != NULL); - assert(file->plugin != NULL); - assert(file->plugin->scan_next != NULL); - - return file->plugin->scan_next(file); -} - -struct input_stream * -archive_file_open_stream(struct archive_file *file, const char *path, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - assert(file != NULL); - assert(file->plugin != NULL); - assert(file->plugin->open_stream != NULL); - - return file->plugin->open_stream(file, path, mutex, cond, - error_r); -} diff --git a/src/audio_check.h b/src/audio_check.h index 9f71cf9c..d4d3f13f 100644 --- a/src/audio_check.h +++ b/src/audio_check.h @@ -28,13 +28,17 @@ /** * The GLib quark used for errors reported by this library. */ -G_GNUC_CONST +gcc_const static inline GQuark audio_format_quark(void) { return g_quark_from_static_string("audio_format"); } +#ifdef __cplusplus +extern "C" { +#endif + bool audio_check_sample_rate(unsigned long sample_rate, GError **error_r); @@ -52,4 +56,8 @@ audio_format_init_checked(struct audio_format *af, unsigned long sample_rate, enum sample_format sample_format, unsigned channels, GError **error_r); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/audio_format.h b/src/audio_format.h index bf77add3..f9b176bc 100644 --- a/src/audio_format.h +++ b/src/audio_format.h @@ -20,7 +20,8 @@ #ifndef MPD_AUDIO_FORMAT_H #define MPD_AUDIO_FORMAT_H -#include <glib.h> +#include "gcc.h" + #include <stdint.h> #include <stdbool.h> #include <assert.h> @@ -189,7 +190,7 @@ audio_valid_channel_count(unsigned channels) * Returns false if the format is not valid for playback with MPD. * This function performs some basic validity checks. */ -G_GNUC_PURE +gcc_pure static inline bool audio_format_valid(const struct audio_format *af) { return audio_valid_sample_rate(af->sample_rate) && @@ -201,7 +202,7 @@ static inline bool audio_format_valid(const struct audio_format *af) * Returns false if the format mask is not valid for playback with * MPD. This function performs some basic validity checks. */ -G_GNUC_PURE +gcc_pure static inline bool audio_format_mask_valid(const struct audio_format *af) { return (af->sample_rate == 0 || @@ -219,11 +220,15 @@ static inline bool audio_format_equals(const struct audio_format *a, a->channels == b->channels; } +#ifdef __cplusplus +extern "C" { +#endif + void audio_format_mask_apply(struct audio_format *af, const struct audio_format *mask); -G_GNUC_CONST +gcc_const static inline unsigned sample_format_size(enum sample_format format) { @@ -254,7 +259,7 @@ sample_format_size(enum sample_format format) /** * Returns the size of each (mono) sample in bytes. */ -G_GNUC_PURE +gcc_pure static inline unsigned audio_format_sample_size(const struct audio_format *af) { return sample_format_size((enum sample_format)af->format); @@ -263,7 +268,7 @@ static inline unsigned audio_format_sample_size(const struct audio_format *af) /** * Returns the size of each full frame in bytes. */ -G_GNUC_PURE +gcc_pure static inline unsigned audio_format_frame_size(const struct audio_format *af) { @@ -274,7 +279,7 @@ audio_format_frame_size(const struct audio_format *af) * Returns the floating point factor which converts a time span to a * storage size in bytes. */ -G_GNUC_PURE +gcc_pure static inline double audio_format_time_to_size(const struct audio_format *af) { return af->sample_rate * audio_format_frame_size(af); @@ -287,7 +292,7 @@ static inline double audio_format_time_to_size(const struct audio_format *af) * @param format a #sample_format enum value * @return the string */ -G_GNUC_PURE G_GNUC_MALLOC +gcc_pure gcc_malloc const char * sample_format_to_string(enum sample_format format); @@ -299,9 +304,13 @@ sample_format_to_string(enum sample_format format); * @param s a buffer to print into * @return the string, or NULL if the #audio_format object is invalid */ -G_GNUC_PURE G_GNUC_MALLOC +gcc_pure gcc_malloc const char * audio_format_to_string(const struct audio_format *af, struct audio_format_string *s); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/buffer.c b/src/buffer.c deleted file mode 100644 index 559f39a9..00000000 --- a/src/buffer.c +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "buffer.h" -#include "chunk.h" -#include "poison.h" - -#include <glib.h> - -#include <assert.h> - -struct music_buffer { - struct music_chunk *chunks; - unsigned num_chunks; - - struct music_chunk *available; - - /** a mutex which protects #available */ - GMutex *mutex; - -#ifndef NDEBUG - unsigned num_allocated; -#endif -}; - -struct music_buffer * -music_buffer_new(unsigned num_chunks) -{ - struct music_buffer *buffer; - struct music_chunk *chunk; - - assert(num_chunks > 0); - - buffer = g_new(struct music_buffer, 1); - - buffer->chunks = g_new(struct music_chunk, num_chunks); - buffer->num_chunks = num_chunks; - - chunk = buffer->available = buffer->chunks; - poison_undefined(chunk, sizeof(*chunk)); - - for (unsigned i = 1; i < num_chunks; ++i) { - chunk->next = &buffer->chunks[i]; - chunk = chunk->next; - poison_undefined(chunk, sizeof(*chunk)); - } - - chunk->next = NULL; - - buffer->mutex = g_mutex_new(); - -#ifndef NDEBUG - buffer->num_allocated = 0; -#endif - - return buffer; -} - -void -music_buffer_free(struct music_buffer *buffer) -{ - assert(buffer->chunks != NULL); - assert(buffer->num_chunks > 0); - assert(buffer->num_allocated == 0); - - g_mutex_free(buffer->mutex); - g_free(buffer->chunks); - g_free(buffer); -} - -unsigned -music_buffer_size(const struct music_buffer *buffer) -{ - return buffer->num_chunks; -} - -struct music_chunk * -music_buffer_allocate(struct music_buffer *buffer) -{ - struct music_chunk *chunk; - - g_mutex_lock(buffer->mutex); - - chunk = buffer->available; - if (chunk != NULL) { - buffer->available = chunk->next; - music_chunk_init(chunk); - -#ifndef NDEBUG - ++buffer->num_allocated; -#endif - } - - g_mutex_unlock(buffer->mutex); - return chunk; -} - -void -music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk) -{ - assert(buffer != NULL); - assert(chunk != NULL); - - if (chunk->other != NULL) - music_buffer_return(buffer, chunk->other); - - g_mutex_lock(buffer->mutex); - - music_chunk_free(chunk); - poison_undefined(chunk, sizeof(*chunk)); - - chunk->next = buffer->available; - buffer->available = chunk; - -#ifndef NDEBUG - --buffer->num_allocated; -#endif - - g_mutex_unlock(buffer->mutex); -} diff --git a/src/chunk.c b/src/chunk.c deleted file mode 100644 index 1eb96f4b..00000000 --- a/src/chunk.c +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "chunk.h" -#include "audio_format.h" -#include "tag.h" - -#include <assert.h> - -void -music_chunk_init(struct music_chunk *chunk) -{ - chunk->other = NULL; - chunk->length = 0; - chunk->tag = NULL; - chunk->replay_gain_serial = 0; -} - -void -music_chunk_free(struct music_chunk *chunk) -{ - if (chunk->tag != NULL) - tag_free(chunk->tag); -} - -#ifndef NDEBUG -bool -music_chunk_check_format(const struct music_chunk *chunk, - const struct audio_format *audio_format) -{ - assert(chunk != NULL); - assert(audio_format != NULL); - assert(audio_format_valid(audio_format)); - - return chunk->length == 0 || - audio_format_equals(&chunk->audio_format, audio_format); -} -#endif - -void * -music_chunk_write(struct music_chunk *chunk, - const struct audio_format *audio_format, - float data_time, uint16_t bit_rate, - size_t *max_length_r) -{ - const size_t frame_size = audio_format_frame_size(audio_format); - size_t num_frames; - - assert(music_chunk_check_format(chunk, audio_format)); - assert(chunk->length == 0 || audio_format_valid(&chunk->audio_format)); - - if (chunk->length == 0) { - /* if the chunk is empty, nobody has set bitRate and - times yet */ - - chunk->bit_rate = bit_rate; - chunk->times = data_time; - } - - num_frames = (sizeof(chunk->data) - chunk->length) / frame_size; - if (num_frames == 0) - return NULL; - -#ifndef NDEBUG - chunk->audio_format = *audio_format; -#endif - - *max_length_r = num_frames * frame_size; - return chunk->data + chunk->length; -} - -bool -music_chunk_expand(struct music_chunk *chunk, - const struct audio_format *audio_format, size_t length) -{ - const size_t frame_size = audio_format_frame_size(audio_format); - - assert(chunk != NULL); - assert(chunk->length + length <= sizeof(chunk->data)); - assert(audio_format_equals(&chunk->audio_format, audio_format)); - - chunk->length += length; - - return chunk->length + frame_size > sizeof(chunk->data); -} diff --git a/src/client_event.c b/src/client_event.c deleted file mode 100644 index 4f54ae0a..00000000 --- a/src/client_event.c +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" -#include "main.h" - -#include <assert.h> - -static gboolean -client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct client *client = data; - - assert(!client_is_expired(client)); - - if (condition != G_IO_OUT) { - client_set_expired(client); - return false; - } - - client_write_deferred(client); - - if (client_is_expired(client)) { - client_close(client); - return false; - } - - g_timer_start(client->last_activity); - - if (g_queue_is_empty(client->deferred_send)) { - /* done sending deferred buffers exist: schedule - read */ - client->source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - client_in_event, client); - return false; - } - - /* write more */ - return true; -} - -gboolean -client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct client *client = data; - enum command_return ret; - - assert(!client_is_expired(client)); - - if (condition != G_IO_IN) { - client_set_expired(client); - return false; - } - - g_timer_start(client->last_activity); - - ret = client_read(client); - switch (ret) { - case COMMAND_RETURN_OK: - case COMMAND_RETURN_ERROR: - break; - - case COMMAND_RETURN_KILL: - client_close(client); - g_main_loop_quit(main_loop); - return false; - - case COMMAND_RETURN_CLOSE: - client_close(client); - return false; - } - - if (client_is_expired(client)) { - client_close(client); - return false; - } - - if (!g_queue_is_empty(client->deferred_send)) { - /* deferred buffers exist: schedule write */ - client->source_id = g_io_add_watch(client->channel, - G_IO_OUT|G_IO_ERR|G_IO_HUP, - client_out_event, client); - return false; - } - - /* read more */ - return true; -} diff --git a/src/client_expire.c b/src/client_expire.c deleted file mode 100644 index 1ca32ebc..00000000 --- a/src/client_expire.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" - -static guint expire_source_id; - -void -client_set_expired(struct client *client) -{ - if (!client_is_expired(client)) - client_schedule_expire(); - - if (client->source_id != 0) { - g_source_remove(client->source_id); - client->source_id = 0; - } - - if (client->channel != NULL) { - g_io_channel_unref(client->channel); - client->channel = NULL; - } -} - -static void -client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct client *client = data; - - if (client_is_expired(client)) { - g_debug("[%u] expired", client->num); - client_close(client); - } else if (!client->idle_waiting && /* idle clients - never expire */ - (int)g_timer_elapsed(client->last_activity, NULL) > - client_timeout) { - g_debug("[%u] timeout", client->num); - client_close(client); - } -} - -static void -client_manager_expire(void) -{ - client_list_foreach(client_check_expired_callback, NULL); -} - -/** - * An idle event which calls client_manager_expire(). - */ -static gboolean -client_manager_expire_event(G_GNUC_UNUSED gpointer data) -{ - expire_source_id = 0; - client_manager_expire(); - return false; -} - -void -client_schedule_expire(void) -{ - if (expire_source_id == 0) - /* delayed deletion */ - expire_source_id = g_idle_add(client_manager_expire_event, - NULL); -} - -void -client_deinit_expire(void) -{ - if (expire_source_id != 0) - g_source_remove(expire_source_id); -} diff --git a/src/client_idle.c b/src/client_idle.c deleted file mode 100644 index 930911d6..00000000 --- a/src/client_idle.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_idle.h" -#include "client_internal.h" -#include "idle.h" - -#include <assert.h> - -/** - * Send "idle" response to this client. - */ -static void -client_idle_notify(struct client *client) -{ - unsigned flags, i; - const char *const* idle_names; - - assert(client->idle_waiting); - assert(client->idle_flags != 0); - - flags = client->idle_flags; - client->idle_flags = 0; - client->idle_waiting = false; - - idle_names = idle_get_names(); - for (i = 0; idle_names[i]; ++i) { - if (flags & (1 << i) & client->idle_subscriptions) - client_printf(client, "changed: %s\n", - idle_names[i]); - } - - client_puts(client, "OK\n"); - g_timer_start(client->last_activity); -} - -void -client_idle_add(struct client *client, unsigned flags) -{ - if (client_is_expired(client)) - return; - - client->idle_flags |= flags; - if (client->idle_waiting - && (client->idle_flags & client->idle_subscriptions)) { - client_idle_notify(client); - client_write_output(client); - } -} - -static void -client_idle_callback(gpointer data, gpointer user_data) -{ - struct client *client = data; - unsigned flags = GPOINTER_TO_UINT(user_data); - - client_idle_add(client, flags); -} - -void client_manager_idle_add(unsigned flags) -{ - assert(flags != 0); - - client_list_foreach(client_idle_callback, GUINT_TO_POINTER(flags)); -} - -bool client_idle_wait(struct client *client, unsigned flags) -{ - assert(!client->idle_waiting); - - client->idle_waiting = true; - client->idle_subscriptions = flags; - - if (client->idle_flags & client->idle_subscriptions) { - client_idle_notify(client); - return true; - } else - return false; -} diff --git a/src/client_idle.h b/src/client_idle.h deleted file mode 100644 index c56fd014..00000000 --- a/src/client_idle.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_IDLE_H -#define MPD_CLIENT_IDLE_H - -#include <stdbool.h> - -struct client; - -void -client_idle_add(struct client *client, unsigned flags); - -/** - * Adds the specified idle flags to all clients and immediately sends - * notifications to all waiting clients. - */ -void -client_manager_idle_add(unsigned flags); - -/** - * Checks whether the client has pending idle flags. If yes, they are - * sent immediately and "true" is returned". If no, it puts the - * client into waiting mode and returns false. - */ -bool -client_idle_wait(struct client *client, unsigned flags); - -#endif diff --git a/src/client_internal.h b/src/client_internal.h deleted file mode 100644 index ba97e4b8..00000000 --- a/src/client_internal.h +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_INTERNAL_H -#define MPD_CLIENT_INTERNAL_H - -#include "client.h" -#include "client_message.h" -#include "command.h" - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "client" - -enum { - CLIENT_MAX_SUBSCRIPTIONS = 16, - CLIENT_MAX_MESSAGES = 64, -}; - -struct deferred_buffer { - size_t size; - char data[sizeof(long)]; -}; - -struct client { - struct player_control *player_control; - - GIOChannel *channel; - guint source_id; - - /** the buffer for reading lines from the #channel */ - struct fifo_buffer *input; - - unsigned permission; - - /** the uid of the client process, or -1 if unknown */ - int uid; - - /** - * How long since the last activity from this client? - */ - GTimer *last_activity; - - GSList *cmd_list; /* for when in list mode */ - int cmd_list_OK; /* print OK after each command execution */ - size_t cmd_list_size; /* mem cmd_list consumes */ - GQueue *deferred_send; /* for output if client is slow */ - size_t deferred_bytes; /* mem deferred_send consumes */ - unsigned int num; /* client number */ - - char send_buf[16384]; - size_t send_buf_used; /* bytes used this instance */ - - /** is this client waiting for an "idle" response? */ - bool idle_waiting; - - /** idle flags pending on this client, to be sent as soon as - the client enters "idle" */ - unsigned idle_flags; - - /** idle flags that the client wants to receive */ - unsigned idle_subscriptions; - - /** - * A list of channel names this client is subscribed to. - */ - GSList *subscriptions; - - /** - * The number of subscriptions in #subscriptions. Used to - * limit the number of subscriptions. - */ - unsigned num_subscriptions; - - /** - * A list of messages this client has received in reverse - * order (latest first). - */ - GSList *messages; - - /** - * The number of messages in #messages. - */ - unsigned num_messages; -}; - -extern unsigned int client_max_connections; -extern int client_timeout; -extern size_t client_max_command_list_size; -extern size_t client_max_output_buffer_size; - -bool -client_list_is_empty(void); - -bool -client_list_is_full(void); - -struct client * -client_list_get_first(void); - -void -client_list_add(struct client *client); - -void -client_list_foreach(GFunc func, gpointer user_data); - -void -client_list_remove(struct client *client); - -void -client_close(struct client *client); - -static inline void -new_cmd_list_ptr(struct client *client, const char *s) -{ - client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s)); -} - -static inline void -free_cmd_list(GSList *list) -{ - for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp)) - g_free(tmp->data); - - g_slist_free(list); -} - -void -client_set_expired(struct client *client); - -/** - * Schedule an "expired" check for all clients: permanently delete - * clients which have been set "expired" with client_set_expired(). - */ -void -client_schedule_expire(void); - -/** - * Removes a scheduled "expired" check. - */ -void -client_deinit_expire(void); - -enum command_return -client_read(struct client *client); - -enum command_return -client_process_line(struct client *client, char *line); - -void -client_write_deferred(struct client *client); - -void -client_write_output(struct client *client); - -gboolean -client_in_event(GIOChannel *source, GIOCondition condition, - gpointer data); - -#endif diff --git a/src/client_message.c b/src/client_message.c deleted file mode 100644 index b681b4e7..00000000 --- a/src/client_message.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "client_message.h" - -#include <assert.h> -#include <glib.h> - -G_GNUC_PURE -static bool -valid_channel_char(const char ch) -{ - return g_ascii_isalnum(ch) || - ch == '_' || ch == '-' || ch == '.' || ch == ':'; -} - -bool -client_message_valid_channel_name(const char *name) -{ - do { - if (!valid_channel_char(*name)) - return false; - } while (*++name != 0); - - return true; -} - -void -client_message_init_null(struct client_message *msg) -{ - assert(msg != NULL); - - msg->channel = NULL; - msg->message = NULL; -} - -void -client_message_init(struct client_message *msg, - const char *channel, const char *message) -{ - assert(msg != NULL); - - msg->channel = g_strdup(channel); - msg->message = g_strdup(message); -} - -void -client_message_copy(struct client_message *dest, - const struct client_message *src) -{ - assert(dest != NULL); - assert(src != NULL); - assert(client_message_defined(src)); - - client_message_init(dest, src->channel, src->message); -} - -struct client_message * -client_message_dup(const struct client_message *src) -{ - struct client_message *dest = g_slice_new(struct client_message); - client_message_copy(dest, src); - return dest; -} - -void -client_message_deinit(struct client_message *msg) -{ - assert(msg != NULL); - - g_free(msg->channel); - g_free(msg->message); -} - -void -client_message_free(struct client_message *msg) -{ - client_message_deinit(msg); - g_slice_free(struct client_message, msg); -} diff --git a/src/client_message.h b/src/client_message.h deleted file mode 100644 index 38c2e761..00000000 --- a/src/client_message.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_MESSAGE_H -#define MPD_CLIENT_MESSAGE_H - -#include <assert.h> -#include <stdbool.h> -#include <stddef.h> -#include <glib.h> - -/** - * A client-to-client message. - */ -struct client_message { - char *channel; - - char *message; -}; - -G_GNUC_PURE -bool -client_message_valid_channel_name(const char *name); - -G_GNUC_PURE -static inline bool -client_message_defined(const struct client_message *msg) -{ - assert(msg != NULL); - assert((msg->channel == NULL) == (msg->message == NULL)); - - return msg->channel != NULL; -} - -void -client_message_init_null(struct client_message *msg); - -void -client_message_init(struct client_message *msg, - const char *channel, const char *message); - -void -client_message_copy(struct client_message *dest, - const struct client_message *src); - -G_GNUC_MALLOC -struct client_message * -client_message_dup(const struct client_message *src); - -void -client_message_deinit(struct client_message *msg); - -void -client_message_free(struct client_message *msg); - -#endif diff --git a/src/client_read.c b/src/client_read.c deleted file mode 100644 index 26ade264..00000000 --- a/src/client_read.c +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" -#include "fifo_buffer.h" - -#include <assert.h> -#include <string.h> - -static char * -client_read_line(struct client *client) -{ - const char *p, *newline; - size_t length; - char *line; - - p = fifo_buffer_read(client->input, &length); - if (p == NULL) - return NULL; - - newline = memchr(p, '\n', length); - if (newline == NULL) - return NULL; - - line = g_strndup(p, newline - p); - fifo_buffer_consume(client->input, newline - p + 1); - - return g_strchomp(line); -} - -static enum command_return -client_input_received(struct client *client, size_t bytesRead) -{ - char *line; - - fifo_buffer_append(client->input, bytesRead); - - /* process all lines */ - - while ((line = client_read_line(client)) != NULL) { - enum command_return ret = client_process_line(client, line); - g_free(line); - - if (ret == COMMAND_RETURN_KILL || - ret == COMMAND_RETURN_CLOSE) - return ret; - if (client_is_expired(client)) - return COMMAND_RETURN_CLOSE; - } - - return COMMAND_RETURN_OK; -} - -enum command_return -client_read(struct client *client) -{ - char *p; - size_t max_length; - GError *error = NULL; - GIOStatus status; - gsize bytes_read; - - assert(client != NULL); - assert(client->channel != NULL); - - p = fifo_buffer_write(client->input, &max_length); - if (p == NULL) { - g_warning("[%u] buffer overflow", client->num); - return COMMAND_RETURN_CLOSE; - } - - status = g_io_channel_read_chars(client->channel, p, max_length, - &bytes_read, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - return client_input_received(client, bytes_read); - - case G_IO_STATUS_AGAIN: - /* try again later, after select() */ - return COMMAND_RETURN_OK; - - case G_IO_STATUS_EOF: - /* peer disconnected */ - return COMMAND_RETURN_CLOSE; - - case G_IO_STATUS_ERROR: - /* I/O error */ - g_warning("failed to read from client %d: %s", - client->num, error->message); - g_error_free(error); - return COMMAND_RETURN_CLOSE; - } - - /* unreachable */ - return COMMAND_RETURN_CLOSE; -} diff --git a/src/client_subscribe.c b/src/client_subscribe.c deleted file mode 100644 index c65a7ed3..00000000 --- a/src/client_subscribe.c +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "client_subscribe.h" -#include "client_internal.h" -#include "client_idle.h" -#include "idle.h" - -#include <string.h> - -G_GNUC_PURE -static GSList * -client_find_subscription(const struct client *client, const char *channel) -{ - for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i)) - if (strcmp((const char *)i->data, channel) == 0) - return i; - - return NULL; -} - -enum client_subscribe_result -client_subscribe(struct client *client, const char *channel) -{ - assert(client != NULL); - assert(channel != NULL); - - if (!client_message_valid_channel_name(channel)) - return CLIENT_SUBSCRIBE_INVALID; - - if (client_find_subscription(client, channel) != NULL) - return CLIENT_SUBSCRIBE_ALREADY; - - if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) - return CLIENT_SUBSCRIBE_FULL; - - client->subscriptions = g_slist_prepend(client->subscriptions, - g_strdup(channel)); - ++client->num_subscriptions; - - idle_add(IDLE_SUBSCRIPTION); - - return CLIENT_SUBSCRIBE_OK; -} - -bool -client_unsubscribe(struct client *client, const char *channel) -{ - GSList *i = client_find_subscription(client, channel); - if (i == NULL) - return false; - - assert(client->num_subscriptions > 0); - - client->subscriptions = g_slist_remove(client->subscriptions, i->data); - --client->num_subscriptions; - - idle_add(IDLE_SUBSCRIPTION); - - assert((client->num_subscriptions == 0) == - (client->subscriptions == NULL)); - - return true; -} - -void -client_unsubscribe_all(struct client *client) -{ - for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i)) - g_free(i->data); - - g_slist_free(client->subscriptions); - client->subscriptions = NULL; - client->num_subscriptions = 0; -} - -bool -client_push_message(struct client *client, const struct client_message *msg) -{ - assert(client != NULL); - assert(msg != NULL); - assert(client_message_defined(msg)); - - if (client->num_messages >= CLIENT_MAX_MESSAGES || - client_find_subscription(client, msg->channel) == NULL) - return false; - - if (client->messages == NULL) - client_idle_add(client, IDLE_MESSAGE); - - client->messages = g_slist_prepend(client->messages, - client_message_dup(msg)); - ++client->num_messages; - - return true; -} - -GSList * -client_read_messages(struct client *client) -{ - GSList *messages = g_slist_reverse(client->messages); - - client->messages = NULL; - client->num_messages = 0; - - return messages; -} diff --git a/src/client_write.c b/src/client_write.c deleted file mode 100644 index 78cfca8a..00000000 --- a/src/client_write.c +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" - -#include <assert.h> -#include <string.h> -#include <stdio.h> - -static size_t -client_write_deferred_buffer(struct client *client, - const struct deferred_buffer *buffer) -{ - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->channel != NULL); - assert(buffer != NULL); - - status = g_io_channel_write_chars - (client->channel, buffer->data, buffer->size, - &bytes_written, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - return bytes_written; - - case G_IO_STATUS_AGAIN: - return 0; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - client_set_expired(client); - return 0; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - client_set_expired(client); - g_warning("failed to flush buffer for %i: %s", - client->num, error->message); - g_error_free(error); - return 0; - } - - /* unreachable */ - return 0; -} - -void -client_write_deferred(struct client *client) -{ - size_t ret; - - while (!g_queue_is_empty(client->deferred_send)) { - struct deferred_buffer *buf = - g_queue_peek_head(client->deferred_send); - - assert(buf->size > 0); - assert(buf->size <= client->deferred_bytes); - - ret = client_write_deferred_buffer(client, buf); - if (ret == 0) - break; - - if (ret < buf->size) { - assert(client->deferred_bytes >= (size_t)ret); - client->deferred_bytes -= ret; - buf->size -= ret; - memmove(buf->data, buf->data + ret, buf->size); - break; - } else { - size_t decr = sizeof(*buf) - - sizeof(buf->data) + buf->size; - - assert(client->deferred_bytes >= decr); - client->deferred_bytes -= decr; - g_free(buf); - g_queue_pop_head(client->deferred_send); - } - - g_timer_start(client->last_activity); - } - - if (g_queue_is_empty(client->deferred_send)) { - g_debug("[%u] buffer empty %lu", client->num, - (unsigned long)client->deferred_bytes); - assert(client->deferred_bytes == 0); - } -} - -static void client_defer_output(struct client *client, - const void *data, size_t length) -{ - size_t alloc; - struct deferred_buffer *buf; - - assert(length > 0); - - alloc = sizeof(*buf) - sizeof(buf->data) + length; - client->deferred_bytes += alloc; - if (client->deferred_bytes > client_max_output_buffer_size) { - g_warning("[%u] output buffer size (%lu) is " - "larger than the max (%lu)", - client->num, - (unsigned long)client->deferred_bytes, - (unsigned long)client_max_output_buffer_size); - /* cause client to close */ - client_set_expired(client); - return; - } - - buf = g_malloc(alloc); - buf->size = length; - memcpy(buf->data, data, length); - - g_queue_push_tail(client->deferred_send, buf); -} - -static void client_write_direct(struct client *client, - const char *data, size_t length) -{ - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->channel != NULL); - assert(data != NULL); - assert(length > 0); - assert(g_queue_is_empty(client->deferred_send)); - - status = g_io_channel_write_chars(client->channel, data, length, - &bytes_written, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - case G_IO_STATUS_AGAIN: - break; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - client_set_expired(client); - return; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - client_set_expired(client); - g_warning("failed to write to %i: %s", - client->num, error->message); - g_error_free(error); - return; - } - - if (bytes_written < length) - client_defer_output(client, data + bytes_written, - length - bytes_written); - - if (!g_queue_is_empty(client->deferred_send)) - g_debug("[%u] buffer created", client->num); -} - -void -client_write_output(struct client *client) -{ - if (client_is_expired(client) || !client->send_buf_used) - return; - - if (!g_queue_is_empty(client->deferred_send)) { - client_defer_output(client, client->send_buf, - client->send_buf_used); - - if (client_is_expired(client)) - return; - - /* try to flush the deferred buffers now; the current - server command may take too long to finish, and - meanwhile try to feed output to the client, - otherwise it will time out. One reason why - deferring is slow might be that currently each - client_write() allocates a new deferred buffer. - This should be optimized after MPD 0.14. */ - client_write_deferred(client); - } else - client_write_direct(client, client->send_buf, - client->send_buf_used); - - client->send_buf_used = 0; -} - -/** - * Write a block of data to the client. - */ -static void client_write(struct client *client, const char *buffer, size_t buflen) -{ - /* if the client is going to be closed, do nothing */ - if (client_is_expired(client)) - return; - - while (buflen > 0 && !client_is_expired(client)) { - size_t copylen; - - assert(client->send_buf_used < sizeof(client->send_buf)); - - copylen = sizeof(client->send_buf) - client->send_buf_used; - if (copylen > buflen) - copylen = buflen; - - memcpy(client->send_buf + client->send_buf_used, buffer, - copylen); - buflen -= copylen; - client->send_buf_used += copylen; - buffer += copylen; - if (client->send_buf_used >= sizeof(client->send_buf)) - client_write_output(client); - } -} - -void client_puts(struct client *client, const char *s) -{ - client_write(client, s, strlen(s)); -} - -void client_vprintf(struct client *client, const char *fmt, va_list args) -{ -#ifndef G_OS_WIN32 - va_list tmp; - int length; - char *buffer; - - va_copy(tmp, args); - length = vsnprintf(NULL, 0, fmt, tmp); - va_end(tmp); - - if (length <= 0) - /* wtf.. */ - return; - - buffer = g_malloc(length + 1); - vsnprintf(buffer, length + 1, fmt, args); - client_write(client, buffer, length); - g_free(buffer); -#else - /* On mingw32, snprintf() expects a 64 bit integer instead of - a "long int" for "%li". This is not consistent with our - expectation, so we're using plain sprintf() here, hoping - the static buffer is large enough. Sorry for this hack, - but WIN32 development is so painful, I'm not in the mood to - do it properly now. */ - - static char buffer[4096]; - vsprintf(buffer, fmt, args); - client_write(client, buffer, strlen(buffer)); -#endif -} - -G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...) -{ - va_list args; - - va_start(args, fmt); - client_vprintf(client, fmt, args); - va_end(args); -} diff --git a/src/clock.h b/src/clock.h index 4ece35ab..f1338938 100644 --- a/src/clock.h +++ b/src/clock.h @@ -20,21 +20,21 @@ #ifndef MPD_CLOCK_H #define MPD_CLOCK_H -#include <glib.h> +#include "gcc.h" #include <stdint.h> /** * Returns the value of a monotonic clock in milliseconds. */ -G_GNUC_PURE +gcc_pure unsigned monotonic_clock_ms(void); /** * Returns the value of a monotonic clock in microseconds. */ -G_GNUC_PURE +gcc_pure uint64_t monotonic_clock_us(void); diff --git a/src/command.c b/src/command.c deleted file mode 100644 index c405925f..00000000 --- a/src/command.c +++ /dev/null @@ -1,2298 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "command.h" -#include "protocol/argparser.h" -#include "protocol/result.h" -#include "player_control.h" -#include "playlist.h" -#include "playlist_print.h" -#include "playlist_save.h" -#include "playlist_queue.h" -#include "playlist_error.h" -#include "queue_print.h" -#include "ls.h" -#include "uri.h" -#include "decoder_print.h" -#include "directory.h" -#include "database.h" -#include "update.h" -#include "volume.h" -#include "stats.h" -#include "permission.h" -#include "tokenizer.h" -#include "stored_playlist.h" -#include "ack.h" -#include "output_command.h" -#include "output_print.h" -#include "locate.h" -#include "dbUtils.h" -#include "db_error.h" -#include "db_print.h" -#include "db_selection.h" -#include "db_lock.h" -#include "tag.h" -#include "client.h" -#include "client_idle.h" -#include "client_internal.h" -#include "client_subscribe.h" -#include "client_file.h" -#include "tag_print.h" -#include "path.h" -#include "replay_gain_config.h" -#include "idle.h" -#include "mapper.h" -#include "song.h" -#include "song_print.h" - -#ifdef ENABLE_SQLITE -#include "sticker.h" -#include "sticker_print.h" -#include "song_sticker.h" -#endif - -#include <assert.h> -#include <time.h> -#include <stdlib.h> -#include <errno.h> - -#define COMMAND_STATUS_STATE "state" -#define COMMAND_STATUS_REPEAT "repeat" -#define COMMAND_STATUS_SINGLE "single" -#define COMMAND_STATUS_CONSUME "consume" -#define COMMAND_STATUS_RANDOM "random" -#define COMMAND_STATUS_PLAYLIST "playlist" -#define COMMAND_STATUS_PLAYLIST_LENGTH "playlistlength" -#define COMMAND_STATUS_SONG "song" -#define COMMAND_STATUS_SONGID "songid" -#define COMMAND_STATUS_NEXTSONG "nextsong" -#define COMMAND_STATUS_NEXTSONGID "nextsongid" -#define COMMAND_STATUS_TIME "time" -#define COMMAND_STATUS_BITRATE "bitrate" -#define COMMAND_STATUS_ERROR "error" -#define COMMAND_STATUS_CROSSFADE "xfade" -#define COMMAND_STATUS_MIXRAMPDB "mixrampdb" -#define COMMAND_STATUS_MIXRAMPDELAY "mixrampdelay" -#define COMMAND_STATUS_AUDIO "audio" -#define COMMAND_STATUS_UPDATING_DB "updating_db" - -/* - * The most we ever use is for search/find, and that limits it to the - * number of tags we can have. Add one for the command, and one extra - * to catch errors clients may send us - */ -#define COMMAND_ARGV_MAX (2+(TAG_NUM_OF_ITEM_TYPES*2)) - -/* if min: -1 don't check args * - * if max: -1 no max args */ -struct command { - const char *cmd; - unsigned permission; - int min; - int max; - enum command_return (*handler)(struct client *client, int argc, char **argv); -}; - -static enum command_return -print_playlist_result(struct client *client, - enum playlist_result result) -{ - switch (result) { - case PLAYLIST_RESULT_SUCCESS: - return COMMAND_RETURN_OK; - - case PLAYLIST_RESULT_ERRNO: - command_error(client, ACK_ERROR_SYSTEM, "%s", - g_strerror(errno)); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_DENIED: - command_error(client, ACK_ERROR_PERMISSION, "Access denied"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_NO_SUCH_SONG: - command_error(client, ACK_ERROR_NO_EXIST, "No such song"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_NO_SUCH_LIST: - command_error(client, ACK_ERROR_NO_EXIST, "No such playlist"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_LIST_EXISTS: - command_error(client, ACK_ERROR_EXIST, - "Playlist already exists"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_BAD_NAME: - command_error(client, ACK_ERROR_ARG, - "playlist name is invalid: " - "playlist names may not contain slashes," - " newlines or carriage returns"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_BAD_RANGE: - command_error(client, ACK_ERROR_ARG, "Bad song index"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_NOT_PLAYING: - command_error(client, ACK_ERROR_PLAYER_SYNC, "Not playing"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_TOO_LARGE: - command_error(client, ACK_ERROR_PLAYLIST_MAX, - "playlist is at the max size"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_DISABLED: - command_error(client, ACK_ERROR_UNKNOWN, - "stored playlist support is disabled"); - return COMMAND_RETURN_ERROR; - } - - assert(0); - return COMMAND_RETURN_ERROR; -} - -/** - * Send the GError to the client and free the GError. - */ -static enum command_return -print_error(struct client *client, GError *error) -{ - assert(client != NULL); - assert(error != NULL); - - g_warning("%s", error->message); - - if (error->domain == playlist_quark()) { - enum playlist_result result = error->code; - g_error_free(error); - return print_playlist_result(client, result); - } else if (error->domain == ack_quark()) { - command_error(client, error->code, "%s", error->message); - g_error_free(error); - return COMMAND_RETURN_ERROR; - } else if (error->domain == db_quark()) { - switch ((enum db_error)error->code) { - case DB_DISABLED: - command_error(client, ACK_ERROR_NO_EXIST, "%s", - error->message); - g_error_free(error); - return COMMAND_RETURN_ERROR; - - case DB_NOT_FOUND: - g_error_free(error); - command_error(client, ACK_ERROR_NO_EXIST, "Not found"); - return COMMAND_RETURN_ERROR; - } - } else if (error->domain == g_file_error_quark()) { - command_error(client, ACK_ERROR_SYSTEM, "%s", - g_strerror(error->code)); - g_error_free(error); - return COMMAND_RETURN_ERROR; - } - - g_error_free(error); - command_error(client, ACK_ERROR_UNKNOWN, "error"); - return COMMAND_RETURN_ERROR; -} - -static void -print_spl_list(struct client *client, GPtrArray *list) -{ - for (unsigned i = 0; i < list->len; ++i) { - struct stored_playlist_info *playlist = - g_ptr_array_index(list, i); - time_t t; -#ifndef WIN32 - struct tm tm; -#endif - char timestamp[32]; - - client_printf(client, "playlist: %s\n", playlist->name); - - t = playlist->mtime; - strftime(timestamp, sizeof(timestamp), -#ifdef G_OS_WIN32 - "%Y-%m-%dT%H:%M:%SZ", - gmtime(&t) -#else - "%FT%TZ", - gmtime_r(&t, &tm) -#endif - ); - client_printf(client, "Last-Modified: %s\n", timestamp); - } -} - -static enum command_return -handle_urlhandlers(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - if (client_is_local(client)) - client_puts(client, "handler: file://\n"); - print_supported_uri_schemes(client); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_decoders(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - decoder_list_print(client); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_tagtypes(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - tag_print_types(client); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_play(struct client *client, int argc, char *argv[]) -{ - int song = -1; - enum playlist_result result; - - if (argc == 2 && !check_int(client, &song, argv[1])) - return COMMAND_RETURN_ERROR; - result = playlist_play(&g_playlist, client->player_control, song); - return print_playlist_result(client, result); -} - -static enum command_return -handle_playid(struct client *client, int argc, char *argv[]) -{ - int id = -1; - enum playlist_result result; - - if (argc == 2 && !check_int(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - - result = playlist_play_id(&g_playlist, client->player_control, id); - return print_playlist_result(client, result); -} - -static enum command_return -handle_stop(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_stop(&g_playlist, client->player_control); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_currentsong(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_print_current(client, &g_playlist); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_pause(struct client *client, - int argc, char *argv[]) -{ - if (argc == 2) { - bool pause_flag; - if (!check_bool(client, &pause_flag, argv[1])) - return COMMAND_RETURN_ERROR; - - pc_set_pause(client->player_control, pause_flag); - } else - pc_pause(client->player_control); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_status(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - const char *state = NULL; - struct player_status player_status; - int updateJobId; - char *error; - int song; - - pc_get_status(client->player_control, &player_status); - - switch (player_status.state) { - case PLAYER_STATE_STOP: - state = "stop"; - break; - case PLAYER_STATE_PAUSE: - state = "pause"; - break; - case PLAYER_STATE_PLAY: - state = "play"; - break; - } - - client_printf(client, - "volume: %i\n" - COMMAND_STATUS_REPEAT ": %i\n" - COMMAND_STATUS_RANDOM ": %i\n" - COMMAND_STATUS_SINGLE ": %i\n" - COMMAND_STATUS_CONSUME ": %i\n" - COMMAND_STATUS_PLAYLIST ": %li\n" - COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" - COMMAND_STATUS_CROSSFADE ": %i\n" - COMMAND_STATUS_MIXRAMPDB ": %f\n" - COMMAND_STATUS_MIXRAMPDELAY ": %f\n" - COMMAND_STATUS_STATE ": %s\n", - volume_level_get(), - playlist_get_repeat(&g_playlist), - playlist_get_random(&g_playlist), - playlist_get_single(&g_playlist), - playlist_get_consume(&g_playlist), - playlist_get_version(&g_playlist), - playlist_get_length(&g_playlist), - (int)(pc_get_cross_fade(client->player_control) + 0.5), - pc_get_mixramp_db(client->player_control), - pc_get_mixramp_delay(client->player_control), - state); - - song = playlist_get_current_song(&g_playlist); - if (song >= 0) { - client_printf(client, - COMMAND_STATUS_SONG ": %i\n" - COMMAND_STATUS_SONGID ": %u\n", - song, playlist_get_song_id(&g_playlist, song)); - } - - if (player_status.state != PLAYER_STATE_STOP) { - struct audio_format_string af_string; - - client_printf(client, - COMMAND_STATUS_TIME ": %i:%i\n" - "elapsed: %1.3f\n" - COMMAND_STATUS_BITRATE ": %u\n" - COMMAND_STATUS_AUDIO ": %s\n", - (int)(player_status.elapsed_time + 0.5), - (int)(player_status.total_time + 0.5), - player_status.elapsed_time, - player_status.bit_rate, - audio_format_to_string(&player_status.audio_format, - &af_string)); - } - - if ((updateJobId = isUpdatingDB())) { - client_printf(client, - COMMAND_STATUS_UPDATING_DB ": %i\n", - updateJobId); - } - - error = pc_get_error_message(client->player_control); - if (error != NULL) { - client_printf(client, - COMMAND_STATUS_ERROR ": %s\n", - error); - g_free(error); - } - - song = playlist_get_next_song(&g_playlist); - if (song >= 0) { - client_printf(client, - COMMAND_STATUS_NEXTSONG ": %i\n" - COMMAND_STATUS_NEXTSONGID ": %u\n", - song, playlist_get_song_id(&g_playlist, song)); - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_kill(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - return COMMAND_RETURN_KILL; -} - -static enum command_return -handle_close(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - return COMMAND_RETURN_CLOSE; -} - -static enum command_return -handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - char *uri = argv[1]; - enum playlist_result result; - - if (strncmp(uri, "file:///", 8) == 0) { - const char *path = uri + 7; - - GError *error = NULL; - if (!client_allow_file(client, path, &error)) - return print_error(client, error); - - result = playlist_append_file(&g_playlist, - client->player_control, - path, - NULL); - return print_playlist_result(client, result); - } - - if (uri_has_scheme(uri)) { - if (!uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return COMMAND_RETURN_ERROR; - } - - result = playlist_append_uri(&g_playlist, - client->player_control, - uri, NULL); - return print_playlist_result(client, result); - } - - GError *error = NULL; - return addAllIn(client->player_control, uri, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_addid(struct client *client, int argc, char *argv[]) -{ - char *uri = argv[1]; - unsigned added_id; - enum playlist_result result; - - if (strncmp(uri, "file:///", 8) == 0) { - const char *path = uri + 7; - - GError *error = NULL; - if (!client_allow_file(client, path, &error)) - return print_error(client, error); - - result = playlist_append_file(&g_playlist, - client->player_control, - path, - &added_id); - } else { - if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return COMMAND_RETURN_ERROR; - } - - result = playlist_append_uri(&g_playlist, - client->player_control, - uri, &added_id); - } - - if (result != PLAYLIST_RESULT_SUCCESS) - return print_playlist_result(client, result); - - if (argc == 3) { - unsigned to; - if (!check_unsigned(client, &to, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_move_id(&g_playlist, client->player_control, - added_id, to); - if (result != PLAYLIST_RESULT_SUCCESS) { - enum command_return ret = - print_playlist_result(client, result); - playlist_delete_id(&g_playlist, client->player_control, - added_id); - return ret; - } - } - - client_printf(client, "Id: %u\n", added_id); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned start, end; - enum playlist_result result; - - if (!check_range(client, &start, &end, argv[1])) - return COMMAND_RETURN_ERROR; - - result = playlist_delete_range(&g_playlist, client->player_control, - start, end); - return print_playlist_result(client, result); -} - -static enum command_return -handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned id; - enum playlist_result result; - - if (!check_unsigned(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - - result = playlist_delete_id(&g_playlist, client->player_control, id); - return print_playlist_result(client, result); -} - -static enum command_return -handle_playlist(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_print_uris(client, &g_playlist); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_shuffle(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - unsigned start = 0, end = queue_length(&g_playlist.queue); - if (argc == 2 && !check_range(client, &start, &end, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_shuffle(&g_playlist, client->player_control, start, end); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_clear(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_clear(&g_playlist, client->player_control); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_save(struct client *client, - G_GNUC_UNUSED int argc, char *argv[]) -{ - enum playlist_result result; - - result = spl_save_playlist(argv[1], &g_playlist); - return print_playlist_result(client, result); -} - -static enum command_return -handle_load(struct client *client, int argc, char *argv[]) -{ - unsigned start_index, end_index; - - if (argc < 3) { - start_index = 0; - end_index = G_MAXUINT; - } else if (!check_range(client, &start_index, &end_index, argv[2])) - return COMMAND_RETURN_ERROR; - - enum playlist_result result; - - result = playlist_open_into_queue(argv[1], - start_index, end_index, - &g_playlist, - client->player_control, true); - if (result != PLAYLIST_RESULT_NO_SUCH_LIST) - return print_playlist_result(client, result); - - GError *error = NULL; - if (playlist_load_spl(&g_playlist, client->player_control, - argv[1], start_index, end_index, - &error)) - return COMMAND_RETURN_OK; - - if (error->domain == playlist_quark() && - error->code == PLAYLIST_RESULT_BAD_NAME) - /* the message for BAD_NAME is confusing when the - client wants to load a playlist file from the music - directory; patch the GError object to show "no such - playlist" instead */ - error->code = PLAYLIST_RESULT_NO_SUCH_LIST; - - return print_error(client, error); -} - -static enum command_return -handle_listplaylist(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - if (playlist_file_print(client, argv[1], false)) - return COMMAND_RETURN_OK; - - GError *error = NULL; - return spl_print(client, argv[1], false, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_listplaylistinfo(struct client *client, - G_GNUC_UNUSED int argc, char *argv[]) -{ - if (playlist_file_print(client, argv[1], true)) - return COMMAND_RETURN_OK; - - GError *error = NULL; - return spl_print(client, argv[1], true, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_lsinfo(struct client *client, int argc, char *argv[]) -{ - const char *uri; - - if (argc == 2) - uri = argv[1]; - else - /* default is root directory */ - uri = ""; - - if (strncmp(uri, "file:///", 8) == 0) { - /* print information about an arbitrary local file */ - const char *path = uri + 7; - - GError *error = NULL; - if (!client_allow_file(client, path, &error)) - return print_error(client, error); - - struct song *song = song_file_load(path, NULL); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "No such file"); - return COMMAND_RETURN_ERROR; - } - - song_print_info(client, song); - song_free(song); - return COMMAND_RETURN_OK; - } - - struct db_selection selection; - db_selection_init(&selection, uri, false); - - GError *error = NULL; - if (!db_selection_print(client, &selection, true, &error)) - return print_error(client, error); - - if (isRootDirectory(uri)) { - GPtrArray *list = spl_list(NULL); - if (list != NULL) { - print_spl_list(client, list); - spl_list_free(list); - } - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_rm(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - GError *error = NULL; - return spl_delete(argv[1], &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_rename(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - GError *error = NULL; - return spl_rename(argv[1], argv[2], &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_plchanges(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - uint32_t version; - - if (!check_uint32(client, &version, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_print_changes_info(client, &g_playlist, version); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - uint32_t version; - - if (!check_uint32(client, &version, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_print_changes_position(client, &g_playlist, version); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_playlistinfo(struct client *client, int argc, char *argv[]) -{ - unsigned start = 0, end = G_MAXUINT; - bool ret; - - if (argc == 2 && !check_range(client, &start, &end, argv[1])) - return COMMAND_RETURN_ERROR; - - ret = playlist_print_info(client, &g_playlist, start, end); - if (!ret) - return print_playlist_result(client, - PLAYLIST_RESULT_BAD_RANGE); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_playlistid(struct client *client, int argc, char *argv[]) -{ - if (argc >= 2) { - unsigned id; - if (!check_unsigned(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - - bool ret = playlist_print_id(client, &g_playlist, id); - if (!ret) - return print_playlist_result(client, - PLAYLIST_RESULT_NO_SUCH_SONG); - } else { - playlist_print_info(client, &g_playlist, 0, G_MAXUINT); - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_find(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *list = - locate_item_list_parse(argv + 1, argc - 1); - - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - GError *error = NULL; - enum command_return ret = findSongsIn(client, "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_findadd(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *list = - locate_item_list_parse(argv + 1, argc - 1); - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - GError *error = NULL; - enum command_return ret = - findAddIn(client->player_control, "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_search(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *list = - locate_item_list_parse(argv + 1, argc - 1); - - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - GError *error = NULL; - enum command_return ret = searchForSongsIn(client, "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_searchadd(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *list = - locate_item_list_parse(argv + 1, argc - 1); - - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - GError *error = NULL; - enum command_return ret = search_add_songs(client->player_control, - "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_searchaddpl(struct client *client, int argc, char *argv[]) -{ - const char *playlist = argv[1]; - - struct locate_item_list *list = - locate_item_list_parse(argv + 2, argc - 2); - - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - GError *error = NULL; - enum command_return ret = - search_add_to_playlist("", playlist, list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_count(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *list = - locate_item_list_parse(argv + 1, argc - 1); - - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - GError *error = NULL; - enum command_return ret = - searchStatsForSongsIn(client, "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_playlistfind(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *list = - locate_item_list_parse(argv + 1, argc - 1); - - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - playlist_print_find(client, &g_playlist, list); - - locate_item_list_free(list); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_playlistsearch(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *list = - locate_item_list_parse(argv + 1, argc - 1); - - if (list == NULL || list->length == 0) { - if (list != NULL) - locate_item_list_free(list); - - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); - return COMMAND_RETURN_ERROR; - } - - playlist_print_search(client, &g_playlist, list); - - locate_item_list_free(list); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_playlistdelete(struct client *client, - G_GNUC_UNUSED int argc, char *argv[]) { - char *playlist = argv[1]; - unsigned from; - - if (!check_unsigned(client, &from, argv[2])) - return COMMAND_RETURN_ERROR; - - GError *error = NULL; - return spl_remove_index(playlist, from, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - char *playlist = argv[1]; - unsigned from, to; - - if (!check_unsigned(client, &from, argv[2])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &to, argv[3])) - return COMMAND_RETURN_ERROR; - - GError *error = NULL; - return spl_move_index(playlist, from, to, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_update(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - const char *path = NULL; - unsigned ret; - - assert(argc <= 2); - if (argc == 2) { - path = argv[1]; - - if (*path == 0 || strcmp(path, "/") == 0) - /* backwards compatibility with MPD 0.15 */ - path = NULL; - else if (!uri_safe_local(path)) { - command_error(client, ACK_ERROR_ARG, - "Malformed path"); - return COMMAND_RETURN_ERROR; - } - } - - ret = update_enqueue(path, false); - if (ret > 0) { - client_printf(client, "updating_db: %i\n", ret); - return COMMAND_RETURN_OK; - } else { - command_error(client, ACK_ERROR_UPDATE_ALREADY, - "already updating"); - return COMMAND_RETURN_ERROR; - } -} - -static enum command_return -handle_rescan(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - const char *path = NULL; - unsigned ret; - - assert(argc <= 2); - if (argc == 2) { - path = argv[1]; - - if (!uri_safe_local(path)) { - command_error(client, ACK_ERROR_ARG, - "Malformed path"); - return COMMAND_RETURN_ERROR; - } - } - - ret = update_enqueue(path, true); - if (ret > 0) { - client_printf(client, "updating_db: %i\n", ret); - return COMMAND_RETURN_OK; - } else { - command_error(client, ACK_ERROR_UPDATE_ALREADY, - "already updating"); - return COMMAND_RETURN_ERROR; - } -} - -static enum command_return -handle_next(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - /* single mode is not considered when this is user who - * wants to change song. */ - const bool single = g_playlist.queue.single; - g_playlist.queue.single = false; - - playlist_next(&g_playlist, client->player_control); - - g_playlist.queue.single = single; - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_previous(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_previous(&g_playlist, client->player_control); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_prio(struct client *client, int argc, char *argv[]) -{ - unsigned priority; - - if (!check_unsigned(client, &priority, argv[1])) - return COMMAND_RETURN_ERROR; - - if (priority > 0xff) { - command_error(client, ACK_ERROR_ARG, - "Priority out of range: %s", argv[1]); - return COMMAND_RETURN_ERROR; - } - - for (int i = 2; i < argc; ++i) { - unsigned start_position, end_position; - if (!check_range(client, &start_position, &end_position, - argv[i])) - return COMMAND_RETURN_ERROR; - - enum playlist_result result = - playlist_set_priority(&g_playlist, - client->player_control, - start_position, end_position, - priority); - if (result != PLAYLIST_RESULT_SUCCESS) - return print_playlist_result(client, result); - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_prioid(struct client *client, int argc, char *argv[]) -{ - unsigned priority; - - if (!check_unsigned(client, &priority, argv[1])) - return COMMAND_RETURN_ERROR; - - if (priority > 0xff) { - command_error(client, ACK_ERROR_ARG, - "Priority out of range: %s", argv[1]); - return COMMAND_RETURN_ERROR; - } - - for (int i = 2; i < argc; ++i) { - unsigned song_id; - if (!check_unsigned(client, &song_id, argv[i])) - return COMMAND_RETURN_ERROR; - - enum playlist_result result = - playlist_set_priority_id(&g_playlist, - client->player_control, - song_id, priority); - if (result != PLAYLIST_RESULT_SUCCESS) - return print_playlist_result(client, result); - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - const char *directory = ""; - - if (argc == 2) - directory = argv[1]; - - GError *error = NULL; - return printAllIn(client, directory, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned level; - bool success; - - if (!check_unsigned(client, &level, argv[1])) - return COMMAND_RETURN_ERROR; - - if (level > 100) { - command_error(client, ACK_ERROR_ARG, "Invalid volume value"); - return COMMAND_RETURN_ERROR; - } - - success = volume_level_change(level); - if (!success) { - command_error(client, ACK_ERROR_SYSTEM, - "problems setting volume"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - bool status; - if (!check_bool(client, &status, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_set_repeat(&g_playlist, client->player_control, status); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - bool status; - if (!check_bool(client, &status, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_set_single(&g_playlist, client->player_control, status); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - bool status; - if (!check_bool(client, &status, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_set_consume(&g_playlist, status); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - bool status; - if (!check_bool(client, &status, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_set_random(&g_playlist, client->player_control, status); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_stats(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - return stats_print(client); -} - -static enum command_return -handle_clearerror(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - pc_clear_error(client->player_control); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_list(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *conditionals; - int tagType = locate_parse_type(argv[1]); - - if (tagType < 0) { - command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]); - return COMMAND_RETURN_ERROR; - } - - if (tagType == LOCATE_TAG_ANY_TYPE) { - command_error(client, ACK_ERROR_ARG, - "\"any\" is not a valid return tag type"); - return COMMAND_RETURN_ERROR; - } - - /* for compatibility with < 0.12.0 */ - if (argc == 3) { - if (tagType != TAG_ALBUM) { - command_error(client, ACK_ERROR_ARG, - "should be \"%s\" for 3 arguments", - tag_item_names[TAG_ALBUM]); - return COMMAND_RETURN_ERROR; - } - - locate_item_list_parse(argv + 1, argc - 1); - - conditionals = locate_item_list_new(1); - conditionals->items[0].tag = TAG_ARTIST; - conditionals->items[0].needle = g_strdup(argv[2]); - } else { - conditionals = - locate_item_list_parse(argv + 2, argc - 2); - if (conditionals == NULL) { - command_error(client, ACK_ERROR_ARG, - "not able to parse args"); - return COMMAND_RETURN_ERROR; - } - } - - GError *error = NULL; - enum command_return ret = - listAllUniqueTags(client, tagType, conditionals, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(conditionals); - - return ret; -} - -static enum command_return -handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned start, end; - int to; - enum playlist_result result; - - if (!check_range(client, &start, &end, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_int(client, &to, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_move_range(&g_playlist, client->player_control, - start, end, to); - return print_playlist_result(client, result); -} - -static enum command_return -handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned id; - int to; - enum playlist_result result; - - if (!check_unsigned(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_int(client, &to, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_move_id(&g_playlist, client->player_control, - id, to); - return print_playlist_result(client, result); -} - -static enum command_return -handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned song1, song2; - enum playlist_result result; - - if (!check_unsigned(client, &song1, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &song2, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_swap_songs(&g_playlist, client->player_control, - song1, song2); - return print_playlist_result(client, result); -} - -static enum command_return -handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned id1, id2; - enum playlist_result result; - - if (!check_unsigned(client, &id1, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &id2, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_swap_songs_id(&g_playlist, client->player_control, - id1, id2); - return print_playlist_result(client, result); -} - -static enum command_return -handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned song, seek_time; - enum playlist_result result; - - if (!check_unsigned(client, &song, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &seek_time, argv[2])) - return COMMAND_RETURN_ERROR; - - result = playlist_seek_song(&g_playlist, client->player_control, - song, seek_time); - return print_playlist_result(client, result); -} - -static enum command_return -handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned id, seek_time; - enum playlist_result result; - - if (!check_unsigned(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &seek_time, argv[2])) - return COMMAND_RETURN_ERROR; - - result = playlist_seek_song_id(&g_playlist, client->player_control, - id, seek_time); - return print_playlist_result(client, result); -} - -static enum command_return -handle_seekcur(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - const char *p = argv[1]; - bool relative = *p == '+' || *p == '-'; - int seek_time; - if (!check_int(client, &seek_time, p)) - return COMMAND_RETURN_ERROR; - - enum playlist_result result = - playlist_seek_current(&g_playlist, client->player_control, - seek_time, relative); - return print_playlist_result(client, result); -} - -static enum command_return -handle_listallinfo(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - const char *directory = ""; - - if (argc == 2) - directory = argv[1]; - - GError *error = NULL; - return printInfoForAllIn(client, directory, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_ping(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_password(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned permission = 0; - - if (getPermissionFromPassword(argv[1], &permission) < 0) { - command_error(client, ACK_ERROR_PASSWORD, "incorrect password"); - return COMMAND_RETURN_ERROR; - } - - client_set_permission(client, permission); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned xfade_time; - - if (!check_unsigned(client, &xfade_time, argv[1])) - return COMMAND_RETURN_ERROR; - pc_set_cross_fade(client->player_control, xfade_time); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_mixrampdb(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - float db; - - if (!check_float(client, &db, argv[1])) - return COMMAND_RETURN_ERROR; - pc_set_mixramp_db(client->player_control, db); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_mixrampdelay(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - float delay_secs; - - if (!check_float(client, &delay_secs, argv[1])) - return COMMAND_RETURN_ERROR; - pc_set_mixramp_delay(client->player_control, delay_secs); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_enableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned device; - bool ret; - - if (!check_unsigned(client, &device, argv[1])) - return COMMAND_RETURN_ERROR; - - ret = audio_output_enable_index(device); - if (!ret) { - command_error(client, ACK_ERROR_NO_EXIST, - "No such audio output"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_disableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned device; - bool ret; - - if (!check_unsigned(client, &device, argv[1])) - return COMMAND_RETURN_ERROR; - - ret = audio_output_disable_index(device); - if (!ret) { - command_error(client, ACK_ERROR_NO_EXIST, - "No such audio output"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_devices(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - printAudioDevices(client); - - return COMMAND_RETURN_OK; -} - -/* don't be fooled, this is the command handler for "commands" command */ -static enum command_return -handle_commands(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]); - -static enum command_return -handle_not_commands(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]); - -static enum command_return -handle_config(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - if (!client_is_local(client)) { - command_error(client, ACK_ERROR_PERMISSION, - "Command only permitted to local clients"); - return COMMAND_RETURN_ERROR; - } - - const char *path = mapper_get_music_directory_utf8(); - if (path != NULL) - client_printf(client, "music_directory: %s\n", path); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_playlistclear(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - GError *error = NULL; - return spl_clear(argv[1], &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_playlistadd(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - char *playlist = argv[1]; - char *uri = argv[2]; - - bool success; - GError *error = NULL; - if (uri_has_scheme(uri)) { - if (!uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return COMMAND_RETURN_ERROR; - } - - success = spl_append_uri(argv[1], playlist, &error); - } else - success = addAllInToStoredPlaylist(uri, playlist, &error); - - if (!success && error == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); - return COMMAND_RETURN_ERROR; - } - - return success ? COMMAND_RETURN_OK : print_error(client, error); -} - -static enum command_return -handle_listplaylists(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - GError *error = NULL; - GPtrArray *list = spl_list(&error); - if (list == NULL) - return print_error(client, error); - - print_spl_list(client, list); - spl_list_free(list); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_replay_gain_mode(struct client *client, - G_GNUC_UNUSED int argc, char *argv[]) -{ - if (!replay_gain_set_mode_string(argv[1])) { - command_error(client, ACK_ERROR_ARG, - "Unrecognized replay gain mode"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_replay_gain_status(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - client_printf(client, "replay_gain_mode: %s\n", - replay_gain_get_mode_string()); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_idle(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - unsigned flags = 0, j; - int i; - const char *const* idle_names; - - idle_names = idle_get_names(); - for (i = 1; i < argc; ++i) { - if (!argv[i]) - continue; - - for (j = 0; idle_names[j]; ++j) { - if (!g_ascii_strcasecmp(argv[i], idle_names[j])) { - flags |= (1 << j); - } - } - } - - /* No argument means that the client wants to receive everything */ - if (flags == 0) - flags = ~0; - - /* enable "idle" mode on this client */ - client_idle_wait(client, flags); - - /* return value is "1" so the caller won't print "OK" */ - return 1; -} - -#ifdef ENABLE_SQLITE -struct sticker_song_find_data { - struct client *client; - const char *name; -}; - -static void -sticker_song_find_print_cb(struct song *song, const char *value, - gpointer user_data) -{ - struct sticker_song_find_data *data = user_data; - - song_print_uri(data->client, song); - sticker_print_value(data->client, data->name, value); -} - -static enum command_return -handle_sticker_song(struct client *client, int argc, char *argv[]) -{ - /* get song song_id key */ - if (argc == 5 && strcmp(argv[1], "get") == 0) { - struct song *song; - char *value; - - song = db_get_song(argv[3]); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such song"); - return COMMAND_RETURN_ERROR; - } - - value = sticker_song_get_value(song, argv[4]); - if (value == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such sticker"); - return COMMAND_RETURN_ERROR; - } - - sticker_print_value(client, argv[4], value); - g_free(value); - - return COMMAND_RETURN_OK; - /* list song song_id */ - } else if (argc == 4 && strcmp(argv[1], "list") == 0) { - struct song *song; - struct sticker *sticker; - - song = db_get_song(argv[3]); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such song"); - return COMMAND_RETURN_ERROR; - } - - sticker = sticker_song_get(song); - if (sticker) { - sticker_print(client, sticker); - sticker_free(sticker); - } - - return COMMAND_RETURN_OK; - /* set song song_id id key */ - } else if (argc == 6 && strcmp(argv[1], "set") == 0) { - struct song *song; - bool ret; - - song = db_get_song(argv[3]); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such song"); - return COMMAND_RETURN_ERROR; - } - - ret = sticker_song_set_value(song, argv[4], argv[5]); - if (!ret) { - command_error(client, ACK_ERROR_SYSTEM, - "failed to set sticker value"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; - /* delete song song_id [key] */ - } else if ((argc == 4 || argc == 5) && - strcmp(argv[1], "delete") == 0) { - struct song *song; - bool ret; - - song = db_get_song(argv[3]); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such song"); - return COMMAND_RETURN_ERROR; - } - - ret = argc == 4 - ? sticker_song_delete(song) - : sticker_song_delete_value(song, argv[4]); - if (!ret) { - command_error(client, ACK_ERROR_SYSTEM, - "no such sticker"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; - /* find song dir key */ - } else if (argc == 5 && strcmp(argv[1], "find") == 0) { - /* "sticker find song a/directory name" */ - struct directory *directory; - bool success; - struct sticker_song_find_data data = { - .client = client, - .name = argv[4], - }; - - db_lock(); - directory = db_get_directory(argv[3]); - if (directory == NULL) { - db_unlock(); - command_error(client, ACK_ERROR_NO_EXIST, - "no such directory"); - return COMMAND_RETURN_ERROR; - } - - success = sticker_song_find(directory, data.name, - sticker_song_find_print_cb, &data); - db_unlock(); - if (!success) { - command_error(client, ACK_ERROR_SYSTEM, - "failed to set search sticker database"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; - } else { - command_error(client, ACK_ERROR_ARG, "bad request"); - return COMMAND_RETURN_ERROR; - } -} - -static enum command_return -handle_sticker(struct client *client, int argc, char *argv[]) -{ - assert(argc >= 4); - - if (!sticker_enabled()) { - command_error(client, ACK_ERROR_UNKNOWN, - "sticker database is disabled"); - return COMMAND_RETURN_ERROR; - } - - if (strcmp(argv[2], "song") == 0) - return handle_sticker_song(client, argc, argv); - else { - command_error(client, ACK_ERROR_ARG, - "unknown sticker domain"); - return COMMAND_RETURN_ERROR; - } -} -#endif - -static enum command_return -handle_subscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - assert(argc == 2); - - switch (client_subscribe(client, argv[1])) { - case CLIENT_SUBSCRIBE_OK: - return COMMAND_RETURN_OK; - - case CLIENT_SUBSCRIBE_INVALID: - command_error(client, ACK_ERROR_ARG, - "invalid channel name"); - return COMMAND_RETURN_ERROR; - - case CLIENT_SUBSCRIBE_ALREADY: - command_error(client, ACK_ERROR_EXIST, - "already subscribed to this channel"); - return COMMAND_RETURN_ERROR; - - case CLIENT_SUBSCRIBE_FULL: - command_error(client, ACK_ERROR_EXIST, - "subscription list is full"); - return COMMAND_RETURN_ERROR; - } - - /* unreachable */ - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_unsubscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - assert(argc == 2); - - if (client_unsubscribe(client, argv[1])) - return COMMAND_RETURN_OK; - else { - command_error(client, ACK_ERROR_NO_EXIST, - "not subscribed to this channel"); - return COMMAND_RETURN_ERROR; - } -} - -struct channels_context { - GStringChunk *chunk; - - GHashTable *channels; -}; - -static void -collect_channels(gpointer data, gpointer user_data) -{ - struct channels_context *context = user_data; - const struct client *client = data; - - for (GSList *i = client->subscriptions; i != NULL; - i = g_slist_next(i)) { - const char *channel = i->data; - - if (g_hash_table_lookup(context->channels, channel) == NULL) { - char *channel2 = g_string_chunk_insert(context->chunk, - channel); - g_hash_table_insert(context->channels, channel2, - context); - } - } -} - -static void -print_channel(gpointer key, G_GNUC_UNUSED gpointer value, gpointer user_data) -{ - struct client *client = user_data; - const char *channel = key; - - client_printf(client, "channel: %s\n", channel); -} - -static enum command_return -handle_channels(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - assert(argc == 1); - - struct channels_context context = { - .chunk = g_string_chunk_new(1024), - .channels = g_hash_table_new(g_str_hash, g_str_equal), - }; - - client_list_foreach(collect_channels, &context); - - g_hash_table_foreach(context.channels, print_channel, client); - - g_hash_table_destroy(context.channels); - g_string_chunk_free(context.chunk); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_read_messages(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - assert(argc == 1); - - GSList *messages = client_read_messages(client); - - for (GSList *i = messages; i != NULL; i = g_slist_next(i)) { - struct client_message *msg = i->data; - - client_printf(client, "channel: %s\nmessage: %s\n", - msg->channel, msg->message); - client_message_free(msg); - } - - g_slist_free(messages); - - return COMMAND_RETURN_OK; -} - -struct send_message_context { - struct client_message msg; - - bool sent; -}; - -static void -send_message(gpointer data, gpointer user_data) -{ - struct send_message_context *context = user_data; - struct client *client = data; - - if (client_push_message(client, &context->msg)) - context->sent = true; -} - -static enum command_return -handle_send_message(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - assert(argc == 3); - - if (!client_message_valid_channel_name(argv[1])) { - command_error(client, ACK_ERROR_ARG, - "invalid channel name"); - return COMMAND_RETURN_ERROR; - } - - struct send_message_context context = { - .sent = false, - }; - - client_message_init(&context.msg, argv[1], argv[2]); - - client_list_foreach(send_message, &context); - - client_message_deinit(&context.msg); - - if (context.sent) - return COMMAND_RETURN_OK; - else { - command_error(client, ACK_ERROR_NO_EXIST, - "nobody is subscribed to this channel"); - return COMMAND_RETURN_ERROR; - } -} - -/** - * The command registry. - * - * This array must be sorted! - */ -static const struct command commands[] = { - { "add", PERMISSION_ADD, 1, 1, handle_add }, - { "addid", PERMISSION_ADD, 1, 2, handle_addid }, - { "channels", PERMISSION_READ, 0, 0, handle_channels }, - { "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, - { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, - { "close", PERMISSION_NONE, -1, -1, handle_close }, - { "commands", PERMISSION_NONE, 0, 0, handle_commands }, - { "config", PERMISSION_ADMIN, 0, 0, handle_config }, - { "consume", PERMISSION_CONTROL, 1, 1, handle_consume }, - { "count", PERMISSION_READ, 2, -1, handle_count }, - { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade }, - { "currentsong", PERMISSION_READ, 0, 0, handle_currentsong }, - { "decoders", PERMISSION_READ, 0, 0, handle_decoders }, - { "delete", PERMISSION_CONTROL, 1, 1, handle_delete }, - { "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid }, - { "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput }, - { "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput }, - { "find", PERMISSION_READ, 2, -1, handle_find }, - { "findadd", PERMISSION_READ, 2, -1, handle_findadd}, - { "idle", PERMISSION_READ, 0, -1, handle_idle }, - { "kill", PERMISSION_ADMIN, -1, -1, handle_kill }, - { "list", PERMISSION_READ, 1, -1, handle_list }, - { "listall", PERMISSION_READ, 0, 1, handle_listall }, - { "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo }, - { "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist }, - { "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo }, - { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists }, - { "load", PERMISSION_ADD, 1, 2, handle_load }, - { "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo }, - { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb }, - { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay }, - { "move", PERMISSION_CONTROL, 2, 2, handle_move }, - { "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid }, - { "next", PERMISSION_CONTROL, 0, 0, handle_next }, - { "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands }, - { "outputs", PERMISSION_READ, 0, 0, handle_devices }, - { "password", PERMISSION_NONE, 1, 1, handle_password }, - { "pause", PERMISSION_CONTROL, 0, 1, handle_pause }, - { "ping", PERMISSION_NONE, 0, 0, handle_ping }, - { "play", PERMISSION_CONTROL, 0, 1, handle_play }, - { "playid", PERMISSION_CONTROL, 0, 1, handle_playid }, - { "playlist", PERMISSION_READ, 0, 0, handle_playlist }, - { "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd }, - { "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear }, - { "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete }, - { "playlistfind", PERMISSION_READ, 2, -1, handle_playlistfind }, - { "playlistid", PERMISSION_READ, 0, 1, handle_playlistid }, - { "playlistinfo", PERMISSION_READ, 0, 1, handle_playlistinfo }, - { "playlistmove", PERMISSION_CONTROL, 3, 3, handle_playlistmove }, - { "playlistsearch", PERMISSION_READ, 2, -1, handle_playlistsearch }, - { "plchanges", PERMISSION_READ, 1, 1, handle_plchanges }, - { "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid }, - { "previous", PERMISSION_CONTROL, 0, 0, handle_previous }, - { "prio", PERMISSION_CONTROL, 2, -1, handle_prio }, - { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid }, - { "random", PERMISSION_CONTROL, 1, 1, handle_random }, - { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages }, - { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, - { "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat }, - { "replay_gain_mode", PERMISSION_CONTROL, 1, 1, - handle_replay_gain_mode }, - { "replay_gain_status", PERMISSION_READ, 0, 0, - handle_replay_gain_status }, - { "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan }, - { "rm", PERMISSION_CONTROL, 1, 1, handle_rm }, - { "save", PERMISSION_CONTROL, 1, 1, handle_save }, - { "search", PERMISSION_READ, 2, -1, handle_search }, - { "searchadd", PERMISSION_ADD, 2, -1, handle_searchadd }, - { "searchaddpl", PERMISSION_CONTROL, 3, -1, handle_searchaddpl }, - { "seek", PERMISSION_CONTROL, 2, 2, handle_seek }, - { "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur }, - { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid }, - { "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message }, - { "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol }, - { "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle }, - { "single", PERMISSION_CONTROL, 1, 1, handle_single }, - { "stats", PERMISSION_READ, 0, 0, handle_stats }, - { "status", PERMISSION_READ, 0, 0, handle_status }, -#ifdef ENABLE_SQLITE - { "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker }, -#endif - { "stop", PERMISSION_CONTROL, 0, 0, handle_stop }, - { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe }, - { "swap", PERMISSION_CONTROL, 2, 2, handle_swap }, - { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid }, - { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, - { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe }, - { "update", PERMISSION_CONTROL, 0, 1, handle_update }, - { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, -}; - -static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]); - -static bool -command_available(G_GNUC_UNUSED const struct command *cmd) -{ -#ifdef ENABLE_SQLITE - if (strcmp(cmd->cmd, "sticker") == 0) - return sticker_enabled(); -#endif - - return true; -} - -/* don't be fooled, this is the command handler for "commands" command */ -static enum command_return -handle_commands(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - const unsigned permission = client_get_permission(client); - const struct command *cmd; - - for (unsigned i = 0; i < num_commands; ++i) { - cmd = &commands[i]; - - if (cmd->permission == (permission & cmd->permission) && - command_available(cmd)) - client_printf(client, "command: %s\n", cmd->cmd); - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_not_commands(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - const unsigned permission = client_get_permission(client); - const struct command *cmd; - - for (unsigned i = 0; i < num_commands; ++i) { - cmd = &commands[i]; - - if (cmd->permission != (permission & cmd->permission)) - client_printf(client, "command: %s\n", cmd->cmd); - } - - return COMMAND_RETURN_OK; -} - -void command_init(void) -{ -#ifndef NDEBUG - /* ensure that the command list is sorted */ - for (unsigned i = 0; i < num_commands - 1; ++i) - assert(strcmp(commands[i].cmd, commands[i + 1].cmd) < 0); -#endif -} - -void command_finish(void) -{ -} - -static const struct command * -command_lookup(const char *name) -{ - unsigned a = 0, b = num_commands, i; - int cmp; - - /* binary search */ - do { - i = (a + b) / 2; - - cmp = strcmp(name, commands[i].cmd); - if (cmp == 0) - return &commands[i]; - else if (cmp < 0) - b = i; - else if (cmp > 0) - a = i + 1; - } while (a < b); - - return NULL; -} - -static bool -command_check_request(const struct command *cmd, struct client *client, - unsigned permission, int argc, char *argv[]) -{ - int min = cmd->min + 1; - int max = cmd->max + 1; - - if (cmd->permission != (permission & cmd->permission)) { - if (client != NULL) - command_error(client, ACK_ERROR_PERMISSION, - "you don't have permission for \"%s\"", - cmd->cmd); - return false; - } - - if (min == 0) - return true; - - if (min == max && max != argc) { - if (client != NULL) - command_error(client, ACK_ERROR_ARG, - "wrong number of arguments for \"%s\"", - argv[0]); - return false; - } else if (argc < min) { - if (client != NULL) - command_error(client, ACK_ERROR_ARG, - "too few arguments for \"%s\"", argv[0]); - return false; - } else if (argc > max && max /* != 0 */ ) { - if (client != NULL) - command_error(client, ACK_ERROR_ARG, - "too many arguments for \"%s\"", argv[0]); - return false; - } else - return true; -} - -static const struct command * -command_checked_lookup(struct client *client, unsigned permission, - int argc, char *argv[]) -{ - const struct command *cmd; - - current_command = ""; - - if (argc == 0) - return NULL; - - cmd = command_lookup(argv[0]); - if (cmd == NULL) { - if (client != NULL) - command_error(client, ACK_ERROR_UNKNOWN, - "unknown command \"%s\"", argv[0]); - return NULL; - } - - current_command = cmd->cmd; - - if (!command_check_request(cmd, client, permission, argc, argv)) - return NULL; - - return cmd; -} - -enum command_return -command_process(struct client *client, unsigned num, char *line) -{ - GError *error = NULL; - int argc; - char *argv[COMMAND_ARGV_MAX] = { NULL }; - const struct command *cmd; - enum command_return ret = COMMAND_RETURN_ERROR; - - command_list_num = num; - - /* get the command name (first word on the line) */ - - argv[0] = tokenizer_next_word(&line, &error); - if (argv[0] == NULL) { - current_command = ""; - if (*line == 0) - command_error(client, ACK_ERROR_UNKNOWN, - "No command given"); - else { - command_error(client, ACK_ERROR_UNKNOWN, - "%s", error->message); - g_error_free(error); - } - current_command = NULL; - - return COMMAND_RETURN_ERROR; - } - - argc = 1; - - /* now parse the arguments (quoted or unquoted) */ - - while (argc < (int)G_N_ELEMENTS(argv) && - (argv[argc] = - tokenizer_next_param(&line, &error)) != NULL) - ++argc; - - /* some error checks; we have to set current_command because - command_error() expects it to be set */ - - current_command = argv[0]; - - if (argc >= (int)G_N_ELEMENTS(argv)) { - command_error(client, ACK_ERROR_ARG, "Too many arguments"); - current_command = NULL; - return COMMAND_RETURN_ERROR; - } - - if (*line != 0) { - command_error(client, ACK_ERROR_ARG, - "%s", error->message); - current_command = NULL; - g_error_free(error); - return COMMAND_RETURN_ERROR; - } - - /* look up and invoke the command handler */ - - cmd = command_checked_lookup(client, client_get_permission(client), - argc, argv); - if (cmd) - ret = cmd->handler(client, argc, argv); - - current_command = NULL; - command_list_num = 0; - - return ret; -} diff --git a/src/command.h b/src/command.h index 68d1f95e..9ea5bb52 100644 --- a/src/command.h +++ b/src/command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,27 +20,34 @@ #ifndef MPD_COMMAND_H #define MPD_COMMAND_H -#include "ack.h" - -#include <glib.h> -#include <stdbool.h> - enum command_return { - COMMAND_RETURN_ERROR = -1, - COMMAND_RETURN_OK = 0, - COMMAND_RETURN_KILL = 10, - COMMAND_RETURN_CLOSE = 20, + /** + * The command has succeeded, but the "OK" response was not + * yet sent to the client. + */ + COMMAND_RETURN_OK, + + /** + * The connection is now in "idle" mode, and no response shall + * be generated. + */ + COMMAND_RETURN_IDLE, + + /** + * There was an error. The "ACK" response was sent to the + * client. + */ + COMMAND_RETURN_ERROR, + + /** + * The connection to this client shall be closed. + */ + COMMAND_RETURN_CLOSE, + + /** + * The MPD process shall be shut down. + */ + COMMAND_RETURN_KILL, }; -struct client; - -void command_init(void); - -void command_finish(void); - -enum command_return -command_process(struct client *client, unsigned num, char *line); - -void command_success(struct client *client); - #endif diff --git a/src/conf.c b/src/conf.c deleted file mode 100644 index 167f2da9..00000000 --- a/src/conf.c +++ /dev/null @@ -1,666 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "conf.h" -#include "utils.h" -#include "string_util.h" -#include "tokenizer.h" -#include "path.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <stdio.h> -#include <stdlib.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "config" - -#define MAX_STRING_SIZE MPD_PATH_MAX+80 - -#define CONF_COMMENT '#' - -struct config_entry { - const char *const name; - const bool repeatable; - const bool block; - - GSList *params; -}; - -static struct config_entry config_entries[] = { - { .name = CONF_MUSIC_DIR, false, false }, - { .name = CONF_PLAYLIST_DIR, false, false }, - { .name = CONF_FOLLOW_INSIDE_SYMLINKS, false, false }, - { .name = CONF_FOLLOW_OUTSIDE_SYMLINKS, false, false }, - { .name = CONF_DB_FILE, false, false }, - { .name = CONF_STICKER_FILE, false, false }, - { .name = CONF_LOG_FILE, false, false }, - { .name = CONF_PID_FILE, false, false }, - { .name = CONF_STATE_FILE, false, false }, - { .name = "restore_paused", false, false }, - { .name = CONF_USER, false, false }, - { .name = CONF_GROUP, false, false }, - { .name = CONF_BIND_TO_ADDRESS, true, false }, - { .name = CONF_PORT, false, false }, - { .name = CONF_LOG_LEVEL, false, false }, - { .name = CONF_ZEROCONF_NAME, false, false }, - { .name = CONF_ZEROCONF_ENABLED, false, false }, - { .name = CONF_PASSWORD, true, false }, - { .name = CONF_DEFAULT_PERMS, false, false }, - { .name = CONF_AUDIO_OUTPUT, true, true }, - { .name = CONF_AUDIO_OUTPUT_FORMAT, false, false }, - { .name = CONF_MIXER_TYPE, false, false }, - { .name = CONF_REPLAYGAIN, false, false }, - { .name = CONF_REPLAYGAIN_PREAMP, false, false }, - { .name = CONF_REPLAYGAIN_MISSING_PREAMP, false, false }, - { .name = CONF_REPLAYGAIN_LIMIT, false, false }, - { .name = CONF_VOLUME_NORMALIZATION, false, false }, - { .name = CONF_SAMPLERATE_CONVERTER, false, false }, - { .name = CONF_AUDIO_BUFFER_SIZE, false, false }, - { .name = CONF_BUFFER_BEFORE_PLAY, false, false }, - { .name = CONF_HTTP_PROXY_HOST, false, false }, - { .name = CONF_HTTP_PROXY_PORT, false, false }, - { .name = CONF_HTTP_PROXY_USER, false, false }, - { .name = CONF_HTTP_PROXY_PASSWORD, false, false }, - { .name = CONF_CONN_TIMEOUT, false, false }, - { .name = CONF_MAX_CONN, false, false }, - { .name = CONF_MAX_PLAYLIST_LENGTH, false, false }, - { .name = CONF_MAX_COMMAND_LIST_SIZE, false, false }, - { .name = CONF_MAX_OUTPUT_BUFFER_SIZE, false, false }, - { .name = CONF_FS_CHARSET, false, false }, - { .name = CONF_ID3V1_ENCODING, false, false }, - { .name = CONF_METADATA_TO_USE, false, false }, - { .name = CONF_SAVE_ABSOLUTE_PATHS, false, false }, - { .name = CONF_DECODER, true, true }, - { .name = CONF_INPUT, true, true }, - { .name = CONF_GAPLESS_MP3_PLAYBACK, false, false }, - { .name = CONF_PLAYLIST_PLUGIN, true, true }, - { .name = CONF_AUTO_UPDATE, false, false }, - { .name = CONF_AUTO_UPDATE_DEPTH, false, false }, - { .name = CONF_DESPOTIFY_USER, false, false }, - { .name = CONF_DESPOTIFY_PASSWORD, false, false}, - { .name = CONF_DESPOTIFY_HIGH_BITRATE, false, false }, - { .name = "filter", true, true }, -}; - -static bool -get_bool(const char *value, bool *value_r) -{ - static const char *t[] = { "yes", "true", "1", NULL }; - static const char *f[] = { "no", "false", "0", NULL }; - - if (string_array_contains(t, value)) { - *value_r = true; - return true; - } - - if (string_array_contains(f, value)) { - *value_r = false; - return true; - } - - return false; -} - -struct config_param * -config_new_param(const char *value, int line) -{ - struct config_param *ret = g_new(struct config_param, 1); - - if (!value) - ret->value = NULL; - else - ret->value = g_strdup(value); - - ret->line = line; - - ret->num_block_params = 0; - ret->block_params = NULL; - ret->used = false; - - return ret; -} - -void -config_param_free(struct config_param *param) -{ - g_free(param->value); - - for (unsigned i = 0; i < param->num_block_params; i++) { - g_free(param->block_params[i].name); - g_free(param->block_params[i].value); - } - - if (param->num_block_params) - g_free(param->block_params); - - g_free(param); -} - -static void -config_param_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct config_param *param = data; - - config_param_free(param); -} - -static struct config_entry * -config_entry_get(const char *name) -{ - for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) { - struct config_entry *entry = &config_entries[i]; - if (strcmp(entry->name, name) == 0) - return entry; - } - - return NULL; -} - -void config_global_finish(void) -{ - for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) { - struct config_entry *entry = &config_entries[i]; - - g_slist_foreach(entry->params, - config_param_free_callback, NULL); - g_slist_free(entry->params); - } -} - -void config_global_init(void) -{ -} - -static void -config_param_check(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct config_param *param = data; - - if (!param->used) - /* this whole config_param was not queried at all - - the feature might be disabled at compile time? - Silently ignore it here. */ - return; - - for (unsigned i = 0; i < param->num_block_params; i++) { - struct block_param *bp = ¶m->block_params[i]; - - if (!bp->used) - g_warning("option '%s' on line %i was not recognized", - bp->name, bp->line); - } -} - -void config_global_check(void) -{ - for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) { - struct config_entry *entry = &config_entries[i]; - - g_slist_foreach(entry->params, config_param_check, NULL); - } -} - -void -config_add_block_param(struct config_param * param, const char *name, - const char *value, int line) -{ - struct block_param *bp; - - assert(config_get_block_param(param, name) == NULL); - - param->num_block_params++; - - param->block_params = g_realloc(param->block_params, - param->num_block_params * - sizeof(param->block_params[0])); - - bp = ¶m->block_params[param->num_block_params - 1]; - - bp->name = g_strdup(name); - bp->value = g_strdup(value); - bp->line = line; - bp->used = false; -} - -static bool -config_read_name_value(struct config_param *param, char *input, unsigned line, - GError **error_r) -{ - const char *name = tokenizer_next_word(&input, error_r); - if (name == NULL) { - assert(*input != 0); - return false; - } - - const char *value = tokenizer_next_string(&input, error_r); - if (value == NULL) { - if (*input == 0) { - assert(error_r == NULL || *error_r == NULL); - g_set_error(error_r, config_quark(), 0, - "Value missing"); - } else { - assert(error_r == NULL || *error_r != NULL); - } - - return false; - } - - if (*input != 0 && *input != CONF_COMMENT) { - g_set_error(error_r, config_quark(), 0, - "Unknown tokens after value"); - return false; - } - - const struct block_param *bp = config_get_block_param(param, name); - if (bp != NULL) { - g_set_error(error_r, config_quark(), 0, - "\"%s\" is duplicate, first defined on line %i", - name, bp->line); - return false; - } - - config_add_block_param(param, name, value, line); - return true; -} - -static struct config_param * -config_read_block(FILE *fp, int *count, char *string, GError **error_r) -{ - struct config_param *ret = config_new_param(NULL, *count); - GError *error = NULL; - - while (true) { - char *line; - - line = fgets(string, MAX_STRING_SIZE, fp); - if (line == NULL) { - config_param_free(ret); - g_set_error(error_r, config_quark(), 0, - "Expected '}' before end-of-file"); - return NULL; - } - - (*count)++; - line = strchug_fast(line); - if (*line == 0 || *line == CONF_COMMENT) - continue; - - if (*line == '}') { - /* end of this block; return from the function - (and from this "while" loop) */ - - line = strchug_fast(line + 1); - if (*line != 0 && *line != CONF_COMMENT) { - config_param_free(ret); - g_set_error(error_r, config_quark(), 0, - "line %i: Unknown tokens after '}'", - *count); - return false; - } - - return ret; - } - - /* parse name and value */ - - if (!config_read_name_value(ret, line, *count, &error)) { - assert(*line != 0); - config_param_free(ret); - g_propagate_prefixed_error(error_r, error, - "line %i: ", *count); - return NULL; - } - } -} - -bool -config_read_file(const char *file, GError **error_r) -{ - FILE *fp; - char string[MAX_STRING_SIZE + 1]; - int count = 0; - struct config_entry *entry; - struct config_param *param; - - g_debug("loading file %s", file); - - if (!(fp = fopen(file, "r"))) { - g_set_error(error_r, config_quark(), errno, - "Failed to open %s: %s", - file, g_strerror(errno)); - return false; - } - - while (fgets(string, MAX_STRING_SIZE, fp)) { - char *line; - const char *name, *value; - GError *error = NULL; - - count++; - - line = strchug_fast(string); - if (*line == 0 || *line == CONF_COMMENT) - continue; - - /* the first token in each line is the name, followed - by either the value or '{' */ - - name = tokenizer_next_word(&line, &error); - if (name == NULL) { - assert(*line != 0); - g_propagate_prefixed_error(error_r, error, - "line %i: ", count); - fclose(fp); - return false; - } - - /* get the definition of that option, and check the - "repeatable" flag */ - - entry = config_entry_get(name); - if (entry == NULL) { - g_set_error(error_r, config_quark(), 0, - "unrecognized parameter in config file at " - "line %i: %s\n", count, name); - fclose(fp); - return false; - } - - if (entry->params != NULL && !entry->repeatable) { - param = entry->params->data; - g_set_error(error_r, config_quark(), 0, - "config parameter \"%s\" is first defined " - "on line %i and redefined on line %i\n", - name, param->line, count); - fclose(fp); - return false; - } - - /* now parse the block or the value */ - - if (entry->block) { - /* it's a block, call config_read_block() */ - - if (*line != '{') { - g_set_error(error_r, config_quark(), 0, - "line %i: '{' expected", count); - fclose(fp); - return false; - } - - line = strchug_fast(line + 1); - if (*line != 0 && *line != CONF_COMMENT) { - g_set_error(error_r, config_quark(), 0, - "line %i: Unknown tokens after '{'", - count); - fclose(fp); - return false; - } - - param = config_read_block(fp, &count, string, error_r); - if (param == NULL) { - fclose(fp); - return false; - } - } else { - /* a string value */ - - value = tokenizer_next_string(&line, &error); - if (value == NULL) { - if (*line == 0) - g_set_error(error_r, config_quark(), 0, - "line %i: Value missing", - count); - else { - g_set_error(error_r, config_quark(), 0, - "line %i: %s", count, - error->message); - g_error_free(error); - } - - fclose(fp); - return false; - } - - if (*line != 0 && *line != CONF_COMMENT) { - g_set_error(error_r, config_quark(), 0, - "line %i: Unknown tokens after value", - count); - fclose(fp); - return false; - } - - param = config_new_param(value, count); - } - - entry->params = g_slist_append(entry->params, param); - } - fclose(fp); - - return true; -} - -const struct config_param * -config_get_next_param(const char *name, const struct config_param * last) -{ - struct config_entry *entry; - GSList *node; - struct config_param *param; - - entry = config_entry_get(name); - if (entry == NULL) - return NULL; - - node = entry->params; - - if (last) { - node = g_slist_find(node, last); - if (node == NULL) - return NULL; - - node = g_slist_next(node); - } - - if (node == NULL) - return NULL; - - param = node->data; - param->used = true; - return param; -} - -const char * -config_get_string(const char *name, const char *default_value) -{ - const struct config_param *param = config_get_param(name); - - if (param == NULL) - return default_value; - - return param->value; -} - -char * -config_dup_path(const char *name, GError **error_r) -{ - assert(error_r != NULL); - assert(*error_r == NULL); - - const struct config_param *param = config_get_param(name); - if (param == NULL) - return NULL; - - char *path = parsePath(param->value, error_r); - if (G_UNLIKELY(path == NULL)) - g_prefix_error(error_r, - "Invalid path in \"%s\" at line %i: ", - name, param->line); - - return path; -} - -unsigned -config_get_unsigned(const char *name, unsigned default_value) -{ - const struct config_param *param = config_get_param(name); - long value; - char *endptr; - - if (param == NULL) - return default_value; - - value = strtol(param->value, &endptr, 0); - if (*endptr != 0 || value < 0) - MPD_ERROR("Not a valid non-negative number in line %i", - param->line); - - return (unsigned)value; -} - -unsigned -config_get_positive(const char *name, unsigned default_value) -{ - const struct config_param *param = config_get_param(name); - long value; - char *endptr; - - if (param == NULL) - return default_value; - - value = strtol(param->value, &endptr, 0); - if (*endptr != 0) - MPD_ERROR("Not a valid number in line %i", param->line); - - if (value <= 0) - MPD_ERROR("Not a positive number in line %i", param->line); - - return (unsigned)value; -} - -const struct block_param * -config_get_block_param(const struct config_param * param, const char *name) -{ - if (param == NULL) - return NULL; - - for (unsigned i = 0; i < param->num_block_params; i++) { - if (0 == strcmp(name, param->block_params[i].name)) { - struct block_param *bp = ¶m->block_params[i]; - bp->used = true; - return bp; - } - } - - return NULL; -} - -bool config_get_bool(const char *name, bool default_value) -{ - const struct config_param *param = config_get_param(name); - bool success, value; - - if (param == NULL) - return default_value; - - success = get_bool(param->value, &value); - if (!success) - MPD_ERROR("%s is not a boolean value (yes, true, 1) or " - "(no, false, 0) on line %i\n", - name, param->line); - - return value; -} - -const char * -config_get_block_string(const struct config_param *param, const char *name, - const char *default_value) -{ - const struct block_param *bp = config_get_block_param(param, name); - - if (bp == NULL) - return default_value; - - return bp->value; -} - -char * -config_dup_block_path(const struct config_param *param, const char *name, - GError **error_r) -{ - assert(error_r != NULL); - assert(*error_r == NULL); - - const struct block_param *bp = config_get_block_param(param, name); - if (bp == NULL) - return NULL; - - char *path = parsePath(bp->value, error_r); - if (G_UNLIKELY(path == NULL)) - g_prefix_error(error_r, - "Invalid path in \"%s\" at line %i: ", - name, bp->line); - - return path; -} - -unsigned -config_get_block_unsigned(const struct config_param *param, const char *name, - unsigned default_value) -{ - const struct block_param *bp = config_get_block_param(param, name); - long value; - char *endptr; - - if (bp == NULL) - return default_value; - - value = strtol(bp->value, &endptr, 0); - if (*endptr != 0) - MPD_ERROR("Not a valid number in line %i", bp->line); - - if (value < 0) - MPD_ERROR("Not a positive number in line %i", bp->line); - - return (unsigned)value; -} - -bool -config_get_block_bool(const struct config_param *param, const char *name, - bool default_value) -{ - const struct block_param *bp = config_get_block_param(param, name); - bool success, value; - - if (bp == NULL) - return default_value; - - success = get_bool(bp->value, &value); - if (!success) - MPD_ERROR("%s is not a boolean value (yes, true, 1) or " - "(no, false, 0) on line %i\n", - name, bp->line); - - return value; -} @@ -20,208 +20,14 @@ #ifndef MPD_CONF_H #define MPD_CONF_H -#include <stdbool.h> -#include <glib.h> - -#define CONF_MUSIC_DIR "music_directory" -#define CONF_PLAYLIST_DIR "playlist_directory" -#define CONF_FOLLOW_INSIDE_SYMLINKS "follow_inside_symlinks" -#define CONF_FOLLOW_OUTSIDE_SYMLINKS "follow_outside_symlinks" -#define CONF_DB_FILE "db_file" -#define CONF_STICKER_FILE "sticker_file" -#define CONF_LOG_FILE "log_file" -#define CONF_PID_FILE "pid_file" -#define CONF_STATE_FILE "state_file" -#define CONF_USER "user" -#define CONF_GROUP "group" -#define CONF_BIND_TO_ADDRESS "bind_to_address" -#define CONF_PORT "port" -#define CONF_LOG_LEVEL "log_level" -#define CONF_ZEROCONF_NAME "zeroconf_name" -#define CONF_ZEROCONF_ENABLED "zeroconf_enabled" -#define CONF_PASSWORD "password" -#define CONF_DEFAULT_PERMS "default_permissions" -#define CONF_AUDIO_OUTPUT "audio_output" -#define CONF_AUDIO_FILTER "filter" -#define CONF_AUDIO_OUTPUT_FORMAT "audio_output_format" -#define CONF_MIXER_TYPE "mixer_type" -#define CONF_REPLAYGAIN "replaygain" -#define CONF_REPLAYGAIN_PREAMP "replaygain_preamp" -#define CONF_REPLAYGAIN_MISSING_PREAMP "replaygain_missing_preamp" -#define CONF_REPLAYGAIN_LIMIT "replaygain_limit" -#define CONF_VOLUME_NORMALIZATION "volume_normalization" -#define CONF_SAMPLERATE_CONVERTER "samplerate_converter" -#define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size" -#define CONF_BUFFER_BEFORE_PLAY "buffer_before_play" -#define CONF_HTTP_PROXY_HOST "http_proxy_host" -#define CONF_HTTP_PROXY_PORT "http_proxy_port" -#define CONF_HTTP_PROXY_USER "http_proxy_user" -#define CONF_HTTP_PROXY_PASSWORD "http_proxy_password" -#define CONF_CONN_TIMEOUT "connection_timeout" -#define CONF_MAX_CONN "max_connections" -#define CONF_MAX_PLAYLIST_LENGTH "max_playlist_length" -#define CONF_MAX_COMMAND_LIST_SIZE "max_command_list_size" -#define CONF_MAX_OUTPUT_BUFFER_SIZE "max_output_buffer_size" -#define CONF_FS_CHARSET "filesystem_charset" -#define CONF_ID3V1_ENCODING "id3v1_encoding" -#define CONF_METADATA_TO_USE "metadata_to_use" -#define CONF_SAVE_ABSOLUTE_PATHS "save_absolute_paths_in_playlists" -#define CONF_DECODER "decoder" -#define CONF_INPUT "input" -#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback" -#define CONF_PLAYLIST_PLUGIN "playlist_plugin" -#define CONF_AUTO_UPDATE "auto_update" -#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth" -#define CONF_DESPOTIFY_USER "despotify_user" -#define CONF_DESPOTIFY_PASSWORD "despotify_password" -#define CONF_DESPOTIFY_HIGH_BITRATE "despotify_high_bitrate" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "ConfigData.hxx" +#include "gcc.h" #define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16) #define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false #define MAX_FILTER_CHAIN_LENGTH 255 -struct block_param { - char *name; - char *value; - int line; - - /** - * This flag is false when nobody has queried the value of - * this option yet. - */ - bool used; -}; - -struct config_param { - char *value; - unsigned int line; - - struct block_param *block_params; - unsigned num_block_params; - - /** - * This flag is false when nobody has queried the value of - * this option yet. - */ - bool used; -}; - -/** - * A GQuark for GError instances, resulting from malformed - * configuration. - */ -G_GNUC_CONST -static inline GQuark -config_quark(void) -{ - return g_quark_from_static_string("config"); -} - -void config_global_init(void); -void config_global_finish(void); - -/** - * Call this function after all configuration has been evaluated. It - * checks for unused parameters, and logs warnings. - */ -void config_global_check(void); - -bool -config_read_file(const char *file, GError **error_r); - -/* don't free the returned value - set _last_ to NULL to get first entry */ -G_GNUC_PURE -const struct config_param * -config_get_next_param(const char *name, const struct config_param *last); - -G_GNUC_PURE -static inline const struct config_param * -config_get_param(const char *name) -{ - return config_get_next_param(name, NULL); -} - -/* Note on G_GNUC_PURE: Some of the functions declared pure are not - really pure in strict sense. They have side effect such that they - validate parameter's value and signal an error if it's invalid. - However, if the argument was already validated or we don't care - about the argument at all, this may be ignored so in the end, we - should be fine with calling those functions pure. */ - -G_GNUC_PURE -const char * -config_get_string(const char *name, const char *default_value); - -/** - * Returns an optional configuration variable which contains an - * absolute path. If there is a tilde prefix, it is expanded. - * Returns NULL if the value is not present. If the path could not be - * parsed, returns NULL and sets the error. - * - * The return value must be freed with g_free(). - */ -G_GNUC_MALLOC -char * -config_dup_path(const char *name, GError **error_r); - -G_GNUC_PURE -unsigned -config_get_unsigned(const char *name, unsigned default_value); - -G_GNUC_PURE -unsigned -config_get_positive(const char *name, unsigned default_value); - -G_GNUC_PURE -const struct block_param * -config_get_block_param(const struct config_param *param, const char *name); - -G_GNUC_PURE -bool config_get_bool(const char *name, bool default_value); - -G_GNUC_PURE -const char * -config_get_block_string(const struct config_param *param, const char *name, - const char *default_value); - -G_GNUC_MALLOC -static inline char * -config_dup_block_string(const struct config_param *param, const char *name, - const char *default_value) -{ - return g_strdup(config_get_block_string(param, name, default_value)); -} - -/** - * Same as config_dup_path(), but looks up the setting in the - * specified block. - */ -G_GNUC_MALLOC -char * -config_dup_block_path(const struct config_param *param, const char *name, - GError **error_r); - -G_GNUC_PURE -unsigned -config_get_block_unsigned(const struct config_param *param, const char *name, - unsigned default_value); - -G_GNUC_PURE -bool -config_get_block_bool(const struct config_param *param, const char *name, - bool default_value); - -G_GNUC_MALLOC -struct config_param * -config_new_param(const char *value, int line); - -void -config_param_free(struct config_param *param); - -void -config_add_block_param(struct config_param * param, const char *name, - const char *value, int line); - #endif diff --git a/src/cue/cue_parser.c b/src/cue/cue_parser.c index 9ccc3bcd..bee757c9 100644 --- a/src/cue/cue_parser.c +++ b/src/cue/cue_parser.c @@ -23,6 +23,8 @@ #include "song.h" #include "tag.h" +#include <glib.h> + #include <assert.h> #include <stdlib.h> diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx new file mode 100644 index 00000000..efaaffeb --- /dev/null +++ b/src/db/ProxyDatabasePlugin.cxx @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ProxyDatabasePlugin.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseSelection.hxx" +#include "PlaylistVector.hxx" +#include "Directory.hxx" +#include "gcc.h" +#include "conf.h" + +extern "C" { +#include "db_error.h" +#include "song.h" +} + +#undef MPD_DIRECTORY_H +#undef MPD_SONG_H +#include <mpd/client.h> + +#include <cassert> +#include <string> +#include <list> + +class ProxyDatabase : public Database { + std::string host; + unsigned port; + + struct mpd_connection *connection; + Directory *root; + +public: + static Database *Create(const struct config_param *param, + GError **error_r); + + virtual bool Open(GError **error_r) override; + virtual void Close() override; + virtual struct song *GetSong(const char *uri_utf8, + GError **error_r) const override; + virtual void ReturnSong(struct song *song) const; + + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const override; + + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + GError **error_r) const override; + +protected: + bool Configure(const struct config_param *param, GError **error_r); +}; + +G_GNUC_CONST +static inline GQuark +libmpdclient_quark(void) +{ + return g_quark_from_static_string("libmpdclient"); +} + +static constexpr struct { + enum tag_type d; + enum mpd_tag_type s; +} tag_table[] = { + { TAG_ARTIST, MPD_TAG_ARTIST }, + { TAG_ALBUM, MPD_TAG_ALBUM }, + { TAG_ALBUM_ARTIST, MPD_TAG_ALBUM_ARTIST }, + { TAG_TITLE, MPD_TAG_TITLE }, + { TAG_TRACK, MPD_TAG_TRACK }, + { TAG_NAME, MPD_TAG_NAME }, + { TAG_GENRE, MPD_TAG_GENRE }, + { TAG_DATE, MPD_TAG_DATE }, + { TAG_COMPOSER, MPD_TAG_COMPOSER }, + { TAG_PERFORMER, MPD_TAG_PERFORMER }, + { TAG_COMMENT, MPD_TAG_COMMENT }, + { TAG_DISC, MPD_TAG_DISC }, + { TAG_MUSICBRAINZ_ARTISTID, MPD_TAG_MUSICBRAINZ_ARTISTID }, + { TAG_MUSICBRAINZ_ALBUMID, MPD_TAG_MUSICBRAINZ_ALBUMID }, + { TAG_MUSICBRAINZ_ALBUMARTISTID, + MPD_TAG_MUSICBRAINZ_ALBUMARTISTID }, + { TAG_MUSICBRAINZ_TRACKID, MPD_TAG_MUSICBRAINZ_TRACKID }, + { TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT } +}; + +G_GNUC_CONST +static enum mpd_tag_type +Convert(enum tag_type tag_type) +{ + for (auto i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i) + if (i->d == tag_type) + return i->s; + + return MPD_TAG_COUNT; +} + +static bool +CheckError(struct mpd_connection *connection, GError **error_r) +{ + const auto error = mpd_connection_get_error(connection); + if (error == MPD_ERROR_SUCCESS) + return true; + + g_set_error_literal(error_r, libmpdclient_quark(), (int)error, + mpd_connection_get_error_message(connection)); + mpd_connection_clear_error(connection); + return false; +} + +Database * +ProxyDatabase::Create(const struct config_param *param, GError **error_r) +{ + ProxyDatabase *db = new ProxyDatabase(); + if (!db->Configure(param, error_r)) { + delete db; + db = NULL; + } + + return db; +} + +bool +ProxyDatabase::Configure(const struct config_param *param, GError **) +{ + host = config_get_block_string(param, "host", ""); + port = config_get_block_unsigned(param, "port", 0); + + return true; +} + +bool +ProxyDatabase::Open(GError **error_r) +{ + connection = mpd_connection_new(host.empty() ? NULL : host.c_str(), + port, 0); + if (connection == NULL) { + g_set_error_literal(error_r, libmpdclient_quark(), + (int)MPD_ERROR_OOM, "Out of memory"); + return false; + } + + if (!CheckError(connection, error_r)) { + mpd_connection_free(connection); + return false; + } + + root = Directory::NewRoot(); + + return true; +} + +void +ProxyDatabase::Close() +{ + assert(connection != nullptr); + + root->Free(); + mpd_connection_free(connection); +} + +static song * +Convert(const struct mpd_song *song); + +struct song * +ProxyDatabase::GetSong(const char *uri, GError **error_r) const +{ + // TODO: implement + // TODO: auto-reconnect + + if (!mpd_send_list_meta(connection, uri)) { + CheckError(connection, error_r); + return nullptr; + } + + struct mpd_song *song = mpd_recv_song(connection); + struct song *song2 = song != nullptr + ? Convert(song) + : nullptr; + mpd_song_free(song); + if (!mpd_response_finish(connection)) { + if (song2 != nullptr) + song_free(song2); + + CheckError(connection, error_r); + return nullptr; + } + + if (song2 == nullptr) + g_set_error(error_r, db_quark(), DB_NOT_FOUND, + "No such song: %s", uri); + + return song2; +} + +void +ProxyDatabase::ReturnSong(struct song *song) const +{ + assert(song != nullptr); + assert(song_in_database(song)); + assert(song_is_detached(song)); + + song_free(song); +} + +static bool +Visit(struct mpd_connection *connection, const char *uri, + bool recursive, VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, GError **error_r); + +static bool +Visit(struct mpd_connection *connection, + bool recursive, const struct mpd_directory *directory, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, GError **error_r) +{ + const char *path = mpd_directory_get_path(directory); + + if (visit_directory) { + Directory *d = Directory::NewGeneric(path, &detached_root); + bool success = visit_directory(*d, error_r); + d->Free(); + if (!success) + return false; + } + + if (recursive && + !Visit(connection, path, recursive, + visit_directory, visit_song, visit_playlist, error_r)) + return false; + + return true; +} + +static void +Copy(struct tag *tag, enum tag_type d_tag, + const struct mpd_song *song, enum mpd_tag_type s_tag) +{ + + for (unsigned i = 0;; ++i) { + const char *value = mpd_song_get_tag(song, s_tag, i); + if (value == NULL) + break; + + tag_add_item(tag, d_tag, value); + } +} + +static song * +Convert(const struct mpd_song *song) +{ + struct song *s = song_detached_new(mpd_song_get_uri(song)); + + s->mtime = mpd_song_get_last_modified(song); + s->start_ms = mpd_song_get_start(song) * 1000; + s->end_ms = mpd_song_get_end(song) * 1000; + + struct tag *tag = tag_new(); + tag->time = mpd_song_get_duration(song); + + tag_begin_add(tag); + for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i) + Copy(tag, i->d, song, i->s); + tag_end_add(tag); + + s->tag = tag; + + return s; +} + +static bool +Visit(const struct mpd_song *song, + VisitSong visit_song, GError **error_r) +{ + if (!visit_song) + return true; + + struct song *s = Convert(song); + bool success = visit_song(*s, error_r); + song_free(s); + + return success; +} + +static bool +Visit(const struct mpd_playlist *playlist, + VisitPlaylist visit_playlist, GError **error_r) +{ + if (!visit_playlist) + return true; + + PlaylistInfo p(mpd_playlist_get_path(playlist), + mpd_playlist_get_last_modified(playlist)); + + return visit_playlist(p, detached_root, error_r); +} + +class ProxyEntity { + struct mpd_entity *entity; + +public: + explicit ProxyEntity(struct mpd_entity *_entity) + :entity(_entity) {} + + ProxyEntity(const ProxyEntity &other) = delete; + + ProxyEntity(ProxyEntity &&other) + :entity(other.entity) { + other.entity = nullptr; + } + + ~ProxyEntity() { + if (entity != nullptr) + mpd_entity_free(entity); + } + + ProxyEntity &operator=(const ProxyEntity &other) = delete; + + operator const struct mpd_entity *() const { + return entity; + } +}; + +static std::list<ProxyEntity> +ReceiveEntities(struct mpd_connection *connection) +{ + std::list<ProxyEntity> entities; + struct mpd_entity *entity; + while ((entity = mpd_recv_entity(connection)) != NULL) + entities.push_back(ProxyEntity(entity)); + + mpd_response_finish(connection); + return entities; +} + +static bool +Visit(struct mpd_connection *connection, const char *uri, + bool recursive, VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, GError **error_r) +{ + if (!mpd_send_list_meta(connection, uri)) + return CheckError(connection, error_r); + + std::list<ProxyEntity> entities(ReceiveEntities(connection)); + if (!CheckError(connection, error_r)) + return false; + + for (const auto &entity : entities) { + switch (mpd_entity_get_type(entity)) { + case MPD_ENTITY_TYPE_UNKNOWN: + break; + + case MPD_ENTITY_TYPE_DIRECTORY: + if (!Visit(connection, recursive, + mpd_entity_get_directory(entity), + visit_directory, visit_song, visit_playlist, + error_r)) + return false; + break; + + case MPD_ENTITY_TYPE_SONG: + if (!Visit(mpd_entity_get_song(entity), visit_song, + error_r)) + return false; + break; + + case MPD_ENTITY_TYPE_PLAYLIST: + if (!Visit(mpd_entity_get_playlist(entity), + visit_playlist, error_r)) + return false; + break; + } + } + + return CheckError(connection, error_r); +} + +bool +ProxyDatabase::Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const +{ + // TODO: match + // TODO: auto-reconnect + + return ::Visit(connection, selection.uri, selection.recursive, + visit_directory, visit_song, visit_playlist, + error_r); +} + +bool +ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r) const +{ + enum mpd_tag_type tag_type2 = Convert(tag_type); + if (tag_type2 == MPD_TAG_COUNT) { + g_set_error_literal(error_r, libmpdclient_quark(), 0, + "Unsupported tag"); + return false; + } + + if (!mpd_search_db_tags(connection, tag_type2)) + return CheckError(connection, error_r); + + // TODO: match + (void)selection; + + if (!mpd_search_commit(connection)) + return CheckError(connection, error_r); + + bool result = true; + + struct mpd_pair *pair; + while (result && + (pair = mpd_recv_pair_tag(connection, tag_type2)) != nullptr) { + result = visit_string(pair->value, error_r); + mpd_return_pair(connection, pair); + } + + return mpd_response_finish(connection) && + CheckError(connection, error_r) && + result; +} + +bool +ProxyDatabase::GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, GError **error_r) const +{ + // TODO: match + (void)selection; + + struct mpd_stats *stats2 = + mpd_run_stats(connection); + if (stats2 == nullptr) + return CheckError(connection, error_r); + + stats.song_count = mpd_stats_get_number_of_songs(stats2); + stats.total_duration = mpd_stats_get_db_play_time(stats2); + stats.artist_count = mpd_stats_get_number_of_artists(stats2); + stats.album_count = mpd_stats_get_number_of_albums(stats2); + mpd_stats_free(stats2); + + return true; +} + +const DatabasePlugin proxy_db_plugin = { + "proxy", + ProxyDatabase::Create, +}; diff --git a/src/db/ProxyDatabasePlugin.hxx b/src/db/ProxyDatabasePlugin.hxx new file mode 100644 index 00000000..8e878bac --- /dev/null +++ b/src/db/ProxyDatabasePlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PROXY_DATABASE_PLUGIN_HXX +#define MPD_PROXY_DATABASE_PLUGIN_HXX + +struct DatabasePlugin; + +extern const DatabasePlugin proxy_db_plugin; + +#endif diff --git a/src/db/SimpleDatabasePlugin.cxx b/src/db/SimpleDatabasePlugin.cxx new file mode 100644 index 00000000..2b720c41 --- /dev/null +++ b/src/db/SimpleDatabasePlugin.cxx @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SimpleDatabasePlugin.hxx" +#include "DatabaseSelection.hxx" +#include "DatabaseHelpers.hxx" +#include "Directory.hxx" +#include "SongFilter.hxx" +#include "DatabaseSave.hxx" +#include "DatabaseLock.hxx" +#include "db_error.h" +#include "TextFile.hxx" +#include "conf.h" +#include "fs/FileSystem.hxx" + +#include <sys/types.h> +#include <errno.h> + +G_GNUC_CONST +static inline GQuark +simple_db_quark(void) +{ + return g_quark_from_static_string("simple_db"); +} + +Database * +SimpleDatabase::Create(const struct config_param *param, GError **error_r) +{ + SimpleDatabase *db = new SimpleDatabase(); + if (!db->Configure(param, error_r)) { + delete db; + db = NULL; + } + + return db; +} + +bool +SimpleDatabase::Configure(const struct config_param *param, GError **error_r) +{ + GError *error = NULL; + + char *_path = config_dup_block_path(param, "path", &error); + if (_path == NULL) { + if (error != NULL) + g_propagate_error(error_r, error); + else + g_set_error(error_r, simple_db_quark(), 0, + "No \"path\" parameter specified"); + return false; + } + + path = Path::FromUTF8(_path); + path_utf8 = _path; + + free(_path); + + if (path.IsNull()) { + g_set_error(error_r, simple_db_quark(), 0, + "Failed to convert database path to FS encoding"); + return false; + } + + return true; +} + +bool +SimpleDatabase::Check(GError **error_r) const +{ + assert(!path.IsNull()); + assert(!path.empty()); + + /* Check if the file exists */ + if (!CheckAccess(path, F_OK)) { + /* If the file doesn't exist, we can't check if we can write + * it, so we are going to try to get the directory path, and + * see if we can write a file in that */ + const Path dirPath = path.GetDirectoryName(); + + /* Check that the parent part of the path is a directory */ + struct stat st; + if (!StatFile(dirPath, st)) { + g_set_error(error_r, simple_db_quark(), errno, + "Couldn't stat parent directory of db file " + "\"%s\": %s", + path_utf8.c_str(), g_strerror(errno)); + return false; + } + + if (!S_ISDIR(st.st_mode)) { + g_set_error(error_r, simple_db_quark(), 0, + "Couldn't create db file \"%s\" because the " + "parent path is not a directory", + path_utf8.c_str()); + return false; + } + + /* Check if we can write to the directory */ + if (!CheckAccess(dirPath, X_OK | W_OK)) { + int error = errno; + const std::string dirPath_utf8 = dirPath.ToUTF8(); + g_set_error(error_r, simple_db_quark(), error, + "Can't create db file in \"%s\": %s", + dirPath_utf8.c_str(), g_strerror(error)); + return false; + } + + return true; + } + + /* Path exists, now check if it's a regular file */ + struct stat st; + if (!StatFile(path, st)) { + g_set_error(error_r, simple_db_quark(), errno, + "Couldn't stat db file \"%s\": %s", + path_utf8.c_str(), g_strerror(errno)); + return false; + } + + if (!S_ISREG(st.st_mode)) { + g_set_error(error_r, simple_db_quark(), 0, + "db file \"%s\" is not a regular file", + path_utf8.c_str()); + return false; + } + + /* And check that we can write to it */ + if (!CheckAccess(path, R_OK | W_OK)) { + g_set_error(error_r, simple_db_quark(), errno, + "Can't open db file \"%s\" for reading/writing: %s", + path_utf8.c_str(), g_strerror(errno)); + return false; + } + + return true; +} + +bool +SimpleDatabase::Load(GError **error_r) +{ + assert(!path.empty()); + assert(root != NULL); + + TextFile file(path); + if (file.HasFailed()) { + g_set_error(error_r, simple_db_quark(), errno, + "Failed to open database file \"%s\": %s", + path_utf8.c_str(), g_strerror(errno)); + return false; + } + + if (!db_load_internal(file, root, error_r)) + return false; + + struct stat st; + if (StatFile(path, st)) + mtime = st.st_mtime; + + return true; +} + +bool +SimpleDatabase::Open(GError **error_r) +{ + root = Directory::NewRoot(); + mtime = 0; + +#ifndef NDEBUG + borrowed_song_count = 0; +#endif + + GError *error = NULL; + if (!Load(&error)) { + root->Free(); + + g_warning("Failed to load database: %s", error->message); + g_error_free(error); + + if (!Check(error_r)) + return false; + + root = Directory::NewRoot(); + } + + return true; +} + +void +SimpleDatabase::Close() +{ + assert(root != NULL); + assert(borrowed_song_count == 0); + + root->Free(); +} + +struct song * +SimpleDatabase::GetSong(const char *uri, GError **error_r) const +{ + assert(root != NULL); + + db_lock(); + song *song = root->LookupSong(uri); + db_unlock(); + if (song == NULL) + g_set_error(error_r, db_quark(), DB_NOT_FOUND, + "No such song: %s", uri); +#ifndef NDEBUG + else + ++const_cast<unsigned &>(borrowed_song_count); +#endif + + return song; +} + +void +SimpleDatabase::ReturnSong(gcc_unused struct song *song) const +{ + assert(song != nullptr); + +#ifndef NDEBUG + assert(borrowed_song_count > 0); + --const_cast<unsigned &>(borrowed_song_count); +#endif +} + +G_GNUC_PURE +const Directory * +SimpleDatabase::LookupDirectory(const char *uri) const +{ + assert(root != NULL); + assert(uri != NULL); + + ScopeDatabaseLock protect; + return root->LookupDirectory(uri); +} + +bool +SimpleDatabase::Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const +{ + ScopeDatabaseLock protect; + + const Directory *directory = root->LookupDirectory(selection.uri); + if (directory == NULL) { + if (visit_song) { + song *song = root->LookupSong(selection.uri); + if (song != nullptr) + return !selection.Match(*song) || + visit_song(*song, error_r); + } + + g_set_error(error_r, db_quark(), DB_NOT_FOUND, + "No such directory"); + return false; + } + + if (selection.recursive && visit_directory && + !visit_directory(*directory, error_r)) + return false; + + return directory->Walk(selection.recursive, selection.filter, + visit_directory, visit_song, visit_playlist, + error_r); +} + +bool +SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r) const +{ + return ::VisitUniqueTags(*this, selection, tag_type, visit_string, + error_r); +} + +bool +SimpleDatabase::GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, GError **error_r) const +{ + return ::GetStats(*this, selection, stats, error_r); +} + +bool +SimpleDatabase::Save(GError **error_r) +{ + db_lock(); + + g_debug("removing empty directories from DB"); + root->PruneEmpty(); + + g_debug("sorting DB"); + root->Sort(); + + db_unlock(); + + g_debug("writing DB"); + + FILE *fp = FOpen(path, FOpenMode::WriteText); + if (!fp) { + g_set_error(error_r, simple_db_quark(), errno, + "unable to write to db file \"%s\": %s", + path_utf8.c_str(), g_strerror(errno)); + return false; + } + + db_save_internal(fp, root); + + if (ferror(fp)) { + g_set_error(error_r, simple_db_quark(), errno, + "Failed to write to database file: %s", + g_strerror(errno)); + fclose(fp); + return false; + } + + fclose(fp); + + struct stat st; + if (StatFile(path, st)) + mtime = st.st_mtime; + + return true; +} + +const DatabasePlugin simple_db_plugin = { + "simple", + SimpleDatabase::Create, +}; diff --git a/src/db/SimpleDatabasePlugin.hxx b/src/db/SimpleDatabasePlugin.hxx new file mode 100644 index 00000000..525e854d --- /dev/null +++ b/src/db/SimpleDatabasePlugin.hxx @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SIMPLE_DATABASE_PLUGIN_HXX +#define MPD_SIMPLE_DATABASE_PLUGIN_HXX + +#include "DatabasePlugin.hxx" +#include "fs/Path.hxx" +#include "gcc.h" + +#include <cassert> + +#include <time.h> + +struct Directory; + +class SimpleDatabase : public Database { + Path path; + std::string path_utf8; + + Directory *root; + + time_t mtime; + +#ifndef NDEBUG + unsigned borrowed_song_count; +#endif + + SimpleDatabase() + :path(Path::Null()) {} + +public: + gcc_pure + Directory *GetRoot() { + assert(root != NULL); + + return root; + } + + bool Save(GError **error_r); + + gcc_pure + time_t GetLastModified() const { + return mtime; + } + + static Database *Create(const struct config_param *param, + GError **error_r); + + virtual bool Open(GError **error_r) override; + virtual void Close() override; + + virtual struct song *GetSong(const char *uri_utf8, + GError **error_r) const override; + virtual void ReturnSong(struct song *song) const; + + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const override; + + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + GError **error_r) const override; + +protected: + bool Configure(const struct config_param *param, GError **error_r); + + gcc_pure + bool Check(GError **error_r) const; + + bool Load(GError **error_r); + + gcc_pure + const Directory *LookupDirectory(const char *uri) const; +}; + +extern const DatabasePlugin simple_db_plugin; + +#endif diff --git a/src/db/simple_db_plugin.c b/src/db/simple_db_plugin.c deleted file mode 100644 index 697e8da5..00000000 --- a/src/db/simple_db_plugin.c +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "simple_db_plugin.h" -#include "db_internal.h" -#include "db_error.h" -#include "db_selection.h" -#include "db_visitor.h" -#include "db_save.h" -#include "db_lock.h" -#include "conf.h" -#include "directory.h" - -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> - -struct simple_db { - struct db base; - - char *path; - - struct directory *root; - - time_t mtime; -}; - -G_GNUC_CONST -static inline GQuark -simple_db_quark(void) -{ - return g_quark_from_static_string("simple_db"); -} - -G_GNUC_PURE -static const struct directory * -simple_db_lookup_directory(const struct simple_db *db, const char *uri) -{ - assert(db != NULL); - assert(db->root != NULL); - assert(uri != NULL); - - db_lock(); - struct directory *directory = - directory_lookup_directory(db->root, uri); - db_unlock(); - return directory; -} - -static struct db * -simple_db_init(const struct config_param *param, GError **error_r) -{ - struct simple_db *db = g_malloc(sizeof(*db)); - db_base_init(&db->base, &simple_db_plugin); - - GError *error = NULL; - db->path = config_dup_block_path(param, "path", &error); - if (db->path == NULL) { - g_free(db); - if (error != NULL) - g_propagate_error(error_r, error); - else - g_set_error(error_r, simple_db_quark(), 0, - "No \"path\" parameter specified"); - return NULL; - } - - return &db->base; -} - -static void -simple_db_finish(struct db *_db) -{ - struct simple_db *db = (struct simple_db *)_db; - - g_free(db->path); - g_free(db); -} - -static bool -simple_db_check(struct simple_db *db, GError **error_r) -{ - assert(db != NULL); - assert(db->path != NULL); - - /* Check if the file exists */ - if (access(db->path, F_OK)) { - /* If the file doesn't exist, we can't check if we can write - * it, so we are going to try to get the directory path, and - * see if we can write a file in that */ - char *dirPath = g_path_get_dirname(db->path); - - /* Check that the parent part of the path is a directory */ - struct stat st; - if (stat(dirPath, &st) < 0) { - g_free(dirPath); - g_set_error(error_r, simple_db_quark(), errno, - "Couldn't stat parent directory of db file " - "\"%s\": %s", - db->path, g_strerror(errno)); - return false; - } - - if (!S_ISDIR(st.st_mode)) { - g_free(dirPath); - g_set_error(error_r, simple_db_quark(), 0, - "Couldn't create db file \"%s\" because the " - "parent path is not a directory", - db->path); - return false; - } - - /* Check if we can write to the directory */ - if (access(dirPath, X_OK | W_OK)) { - g_set_error(error_r, simple_db_quark(), errno, - "Can't create db file in \"%s\": %s", - dirPath, g_strerror(errno)); - g_free(dirPath); - return false; - } - - g_free(dirPath); - - return true; - } - - /* Path exists, now check if it's a regular file */ - struct stat st; - if (stat(db->path, &st) < 0) { - g_set_error(error_r, simple_db_quark(), errno, - "Couldn't stat db file \"%s\": %s", - db->path, g_strerror(errno)); - return false; - } - - if (!S_ISREG(st.st_mode)) { - g_set_error(error_r, simple_db_quark(), 0, - "db file \"%s\" is not a regular file", - db->path); - return false; - } - - /* And check that we can write to it */ - if (access(db->path, R_OK | W_OK)) { - g_set_error(error_r, simple_db_quark(), errno, - "Can't open db file \"%s\" for reading/writing: %s", - db->path, g_strerror(errno)); - return false; - } - - return true; -} - -static bool -simple_db_load(struct simple_db *db, GError **error_r) -{ - assert(db != NULL); - assert(db->path != NULL); - assert(db->root != NULL); - - FILE *fp = fopen(db->path, "r"); - if (fp == NULL) { - g_set_error(error_r, simple_db_quark(), errno, - "Failed to open database file \"%s\": %s", - db->path, g_strerror(errno)); - return false; - } - - if (!db_load_internal(fp, db->root, error_r)) { - fclose(fp); - return false; - } - - fclose(fp); - - struct stat st; - if (stat(db->path, &st) == 0) - db->mtime = st.st_mtime; - - return true; -} - -static bool -simple_db_open(struct db *_db, G_GNUC_UNUSED GError **error_r) -{ - struct simple_db *db = (struct simple_db *)_db; - - db->root = directory_new_root(); - db->mtime = 0; - - GError *error = NULL; - if (!simple_db_load(db, &error)) { - directory_free(db->root); - - g_warning("Failed to load database: %s", error->message); - g_error_free(error); - - if (!simple_db_check(db, error_r)) - return false; - - db->root = directory_new_root(); - } - - return true; -} - -static void -simple_db_close(struct db *_db) -{ - struct simple_db *db = (struct simple_db *)_db; - - assert(db->root != NULL); - - directory_free(db->root); -} - -static struct song * -simple_db_get_song(struct db *_db, const char *uri, GError **error_r) -{ - struct simple_db *db = (struct simple_db *)_db; - - assert(db->root != NULL); - - db_lock(); - struct song *song = directory_lookup_song(db->root, uri); - db_unlock(); - if (song == NULL) - g_set_error(error_r, db_quark(), DB_NOT_FOUND, - "No such song: %s", uri); - - return song; -} - -static bool -simple_db_visit(struct db *_db, const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - const struct simple_db *db = (const struct simple_db *)_db; - const struct directory *directory = - simple_db_lookup_directory(db, selection->uri); - if (directory == NULL) { - struct song *song; - if (visitor->song != NULL && - (song = simple_db_get_song(_db, selection->uri, NULL)) != NULL) - return visitor->song(song, ctx, error_r); - - g_set_error(error_r, db_quark(), DB_NOT_FOUND, - "No such directory"); - return false; - } - - if (selection->recursive && visitor->directory != NULL && - !visitor->directory(directory, ctx, error_r)) - return false; - - db_lock(); - bool ret = directory_walk(directory, selection->recursive, - visitor, ctx, error_r); - db_unlock(); - return ret; -} - -const struct db_plugin simple_db_plugin = { - .name = "simple", - .init = simple_db_init, - .finish = simple_db_finish, - .open = simple_db_open, - .close = simple_db_close, - .get_song = simple_db_get_song, - .visit = simple_db_visit, -}; - -struct directory * -simple_db_get_root(struct db *_db) -{ - struct simple_db *db = (struct simple_db *)_db; - - assert(db != NULL); - assert(db->root != NULL); - - return db->root; -} - -bool -simple_db_save(struct db *_db, GError **error_r) -{ - struct simple_db *db = (struct simple_db *)_db; - struct directory *music_root = db->root; - - db_lock(); - - g_debug("removing empty directories from DB"); - directory_prune_empty(music_root); - - g_debug("sorting DB"); - directory_sort(music_root); - - db_unlock(); - - g_debug("writing DB"); - - FILE *fp = fopen(db->path, "w"); - if (!fp) { - g_set_error(error_r, simple_db_quark(), errno, - "unable to write to db file \"%s\": %s", - db->path, g_strerror(errno)); - return false; - } - - db_save_internal(fp, music_root); - - if (ferror(fp)) { - g_set_error(error_r, simple_db_quark(), errno, - "Failed to write to database file: %s", - g_strerror(errno)); - fclose(fp); - return false; - } - - fclose(fp); - - struct stat st; - if (stat(db->path, &st) == 0) - db->mtime = st.st_mtime; - - return true; -} - -time_t -simple_db_get_mtime(const struct db *_db) -{ - const struct simple_db *db = (const struct simple_db *)_db; - - assert(db != NULL); - assert(db->root != NULL); - - return db->mtime; -} diff --git a/src/dbUtils.c b/src/dbUtils.c deleted file mode 100644 index c212d9f9..00000000 --- a/src/dbUtils.c +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "dbUtils.h" -#include "locate.h" -#include "database.h" -#include "db_visitor.h" -#include "playlist.h" -#include "stored_playlist.h" - -#include <glib.h> - -static bool -add_to_queue_song(struct song *song, void *ctx, GError **error_r) -{ - struct player_control *pc = ctx; - - enum playlist_result result = - playlist_append_song(&g_playlist, pc, song, NULL); - if (result != PLAYLIST_RESULT_SUCCESS) { - g_set_error(error_r, playlist_quark(), result, - "Playlist error"); - return false; - } - - return true; -} - -static const struct db_visitor add_to_queue_visitor = { - .song = add_to_queue_song, -}; - -bool -addAllIn(struct player_control *pc, const char *uri, GError **error_r) -{ - return db_walk(uri, &add_to_queue_visitor, pc, error_r); -} - -struct add_data { - const char *path; -}; - -static bool -add_to_spl_song(struct song *song, void *ctx, GError **error_r) -{ - struct add_data *data = ctx; - - if (!spl_append_song(data->path, song, error_r)) - return false; - - return true; -} - -static const struct db_visitor add_to_spl_visitor = { - .song = add_to_spl_song, -}; - -bool -addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8, - GError **error_r) -{ - struct add_data data = { - .path = path_utf8, - }; - - return db_walk(uri_utf8, &add_to_spl_visitor, &data, error_r); -} - -struct find_add_data { - struct player_control *pc; - const struct locate_item_list *criteria; -}; - -static bool -find_add_song(struct song *song, void *ctx, GError **error_r) -{ - struct find_add_data *data = ctx; - - if (!locate_song_match(song, data->criteria)) - return true; - - enum playlist_result result = - playlist_append_song(&g_playlist, data->pc, - song, NULL); - if (result != PLAYLIST_RESULT_SUCCESS) { - g_set_error(error_r, playlist_quark(), result, - "Playlist error"); - return false; - } - - return true; -} - -static const struct db_visitor find_add_visitor = { - .song = find_add_song, -}; - -bool -findAddIn(struct player_control *pc, const char *name, - const struct locate_item_list *criteria, GError **error_r) -{ - struct find_add_data data; - data.pc = pc; - data.criteria = criteria; - - return db_walk(name, &find_add_visitor, &data, error_r); -} - -static bool -searchadd_visitor_song(struct song *song, void *_data, GError **error_r) -{ - struct find_add_data *data = _data; - - if (!locate_song_search(song, data->criteria)) - return true; - - enum playlist_result result = - playlist_append_song(&g_playlist, data->pc, song, NULL); - if (result != PLAYLIST_RESULT_SUCCESS) { - g_set_error(error_r, playlist_quark(), result, - "Playlist error"); - return false; - } - - return true; -} - -static const struct db_visitor searchadd_visitor = { - .song = searchadd_visitor_song, -}; - -bool -search_add_songs(struct player_control *pc, const char *uri, - const struct locate_item_list *criteria, - GError **error_r) -{ - struct locate_item_list *new_list = - locate_item_list_casefold(criteria); - struct find_add_data data = { - .pc = pc, - .criteria = new_list, - }; - - bool success = db_walk(uri, &searchadd_visitor, &data, error_r); - - locate_item_list_free(new_list); - - return success; -} - -struct search_add_playlist_data { - const char *playlist; - const struct locate_item_list *criteria; -}; - -static bool -searchaddpl_visitor_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct search_add_playlist_data *data = _data; - - if (!locate_song_search(song, data->criteria)) - return true; - - if (!spl_append_song(data->playlist, song, error_r)) - return false; - - return true; -} - -static const struct db_visitor searchaddpl_visitor = { - .song = searchaddpl_visitor_song, -}; - -bool -search_add_to_playlist(const char *uri, const char *path_utf8, - const struct locate_item_list *criteria, - GError **error_r) -{ - struct locate_item_list *new_list - = locate_item_list_casefold(criteria); - struct search_add_playlist_data data = { - .playlist = path_utf8, - .criteria = new_list, - }; - - bool success = db_walk(uri, &searchaddpl_visitor, &data, error_r); - - locate_item_list_free(new_list); - - return success; -} diff --git a/src/dbUtils.h b/src/dbUtils.h deleted file mode 100644 index 706c807f..00000000 --- a/src/dbUtils.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_UTILS_H -#define MPD_DB_UTILS_H - -#include "gcc.h" - -#include <glib.h> -#include <stdbool.h> - -struct locate_item_list; -struct player_control; - -gcc_nonnull(1,2) -bool -addAllIn(struct player_control *pc, const char *uri, GError **error_r); - -gcc_nonnull(1,2) -bool -addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8, - GError **error_r); - -gcc_nonnull(1,2,3) -bool -findAddIn(struct player_control *pc, const char *name, - const struct locate_item_list *criteria, GError **error_r); - -gcc_nonnull(1,2,3) -bool -search_add_songs(struct player_control *pc, const char *uri, - const struct locate_item_list *criteria, GError **error_r); - -gcc_nonnull(1,2,3) -bool -search_add_to_playlist(const char *uri, const char *path_utf8, - const struct locate_item_list *criteria, - GError **error_r); - -#endif diff --git a/src/db_plugin.h b/src/db_plugin.h deleted file mode 100644 index 1c7e14ed..00000000 --- a/src/db_plugin.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This header declares the db_plugin class. It describes a - * plugin API for databases of song metadata. - */ - -#ifndef MPD_DB_PLUGIN_H -#define MPD_DB_PLUGIN_H - -#include <glib.h> -#include <assert.h> -#include <stdbool.h> - -struct config_param; -struct db_selection; -struct db_visitor; - -struct db { - const struct db_plugin *plugin; -}; - -struct db_plugin { - const char *name; - - /** - * Allocates and configures a database. - */ - struct db *(*init)(const struct config_param *param, GError **error_r); - - /** - * Free instance data. - */ - void (*finish)(struct db *db); - - /** - * Open the database. Read it into memory if applicable. - */ - bool (*open)(struct db *db, GError **error_r); - - /** - * Close the database, free allocated memory. - */ - void (*close)(struct db *db); - - /** - * Look up a song (including tag data) in the database. - * - * @param the URI of the song within the music directory - * (UTF-8) - */ - struct song *(*get_song)(struct db *db, const char *uri, - GError **error_r); - - /** - * Visit the selected entities. - */ - bool (*visit)(struct db *db, const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r); -}; - -G_GNUC_MALLOC -static inline struct db * -db_plugin_new(const struct db_plugin *plugin, const struct config_param *param, - GError **error_r) -{ - assert(plugin != NULL); - assert(plugin->init != NULL); - assert(plugin->finish != NULL); - assert(plugin->get_song != NULL); - assert(plugin->visit != NULL); - assert(error_r == NULL || *error_r == NULL); - - struct db *db = plugin->init(param, error_r); - assert(db == NULL || db->plugin == plugin); - assert(db != NULL || error_r == NULL || *error_r != NULL); - - return db; -} - -static inline void -db_plugin_free(struct db *db) -{ - assert(db != NULL); - assert(db->plugin != NULL); - assert(db->plugin->finish != NULL); - - db->plugin->finish(db); -} - -static inline bool -db_plugin_open(struct db *db, GError **error_r) -{ - assert(db != NULL); - assert(db->plugin != NULL); - - return db->plugin->open != NULL - ? db->plugin->open(db, error_r) - : true; -} - -static inline void -db_plugin_close(struct db *db) -{ - assert(db != NULL); - assert(db->plugin != NULL); - - if (db->plugin->close != NULL) - db->plugin->close(db); -} - -static inline struct song * -db_plugin_get_song(struct db *db, const char *uri, GError **error_r) -{ - assert(db != NULL); - assert(db->plugin != NULL); - assert(db->plugin->get_song != NULL); - assert(uri != NULL); - - return db->plugin->get_song(db, uri, error_r); -} - -static inline bool -db_plugin_visit(struct db *db, const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - assert(db != NULL); - assert(db->plugin != NULL); - assert(selection != NULL); - assert(visitor != NULL); - assert(error_r == NULL || *error_r == NULL); - - return db->plugin->visit(db, selection, visitor, ctx, error_r); -} - -#endif diff --git a/src/db_print.c b/src/db_print.c deleted file mode 100644 index 4d7e3f5e..00000000 --- a/src/db_print.c +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "db_print.h" -#include "db_selection.h" -#include "db_visitor.h" -#include "locate.h" -#include "directory.h" -#include "database.h" -#include "client.h" -#include "song.h" -#include "song_print.h" -#include "playlist_vector.h" -#include "tag.h" -#include "strset.h" - -#include <glib.h> - -typedef struct _ListCommandItem { - int8_t tagType; - const struct locate_item_list *criteria; -} ListCommandItem; - -typedef struct _SearchStats { - const struct locate_item_list *criteria; - int numberOfSongs; - unsigned long playTime; -} SearchStats; - -static bool -print_visitor_directory(const struct directory *directory, void *data, - G_GNUC_UNUSED GError **error_r) -{ - struct client *client = data; - - if (!directory_is_root(directory)) - client_printf(client, "directory: %s\n", directory_get_path(directory)); - - return true; -} - -static void -print_playlist_in_directory(struct client *client, - const struct directory *directory, - const char *name_utf8) -{ - if (directory_is_root(directory)) - client_printf(client, "playlist: %s\n", name_utf8); - else - client_printf(client, "playlist: %s/%s\n", - directory_get_path(directory), name_utf8); -} - -static bool -print_visitor_song(struct song *song, void *data, - G_GNUC_UNUSED GError **error_r) -{ - assert(song != NULL); - assert(song->parent != NULL); - - struct client *client = data; - song_print_uri(client, song); - - if (song->tag != NULL && song->tag->has_playlist) - /* this song file has an embedded CUE sheet */ - print_playlist_in_directory(client, song->parent, - song->uri); - - return true; -} - -static bool -print_visitor_song_info(struct song *song, void *data, - G_GNUC_UNUSED GError **error_r) -{ - assert(song != NULL); - assert(song->parent != NULL); - - struct client *client = data; - song_print_info(client, song); - - if (song->tag != NULL && song->tag->has_playlist) - /* this song file has an embedded CUE sheet */ - print_playlist_in_directory(client, song->parent, - song->uri); - - return true; -} - -static bool -print_visitor_playlist(const struct playlist_metadata *playlist, - const struct directory *directory, void *ctx, - G_GNUC_UNUSED GError **error_r) -{ - struct client *client = ctx; - print_playlist_in_directory(client, directory, playlist->name); - return true; -} - -static bool -print_visitor_playlist_info(const struct playlist_metadata *playlist, - const struct directory *directory, - void *ctx, G_GNUC_UNUSED GError **error_r) -{ - struct client *client = ctx; - print_playlist_in_directory(client, directory, playlist->name); - -#ifndef G_OS_WIN32 - struct tm tm; -#endif - char timestamp[32]; - time_t t = playlist->mtime; - strftime(timestamp, sizeof(timestamp), -#ifdef G_OS_WIN32 - "%Y-%m-%dT%H:%M:%SZ", - gmtime(&t) -#else - "%FT%TZ", - gmtime_r(&t, &tm) -#endif - ); - client_printf(client, "Last-Modified: %s\n", timestamp); - - return true; -} - -static const struct db_visitor print_visitor = { - .directory = print_visitor_directory, - .song = print_visitor_song, - .playlist = print_visitor_playlist, -}; - -static const struct db_visitor print_info_visitor = { - .directory = print_visitor_directory, - .song = print_visitor_song_info, - .playlist = print_visitor_playlist_info, -}; - -bool -db_selection_print(struct client *client, const struct db_selection *selection, - bool full, GError **error_r) -{ - return db_visit(selection, full ? &print_info_visitor : &print_visitor, - client, error_r); -} - -struct search_data { - struct client *client; - const struct locate_item_list *criteria; -}; - -static bool -search_visitor_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct search_data *data = _data; - - if (locate_song_search(song, data->criteria)) - song_print_info(data->client, song); - - return true; -} - -static const struct db_visitor search_visitor = { - .song = search_visitor_song, -}; - -bool -searchForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r) -{ - struct locate_item_list *new_list - = locate_item_list_casefold(criteria); - struct search_data data; - - data.client = client; - data.criteria = new_list; - - bool success = db_walk(name, &search_visitor, &data, error_r); - - locate_item_list_free(new_list); - - return success; -} - -static bool -find_visitor_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct search_data *data = _data; - - if (locate_song_match(song, data->criteria)) - song_print_info(data->client, song); - - return true; -} - -static const struct db_visitor find_visitor = { - .song = find_visitor_song, -}; - -bool -findSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r) -{ - struct search_data data; - - data.client = client; - data.criteria = criteria; - - return db_walk(name, &find_visitor, &data, error_r); -} - -static void printSearchStats(struct client *client, SearchStats *stats) -{ - client_printf(client, "songs: %i\n", stats->numberOfSongs); - client_printf(client, "playtime: %li\n", stats->playTime); -} - -static bool -stats_visitor_song(struct song *song, void *data, - G_GNUC_UNUSED GError **error_r) -{ - SearchStats *stats = data; - - if (locate_song_match(song, stats->criteria)) { - stats->numberOfSongs++; - stats->playTime += song_get_duration(song); - } - - return true; -} - -static const struct db_visitor stats_visitor = { - .song = stats_visitor_song, -}; - -bool -searchStatsForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r) -{ - SearchStats stats; - - stats.criteria = criteria; - stats.numberOfSongs = 0; - stats.playTime = 0; - - if (!db_walk(name, &stats_visitor, &stats, error_r)) - return false; - - printSearchStats(client, &stats); - return true; -} - -bool -printAllIn(struct client *client, const char *uri_utf8, GError **error_r) -{ - struct db_selection selection; - db_selection_init(&selection, uri_utf8, true); - return db_selection_print(client, &selection, false, error_r); -} - -bool -printInfoForAllIn(struct client *client, const char *uri_utf8, - GError **error_r) -{ - struct db_selection selection; - db_selection_init(&selection, uri_utf8, true); - return db_selection_print(client, &selection, true, error_r); -} - -static ListCommandItem * -newListCommandItem(int tagType, const struct locate_item_list *criteria) -{ - ListCommandItem *item = g_new(ListCommandItem, 1); - - item->tagType = tagType; - item->criteria = criteria; - - return item; -} - -static void freeListCommandItem(ListCommandItem * item) -{ - g_free(item); -} - -static void -visitTag(struct client *client, struct strset *set, - struct song *song, enum tag_type tagType) -{ - struct tag *tag = song->tag; - bool found = false; - - if (tagType == LOCATE_TAG_FILE_TYPE) { - song_print_uri(client, song); - return; - } - - if (!tag) - return; - - for (unsigned i = 0; i < tag->num_items; i++) { - if (tag->items[i]->type == tagType) { - strset_add(set, tag->items[i]->value); - found = true; - } - } - - if (!found) - strset_add(set, ""); -} - -struct list_tags_data { - struct client *client; - ListCommandItem *item; - struct strset *set; -}; - -static bool -unique_tags_visitor_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct list_tags_data *data = _data; - ListCommandItem *item = data->item; - - if (locate_song_match(song, item->criteria)) - visitTag(data->client, data->set, song, item->tagType); - - return true; -} - -static const struct db_visitor unique_tags_visitor = { - .song = unique_tags_visitor_song, -}; - -bool -listAllUniqueTags(struct client *client, int type, - const struct locate_item_list *criteria, - GError **error_r) -{ - ListCommandItem *item = newListCommandItem(type, criteria); - struct list_tags_data data = { - .client = client, - .item = item, - }; - - if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { - data.set = strset_new(); - } - - if (!db_walk("", &unique_tags_visitor, &data, error_r)) { - freeListCommandItem(item); - return false; - } - - if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { - const char *value; - - strset_rewind(data.set); - - while ((value = strset_next(data.set)) != NULL) - client_printf(client, "%s: %s\n", - tag_item_names[type], - value); - - strset_free(data.set); - } - - freeListCommandItem(item); - - return true; -} diff --git a/src/db_visitor.h b/src/db_visitor.h deleted file mode 100644 index 6b90c186..00000000 --- a/src/db_visitor.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_VISITOR_H -#define MPD_DB_VISITOR_H - -struct directory; -struct song; -struct playlist_metadata; - -struct db_visitor { - /** - * Visit a directory. Optional method. - * - * @return true to continue the operation, false on error (set error_r) - */ - bool (*directory)(const struct directory *directory, void *ctx, - GError **error_r); - - /** - * Visit a song. Optional method. - * - * @return true to continue the operation, false on error (set error_r) - */ - bool (*song)(struct song *song, void *ctx, GError **error_r); - - /** - * Visit a playlist. Optional method. - * - * @param directory the directory the playlist resides in - * @return true to continue the operation, false on error (set error_r) - */ - bool (*playlist)(const struct playlist_metadata *playlist, - const struct directory *directory, void *ctx, - GError **error_r); -}; - -#endif diff --git a/src/decoder/AdPlugDecoderPlugin.cxx b/src/decoder/AdPlugDecoderPlugin.cxx new file mode 100644 index 00000000..6d08fab5 --- /dev/null +++ b/src/decoder/AdPlugDecoderPlugin.cxx @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "AdPlugDecoderPlugin.h" +#include "tag_handler.h" +#include "decoder_api.h" + +extern "C" { +#include "audio_check.h" +} + +#include <adplug/adplug.h> +#include <adplug/emuopl.h> + +#include <glib.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "adplug" + +static unsigned sample_rate; + +static bool +adplug_init(const struct config_param *param) +{ + GError *error = NULL; + + sample_rate = config_get_block_unsigned(param, "sample_rate", 48000); + if (!audio_check_sample_rate(sample_rate, &error)) { + g_warning("%s\n", error->message); + g_error_free(error); + return false; + } + + return true; +} + +static void +adplug_file_decode(struct decoder *decoder, const char *path_fs) +{ + CEmuopl opl(sample_rate, true, true); + opl.init(); + + CPlayer *player = CAdPlug::factory(path_fs, &opl); + if (player == nullptr) + return; + + struct audio_format audio_format; + audio_format_init(&audio_format, sample_rate, SAMPLE_FORMAT_S16, 2); + assert(audio_format_valid(&audio_format)); + + decoder_initialized(decoder, &audio_format, false, + player->songlength() / 1000.); + + int16_t buffer[2048]; + const unsigned frames_per_buffer = G_N_ELEMENTS(buffer) / 2; + enum decoder_command cmd; + + do { + if (!player->update()) + break; + + opl.update(buffer, frames_per_buffer); + cmd = decoder_data(decoder, NULL, + buffer, sizeof(buffer), + 0); + } while (cmd == DECODE_COMMAND_NONE); + + delete player; +} + +static void +adplug_scan_tag(enum tag_type type, const std::string &value, + const struct tag_handler *handler, void *handler_ctx) +{ + if (!value.empty()) + tag_handler_invoke_tag(handler, handler_ctx, + type, value.c_str()); +} + +static bool +adplug_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + CEmuopl opl(sample_rate, true, true); + opl.init(); + + CPlayer *player = CAdPlug::factory(path_fs, &opl); + if (player == nullptr) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, + player->songlength() / 1000); + + if (handler->tag != nullptr) { + adplug_scan_tag(TAG_TITLE, player->gettitle(), + handler, handler_ctx); + adplug_scan_tag(TAG_ARTIST, player->getauthor(), + handler, handler_ctx); + adplug_scan_tag(TAG_COMMENT, player->getdesc(), + handler, handler_ctx); + } + + delete player; + return true; +} + +static const char *const adplug_suffixes[] = { + "amd", + "d00", + "hsc", + "laa", + "rad", + "raw", + "sa2", + nullptr +}; + +const struct decoder_plugin adplug_decoder_plugin = { + "adplug", + adplug_init, + nullptr, + nullptr, + adplug_file_decode, + adplug_scan_file, + nullptr, + nullptr, + adplug_suffixes, + nullptr, +}; diff --git a/src/decoder/AdPlugDecoderPlugin.h b/src/decoder/AdPlugDecoderPlugin.h new file mode 100644 index 00000000..9fdf438a --- /dev/null +++ b/src/decoder/AdPlugDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_ADPLUG_H +#define MPD_DECODER_ADPLUG_H + +extern const struct decoder_plugin adplug_decoder_plugin; + +#endif diff --git a/src/decoder/_flac_common.c b/src/decoder/FLACCommon.cxx index d7f0c4a8..25fd1f96 100644 --- a/src/decoder/_flac_common.c +++ b/src/decoder/FLACCommon.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,40 +22,35 @@ */ #include "config.h" -#include "_flac_common.h" -#include "flac_metadata.h" -#include "flac_pcm.h" +#include "FLACCommon.hxx" +#include "FLACMetaData.hxx" +#include "FLAC_PCM.hxx" + +extern "C" { #include "audio_check.h" +} #include <glib.h> #include <assert.h> -void -flac_data_init(struct flac_data *data, struct decoder * decoder, - struct input_stream *input_stream) +flac_data::flac_data(struct decoder *_decoder, + struct input_stream *_input_stream) + :FLACInput(_input_stream, _decoder), + initialized(false), unsupported(false), + total_frames(0), first_frame(0), next_frame(0), position(0), + decoder(_decoder), input_stream(_input_stream), + tag(nullptr) { - pcm_buffer_init(&data->buffer); - - data->unsupported = false; - data->initialized = false; - data->total_frames = 0; - data->first_frame = 0; - data->next_frame = 0; - - data->position = 0; - data->decoder = decoder; - data->input_stream = input_stream; - data->tag = NULL; + pcm_buffer_init(&buffer); } -void -flac_data_deinit(struct flac_data *data) +flac_data::~flac_data() { - pcm_buffer_deinit(&data->buffer); + pcm_buffer_deinit(&buffer); - if (data->tag != NULL) - tag_free(data->tag); + if (tag != nullptr) + tag_free(tag); } static enum sample_format @@ -86,7 +81,7 @@ flac_got_stream_info(struct flac_data *data, if (data->initialized || data->unsupported) return; - GError *error = NULL; + GError *error = nullptr; if (!audio_format_init_checked(&data->audio_format, stream_info->sample_rate, flac_sample_format(stream_info->bits_per_sample), @@ -114,7 +109,6 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, struct replay_gain_info rgi; char *mixramp_start; char *mixramp_end; - float replay_gain_db = 0; switch (block->type) { case FLAC__METADATA_TYPE_STREAMINFO: @@ -123,14 +117,14 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, case FLAC__METADATA_TYPE_VORBIS_COMMENT: if (flac_parse_replay_gain(&rgi, block)) - replay_gain_db = decoder_replay_gain(data->decoder, &rgi); + decoder_replay_gain(data->decoder, &rgi); if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block)) - decoder_mixramp(data->decoder, replay_gain_db, + decoder_mixramp(data->decoder, mixramp_start, mixramp_end); - if (data->tag != NULL) - flac_vorbis_comments_to_tag(data->tag, NULL, + if (data->tag != nullptr) + flac_vorbis_comments_to_tag(data->tag, &block->data.vorbis_comment); default: @@ -138,15 +132,6 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, } } -void flac_error_common_cb(const FLAC__StreamDecoderErrorStatus status, - struct flac_data *data) -{ - if (decoder_get_command(data->decoder) == DECODE_COMMAND_STOP) - return; - - g_warning("%s", FLAC__StreamDecoderErrorStatusString[status]); -} - /** * This function attempts to call decoder_initialized() in case there * was no STREAMINFO block. This is allowed for nonseekable streams, @@ -160,7 +145,7 @@ flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) if (data->unsupported) return false; - GError *error = NULL; + GError *error = nullptr; if (!audio_format_init_checked(&data->audio_format, header->sample_rate, flac_sample_format(header->bits_per_sample), @@ -199,7 +184,7 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame, buffer = pcm_buffer_get(&data->buffer, buffer_size); flac_convert(buffer, frame->header.channels, - data->audio_format.format, buf, + (enum sample_format)data->audio_format.format, buf, 0, frame->header.blocksize); if (nbytes > 0) diff --git a/src/decoder/_flac_common.h b/src/decoder/FLACCommon.hxx index 0d90ba65..b80372bb 100644 --- a/src/decoder/_flac_common.h +++ b/src/decoder/FLACCommon.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,13 +21,15 @@ * Common data structures and functions used by FLAC and OggFLAC */ -#ifndef MPD_FLAC_COMMON_H -#define MPD_FLAC_COMMON_H +#ifndef MPD_FLAC_COMMON_HXX +#define MPD_FLAC_COMMON_HXX +#include "FLACInput.hxx" #include "decoder_api.h" -#include "pcm_buffer.h" -#include <glib.h> +extern "C" { +#include "pcm_buffer.h" +} #include <FLAC/stream_decoder.h> #include <FLAC/metadata.h> @@ -35,7 +37,7 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "flac" -struct flac_data { +struct flac_data : public FLACInput { struct pcm_buffer buffer; /** @@ -78,25 +80,19 @@ struct flac_data { FLAC__uint64 next_frame; FLAC__uint64 position; + struct decoder *decoder; struct input_stream *input_stream; - struct tag *tag; -}; -/* initializes a given FlacData struct */ -void -flac_data_init(struct flac_data *data, struct decoder * decoder, - struct input_stream *input_stream); + struct tag *tag; -void -flac_data_deinit(struct flac_data *data); + flac_data(struct decoder *decoder, struct input_stream *input_stream); + ~flac_data(); +}; void flac_metadata_common_cb(const FLAC__StreamMetadata * block, struct flac_data *data); -void flac_error_common_cb(FLAC__StreamDecoderErrorStatus status, - struct flac_data *data); - FLAC__StreamDecoderWriteStatus flac_common_write(struct flac_data *data, const FLAC__Frame * frame, const FLAC__int32 *const buf[], diff --git a/src/decoder/flac_decoder_plugin.c b/src/decoder/FLACDecoderPlugin.cxx index fb0b3502..43b60409 100644 --- a/src/decoder/flac_decoder_plugin.c +++ b/src/decoder/FLACDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,10 @@ */ #include "config.h" /* must be first for large file support */ -#include "_flac_common.h" -#include "flac_compat.h" -#include "flac_metadata.h" - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 -#include "_ogg_common.h" -#endif +#include "FLACDecoderPlugin.h" +#include "FLACCommon.hxx" +#include "FLACMetaData.hxx" +#include "OggCodec.hxx" #include <glib.h> @@ -34,114 +31,10 @@ #include <sys/stat.h> #include <sys/types.h> -/* this code was based on flac123, from flac-tools */ - -static FLAC__StreamDecoderReadStatus -flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__byte buf[], flac_read_status_size_t *bytes, - void *fdata) -{ - struct flac_data *data = fdata; - size_t r; - - r = decoder_read(data->decoder, data->input_stream, - (void *)buf, *bytes); - *bytes = r; - - if (r == 0) { - if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || - input_stream_lock_eof(data->input_stream)) - return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; - else - return FLAC__STREAM_DECODER_READ_STATUS_ABORT; - } - - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; -} - -static FLAC__StreamDecoderSeekStatus -flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (!data->input_stream->seekable) - return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; - - if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET, - NULL)) - return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; - - return FLAC__STREAM_DECODER_SEEK_STATUS_OK; -} - -static FLAC__StreamDecoderTellStatus -flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 * offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (!data->input_stream->seekable) - return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; - - *offset = (long)(data->input_stream->offset); - - return FLAC__STREAM_DECODER_TELL_STATUS_OK; -} - -static FLAC__StreamDecoderLengthStatus -flac_length_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 * length, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (data->input_stream->size < 0) - return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; - - *length = (size_t) (data->input_stream->size); - - return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; -} - -static FLAC__bool -flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && - decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || - input_stream_lock_eof(data->input_stream); -} - -static void -flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__StreamDecoderErrorStatus status, void *fdata) -{ - flac_error_common_cb(status, (struct flac_data *) fdata); -} - #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) -{ - switch (state) { - case FLAC__SEEKABLE_STREAM_DECODER_OK: - case FLAC__SEEKABLE_STREAM_DECODER_SEEKING: - case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: - return; - - case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: - case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: - case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: - break; - } +#error libFLAC is too old +#endif - g_warning("%s\n", FLAC__SeekableStreamDecoderStateString[state]); -} -#else /* FLAC_API_VERSION_CURRENT >= 7 */ static void flacPrintErroredState(FLAC__StreamDecoderState state) { switch (state) { @@ -162,7 +55,6 @@ static void flacPrintErroredState(FLAC__StreamDecoderState state) g_warning("%s\n", FLAC__StreamDecoderStateString[state]); } -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec, const FLAC__StreamMetadata * block, void *vdata) @@ -195,7 +87,30 @@ static bool flac_scan_file(const char *file, const struct tag_handler *handler, void *handler_ctx) { - return flac_scan_file2(file, NULL, handler, handler_ctx); + FLACMetadataChain chain; + if (!chain.Read(file)) { + g_debug("Failed to read FLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static bool +flac_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + FLACMetadataChain chain; + if (!chain.Read(is)) { + g_debug("Failed to read FLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; } /** @@ -205,15 +120,13 @@ static FLAC__StreamDecoder * flac_decoder_new(void) { FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); - if (sd == NULL) { + if (sd == nullptr) { g_warning("FLAC__stream_decoder_new() failed"); - return NULL; + return nullptr; } -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) g_debug("FLAC__stream_decoder_set_metadata_respond() has failed"); -#endif return sd; } @@ -259,7 +172,7 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, data->first_frame = t_start; while (true) { - if (data->tag != NULL && !tag_is_empty(data->tag)) { + if (data->tag != nullptr && !tag_is_empty(data->tag)) { cmd = decoder_tag(data->decoder, data->input_stream, data->tag); tag_free(data->tag); @@ -300,34 +213,30 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, static FLAC__StreamDecoderInitStatus stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) { -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 return FLAC__stream_decoder_init_ogg_stream(flac_dec, - flac_read_cb, - flac_seek_cb, - flac_tell_cb, - flac_length_cb, - flac_eof_cb, + FLACInput::Read, + FLACInput::Seek, + FLACInput::Tell, + FLACInput::Length, + FLACInput::Eof, flac_write_cb, flacMetadata, - flac_error_cb, + FLACInput::Error, data); -#else - (void)flac_dec; - (void)data; - - return FLAC__STREAM_DECODER_INIT_STATUS_ERROR; -#endif } static FLAC__StreamDecoderInitStatus stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) { return FLAC__stream_decoder_init_stream(flac_dec, - flac_read_cb, flac_seek_cb, - flac_tell_cb, flac_length_cb, - flac_eof_cb, flac_write_cb, + FLACInput::Read, + FLACInput::Seek, + FLACInput::Tell, + FLACInput::Length, + FLACInput::Eof, + flac_write_cb, flacMetadata, - flac_error_cb, + FLACInput::Error, data); } @@ -345,28 +254,23 @@ flac_decode_internal(struct decoder * decoder, bool is_ogg) { FLAC__StreamDecoder *flac_dec; - struct flac_data data; flac_dec = flac_decoder_new(); - if (flac_dec == NULL) + if (flac_dec == nullptr) return; - flac_data_init(&data, decoder, input_stream); + struct flac_data data(decoder, input_stream); data.tag = tag_new(); FLAC__StreamDecoderInitStatus status = stream_init(flac_dec, &data, is_ogg); if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { - flac_data_deinit(&data); FLAC__stream_decoder_delete(flac_dec); -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 g_warning("%s", FLAC__StreamDecoderInitStatusString[status]); -#endif return; } if (!flac_decoder_initialize(&data, flac_dec, 0)) { - flac_data_deinit(&data); FLAC__stream_decoder_finish(flac_dec); FLAC__stream_decoder_delete(flac_dec); return; @@ -374,8 +278,6 @@ flac_decode_internal(struct decoder * decoder, flac_decoder_loop(&data, flac_dec, 0, 0); - flac_data_deinit(&data); - FLAC__stream_decoder_finish(flac_dec); FLAC__stream_decoder_delete(flac_dec); } @@ -386,101 +288,96 @@ flac_decode(struct decoder * decoder, struct input_stream *input_stream) flac_decode_internal(decoder, input_stream, false); } -#ifndef HAVE_OGGFLAC - static bool oggflac_init(G_GNUC_UNUSED const struct config_param *param) { -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 return !!FLAC_API_SUPPORTS_OGG_FLAC; -#else - /* disable oggflac when libflac is too old */ - return false; -#endif } -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - static bool oggflac_scan_file(const char *file, const struct tag_handler *handler, void *handler_ctx) { - FLAC__Metadata_Iterator *it; - FLAC__StreamMetadata *block; - FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); - - if (!(FLAC__metadata_chain_read_ogg(chain, file))) { - FLAC__metadata_chain_delete(chain); + FLACMetadataChain chain; + if (!chain.ReadOgg(file)) { + g_debug("Failed to read OggFLAC tags: %s", + chain.GetStatusString()); return false; } - it = FLAC__metadata_iterator_new(); - FLAC__metadata_iterator_init(it, chain); - - do { - if (!(block = FLAC__metadata_iterator_get_block(it))) - break; + chain.Scan(handler, handler_ctx); + return true; +} - flac_scan_metadata(NULL, block, - handler, handler_ctx); - } while (FLAC__metadata_iterator_next(it)); - FLAC__metadata_iterator_delete(it); +static bool +oggflac_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + FLACMetadataChain chain; + if (!chain.ReadOgg(is)) { + g_debug("Failed to read OggFLAC tags: %s", + chain.GetStatusString()); + return false; + } - FLAC__metadata_chain_delete(chain); + chain.Scan(handler, handler_ctx); return true; } static void oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) { - if (ogg_stream_type_detect(input_stream) != FLAC) + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_FLAC) return; - /* rewind the stream, because ogg_stream_type_detect() has + /* rewind the stream, because ogg_codec_detect() has moved it */ - input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); + input_stream_lock_seek(input_stream, 0, SEEK_SET, nullptr); flac_decode_internal(decoder, input_stream, true); } -static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; +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", - NULL + nullptr }; -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - const struct decoder_plugin oggflac_decoder_plugin = { - .name = "oggflac", - .init = oggflac_init, -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - .stream_decode = oggflac_decode, - .scan_file = oggflac_scan_file, - .suffixes = oggflac_suffixes, - .mime_types = oggflac_mime_types -#endif + "oggflac", + oggflac_init, + nullptr, + oggflac_decode, + nullptr, + oggflac_scan_file, + oggflac_scan_stream, + nullptr, + oggflac_suffixes, + oggflac_mime_types, }; -#endif /* HAVE_OGGFLAC */ - -static const char *const flac_suffixes[] = { "flac", NULL }; +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", - NULL + nullptr }; const struct decoder_plugin flac_decoder_plugin = { - .name = "flac", - .stream_decode = flac_decode, - .scan_file = flac_scan_file, - .suffixes = flac_suffixes, - .mime_types = flac_mime_types, + "flac", + nullptr, + nullptr, + flac_decode, + nullptr, + flac_scan_file, + flac_scan_stream, + nullptr, + flac_suffixes, + flac_mime_types, }; diff --git a/src/decoder/FLACDecoderPlugin.h b/src/decoder/FLACDecoderPlugin.h new file mode 100644 index 00000000..c99deeef --- /dev/null +++ b/src/decoder/FLACDecoderPlugin.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_FLAC_H +#define MPD_DECODER_FLAC_H + +extern const struct decoder_plugin flac_decoder_plugin; +extern const struct decoder_plugin oggflac_decoder_plugin; + +#endif diff --git a/src/decoder/FLACIOHandle.cxx b/src/decoder/FLACIOHandle.cxx new file mode 100644 index 00000000..08ec36e4 --- /dev/null +++ b/src/decoder/FLACIOHandle.cxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FLACIOHandle.hxx" +#include "io_error.h" +#include "gcc.h" + +#include <errno.h> + +static size_t +FLACIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) +{ + input_stream *is = (input_stream *)handle; + + uint8_t *const p0 = (uint8_t *)ptr, *p = p0, + *const end = p0 + size * nmemb; + + /* libFLAC is very picky about short reads, and expects the IO + callback to fill the whole buffer (undocumented!) */ + + GError *error = nullptr; + while (p < end) { + size_t nbytes = input_stream_lock_read(is, p, end - p, &error); + if (nbytes == 0) { + if (error == nullptr) + /* end of file */ + break; + + if (error->domain == errno_quark()) + errno = error->code; + else + /* just some random non-zero + errno value */ + errno = EINVAL; + g_error_free(error); + return 0; + } + + p += nbytes; + } + + /* libFLAC expects a clean errno after returning from the IO + callbacks (undocumented!) */ + errno = 0; + return (p - p0) / size; +} + +static int +FLACIOSeek(FLAC__IOHandle handle, FLAC__int64 offset, int whence) +{ + input_stream *is = (input_stream *)handle; + + return input_stream_lock_seek(is, offset, whence, nullptr) ? 0 : -1; +} + +static FLAC__int64 +FLACIOTell(FLAC__IOHandle handle) +{ + input_stream *is = (input_stream *)handle; + + return is->offset; +} + +static int +FLACIOEof(FLAC__IOHandle handle) +{ + input_stream *is = (input_stream *)handle; + + return input_stream_lock_eof(is); +} + +static int +FLACIOClose(gcc_unused FLAC__IOHandle handle) +{ + /* no-op because the libFLAC caller is repsonsible for closing + the #input_stream */ + + return 0; +} + +const FLAC__IOCallbacks flac_io_callbacks = { + FLACIORead, + nullptr, + nullptr, + nullptr, + FLACIOEof, + FLACIOClose, +}; + +const FLAC__IOCallbacks flac_io_callbacks_seekable = { + FLACIORead, + nullptr, + FLACIOSeek, + FLACIOTell, + FLACIOEof, + FLACIOClose, +}; diff --git a/src/decoder/FLACIOHandle.hxx b/src/decoder/FLACIOHandle.hxx new file mode 100644 index 00000000..505d2db1 --- /dev/null +++ b/src/decoder/FLACIOHandle.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_IO_HANDLE_HXX +#define MPD_FLAC_IO_HANDLE_HXX + +#include "gcc.h" +#include "InputStream.hxx" + +#include <FLAC/callback.h> + +extern const FLAC__IOCallbacks flac_io_callbacks; +extern const FLAC__IOCallbacks flac_io_callbacks_seekable; + +static inline FLAC__IOHandle +ToFLACIOHandle(input_stream *is) +{ + return (FLAC__IOHandle)is; +} + +static inline const FLAC__IOCallbacks & +GetFLACIOCallbacks(const input_stream *is) +{ + return is->seekable + ? flac_io_callbacks_seekable + : flac_io_callbacks; +} + +#endif diff --git a/src/decoder/FLACInput.cxx b/src/decoder/FLACInput.cxx new file mode 100644 index 00000000..ba0a86ce --- /dev/null +++ b/src/decoder/FLACInput.cxx @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FLACInput.hxx" +#include "decoder_api.h" +#include "gcc.h" +#include "InputStream.hxx" + +FLAC__StreamDecoderReadStatus +FLACInput::Read(FLAC__byte buffer[], size_t *bytes) +{ + size_t r = decoder_read(decoder, input_stream, (void *)buffer, *bytes); + *bytes = r; + + if (r == 0) { + if (input_stream_lock_eof(input_stream) || + (decoder != nullptr && + decoder_get_command(decoder) != DECODE_COMMAND_NONE)) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + else + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } + + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + +FLAC__StreamDecoderSeekStatus +FLACInput::Seek(FLAC__uint64 absolute_byte_offset) +{ + if (!input_stream->seekable) + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; + + if (!input_stream_lock_seek(input_stream, + absolute_byte_offset, SEEK_SET, + nullptr)) + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; +} + +FLAC__StreamDecoderTellStatus +FLACInput::Tell(FLAC__uint64 *absolute_byte_offset) +{ + if (!input_stream->seekable) + return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; + + *absolute_byte_offset = (FLAC__uint64)input_stream->offset; + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +} + +FLAC__StreamDecoderLengthStatus +FLACInput::Length(FLAC__uint64 *stream_length) +{ + if (input_stream->size < 0) + return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; + + *stream_length = (FLAC__uint64)input_stream->size; + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; +} + +FLAC__bool +FLACInput::Eof() +{ + return (decoder != nullptr && + decoder_get_command(decoder) != DECODE_COMMAND_NONE && + decoder_get_command(decoder) != DECODE_COMMAND_SEEK) || + input_stream_lock_eof(input_stream); +} + +void +FLACInput::Error(FLAC__StreamDecoderErrorStatus status) +{ + if (decoder == nullptr || + decoder_get_command(decoder) != DECODE_COMMAND_STOP) + g_warning("%s", FLAC__StreamDecoderErrorStatusString[status]); +} + +FLAC__StreamDecoderReadStatus +FLACInput::Read(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__byte buffer[], size_t *bytes, + void *client_data) +{ + FLACInput *i = (FLACInput *)client_data; + + return i->Read(buffer, bytes); +} + +FLAC__StreamDecoderSeekStatus +FLACInput::Seek(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 absolute_byte_offset, void *client_data) +{ + FLACInput *i = (FLACInput *)client_data; + + return i->Seek(absolute_byte_offset); +} + +FLAC__StreamDecoderTellStatus +FLACInput::Tell(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + FLACInput *i = (FLACInput *)client_data; + + return i->Tell(absolute_byte_offset); +} + +FLAC__StreamDecoderLengthStatus +FLACInput::Length(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *stream_length, void *client_data) +{ + FLACInput *i = (FLACInput *)client_data; + + return i->Length(stream_length); +} + +FLAC__bool +FLACInput::Eof(gcc_unused const FLAC__StreamDecoder *flac_decoder, + void *client_data) +{ + FLACInput *i = (FLACInput *)client_data; + + return i->Eof(); +} + +void +FLACInput::Error(gcc_unused const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + FLACInput *i = (FLACInput *)client_data; + + i->Error(status); +} + diff --git a/src/decoder/FLACInput.hxx b/src/decoder/FLACInput.hxx new file mode 100644 index 00000000..7661567d --- /dev/null +++ b/src/decoder/FLACInput.hxx @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_INPUT_HXX +#define MPD_FLAC_INPUT_HXX + +#include <FLAC/stream_decoder.h> + +/** + * This class wraps an #input_stream in libFLAC stream decoder + * callbacks. + */ +class FLACInput { + struct decoder *decoder; + + struct input_stream *input_stream; + +public: + FLACInput(struct input_stream *_input_stream, + struct decoder *_decoder=nullptr) + :decoder(_decoder), input_stream(_input_stream) {} + +protected: + FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes); + FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset); + FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset); + FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length); + FLAC__bool Eof(); + void Error(FLAC__StreamDecoderErrorStatus status); + +public: + static FLAC__StreamDecoderReadStatus + Read(const FLAC__StreamDecoder *flac_decoder, + FLAC__byte buffer[], size_t *bytes, void *client_data); + + static FLAC__StreamDecoderSeekStatus + Seek(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 absolute_byte_offset, void *client_data); + + static FLAC__StreamDecoderTellStatus + Tell(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *absolute_byte_offset, void *client_data); + + static FLAC__StreamDecoderLengthStatus + Length(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *stream_length, void *client_data); + + static FLAC__bool + Eof(const FLAC__StreamDecoder *flac_decoder, void *client_data); + + static void + Error(const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, void *client_data); +}; + +#endif diff --git a/src/decoder/flac_metadata.c b/src/decoder/FLACMetaData.cxx index bd1eaf32..8273a230 100644 --- a/src/decoder/flac_metadata.c +++ b/src/decoder/FLACMetaData.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,16 +18,20 @@ */ #include "config.h" -#include "flac_metadata.h" -#include "replay_gain_info.h" +#include "FLACMetaData.hxx" + +extern "C" { +#include "XiphTags.h" +} + #include "tag.h" #include "tag_handler.h" #include "tag_table.h" +#include "replay_gain_info.h" #include <glib.h> #include <assert.h> -#include <stdbool.h> #include <stdlib.h> static bool @@ -91,7 +95,7 @@ flac_find_string_comment(const FLAC__StreamMetadata *block, int len; const unsigned char *p; - *str = NULL; + *str = nullptr; offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, cmnt); if (offset < 0) @@ -128,36 +132,21 @@ flac_parse_mixramp(char **mixramp_start, char **mixramp_end, */ static const char * flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, const char *char_tnum, size_t *length_r) + const char *name, size_t *length_r) { size_t name_length = strlen(name); - size_t char_tnum_length = 0; const char *comment = (const char*)entry->entry; if (entry->length <= name_length || g_ascii_strncasecmp(comment, name, name_length) != 0) - return NULL; - - if (char_tnum != NULL) { - char_tnum_length = strlen(char_tnum); - if (entry->length > name_length + char_tnum_length + 2 && - comment[name_length] == '[' && - g_ascii_strncasecmp(comment + name_length + 1, - char_tnum, char_tnum_length) == 0 && - comment[name_length + char_tnum_length + 1] == ']') - name_length = name_length + char_tnum_length + 2; - else if (entry->length > name_length + char_tnum_length && - g_ascii_strncasecmp(comment + name_length, - char_tnum, char_tnum_length) == 0) - name_length = name_length + char_tnum_length; - } + return nullptr; if (comment[name_length] == '=') { *length_r = entry->length - name_length - 1; return comment + name_length + 1; } - return NULL; + return nullptr; } /** @@ -167,14 +156,13 @@ flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, static bool flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, const char *name, enum tag_type tag_type, - const char *char_tnum, const struct tag_handler *handler, void *handler_ctx) { const char *value; size_t value_length; - value = flac_comment_value(entry, name, char_tnum, &value_length); - if (value != NULL) { + 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); @@ -184,23 +172,15 @@ flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, return false; } -static const struct tag_table flac_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - static void -flac_scan_comment(const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment_Entry *entry, +flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, const struct tag_handler *handler, void *handler_ctx) { - if (handler->pair != NULL) { + if (handler->pair != nullptr) { char *name = g_strdup((const char*)entry->entry); char *value = strchr(name, '='); - if (value != NULL && value > name) { + if (value != nullptr && value > name) { *value++ = 0; tag_handler_invoke_pair(handler, handler_ctx, name, value); @@ -209,36 +189,34 @@ flac_scan_comment(const char *char_tnum, g_free(name); } - for (const struct tag_table *i = flac_tags; i->name != NULL; ++i) - if (flac_copy_comment(entry, i->name, i->type, char_tnum, + 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], i, char_tnum, + tag_item_names[i], (enum tag_type)i, handler, handler_ctx)) return; } static void -flac_scan_comments(const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment *comment, +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(char_tnum, &comment->comments[i], + flac_scan_comment(&comment->comments[i], handler, handler_ctx); } void -flac_scan_metadata(const char *track, - const FLAC__StreamMetadata *block, +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(track, &block->data.vorbis_comment, + flac_scan_comments(&block->data.vorbis_comment, handler, handler_ctx); break; @@ -254,70 +232,22 @@ flac_scan_metadata(const char *track, } void -flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, +flac_vorbis_comments_to_tag(struct tag *tag, const FLAC__StreamMetadata_VorbisComment *comment) { - flac_scan_comments(char_tnum, comment, - &add_tag_handler, tag); + flac_scan_comments(comment, &add_tag_handler, tag); } -bool -flac_scan_file2(const char *file, const char *char_tnum, - const struct tag_handler *handler, void *handler_ctx) +void +FLACMetadataChain::Scan(const struct tag_handler *handler, void *handler_ctx) { - FLAC__Metadata_SimpleIterator *it; - FLAC__StreamMetadata *block = NULL; - - it = FLAC__metadata_simple_iterator_new(); - if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) { - const char *err; - FLAC_API FLAC__Metadata_SimpleIteratorStatus s; - - s = FLAC__metadata_simple_iterator_status(it); - - switch (s) { /* slightly more human-friendly messages: */ - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT: - err = "illegal input"; - break; - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE: - err = "error opening file"; - break; - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE: - err = "not a FLAC file"; - break; - default: - err = FLAC__Metadata_SimpleIteratorStatusString[s]; - } - g_debug("Reading '%s' metadata gave the following error: %s\n", - file, err); - FLAC__metadata_simple_iterator_delete(it); - return false; - } + FLACMetadataIterator iterator(*this); do { - block = FLAC__metadata_simple_iterator_get_block(it); - if (!block) + FLAC__StreamMetadata *block = iterator.GetBlock(); + if (block == nullptr) break; - flac_scan_metadata(char_tnum, block, handler, handler_ctx); - FLAC__metadata_object_delete(block); - } while (FLAC__metadata_simple_iterator_next(it)); - - FLAC__metadata_simple_iterator_delete(it); - - return true; -} - -struct tag * -flac_tag_load(const char *file, const char *char_tnum) -{ - struct tag *tag = tag_new(); - - if (!flac_scan_file2(file, char_tnum, &add_tag_handler, tag) || - tag_is_empty(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; + flac_scan_metadata(block, handler, handler_ctx); + } while (iterator.Next()); } diff --git a/src/decoder/FLACMetaData.hxx b/src/decoder/FLACMetaData.hxx new file mode 100644 index 00000000..0eceec23 --- /dev/null +++ b/src/decoder/FLACMetaData.hxx @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_METADATA_H +#define MPD_FLAC_METADATA_H + +#include "gcc.h" +#include "FLACIOHandle.hxx" + +#include <FLAC/metadata.h> + +#include <assert.h> + +class FLACMetadataChain { + FLAC__Metadata_Chain *chain; + +public: + FLACMetadataChain():chain(::FLAC__metadata_chain_new()) {} + + ~FLACMetadataChain() { + ::FLAC__metadata_chain_delete(chain); + } + + explicit operator FLAC__Metadata_Chain *() { + return chain; + } + + bool Read(const char *path) { + return ::FLAC__metadata_chain_read(chain, path); + } + + bool Read(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) { + return ::FLAC__metadata_chain_read_with_callbacks(chain, + handle, + callbacks); + } + + bool Read(input_stream *is) { + return Read(::ToFLACIOHandle(is), ::GetFLACIOCallbacks(is)); + } + + bool ReadOgg(const char *path) { + return ::FLAC__metadata_chain_read_ogg(chain, path); + } + + bool ReadOgg(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) { + return ::FLAC__metadata_chain_read_ogg_with_callbacks(chain, + handle, + callbacks); + } + + bool ReadOgg(input_stream *is) { + return ReadOgg(::ToFLACIOHandle(is), ::GetFLACIOCallbacks(is)); + } + + gcc_pure + FLAC__Metadata_ChainStatus GetStatus() const { + return ::FLAC__metadata_chain_status(chain); + } + + gcc_pure + const char *GetStatusString() const { + return FLAC__Metadata_ChainStatusString[GetStatus()]; + } + + void Scan(const struct tag_handler *handler, void *handler_ctx); +}; + +class FLACMetadataIterator { + FLAC__Metadata_Iterator *iterator; + +public: + FLACMetadataIterator():iterator(::FLAC__metadata_iterator_new()) {} + + FLACMetadataIterator(FLACMetadataChain &chain) + :iterator(::FLAC__metadata_iterator_new()) { + ::FLAC__metadata_iterator_init(iterator, + (FLAC__Metadata_Chain *)chain); + } + + ~FLACMetadataIterator() { + ::FLAC__metadata_iterator_delete(iterator); + } + + bool Next() { + return ::FLAC__metadata_iterator_next(iterator); + } + + gcc_pure + FLAC__StreamMetadata *GetBlock() { + return ::FLAC__metadata_iterator_get_block(iterator); + } +}; + +struct tag_handler; +struct tag; +struct replay_gain_info; + +static inline unsigned +flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info) +{ + assert(stream_info->sample_rate > 0); + + return (stream_info->total_samples + stream_info->sample_rate - 1) / + stream_info->sample_rate; +} + +bool +flac_parse_replay_gain(struct replay_gain_info *rgi, + const FLAC__StreamMetadata *block); + +bool +flac_parse_mixramp(char **mixramp_start, char **mixramp_end, + const FLAC__StreamMetadata *block); + +void +flac_vorbis_comments_to_tag(struct 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/flac_pcm.c b/src/decoder/FLAC_PCM.cxx index 6964d8ac..303530aa 100644 --- a/src/decoder/flac_pcm.c +++ b/src/decoder/FLAC_PCM.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "flac_pcm.h" +#include "FLAC_PCM.hxx" #include <assert.h> diff --git a/src/decoder/flac_pcm.h b/src/decoder/FLAC_PCM.hxx index a931998c..97d214c1 100644 --- a/src/decoder/flac_pcm.h +++ b/src/decoder/FLAC_PCM.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_FLAC_PCM_H -#define MPD_FLAC_PCM_H +#ifndef MPD_FLAC_PCM_HXX +#define MPD_FLAC_PCM_HXX #include "audio_format.h" diff --git a/src/decoder/ffmpeg_decoder_plugin.c b/src/decoder/FfmpegDecoderPlugin.cxx index 4c4cb2b8..dd98b968 100644 --- a/src/decoder/ffmpeg_decoder_plugin.c +++ b/src/decoder/FfmpegDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,19 @@ * 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 "decoder_api.h" -#include "audio_check.h" -#include "ffmpeg_metadata.h" +#include "FfmpegMetaData.hxx" #include "tag_handler.h" +#include "InputStream.hxx" + +extern "C" { +#include "audio_check.h" +} #include <glib.h> @@ -34,15 +42,15 @@ #include <sys/stat.h> #include <unistd.h> +extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavformat/avio.h> #include <libavutil/avutil.h> #include <libavutil/log.h> #include <libavutil/mathematics.h> -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) #include <libavutil/dict.h> -#endif +} #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "ffmpeg" @@ -82,18 +90,15 @@ struct mpd_ffmpeg_stream { struct decoder *decoder; struct input_stream *input; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0) AVIOContext *io; -#else - ByteIOContext *io; -#endif + unsigned char buffer[8192]; }; static int mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size) { - struct mpd_ffmpeg_stream *stream = opaque; + struct mpd_ffmpeg_stream *stream = (struct mpd_ffmpeg_stream *)opaque; return decoder_read(stream->decoder, stream->input, (void *)buf, size); @@ -102,7 +107,7 @@ mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size) static int64_t mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) { - struct mpd_ffmpeg_stream *stream = opaque; + struct mpd_ffmpeg_stream *stream = (struct mpd_ffmpeg_stream *)opaque; if (whence == AVSEEK_SIZE) return stream->input->size; @@ -119,19 +124,11 @@ mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input) struct mpd_ffmpeg_stream *stream = g_new(struct mpd_ffmpeg_stream, 1); stream->decoder = decoder; stream->input = input; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0) stream->io = avio_alloc_context(stream->buffer, sizeof(stream->buffer), false, stream, mpd_ffmpeg_stream_read, NULL, input->seekable ? mpd_ffmpeg_stream_seek : NULL); -#else - stream->io = av_alloc_put_byte(stream->buffer, sizeof(stream->buffer), - false, stream, - mpd_ffmpeg_stream_read, NULL, - input->seekable - ? mpd_ffmpeg_stream_seek : NULL); -#endif if (stream->io == NULL) { g_free(stream); return NULL; @@ -146,15 +143,10 @@ mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input) */ static int mpd_ffmpeg_open_input(AVFormatContext **ic_ptr, -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0) AVIOContext *pb, -#else - ByteIOContext *pb, -#endif const char *filename, AVInputFormat *fmt) { -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,1,3) AVFormatContext *context = avformat_alloc_context(); if (context == NULL) return AVERROR(ENOMEM); @@ -162,9 +154,6 @@ mpd_ffmpeg_open_input(AVFormatContext **ic_ptr, context->pb = pb; *ic_ptr = context; return avformat_open_input(ic_ptr, filename, fmt, NULL); -#else - return av_open_input_stream(ic_ptr, pb, filename, fmt, NULL); -#endif } static void @@ -188,11 +177,7 @@ ffmpeg_find_audio_stream(const AVFormatContext *format_context) { for (unsigned i = 0; i < format_context->nb_streams; ++i) if (format_context->streams[i]->codec->codec_type == -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 64, 0) AVMEDIA_TYPE_AUDIO) -#else - CODEC_TYPE_AUDIO) -#endif return i; return -1; @@ -291,12 +276,7 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, decoder_timestamp(decoder, time_from_ffmpeg(packet->pts, *time_base)); -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0) AVPacket packet2 = *packet; -#else - const uint8_t *packet_data = packet->data; - int packet_size = packet->size; -#endif #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0) uint8_t aligned_buffer[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16]; @@ -305,16 +285,11 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, /* libavcodec < 0.8 needs an aligned buffer */ uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16]; size_t buffer_size = sizeof(audio_buf); - int16_t *aligned_buffer = align16(audio_buf, &buffer_size); + int16_t *aligned_buffer = (int16_t *)align16(audio_buf, &buffer_size); #endif enum decoder_command cmd = DECODE_COMMAND_NONE; - while ( -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0) - packet2.size > 0 && -#else - packet_size > 0 && -#endif + while (packet2.size > 0 && cmd == DECODE_COMMAND_NONE) { int audio_size = buffer_size; #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0) @@ -332,14 +307,10 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, len = audio_size; } else if (len >= 0) len = -1; -#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0) +#else int len = avcodec_decode_audio3(codec_context, aligned_buffer, &audio_size, &packet2); -#else - int len = avcodec_decode_audio2(codec_context, - aligned_buffer, &audio_size, - packet_data, packet_size); #endif if (len < 0) { @@ -348,13 +319,8 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, break; } -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0) packet2.data += len; packet2.size -= len; -#else - packet_data += len; - packet_size -= len; -#endif if (audio_size <= 0) continue; @@ -366,33 +332,21 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, return cmd; } -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(52, 94, 1) -#define AVSampleFormat SampleFormat -#endif - G_GNUC_CONST static enum sample_format ffmpeg_sample_format(enum AVSampleFormat sample_fmt) { switch (sample_fmt) { -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1) case AV_SAMPLE_FMT_S16: #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0) case AV_SAMPLE_FMT_S16P: #endif -#else - case SAMPLE_FMT_S16: -#endif return SAMPLE_FORMAT_S16; -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1) case AV_SAMPLE_FMT_S32: #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0) case AV_SAMPLE_FMT_S32P: #endif -#else - case SAMPLE_FMT_S32: -#endif return SAMPLE_FORMAT_S32; #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0) @@ -404,7 +358,6 @@ ffmpeg_sample_format(enum AVSampleFormat sample_fmt) break; } -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1) char buffer[64]; const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer), sample_fmt); @@ -412,7 +365,6 @@ ffmpeg_sample_format(enum AVSampleFormat sample_fmt) g_warning("Unsupported libavcodec SampleFormat value: %s (%d)", name, sample_fmt); else -#endif g_warning("Unsupported libavcodec SampleFormat value: %d", sample_fmt); return SAMPLE_FORMAT_UNDEFINED; @@ -426,7 +378,7 @@ ffmpeg_probe(struct decoder *decoder, struct input_stream *is) PADDING = 16, }; - unsigned char *buffer = g_malloc(BUFFER_SIZE); + unsigned char *buffer = (unsigned char *)g_malloc(BUFFER_SIZE); size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); if (nbytes <= PADDING || !input_stream_lock_seek(is, 0, SEEK_SET, NULL)) { @@ -440,11 +392,10 @@ ffmpeg_probe(struct decoder *decoder, struct input_stream *is) size */ nbytes -= PADDING; - AVProbeData avpd = { - .buf = buffer, - .buf_size = nbytes, - .filename = is->uri, - }; + AVProbeData avpd; + avpd.buf = buffer; + avpd.buf_size = nbytes; + avpd.filename = is->uri.c_str(); AVInputFormat *format = av_probe_input_format(&avpd, true); g_free(buffer); @@ -471,7 +422,8 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input) //ffmpeg works with ours "fileops" helper AVFormatContext *format_context = NULL; - if (mpd_ffmpeg_open_input(&format_context, stream->io, input->uri, + if (mpd_ffmpeg_open_input(&format_context, stream->io, + input->uri.c_str(), input_format) != 0) { g_warning("Open failed\n"); mpd_ffmpeg_stream_close(stream); @@ -630,7 +582,7 @@ ffmpeg_scan_stream(struct input_stream *is, return false; AVFormatContext *f = NULL; - if (mpd_ffmpeg_open_input(&f, stream->io, is->uri, + if (mpd_ffmpeg_open_input(&f, stream->io, is->uri.c_str(), input_format) != 0) { mpd_ffmpeg_stream_close(stream); return false; @@ -656,10 +608,6 @@ ffmpeg_scan_stream(struct input_stream *is, tag_handler_invoke_duration(handler, handler_ctx, f->duration / AV_TIME_BASE); -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,101,0) - av_metadata_conv(f, NULL, f->iformat->metadata_conv); -#endif - ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx); int idx = ffmpeg_find_audio_stream(f); if (idx >= 0) @@ -791,10 +739,14 @@ static const char *const ffmpeg_mime_types[] = { }; const struct decoder_plugin ffmpeg_decoder_plugin = { - .name = "ffmpeg", - .init = ffmpeg_init, - .stream_decode = ffmpeg_decode, - .scan_stream = ffmpeg_scan_stream, - .suffixes = ffmpeg_suffixes, - .mime_types = ffmpeg_mime_types + "ffmpeg", + ffmpeg_init, + nullptr, + ffmpeg_decode, + nullptr, + nullptr, + ffmpeg_scan_stream, + nullptr, + ffmpeg_suffixes, + ffmpeg_mime_types }; diff --git a/src/decoder/FfmpegDecoderPlugin.hxx b/src/decoder/FfmpegDecoderPlugin.hxx new file mode 100644 index 00000000..9a637fff --- /dev/null +++ b/src/decoder/FfmpegDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_FFMPEG_HXX +#define MPD_DECODER_FFMPEG_HXX + +extern const struct decoder_plugin ffmpeg_decoder_plugin; + +#endif diff --git a/src/decoder/ffmpeg_metadata.c b/src/decoder/FfmpegMetaData.cxx index 3ef774f6..2d7ebbca 100644 --- a/src/decoder/ffmpeg_metadata.c +++ b/src/decoder/FfmpegMetaData.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,11 @@ * 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 "ffmpeg_metadata.h" +#include "FfmpegMetaData.hxx" #include "tag_table.h" #include "tag_handler.h" @@ -26,9 +29,6 @@ #define G_LOG_DOMAIN "ffmpeg" static const struct tag_table ffmpeg_tags[] = { -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,50,0) - { "author", TAG_ARTIST }, -#endif { "year", TAG_DATE }, { "author-sort", TAG_ARTIST_SORT }, { "album_artist", TAG_ALBUM_ARTIST }, @@ -50,8 +50,6 @@ ffmpeg_copy_metadata(enum tag_type type, type, mt->value); } -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) - static void ffmpeg_scan_pairs(AVDictionary *dict, const struct tag_handler *handler, void *handler_ctx) @@ -63,14 +61,12 @@ ffmpeg_scan_pairs(AVDictionary *dict, i->key, i->value); } -#endif - void ffmpeg_scan_dictionary(AVDictionary *dict, const struct tag_handler *handler, void *handler_ctx) { for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - ffmpeg_copy_metadata(i, dict, tag_item_names[i], + ffmpeg_copy_metadata(tag_type(i), dict, tag_item_names[i], handler, handler_ctx); for (const struct tag_table *i = ffmpeg_tags; @@ -78,8 +74,6 @@ ffmpeg_scan_dictionary(AVDictionary *dict, ffmpeg_copy_metadata(i->type, dict, i->name, handler, handler_ctx); -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) if (handler->pair != NULL) ffmpeg_scan_pairs(dict, handler, handler_ctx); -#endif } diff --git a/src/decoder/ffmpeg_metadata.h b/src/decoder/FfmpegMetaData.hxx index 60658f47..466d2cb1 100644 --- a/src/decoder/ffmpeg_metadata.h +++ b/src/decoder/FfmpegMetaData.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,20 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_FFMPEG_METADATA_H -#define MPD_FFMPEG_METADATA_H +#ifndef MPD_FFMPEG_METADATA_HXX +#define MPD_FFMPEG_METADATA_HXX +extern "C" { #include <libavformat/avformat.h> #include <libavutil/avutil.h> -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) #include <libavutil/dict.h> -#endif - -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53,1,0) -#define AVDictionary AVMetadata -#define AVDictionaryEntry AVMetadataTag -#define av_dict_get av_metadata_get -#endif +} struct tag_handler; diff --git a/src/decoder/_ogg_common.c b/src/decoder/OggCodec.cxx index 09d2712d..5ad9c69d 100644 --- a/src/decoder/_ogg_common.c +++ b/src/decoder/OggCodec.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,25 +22,27 @@ */ #include "config.h" -#include "_ogg_common.h" +#include "OggCodec.hxx" -ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream) +enum ogg_codec +ogg_codec_detect(struct decoder *decoder, struct input_stream *is) { /* oggflac detection based on code in ogg123 and this post * http://lists.xiph.org/pipermail/flac/2004-December/000393.html * ogg123 trunk still doesn't have this patch as of June 2005 */ unsigned char buf[41]; - size_t r; - - r = decoder_read(NULL, inStream, buf, sizeof(buf)); + size_t r = decoder_read(decoder, is, buf, sizeof(buf)); if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0) - return VORBIS; + 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 FLAC; + return OGG_CODEC_FLAC; + + if (memcmp(buf + 28, "Opus", 4) == 0) + return OGG_CODEC_OPUS; - return VORBIS; + return OGG_CODEC_VORBIS; } diff --git a/src/decoder/_ogg_common.h b/src/decoder/OggCodec.hxx index 85e4ebba..e241560f 100644 --- a/src/decoder/_ogg_common.h +++ b/src/decoder/OggCodec.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,13 +21,19 @@ * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) */ -#ifndef MPD_OGG_COMMON_H -#define MPD_OGG_COMMON_H +#ifndef MPD_OGG_CODEC_HXX +#define MPD_OGG_CODEC_HXX #include "decoder_api.h" -typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type; +enum ogg_codec { + OGG_CODEC_UNKNOWN, + OGG_CODEC_VORBIS, + OGG_CODEC_FLAC, + OGG_CODEC_OPUS, +}; -ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream); +enum ogg_codec +ogg_codec_detect(struct decoder *decoder, struct input_stream *is); #endif /* _OGG_COMMON_H */ diff --git a/src/decoder/OggFind.cxx b/src/decoder/OggFind.cxx new file mode 100644 index 00000000..9df4c645 --- /dev/null +++ b/src/decoder/OggFind.cxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OggFind.hxx" +#include "OggSyncState.hxx" + +bool +OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet) +{ + while (true) { + int r = ogg_stream_packetout(&os, &packet); + if (r == 0) { + if (!oy.ExpectPageIn(os)) + return false; + + continue; + } else if (r > 0 && packet.e_o_s) + return true; + } +} diff --git a/src/decoder/OggFind.hxx b/src/decoder/OggFind.hxx new file mode 100644 index 00000000..7d18d206 --- /dev/null +++ b/src/decoder/OggFind.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OGG_FIND_HXX +#define MPD_OGG_FIND_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +class OggSyncState; + +/** + * Skip all pages/packets until an end-of-stream (EOS) packet for the + * specified stream is found. + * + * @return true if the EOS packet was found + */ +bool +OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet); + +#endif diff --git a/src/decoder/OggSyncState.hxx b/src/decoder/OggSyncState.hxx new file mode 100644 index 00000000..eaeb9bd8 --- /dev/null +++ b/src/decoder/OggSyncState.hxx @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OGG_SYNC_STATE_HXX +#define MPD_OGG_SYNC_STATE_HXX + +#include "check.h" +#include "OggUtil.hxx" + +#include <ogg/ogg.h> + +#include <stddef.h> + +/** + * Wrapper for an ogg_sync_state. + */ +class OggSyncState { + ogg_sync_state oy; + + input_stream &is; + struct decoder *const decoder; + +public: + OggSyncState(input_stream &_is, struct decoder *const _decoder=nullptr) + :is(_is), decoder(_decoder) { + ogg_sync_init(&oy); + } + + ~OggSyncState() { + ogg_sync_clear(&oy); + } + + void Reset() { + ogg_sync_reset(&oy); + } + + bool Feed(size_t size) { + return OggFeed(oy, decoder, &is, size); + } + + bool ExpectPage(ogg_page &page) { + return OggExpectPage(oy, page, decoder, &is); + } + + bool ExpectFirstPage(ogg_stream_state &os) { + return OggExpectFirstPage(oy, os, decoder, &is); + } + + bool ExpectPageIn(ogg_stream_state &os) { + return OggExpectPageIn(oy, os, decoder, &is); + } + + bool ExpectPageSeek(ogg_page &page) { + return OggExpectPageSeek(oy, page, decoder, &is); + } + + bool ExpectPageSeekIn(ogg_stream_state &os) { + return OggExpectPageSeekIn(oy, os, decoder, &is); + } +}; + +#endif diff --git a/src/decoder/OggUtil.cxx b/src/decoder/OggUtil.cxx new file mode 100644 index 00000000..a1125a2c --- /dev/null +++ b/src/decoder/OggUtil.cxx @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OggUtil.hxx" +#include "decoder_api.h" + +bool +OggFeed(ogg_sync_state &oy, struct decoder *decoder, + input_stream *input_stream, size_t size) +{ + char *buffer = ogg_sync_buffer(&oy, size); + if (buffer == nullptr) + return false; + + size_t nbytes = decoder_read(decoder, input_stream, + buffer, size); + if (nbytes == 0) + return false; + + ogg_sync_wrote(&oy, nbytes); + return true; +} + +bool +OggExpectPage(ogg_sync_state &oy, ogg_page &page, + decoder *decoder, input_stream *input_stream) +{ + while (true) { + int r = ogg_sync_pageout(&oy, &page); + if (r != 0) + return r > 0; + + if (!OggFeed(oy, decoder, input_stream, 1024)) + return false; + } +} + +bool +OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is) +{ + ogg_page page; + if (!OggExpectPage(oy, page, decoder, is)) + return false; + + ogg_stream_init(&os, ogg_page_serialno(&page)); + ogg_stream_pagein(&os, &page); + return true; +} + +bool +OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is) +{ + ogg_page page; + if (!OggExpectPage(oy, page, decoder, is)) + return false; + + ogg_stream_pagein(&os, &page); + return true; +} + +bool +OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, + decoder *decoder, input_stream *input_stream) +{ + size_t remaining_skipped = 16384; + + while (true) { + int r = ogg_sync_pageseek(&oy, &page); + if (r > 0) + return true; + + if (r < 0) { + /* skipped -r bytes */ + size_t nbytes = -r; + if (nbytes > remaining_skipped) + /* still no ogg page - we lost our + patience, abort */ + return false; + + remaining_skipped -= nbytes; + continue; + } + + if (!OggFeed(oy, decoder, input_stream, 1024)) + return false; + } +} + +bool +OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is) +{ + ogg_page page; + if (!OggExpectPageSeek(oy, page, decoder, is)) + return false; + + ogg_stream_pagein(&os, &page); + return true; +} diff --git a/src/decoder/OggUtil.hxx b/src/decoder/OggUtil.hxx new file mode 100644 index 00000000..32479781 --- /dev/null +++ b/src/decoder/OggUtil.hxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OGG_UTIL_HXX +#define MPD_OGG_UTIL_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +#include <stddef.h> + +struct input_stream; +struct decoder; + +/** + * Feed data from the #input_stream into the #ogg_sync_state. + * + * @return false on error or end-of-file + */ +bool +OggFeed(ogg_sync_state &oy, struct decoder *decoder, input_stream *is, + size_t size); + +/** + * Feed into the #ogg_sync_state until a page gets available. Garbage + * data at the beginning is considered a fatal error. + * + * @return true if a page is available + */ +bool +OggExpectPage(ogg_sync_state &oy, ogg_page &page, + decoder *decoder, input_stream *input_stream); + +/** + * Combines OggExpectPage(), ogg_stream_init() and + * ogg_stream_pagein(). + * + * @return true if the stream was initialized and the first page was + * delivered to it + */ +bool +OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is); + +/** + * Combines OggExpectPage() and ogg_stream_pagein(). + * + * @return true if a page was delivered to the stream + */ +bool +OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is); + +/** + * Like OggExpectPage(), but allow skipping garbage (after seeking). + */ +bool +OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, + decoder *decoder, input_stream *input_stream); + +/** + * Combines OggExpectPageSeek() and ogg_stream_pagein(). + * + * @return true if a page was delivered to the stream + */ +bool +OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is); + +#endif diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx new file mode 100644 index 00000000..3e3a1e4e --- /dev/null +++ b/src/decoder/OpusDecoderPlugin.cxx @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "OpusDecoderPlugin.h" +#include "OpusHead.hxx" +#include "OpusTags.hxx" +#include "OggUtil.hxx" +#include "OggFind.hxx" +#include "OggSyncState.hxx" +#include "decoder_api.h" +#include "OggCodec.hxx" +#include "audio_check.h" +#include "tag_handler.h" +#include "InputStream.hxx" + +#include <opus.h> +#include <ogg/ogg.h> + +#include <glib.h> + +#include <stdio.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "opus" + +static const opus_int32 opus_sample_rate = 48000; + +gcc_pure +static bool +IsOpusHead(const ogg_packet &packet) +{ + return packet.bytes >= 8 && memcmp(packet.packet, "OpusHead", 8) == 0; +} + +gcc_pure +static bool +IsOpusTags(const ogg_packet &packet) +{ + return packet.bytes >= 8 && memcmp(packet.packet, "OpusTags", 8) == 0; +} + +static bool +mpd_opus_init(G_GNUC_UNUSED const struct config_param *param) +{ + g_debug("%s", opus_get_version_string()); + + return true; +} + +class MPDOpusDecoder { + struct decoder *decoder; + struct input_stream *input_stream; + + ogg_stream_state os; + + OpusDecoder *opus_decoder; + opus_int16 *output_buffer; + unsigned output_size; + + bool os_initialized; + bool found_opus; + + int opus_serialno; + + size_t frame_size; + +public: + MPDOpusDecoder(struct decoder *_decoder, + struct input_stream *_input_stream) + :decoder(_decoder), input_stream(_input_stream), + opus_decoder(nullptr), + output_buffer(nullptr), output_size(0), + os_initialized(false), found_opus(false) {} + ~MPDOpusDecoder(); + + bool ReadFirstPage(OggSyncState &oy); + bool ReadNextPage(OggSyncState &oy); + + enum decoder_command HandlePackets(); + enum decoder_command HandlePacket(const ogg_packet &packet); + enum decoder_command HandleBOS(const ogg_packet &packet); + enum decoder_command HandleTags(const ogg_packet &packet); + enum decoder_command HandleAudio(const ogg_packet &packet); +}; + +MPDOpusDecoder::~MPDOpusDecoder() +{ + g_free(output_buffer); + + if (opus_decoder != nullptr) + opus_decoder_destroy(opus_decoder); + + if (os_initialized) + ogg_stream_clear(&os); +} + +inline bool +MPDOpusDecoder::ReadFirstPage(OggSyncState &oy) +{ + assert(!os_initialized); + + if (!oy.ExpectFirstPage(os)) + return false; + + os_initialized = true; + return true; +} + +inline bool +MPDOpusDecoder::ReadNextPage(OggSyncState &oy) +{ + assert(os_initialized); + + ogg_page page; + if (!oy.ExpectPage(page)) + return false; + + const auto page_serialno = ogg_page_serialno(&page); + if (page_serialno != os.serialno) + ogg_stream_reset_serialno(&os, page_serialno); + + ogg_stream_pagein(&os, &page); + return true; +} + +inline enum decoder_command +MPDOpusDecoder::HandlePackets() +{ + ogg_packet packet; + while (ogg_stream_packetout(&os, &packet) == 1) { + enum decoder_command cmd = HandlePacket(packet); + if (cmd != DECODE_COMMAND_NONE) + return cmd; + } + + return DECODE_COMMAND_NONE; +} + +inline enum decoder_command +MPDOpusDecoder::HandlePacket(const ogg_packet &packet) +{ + if (packet.e_o_s) + return DECODE_COMMAND_STOP; + + if (packet.b_o_s) + return HandleBOS(packet); + else if (!found_opus) + return DECODE_COMMAND_STOP; + + if (IsOpusTags(packet)) + return HandleTags(packet); + + return HandleAudio(packet); +} + +inline enum decoder_command +MPDOpusDecoder::HandleBOS(const ogg_packet &packet) +{ + assert(packet.b_o_s); + + if (found_opus || !IsOpusHead(packet)) + return DECODE_COMMAND_STOP; + + unsigned channels; + if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || + !audio_valid_channel_count(channels)) + return DECODE_COMMAND_STOP; + + assert(opus_decoder == nullptr); + assert(output_buffer == nullptr); + + opus_serialno = os.serialno; + found_opus = true; + + /* TODO: parse attributes from the OpusHead (sample rate, + channels, ...) */ + + int opus_error; + opus_decoder = opus_decoder_create(opus_sample_rate, channels, + &opus_error); + if (opus_decoder == nullptr) { + g_warning("libopus error: %s", + opus_strerror(opus_error)); + return DECODE_COMMAND_STOP; + } + + struct audio_format audio_format; + audio_format_init(&audio_format, opus_sample_rate, + SAMPLE_FORMAT_S16, channels); + decoder_initialized(decoder, &audio_format, false, -1); + frame_size = audio_format_frame_size(&audio_format); + + /* allocate an output buffer for 16 bit PCM samples big enough + to hold a quarter second, larger than 120ms required by + libopus */ + output_size = audio_format.sample_rate / 4; + output_buffer = (opus_int16 *) + g_malloc(sizeof(*output_buffer) * output_size * + audio_format.channels); + + return decoder_get_command(decoder); +} + +inline enum decoder_command +MPDOpusDecoder::HandleTags(const ogg_packet &packet) +{ + struct tag *tag = tag_new(); + + enum decoder_command cmd; + if (ScanOpusTags(packet.packet, packet.bytes, &add_tag_handler, tag) && + !tag_is_empty(tag)) + cmd = decoder_tag(decoder, input_stream, tag); + else + cmd = decoder_get_command(decoder); + + tag_free(tag); + return cmd; +} + +inline enum decoder_command +MPDOpusDecoder::HandleAudio(const ogg_packet &packet) +{ + assert(opus_decoder != nullptr); + + int nframes = opus_decode(opus_decoder, + (const unsigned char*)packet.packet, + packet.bytes, + output_buffer, output_size, + 0); + if (nframes < 0) { + g_warning("%s", opus_strerror(nframes)); + return DECODE_COMMAND_STOP; + } + + if (nframes > 0) { + const size_t nbytes = nframes * frame_size; + enum decoder_command cmd = + decoder_data(decoder, input_stream, + output_buffer, nbytes, + 0); + if (cmd != DECODE_COMMAND_NONE) + return cmd; + } + + return DECODE_COMMAND_NONE; +} + +static void +mpd_opus_stream_decode(struct decoder *decoder, + struct input_stream *input_stream) +{ + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_OPUS) + return; + + /* rewind the stream, because ogg_codec_detect() has + moved it */ + input_stream_lock_seek(input_stream, 0, SEEK_SET, nullptr); + + MPDOpusDecoder d(decoder, input_stream); + OggSyncState oy(*input_stream, decoder); + + if (!d.ReadFirstPage(oy)) + return; + + while (true) { + enum decoder_command cmd = d.HandlePackets(); + if (cmd != DECODE_COMMAND_NONE) + break; + + if (!d.ReadNextPage(oy)) + break; + + } +} + +static bool +SeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, + input_stream *is) +{ + if (is->size > 0 && is->size - is->offset < 65536) + return OggFindEOS(oy, os, packet); + + if (!input_stream_cheap_seeking(is)) + return false; + + oy.Reset(); + + return input_stream_lock_seek(is, -65536, SEEK_END, nullptr) && + oy.ExpectPageSeekIn(os) && + OggFindEOS(oy, os, packet); +} + +static bool +mpd_opus_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + OggSyncState oy(*is); + + ogg_stream_state os; + if (!oy.ExpectFirstPage(os)) + return false; + + /* read at most two more pages */ + unsigned remaining_pages = 2; + + bool result = false; + + ogg_packet packet; + while (true) { + int r = ogg_stream_packetout(&os, &packet); + if (r < 0) { + result = false; + break; + } + + if (r == 0) { + if (remaining_pages-- == 0) + break; + + if (!oy.ExpectPageIn(os)) { + result = false; + break; + } + + continue; + } + + if (packet.b_o_s) { + if (!IsOpusHead(packet)) + break; + + unsigned channels; + if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || + !audio_valid_channel_count(channels)) { + result = false; + break; + } + + result = true; + } else if (!result) + break; + else if (IsOpusTags(packet)) { + if (!ScanOpusTags(packet.packet, packet.bytes, + handler, handler_ctx)) + result = false; + + break; + } + } + + if (packet.e_o_s || SeekFindEOS(oy, os, packet, is)) + tag_handler_invoke_duration(handler, handler_ctx, + packet.granulepos / opus_sample_rate); + + ogg_stream_clear(&os); + + return result; +} + +static const char *const opus_suffixes[] = { + "opus", + "ogg", + "oga", + nullptr +}; + +static const char *const opus_mime_types[] = { + "audio/opus", + nullptr +}; + +const struct decoder_plugin opus_decoder_plugin = { + "opus", + mpd_opus_init, + nullptr, + mpd_opus_stream_decode, + nullptr, + nullptr, + mpd_opus_scan_stream, + nullptr, + opus_suffixes, + opus_mime_types, +}; diff --git a/src/decoder/OpusDecoderPlugin.h b/src/decoder/OpusDecoderPlugin.h new file mode 100644 index 00000000..c95d6ded --- /dev/null +++ b/src/decoder/OpusDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_OPUS_H +#define MPD_DECODER_OPUS_H + +extern const struct decoder_plugin opus_decoder_plugin; + +#endif diff --git a/src/decoder/OpusHead.cxx b/src/decoder/OpusHead.cxx new file mode 100644 index 00000000..c57e08e1 --- /dev/null +++ b/src/decoder/OpusHead.cxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OpusHead.hxx" + +#include <stdint.h> +#include <string.h> + +struct OpusHead { + char signature[8]; + uint8_t version, channels; + uint16_t pre_skip; + uint32_t sample_rate; + uint16_t output_gain; + uint8_t channel_mapping; +}; + +bool +ScanOpusHeader(const void *data, size_t size, unsigned &channels_r) +{ + const OpusHead *h = (const OpusHead *)data; + if (size < 19 || (h->version & 0xf0) != 0) + return false; + + channels_r = h->channels; + return true; +} diff --git a/src/decoder/OpusHead.hxx b/src/decoder/OpusHead.hxx new file mode 100644 index 00000000..9f75c4f7 --- /dev/null +++ b/src/decoder/OpusHead.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_HEAD_HXX +#define MPD_OPUS_HEAD_HXX + +#include "check.h" + +#include <stddef.h> + +bool +ScanOpusHeader(const void *data, size_t size, unsigned &channels_r); + +#endif diff --git a/src/decoder/OpusReader.hxx b/src/decoder/OpusReader.hxx new file mode 100644 index 00000000..1fd07b55 --- /dev/null +++ b/src/decoder/OpusReader.hxx @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_READER_HXX +#define MPD_OPUS_READER_HXX + +#include "check.h" +#include "string_util.h" + +#include <stdint.h> +#include <string.h> + +class OpusReader { + const uint8_t *p, *const end; + +public: + OpusReader(const void *_p, size_t size) + :p((const uint8_t *)_p), end(p + size) {} + + bool Skip(size_t length) { + p += length; + return p <= end; + } + + const void *Read(size_t length) { + const uint8_t *result = p; + return Skip(length) + ? result + : nullptr; + } + + bool Expect(const void *value, size_t length) { + const void *data = Read(length); + return data != nullptr && memcmp(value, data, length) == 0; + } + + bool ReadByte(uint8_t &value_r) { + if (p >= end) + return false; + + value_r = *p++; + return true; + } + + bool ReadShort(uint16_t &value_r) { + const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); + if (value == nullptr) + return false; + + value_r = value[0] | (value[1] << 8); + return true; + } + + bool ReadWord(uint32_t &value_r) { + const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); + if (value == nullptr) + return false; + + value_r = value[0] | (value[1] << 8) + | (value[2] << 16) | (value[3] << 24); + return true; + } + + bool SkipString() { + uint32_t length; + return ReadWord(length) && Skip(length); + } + + char *ReadString() { + uint32_t length; + if (!ReadWord(length)) + return nullptr; + + const char *src = (const char *)Read(length); + if (src == nullptr) + return nullptr; + + return strndup(src, length); + } +}; + +#endif diff --git a/src/decoder/OpusTags.cxx b/src/decoder/OpusTags.cxx new file mode 100644 index 00000000..cb35a624 --- /dev/null +++ b/src/decoder/OpusTags.cxx @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OpusTags.hxx" +#include "OpusReader.hxx" +#include "XiphTags.h" +#include "tag_handler.h" + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +static void +ScanOneOpusTag(const char *name, const char *value, + const struct tag_handler *handler, void *ctx) +{ + tag_handler_invoke_pair(handler, ctx, name, value); + + if (handler->tag != nullptr) { + enum tag_type t = tag_table_lookup_i(xiph_tags, name); + if (t != TAG_NUM_OF_ITEM_TYPES) + tag_handler_invoke_tag(handler, ctx, t, value); + } +} + +bool +ScanOpusTags(const void *data, size_t size, + const struct tag_handler *handler, void *ctx) +{ + OpusReader r(data, size); + if (!r.Expect("OpusTags", 8)) + return false; + + if (handler->pair == nullptr && handler->tag == nullptr) + return true; + + if (!r.SkipString()) + return false; + + uint32_t n; + if (!r.ReadWord(n)) + return false; + + while (n-- > 0) { + char *p = r.ReadString(); + if (p == nullptr) + return false; + + char *eq = strchr(p, '='); + if (eq != nullptr && eq > p) { + *eq = 0; + + ScanOneOpusTag(p, eq + 1, handler, ctx); + } + + free(p); + } + + return true; +} diff --git a/src/decoder/OpusTags.hxx b/src/decoder/OpusTags.hxx new file mode 100644 index 00000000..2f3eec84 --- /dev/null +++ b/src/decoder/OpusTags.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_TAGS_HXX +#define MPD_OPUS_TAGS_HXX + +#include "check.h" + +#include <stddef.h> + +bool +ScanOpusTags(const void *data, size_t size, + const struct tag_handler *handler, void *ctx); + +#endif diff --git a/src/decoder/vorbis_comments.c b/src/decoder/VorbisComments.cxx index 6c2d57b7..10fe2236 100644 --- a/src/decoder/vorbis_comments.c +++ b/src/decoder/VorbisComments.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,8 @@ */ #include "config.h" -#include "vorbis_comments.h" +#include "VorbisComments.hxx" +#include "XiphTags.h" #include "tag.h" #include "tag_table.h" #include "tag_handler.h" @@ -95,13 +96,6 @@ vorbis_copy_comment(const char *comment, return false; } -static const struct tag_table vorbis_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - static void vorbis_scan_comment(const char *comment, const struct tag_handler *handler, void *handler_ctx) @@ -119,14 +113,14 @@ vorbis_scan_comment(const char *comment, g_free(name); } - for (const struct tag_table *i = vorbis_tags; i->name != NULL; ++i) + for (const struct tag_table *i = xiph_tags; i->name != NULL; ++i) if (vorbis_copy_comment(comment, i->name, i->type, handler, handler_ctx)) return; for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) if (vorbis_copy_comment(comment, - tag_item_names[i], i, + tag_item_names[i], tag_type(i), handler, handler_ctx)) return; } diff --git a/src/decoder/vorbis_comments.h b/src/decoder/VorbisComments.hxx index c1509693..8212cac4 100644 --- a/src/decoder/vorbis_comments.h +++ b/src/decoder/VorbisComments.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_VORBIS_COMMENTS_H -#define MPD_VORBIS_COMMENTS_H +#ifndef MPD_VORBIS_COMMENTS_HXX +#define MPD_VORBIS_COMMENTS_HXX #include "check.h" diff --git a/src/decoder/vorbis_decoder_plugin.c b/src/decoder/VorbisDecoderPlugin.cxx index 15cdc0ca..488786ed 100644 --- a/src/decoder/vorbis_decoder_plugin.c +++ b/src/decoder/VorbisDecoderPlugin.cxx @@ -18,10 +18,17 @@ */ #include "config.h" -#include "vorbis_comments.h" -#include "_ogg_common.h" +#include "VorbisDecoderPlugin.h" +#include "VorbisComments.hxx" +#include "decoder_api.h" +#include "InputStream.hxx" +#include "OggCodec.hxx" + +extern "C" { #include "audio_check.h" #include "uri.h" +} + #include "tag_handler.h" #ifndef HAVE_TREMOR @@ -48,12 +55,11 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "vorbis" -#define OGG_CHUNK_SIZE 4096 #if G_BYTE_ORDER == G_BIG_ENDIAN -#define OGG_DECODE_USE_BIGENDIAN 1 +#define VORBIS_BIG_ENDIAN true #else -#define OGG_DECODE_USE_BIGENDIAN 0 +#define VORBIS_BIG_ENDIAN false #endif struct vorbis_input_stream { @@ -65,10 +71,9 @@ struct vorbis_input_stream { static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) { - struct vorbis_input_stream *vis = data; - size_t ret; - - ret = decoder_read(vis->decoder, vis->input_stream, ptr, size * nmemb); + 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; @@ -77,7 +82,7 @@ static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence) { - struct vorbis_input_stream *vis = data; + struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; return vis->seekable && (!vis->decoder || decoder_get_command(vis->decoder) != DECODE_COMMAND_STOP) && @@ -93,16 +98,16 @@ static int ogg_close_cb(G_GNUC_UNUSED void *data) static long ogg_tell_cb(void *data) { - const struct vorbis_input_stream *vis = data; + struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; return (long)vis->input_stream->offset; } static const ov_callbacks vorbis_is_callbacks = { - .read_func = ogg_read_cb, - .seek_func = ogg_seek_cb, - .close_func = ogg_close_cb, - .tell_func = ogg_tell_cb, + ogg_read_cb, + ogg_seek_cb, + ogg_close_cb, + ogg_tell_cb, }; static const char * @@ -135,9 +140,7 @@ vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf, { vis->decoder = decoder; vis->input_stream = input_stream; - vis->seekable = input_stream->seekable && - (input_stream->uri == NULL || - !uri_has_scheme(input_stream->uri)); + vis->seekable = input_stream_cheap_seeking(input_stream); int ret = ov_open_callbacks(vis, vf, NULL, 0, vorbis_is_callbacks); if (ret < 0) { @@ -155,9 +158,7 @@ static void vorbis_send_comments(struct decoder *decoder, struct input_stream *is, char **comments) { - struct tag *tag; - - tag = vorbis_comments_to_tag(comments); + struct tag *tag = vorbis_comments_to_tag(comments); if (!tag) return; @@ -165,55 +166,79 @@ vorbis_send_comments(struct decoder *decoder, struct input_stream *is, tag_free(tag); } +#ifndef HAVE_TREMOR +static void +vorbis_interleave(float *dest, const float *const*src, + unsigned nframes, unsigned channels) +{ + for (const float *const*src_end = src + channels; + src != src_end; ++src, ++dest) { + float *d = dest; + for (const float *s = *src, *s_end = s + nframes; + s != s_end; ++s, d += channels) + *d = *s; + } +} +#endif + /* public */ static void vorbis_stream_decode(struct decoder *decoder, struct input_stream *input_stream) { GError *error = NULL; - OggVorbis_File vf; - struct vorbis_input_stream vis; - struct audio_format audio_format; - float total_time; - int current_section; - int prev_section = -1; - long ret; - char chunk[OGG_CHUNK_SIZE]; - long bitRate = 0; - long test; - const vorbis_info *vi; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - if (ogg_stream_type_detect(input_stream) != VORBIS) + + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_VORBIS) return; - /* rewind the stream, because ogg_stream_type_detect() has + /* rewind the stream, because ogg_codec_detect() has moved it */ input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); + struct vorbis_input_stream vis; + OggVorbis_File vf; if (!vorbis_is_open(&vis, &vf, decoder, input_stream)) return; - vi = ov_info(&vf, -1); + const vorbis_info *vi = ov_info(&vf, -1); if (vi == NULL) { g_warning("ov_info() has failed"); return; } + struct audio_format audio_format; if (!audio_format_init_checked(&audio_format, vi->rate, +#ifdef HAVE_TREMOR SAMPLE_FORMAT_S16, +#else + SAMPLE_FORMAT_FLOAT, +#endif vi->channels, &error)) { g_warning("%s", error->message); g_error_free(error); return; } - total_time = ov_time_total(&vf, -1); + float total_time = ov_time_total(&vf, -1); if (total_time < 0) total_time = 0; decoder_initialized(decoder, &audio_format, vis.seekable, total_time); + enum decoder_command cmd = decoder_get_command(decoder); + +#ifdef HAVE_TREMOR + char buffer[4096]; +#else + float buffer[2048]; + const int frames_per_buffer = + G_N_ELEMENTS(buffer) / audio_format.channels; + const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels; +#endif + + int prev_section = -1; + unsigned kbit_rate = 0; + do { if (cmd == DECODE_COMMAND_SEEK) { double seek_where = decoder_seek_where(decoder); @@ -223,17 +248,33 @@ vorbis_stream_decode(struct decoder *decoder, decoder_seek_error(decoder); } - ret = ov_read(&vf, chunk, sizeof(chunk), - OGG_DECODE_USE_BIGENDIAN, 2, 1, ¤t_section); - if (ret == OV_HOLE) /* bad packet */ - ret = 0; - else if (ret <= 0) + int current_section; + +#ifdef HAVE_TREMOR + long nbytes = ov_read(&vf, buffer, sizeof(buffer), + VORBIS_BIG_ENDIAN, 2, 1, + ¤t_section); +#else + float **per_channel; + long nframes = ov_read_float(&vf, &per_channel, + frames_per_buffer, + ¤t_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) { - char **comments; - vi = ov_info(&vf, -1); if (vi == NULL) { g_warning("ov_info() has failed"); @@ -248,7 +289,7 @@ vorbis_stream_decode(struct decoder *decoder, break; } - comments = ov_comment(&vf, -1)->user_comments; + char **comments = ov_comment(&vf, -1)->user_comments; vorbis_send_comments(decoder, input_stream, comments); struct replay_gain_info rgi; @@ -258,12 +299,13 @@ vorbis_stream_decode(struct decoder *decoder, prev_section = current_section; } - if ((test = ov_bitrate_instant(&vf)) > 0) - bitRate = test / 1000; + long test = ov_bitrate_instant(&vf); + if (test > 0) + kbit_rate = test / 1000; cmd = decoder_data(decoder, input_stream, - chunk, ret, - bitRate); + buffer, nbytes, + kbit_rate); } while (cmd != DECODE_COMMAND_STOP); ov_clear(&vf); @@ -306,9 +348,14 @@ static const char *const vorbis_mime_types[] = { }; const struct decoder_plugin vorbis_decoder_plugin = { - .name = "vorbis", - .stream_decode = vorbis_stream_decode, - .scan_stream = vorbis_scan_stream, - .suffixes = vorbis_suffixes, - .mime_types = vorbis_mime_types + "vorbis", + nullptr, + nullptr, + vorbis_stream_decode, + nullptr, + nullptr, + vorbis_scan_stream, + nullptr, + vorbis_suffixes, + vorbis_mime_types }; diff --git a/src/decoder/VorbisDecoderPlugin.h b/src/decoder/VorbisDecoderPlugin.h new file mode 100644 index 00000000..618c9ffd --- /dev/null +++ b/src/decoder/VorbisDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_VORBIS_H +#define MPD_DECODER_VORBIS_H + +extern const struct decoder_plugin vorbis_decoder_plugin; + +#endif diff --git a/src/decoder/wavpack_decoder_plugin.c b/src/decoder/WavpackDecoderPlugin.cxx index 9ebd0fcc..bac62d42 100644 --- a/src/decoder/wavpack_decoder_plugin.c +++ b/src/decoder/WavpackDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,14 @@ */ #include "config.h" +#include "WavpackDecoderPlugin.hxx" #include "decoder_api.h" +#include "InputStream.hxx" + +extern "C" { #include "audio_check.h" -#include "path.h" -#include "utils.h" -#include "tag_table.h" +} + #include "tag_handler.h" #include "tag_ape.h" @@ -30,7 +33,6 @@ #include <glib.h> #include <assert.h> -#include <unistd.h> #include <stdio.h> #include <stdlib.h> @@ -53,18 +55,18 @@ typedef void (*format_samples_t)( static void format_samples_int(int bytes_per_sample, void *buffer, uint32_t count) { - int32_t *src = buffer; + int32_t *src = (int32_t *)buffer; switch (bytes_per_sample) { case 1: { - int8_t *dst = buffer; + 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. */ - assert_static(sizeof(*dst) <= sizeof(*src)); + static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); /* pass through and align 8-bit samples */ while (count--) { @@ -73,8 +75,8 @@ format_samples_int(int bytes_per_sample, void *buffer, uint32_t count) break; } case 2: { - uint16_t *dst = buffer; - assert_static(sizeof(*dst) <= sizeof(*src)); + uint16_t *dst = (uint16_t *)buffer; + static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); /* pass through and align 16-bit samples */ while (count--) { @@ -97,7 +99,7 @@ static void format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer, uint32_t count) { - float *p = buffer; + float *p = (float *)buffer; while (count--) { *p /= (1 << 23); @@ -359,7 +361,7 @@ static struct wavpack_input * wpin(void *id) { assert(id); - return id; + return (struct wavpack_input *)id; } static int32_t @@ -441,14 +443,14 @@ wavpack_input_can_seek(void *id) } static WavpackStreamReader mpd_is_reader = { - .read_bytes = wavpack_input_read_bytes, - .get_pos = wavpack_input_get_pos, - .set_pos_abs = wavpack_input_set_pos_abs, - .set_pos_rel = wavpack_input_set_pos_rel, - .push_back_byte = wavpack_input_push_back_byte, - .get_length = wavpack_input_get_length, - .can_seek = wavpack_input_can_seek, - .write_bytes = NULL /* no need to write edited tags */ + 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 @@ -462,7 +464,7 @@ wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder, static struct input_stream * wavpack_open_wvc(struct decoder *decoder, const char *uri, - GMutex *mutex, GCond *cond, + Mutex &mutex, Cond &cond, struct wavpack_input *wpi) { struct input_stream *is_wvc; @@ -475,7 +477,7 @@ wavpack_open_wvc(struct decoder *decoder, const char *uri, * single files. utf8url is not absolute file path :/ */ if (uri == NULL) - return false; + return nullptr; wvc_url = g_strconcat(uri, "c", NULL); is_wvc = input_stream_open(wvc_url, mutex, cond, NULL); @@ -515,7 +517,8 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is) struct wavpack_input isp, isp_wvc; bool can_seek = is->seekable; - is_wvc = wavpack_open_wvc(decoder, is->uri, is->mutex, is->cond, + is_wvc = wavpack_open_wvc(decoder, is->uri.c_str(), + is->mutex, is->cond, &isp_wvc); if (is_wvc != NULL) { open_flags |= OPEN_WVC; @@ -587,10 +590,14 @@ static char const *const wavpack_mime_types[] = { }; const struct decoder_plugin wavpack_decoder_plugin = { - .name = "wavpack", - .stream_decode = wavpack_streamdecode, - .file_decode = wavpack_filedecode, - .scan_file = wavpack_scan_file, - .suffixes = wavpack_suffixes, - .mime_types = wavpack_mime_types + "wavpack", + nullptr, + nullptr, + wavpack_streamdecode, + wavpack_filedecode, + wavpack_scan_file, + nullptr, + nullptr, + wavpack_suffixes, + wavpack_mime_types }; diff --git a/src/decoder/WavpackDecoderPlugin.hxx b/src/decoder/WavpackDecoderPlugin.hxx new file mode 100644 index 00000000..9ebe6354 --- /dev/null +++ b/src/decoder/WavpackDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_WAVPACK_HXX +#define MPD_DECODER_WAVPACK_HXX + +extern const struct decoder_plugin wavpack_decoder_plugin; + +#endif diff --git a/src/decoder/XiphTags.c b/src/decoder/XiphTags.c new file mode 100644 index 00000000..d55787b9 --- /dev/null +++ b/src/decoder/XiphTags.c @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "XiphTags.h" + +const struct tag_table xiph_tags[] = { + { "tracknumber", TAG_TRACK }, + { "discnumber", TAG_DISC }, + { "album artist", TAG_ALBUM_ARTIST }, + { NULL, TAG_NUM_OF_ITEM_TYPES } +}; diff --git a/src/decoder/XiphTags.h b/src/decoder/XiphTags.h new file mode 100644 index 00000000..22a4e220 --- /dev/null +++ b/src/decoder/XiphTags.h @@ -0,0 +1,28 @@ +/* + * 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_XIPH_TAGS_H +#define MPD_XIPH_TAGS_H + +#include "check.h" +#include "tag_table.h" + +extern const struct tag_table xiph_tags[]; + +#endif diff --git a/src/decoder/audiofile_decoder_plugin.c b/src/decoder/audiofile_decoder_plugin.c index b344795e..d2ceee8a 100644 --- a/src/decoder/audiofile_decoder_plugin.c +++ b/src/decoder/audiofile_decoder_plugin.c @@ -69,14 +69,14 @@ static AFfileoffset audiofile_file_length(AFvirtualfile *vfile) { struct input_stream *is = (struct input_stream *) vfile->closure; - return is->size; + return input_stream_get_size(is); } static AFfileoffset audiofile_file_tell(AFvirtualfile *vfile) { struct input_stream *is = (struct input_stream *) vfile->closure; - return is->offset; + return input_stream_get_offset(is); } static void @@ -93,7 +93,7 @@ audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative) struct input_stream *is = (struct input_stream *) vfile->closure; int whence = (is_relative ? SEEK_CUR : SEEK_SET); if (input_stream_lock_seek(is, offset, whence, NULL)) { - return is->offset; + return input_stream_get_offset(is); } else { return -1; } @@ -166,7 +166,7 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is) char chunk[CHUNK_SIZE]; enum decoder_command cmd; - if (!is->seekable) { + if (!input_stream_is_seekable(is)) { g_warning("not seekable"); return; } @@ -194,7 +194,7 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is) total_time = ((float)frame_count / (float)audio_format.sample_rate); - bit_rate = (uint16_t)(is->size * 8.0 / total_time / 1000.0 + 0.5); + bit_rate = (uint16_t)(input_stream_get_size(is) * 8.0 / total_time / 1000.0 + 0.5); fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1); diff --git a/src/decoder/dsdiff_decoder_plugin.c b/src/decoder/dsdiff_decoder_plugin.c index 84471fb3..44d12d89 100644 --- a/src/decoder/dsdiff_decoder_plugin.c +++ b/src/decoder/dsdiff_decoder_plugin.c @@ -52,10 +52,23 @@ struct dsdiff_chunk_header { uint32_t size_high, size_low; }; +/** struct for DSDIFF native Artist and Title tags */ +struct dsdiff_native_tag { + uint32_t size; +}; + struct dsdiff_metadata { unsigned sample_rate, channels; bool bitreverse; uint64_t chunk_size; +#ifdef HAVE_ID3TAG + goffset id3_offset; + uint64_t id3_size; +#endif + /** offset for artist tag */ + goffset diar_offset; + /** offset for title tag */ + goffset diti_offset; }; static bool lsbitfirst; @@ -115,12 +128,12 @@ dsdiff_read_prop_snd(struct decoder *decoder, struct input_stream *is, goffset end_offset) { struct dsdiff_chunk_header header; - while ((goffset)(is->offset + sizeof(header)) <= end_offset) { + while ((goffset)(input_stream_get_offset(is) + sizeof(header)) <= end_offset) { if (!dsdiff_read_chunk_header(decoder, is, &header)) return false; - goffset chunk_end_offset = - is->offset + dsdiff_chunk_size(&header); + goffset chunk_end_offset = input_stream_get_offset(is) + + dsdiff_chunk_size(&header); if (chunk_end_offset > end_offset) return false; @@ -161,7 +174,7 @@ dsdiff_read_prop_snd(struct decoder *decoder, struct input_stream *is, } } - return is->offset == end_offset; + return input_stream_get_offset(is) == end_offset; } /** @@ -173,7 +186,7 @@ dsdiff_read_prop(struct decoder *decoder, struct input_stream *is, const struct dsdiff_chunk_header *prop_header) { uint64_t prop_size = dsdiff_chunk_size(prop_header); - goffset end_offset = is->offset + prop_size; + goffset end_offset = input_stream_get_offset(is) + prop_size; struct dsdlib_id prop_id; if (prop_size < sizeof(prop_id) || @@ -187,6 +200,127 @@ dsdiff_read_prop(struct decoder *decoder, struct input_stream *is, return dsdlib_skip_to(decoder, is, end_offset); } +static void +dsdiff_handle_native_tag(struct input_stream *is, + const struct tag_handler *handler, + void *handler_ctx, goffset tagoffset, + enum tag_type type) +{ + if (!dsdlib_skip_to(NULL, is, tagoffset)) + return; + + struct dsdiff_native_tag metatag; + + if (!dsdlib_read(NULL, is, &metatag, sizeof(metatag))) + return; + + uint32_t length = GUINT32_FROM_BE(metatag.size); + + /* Check and limit size of the tag to prevent a stack overflow */ + if (length == 0 || length > 60) + return; + + char string[length]; + char *label; + label = string; + + if (!dsdlib_read(NULL, is, label, (size_t)length)) + return; + + string[length] = '\0'; + tag_handler_invoke_tag(handler, handler_ctx, type, label); + return; +} + +/** + * Read and parse additional metadata chunks for tagging purposes. By default + * dsdiff files only support equivalents for artist and title but some of the + * extract tools add an id3 tag to provide more tags. If such id3 is found + * this will be used for tagging otherwise the native tags (if any) will be + * used + */ + +static bool +dsdiff_read_metadata_extra(struct decoder *decoder, struct input_stream *is, + struct dsdiff_metadata *metadata, + struct dsdiff_chunk_header *chunk_header, + const struct tag_handler *handler, + void *handler_ctx) +{ + + /* skip from DSD data to next chunk header */ + if (!dsdlib_skip(decoder, is, metadata->chunk_size)) + return false; + if (!dsdiff_read_chunk_header(decoder, is, chunk_header)) + return false; + +#ifdef HAVE_ID3TAG + metadata->id3_size = 0; +#endif + + /* Now process all the remaining chunk headers in the stream + and record their position and size */ + + const goffset size = input_stream_get_size(is); + while (input_stream_get_offset(is) < size) { + uint64_t chunk_size = dsdiff_chunk_size(chunk_header); + + /* DIIN chunk, is directly followed by other chunks */ + if (dsdlib_id_equals(&chunk_header->id, "DIIN")) + chunk_size = 0; + + /* DIAR chunk - DSDIFF native tag for Artist */ + if (dsdlib_id_equals(&chunk_header->id, "DIAR")) { + chunk_size = dsdiff_chunk_size(chunk_header); + metadata->diar_offset = input_stream_get_offset(is); + } + + /* DITI chunk - DSDIFF native tag for Title */ + if (dsdlib_id_equals(&chunk_header->id, "DITI")) { + chunk_size = dsdiff_chunk_size(chunk_header); + metadata->diti_offset = input_stream_get_offset(is); + } +#ifdef HAVE_ID3TAG + /* 'ID3 ' chunk, offspec. Used by sacdextract */ + if (dsdlib_id_equals(&chunk_header->id, "ID3 ")) { + chunk_size = dsdiff_chunk_size(chunk_header); + metadata->id3_offset = input_stream_get_offset(is); + metadata->id3_size = chunk_size; + } +#endif + if (chunk_size != 0) { + if (!dsdlib_skip(decoder, is, chunk_size)) + break; + } + + if (input_stream_get_offset(is) < size) { + if (!dsdiff_read_chunk_header(decoder, is, chunk_header)) + return false; + } + chunk_size = 0; + } + /* done processing chunk headers, process tags if any */ + +#ifdef HAVE_ID3TAG + if (metadata->id3_offset != 0) + { + /* a ID3 tag has preference over the other tags, do not process + other tags if we have one */ + dsdlib_tag_id3(is, handler, handler_ctx, metadata->id3_offset); + return true; + } +#endif + + if (metadata->diar_offset != 0) + dsdiff_handle_native_tag(is, handler, handler_ctx, + metadata->diar_offset, TAG_ARTIST); + + if (metadata->diti_offset != 0) + dsdiff_handle_native_tag(is, handler, handler_ctx, + metadata->diti_offset, TAG_TITLE); + return true; +} + /** * Read and parse all metadata chunks at the beginning. Stop when the * first "DSD" chunk is seen, and return its header in the @@ -221,7 +355,8 @@ dsdiff_read_metadata(struct decoder *decoder, struct input_stream *is, /* ignore unknown chunk */ uint64_t chunk_size; chunk_size = dsdiff_chunk_size(chunk_header); - goffset chunk_end_offset = is->offset + chunk_size; + goffset chunk_end_offset = input_stream_get_offset(is) + + chunk_size; if (!dsdlib_skip_to(decoder, is, chunk_end_offset)) return false; @@ -374,6 +509,10 @@ dsdiff_scan_stream(struct input_stream *is, metadata.sample_rate; tag_handler_invoke_duration(handler, handler_ctx, songtime); + /* Read additional metadata and created tags if available */ + dsdiff_read_metadata_extra(NULL, is, &metadata, &chunk_header, + handler, handler_ctx); + return true; } diff --git a/src/decoder/dsdlib.c b/src/decoder/dsdlib.c index 3df9497c..d3043fb0 100644 --- a/src/decoder/dsdlib.c +++ b/src/decoder/dsdlib.c @@ -27,12 +27,18 @@ #include "dsf_decoder_plugin.h" #include "decoder_api.h" #include "util/bit_reverse.h" +#include "tag_handler.h" +#include "tag_id3.h" #include "dsdlib.h" #include "dsdiff_decoder_plugin.h" #include <unistd.h> #include <stdio.h> /* for SEEK_SET, SEEK_CUR */ +#ifdef HAVE_ID3TAG +#include <id3tag.h> +#endif + bool dsdlib_id_equals(const struct dsdlib_id *id, const char *s) { @@ -58,24 +64,24 @@ bool dsdlib_skip_to(struct decoder *decoder, struct input_stream *is, goffset offset) { - if (is->seekable) + if (input_stream_is_seekable(is)) return input_stream_seek(is, offset, SEEK_SET, NULL); - if (is->offset > offset) + if (input_stream_get_offset(is) > offset) return false; char buffer[8192]; - while (is->offset < offset) { + while (input_stream_get_offset(is) < offset) { size_t length = sizeof(buffer); - if (offset - is->offset < (goffset)length) - length = offset - is->offset; + if (offset - input_stream_get_offset(is) < (goffset)length) + length = offset - input_stream_get_offset(is); size_t nbytes = decoder_read(decoder, is, buffer, length); if (nbytes == 0) return false; } - assert(is->offset == offset); + assert(input_stream_get_offset(is) == offset); return true; } @@ -91,7 +97,7 @@ dsdlib_skip(struct decoder *decoder, struct input_stream *is, if (delta == 0) return true; - if (is->seekable) + if (input_stream_is_seekable(is)) return input_stream_seek(is, delta, SEEK_CUR, NULL); char buffer[8192]; @@ -110,3 +116,55 @@ dsdlib_skip(struct decoder *decoder, struct input_stream *is, return true; } +/** + * Add tags from ID3 tag. All tags commonly found in the ID3 tags of + * DSF and DSDIFF files are imported + */ + +#ifdef HAVE_ID3TAG +void +dsdlib_tag_id3(struct input_stream *is, + const struct tag_handler *handler, + void *handler_ctx, goffset tagoffset) +{ + assert(tagoffset >= 0); + + if (tagoffset == 0) + return; + + if (!dsdlib_skip_to(NULL, is, tagoffset)) + return; + + struct id3_tag *id3_tag = NULL; + id3_length_t count; + + /* Prevent broken files causing problems */ + const goffset size = input_stream_get_size(is); + const goffset offset = input_stream_get_offset(is); + if (offset >= size) + return; + + count = size - offset; + + /* Check and limit id3 tag size to prevent a stack overflow */ + if (count == 0 || count > 4096) + return; + + id3_byte_t dsdid3[count]; + id3_byte_t *dsdid3data; + dsdid3data = dsdid3; + + if (!dsdlib_read(NULL, is, dsdid3data, count)) + return; + + id3_tag = id3_tag_parse(dsdid3data, count); + if (id3_tag == NULL) + return; + + scan_id3_tag(id3_tag, handler, handler_ctx); + + id3_tag_delete(id3_tag); + + return; +} +#endif diff --git a/src/decoder/dsdlib.h b/src/decoder/dsdlib.h index d9675f5f..0912740c 100644 --- a/src/decoder/dsdlib.h +++ b/src/decoder/dsdlib.h @@ -39,4 +39,9 @@ bool dsdlib_skip(struct decoder *decoder, struct input_stream *is, goffset delta); +void +dsdlib_tag_id3(struct input_stream *is, + const struct tag_handler *handler, + void *handler_ctx, goffset tagoffset); + #endif diff --git a/src/decoder/dsf_decoder_plugin.c b/src/decoder/dsf_decoder_plugin.c index c0107eb3..23576a62 100644 --- a/src/decoder/dsf_decoder_plugin.c +++ b/src/decoder/dsf_decoder_plugin.c @@ -45,6 +45,10 @@ struct dsf_metadata { unsigned sample_rate, channels; bool bitreverse; uint64_t chunk_size; +#ifdef HAVE_ID3TAG + goffset id3_offset; + uint64_t id3_size; +#endif }; struct dsf_header { @@ -57,6 +61,7 @@ struct dsf_header { /** pointer to id3v2 metadata, should be at the end of the file */ uint32_t pmeta_low, pmeta_high; }; + /** DSF file fmt chunk */ struct dsf_fmt_chunk { @@ -109,6 +114,12 @@ dsf_read_metadata(struct decoder *decoder, struct input_stream *is, if (sizeof(dsf_header) != chunk_size) return false; +#ifdef HAVE_ID3TAG + uint64_t metadata_offset; + metadata_offset = (((uint64_t)GUINT32_FROM_LE(dsf_header.pmeta_high)) << 32) | + ((uint64_t)GUINT32_FROM_LE(dsf_header.pmeta_low)); +#endif + /* read the 'fmt ' chunk of the DSF file */ struct dsf_fmt_chunk dsf_fmt_chunk; if (!dsdlib_read(decoder, is, &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) || @@ -153,9 +164,20 @@ dsf_read_metadata(struct decoder *decoder, struct input_stream *is, data_size -= sizeof(data_chunk); metadata->chunk_size = data_size; + /* data_size cannot be bigger or equal to total file size */ + const uint64_t size = (uint64_t)input_stream_get_size(is); + if (data_size >= size) + return false; + metadata->channels = (unsigned) dsf_fmt_chunk.channelnum; metadata->sample_rate = samplefreq; - +#ifdef HAVE_ID3TAG + /* metada_offset cannot be bigger then or equal to total file size */ + if (metadata_offset >= size) + metadata->id3_offset = 0; + else + metadata->id3_offset = (goffset) metadata_offset; +#endif /* check bits per sample format, determine if bitreverse is needed */ metadata->bitreverse = dsf_fmt_chunk.bitssample == 1; return true; @@ -285,7 +307,7 @@ dsf_stream_decode(struct decoder *decoder, struct input_stream *is) decoder_initialized(decoder, &audio_format, false, songtime); if (!dsf_decode_chunk(decoder, is, metadata.channels, - metadata.chunk_size, + chunk_size, metadata.bitreverse)) return; } @@ -316,6 +338,10 @@ dsf_scan_stream(struct input_stream *is, 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; } diff --git a/src/decoder/faad_decoder_plugin.c b/src/decoder/faad_decoder_plugin.c index 911f033b..a7ece93f 100644 --- a/src/decoder/faad_decoder_plugin.c +++ b/src/decoder/faad_decoder_plugin.c @@ -23,16 +23,18 @@ #include "audio_check.h" #include "tag_handler.h" -#define AAC_MAX_CHANNELS 6 +#include <neaacdec.h> + +#include <glib.h> #include <assert.h> #include <unistd.h> -#include <faad.h> -#include <glib.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "faad" +#define AAC_MAX_CHANNELS 6 + static const unsigned adts_sample_rates[] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, 0, 0, 0 @@ -175,7 +177,8 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is) size_t length; bool success; - fileread = is->size >= 0 ? is->size : 0; + const goffset size = input_stream_get_size(is); + fileread = size >= 0 ? size : 0; decoder_buffer_fill(buffer); data = decoder_buffer_read(buffer, &length); @@ -201,7 +204,7 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is) return -1; } - if (is->seekable && length >= 2 && + if (input_stream_is_seekable(is) && length >= 2 && data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) { /* obtain the duration from the ADTS header */ float song_length = adts_song_duration(buffer); @@ -238,11 +241,11 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is) } /** - * Wrapper for faacDecInit() which works around some API + * Wrapper for NeAACDecInit() which works around some API * inconsistencies in libfaad. */ static bool -faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer, +faad_decoder_init(NeAACDecHandle decoder, struct decoder_buffer *buffer, struct audio_format *audio_format, GError **error_r) { union { @@ -270,10 +273,8 @@ faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer, return false; } - nbytes = faacDecInit(decoder, u.out, -#ifdef HAVE_FAAD_BUFLEN_FUNCS + nbytes = NeAACDecInit(decoder, u.out, length, -#endif sample_rate_p, &channels); if (nbytes < 0) { g_set_error(error_r, faad_decoder_quark(), 0, @@ -288,12 +289,12 @@ faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer, } /** - * Wrapper for faacDecDecode() which works around some API + * Wrapper for NeAACDecDecode() which works around some API * inconsistencies in libfaad. */ static const void * -faad_decoder_decode(faacDecHandle decoder, struct decoder_buffer *buffer, - faacDecFrameInfo *frame_info) +faad_decoder_decode(NeAACDecHandle decoder, struct decoder_buffer *buffer, + NeAACDecFrameInfo *frame_info) { union { /* deconst hack for libfaad */ @@ -301,20 +302,13 @@ faad_decoder_decode(faacDecHandle decoder, struct decoder_buffer *buffer, void *out; } u; size_t length; - void *result; u.in = decoder_buffer_read(buffer, &length); if (u.in == NULL) return NULL; - result = faacDecDecode(decoder, frame_info, - u.out -#ifdef HAVE_FAAD_BUFLEN_FUNCS - , length -#endif - ); - - return result; + return NeAACDecDecode(decoder, frame_info, + u.out, length); } /** @@ -327,8 +321,6 @@ faad_get_file_time_float(struct input_stream *is) { struct decoder_buffer *buffer; float length; - faacDecHandle decoder; - faacDecConfigurationPtr config; buffer = decoder_buffer_new(NULL, is, FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); @@ -338,11 +330,12 @@ faad_get_file_time_float(struct input_stream *is) bool ret; struct audio_format audio_format; - decoder = faacDecOpen(); + NeAACDecHandle decoder = NeAACDecOpen(); - config = faacDecGetCurrentConfiguration(decoder); + NeAACDecConfigurationPtr config = + NeAACDecGetCurrentConfiguration(decoder); config->outputFormat = FAAD_FMT_16BIT; - faacDecSetConfiguration(decoder, config); + NeAACDecSetConfiguration(decoder, config); decoder_buffer_fill(buffer); @@ -350,7 +343,7 @@ faad_get_file_time_float(struct input_stream *is) if (ret) length = 0; - faacDecClose(decoder); + NeAACDecClose(decoder); } decoder_buffer_free(buffer); @@ -380,9 +373,7 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) { GError *error = NULL; float total_time = 0; - faacDecHandle decoder; struct audio_format audio_format; - faacDecConfigurationPtr config; bool ret; uint16_t bit_rate = 0; struct decoder_buffer *buffer; @@ -394,17 +385,14 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) /* create the libfaad decoder */ - decoder = faacDecOpen(); + NeAACDecHandle decoder = NeAACDecOpen(); - config = faacDecGetCurrentConfiguration(decoder); + NeAACDecConfigurationPtr config = + NeAACDecGetCurrentConfiguration(decoder); config->outputFormat = FAAD_FMT_16BIT; -#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX config->downMatrix = 1; -#endif -#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR config->dontUpSampleImplicitSBR = 0; -#endif - faacDecSetConfiguration(decoder, config); + NeAACDecSetConfiguration(decoder, config); while (!decoder_buffer_is_full(buffer) && !input_stream_lock_eof(is) && @@ -419,7 +407,7 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) if (!ret) { g_warning("%s", error->message); g_error_free(error); - faacDecClose(decoder); + NeAACDecClose(decoder); return; } @@ -432,7 +420,7 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) do { size_t frame_size; const void *decoded; - faacDecFrameInfo frame_info; + NeAACDecFrameInfo frame_info; /* find the next frame */ @@ -447,7 +435,7 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) if (frame_info.error > 0) { g_warning("error decoding AAC stream: %s\n", - faacDecGetErrorMessage(frame_info.error)); + NeAACDecGetErrorMessage(frame_info.error)); break; } @@ -457,14 +445,12 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) break; } -#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE if (frame_info.samplerate != audio_format.sample_rate) { g_warning("sample rate changed from %u to %lu", audio_format.sample_rate, (unsigned long)frame_info.samplerate); break; } -#endif decoder_buffer_consume(buffer, frame_info.bytesconsumed); @@ -485,7 +471,7 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) /* cleanup */ - faacDecClose(decoder); + NeAACDecClose(decoder); } static bool diff --git a/src/decoder/flac_compat.h b/src/decoder/flac_compat.h deleted file mode 100644 index 9a30acc2..00000000 --- a/src/decoder/flac_compat.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Common data structures and functions used by FLAC and OggFLAC - */ - -#ifndef MPD_FLAC_COMPAT_H -#define MPD_FLAC_COMPAT_H - -#include <FLAC/export.h> -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -# include <FLAC/seekable_stream_decoder.h> - -/* starting with libFLAC 1.1.3, the SeekableStreamDecoder has been - merged into the StreamDecoder. The following macros try to emulate - the new API for libFLAC 1.1.2 by mapping MPD's StreamDecoder calls - to the old SeekableStreamDecoder API. */ - -#define FLAC__StreamDecoder FLAC__SeekableStreamDecoder -#define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new -#define FLAC__stream_decoder_get_decode_position FLAC__seekable_stream_decoder_get_decode_position -#define FLAC__stream_decoder_get_state FLAC__seekable_stream_decoder_get_state -#define FLAC__stream_decoder_process_single FLAC__seekable_stream_decoder_process_single -#define FLAC__stream_decoder_process_until_end_of_metadata FLAC__seekable_stream_decoder_process_until_end_of_metadata -#define FLAC__stream_decoder_seek_absolute FLAC__seekable_stream_decoder_seek_absolute -#define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish -#define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete - -#define FLAC__STREAM_DECODER_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM - -typedef unsigned flac_read_status_size_t; - -#define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus -#define FLAC__STREAM_DECODER_READ_STATUS_CONTINUE FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK -#define FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK -#define FLAC__STREAM_DECODER_READ_STATUS_ABORT FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR - -#define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus -#define FLAC__STREAM_DECODER_SEEK_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK -#define FLAC__STREAM_DECODER_SEEK_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR -#define FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR - -#define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus -#define FLAC__STREAM_DECODER_TELL_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK -#define FLAC__STREAM_DECODER_TELL_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR -#define FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR - -#define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus -#define FLAC__STREAM_DECODER_LENGTH_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK -#define FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR -#define FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR - -typedef enum { - FLAC__STREAM_DECODER_INIT_STATUS_OK, - FLAC__STREAM_DECODER_INIT_STATUS_ERROR, -} FLAC__StreamDecoderInitStatus; - -static inline FLAC__StreamDecoderInitStatus -FLAC__stream_decoder_init_stream(FLAC__SeekableStreamDecoder *decoder, - FLAC__SeekableStreamDecoderReadCallback read_cb, - FLAC__SeekableStreamDecoderSeekCallback seek_cb, - FLAC__SeekableStreamDecoderTellCallback tell_cb, - FLAC__SeekableStreamDecoderLengthCallback length_cb, - FLAC__SeekableStreamDecoderEofCallback eof_cb, - FLAC__SeekableStreamDecoderWriteCallback write_cb, - FLAC__SeekableStreamDecoderMetadataCallback metadata_cb, - FLAC__SeekableStreamDecoderErrorCallback error_cb, - void *data) -{ - return FLAC__seekable_stream_decoder_set_read_callback(decoder, read_cb) && - FLAC__seekable_stream_decoder_set_seek_callback(decoder, seek_cb) && - FLAC__seekable_stream_decoder_set_tell_callback(decoder, tell_cb) && - FLAC__seekable_stream_decoder_set_length_callback(decoder, length_cb) && - FLAC__seekable_stream_decoder_set_eof_callback(decoder, eof_cb) && - FLAC__seekable_stream_decoder_set_write_callback(decoder, write_cb) && - FLAC__seekable_stream_decoder_set_metadata_callback(decoder, metadata_cb) && - FLAC__seekable_stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT) && - FLAC__seekable_stream_decoder_set_error_callback(decoder, error_cb) && - FLAC__seekable_stream_decoder_set_client_data(decoder, data) && - FLAC__seekable_stream_decoder_init(decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK - ? FLAC__STREAM_DECODER_INIT_STATUS_OK - : FLAC__STREAM_DECODER_INIT_STATUS_ERROR; -} - -#else /* FLAC_API_VERSION_CURRENT > 7 */ - -# include <FLAC/stream_decoder.h> - -# define flac_init(a,b,c,d,e,f,g,h,i,j) \ - (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \ - == FLAC__STREAM_DECODER_INIT_STATUS_OK) - -typedef size_t flac_read_status_size_t; - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -#endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/flac_metadata.h b/src/decoder/flac_metadata.h deleted file mode 100644 index 3c463d5d..00000000 --- a/src/decoder/flac_metadata.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FLAC_METADATA_H -#define MPD_FLAC_METADATA_H - -#include <assert.h> -#include <stdbool.h> -#include <FLAC/metadata.h> - -struct tag_handler; -struct tag; -struct replay_gain_info; - -static inline unsigned -flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info) -{ - assert(stream_info->sample_rate > 0); - - return (stream_info->total_samples + stream_info->sample_rate - 1) / - stream_info->sample_rate; -} - -bool -flac_parse_replay_gain(struct replay_gain_info *rgi, - const FLAC__StreamMetadata *block); - -bool -flac_parse_mixramp(char **mixramp_start, char **mixramp_end, - const FLAC__StreamMetadata *block); - -void -flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment *comment); - -void -flac_scan_metadata(const char *track, - const FLAC__StreamMetadata *block, - const struct tag_handler *handler, void *handler_ctx); - -bool -flac_scan_file2(const char *file, const char *char_tnum, - const struct tag_handler *handler, void *handler_ctx); - -struct tag * -flac_tag_load(const char *file, const char *char_tnum); - -#endif diff --git a/src/decoder/mad_decoder_plugin.c b/src/decoder/mad_decoder_plugin.c index 62c37164..4e2e0331 100644 --- a/src/decoder/mad_decoder_plugin.c +++ b/src/decoder/mad_decoder_plugin.c @@ -76,9 +76,9 @@ mad_fixed_to_24_sample(mad_fixed_t sample) sample = sample + (1L << (MAD_F_FRACBITS - bits)); /* clip */ - if (sample > MAX) + if (gcc_unlikely(sample > MAX)) sample = MAX; - else if (sample < MIN) + else if (gcc_unlikely(sample < MIN)) sample = MIN; /* quantize */ @@ -359,15 +359,14 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize, struct replay_gain_info rgi; char *mixramp_start; char *mixramp_end; - float replay_gain_db = 0; if (parse_id3_replay_gain_info(&rgi, id3_tag)) { - replay_gain_db = decoder_replay_gain(data->decoder, &rgi); + decoder_replay_gain(data->decoder, &rgi); data->found_replay_gain = true; } if (parse_id3_mixramp(&mixramp_start, &mixramp_end, id3_tag)) - decoder_mixramp(data->decoder, replay_gain_db, + decoder_mixramp(data->decoder, mixramp_start, mixramp_end); } @@ -756,7 +755,7 @@ mp3_frame_duration(const struct mad_frame *frame) static goffset mp3_this_frame_offset(const struct mp3_data *data) { - goffset offset = data->input_stream->offset; + goffset offset = input_stream_get_offset(data->input_stream); if (data->stream.this_frame != NULL) offset -= data->stream.bufend - data->stream.this_frame; @@ -769,7 +768,8 @@ mp3_this_frame_offset(const struct mp3_data *data) static goffset mp3_rest_including_this_frame(const struct mp3_data *data) { - return data->input_stream->size - mp3_this_frame_offset(data); + return input_stream_get_size(data->input_stream) + - mp3_this_frame_offset(data); } /** @@ -842,7 +842,7 @@ mp3_decode_first_frame(struct mp3_data *data, struct tag **tag) if (parse_lame(&lame, &ptr, &bitlen)) { if (gapless_playback && - data->input_stream->seekable) { + input_stream_is_seekable(data->input_stream)) { data->drop_start_samples = lame.encoder_delay + DECODERDELAY; data->drop_end_samples = lame.encoder_padding; @@ -1082,7 +1082,7 @@ mp3_read(struct mp3_data *data) if (cmd == DECODE_COMMAND_SEEK) { unsigned long j; - assert(data->input_stream->seekable); + assert(input_stream_is_seekable(data->input_stream)); j = mp3_time_to_frame(data, decoder_seek_where(decoder)); @@ -1164,7 +1164,8 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream) } decoder_initialized(decoder, &audio_format, - data.input_stream->seekable, data.total_time); + input_stream_is_seekable(input_stream), + data.total_time); if (tag != NULL) { decoder_tag(decoder, input_stream, tag); diff --git a/src/decoder/modplug_decoder_plugin.c b/src/decoder/modplug_decoder_plugin.c index 21ee79e7..bc3d8176 100644 --- a/src/decoder/modplug_decoder_plugin.c +++ b/src/decoder/modplug_decoder_plugin.c @@ -41,19 +41,21 @@ static GByteArray *mod_loadfile(struct decoder *decoder, struct input_stream *is GByteArray *bdatas; size_t ret; - if (is->size == 0) { + const goffset size = input_stream_get_size(is); + + if (size == 0) { g_warning("file is empty"); return NULL; } - if (is->size > MODPLUG_FILE_LIMIT) { + if (size > MODPLUG_FILE_LIMIT) { g_warning("file too large"); return NULL; } //known/unknown size, preallocate array, lets read in chunks - if (is->size > 0) { - bdatas = g_byte_array_sized_new(is->size); + if (size > 0) { + bdatas = g_byte_array_sized_new(size); } else { bdatas = g_byte_array_sized_new(MODPLUG_PREALLOC_BLOCK); } @@ -126,7 +128,8 @@ mod_decode(struct decoder *decoder, struct input_stream *is) assert(audio_format_valid(&audio_format)); decoder_initialized(decoder, &audio_format, - is->seekable, ModPlug_GetLength(f) / 1000.0); + input_stream_is_seekable(is), + ModPlug_GetLength(f) / 1000.0); do { ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE); diff --git a/src/decoder/mp4ff_decoder_plugin.c b/src/decoder/mp4ff_decoder_plugin.c deleted file mode 100644 index ca78a22d..00000000 --- a/src/decoder/mp4ff_decoder_plugin.c +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" -#include "tag_table.h" -#include "tag_handler.h" - -#include <glib.h> - -#include <mp4ff.h> -#include <faad.h> - -#include <assert.h> -#include <stdlib.h> -#include <unistd.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mp4ff" - -/* all code here is either based on or copied from FAAD2's frontend code */ - -struct mp4ff_input_stream { - mp4ff_callback_t callback; - - struct decoder *decoder; - struct input_stream *input_stream; -}; - -static int -mp4_get_aac_track(mp4ff_t * infile, faacDecHandle decoder, - uint32_t *sample_rate, unsigned char *channels_r) -{ -#ifdef HAVE_FAAD_LONG - /* neaacdec.h declares all arguments as "unsigned long", but - internally expects uint32_t pointers. To avoid gcc - warnings, use this workaround. */ - unsigned long *sample_rate_r = (unsigned long*)sample_rate; -#else - uint32_t *sample_rate_r = sample_rate; -#endif - int i, rc; - int num_tracks = mp4ff_total_tracks(infile); - - for (i = 0; i < num_tracks; i++) { - unsigned char *buff = NULL; - unsigned int buff_size = 0; - - if (mp4ff_get_track_type(infile, i) != 1) - /* not an audio track */ - continue; - - if (decoder == NULL) - /* have don't have a decoder to initialize - - we're done now, because we found an audio - track */ - return i; - - mp4ff_get_decoder_config(infile, i, &buff, &buff_size); - if (buff == NULL) - continue; - - rc = faacDecInit2(decoder, buff, buff_size, - sample_rate_r, channels_r); - free(buff); - - if (rc >= 0) - /* found a valid AAC track */ - return i; - } - - /* can't decode this */ - return -1; -} - -static uint32_t -mp4_read(void *user_data, void *buffer, uint32_t length) -{ - struct mp4ff_input_stream *mis = user_data; - - if (length == 0) - /* libmp4ff is known to attempt to read 0 bytes - make - this a special case, because the input_stream API - would not allow this */ - return 0; - - return decoder_read(mis->decoder, mis->input_stream, buffer, length); -} - -static uint32_t -mp4_seek(void *user_data, uint64_t position) -{ - struct mp4ff_input_stream *mis = user_data; - - return input_stream_lock_seek(mis->input_stream, position, SEEK_SET, - NULL) - ? 0 : -1; -} - -static const mp4ff_callback_t mpd_mp4ff_callback = { - .read = mp4_read, - .seek = mp4_seek, -}; - -static mp4ff_t * -mp4ff_input_stream_open(struct mp4ff_input_stream *mis, - struct decoder *decoder, - struct input_stream *input_stream) -{ - mis->callback = mpd_mp4ff_callback; - mis->callback.user_data = mis; - mis->decoder = decoder; - mis->input_stream = input_stream; - - return mp4ff_open_read(&mis->callback); -} - -static faacDecHandle -mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format) -{ - faacDecHandle decoder; - faacDecConfigurationPtr config; - int track; - uint32_t sample_rate; - unsigned char channels; - GError *error = NULL; - - decoder = faacDecOpen(); - - config = faacDecGetCurrentConfiguration(decoder); - config->outputFormat = FAAD_FMT_16BIT; -#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX - config->downMatrix = 1; -#endif -#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR - config->dontUpSampleImplicitSBR = 0; -#endif - faacDecSetConfiguration(decoder, config); - - track = mp4_get_aac_track(mp4fh, decoder, &sample_rate, &channels); - if (track < 0) { - g_warning("No AAC track found"); - faacDecClose(decoder); - return NULL; - } - - if (!audio_format_init_checked(audio_format, sample_rate, - SAMPLE_FORMAT_S16, channels, - &error)) { - g_warning("%s", error->message); - g_error_free(error); - faacDecClose(decoder); - return NULL; - } - - *track_r = track; - - return decoder; -} - -static void -mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) -{ - struct mp4ff_input_stream mis; - mp4ff_t *mp4fh; - int32_t track; - float file_time, total_time; - int32_t scale; - faacDecHandle decoder; - struct audio_format audio_format; - faacDecFrameInfo frame_info; - unsigned char *mp4_buffer; - unsigned int mp4_buffer_size; - long sample_id; - long num_samples; - long dur; - unsigned int sample_count; - char *sample_buffer; - size_t sample_buffer_length; - unsigned int initial = 1; - float *seek_table; - long seek_table_end = -1; - bool seek_position_found = false; - long offset; - uint16_t bit_rate = 0; - bool seeking = false; - double seek_where = 0; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - mp4fh = mp4ff_input_stream_open(&mis, mpd_decoder, input_stream); - if (!mp4fh) { - g_warning("Input does not appear to be a mp4 stream.\n"); - return; - } - - decoder = mp4_faad_new(mp4fh, &track, &audio_format); - if (decoder == NULL) { - mp4ff_close(mp4fh); - return; - } - - file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); - scale = mp4ff_time_scale(mp4fh, track); - - if (scale < 0) { - g_warning("Error getting audio format of mp4 AAC track.\n"); - faacDecClose(decoder); - mp4ff_close(mp4fh); - return; - } - total_time = ((float)file_time) / scale; - - num_samples = mp4ff_num_samples(mp4fh, track); - if (num_samples > (long)(G_MAXINT / sizeof(float))) { - g_warning("Integer overflow.\n"); - faacDecClose(decoder); - mp4ff_close(mp4fh); - return; - } - - file_time = 0.0; - - seek_table = input_stream->seekable - ? g_malloc(sizeof(float) * num_samples) - : NULL; - - decoder_initialized(mpd_decoder, &audio_format, - input_stream->seekable, - total_time); - - for (sample_id = 0; - sample_id < num_samples && cmd != DECODE_COMMAND_STOP; - sample_id++) { - if (cmd == DECODE_COMMAND_SEEK) { - assert(seek_table != NULL); - - seeking = true; - seek_where = decoder_seek_where(mpd_decoder); - } - - if (seeking && seek_table_end > 1 && - seek_table[seek_table_end] >= seek_where) { - int i = 2; - - assert(seek_table != NULL); - - while (seek_table[i] < seek_where) - i++; - sample_id = i - 1; - file_time = seek_table[sample_id]; - } - - dur = mp4ff_get_sample_duration(mp4fh, track, sample_id); - offset = mp4ff_get_sample_offset(mp4fh, track, sample_id); - - if (seek_table != NULL && sample_id > seek_table_end) { - seek_table[sample_id] = file_time; - seek_table_end = sample_id; - } - - if (sample_id == 0) - dur = 0; - if (offset > dur) - dur = 0; - else - dur -= offset; - file_time += ((float)dur) / scale; - - if (seeking && file_time >= seek_where) - seek_position_found = true; - - if (seeking && seek_position_found) { - seek_position_found = false; - seeking = 0; - decoder_command_finished(mpd_decoder); - } - - if (seeking) - continue; - - if (mp4ff_read_sample(mp4fh, track, sample_id, &mp4_buffer, - &mp4_buffer_size) == 0) - break; - -#ifdef HAVE_FAAD_BUFLEN_FUNCS - sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer, - mp4_buffer_size); -#else - sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer); -#endif - - free(mp4_buffer); - - if (frame_info.error > 0) { - g_warning("faad2 error: %s\n", - faacDecGetErrorMessage(frame_info.error)); - break; - } - - if (frame_info.channels != audio_format.channels) { - g_warning("channel count changed from %u to %u", - audio_format.channels, frame_info.channels); - break; - } - -#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE - if (frame_info.samplerate != audio_format.sample_rate) { - g_warning("sample rate changed from %u to %lu", - audio_format.sample_rate, - (unsigned long)frame_info.samplerate); - break; - } -#endif - - if (audio_format.channels * (unsigned long)(dur + offset) > frame_info.samples) { - dur = frame_info.samples / audio_format.channels; - offset = 0; - } - - sample_count = (unsigned long)(dur * audio_format.channels); - - if (sample_count > 0) { - initial = 0; - bit_rate = frame_info.bytesconsumed * 8.0 * - frame_info.channels * scale / - frame_info.samples / 1000 + 0.5; - } - - sample_buffer_length = sample_count * 2; - - sample_buffer += offset * audio_format.channels * 2; - - cmd = decoder_data(mpd_decoder, input_stream, - sample_buffer, sample_buffer_length, - bit_rate); - } - - g_free(seek_table); - faacDecClose(decoder); - mp4ff_close(mp4fh); -} - -static const struct tag_table mp4ff_tags[] = { - { "album artist", TAG_ALBUM_ARTIST }, - { "writer", TAG_COMPOSER }, - { "band", TAG_PERFORMER }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - -static enum tag_type -mp4ff_tag_name_parse(const char *name) -{ - enum tag_type type = tag_table_lookup_i(mp4ff_tags, name); - if (type == TAG_NUM_OF_ITEM_TYPES) - type = tag_name_parse_i(name); - - if (g_ascii_strcasecmp(name, "albumartist") == 0 || - g_ascii_strcasecmp(name, "album_artist") == 0) - return TAG_ALBUM_ARTIST; - - return type; -} - -static bool -mp4ff_scan_stream(struct input_stream *is, - const struct tag_handler *handler, void *handler_ctx) -{ - struct mp4ff_input_stream mis; - int32_t track; - int32_t file_time; - int32_t scale; - int i; - - mp4ff_t *mp4fh = mp4ff_input_stream_open(&mis, NULL, is); - if (mp4fh == NULL) - return false; - - track = mp4_get_aac_track(mp4fh, NULL, NULL, NULL); - if (track < 0) { - mp4ff_close(mp4fh); - return false; - } - - file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); - scale = mp4ff_time_scale(mp4fh, track); - if (scale < 0) { - mp4ff_close(mp4fh); - return false; - } - - tag_handler_invoke_duration(handler, handler_ctx, - ((float)file_time) / scale + 0.5); - - for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) { - char *item; - char *value; - - mp4ff_meta_get_by_index(mp4fh, i, &item, &value); - - tag_handler_invoke_pair(handler, handler_ctx, item, value); - - enum tag_type type = mp4ff_tag_name_parse(item); - if (type != TAG_NUM_OF_ITEM_TYPES) - tag_handler_invoke_tag(handler, handler_ctx, - type, value); - - free(item); - free(value); - } - - mp4ff_close(mp4fh); - - return true; -} - -static const char *const mp4_suffixes[] = { - "m4a", - "m4b", - "mp4", - NULL -}; - -static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL }; - -const struct decoder_plugin mp4ff_decoder_plugin = { - .name = "mp4ff", - .stream_decode = mp4_decode, - .scan_stream = mp4ff_scan_stream, - .suffixes = mp4_suffixes, - .mime_types = mp4_mime_types, -}; diff --git a/src/decoder/mpcdec_decoder_plugin.c b/src/decoder/mpcdec_decoder_plugin.c index d4768b35..77db2416 100644 --- a/src/decoder/mpcdec_decoder_plugin.c +++ b/src/decoder/mpcdec_decoder_plugin.c @@ -70,7 +70,7 @@ mpc_tell_cb(cb_first_arg) { struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - return (long)(data->is->offset); + return (long)input_stream_get_offset(data->is); } static mpc_bool_t @@ -78,7 +78,7 @@ mpc_canseek_cb(cb_first_arg) { struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - return data->is->seekable; + return input_stream_is_seekable(data->is); } static mpc_int32_t @@ -86,7 +86,7 @@ mpc_getsize_cb(cb_first_arg) { struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - return data->is->size; + return input_stream_get_size(data->is); } /* this _looks_ performance-critical, don't de-inline -- eric */ @@ -222,7 +222,7 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) decoder_replay_gain(mpd_decoder, &replay_gain_info); decoder_initialized(mpd_decoder, &audio_format, - is->seekable, + input_stream_is_seekable(is), mpc_streaminfo_get_length(&info)); do { diff --git a/src/decoder/pcm_decoder_plugin.c b/src/decoder/pcm_decoder_plugin.c index fc7dffc0..d529cef5 100644 --- a/src/decoder/pcm_decoder_plugin.c +++ b/src/decoder/pcm_decoder_plugin.c @@ -38,8 +38,9 @@ pcm_stream_decode(struct decoder *decoder, struct input_stream *is) .channels = 2, }; - const bool reverse_endian = is->mime != NULL && - strcmp(is->mime, "audio/x-mpd-cdda-pcm-reverse") == 0; + const char *const mime = input_stream_get_mime_type(is); + const bool reverse_endian = mime != NULL && + strcmp(mime, "audio/x-mpd-cdda-pcm-reverse") == 0; GError *error = NULL; enum decoder_command cmd; @@ -47,10 +48,12 @@ pcm_stream_decode(struct decoder *decoder, struct input_stream *is) double time_to_size = audio_format_time_to_size(&audio_format); float total_time = -1; - if (is->size >= 0) - total_time = is->size / time_to_size; + const goffset size = input_stream_get_size(is); + if (size >= 0) + total_time = size / time_to_size; - decoder_initialized(decoder, &audio_format, is->seekable, total_time); + decoder_initialized(decoder, &audio_format, + input_stream_is_seekable(is), total_time); do { char buffer[4096]; diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx index 5d162f17..175d2ee7 100644 --- a/src/decoder/sidplay_decoder_plugin.cxx +++ b/src/decoder/sidplay_decoder_plugin.cxx @@ -18,9 +18,9 @@ */ #include "config.h" +#include "../decoder_api.h" extern "C" { -#include "../decoder_api.h" #include "tag_handler.h" } @@ -104,7 +104,7 @@ sidplay_init(const struct config_param *param) return true; } -void +static void sidplay_finish() { g_pattern_spec_free(path_with_subtune); @@ -136,7 +136,7 @@ get_container_name(const char *path_fs) * returns tune number from file.sid/tune_xxx.sid style path or 1 if * no subtune is appended */ -static int +static unsigned get_song_num(const char *path_fs) { if(g_pattern_match(path_with_subtune, @@ -172,7 +172,7 @@ get_song_length(const char *path_fs) char md5sum[SIDTUNE_MD5_LENGTH+1]; tune.createMD5(md5sum); - int song_num=get_song_num(path_fs); + const unsigned song_num = get_song_num(path_fs); gsize num_items; gchar **values=g_key_file_get_string_list(songlength_database, @@ -330,7 +330,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) decoder_command_finished(decoder); } - if (song_len > 0 && player.time() >= song_len) + if (song_len > 0 && player.time() >= (unsigned)song_len) break; } while (cmd != DECODE_COMMAND_STOP); diff --git a/src/decoder/sndfile_decoder_plugin.c b/src/decoder/sndfile_decoder_plugin.c index 8dd98236..e70a2dc2 100644 --- a/src/decoder/sndfile_decoder_plugin.c +++ b/src/decoder/sndfile_decoder_plugin.c @@ -32,7 +32,7 @@ sndfile_vio_get_filelen(void *user_data) { const struct input_stream *is = user_data; - return is->size; + return input_stream_get_size(is); } static sf_count_t @@ -45,7 +45,7 @@ sndfile_vio_seek(sf_count_t offset, int whence, void *user_data) if (!success) return -1; - return is->offset; + return input_stream_get_offset(is); } static sf_count_t @@ -79,7 +79,7 @@ sndfile_vio_tell(void *user_data) { const struct input_stream *is = user_data; - return is->offset; + return input_stream_get_offset(is); } /** diff --git a/src/decoder_api.h b/src/decoder_api.h index 6e011c39..3f84ca8b 100644 --- a/src/decoder_api.h +++ b/src/decoder_api.h @@ -38,6 +38,10 @@ #include <stdbool.h> +#ifdef __cplusplus +extern "C" { +#endif + /** * Notify the player thread that it has finished initialization and * that it has read the song's meta data. @@ -152,9 +156,8 @@ decoder_tag(struct decoder *decoder, struct input_stream *is, * @param decoder the decoder object * @param rgi the replay_gain_info object; may be NULL to invalidate * the previous replay gain values - * @return the replay gain adjustment used */ -float +void decoder_replay_gain(struct decoder *decoder, const struct replay_gain_info *replay_gain_info); @@ -162,12 +165,15 @@ decoder_replay_gain(struct decoder *decoder, * Store MixRamp tags. * * @param decoder the decoder object - * @param replay_gain_db the ReplayGain adjustment used for this song * @param mixramp_start the mixramp_start tag; may be NULL to invalidate * @param mixramp_end the mixramp_end tag; may be NULL to invalidate */ void -decoder_mixramp(struct decoder *decoder, float replay_gain_db, +decoder_mixramp(struct decoder *decoder, char *mixramp_start, char *mixramp_end); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/decoder_control.c b/src/decoder_control.c deleted file mode 100644 index 2ce03b66..00000000 --- a/src/decoder_control.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_control.h" -#include "pipe.h" - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "decoder_control" - -struct decoder_control * -dc_new(GCond *client_cond) -{ - struct decoder_control *dc = g_new(struct decoder_control, 1); - - dc->thread = NULL; - - dc->mutex = g_mutex_new(); - dc->cond = g_cond_new(); - dc->client_cond = client_cond; - - dc->state = DECODE_STATE_STOP; - dc->command = DECODE_COMMAND_NONE; - - dc->replay_gain_db = 0; - dc->replay_gain_prev_db = 0; - dc->mixramp_start = NULL; - dc->mixramp_end = NULL; - dc->mixramp_prev_end = NULL; - - return dc; -} - -void -dc_free(struct decoder_control *dc) -{ - g_cond_free(dc->cond); - g_mutex_free(dc->mutex); - g_free(dc->mixramp_start); - g_free(dc->mixramp_end); - g_free(dc->mixramp_prev_end); - g_free(dc); -} - -static void -dc_command_wait_locked(struct decoder_control *dc) -{ - while (dc->command != DECODE_COMMAND_NONE) - g_cond_wait(dc->client_cond, dc->mutex); -} - -static void -dc_command_locked(struct decoder_control *dc, enum decoder_command cmd) -{ - dc->command = cmd; - decoder_signal(dc); - dc_command_wait_locked(dc); -} - -static void -dc_command(struct decoder_control *dc, enum decoder_command cmd) -{ - decoder_lock(dc); - dc_command_locked(dc, cmd); - decoder_unlock(dc); -} - -static void -dc_command_async(struct decoder_control *dc, enum decoder_command cmd) -{ - decoder_lock(dc); - - dc->command = cmd; - decoder_signal(dc); - - decoder_unlock(dc); -} - -void -dc_start(struct decoder_control *dc, struct song *song, - unsigned start_ms, unsigned end_ms, - struct music_buffer *buffer, struct music_pipe *pipe) -{ - assert(song != NULL); - assert(buffer != NULL); - assert(pipe != NULL); - assert(music_pipe_empty(pipe)); - - dc->song = song; - dc->start_ms = start_ms; - dc->end_ms = end_ms; - dc->buffer = buffer; - dc->pipe = pipe; - dc_command(dc, DECODE_COMMAND_START); -} - -void -dc_stop(struct decoder_control *dc) -{ - decoder_lock(dc); - - if (dc->command != DECODE_COMMAND_NONE) - /* Attempt to cancel the current command. If it's too - late and the decoder thread is already executing - the old command, we'll call STOP again in this - function (see below). */ - dc_command_locked(dc, DECODE_COMMAND_STOP); - - if (dc->state != DECODE_STATE_STOP && dc->state != DECODE_STATE_ERROR) - dc_command_locked(dc, DECODE_COMMAND_STOP); - - decoder_unlock(dc); -} - -bool -dc_seek(struct decoder_control *dc, double where) -{ - assert(dc->state != DECODE_STATE_START); - assert(where >= 0.0); - - if (dc->state == DECODE_STATE_STOP || - dc->state == DECODE_STATE_ERROR || !dc->seekable) - return false; - - dc->seek_where = where; - dc->seek_error = false; - dc_command(dc, DECODE_COMMAND_SEEK); - - if (dc->seek_error) - return false; - - return true; -} - -void -dc_quit(struct decoder_control *dc) -{ - assert(dc->thread != NULL); - - dc->quit = true; - dc_command_async(dc, DECODE_COMMAND_STOP); - - g_thread_join(dc->thread); - dc->thread = NULL; -} - -void -dc_mixramp_start(struct decoder_control *dc, char *mixramp_start) -{ - assert(dc != NULL); - - g_free(dc->mixramp_start); - dc->mixramp_start = mixramp_start; -} - -void -dc_mixramp_end(struct decoder_control *dc, char *mixramp_end) -{ - assert(dc != NULL); - - g_free(dc->mixramp_end); - dc->mixramp_end = mixramp_end; -} - -void -dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end) -{ - assert(dc != NULL); - - g_free(dc->mixramp_prev_end); - dc->mixramp_prev_end = mixramp_prev_end; -} diff --git a/src/decoder_control.h b/src/decoder_control.h deleted file mode 100644 index 566b153e..00000000 --- a/src/decoder_control.h +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_CONTROL_H -#define MPD_DECODER_CONTROL_H - -#include "decoder_command.h" -#include "audio_format.h" - -#include <glib.h> - -#include <assert.h> - -enum decoder_state { - DECODE_STATE_STOP = 0, - DECODE_STATE_START, - DECODE_STATE_DECODE, - - /** - * The last "START" command failed, because there was an I/O - * error or because no decoder was able to decode the file. - * This state will only come after START; once the state has - * turned to DECODE, by definition no such error can occur. - */ - DECODE_STATE_ERROR, -}; - -struct decoder_control { - /** the handle of the decoder thread, or NULL if the decoder - thread isn't running */ - GThread *thread; - - /** - * This lock protects #state and #command. - */ - GMutex *mutex; - - /** - * Trigger this object after you have modified #command. This - * is also used by the decoder thread to notify the caller - * when it has finished a command. - */ - GCond *cond; - - /** - * The trigger of this object's client. It is signalled - * whenever an event occurs. - */ - GCond *client_cond; - - enum decoder_state state; - enum decoder_command command; - - bool quit; - bool seek_error; - bool seekable; - double seek_where; - - /** the format of the song file */ - struct audio_format in_audio_format; - - /** the format being sent to the music pipe */ - struct audio_format out_audio_format; - - /** - * The song currently being decoded. This attribute is set by - * the player thread, when it sends the #DECODE_COMMAND_START - * command. - */ - const struct song *song; - - /** - * The initial seek position (in milliseconds), e.g. to the - * start of a sub-track described by a CUE file. - * - * This attribute is set by dc_start(). - */ - unsigned start_ms; - - /** - * The decoder will stop when it reaches this position (in - * milliseconds). 0 means don't stop before the end of the - * file. - * - * This attribute is set by dc_start(). - */ - unsigned end_ms; - - float total_time; - - /** the #music_chunk allocator */ - struct music_buffer *buffer; - - /** - * The destination pipe for decoded chunks. The caller thread - * owns this object, and is responsible for freeing it. - */ - struct music_pipe *pipe; - - float replay_gain_db; - float replay_gain_prev_db; - char *mixramp_start; - char *mixramp_end; - char *mixramp_prev_end; -}; - -G_GNUC_MALLOC -struct decoder_control * -dc_new(GCond *client_cond); - -void -dc_free(struct decoder_control *dc); - -/** - * Locks the #decoder_control object. - */ -static inline void -decoder_lock(struct decoder_control *dc) -{ - g_mutex_lock(dc->mutex); -} - -/** - * Unlocks the #decoder_control object. - */ -static inline void -decoder_unlock(struct decoder_control *dc) -{ - g_mutex_unlock(dc->mutex); -} - -/** - * Waits for a signal on the #decoder_control object. This function - * is only valid in the decoder thread. The object must be locked - * prior to calling this function. - */ -static inline void -decoder_wait(struct decoder_control *dc) -{ - g_cond_wait(dc->cond, dc->mutex); -} - -/** - * Signals the #decoder_control object. This function is only valid - * in the player thread. The object should be locked prior to calling - * this function. - */ -static inline void -decoder_signal(struct decoder_control *dc) -{ - g_cond_signal(dc->cond); -} - -static inline bool -decoder_is_idle(const struct decoder_control *dc) -{ - return dc->state == DECODE_STATE_STOP || - dc->state == DECODE_STATE_ERROR; -} - -static inline bool -decoder_is_starting(const struct decoder_control *dc) -{ - return dc->state == DECODE_STATE_START; -} - -static inline bool -decoder_has_failed(const struct decoder_control *dc) -{ - assert(dc->command == DECODE_COMMAND_NONE); - - return dc->state == DECODE_STATE_ERROR; -} - -static inline bool -decoder_lock_is_idle(struct decoder_control *dc) -{ - bool ret; - - decoder_lock(dc); - ret = decoder_is_idle(dc); - decoder_unlock(dc); - - return ret; -} - -static inline bool -decoder_lock_is_starting(struct decoder_control *dc) -{ - bool ret; - - decoder_lock(dc); - ret = decoder_is_starting(dc); - decoder_unlock(dc); - - return ret; -} - -static inline bool -decoder_lock_has_failed(struct decoder_control *dc) -{ - bool ret; - - decoder_lock(dc); - ret = decoder_has_failed(dc); - decoder_unlock(dc); - - return ret; -} - -static inline const struct song * -decoder_current_song(const struct decoder_control *dc) -{ - switch (dc->state) { - case DECODE_STATE_STOP: - case DECODE_STATE_ERROR: - return NULL; - - case DECODE_STATE_START: - case DECODE_STATE_DECODE: - return dc->song; - } - - assert(false); - return NULL; -} - -/** - * Start the decoder. - * - * @param the decoder - * @param song the song to be decoded - * @param start_ms see #decoder_control - * @param end_ms see #decoder_control - * @param pipe the pipe which receives the decoded chunks (owned by - * the caller) - */ -void -dc_start(struct decoder_control *dc, struct song *song, - unsigned start_ms, unsigned end_ms, - struct music_buffer *buffer, struct music_pipe *pipe); - -void -dc_stop(struct decoder_control *dc); - -bool -dc_seek(struct decoder_control *dc, double where); - -void -dc_quit(struct decoder_control *dc); - -void -dc_mixramp_start(struct decoder_control *dc, char *mixramp_start); - -void -dc_mixramp_end(struct decoder_control *dc, char *mixramp_end); - -void -dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end); - -#endif diff --git a/src/decoder_error.h b/src/decoder_error.h new file mode 100644 index 00000000..a12a3193 --- /dev/null +++ b/src/decoder_error.h @@ -0,0 +1,35 @@ +/* + * 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_ERROR_H +#define MPD_DECODER_ERROR_H + +#include <glib.h> + +/** + * Quark for GError.domain. + */ +G_GNUC_CONST +static inline GQuark +decoder_quark(void) +{ + return g_quark_from_static_string("decoder"); +} + +#endif diff --git a/src/decoder_plugin.h b/src/decoder_plugin.h index 933ba675..b7ab738b 100644 --- a/src/decoder_plugin.h +++ b/src/decoder_plugin.h @@ -190,6 +190,10 @@ decoder_plugin_container_scan( const struct decoder_plugin *plugin, return plugin->container_scan(pathname, tnum); } +#ifdef __cplusplus +extern "C" { +#endif + /** * Does the plugin announce the specified file name suffix? */ @@ -204,4 +208,8 @@ bool decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin, const char *mime_type); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/directory.c b/src/directory.c deleted file mode 100644 index e886698d..00000000 --- a/src/directory.c +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "directory.h" -#include "song.h" -#include "song_sort.h" -#include "playlist_vector.h" -#include "path.h" -#include "util/list_sort.h" -#include "db_visitor.h" -#include "db_lock.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -struct directory * -directory_new(const char *path, struct directory *parent) -{ - struct directory *directory; - size_t pathlen = strlen(path); - - assert(path != NULL); - assert((*path == 0) == (parent == NULL)); - - directory = g_malloc0(sizeof(*directory) - - sizeof(directory->path) + pathlen + 1); - INIT_LIST_HEAD(&directory->children); - INIT_LIST_HEAD(&directory->songs); - INIT_LIST_HEAD(&directory->playlists); - - directory->parent = parent; - memcpy(directory->path, path, pathlen + 1); - - return directory; -} - -void -directory_free(struct directory *directory) -{ - playlist_vector_deinit(&directory->playlists); - - struct song *song, *ns; - directory_for_each_song_safe(song, ns, directory) - song_free(song); - - struct directory *child, *n; - directory_for_each_child_safe(child, n, directory) - directory_free(child); - - g_free(directory); - /* this resets last dir returned */ - /*directory_get_path(NULL); */ -} - -void -directory_delete(struct directory *directory) -{ - assert(holding_db_lock()); - assert(directory != NULL); - assert(directory->parent != NULL); - - list_del(&directory->siblings); - directory_free(directory); -} - -const char * -directory_get_name(const struct directory *directory) -{ - assert(!directory_is_root(directory)); - assert(directory->path != NULL); - - const char *slash = strrchr(directory->path, '/'); - assert((slash == NULL) == directory_is_root(directory->parent)); - - return slash != NULL - ? slash + 1 - : directory->path; -} - -struct directory * -directory_new_child(struct directory *parent, const char *name_utf8) -{ - assert(holding_db_lock()); - assert(parent != NULL); - assert(name_utf8 != NULL); - assert(*name_utf8 != 0); - - char *allocated; - const char *path_utf8; - if (directory_is_root(parent)) { - allocated = NULL; - path_utf8 = name_utf8; - } else { - allocated = g_strconcat(directory_get_path(parent), - "/", name_utf8, NULL); - path_utf8 = allocated; - } - - struct directory *directory = directory_new(path_utf8, parent); - g_free(allocated); - - list_add_tail(&directory->siblings, &parent->children); - return directory; -} - -struct directory * -directory_get_child(const struct directory *directory, const char *name) -{ - assert(holding_db_lock()); - - struct directory *child; - directory_for_each_child(child, directory) - if (strcmp(directory_get_name(child), name) == 0) - return child; - - return NULL; -} - -void -directory_prune_empty(struct directory *directory) -{ - assert(holding_db_lock()); - - struct directory *child, *n; - directory_for_each_child_safe(child, n, directory) { - directory_prune_empty(child); - - if (directory_is_empty(child)) - directory_delete(child); - } -} - -struct directory * -directory_lookup_directory(struct directory *directory, const char *uri) -{ - assert(holding_db_lock()); - assert(uri != NULL); - - if (isRootDirectory(uri)) - return directory; - - char *duplicated = g_strdup(uri), *name = duplicated; - - while (1) { - char *slash = strchr(name, '/'); - if (slash == name) { - directory = NULL; - break; - } - - if (slash != NULL) - *slash = '\0'; - - directory = directory_get_child(directory, name); - if (directory == NULL || slash == NULL) - break; - - name = slash + 1; - } - - g_free(duplicated); - - return directory; -} - -void -directory_add_song(struct directory *directory, struct song *song) -{ - assert(holding_db_lock()); - assert(directory != NULL); - assert(song != NULL); - assert(song->parent == directory); - - list_add_tail(&song->siblings, &directory->songs); -} - -void -directory_remove_song(G_GNUC_UNUSED struct directory *directory, - struct song *song) -{ - assert(holding_db_lock()); - assert(directory != NULL); - assert(song != NULL); - assert(song->parent == directory); - - list_del(&song->siblings); -} - -struct song * -directory_get_song(const struct directory *directory, const char *name_utf8) -{ - assert(holding_db_lock()); - assert(directory != NULL); - assert(name_utf8 != NULL); - - struct song *song; - directory_for_each_song(song, directory) { - assert(song->parent == directory); - - if (strcmp(song->uri, name_utf8) == 0) - return song; - } - - return NULL; -} - -struct song * -directory_lookup_song(struct directory *directory, const char *uri) -{ - char *duplicated, *base; - - assert(holding_db_lock()); - assert(directory != NULL); - assert(uri != NULL); - - duplicated = g_strdup(uri); - base = strrchr(duplicated, '/'); - - if (base != NULL) { - *base++ = 0; - directory = directory_lookup_directory(directory, duplicated); - if (directory == NULL) { - g_free(duplicated); - return NULL; - } - } else - base = duplicated; - - struct song *song = directory_get_song(directory, base); - assert(song == NULL || song->parent == directory); - - g_free(duplicated); - return song; - -} - -static int -directory_cmp(G_GNUC_UNUSED void *priv, - struct list_head *_a, struct list_head *_b) -{ - const struct directory *a = (const struct directory *)_a; - const struct directory *b = (const struct directory *)_b; - return g_utf8_collate(a->path, b->path); -} - -void -directory_sort(struct directory *directory) -{ - assert(holding_db_lock()); - - list_sort(NULL, &directory->children, directory_cmp); - song_list_sort(&directory->songs); - - struct directory *child; - directory_for_each_child(child, directory) - directory_sort(child); -} - -bool -directory_walk(const struct directory *directory, bool recursive, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - assert(directory != NULL); - assert(visitor != NULL); - assert(error_r == NULL || *error_r == NULL); - - if (visitor->song != NULL) { - struct song *song; - directory_for_each_song(song, directory) - if (!visitor->song(song, ctx, error_r)) - return false; - } - - if (visitor->playlist != NULL) { - struct playlist_metadata *i; - directory_for_each_playlist(i, directory) - if (!visitor->playlist(i, directory, ctx, error_r)) - return false; - } - - struct directory *child; - directory_for_each_child(child, directory) { - if (visitor->directory != NULL && - !visitor->directory(child, ctx, error_r)) - return false; - - if (recursive && - !directory_walk(child, recursive, visitor, ctx, error_r)) - return false; - } - - return true; -} diff --git a/src/directory.h b/src/directory.h deleted file mode 100644 index b3cd9c8c..00000000 --- a/src/directory.h +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DIRECTORY_H -#define MPD_DIRECTORY_H - -#include "check.h" -#include "util/list.h" - -#include <glib.h> -#include <stdbool.h> -#include <sys/types.h> - -#define DEVICE_INARCHIVE (dev_t)(-1) -#define DEVICE_CONTAINER (dev_t)(-2) - -#define directory_for_each_child(pos, directory) \ - list_for_each_entry(pos, &directory->children, siblings) - -#define directory_for_each_child_safe(pos, n, directory) \ - list_for_each_entry_safe(pos, n, &directory->children, siblings) - -#define directory_for_each_song(pos, directory) \ - list_for_each_entry(pos, &directory->songs, siblings) - -#define directory_for_each_song_safe(pos, n, directory) \ - list_for_each_entry_safe(pos, n, &directory->songs, siblings) - -#define directory_for_each_playlist(pos, directory) \ - list_for_each_entry(pos, &directory->playlists, siblings) - -#define directory_for_each_playlist_safe(pos, n, directory) \ - list_for_each_entry_safe(pos, n, &directory->playlists, siblings) - -struct song; -struct db_visitor; - -struct directory { - /** - * Pointers to the siblings of this directory within the - * parent directory. It is unused (undefined) in the root - * directory. - * - * This attribute is protected with the global #db_mutex. - * Read access in the update thread does not need protection. - */ - struct list_head siblings; - - /** - * A doubly linked list of child directories. - * - * This attribute is protected with the global #db_mutex. - * Read access in the update thread does not need protection. - */ - struct list_head children; - - /** - * A doubly linked list of songs within this directory. - * - * This attribute is protected with the global #db_mutex. - * Read access in the update thread does not need protection. - */ - struct list_head songs; - - struct list_head playlists; - - struct directory *parent; - time_t mtime; - ino_t inode; - dev_t device; - bool have_stat; /* not needed if ino_t == dev_t == 0 is impossible */ - char path[sizeof(long)]; -}; - -static inline bool -isRootDirectory(const char *name) -{ - return name[0] == 0 || (name[0] == '/' && name[1] == 0); -} - -/** - * Generic constructor for #directory object. - */ -G_GNUC_MALLOC -struct directory * -directory_new(const char *dirname, struct directory *parent); - -/** - * Create a new root #directory object. - */ -G_GNUC_MALLOC -static inline struct directory * -directory_new_root(void) -{ - return directory_new("", NULL); -} - -/** - * Free this #directory object (and the whole object tree within it), - * assuming it was already removed from the parent. - */ -void -directory_free(struct directory *directory); - -/** - * Remove this #directory object from its parent and free it. This - * must not be called with the root directory. - * - * Caller must lock the #db_mutex. - */ -void -directory_delete(struct directory *directory); - -static inline bool -directory_is_empty(const struct directory *directory) -{ - return list_empty(&directory->children) && - list_empty(&directory->songs) && - list_empty(&directory->playlists); -} - -static inline const char * -directory_get_path(const struct directory *directory) -{ - return directory->path; -} - -/** - * Is this the root directory of the music database? - */ -static inline bool -directory_is_root(const struct directory *directory) -{ - return directory->parent == NULL; -} - -/** - * Returns the base name of the directory. - */ -G_GNUC_PURE -const char * -directory_get_name(const struct directory *directory); - -/** - * Caller must lock the #db_mutex. - */ -G_GNUC_PURE -struct directory * -directory_get_child(const struct directory *directory, const char *name); - -/** - * Create a new #directory object as a child of the given one. - * - * Caller must lock the #db_mutex. - * - * @param parent the parent directory the new one will be added to - * @param name_utf8 the UTF-8 encoded name of the new sub directory - */ -G_GNUC_MALLOC -struct directory * -directory_new_child(struct directory *parent, const char *name_utf8); - -/** - * Look up a sub directory, and create the object if it does not - * exist. - * - * Caller must lock the #db_mutex. - */ -static inline struct directory * -directory_make_child(struct directory *directory, const char *name_utf8) -{ - struct directory *child = directory_get_child(directory, name_utf8); - if (child == NULL) - child = directory_new_child(directory, name_utf8); - return child; -} - -/** - * Caller must lock the #db_mutex. - */ -void -directory_prune_empty(struct directory *directory); - -/** - * Looks up a directory by its relative URI. - * - * @param directory the parent (or grandparent, ...) directory - * @param uri the relative URI - * @return the directory, or NULL if none was found - */ -struct directory * -directory_lookup_directory(struct directory *directory, const char *uri); - -/** - * Add a song object to this directory. Its "parent" attribute must - * be set already. - */ -void -directory_add_song(struct directory *directory, struct song *song); - -/** - * Remove a song object from this directory (which effectively - * invalidates the song object, because the "parent" attribute becomes - * stale), but does not free it. - */ -void -directory_remove_song(struct directory *directory, struct song *song); - -/** - * Look up a song in this directory by its name. - * - * Caller must lock the #db_mutex. - */ -G_GNUC_PURE -struct song * -directory_get_song(const struct directory *directory, const char *name_utf8); - -/** - * Looks up a song by its relative URI. - * - * Caller must lock the #db_mutex. - * - * @param directory the parent (or grandparent, ...) directory - * @param uri the relative URI - * @return the song, or NULL if none was found - */ -struct song * -directory_lookup_song(struct directory *directory, const char *uri); - -/** - * Sort all directory entries recursively. - * - * Caller must lock the #db_mutex. - */ -void -directory_sort(struct directory *directory); - -/** - * Caller must lock #db_mutex. - */ -bool -directory_walk(const struct directory *directory, bool recursive, - const struct db_visitor *visitor, void *ctx, - GError **error_r); - -#endif diff --git a/src/dsd2pcm/dsd2pcm.hpp b/src/dsd2pcm/dsd2pcm.hpp index b1b2ae1c..8f3f5519 100644 --- a/src/dsd2pcm/dsd2pcm.hpp +++ b/src/dsd2pcm/dsd2pcm.hpp @@ -13,11 +13,9 @@ class dxd { dsd2pcm_ctx *handle; public: - dxd() : handle(dsd2pcm_init()) - { if (!handle) throw std::runtime_error("wtf?!"); } + dxd() : handle(dsd2pcm_init()) {} - dxd(dxd const& x) : handle(dsd2pcm_clone(x.handle)) - { if (!handle) throw std::runtime_error("wtf?!"); } + dxd(dxd const& x) : handle(dsd2pcm_clone(x.handle)) {} ~dxd() { dsd2pcm_destroy(handle); } diff --git a/src/dsd2pcm/noiseshape.hpp b/src/dsd2pcm/noiseshape.hpp index 726272f9..1fc698b3 100644 --- a/src/dsd2pcm/noiseshape.hpp +++ b/src/dsd2pcm/noiseshape.hpp @@ -14,14 +14,12 @@ class noise_shaper public: noise_shaper(int sos_count, const float *bbaa) { - if (noise_shape_init(&ctx,sos_count,bbaa)) - throw std::runtime_error("noise shaper initialization failed"); + noise_shape_init(&ctx, sos_count, bbaa); } noise_shaper(noise_shaper const& x) { - if (noise_shape_clone(&x.ctx,&ctx)) - throw std::runtime_error("noise shaper initialization failed"); + noise_shape_clone(&x.ctx,&ctx); } ~noise_shaper() @@ -31,8 +29,7 @@ public: { if (this != &x) { noise_shape_destroy(&ctx); - if (noise_shape_clone(&x.ctx,&ctx)) - throw std::runtime_error("noise shaper initialization failed"); + noise_shape_clone(&x.ctx,&ctx); } return *this; } diff --git a/src/dummy.cxx b/src/dummy.cxx deleted file mode 100644 index b21555d0..00000000 --- a/src/dummy.cxx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Just a dummy C++ file that is linked to work around an automake - * bug: automake uses CXXLD when at least one source is C++, but when - * you link a static library with a C++ source, it uses CCLD. This - * causes linker problems (undefined reference to 'operator - * delete(void*)'), because CCLD does not link with libstdc++. - * - * By linking with this empty C++ source, automake decides to use - * CXXLD. - * - */ diff --git a/src/encoder/OggStream.hxx b/src/encoder/OggStream.hxx new file mode 100644 index 00000000..ce847f49 --- /dev/null +++ b/src/encoder/OggStream.hxx @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OGG_STREAM_HXX +#define MPD_OGG_STREAM_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +#include <assert.h> +#include <string.h> +#include <stdint.h> + +class OggStream { + ogg_stream_state state; + + bool flush; + +#ifndef NDEBUG + bool initialized; +#endif + +public: +#ifndef NDEBUG + OggStream():initialized(false) {} + ~OggStream() { + assert(!initialized); + } +#endif + + void Initialize(int serialno) { + assert(!initialized); + + ogg_stream_init(&state, serialno); + + /* set "flush" to true, so the caller gets the full + headers on the first read() */ + flush = true; + +#ifndef NDEBUG + initialized = true; +#endif + } + + void Reinitialize(int serialno) { + assert(initialized); + + ogg_stream_reset_serialno(&state, serialno); + + /* set "flush" to true, so the caller gets the full + headers on the first read() */ + flush = true; + } + + void Deinitialize() { + assert(initialized); + + ogg_stream_clear(&state); + +#ifndef NDEBUG + initialized = false; +#endif + } + + void Flush() { + assert(initialized); + + flush = true; + } + + void PacketIn(const ogg_packet &packet) { + assert(initialized); + + ogg_stream_packetin(&state, + const_cast<ogg_packet *>(&packet)); + } + + bool PageOut(ogg_page &page) { + int result = ogg_stream_pageout(&state, &page); + if (result == 0 && flush) { + flush = false; + result = ogg_stream_flush(&state, &page); + } + + return result != 0; + } + + size_t PageOut(void *_buffer, size_t size) { + ogg_page page; + if (!PageOut(page)) + return 0; + + assert(page.header_len > 0 || page.body_len > 0); + + size_t header_len = (size_t)page.header_len; + size_t body_len = (size_t)page.body_len; + assert(header_len <= size); + + if (header_len + body_len > size) + /* TODO: better overflow handling */ + body_len = size - header_len; + + uint8_t *buffer = (uint8_t *)_buffer; + memcpy(buffer, page.header, header_len); + memcpy(buffer + header_len, page.body, body_len); + + return header_len + body_len; + } +}; + +#endif diff --git a/src/encoder/OpusEncoderPlugin.cxx b/src/encoder/OpusEncoderPlugin.cxx new file mode 100644 index 00000000..8d2c0974 --- /dev/null +++ b/src/encoder/OpusEncoderPlugin.cxx @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OpusEncoderPlugin.hxx" +#include "OggStream.hxx" +#include "encoder_api.h" +#include "encoder_plugin.h" +#include "audio_format.h" +#include "mpd_error.h" + +#include <opus.h> +#include <ogg/ogg.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "opus_encoder" + +struct opus_encoder { + /** the base class */ + struct encoder encoder; + + /* configuration */ + + opus_int32 bitrate; + int complexity; + int signal; + + /* runtime information */ + + struct audio_format 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_struct_init(&encoder, &opus_encoder_plugin); + } +}; + +gcc_const +static inline GQuark +opus_encoder_quark(void) +{ + return g_quark_from_static_string("opus_encoder"); +} + +static bool +opus_encoder_configure(struct opus_encoder *encoder, + const struct config_param *param, GError **error_r) +{ + const char *value = config_get_block_string(param, "bitrate", "auto"); + if (strcmp(value, "auto") == 0) + encoder->bitrate = OPUS_AUTO; + else if (strcmp(value, "max") == 0) + encoder->bitrate = OPUS_BITRATE_MAX; + else { + char *endptr; + encoder->bitrate = strtoul(value, &endptr, 10); + if (endptr == value || *endptr != 0 || + encoder->bitrate < 500 || encoder->bitrate > 512000) { + g_set_error(error_r, opus_encoder_quark(), 0, + "Invalid bit rate"); + return false; + } + } + + encoder->complexity = config_get_block_unsigned(param, "complexity", + 10); + if (encoder->complexity > 10) { + g_set_error(error_r, opus_encoder_quark(), 0, + "Invalid complexity"); + return false; + } + + value = config_get_block_string(param, "signal", "auto"); + if (strcmp(value, "auto") == 0) + encoder->bitrate = OPUS_AUTO; + else if (strcmp(value, "voice") == 0) + encoder->bitrate = OPUS_SIGNAL_VOICE; + else if (strcmp(value, "music") == 0) + encoder->bitrate = OPUS_SIGNAL_MUSIC; + else { + g_set_error(error_r, opus_encoder_quark(), 0, + "Invalid signal"); + return false; + } + + return true; +} + +static struct encoder * +opus_encoder_init(const struct config_param *param, GError **error) +{ + opus_encoder *encoder = new opus_encoder(); + + /* load configuration from "param" */ + if (!opus_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + delete encoder; + return NULL; + } + + return &encoder->encoder; +} + +static void +opus_encoder_finish(struct 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(struct encoder *_encoder, + struct audio_format *audio_format, + GError **error_r) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + /* libopus supports only 48 kHz */ + audio_format->sample_rate = 48000; + + if (audio_format->channels > 2) + audio_format->channels = 1; + + switch ((enum sample_format)audio_format->format) { + case SAMPLE_FORMAT_S16: + case SAMPLE_FORMAT_FLOAT: + break; + + case SAMPLE_FORMAT_S8: + audio_format->format = SAMPLE_FORMAT_S16; + break; + + default: + audio_format->format = SAMPLE_FORMAT_FLOAT; + break; + } + + encoder->audio_format = *audio_format; + encoder->frame_size = audio_format_frame_size(audio_format); + + int error; + encoder->enc = opus_encoder_create(audio_format->sample_rate, + audio_format->channels, + OPUS_APPLICATION_AUDIO, + &error); + if (encoder->enc == nullptr) { + g_set_error_literal(error_r, opus_encoder_quark(), error, + opus_strerror(error)); + return false; + } + + opus_encoder_ctl(encoder->enc, OPUS_SET_BITRATE(encoder->bitrate)); + opus_encoder_ctl(encoder->enc, + OPUS_SET_COMPLEXITY(encoder->complexity)); + opus_encoder_ctl(encoder->enc, OPUS_SET_SIGNAL(encoder->signal)); + + opus_encoder_ctl(encoder->enc, OPUS_GET_LOOKAHEAD(&encoder->lookahead)); + + encoder->buffer_frames = audio_format->sample_rate / 50; + encoder->buffer_size = encoder->frame_size * encoder->buffer_frames; + encoder->buffer_position = 0; + encoder->buffer = (unsigned char *)g_malloc(encoder->buffer_size); + + encoder->stream.Initialize(g_random_int()); + encoder->packetno = 0; + + return true; +} + +static void +opus_encoder_close(struct encoder *_encoder) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Deinitialize(); + g_free(encoder->buffer); + opus_encoder_destroy(encoder->enc); +} + +static bool +opus_encoder_do_encode(struct opus_encoder *encoder, bool eos, + GError **error_r) +{ + assert(encoder->buffer_position == encoder->buffer_size); + + opus_int32 result = + encoder->audio_format.format == SAMPLE_FORMAT_S16 + ? opus_encode(encoder->enc, + (const opus_int16 *)encoder->buffer, + encoder->buffer_frames, + encoder->buffer2, + sizeof(encoder->buffer2)) + : opus_encode_float(encoder->enc, + (const float *)encoder->buffer, + encoder->buffer_frames, + encoder->buffer2, + sizeof(encoder->buffer2)); + if (result < 0) { + g_set_error_literal(error_r, opus_encoder_quark(), 0, + "Opus encoder error"); + return false; + } + + encoder->granulepos += encoder->buffer_frames; + + ogg_packet packet; + packet.packet = encoder->buffer2; + packet.bytes = result; + packet.b_o_s = false; + packet.e_o_s = eos; + packet.granulepos = encoder->granulepos; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + + encoder->buffer_position = 0; + + return true; +} + +static bool +opus_encoder_end(struct encoder *_encoder, GError **error_r) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Flush(); + + memset(encoder->buffer + encoder->buffer_position, 0, + encoder->buffer_size - encoder->buffer_position); + encoder->buffer_position = encoder->buffer_size; + + return opus_encoder_do_encode(encoder, true, error_r); +} + +static bool +opus_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Flush(); + return true; +} + +static bool +opus_encoder_write_silence(struct opus_encoder *encoder, unsigned fill_frames, + GError **error_r) +{ + size_t fill_bytes = fill_frames * encoder->frame_size; + + while (fill_bytes > 0) { + size_t nbytes = + encoder->buffer_size - encoder->buffer_position; + if (nbytes > fill_bytes) + nbytes = fill_bytes; + + memset(encoder->buffer + encoder->buffer_position, + 0, nbytes); + encoder->buffer_position += nbytes; + fill_bytes -= nbytes; + + if (encoder->buffer_position == encoder->buffer_size && + !opus_encoder_do_encode(encoder, false, error_r)) + return false; + } + + return true; +} + +static bool +opus_encoder_write(struct encoder *_encoder, + const void *_data, size_t length, + GError **error_r) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + const uint8_t *data = (const uint8_t *)_data; + + if (encoder->lookahead > 0) { + /* generate some silence at the beginning of the + stream */ + + assert(encoder->buffer_position == 0); + + if (!opus_encoder_write_silence(encoder, encoder->lookahead, + error_r)) + return false; + + encoder->lookahead = 0; + } + + while (length > 0) { + size_t nbytes = + encoder->buffer_size - encoder->buffer_position; + if (nbytes > length) + nbytes = length; + + memcpy(encoder->buffer + encoder->buffer_position, + data, nbytes); + data += nbytes; + length -= nbytes; + encoder->buffer_position += nbytes; + + if (encoder->buffer_position == encoder->buffer_size && + !opus_encoder_do_encode(encoder, false, error_r)) + return false; + } + + return true; +} + +static void +opus_encoder_generate_head(struct opus_encoder *encoder) +{ + unsigned char header[19]; + memcpy(header, "OpusHead", 8); + header[8] = 1; + header[9] = encoder->audio_format.channels; + *(uint16_t *)(header + 10) = GUINT16_TO_LE(encoder->lookahead); + *(uint32_t *)(header + 12) = + GUINT32_TO_LE(encoder->audio_format.sample_rate); + header[16] = 0; + header[17] = 0; + header[18] = 0; + + ogg_packet packet; + packet.packet = header; + packet.bytes = 19; + packet.b_o_s = true; + packet.e_o_s = false; + packet.granulepos = 0; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + encoder->stream.Flush(); +} + +static void +opus_encoder_generate_tags(struct opus_encoder *encoder) +{ + const char *version = opus_get_version_string(); + size_t version_length = strlen(version); + + size_t comments_size = 8 + 4 + version_length + 4; + unsigned char *comments = (unsigned char *)g_malloc(comments_size); + memcpy(comments, "OpusTags", 8); + *(uint32_t *)(comments + 8) = GUINT32_TO_LE(version_length); + memcpy(comments + 12, version, version_length); + *(uint32_t *)(comments + 12 + version_length) = GUINT32_TO_LE(0); + + ogg_packet packet; + packet.packet = comments; + packet.bytes = comments_size; + packet.b_o_s = false; + packet.e_o_s = false; + packet.granulepos = 0; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + encoder->stream.Flush(); + + g_free(comments); +} + +static size_t +opus_encoder_read(struct encoder *_encoder, void *dest, size_t length) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + if (encoder->packetno == 0) + opus_encoder_generate_head(encoder); + else if (encoder->packetno == 1) + opus_encoder_generate_tags(encoder); + + return encoder->stream.PageOut(dest, length); +} + +static const char * +opus_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +{ + return "audio/ogg"; +} + +const struct encoder_plugin opus_encoder_plugin = { + "opus", + opus_encoder_init, + opus_encoder_finish, + opus_encoder_open, + opus_encoder_close, + opus_encoder_end, + opus_encoder_flush, + nullptr, + nullptr, + opus_encoder_write, + opus_encoder_read, + opus_encoder_get_mime_type, +}; diff --git a/src/encoder/OpusEncoderPlugin.hxx b/src/encoder/OpusEncoderPlugin.hxx new file mode 100644 index 00000000..f5437720 --- /dev/null +++ b/src/encoder/OpusEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ENCODER_OPUS_H +#define MPD_ENCODER_OPUS_H + +extern const struct encoder_plugin opus_encoder_plugin; + +#endif diff --git a/src/encoder/vorbis_encoder.c b/src/encoder/VorbisEncoderPlugin.cxx index 468cf38e..dc7ef0d5 100644 --- a/src/encoder/vorbis_encoder.c +++ b/src/encoder/VorbisEncoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,8 @@ */ #include "config.h" +#include "VorbisEncoderPlugin.hxx" +#include "OggStream.hxx" #include "encoder_api.h" #include "encoder_plugin.h" #include "tag.h" @@ -44,16 +46,16 @@ struct vorbis_encoder { struct audio_format audio_format; - ogg_stream_state os; - vorbis_dsp_state vd; vorbis_block vb; vorbis_info vi; - bool flush; -}; + OggStream stream; -extern const struct encoder_plugin vorbis_encoder_plugin; + vorbis_encoder() { + encoder_struct_init(&encoder, &vorbis_encoder_plugin); + } +}; static inline GQuark vorbis_encoder_quark(void) @@ -65,8 +67,8 @@ static bool vorbis_encoder_configure(struct vorbis_encoder *encoder, const struct config_param *param, GError **error) { - const char *value = config_get_block_string(param, "quality", NULL); - if (value != NULL) { + const char *value = config_get_block_string(param, "quality", nullptr); + if (value != nullptr) { /* a quality was configured (VBR) */ char *endptr; @@ -81,7 +83,7 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder, return false; } - if (config_get_block_string(param, "bitrate", NULL) != NULL) { + if (config_get_block_string(param, "bitrate", nullptr) != nullptr) { g_set_error(error, vorbis_encoder_quark(), 0, "quality and bitrate are " "both defined (line %i)", @@ -91,8 +93,8 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder, } else { /* a bit rate was configured */ - value = config_get_block_string(param, "bitrate", NULL); - if (value == NULL) { + value = config_get_block_string(param, "bitrate", nullptr); + if (value == nullptr) { g_set_error(error, vorbis_encoder_quark(), 0, "neither bitrate nor quality defined " "at line %i", @@ -118,14 +120,13 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder, static struct encoder * vorbis_encoder_init(const struct config_param *param, GError **error) { - struct vorbis_encoder *encoder = g_new(struct vorbis_encoder, 1); - encoder_struct_init(&encoder->encoder, &vorbis_encoder_plugin); + 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 */ - g_free(encoder); - return NULL; + delete encoder; + return nullptr; } return &encoder->encoder; @@ -138,7 +139,7 @@ vorbis_encoder_finish(struct encoder *_encoder) /* the real libvorbis/libogg cleanup was already performed by vorbis_encoder_close(), so no real work here */ - g_free(encoder); + delete encoder; } static bool @@ -174,7 +175,7 @@ vorbis_encoder_reinit(struct vorbis_encoder *encoder, GError **error) vorbis_analysis_init(&encoder->vd, &encoder->vi); vorbis_block_init(&encoder->vd, &encoder->vb); - ogg_stream_init(&encoder->os, g_random_int()); + encoder->stream.Initialize(g_random_int()); return true; } @@ -187,9 +188,9 @@ vorbis_encoder_headerout(struct vorbis_encoder *encoder, vorbis_comment *vc) vorbis_analysis_headerout(&encoder->vd, vc, &packet, &comments, &codebooks); - ogg_stream_packetin(&encoder->os, &packet); - ogg_stream_packetin(&encoder->os, &comments); - ogg_stream_packetin(&encoder->os, &codebooks); + encoder->stream.PacketIn(packet); + encoder->stream.PacketIn(comments); + encoder->stream.PacketIn(codebooks); } static void @@ -209,7 +210,7 @@ vorbis_encoder_open(struct encoder *_encoder, { struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - audio_format->format = SAMPLE_FORMAT_S16; + audio_format->format = SAMPLE_FORMAT_FLOAT; encoder->audio_format = *audio_format; @@ -218,17 +219,13 @@ vorbis_encoder_open(struct encoder *_encoder, vorbis_encoder_send_header(encoder); - /* set "flush" to true, so the caller gets the full headers on - the first read() */ - encoder->flush = true; - return true; } static void vorbis_encoder_clear(struct vorbis_encoder *encoder) { - ogg_stream_clear(&encoder->os); + encoder->stream.Deinitialize(); vorbis_block_clear(&encoder->vb); vorbis_dsp_clear(&encoder->vd); vorbis_info_clear(&encoder->vi); @@ -246,12 +243,12 @@ static void vorbis_encoder_blockout(struct vorbis_encoder *encoder) { while (vorbis_analysis_blockout(&encoder->vd, &encoder->vb) == 1) { - vorbis_analysis(&encoder->vb, NULL); + vorbis_analysis(&encoder->vb, nullptr); vorbis_bitrate_addblock(&encoder->vb); ogg_packet packet; while (vorbis_bitrate_flushpacket(&encoder->vd, &packet)) - ogg_stream_packetin(&encoder->os, &packet); + encoder->stream.PacketIn(packet); } } @@ -260,7 +257,7 @@ vorbis_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) { struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - encoder->flush = true; + encoder->stream.Flush(); return true; } @@ -279,7 +276,7 @@ vorbis_encoder_pre_tag(struct encoder *_encoder, G_GNUC_UNUSED GError **error) vorbis_analysis_init(&encoder->vd, &encoder->vi); vorbis_block_init(&encoder->vd, &encoder->vb); - encoder->flush = true; + encoder->stream.Flush(); return true; } @@ -308,28 +305,23 @@ vorbis_encoder_tag(struct encoder *_encoder, const struct tag *tag, /* reset ogg_stream_state and begin a new stream */ - ogg_stream_reset_serialno(&encoder->os, g_random_int()); + encoder->stream.Reinitialize(g_random_int()); /* send that vorbis_comment to the ogg_stream_state */ vorbis_encoder_headerout(encoder, &comment); vorbis_comment_clear(&comment); - /* the next vorbis_encoder_read() call should flush the - ogg_stream_state */ - - encoder->flush = true; - return true; } static void -pcm16_to_vorbis_buffer(float **dest, const int16_t *src, - unsigned num_frames, unsigned num_channels) +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++ / 32768.0; + dest[j][i] = *src++; } static bool @@ -344,10 +336,11 @@ vorbis_encoder_write(struct encoder *_encoder, /* this is for only 16-bit audio */ - pcm16_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd, - num_frames), - (const int16_t *)data, - num_frames, encoder->audio_format.channels); + 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); @@ -355,34 +348,11 @@ vorbis_encoder_write(struct encoder *_encoder, } static size_t -vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length) +vorbis_encoder_read(struct encoder *_encoder, void *dest, size_t length) { struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - unsigned char *dest = _dest; - - ogg_page page; - int ret = ogg_stream_pageout(&encoder->os, &page); - if (ret == 0 && encoder->flush) { - encoder->flush = false; - ret = ogg_stream_flush(&encoder->os, &page); - - } - - if (ret == 0) - return 0; - - assert(page.header_len > 0 || page.body_len > 0); - - size_t nbytes = (size_t)page.header_len + (size_t)page.body_len; - - if (nbytes > length) - /* XXX better error handling */ - MPD_ERROR("buffer too small"); - - memcpy(dest, page.header, page.header_len); - memcpy(dest + page.header_len, page.body, page.body_len); - return nbytes; + return encoder->stream.PageOut(dest, length); } static const char * @@ -392,16 +362,16 @@ vorbis_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) } const struct encoder_plugin vorbis_encoder_plugin = { - .name = "vorbis", - .init = vorbis_encoder_init, - .finish = vorbis_encoder_finish, - .open = vorbis_encoder_open, - .close = vorbis_encoder_close, - .end = vorbis_encoder_pre_tag, - .flush = vorbis_encoder_flush, - .pre_tag = vorbis_encoder_pre_tag, - .tag = vorbis_encoder_tag, - .write = vorbis_encoder_write, - .read = vorbis_encoder_read, - .get_mime_type = vorbis_encoder_get_mime_type, + "vorbis", + vorbis_encoder_init, + vorbis_encoder_finish, + vorbis_encoder_open, + vorbis_encoder_close, + vorbis_encoder_pre_tag, + vorbis_encoder_flush, + vorbis_encoder_pre_tag, + vorbis_encoder_tag, + vorbis_encoder_write, + vorbis_encoder_read, + vorbis_encoder_get_mime_type, }; diff --git a/src/encoder/VorbisEncoderPlugin.hxx b/src/encoder/VorbisEncoderPlugin.hxx new file mode 100644 index 00000000..4cddf1b1 --- /dev/null +++ b/src/encoder/VorbisEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ENCODER_VORBIS_H +#define MPD_ENCODER_VORBIS_H + +extern const struct encoder_plugin vorbis_encoder_plugin; + +#endif diff --git a/src/encoder/flac_encoder.c b/src/encoder/flac_encoder.c index e32588e2..db6503fb 100644 --- a/src/encoder/flac_encoder.c +++ b/src/encoder/flac_encoder.c @@ -22,14 +22,18 @@ #include "encoder_plugin.h" #include "audio_format.h" #include "pcm_buffer.h" -#include "fifo_buffer.h" -#include "growing_fifo.h" +#include "util/fifo_buffer.h" +#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 { struct encoder encoder; @@ -98,8 +102,6 @@ static bool flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample, GError **error) { -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -#else if ( !FLAC__stream_encoder_set_compression_level(encoder->fse, encoder->compression)) { g_set_error(error, flac_encoder_quark(), 0, @@ -107,7 +109,7 @@ flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample, encoder->compression); return false; } -#endif + if ( !FLAC__stream_encoder_set_channels(encoder->fse, encoder->audio_format.channels)) { g_set_error(error, flac_encoder_quark(), 0, @@ -135,11 +137,7 @@ flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample, static FLAC__StreamEncoderWriteStatus flac_write_callback(G_GNUC_UNUSED const FLAC__StreamEncoder *fse, const FLAC__byte data[], -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 - unsigned bytes, -#else size_t bytes, -#endif G_GNUC_UNUSED unsigned samples, G_GNUC_UNUSED unsigned current_frame, void *client_data) { @@ -209,24 +207,6 @@ flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, /* this immediately outputs data through callback */ -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 - { - FLAC__StreamEncoderState init_status; - - FLAC__stream_encoder_set_write_callback(encoder->fse, - flac_write_callback); - - init_status = FLAC__stream_encoder_init(encoder->fse); - - if (init_status != FLAC__STREAM_ENCODER_OK) { - g_set_error(error, flac_encoder_quark(), 0, - "failed to initialize encoder: %s\n", - FLAC__StreamEncoderStateString[init_status]); - flac_encoder_close(_encoder); - return false; - } - } -#else { FLAC__StreamEncoderInitStatus init_status; @@ -242,7 +222,6 @@ flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, return false; } } -#endif return true; } diff --git a/src/encoder/lame_encoder.c b/src/encoder/lame_encoder.c index 3bb99ea2..53594d34 100644 --- a/src/encoder/lame_encoder.c +++ b/src/encoder/lame_encoder.c @@ -23,6 +23,9 @@ #include "audio_format.h" #include <lame/lame.h> + +#include <glib.h> + #include <assert.h> #include <string.h> @@ -225,7 +228,7 @@ lame_encoder_close(struct encoder *_encoder) static bool lame_encoder_write(struct encoder *_encoder, const void *data, size_t length, - G_GNUC_UNUSED GError **error) + gcc_unused GError **error) { struct lame_encoder *encoder = (struct lame_encoder *)_encoder; unsigned num_frames; @@ -283,7 +286,7 @@ lame_encoder_read(struct encoder *_encoder, void *dest, size_t length) } static const char * -lame_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +lame_encoder_get_mime_type(gcc_unused struct encoder *_encoder) { return "audio/mpeg"; } diff --git a/src/encoder/null_encoder.c b/src/encoder/null_encoder.c index 48cdf139..b973adf7 100644 --- a/src/encoder/null_encoder.c +++ b/src/encoder/null_encoder.c @@ -20,8 +20,11 @@ #include "config.h" #include "encoder_api.h" #include "encoder_plugin.h" -#include "fifo_buffer.h" -#include "growing_fifo.h" +#include "util/fifo_buffer.h" +#include "util/growing_fifo.h" +#include "gcc.h" + +#include <glib.h> #include <assert.h> #include <string.h> @@ -34,15 +37,9 @@ struct null_encoder { extern const struct encoder_plugin null_encoder_plugin; -static inline GQuark -null_encoder_quark(void) -{ - return g_quark_from_static_string("null_encoder"); -} - static struct encoder * -null_encoder_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error) +null_encoder_init(gcc_unused const struct config_param *param, + gcc_unused GError **error) { struct null_encoder *encoder; @@ -71,8 +68,8 @@ null_encoder_close(struct encoder *_encoder) static bool null_encoder_open(struct encoder *_encoder, - G_GNUC_UNUSED struct audio_format *audio_format, - G_GNUC_UNUSED GError **error) + gcc_unused struct audio_format *audio_format, + gcc_unused GError **error) { struct null_encoder *encoder = (struct null_encoder *)_encoder; @@ -83,7 +80,7 @@ null_encoder_open(struct encoder *_encoder, static bool null_encoder_write(struct encoder *_encoder, const void *data, size_t length, - G_GNUC_UNUSED GError **error) + gcc_unused GError **error) { struct null_encoder *encoder = (struct null_encoder *)_encoder; diff --git a/src/encoder/twolame_encoder.c b/src/encoder/twolame_encoder.c index 934b2ab2..ff184977 100644 --- a/src/encoder/twolame_encoder.c +++ b/src/encoder/twolame_encoder.c @@ -23,6 +23,9 @@ #include "audio_format.h" #include <twolame.h> + +#include <glib.h> + #include <assert.h> #include <string.h> @@ -224,7 +227,7 @@ twolame_encoder_close(struct encoder *_encoder) } static bool -twolame_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) +twolame_encoder_flush(struct encoder *_encoder, gcc_unused GError **error) { struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder; @@ -235,7 +238,7 @@ twolame_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) static bool twolame_encoder_write(struct encoder *_encoder, const void *data, size_t length, - G_GNUC_UNUSED GError **error) + gcc_unused GError **error) { struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder; unsigned num_frames; @@ -289,7 +292,7 @@ twolame_encoder_read(struct encoder *_encoder, void *dest, size_t length) } static const char * -twolame_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +twolame_encoder_get_mime_type(gcc_unused struct encoder *_encoder) { return "audio/mpeg"; } diff --git a/src/encoder/wave_encoder.c b/src/encoder/wave_encoder.c index 9eeb4d51..9b3c21fc 100644 --- a/src/encoder/wave_encoder.c +++ b/src/encoder/wave_encoder.c @@ -20,8 +20,10 @@ #include "config.h" #include "encoder_api.h" #include "encoder_plugin.h" -#include "fifo_buffer.h" -#include "growing_fifo.h" +#include "util/fifo_buffer.h" +#include "util/growing_fifo.h" + +#include <glib.h> #include <assert.h> #include <string.h> @@ -51,12 +53,6 @@ struct wave_header { extern const struct encoder_plugin wave_encoder_plugin; -static inline GQuark -wave_encoder_quark(void) -{ - return g_quark_from_static_string("wave_encoder"); -} - static void fill_wave_header(struct wave_header *header, int channels, int bits, int freq, int block_size) @@ -85,8 +81,8 @@ fill_wave_header(struct wave_header *header, int channels, int bits, } static struct encoder * -wave_encoder_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error) +wave_encoder_init(gcc_unused const struct config_param *param, + gcc_unused GError **error) { struct wave_encoder *encoder; @@ -106,8 +102,8 @@ wave_encoder_finish(struct encoder *_encoder) static bool wave_encoder_open(struct encoder *_encoder, - G_GNUC_UNUSED struct audio_format *audio_format, - G_GNUC_UNUSED GError **error) + gcc_unused struct audio_format *audio_format, + gcc_unused GError **error) { struct wave_encoder *encoder = (struct wave_encoder *)_encoder; @@ -202,7 +198,7 @@ pcm24_to_wave(uint8_t *dst8, const uint32_t *src32, size_t length) static bool wave_encoder_write(struct encoder *_encoder, const void *src, size_t length, - G_GNUC_UNUSED GError **error) + gcc_unused GError **error) { struct wave_encoder *encoder = (struct wave_encoder *)_encoder; @@ -261,7 +257,7 @@ wave_encoder_read(struct encoder *_encoder, void *dest, size_t length) } static const char * -wave_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +wave_encoder_get_mime_type(gcc_unused struct encoder *_encoder) { return "audio/wav"; } diff --git a/src/encoder_list.c b/src/encoder_list.c index 2326c109..029b4be3 100644 --- a/src/encoder_list.c +++ b/src/encoder_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,11 +20,12 @@ #include "config.h" #include "encoder_list.h" #include "encoder_plugin.h" +#include "encoder/VorbisEncoderPlugin.hxx" +#include "encoder/OpusEncoderPlugin.hxx" #include <string.h> extern const struct encoder_plugin null_encoder_plugin; -extern const struct encoder_plugin vorbis_encoder_plugin; extern const struct encoder_plugin lame_encoder_plugin; extern const struct encoder_plugin twolame_encoder_plugin; extern const struct encoder_plugin wave_encoder_plugin; @@ -35,6 +36,9 @@ const struct encoder_plugin *const encoder_plugins[] = { #ifdef ENABLE_VORBIS_ENCODER &vorbis_encoder_plugin, #endif +#ifdef HAVE_OPUS + &opus_encoder_plugin, +#endif #ifdef ENABLE_LAME_ENCODER &lame_encoder_plugin, #endif diff --git a/src/encoder_list.h b/src/encoder_list.h index fb1c9bf9..31663c75 100644 --- a/src/encoder_list.h +++ b/src/encoder_list.h @@ -30,6 +30,10 @@ extern const struct encoder_plugin *const encoder_plugins[]; (plugin = *encoder_plugin_iterator) != NULL; \ ++encoder_plugin_iterator) +#ifdef __cplusplus +extern "C" { +#endif + /** * Looks up an encoder plugin by its name. * @@ -40,4 +44,8 @@ extern const struct encoder_plugin *const encoder_plugins[]; const struct encoder_plugin * encoder_plugin_get(const char *name); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/encoder_plugin.h b/src/encoder_plugin.h index 3a42d79f..e0748a13 100644 --- a/src/encoder_plugin.h +++ b/src/encoder_plugin.h @@ -20,7 +20,7 @@ #ifndef MPD_ENCODER_PLUGIN_H #define MPD_ENCODER_PLUGIN_H -#include <glib.h> +#include "gerror.h" #include <assert.h> #include <stdbool.h> diff --git a/src/event/BufferedSocket.cxx b/src/event/BufferedSocket.cxx new file mode 100644 index 00000000..05e70344 --- /dev/null +++ b/src/event/BufferedSocket.cxx @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "BufferedSocket.hxx" +#include "SocketError.hxx" +#include "util/fifo_buffer.h" + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +BufferedSocket::~BufferedSocket() +{ + if (input != nullptr) + fifo_buffer_free(input); +} + +BufferedSocket::ssize_t +BufferedSocket::DirectRead(void *data, size_t length) +{ + const auto nbytes = SocketMonitor::Read((char *)data, length); + if (gcc_likely(nbytes > 0)) + return nbytes; + + if (nbytes == 0) { + OnSocketClosed(); + return -1; + } + + const auto code = GetSocketError(); + if (IsSocketErrorAgain(code)) + return 0; + + if (IsSocketErrorClosed(code)) + OnSocketClosed(); + else + OnSocketError(NewSocketError(code)); + return -1; +} + +bool +BufferedSocket::ReadToBuffer() +{ + assert(IsDefined()); + + if (input == nullptr) + input = fifo_buffer_new(8192); + + size_t length; + void *buffer = fifo_buffer_write(input, &length); + assert(buffer != nullptr); + + const auto nbytes = DirectRead(buffer, length); + if (nbytes > 0) + fifo_buffer_append(input, nbytes); + + return nbytes >= 0; +} + +bool +BufferedSocket::ResumeInput() +{ + assert(IsDefined()); + + if (input == nullptr) { + ScheduleRead(); + return true; + } + + while (true) { + size_t length; + const void *data = fifo_buffer_read(input, &length); + if (data == nullptr) { + ScheduleRead(); + return true; + } + + const auto result = OnSocketInput(data, length); + switch (result) { + case InputResult::MORE: + if (fifo_buffer_is_full(input)) { + // TODO + OnSocketError(g_error_new_literal(g_quark_from_static_string("buffered_socket"), + 0, "Input buffer is full")); + return false; + } + + ScheduleRead(); + return true; + + case InputResult::PAUSE: + CancelRead(); + return true; + + case InputResult::AGAIN: + continue; + + case InputResult::CLOSED: + return false; + } + } +} + +void +BufferedSocket::ConsumeInput(size_t nbytes) +{ + assert(IsDefined()); + + fifo_buffer_consume(input, nbytes); +} + +bool +BufferedSocket::OnSocketReady(unsigned flags) +{ + assert(IsDefined()); + + if (gcc_unlikely(flags & (ERROR|HANGUP))) { + OnSocketClosed(); + return false; + } + + if (flags & READ) { + assert(input == nullptr || !fifo_buffer_is_full(input)); + + if (!ReadToBuffer() || !ResumeInput()) + return false; + + if (input == nullptr || !fifo_buffer_is_full(input)) + ScheduleRead(); + } + + return true; +} diff --git a/src/event/BufferedSocket.hxx b/src/event/BufferedSocket.hxx new file mode 100644 index 00000000..86deb8d9 --- /dev/null +++ b/src/event/BufferedSocket.hxx @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_BUFFERED_SOCKET_HXX +#define MPD_BUFFERED_SOCKET_HXX + +#include "check.h" +#include "SocketMonitor.hxx" +#include "gcc.h" + +struct fifo_buffer; + +/** + * A #SocketMonitor specialization that adds an input buffer. + */ +class BufferedSocket : protected SocketMonitor { + fifo_buffer *input; + +public: + BufferedSocket(int _fd, EventLoop &_loop) + :SocketMonitor(_fd, _loop), input(nullptr) { + ScheduleRead(); + } + + ~BufferedSocket(); + + using SocketMonitor::IsDefined; + using SocketMonitor::Close; + using SocketMonitor::Write; + +private: + ssize_t DirectRead(void *data, size_t length); + + /** + * Receive data from the socket to the input buffer. + * + * @return false if the socket has been closed + */ + bool ReadToBuffer(); + +protected: + /** + * @return false if the socket has been closed + */ + bool ResumeInput(); + + /** + * Mark a portion of the input buffer "consumed". Only + * allowed to be called from OnSocketInput(). This method + * does not invalidate the pointer passed to OnSocketInput() + * yet. + */ + void ConsumeInput(size_t nbytes); + + enum class InputResult { + /** + * The method was successful, and it is ready to + * read more data. + */ + MORE, + + /** + * The method does not want to get more data for now. + * It will call ResumeInput() when it's ready for + * more. + */ + PAUSE, + + /** + * The method wants to be called again immediately, if + * there's more data in the buffer. + */ + AGAIN, + + /** + * The method has closed the socket. + */ + CLOSED, + }; + + virtual InputResult OnSocketInput(const void *data, size_t length) = 0; + virtual void OnSocketError(GError *error) = 0; + virtual void OnSocketClosed() = 0; + + virtual bool OnSocketReady(unsigned flags) override; +}; + +#endif diff --git a/src/event/FullyBufferedSocket.cxx b/src/event/FullyBufferedSocket.cxx new file mode 100644 index 00000000..a92cb68a --- /dev/null +++ b/src/event/FullyBufferedSocket.cxx @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FullyBufferedSocket.hxx" +#include "SocketError.hxx" +#include "util/fifo_buffer.h" + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +#ifndef WIN32 +#include <sys/types.h> +#include <sys/socket.h> +#endif + +FullyBufferedSocket::ssize_t +FullyBufferedSocket::DirectWrite(const void *data, size_t length) +{ + const auto nbytes = SocketMonitor::Write((const char *)data, length); + if (gcc_unlikely(nbytes < 0)) { + const auto code = GetSocketError(); + if (IsSocketErrorAgain(code)) + return 0; + + Cancel(); + + if (IsSocketErrorClosed(code)) + OnSocketClosed(); + else + OnSocketError(NewSocketError(code)); + } + + return nbytes; +} + +bool +FullyBufferedSocket::WriteFromBuffer() +{ + assert(IsDefined()); + + size_t length; + const void *data = output.Read(&length); + if (data == nullptr) { + CancelWrite(); + return true; + } + + auto nbytes = DirectWrite(data, length); + if (gcc_unlikely(nbytes <= 0)) + return nbytes == 0; + + output.Consume(nbytes); + + if (output.IsEmpty()) + CancelWrite(); + + return true; +} + +bool +FullyBufferedSocket::Write(const void *data, size_t length) +{ + assert(IsDefined()); + +#if 0 + /* TODO: disabled because this would add overhead on some callers (the ones that often), but it may be useful */ + + if (output.IsEmpty()) { + /* try to write it directly first */ + const auto nbytes = DirectWrite(data, length); + if (gcc_likely(nbytes > 0)) { + data = (const uint8_t *)data + nbytes; + length -= nbytes; + if (length == 0) + return true; + } else if (nbytes < 0) + return false; + } +#endif + + if (!output.Append(data, length)) { + // TODO + OnSocketError(g_error_new_literal(g_quark_from_static_string("buffered_socket"), + 0, "Output buffer is full")); + return false; + } + + ScheduleWrite(); + return true; +} + +bool +FullyBufferedSocket::OnSocketReady(unsigned flags) +{ + const bool was_empty = output.IsEmpty(); + if (!BufferedSocket::OnSocketReady(flags)) + return false; + + if (was_empty && !output.IsEmpty()) + /* just in case the OnSocketInput() method has added + data to the output buffer: try to send it now + instead of waiting for the next event loop + iteration */ + flags |= WRITE; + + if (flags & WRITE) { + assert(!output.IsEmpty()); + + if (!WriteFromBuffer()) + return false; + } + + return true; +} diff --git a/src/event/FullyBufferedSocket.hxx b/src/event/FullyBufferedSocket.hxx new file mode 100644 index 00000000..c67c2c78 --- /dev/null +++ b/src/event/FullyBufferedSocket.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FULLY_BUFFERED_SOCKET_HXX +#define MPD_FULLY_BUFFERED_SOCKET_HXX + +#include "check.h" +#include "BufferedSocket.hxx" +#include "util/PeakBuffer.hxx" +#include "gcc.h" + +/** + * A #BufferedSocket specialization that adds an output buffer. + */ +class FullyBufferedSocket : protected BufferedSocket { + PeakBuffer output; + +public: + FullyBufferedSocket(int _fd, EventLoop &_loop, + size_t normal_size, size_t peak_size=0) + :BufferedSocket(_fd, _loop), + output(normal_size, peak_size) { + } + + using BufferedSocket::IsDefined; + using BufferedSocket::Close; + +private: + ssize_t DirectWrite(const void *data, size_t length); + + /** + * Send data from the output buffer to the socket. + * + * @return false if the socket has been closed + */ + bool WriteFromBuffer(); + +protected: + /** + * @return false if the socket has been closed + */ + bool Write(const void *data, size_t length); + + virtual bool OnSocketReady(unsigned flags) override; +}; + +#endif diff --git a/src/event/Loop.hxx b/src/event/Loop.hxx new file mode 100644 index 00000000..72731ea2 --- /dev/null +++ b/src/event/Loop.hxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_EVENT_LOOP_HXX +#define MPD_EVENT_LOOP_HXX + +#include "check.h" +#include "gcc.h" + +#include <glib.h> + +class EventLoop { + GMainContext *context; + GMainLoop *loop; + +public: + EventLoop() + :context(g_main_context_new()), + loop(g_main_loop_new(context, false)) {} + + struct Default {}; + EventLoop(gcc_unused Default _dummy) + :context(g_main_context_ref(g_main_context_default())), + loop(g_main_loop_new(context, false)) {} + + ~EventLoop() { + g_main_loop_unref(loop); + g_main_context_unref(context); + } + + GMainContext *GetContext() { + return context; + } + + void WakeUp() { + g_main_context_wakeup(context); + } + + void Break() { + g_main_loop_quit(loop); + } + + void Run() { + g_main_loop_run(loop); + } + + guint AddIdle(GSourceFunc function, gpointer data) { + GSource *source = g_idle_source_new(); + g_source_set_callback(source, function, data, NULL); + guint id = g_source_attach(source, GetContext()); + g_source_unref(source); + return id; + } + + GSource *AddTimeout(guint interval_ms, + GSourceFunc function, gpointer data) { + GSource *source = g_timeout_source_new(interval_ms); + g_source_set_callback(source, function, data, nullptr); + g_source_attach(source, GetContext()); + return source; + } + + GSource *AddTimeoutSeconds(guint interval_s, + GSourceFunc function, gpointer data) { + GSource *source = g_timeout_source_new_seconds(interval_s); + g_source_set_callback(source, function, data, nullptr); + g_source_attach(source, GetContext()); + return source; + } +}; + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event/MultiSocketMonitor.cxx b/src/event/MultiSocketMonitor.cxx new file mode 100644 index 00000000..6f20b907 --- /dev/null +++ b/src/event/MultiSocketMonitor.cxx @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MultiSocketMonitor.hxx" +#include "Loop.hxx" +#include "fd_util.h" +#include "gcc.h" + +#include <assert.h> + +/** + * The vtable for our GSource implementation. Unfortunately, we + * cannot declare it "const", because g_source_new() takes a non-const + * pointer, for whatever reason. + */ +static GSourceFuncs multi_socket_monitor_source_funcs = { + MultiSocketMonitor::Prepare, + MultiSocketMonitor::Check, + MultiSocketMonitor::Dispatch, + nullptr, + nullptr, + nullptr, +}; + +MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop) + :loop(_loop), + source((Source *)g_source_new(&multi_socket_monitor_source_funcs, + sizeof(*source))) { + source->monitor = this; + + g_source_attach(&source->base, loop.GetContext()); +} + +MultiSocketMonitor::~MultiSocketMonitor() +{ + g_source_destroy(&source->base); + g_source_unref(&source->base); + source = nullptr; +} + +bool +MultiSocketMonitor::Check() const +{ + if (CheckSockets()) + return true; + + for (const auto &i : fds) + if (i.revents != 0) + return true; + + return false; +} + +/* + * GSource methods + * + */ + +gboolean +MultiSocketMonitor::Prepare(GSource *_source, gint *timeout_r) +{ + Source &source = *(Source *)_source; + MultiSocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + return monitor.Prepare(timeout_r); +} + +gboolean +MultiSocketMonitor::Check(GSource *_source) +{ + const Source &source = *(const Source *)_source; + const MultiSocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + return monitor.Check(); +} + +gboolean +MultiSocketMonitor::Dispatch(GSource *_source, + gcc_unused GSourceFunc callback, + gcc_unused gpointer user_data) +{ + Source &source = *(Source *)_source; + MultiSocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + monitor.Dispatch(); + return true; +} diff --git a/src/event/MultiSocketMonitor.hxx b/src/event/MultiSocketMonitor.hxx new file mode 100644 index 00000000..bf0a221a --- /dev/null +++ b/src/event/MultiSocketMonitor.hxx @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MULTI_SOCKET_MONITOR_HXX +#define MPD_MULTI_SOCKET_MONITOR_HXX + +#include "check.h" +#include "gcc.h" +#include "glib_compat.h" + +#include <glib.h> + +#include <forward_list> + +#include <assert.h> + +#ifdef WIN32 +/* ERRORis a WIN32 macro that poisons our namespace; this is a + kludge to allow us to use it anyway */ +#ifdef ERROR +#undef ERROR +#endif +#endif + +class EventLoop; + +/** + * Monitor multiple sockets. + */ +class MultiSocketMonitor { + struct Source { + GSource base; + + MultiSocketMonitor *monitor; + }; + + EventLoop &loop; + Source *source; + std::forward_list<GPollFD> fds; + +public: + static constexpr unsigned READ = G_IO_IN; + static constexpr unsigned WRITE = G_IO_OUT; + static constexpr unsigned ERROR = G_IO_ERR; + static constexpr unsigned HANGUP = G_IO_HUP; + + MultiSocketMonitor(EventLoop &_loop); + ~MultiSocketMonitor(); + +public: + gcc_pure + gint64 GetTime() const { + return g_source_get_time(&source->base); + } + + void InvalidateSockets() { + /* no-op because GLib always calls the GSource's + "prepare" method before each poll() anyway */ + } + + void AddSocket(int fd, unsigned events) { + fds.push_front({fd, gushort(events), 0}); + g_source_add_poll(&source->base, &fds.front()); + } + + template<typename E> + void UpdateSocketList(E &&e) { + for (auto prev = fds.before_begin(), end = fds.end(), + i = std::next(prev); + i != end; i = std::next(prev)) { + assert(i->events != 0); + + unsigned events = e(i->fd); + if (events != 0) { + i->events = events; + prev = i; + } else { + g_source_remove_poll(&source->base, &*i); + fds.erase_after(prev); + } + } + } + +protected: + virtual void PrepareSockets(gcc_unused gint *timeout_r) {} + virtual bool CheckSockets() const { return false; } + virtual void DispatchSockets() = 0; + +public: + /* GSource callbacks */ + static gboolean Prepare(GSource *source, gint *timeout_r); + static gboolean Check(GSource *source); + static gboolean Dispatch(GSource *source, GSourceFunc callback, + gpointer user_data); + +private: + bool Prepare(gint *timeout_r) { + PrepareSockets(timeout_r); + return false; + } + + bool Check() const; + + void Dispatch() { + DispatchSockets(); + } +}; + +#endif diff --git a/src/event/ServerSocket.cxx b/src/event/ServerSocket.cxx new file mode 100644 index 00000000..119bfe1d --- /dev/null +++ b/src/event/ServerSocket.cxx @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#ifdef HAVE_STRUCT_UCRED +#define _GNU_SOURCE 1 +#endif + +#include "ServerSocket.hxx" +#include "SocketUtil.hxx" +#include "SocketError.hxx" +#include "event/SocketMonitor.hxx" +#include "resolver.h" +#include "fd_util.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netdb.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "listen" + +#define DEFAULT_PORT 6600 + +class OneServerSocket final : private SocketMonitor { + ServerSocket &parent; + + const unsigned serial; + + char *path; + + size_t address_length; + struct sockaddr *address; + +public: + OneServerSocket(EventLoop &_loop, ServerSocket &_parent, + unsigned _serial, + const struct sockaddr *_address, + size_t _address_length) + :SocketMonitor(_loop), + parent(_parent), serial(_serial), + path(nullptr), + address_length(_address_length), + address((sockaddr *)g_memdup(_address, _address_length)) + { + assert(_address != nullptr); + assert(_address_length > 0); + } + + OneServerSocket(const OneServerSocket &other) = delete; + OneServerSocket &operator=(const OneServerSocket &other) = delete; + + ~OneServerSocket() { + g_free(path); + g_free(address); + } + + unsigned GetSerial() const { + return serial; + } + + void SetPath(const char *_path) { + assert(path == nullptr); + + path = g_strdup(_path); + } + + bool Open(GError **error_r); + + using SocketMonitor::IsDefined; + using SocketMonitor::Close; + + char *ToString() const; + + void SetFD(int _fd) { + SocketMonitor::Open(_fd); + SocketMonitor::ScheduleRead(); + } + + void Accept(); + +private: + virtual bool OnSocketReady(unsigned flags) override; +}; + +static GQuark +server_socket_quark(void) +{ + return g_quark_from_static_string("server_socket"); +} + +/** + * Wraper for sockaddr_to_string() which never fails. + */ +char * +OneServerSocket::ToString() const +{ + char *p = sockaddr_to_string(address, address_length, nullptr); + if (p == nullptr) + p = g_strdup("[unknown]"); + return p; +} + +static int +get_remote_uid(int fd) +{ +#ifdef HAVE_STRUCT_UCRED + struct ucred cred; + socklen_t len = sizeof (cred); + + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0) + return 0; + + return cred.uid; +#else +#ifdef HAVE_GETPEEREID + uid_t euid; + gid_t egid; + + if (getpeereid(fd, &euid, &egid) == 0) + return euid; +#else + (void)fd; +#endif + return -1; +#endif +} + +inline void +OneServerSocket::Accept() +{ + struct sockaddr_storage peer_address; + size_t peer_address_length = sizeof(peer_address); + int peer_fd = + accept_cloexec_nonblock(Get(), (struct sockaddr*)&peer_address, + &peer_address_length); + if (peer_fd < 0) { + const SocketErrorMessage msg; + g_warning("accept() failed: %s", (const char *)msg); + return; + } + + if (socket_keepalive(peer_fd)) { + const SocketErrorMessage msg; + g_warning("Could not set TCP keepalive option: %s", + (const char *)msg); + } + + parent.OnAccept(peer_fd, + (const sockaddr &)peer_address, + peer_address_length, get_remote_uid(peer_fd)); +} + +bool +OneServerSocket::OnSocketReady(gcc_unused unsigned flags) +{ + Accept(); + return true; +} + +inline bool +OneServerSocket::Open(GError **error_r) +{ + assert(!IsDefined()); + + int _fd = socket_bind_listen(address->sa_family, + SOCK_STREAM, 0, + address, address_length, 5, + error_r); + if (_fd < 0) + return false; + + /* allow everybody to connect */ + + if (path != nullptr) + chmod(path, 0666); + + /* register in the GLib main loop */ + + SetFD(_fd); + + return true; +} + +ServerSocket::ServerSocket(EventLoop &_loop) + :loop(_loop), next_serial(1) {} + +/* this is just here to allow the OneServerSocket forward + declaration */ +ServerSocket::~ServerSocket() {} + +bool +ServerSocket::Open(GError **error_r) +{ + OneServerSocket *good = nullptr, *bad = nullptr; + GError *last_error = nullptr; + + for (auto &i : sockets) { + assert(i.GetSerial() > 0); + assert(good == nullptr || i.GetSerial() <= good->GetSerial()); + + if (bad != nullptr && i.GetSerial() != bad->GetSerial()) { + Close(); + g_propagate_error(error_r, last_error); + return false; + } + + GError *error = nullptr; + if (!i.Open(&error)) { + if (good != nullptr && good->GetSerial() == i.GetSerial()) { + char *address_string = i.ToString(); + char *good_string = good->ToString(); + g_warning("bind to '%s' failed: %s " + "(continuing anyway, because " + "binding to '%s' succeeded)", + address_string, error->message, + good_string); + g_free(address_string); + g_free(good_string); + g_error_free(error); + } else if (bad == nullptr) { + bad = &i; + + char *address_string = i.ToString(); + g_propagate_prefixed_error(&last_error, error, + "Failed to bind to '%s': ", + address_string); + g_free(address_string); + } else + g_error_free(error); + continue; + } + + /* mark this socket as "good", and clear previous + errors */ + + good = &i; + + if (bad != nullptr) { + bad = nullptr; + g_error_free(last_error); + last_error = nullptr; + } + } + + if (bad != nullptr) { + Close(); + g_propagate_error(error_r, last_error); + return false; + } + + return true; +} + +void +ServerSocket::Close() +{ + for (auto &i : sockets) + if (i.IsDefined()) + i.Close(); +} + +OneServerSocket & +ServerSocket::AddAddress(const sockaddr &address, size_t address_length) +{ + sockets.emplace_front(loop, *this, next_serial, + &address, address_length); + + return sockets.front(); +} + +bool +ServerSocket::AddFD(int fd, GError **error_r) +{ + assert(fd >= 0); + + struct sockaddr_storage address; + socklen_t address_length = sizeof(address); + if (getsockname(fd, (struct sockaddr *)&address, + &address_length) < 0) { + SetSocketError(error_r); + g_prefix_error(error_r, "Failed to get socket address"); + return false; + } + + OneServerSocket &s = AddAddress((const sockaddr &)address, + address_length); + s.SetFD(fd); + + return true; +} + +#ifdef HAVE_TCP + +inline void +ServerSocket::AddPortIPv4(unsigned port) +{ + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_port = htons(port); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + + AddAddress((const sockaddr &)sin, sizeof(sin)); +} + +#ifdef HAVE_IPV6 +inline void +ServerSocket::AddPortIPv6(unsigned port) +{ + struct sockaddr_in6 sin; + memset(&sin, 0, sizeof(sin)); + sin.sin6_port = htons(port); + sin.sin6_family = AF_INET6; + + AddAddress((const sockaddr &)sin, sizeof(sin)); +} +#endif /* HAVE_IPV6 */ + +#endif /* HAVE_TCP */ + +bool +ServerSocket::AddPort(unsigned port, GError **error_r) +{ +#ifdef HAVE_TCP + if (port == 0 || port > 0xffff) { + g_set_error(error_r, server_socket_quark(), 0, + "Invalid TCP port"); + return false; + } + +#ifdef HAVE_IPV6 + AddPortIPv6(port); +#endif + AddPortIPv4(port); + + ++next_serial; + + return true; +#else /* HAVE_TCP */ + (void)port; + + g_set_error(error_r, server_socket_quark(), 0, + "TCP support is disabled"); + return false; +#endif /* HAVE_TCP */ +} + +bool +ServerSocket::AddHost(const char *hostname, unsigned port, GError **error_r) +{ +#ifdef HAVE_TCP + struct addrinfo *ai = resolve_host_port(hostname, port, + AI_PASSIVE, SOCK_STREAM, + error_r); + if (ai == nullptr) + return false; + + for (const struct addrinfo *i = ai; i != nullptr; i = i->ai_next) + AddAddress(*i->ai_addr, i->ai_addrlen); + + freeaddrinfo(ai); + + ++next_serial; + + return true; +#else /* HAVE_TCP */ + (void)hostname; + (void)port; + + g_set_error(error_r, server_socket_quark(), 0, + "TCP support is disabled"); + return false; +#endif /* HAVE_TCP */ +} + +bool +ServerSocket::AddPath(const char *path, GError **error_r) +{ +#ifdef HAVE_UN + struct sockaddr_un s_un; + + size_t path_length = strlen(path); + if (path_length >= sizeof(s_un.sun_path)) { + g_set_error(error_r, server_socket_quark(), 0, + "UNIX socket path is too long"); + return false; + } + + unlink(path); + + s_un.sun_family = AF_UNIX; + memcpy(s_un.sun_path, path, path_length + 1); + + OneServerSocket &s = AddAddress((const sockaddr &)s_un, sizeof(s_un)); + s.SetPath(path); + + return true; +#else /* !HAVE_UN */ + (void)path; + + g_set_error(error_r, server_socket_quark(), 0, + "UNIX domain socket support is disabled"); + return false; +#endif /* !HAVE_UN */ +} + diff --git a/src/event/ServerSocket.hxx b/src/event/ServerSocket.hxx new file mode 100644 index 00000000..600cdf8a --- /dev/null +++ b/src/event/ServerSocket.hxx @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SERVER_SOCKET_HXX +#define MPD_SERVER_SOCKET_HXX + +#include "gerror.h" + +#include <forward_list> + +#include <stddef.h> + +struct sockaddr; +class EventLoop; + +typedef void (*server_socket_callback_t)(int fd, + const struct sockaddr *address, + size_t address_length, int uid, + void *ctx); + +class OneServerSocket; + +class ServerSocket { + friend class OneServerSocket; + + EventLoop &loop; + + std::forward_list<OneServerSocket> sockets; + + unsigned next_serial; + +public: + ServerSocket(EventLoop &_loop); + ~ServerSocket(); + + EventLoop &GetEventLoop() { + return loop; + } + +private: + OneServerSocket &AddAddress(const sockaddr &address, size_t length); + + /** + * Add a listener on a port on all IPv4 interfaces. + * + * @param port the TCP port + */ + void AddPortIPv4(unsigned port); + + /** + * Add a listener on a port on all IPv6 interfaces. + * + * @param port the TCP port + */ + void AddPortIPv6(unsigned port); + +public: + /** + * Add a listener on a port on all interfaces. + * + * @param port the TCP port + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return true on success + */ + bool AddPort(unsigned port, GError **error_r); + + /** + * Resolves a host name, and adds listeners on all addresses in the + * result set. + * + * @param hostname the host name to be resolved + * @param port the TCP port + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return true on success + */ + bool AddHost(const char *hostname, unsigned port, GError **error_r); + + /** + * Add a listener on a Unix domain socket. + * + * @param path the absolute socket path + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return true on success + */ + bool AddPath(const char *path, GError **error_r); + + /** + * Add a socket descriptor that is accepting connections. After this + * has been called, don't call server_socket_open(), because the + * socket is already open. + */ + bool AddFD(int fd, GError **error_r); + + bool Open(GError **error_r); + void Close(); + +protected: + virtual void OnAccept(int fd, const sockaddr &address, + size_t address_length, int uid) = 0; +}; + +#endif diff --git a/src/event/SocketMonitor.cxx b/src/event/SocketMonitor.cxx new file mode 100644 index 00000000..6efa6964 --- /dev/null +++ b/src/event/SocketMonitor.cxx @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SocketMonitor.hxx" +#include "Loop.hxx" +#include "fd_util.h" +#include "gcc.h" + +#include <assert.h> + +#ifdef WIN32 +#include <winsock2.h> +#else +#include <sys/types.h> +#include <sys/socket.h> +#endif + +/* + * GSource methods + * + */ + +gboolean +SocketMonitor::Prepare(gcc_unused GSource *source, gcc_unused gint *timeout_r) +{ + return false; +} + +gboolean +SocketMonitor::Check(GSource *_source) +{ + const Source &source = *(const Source *)_source; + const SocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + return monitor.Check(); +} + +gboolean +SocketMonitor::Dispatch(GSource *_source, + gcc_unused GSourceFunc callback, + gcc_unused gpointer user_data) +{ + Source &source = *(Source *)_source; + SocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + monitor.Dispatch(); + return true; +} + +/** + * The vtable for our GSource implementation. Unfortunately, we + * cannot declare it "const", because g_source_new() takes a non-const + * pointer, for whatever reason. + */ +static GSourceFuncs socket_monitor_source_funcs = { + SocketMonitor::Prepare, + SocketMonitor::Check, + SocketMonitor::Dispatch, + nullptr, + nullptr, + nullptr, +}; + +SocketMonitor::SocketMonitor(int _fd, EventLoop &_loop) + :fd(-1), loop(_loop), + source(nullptr) { + assert(_fd >= 0); + + Open(_fd); +} + +SocketMonitor::~SocketMonitor() +{ + if (IsDefined()) + Close(); +} + +void +SocketMonitor::Open(int _fd) +{ + assert(fd < 0); + assert(source == nullptr); + assert(_fd >= 0); + + fd = _fd; + poll = {fd, 0, 0}; + + source = (Source *)g_source_new(&socket_monitor_source_funcs, + sizeof(*source)); + source->monitor = this; + + g_source_attach(&source->base, loop.GetContext()); + g_source_add_poll(&source->base, &poll); +} + +int +SocketMonitor::Steal() +{ + assert(IsDefined()); + + Cancel(); + + int result = fd; + fd = -1; + + g_source_destroy(&source->base); + g_source_unref(&source->base); + source = nullptr; + + return result; +} + +void +SocketMonitor::Close() +{ + close_socket(Steal()); +} + +SocketMonitor::ssize_t +SocketMonitor::Read(void *data, size_t length) +{ + int flags = 0; +#ifdef MSG_DONTWAIT + flags |= MSG_DONTWAIT; +#endif + + return recv(Get(), (char *)data, length, flags); +} + +SocketMonitor::ssize_t +SocketMonitor::Write(const void *data, size_t length) +{ + int flags = 0; +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif +#ifdef MSG_DONTWAIT + flags |= MSG_DONTWAIT; +#endif + + return send(Get(), (const char *)data, length, flags); +} + +void +SocketMonitor::CommitEventFlags() +{ + loop.WakeUp(); +} diff --git a/src/event/SocketMonitor.hxx b/src/event/SocketMonitor.hxx new file mode 100644 index 00000000..c60b8efd --- /dev/null +++ b/src/event/SocketMonitor.hxx @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SOCKET_MONITOR_HXX +#define MPD_SOCKET_MONITOR_HXX + +#include "check.h" + +#include <glib.h> + +#include <type_traits> + +#include <assert.h> +#include <stddef.h> + +#ifdef WIN32 +/* ERRORis a WIN32 macro that poisons our namespace; this is a + kludge to allow us to use it anyway */ +#ifdef ERROR +#undef ERROR +#endif +#endif + +class EventLoop; + +class SocketMonitor { + struct Source { + GSource base; + + SocketMonitor *monitor; + }; + + int fd; + EventLoop &loop; + Source *source; + GPollFD poll; + +public: + static constexpr unsigned READ = G_IO_IN; + static constexpr unsigned WRITE = G_IO_OUT; + static constexpr unsigned ERROR = G_IO_ERR; + static constexpr unsigned HANGUP = G_IO_HUP; + + typedef std::make_signed<size_t>::type ssize_t; + + SocketMonitor(EventLoop &_loop) + :fd(-1), loop(_loop), source(nullptr) {} + + SocketMonitor(int _fd, EventLoop &_loop); + + ~SocketMonitor(); + + bool IsDefined() const { + return fd >= 0; + } + + int Get() const { + assert(IsDefined()); + + return fd; + } + + void Open(int _fd); + + /** + * "Steal" the socket descriptor. This abandons the socket + * and puts the responsibility for closing it to the caller. + */ + int Steal(); + + void Close(); + + void Schedule(unsigned flags) { + poll.events = flags; + poll.revents &= flags; + CommitEventFlags(); + } + + void Cancel() { + poll.events = 0; + CommitEventFlags(); + } + + void ScheduleRead() { + poll.events |= READ|HANGUP|ERROR; + CommitEventFlags(); + } + + void ScheduleWrite() { + poll.events |= WRITE; + CommitEventFlags(); + } + + void CancelRead() { + poll.events &= ~(READ|HANGUP|ERROR); + CommitEventFlags(); + } + + void CancelWrite() { + poll.events &= ~WRITE; + CommitEventFlags(); + } + + ssize_t Read(void *data, size_t length); + ssize_t Write(const void *data, size_t length); + +protected: + /** + * @return false if the socket has been closed + */ + virtual bool OnSocketReady(unsigned flags) = 0; + +public: + /* GSource callbacks */ + static gboolean Prepare(GSource *source, gint *timeout_r); + static gboolean Check(GSource *source); + static gboolean Dispatch(GSource *source, GSourceFunc callback, + gpointer user_data); + +private: + void CommitEventFlags(); + + bool Check() const { + return (poll.revents & poll.events) != 0; + } + + void Dispatch() { + OnSocketReady(poll.revents & poll.events); + } +}; + +#endif diff --git a/src/event/TimeoutMonitor.cxx b/src/event/TimeoutMonitor.cxx new file mode 100644 index 00000000..e0bf997a --- /dev/null +++ b/src/event/TimeoutMonitor.cxx @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TimeoutMonitor.hxx" +#include "Loop.hxx" + +void +TimeoutMonitor::Cancel() +{ + if (source != nullptr) { + g_source_destroy(source); + g_source_unref(source); + source = nullptr; + } +} + +void +TimeoutMonitor::Schedule(unsigned ms) +{ + Cancel(); + source = loop.AddTimeout(ms, Callback, this); +} + +void +TimeoutMonitor::ScheduleSeconds(unsigned s) +{ + Cancel(); + source = loop.AddTimeoutSeconds(s, Callback, this); +} + +bool +TimeoutMonitor::Run() +{ + bool result = OnTimeout(); + if (!result && source != nullptr) { + g_source_unref(source); + source = nullptr; + } + + return result; +} + +gboolean +TimeoutMonitor::Callback(gpointer data) +{ + TimeoutMonitor &monitor = *(TimeoutMonitor *)data; + return monitor.Run(); +} diff --git a/src/event/TimeoutMonitor.hxx b/src/event/TimeoutMonitor.hxx new file mode 100644 index 00000000..6914bcb6 --- /dev/null +++ b/src/event/TimeoutMonitor.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SOCKET_TIMEOUT_MONITOR_HXX +#define MPD_SOCKET_TIMEOUT_MONITOR_HXX + +#include "check.h" + +#include <glib.h> + +class EventLoop; + +class TimeoutMonitor { + EventLoop &loop; + GSource *source; + +public: + TimeoutMonitor(EventLoop &_loop) + :loop(_loop), source(nullptr) {} + + ~TimeoutMonitor() { + Cancel(); + } + + bool IsActive() const { + return source != nullptr; + } + + void Schedule(unsigned ms); + void ScheduleSeconds(unsigned s); + void Cancel(); + +protected: + /** + * @return true reschedules the timeout again + */ + virtual bool OnTimeout() = 0; + +private: + bool Run(); + static gboolean Callback(gpointer data); +}; + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event/WakeFD.cxx b/src/event/WakeFD.cxx new file mode 100644 index 00000000..1a84f564 --- /dev/null +++ b/src/event/WakeFD.cxx @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "WakeFD.hxx" +#include "fd_util.h" +#include "gcc.h" + +#include <unistd.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock2.h> +#include <cstring> /* for memset() */ +#endif + +#ifdef HAVE_EVENTFD +#include <sys/eventfd.h> +#endif + +#ifdef WIN32 +static bool PoorSocketPair(int fd[2]); +#endif + +bool +WakeFD::Create() +{ + assert(fds[0] == -1); + assert(fds[1] == -1); + +#ifdef WIN32 + return PoorSocketPair(fds); +#else +#ifdef HAVE_EVENTFD + fds[0] = eventfd_cloexec_nonblock(0, 0); + if (fds[0] >= 0) { + fds[1] = -2; + return true; + } +#endif + return pipe_cloexec_nonblock(fds) >= 0; +#endif +} + +void +WakeFD::Destroy() +{ +#ifdef WIN32 + closesocket(fds[0]); + closesocket(fds[1]); +#else + close(fds[0]); +#ifdef HAVE_EVENTFD + if (!IsEventFD()) +#endif + close(fds[1]); +#endif + +#ifndef NDEBUG + fds[0] = -1; + fds[1] = -1; +#endif +} + +bool +WakeFD::Read() +{ + assert(fds[0] >= 0); + +#ifdef WIN32 + assert(fds[1] >= 0); + char buffer[256]; + return recv(fds[0], buffer, sizeof(buffer), 0) > 0; +#else + +#ifdef HAVE_EVENTFD + if (IsEventFD()) { + eventfd_t value; + return read(fds[0], &value, + sizeof(value)) == (ssize_t)sizeof(value); + } +#endif + + assert(fds[1] >= 0); + + char buffer[256]; + return read(fds[0], buffer, sizeof(buffer)) > 0; +#endif +} + +void +WakeFD::Write() +{ + assert(fds[0] >= 0); + +#ifdef WIN32 + assert(fds[1] >= 0); + + send(fds[1], "", 1, 0); +#else + +#ifdef HAVE_EVENTFD + if (IsEventFD()) { + static constexpr eventfd_t value = 1; + gcc_unused ssize_t nbytes = + write(fds[0], &value, sizeof(value)); + return; + } +#endif + + assert(fds[1] >= 0); + + gcc_unused ssize_t nbytes = write(fds[1], "", 1); +#endif +} + +#ifdef WIN32 + +static void SafeCloseSocket(SOCKET s) +{ + int error = WSAGetLastError(); + closesocket(s); + WSASetLastError(error); +} + +/* Our poor man's socketpair() implementation + * Due to limited protocol/address family support and primitive error handling + * it's better to keep this as a private implementation detail of WakeFD + * rather than wide-available API. + */ +static bool PoorSocketPair(int fd[2]) +{ + assert (fd != nullptr); + + SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listen_socket == INVALID_SOCKET) + return false; + + sockaddr_in address; + std::memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + int ret = bind(listen_socket, + reinterpret_cast<sockaddr*>(&address), + sizeof(address)); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + return false; + } + + ret = listen(listen_socket, 1); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + return false; + } + + int address_len = sizeof(address); + ret = getsockname(listen_socket, + reinterpret_cast<sockaddr*>(&address), + &address_len); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + return false; + } + + SOCKET socket0 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (socket0 == INVALID_SOCKET) { + SafeCloseSocket(listen_socket); + return false; + } + + ret = connect(socket0, + reinterpret_cast<sockaddr*>(&address), + sizeof(address)); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + SafeCloseSocket(socket0); + return false; + } + + SOCKET socket1 = accept(listen_socket, nullptr, nullptr); + if (socket1 == INVALID_SOCKET) { + SafeCloseSocket(listen_socket); + SafeCloseSocket(socket0); + return false; + } + + SafeCloseSocket(listen_socket); + + u_long non_block = 1; + if (ioctlsocket(socket0, FIONBIO, &non_block) < 0 + || ioctlsocket(socket1, FIONBIO, &non_block) < 0) { + SafeCloseSocket(socket0); + SafeCloseSocket(socket1); + return false; + } + + fd[0] = static_cast<int>(socket0); + fd[1] = static_cast<int>(socket1); + + return true; +} + +#endif diff --git a/src/event/WakeFD.hxx b/src/event/WakeFD.hxx new file mode 100644 index 00000000..15b66b4c --- /dev/null +++ b/src/event/WakeFD.hxx @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_WAKE_FD_HXX +#define MPD_WAKE_FD_HXX + +#include "check.h" + +#include <assert.h> + +/** + * This class can be used to wake up an I/O event loop. + * + * For optimization purposes, this class does not have a constructor + * or a destructor. + */ +class WakeFD { + int fds[2]; + +public: +#ifdef NDEBUG + WakeFD() = default; +#else + WakeFD():fds{-1, -1} {}; +#endif + + WakeFD(const WakeFD &other) = delete; + WakeFD &operator=(const WakeFD &other) = delete; + + bool Create(); + void Destroy(); + + int Get() const { + assert(fds[0] >= 0); +#ifndef HAVE_EVENTFD + assert(fds[1] >= 0); +#endif + + return fds[0]; + } + + /** + * Checks if Write() was called at least once since the last + * Read() call. + */ + bool Read(); + + /** + * Wakes up the reader. Multiple calls to this function will + * be combined to one wakeup. + */ + void Write(); + +private: +#ifdef HAVE_EVENTFD + bool IsEventFD() { + assert(fds[0] >= 0); + + return fds[1] == -2; + } +#endif +}; + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event_pipe.c b/src/event_pipe.c deleted file mode 100644 index d5c3b956..00000000 --- a/src/event_pipe.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "event_pipe.h" -#include "fd_util.h" -#include "mpd_error.h" - -#include <stdbool.h> -#include <assert.h> -#include <glib.h> -#include <string.h> -#include <errno.h> -#include <sys/types.h> -#include <unistd.h> - -#ifdef WIN32 -/* for _O_BINARY */ -#include <fcntl.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "event_pipe" - -static int event_pipe[2]; -static GIOChannel *event_channel; -static guint event_pipe_source_id; -static GMutex *event_pipe_mutex; -static bool pipe_events[PIPE_EVENT_MAX]; -static event_pipe_callback_t event_pipe_callbacks[PIPE_EVENT_MAX]; - -/** - * Invoke the callback for a certain event. - */ -static void -event_pipe_invoke(enum pipe_event event) -{ - assert((unsigned)event < PIPE_EVENT_MAX); - assert(event_pipe_callbacks[event] != NULL); - - event_pipe_callbacks[event](); -} - -static gboolean -main_notify_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - G_GNUC_UNUSED gpointer data) -{ - char buffer[256]; - gsize bytes_read; - GError *error = NULL; - GIOStatus status = g_io_channel_read_chars(event_channel, - buffer, sizeof(buffer), - &bytes_read, &error); - if (status == G_IO_STATUS_ERROR) - MPD_ERROR("error reading from pipe: %s", error->message); - - bool events[PIPE_EVENT_MAX]; - g_mutex_lock(event_pipe_mutex); - memcpy(events, pipe_events, sizeof(events)); - memset(pipe_events, 0, sizeof(pipe_events)); - g_mutex_unlock(event_pipe_mutex); - - for (unsigned i = 0; i < PIPE_EVENT_MAX; ++i) - if (events[i]) - /* invoke the event handler */ - event_pipe_invoke(i); - - return true; -} - -void event_pipe_init(void) -{ - GIOChannel *channel; - int ret; - - ret = pipe_cloexec_nonblock(event_pipe); - if (ret < 0) - MPD_ERROR("Couldn't open pipe: %s", strerror(errno)); - -#ifndef G_OS_WIN32 - channel = g_io_channel_unix_new(event_pipe[0]); -#else - channel = g_io_channel_win32_new_fd(event_pipe[0]); -#endif - g_io_channel_set_encoding(channel, NULL, NULL); - g_io_channel_set_buffered(channel, false); - - event_pipe_source_id = g_io_add_watch(channel, G_IO_IN, - main_notify_event, NULL); - - event_channel = channel; - - event_pipe_mutex = g_mutex_new(); -} - -void event_pipe_deinit(void) -{ - g_mutex_free(event_pipe_mutex); - - g_source_remove(event_pipe_source_id); - g_io_channel_unref(event_channel); - -#ifndef WIN32 - /* By some strange reason this call hangs on Win32 */ - close(event_pipe[0]); -#endif - close(event_pipe[1]); -} - -void -event_pipe_register(enum pipe_event event, event_pipe_callback_t callback) -{ - assert((unsigned)event < PIPE_EVENT_MAX); - assert(event_pipe_callbacks[event] == NULL); - - event_pipe_callbacks[event] = callback; -} - -void event_pipe_emit(enum pipe_event event) -{ - ssize_t w; - - assert((unsigned)event < PIPE_EVENT_MAX); - - g_mutex_lock(event_pipe_mutex); - if (pipe_events[event]) { - /* already set: don't write */ - g_mutex_unlock(event_pipe_mutex); - return; - } - - pipe_events[event] = true; - g_mutex_unlock(event_pipe_mutex); - - w = write(event_pipe[1], "", 1); - if (w < 0 && errno != EAGAIN && errno != EINTR) - MPD_ERROR("error writing to pipe: %s", strerror(errno)); -} - -void event_pipe_emit_fast(enum pipe_event event) -{ - assert((unsigned)event < PIPE_EVENT_MAX); - - pipe_events[event] = true; - - G_GNUC_UNUSED ssize_t nbytes = write(event_pipe[1], "", 1); -} diff --git a/src/event_pipe.h b/src/event_pipe.h deleted file mode 100644 index 3734bb86..00000000 --- a/src/event_pipe.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef EVENT_PIPE_H -#define EVENT_PIPE_H - -#include <glib.h> - -enum pipe_event { - /** database update was finished */ - PIPE_EVENT_UPDATE, - - /** during database update, a song was deleted */ - PIPE_EVENT_DELETE, - - /** an idle event was emitted */ - PIPE_EVENT_IDLE, - - /** must call playlist_sync() */ - PIPE_EVENT_PLAYLIST, - - /** the current song's tag has changed */ - PIPE_EVENT_TAG, - - /** SIGHUP received: reload configuration, roll log file */ - PIPE_EVENT_RELOAD, - - /** a hardware mixer plugin has detected a change */ - PIPE_EVENT_MIXER, - - /** shutdown requested */ - PIPE_EVENT_SHUTDOWN, - - PIPE_EVENT_MAX -}; - -typedef void (*event_pipe_callback_t)(void); - -void event_pipe_init(void); - -void event_pipe_deinit(void); - -void -event_pipe_register(enum pipe_event event, event_pipe_callback_t callback); - -void event_pipe_emit(enum pipe_event event); - -/** - * Similar to event_pipe_emit(), but aimed for use in signal handlers: - * it doesn't lock the mutex, and doesn't log on error. That makes it - * potentially lossy, but for its intended use, that does not matter. - */ -void event_pipe_emit_fast(enum pipe_event event); - -#endif /* MAIN_NOTIFY_H */ diff --git a/src/fd_util.c b/src/fd_util.c index 882b4c7d..ea29d6ea 100644 --- a/src/fd_util.c +++ b/src/fd_util.c @@ -49,6 +49,10 @@ #include <sys/inotify.h> #endif +#ifdef HAVE_EVENTFD +#include <sys/eventfd.h> +#endif + #ifndef WIN32 static int @@ -328,6 +332,16 @@ inotify_init_cloexec(void) #endif +#ifdef HAVE_EVENTFD + +int +eventfd_cloexec_nonblock(unsigned initval, int flags) +{ + return eventfd(initval, flags | EFD_CLOEXEC | EFD_NONBLOCK); +} + +#endif + int close_socket(int fd) { diff --git a/src/fd_util.h b/src/fd_util.h index dd4df7a1..e65c6a69 100644 --- a/src/fd_util.h +++ b/src/fd_util.h @@ -51,6 +51,10 @@ struct sockaddr; +#ifdef __cplusplus +extern "C" { +#endif + /** * Wrapper for dup(), which sets the CLOEXEC flag on the new * descriptor. @@ -140,10 +144,25 @@ inotify_init_cloexec(void); #endif +#ifdef HAVE_EVENTFD + +/** + * Wrapper for eventfd() which sets the flags CLOEXEC and NONBLOCK + * flag (atomically if supported by the OS). + */ +int +eventfd_cloexec_nonblock(unsigned initval, int flags); + +#endif + /** * Portable wrapper for close(); use closesocket() on WIN32/WinSock. */ int close_socket(int fd); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif diff --git a/src/filter/AutoConvertFilterPlugin.cxx b/src/filter/AutoConvertFilterPlugin.cxx new file mode 100644 index 00000000..55ee4694 --- /dev/null +++ b/src/filter/AutoConvertFilterPlugin.cxx @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "AutoConvertFilterPlugin.hxx" +#include "ConvertFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "audio_format.h" + +#include <assert.h> + +class AutoConvertFilter final : public Filter { + /** + * The audio format being fed to the underlying filter. This + * plugin actually doesn't need this variable, we have it here + * just so our open() method doesn't return a stack pointer. + */ + audio_format child_audio_format; + + /** + * 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 const audio_format *Open(audio_format &af, GError **error_r); + virtual void Close(); + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r); +}; + +const struct audio_format * +AutoConvertFilter::Open(audio_format &in_audio_format, GError **error_r) +{ + assert(audio_format_valid(&in_audio_format)); + + /* open the "real" filter */ + + child_audio_format = in_audio_format; + const audio_format *out_audio_format = + filter->Open(child_audio_format, error_r); + if (out_audio_format == nullptr) + return nullptr; + + /* need to convert? */ + + if (!audio_format_equals(&child_audio_format, &in_audio_format)) { + /* yes - create a convert_filter */ + + convert = filter_new(&convert_filter_plugin, nullptr, error_r); + if (convert == nullptr) { + filter->Close(); + return nullptr; + } + + audio_format audio_format2 = in_audio_format; + const audio_format *audio_format3 = + convert->Open(audio_format2, error_r); + if (audio_format3 == nullptr) { + delete convert; + filter->Close(); + return nullptr; + } + + assert(audio_format_equals(&audio_format2, &in_audio_format)); + + convert_filter_set(convert, child_audio_format); + } else + /* no */ + convert = nullptr; + + return out_audio_format; +} + +void +AutoConvertFilter::Close() +{ + if (convert != nullptr) { + convert->Close(); + delete convert; + } + + filter->Close(); +} + +const void * +AutoConvertFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + if (convert != nullptr) { + src = convert->FilterPCM(src, src_size, &src_size, error_r); + if (src == nullptr) + return nullptr; + } + + return filter->FilterPCM(src, src_size, dest_size_r, error_r); +} + +Filter * +autoconvert_filter_new(Filter *filter) +{ + return new AutoConvertFilter(filter); +} diff --git a/src/filter/autoconvert_filter_plugin.h b/src/filter/AutoConvertFilterPlugin.hxx index def08ab7..7db72a34 100644 --- a/src/filter/autoconvert_filter_plugin.h +++ b/src/filter/AutoConvertFilterPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef AUTOCONVERT_FILTER_PLUGIN_H -#define AUTOCONVERT_FILTER_PLUGIN_H +#ifndef MPD_AUTOCONVERT_FILTER_PLUGIN_HXX +#define MPD_AUTOCONVERT_FILTER_PLUGIN_HXX -struct filter; +class Filter; /** * Creates a new "autoconvert" filter. When opened, it ensures that @@ -28,7 +28,7 @@ struct filter; * requests a different format, it automatically creates a * convert_filter. */ -struct filter * -autoconvert_filter_new(struct filter *filter); +Filter * +autoconvert_filter_new(Filter *filter); #endif diff --git a/src/filter/ChainFilterPlugin.cxx b/src/filter/ChainFilterPlugin.cxx new file mode 100644 index 00000000..c8666615 --- /dev/null +++ b/src/filter/ChainFilterPlugin.cxx @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ChainFilterPlugin.hxx" +#include "conf.h" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "audio_format.h" + +#include <glib.h> + +#include <list> + +#include <assert.h> + +class ChainFilter final : public Filter { + struct Child { + const char *name; + Filter *filter; + + Child(const char *_name, Filter *_filter) + :name(_name), filter(_filter) {} + ~Child() { + delete filter; + } + + Child(const Child &) = delete; + Child &operator=(const Child &) = delete; + }; + + std::list<Child> children; + +public: + void Append(const char *name, Filter *filter) { + children.emplace_back(name, filter); + } + + virtual const audio_format *Open(audio_format &af, GError **error_r); + virtual void Close(); + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r); + +private: + /** + * Close all filters in the chain until #until is reached. + * #until itself is not closed. + */ + void CloseUntil(const Filter *until); +}; + +static inline GQuark +filter_quark(void) +{ + return g_quark_from_static_string("filter"); +} + +static Filter * +chain_filter_init(gcc_unused const struct config_param *param, + gcc_unused GError **error_r) +{ + return new ChainFilter(); +} + +void +ChainFilter::CloseUntil(const Filter *until) +{ + for (auto &child : children) { + if (child.filter == until) + /* don't close this filter */ + return; + + /* close this filter */ + child.filter->Close(); + } + + /* this assertion fails if #until does not exist (anymore) */ + assert(false); +} + +static const struct audio_format * +chain_open_child(const char *name, Filter *filter, + const audio_format &prev_audio_format, + GError **error_r) +{ + audio_format conv_audio_format = prev_audio_format; + const audio_format *next_audio_format = + filter->Open(conv_audio_format, error_r); + if (next_audio_format == NULL) + return NULL; + + if (!audio_format_equals(&conv_audio_format, &prev_audio_format)) { + struct audio_format_string s; + + filter->Close(); + g_set_error(error_r, filter_quark(), 0, + "Audio format not supported by filter '%s': %s", + name, + audio_format_to_string(&prev_audio_format, &s)); + return NULL; + } + + return next_audio_format; +} + +const audio_format * +ChainFilter::Open(audio_format &in_audio_format, GError **error_r) +{ + const audio_format *audio_format = &in_audio_format; + + for (auto &child : children) { + audio_format = chain_open_child(child.name, child.filter, + *audio_format, error_r); + if (audio_format == NULL) { + /* rollback, close all children */ + CloseUntil(child.filter); + return NULL; + } + } + + /* return the output format of the last filter */ + return audio_format; +} + +void +ChainFilter::Close() +{ + for (auto &child : children) + child.filter->Close(); +} + +const void * +ChainFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + for (auto &child : children) { + /* feed the output of the previous filter as input + into the current one */ + src = child.filter->FilterPCM(src, src_size, &src_size, + error_r); + if (src == NULL) + return NULL; + } + + /* return the output of the last filter */ + *dest_size_r = src_size; + return src; +} + +const struct filter_plugin chain_filter_plugin = { + "chain", + chain_filter_init, +}; + +Filter * +filter_chain_new(void) +{ + return new ChainFilter(); +} + +void +filter_chain_append(Filter &_chain, const char *name, Filter *filter) +{ + ChainFilter &chain = (ChainFilter &)_chain; + + chain.Append(name, filter); +} diff --git a/src/filter/chain_filter_plugin.h b/src/filter/ChainFilterPlugin.hxx index 1dba4666..884c7ca1 100644 --- a/src/filter/chain_filter_plugin.h +++ b/src/filter/ChainFilterPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,15 +24,15 @@ * output to the next one. */ -#ifndef MPD_FILTER_CHAIN_H -#define MPD_FILTER_CHAIN_H +#ifndef MPD_FILTER_CHAIN_HXX +#define MPD_FILTER_CHAIN_HXX -struct filter; +class Filter; /** * Creates a new filter chain. */ -struct filter * +Filter * filter_chain_new(void); /** @@ -43,6 +43,6 @@ filter_chain_new(void); * @param filter the filter to be appended to #chain */ void -filter_chain_append(struct filter *chain, struct filter *filter); +filter_chain_append(Filter &chain, const char *name, Filter *filter); #endif diff --git a/src/filter/ConvertFilterPlugin.cxx b/src/filter/ConvertFilterPlugin.cxx new file mode 100644 index 00000000..2c690765 --- /dev/null +++ b/src/filter/ConvertFilterPlugin.cxx @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ConvertFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "conf.h" +#include "PcmConvert.hxx" +#include "util/Manual.hxx" +#include "audio_format.h" +#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. + */ + audio_format in_audio_format; + + /** + * The output audio format; the consumer of this plugin + * expects PCM data in this format. This defaults to + * #in_audio_format, and can be set with convert_filter_set(). + */ + audio_format out_audio_format; + + Manual<PcmConvert> state; + +public: + void Set(const audio_format &_out_audio_format) { + assert(audio_format_valid(&in_audio_format)); + assert(audio_format_valid(&out_audio_format)); + assert(audio_format_valid(&_out_audio_format)); + + out_audio_format = _out_audio_format; + } + + virtual const audio_format *Open(audio_format &af, GError **error_r); + virtual void Close(); + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r); +}; + +static Filter * +convert_filter_init(gcc_unused const struct config_param *param, + gcc_unused GError **error_r) +{ + return new ConvertFilter(); +} + +const struct audio_format * +ConvertFilter::Open(audio_format &audio_format, gcc_unused GError **error_r) +{ + assert(audio_format_valid(&audio_format)); + + in_audio_format = out_audio_format = audio_format; + state.Construct(); + + return &in_audio_format; +} + +void +ConvertFilter::Close() +{ + state.Destruct(); + + poison_undefined(&in_audio_format, sizeof(in_audio_format)); + poison_undefined(&out_audio_format, sizeof(out_audio_format)); +} + +const void * +ConvertFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + if (audio_format_equals(&in_audio_format, &out_audio_format)) { + /* optimized special case: no-op */ + *dest_size_r = src_size; + return src; + } + + return state->Convert(&in_audio_format, + src, src_size, + &out_audio_format, dest_size_r, + error_r); +} + +const struct filter_plugin convert_filter_plugin = { + "convert", + convert_filter_init, +}; + +void +convert_filter_set(Filter *_filter, const audio_format &out_audio_format) +{ + ConvertFilter *filter = (ConvertFilter *)_filter; + + filter->Set(out_audio_format); +} diff --git a/src/filter/convert_filter_plugin.h b/src/filter/ConvertFilterPlugin.hxx index 156adf8e..840bf496 100644 --- a/src/filter/convert_filter_plugin.h +++ b/src/filter/ConvertFilterPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef CONVERT_FILTER_PLUGIN_H -#define CONVERT_FILTER_PLUGIN_H +#ifndef MPD_CONVERT_FILTER_PLUGIN_HXX +#define MPD_CONVERT_FILTER_PLUGIN_HXX -struct filter; +class Filter; struct audio_format; /** @@ -30,7 +30,6 @@ struct audio_format; * the last in a chain. */ void -convert_filter_set(struct filter *filter, - const struct audio_format *out_audio_format); +convert_filter_set(Filter *filter, const audio_format &out_audio_format); #endif diff --git a/src/filter/NormalizeFilterPlugin.cxx b/src/filter/NormalizeFilterPlugin.cxx new file mode 100644 index 00000000..e18c5cdf --- /dev/null +++ b/src/filter/NormalizeFilterPlugin.cxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "pcm_buffer.h" +#include "audio_format.h" +#include "AudioCompress/compress.h" + +#include <assert.h> +#include <string.h> + +class NormalizeFilter final : public Filter { + struct Compressor *compressor; + + struct pcm_buffer buffer; + +public: + virtual const audio_format *Open(audio_format &af, GError **error_r); + virtual void Close(); + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r); +}; + +static Filter * +normalize_filter_init(gcc_unused const struct config_param *param, + gcc_unused GError **error_r) +{ + return new NormalizeFilter(); +} + +const struct audio_format * +NormalizeFilter::Open(audio_format &audio_format, gcc_unused GError **error_r) +{ + audio_format.format = SAMPLE_FORMAT_S16; + + compressor = Compressor_new(0); + pcm_buffer_init(&buffer); + + return &audio_format; +} + +void +NormalizeFilter::Close() +{ + pcm_buffer_deinit(&buffer); + Compressor_delete(compressor); +} + +const void * +NormalizeFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, gcc_unused GError **error_r) +{ + int16_t *dest = (int16_t *)pcm_buffer_get(&buffer, src_size); + memcpy(dest, src, src_size); + + Compressor_Process_int16(compressor, dest, src_size / 2); + + *dest_size_r = src_size; + return dest; +} + +const struct filter_plugin normalize_filter_plugin = { + "normalize", + normalize_filter_init, +}; diff --git a/src/filter/NullFilterPlugin.cxx b/src/filter/NullFilterPlugin.cxx new file mode 100644 index 00000000..d68065a3 --- /dev/null +++ b/src/filter/NullFilterPlugin.cxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * 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 "gcc.h" + +class NullFilter final : public Filter { +public: + virtual const audio_format *Open(audio_format &af, + gcc_unused GError **error_r) { + return ⁡ + } + + virtual void Close() {} + + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, + gcc_unused GError **error_r) { + *dest_size_r = src_size; + return src; + } +}; + +static Filter * +null_filter_init(gcc_unused const struct config_param *param, + gcc_unused GError **error_r) +{ + return new NullFilter(); +} + +const struct filter_plugin null_filter_plugin = { + "null", + null_filter_init, +}; diff --git a/src/filter/ReplayGainFilterPlugin.cxx b/src/filter/ReplayGainFilterPlugin.cxx new file mode 100644 index 00000000..1fa2269b --- /dev/null +++ b/src/filter/ReplayGainFilterPlugin.cxx @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ReplayGainFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "audio_format.h" +#include "replay_gain_info.h" +#include "replay_gain_config.h" +#include "MixerControl.hxx" +#include "PcmVolume.hxx" + +extern "C" { +#include "pcm_buffer.h" +} + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "replay_gain" + +class ReplayGainFilter final : public Filter { + /** + * If set, then this hardware mixer is used for applying + * replay gain, instead of the software volume library. + */ + struct mixer *mixer; + + /** + * The base volume level for scale=1.0, between 1 and 100 + * (including). + */ + unsigned base; + + enum replay_gain_mode mode; + + struct replay_gain_info info; + + /** + * The current volume, between 0 and a value that may or may not exceed + * #PCM_VOLUME_1. + * + * If the default value of true is used for replaygain_limit, the + * application of the volume to the signal will never cause clipping. + * + * On the other hand, if the user has set replaygain_limit to false, + * the chance of clipping is explicitly preferred if that's required to + * maintain a consistent audio level. Whether clipping will actually + * occur depends on what value the user is using for replaygain_preamp. + */ + unsigned volume; + + struct audio_format format; + + struct pcm_buffer buffer; + +public: + ReplayGainFilter() + :mixer(nullptr), mode(REPLAY_GAIN_OFF), + volume(PCM_VOLUME_1) { + replay_gain_info_init(&info); + } + + void SetMixer(struct mixer *_mixer, unsigned _base) { + assert(_mixer == NULL || (_base > 0 && _base <= 100)); + + mixer = _mixer; + base = _base; + + Update(); + } + + void SetInfo(const struct replay_gain_info *_info) { + if (_info != NULL) { + info = *_info; + replay_gain_info_complete(&info); + } else + replay_gain_info_init(&info); + + Update(); + } + + void SetMode(enum replay_gain_mode _mode) { + if (_mode == mode) + /* no change */ + return; + + g_debug("replay gain mode has changed %d->%d\n", mode, _mode); + + mode = _mode; + Update(); + } + + /** + * Recalculates the new volume after a property was changed. + */ + void Update(); + + virtual const audio_format *Open(audio_format &af, GError **error_r); + virtual void Close(); + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r); +}; + +static inline GQuark +replay_gain_quark(void) +{ + return g_quark_from_static_string("replay_gain"); +} + +void +ReplayGainFilter::Update() +{ + if (mode != REPLAY_GAIN_OFF) { + float scale = replay_gain_tuple_scale(&info.tuples[mode], + replay_gain_preamp, replay_gain_missing_preamp, replay_gain_limit); + g_debug("scale=%f\n", (double)scale); + + volume = pcm_float_to_volume(scale); + } else + volume = PCM_VOLUME_1; + + if (mixer != NULL) { + /* update the hardware mixer volume */ + + unsigned _volume = (volume * base) / PCM_VOLUME_1; + if (_volume > 100) + _volume = 100; + + GError *error = NULL; + if (!mixer_set_volume(mixer, _volume, &error)) { + g_warning("Failed to update hardware mixer: %s", + error->message); + g_error_free(error); + } + } +} + +static Filter * +replay_gain_filter_init(gcc_unused const struct config_param *param, + gcc_unused GError **error_r) +{ + return new ReplayGainFilter(); +} + +const audio_format * +ReplayGainFilter::Open(audio_format &af, gcc_unused GError **error_r) +{ + format = af; + pcm_buffer_init(&buffer); + + return &format; +} + +void +ReplayGainFilter::Close() +{ + pcm_buffer_deinit(&buffer); +} + +const void * +ReplayGainFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + + *dest_size_r = src_size; + + if (volume == PCM_VOLUME_1) + /* optimized special case: 100% volume = no-op */ + return src; + + void *dest = pcm_buffer_get(&buffer, 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, + sample_format(format.format), + volume); + if (!success) { + g_set_error(error_r, replay_gain_quark(), 0, + "pcm_volume() has failed"); + return NULL; + } + + return dest; +} + +const struct filter_plugin replay_gain_filter_plugin = { + "replay_gain", + replay_gain_filter_init, +}; + +void +replay_gain_filter_set_mixer(Filter *_filter, struct mixer *mixer, + unsigned base) +{ + ReplayGainFilter *filter = (ReplayGainFilter *)_filter; + + filter->SetMixer(mixer, base); +} + +void +replay_gain_filter_set_info(Filter *_filter, const replay_gain_info *info) +{ + ReplayGainFilter *filter = (ReplayGainFilter *)_filter; + + filter->SetInfo(info); +} + +void +replay_gain_filter_set_mode(Filter *_filter, enum replay_gain_mode mode) +{ + ReplayGainFilter *filter = (ReplayGainFilter *)_filter; + + filter->SetMode(mode); +} diff --git a/src/filter/replay_gain_filter_plugin.h b/src/filter/ReplayGainFilterPlugin.hxx index 45b738e4..dd8ceb95 100644 --- a/src/filter/replay_gain_filter_plugin.h +++ b/src/filter/ReplayGainFilterPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,12 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef REPLAY_GAIN_FILTER_PLUGIN_H -#define REPLAY_GAIN_FILTER_PLUGIN_H +#ifndef MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX +#define MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX #include "replay_gain_info.h" -struct filter; +class Filter; struct mixer; /** @@ -34,7 +34,7 @@ struct mixer; * (including). */ void -replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer, +replay_gain_filter_set_mixer(Filter *_filter, struct mixer *mixer, unsigned base); /** @@ -44,7 +44,9 @@ replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer, * gain data is available for the current song */ void -replay_gain_filter_set_info(struct filter *filter, - const struct replay_gain_info *info); +replay_gain_filter_set_info(Filter *filter, const replay_gain_info *info); + +void +replay_gain_filter_set_mode(Filter *filter, enum replay_gain_mode mode); #endif diff --git a/src/filter/route_filter_plugin.c b/src/filter/RouteFilterPlugin.cxx index 3bf8677e..55957893 100644 --- a/src/filter/route_filter_plugin.c +++ b/src/filter/RouteFilterPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -41,25 +41,19 @@ #include "config.h" #include "conf.h" +#include "ConfigQuark.hxx" #include "audio_format.h" #include "audio_check.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" #include "pcm_buffer.h" #include <assert.h> #include <string.h> #include <stdlib.h> - -struct route_filter { - - /** - * Inherit (and support cast to/from) filter - */ - struct filter base; - +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 @@ -109,21 +103,31 @@ struct route_filter { */ struct pcm_buffer output_buffer; +public: + RouteFilter():sources(nullptr) {} + ~RouteFilter() { + g_free(sources); + } + + /** + * Parse the "routes" section, a string on the form + * a>b, c>d, e>f, ... + * where a... are non-unique, non-negative integers + * and input channel a gets copied to output channel b, etc. + * @param param the configuration block to read + * @param filter a route_filter whose min_channels and sources[] to set + * @return true on success, false on error + */ + bool Configure(const config_param *param, GError **error_r); + + virtual const audio_format *Open(audio_format &af, GError **error_r); + virtual void Close(); + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r); }; -/** - * Parse the "routes" section, a string on the form - * a>b, c>d, e>f, ... - * where a... are non-unique, non-negative integers - * and input channel a gets copied to output channel b, etc. - * @param param the configuration block to read - * @param filter a route_filter whose min_channels and sources[] to set - * @return true on success, false on error - */ -static bool -route_filter_parse(const struct config_param *param, - struct route_filter *filter, - GError **error_r) { +bool +RouteFilter::Configure(const config_param *param, GError **error_r) { /* TODO: * With a more clever way of marking "don't copy to output N", @@ -138,8 +142,8 @@ route_filter_parse(const struct config_param *param, const char *routes = config_get_block_string(param, "routes", "0>0, 1>1"); - filter->min_input_channels = 0; - filter->min_output_channels = 0; + min_input_channels = 0; + min_output_channels = 0; tokens = g_strsplit(routes, ",", 255); number_of_copies = g_strv_length(tokens); @@ -170,28 +174,28 @@ route_filter_parse(const struct config_param *param, // Keep track of the highest channel numbers seen // as either in- or outputs - if (source >= filter->min_input_channels) - filter->min_input_channels = source + 1; - if (dest >= filter->min_output_channels) - filter->min_output_channels = dest + 1; + if (source >= min_input_channels) + min_input_channels = source + 1; + if (dest >= min_output_channels) + min_output_channels = dest + 1; g_strfreev(sd); } - if (!audio_valid_channel_count(filter->min_output_channels)) { + if (!audio_valid_channel_count(min_output_channels)) { g_strfreev(tokens); g_set_error(error_r, audio_format_quark(), 0, "Invalid number of output channels requested: %d", - filter->min_output_channels); + min_output_channels); return false; } // Allocate a map of "copy nothing to me" - filter->sources = - g_malloc(filter->min_output_channels * sizeof(signed char)); + sources = (signed char *) + g_malloc(min_output_channels * sizeof(signed char)); - for (int i=0; i<filter->min_output_channels; ++i) - filter->sources[i] = -1; + for (int i=0; i<min_output_channels; ++i) + sources[i] = -1; // Run through the spec again, and save the // actual mapping output <- input @@ -215,7 +219,7 @@ route_filter_parse(const struct config_param *param, source = strtol(sd[0], NULL, 10); dest = strtol(sd[1], NULL, 10); - filter->sources[dest] = source; + sources[dest] = source; g_strfreev(sd); } @@ -225,92 +229,73 @@ route_filter_parse(const struct config_param *param, return true; } -static struct filter * -route_filter_init(const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct route_filter *filter = g_new(struct route_filter, 1); - filter_init(&filter->base, &route_filter_plugin); - - // Allocate and set the filter->sources[] array - route_filter_parse(param, filter, error_r); - - return &filter->base; -} - -static void -route_filter_finish(struct filter *_filter) +static Filter * +route_filter_init(const config_param *param, GError **error_r) { - struct route_filter *filter = (struct route_filter *)_filter; + RouteFilter *filter = new RouteFilter(); + if (!filter->Configure(param, error_r)) { + delete filter; + return nullptr; + } - g_free(filter->sources); - g_free(filter); + return filter; } -static const struct audio_format * -route_filter_open(struct filter *_filter, struct audio_format *audio_format, - G_GNUC_UNUSED GError **error_r) +const struct audio_format * +RouteFilter::Open(audio_format &audio_format, gcc_unused GError **error_r) { - struct route_filter *filter = (struct route_filter *)_filter; - // Copy the input format for later reference - filter->input_format = *audio_format; - filter->input_frame_size = - audio_format_frame_size(&filter->input_format); + input_format = audio_format; + input_frame_size = audio_format_frame_size(&input_format); // Decide on an output format which has enough channels, // and is otherwise identical - filter->output_format = *audio_format; - filter->output_format.channels = filter->min_output_channels; + output_format = audio_format; + output_format.channels = min_output_channels; // Precalculate this simple value, to speed up allocation later - filter->output_frame_size = - audio_format_frame_size(&filter->output_format); + output_frame_size = audio_format_frame_size(&output_format); // This buffer grows as needed - pcm_buffer_init(&filter->output_buffer); + pcm_buffer_init(&output_buffer); - return &filter->output_format; + return &output_format; } -static void -route_filter_close(struct filter *_filter) +void +RouteFilter::Close() { - struct route_filter *filter = (struct route_filter *)_filter; - - pcm_buffer_deinit(&filter->output_buffer); + pcm_buffer_deinit(&output_buffer); } -static const void * -route_filter_filter(struct filter *_filter, - const void *src, size_t src_size, - size_t *dest_size_r, G_GNUC_UNUSED GError **error_r) +const void * +RouteFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, gcc_unused GError **error_r) { - struct route_filter *filter = (struct route_filter *)_filter; - - size_t number_of_frames = src_size / filter->input_frame_size; + size_t number_of_frames = src_size / input_frame_size; size_t bytes_per_frame_per_channel = - audio_format_sample_size(&filter->input_format); + audio_format_sample_size(&input_format); // A moving pointer that always refers to channel 0 in the input, at the currently handled frame - const uint8_t *base_source = src; + const uint8_t *base_source = (const uint8_t *)src; // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output uint8_t *chan_destination; // Grow our reusable buffer, if needed, and set the moving pointer - *dest_size_r = number_of_frames * filter->output_frame_size; - chan_destination = pcm_buffer_get(&filter->output_buffer, *dest_size_r); + *dest_size_r = number_of_frames * output_frame_size; + chan_destination = (uint8_t *) + pcm_buffer_get(&output_buffer, *dest_size_r); // Perform our copy operations, with N input channels and M output channels for (unsigned int s=0; s<number_of_frames; ++s) { // Need to perform one copy per output channel - for (unsigned int c=0; c<filter->min_output_channels; ++c) { - if (filter->sources[c] == -1 || - (unsigned)filter->sources[c] >= filter->input_format.channels) { + 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, @@ -320,7 +305,7 @@ route_filter_filter(struct filter *_filter, // Get the data from channel sources[c] // and copy it to the output const uint8_t *data = base_source + - (filter->sources[c] * bytes_per_frame_per_channel); + (sources[c] * bytes_per_frame_per_channel); memcpy(chan_destination, data, bytes_per_frame_per_channel); @@ -331,18 +316,14 @@ route_filter_filter(struct filter *_filter, // Go on to the next N input samples - base_source += filter->input_frame_size; + base_source += input_frame_size; } // Here it is, ladies and gentlemen! Rerouted data! - return (void *) filter->output_buffer.buffer; + return (void *) output_buffer.buffer; } const struct filter_plugin route_filter_plugin = { - .name = "route", - .init = route_filter_init, - .finish = route_filter_finish, - .open = route_filter_open, - .close = route_filter_close, - .filter = route_filter_filter, + "route", + route_filter_init, }; diff --git a/src/filter/VolumeFilterPlugin.cxx b/src/filter/VolumeFilterPlugin.cxx new file mode 100644 index 00000000..0689f5da --- /dev/null +++ b/src/filter/VolumeFilterPlugin.cxx @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "VolumeFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "conf.h" +#include "pcm_buffer.h" +#include "PcmVolume.hxx" +#include "audio_format.h" + +#include <assert.h> +#include <string.h> + +class VolumeFilter final : public Filter { + /** + * The current volume, from 0 to #PCM_VOLUME_1. + */ + unsigned volume; + + struct audio_format format; + + struct pcm_buffer 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 const audio_format *Open(audio_format &af, GError **error_r); + virtual void Close(); + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r); +}; + +static inline GQuark +volume_quark(void) +{ + return g_quark_from_static_string("pcm_volume"); +} + +static Filter * +volume_filter_init(gcc_unused const struct config_param *param, + gcc_unused GError **error_r) +{ + return new VolumeFilter(); +} + +const struct audio_format * +VolumeFilter::Open(audio_format &audio_format, gcc_unused GError **error_r) +{ + format = audio_format; + pcm_buffer_init(&buffer); + + return &format; +} + +void +VolumeFilter::Close() +{ + pcm_buffer_deinit(&buffer); +} + +const void * +VolumeFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + *dest_size_r = src_size; + + if (volume >= PCM_VOLUME_1) + /* optimized special case: 100% volume = no-op */ + return src; + + void *dest = pcm_buffer_get(&buffer, 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, + sample_format(format.format), + volume); + if (!success) { + g_set_error(error_r, volume_quark(), 0, + "pcm_volume() has failed"); + return NULL; + } + + return dest; +} + +const struct filter_plugin volume_filter_plugin = { + "volume", + volume_filter_init, +}; + +unsigned +volume_filter_get(const Filter *_filter) +{ + const VolumeFilter *filter = + (const VolumeFilter *)_filter; + + return filter->GetVolume(); +} + +void +volume_filter_set(Filter *_filter, unsigned volume) +{ + VolumeFilter *filter = (VolumeFilter *)_filter; + + filter->SetVolume(volume); +} + diff --git a/src/filter/volume_filter_plugin.h b/src/filter/VolumeFilterPlugin.hxx index 5b16f7e5..822b7e93 100644 --- a/src/filter/volume_filter_plugin.h +++ b/src/filter/VolumeFilterPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,15 +17,15 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef VOLUME_FILTER_PLUGIN_H -#define VOLUME_FILTER_PLUGIN_H +#ifndef MPD_VOLUME_FILTER_PLUGIN_HXX +#define MPD_VOLUME_FILTER_PLUGIN_HXX -struct filter; +class Filter; unsigned -volume_filter_get(const struct filter *filter); +volume_filter_get(const Filter *filter); void -volume_filter_set(struct filter *filter, unsigned volume); +volume_filter_set(Filter *filter, unsigned volume); #endif diff --git a/src/filter/autoconvert_filter_plugin.c b/src/filter/autoconvert_filter_plugin.c deleted file mode 100644 index 3826a0fb..00000000 --- a/src/filter/autoconvert_filter_plugin.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "filter/autoconvert_filter_plugin.h" -#include "filter/convert_filter_plugin.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" -#include "conf.h" -#include "pcm_convert.h" -#include "audio_format.h" -#include "poison.h" - -#include <assert.h> -#include <string.h> - -struct autoconvert_filter { - struct filter base; - - /** - * The audio format being fed to the underlying filter. This - * plugin actually doesn't need this variable, we have it here - * just so our open() method doesn't return a stack pointer. - */ - struct audio_format in_audio_format; - - /** - * The underlying filter. - */ - struct filter *filter; - - /** - * A convert_filter, just in case conversion is needed. NULL - * if unused. - */ - struct filter *convert; -}; - -static void -autoconvert_filter_finish(struct filter *_filter) -{ - struct autoconvert_filter *filter = - (struct autoconvert_filter *)_filter; - - filter_free(filter->filter); - g_free(filter); -} - -static const struct audio_format * -autoconvert_filter_open(struct filter *_filter, - struct audio_format *in_audio_format, - GError **error_r) -{ - struct autoconvert_filter *filter = - (struct autoconvert_filter *)_filter; - const struct audio_format *out_audio_format; - - assert(audio_format_valid(in_audio_format)); - - /* open the "real" filter */ - - filter->in_audio_format = *in_audio_format; - - out_audio_format = filter_open(filter->filter, - &filter->in_audio_format, error_r); - if (out_audio_format == NULL) - return NULL; - - /* need to convert? */ - - if (!audio_format_equals(&filter->in_audio_format, in_audio_format)) { - /* yes - create a convert_filter */ - struct audio_format audio_format2 = *in_audio_format; - const struct audio_format *audio_format3; - - filter->convert = filter_new(&convert_filter_plugin, NULL, - error_r); - if (filter->convert == NULL) { - filter_close(filter->filter); - return NULL; - } - - audio_format3 = filter_open(filter->convert, &audio_format2, - error_r); - if (audio_format3 == NULL) { - filter_free(filter->convert); - filter_close(filter->filter); - return NULL; - } - - assert(audio_format_equals(&audio_format2, in_audio_format)); - - convert_filter_set(filter->convert, &filter->in_audio_format); - } else - /* no */ - filter->convert = NULL; - - return out_audio_format; -} - -static void -autoconvert_filter_close(struct filter *_filter) -{ - struct autoconvert_filter *filter = - (struct autoconvert_filter *)_filter; - - if (filter->convert != NULL) { - filter_close(filter->convert); - filter_free(filter->convert); - } - - filter_close(filter->filter); -} - -static const void * -autoconvert_filter_filter(struct filter *_filter, const void *src, - size_t src_size, size_t *dest_size_r, - GError **error_r) -{ - struct autoconvert_filter *filter = - (struct autoconvert_filter *)_filter; - - if (filter->convert != NULL) { - src = filter_filter(filter->convert, src, src_size, &src_size, - error_r); - if (src == NULL) - return NULL; - } - - return filter_filter(filter->filter, src, src_size, dest_size_r, - error_r); -} - -static const struct filter_plugin autoconvert_filter_plugin = { - .name = "convert", - .finish = autoconvert_filter_finish, - .open = autoconvert_filter_open, - .close = autoconvert_filter_close, - .filter = autoconvert_filter_filter, -}; - -struct filter * -autoconvert_filter_new(struct filter *_filter) -{ - struct autoconvert_filter *filter = - g_new(struct autoconvert_filter, 1); - - filter_init(&filter->base, &autoconvert_filter_plugin); - filter->filter = _filter; - - return &filter->base; -} diff --git a/src/filter/chain_filter_plugin.c b/src/filter/chain_filter_plugin.c deleted file mode 100644 index 2c785a36..00000000 --- a/src/filter/chain_filter_plugin.c +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "conf.h" -#include "filter/chain_filter_plugin.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" -#include "audio_format.h" - -#include <assert.h> - -struct filter_chain { - /** the base class */ - struct filter base; - - GSList *children; -}; - -static inline GQuark -filter_quark(void) -{ - return g_quark_from_static_string("filter"); -} - -static struct filter * -chain_filter_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct filter_chain *chain = g_new(struct filter_chain, 1); - - filter_init(&chain->base, &chain_filter_plugin); - chain->children = NULL; - - return &chain->base; -} - -static void -chain_free_child(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct filter *filter = data; - - filter_free(filter); -} - -static void -chain_filter_finish(struct filter *_filter) -{ - struct filter_chain *chain = (struct filter_chain *)_filter; - - g_slist_foreach(chain->children, chain_free_child, NULL); - g_slist_free(chain->children); - - g_free(chain); -} - -/** - * Close all filters in the chain until #until is reached. #until - * itself is not closed. - */ -static void -chain_close_until(struct filter_chain *chain, const struct filter *until) -{ - GSList *i = chain->children; - struct filter *filter; - - while (true) { - /* this assertion fails if #until does not exist - (anymore) */ - assert(i != NULL); - - if (i->data == until) - /* don't close this filter */ - break; - - /* close this filter */ - filter = i->data; - filter_close(filter); - - i = g_slist_next(i); - } -} - -static const struct audio_format * -chain_open_child(struct filter *filter, - const struct audio_format *prev_audio_format, - GError **error_r) -{ - struct audio_format conv_audio_format = *prev_audio_format; - const struct audio_format *next_audio_format; - - next_audio_format = filter_open(filter, &conv_audio_format, error_r); - if (next_audio_format == NULL) - return NULL; - - if (!audio_format_equals(&conv_audio_format, prev_audio_format)) { - struct audio_format_string s; - - filter_close(filter); - g_set_error(error_r, filter_quark(), 0, - "Audio format not supported by filter '%s': %s", - filter->plugin->name, - audio_format_to_string(prev_audio_format, &s)); - return NULL; - } - - return next_audio_format; -} - -static const struct audio_format * -chain_filter_open(struct filter *_filter, struct audio_format *in_audio_format, - GError **error_r) -{ - struct filter_chain *chain = (struct filter_chain *)_filter; - const struct audio_format *audio_format = in_audio_format; - - for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) { - struct filter *filter = i->data; - - audio_format = chain_open_child(filter, audio_format, error_r); - if (audio_format == NULL) { - /* rollback, close all children */ - chain_close_until(chain, filter); - return NULL; - } - } - - /* return the output format of the last filter */ - return audio_format; -} - -static void -chain_close_child(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct filter *filter = data; - - filter_close(filter); -} - -static void -chain_filter_close(struct filter *_filter) -{ - struct filter_chain *chain = (struct filter_chain *)_filter; - - g_slist_foreach(chain->children, chain_close_child, NULL); -} - -static const void * -chain_filter_filter(struct filter *_filter, - const void *src, size_t src_size, - size_t *dest_size_r, GError **error_r) -{ - struct filter_chain *chain = (struct filter_chain *)_filter; - - for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) { - struct filter *filter = i->data; - - /* feed the output of the previous filter as input - into the current one */ - src = filter_filter(filter, src, src_size, &src_size, error_r); - if (src == NULL) - return NULL; - } - - /* return the output of the last filter */ - *dest_size_r = src_size; - return src; -} - -const struct filter_plugin chain_filter_plugin = { - .name = "chain", - .init = chain_filter_init, - .finish = chain_filter_finish, - .open = chain_filter_open, - .close = chain_filter_close, - .filter = chain_filter_filter, -}; - -struct filter * -filter_chain_new(void) -{ - struct filter *filter = filter_new(&chain_filter_plugin, NULL, NULL); - /* chain_filter_init() never fails */ - assert(filter != NULL); - - return filter; -} - -void -filter_chain_append(struct filter *_chain, struct filter *filter) -{ - struct filter_chain *chain = (struct filter_chain *)_chain; - - chain->children = g_slist_append(chain->children, filter); -} - diff --git a/src/filter/convert_filter_plugin.c b/src/filter/convert_filter_plugin.c deleted file mode 100644 index c55b69af..00000000 --- a/src/filter/convert_filter_plugin.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "filter/convert_filter_plugin.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" -#include "conf.h" -#include "pcm_convert.h" -#include "audio_format.h" -#include "poison.h" - -#include <assert.h> -#include <string.h> - -struct convert_filter { - struct filter base; - - /** - * The current convert, from 0 to #PCM_CONVERT_1. - */ - unsigned convert; - - /** - * The input audio format; PCM data is passed to the filter() - * method in this format. - */ - struct audio_format in_audio_format; - - /** - * The output audio format; the consumer of this plugin - * expects PCM data in this format. This defaults to - * #in_audio_format, and can be set with convert_filter_set(). - */ - struct audio_format out_audio_format; - - struct pcm_convert_state state; -}; - -static struct filter * -convert_filter_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct convert_filter *filter = g_new(struct convert_filter, 1); - - filter_init(&filter->base, &convert_filter_plugin); - return &filter->base; -} - -static void -convert_filter_finish(struct filter *filter) -{ - g_free(filter); -} - -static const struct audio_format * -convert_filter_open(struct filter *_filter, struct audio_format *audio_format, - G_GNUC_UNUSED GError **error_r) -{ - struct convert_filter *filter = (struct convert_filter *)_filter; - - assert(audio_format_valid(audio_format)); - - filter->in_audio_format = filter->out_audio_format = *audio_format; - pcm_convert_init(&filter->state); - - return &filter->in_audio_format; -} - -static void -convert_filter_close(struct filter *_filter) -{ - struct convert_filter *filter = (struct convert_filter *)_filter; - - pcm_convert_deinit(&filter->state); - - poison_undefined(&filter->in_audio_format, - sizeof(filter->in_audio_format)); - poison_undefined(&filter->out_audio_format, - sizeof(filter->out_audio_format)); -} - -static const void * -convert_filter_filter(struct filter *_filter, const void *src, size_t src_size, - size_t *dest_size_r, GError **error_r) -{ - struct convert_filter *filter = (struct convert_filter *)_filter; - const void *dest; - - if (audio_format_equals(&filter->in_audio_format, - &filter->out_audio_format)) { - /* optimized special case: no-op */ - *dest_size_r = src_size; - return src; - } - - dest = pcm_convert(&filter->state, &filter->in_audio_format, - src, src_size, - &filter->out_audio_format, dest_size_r, - error_r); - if (dest == NULL) - return NULL; - - return dest; -} - -const struct filter_plugin convert_filter_plugin = { - .name = "convert", - .init = convert_filter_init, - .finish = convert_filter_finish, - .open = convert_filter_open, - .close = convert_filter_close, - .filter = convert_filter_filter, -}; - -void -convert_filter_set(struct filter *_filter, - const struct audio_format *out_audio_format) -{ - struct convert_filter *filter = (struct convert_filter *)_filter; - - assert(filter != NULL); - assert(audio_format_valid(&filter->in_audio_format)); - assert(audio_format_valid(&filter->out_audio_format)); - assert(out_audio_format != NULL); - assert(audio_format_valid(out_audio_format)); - - filter->out_audio_format = *out_audio_format; -} diff --git a/src/filter/normalize_filter_plugin.c b/src/filter/normalize_filter_plugin.c deleted file mode 100644 index 2151482e..00000000 --- a/src/filter/normalize_filter_plugin.c +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" -#include "pcm_buffer.h" -#include "audio_format.h" -#include "AudioCompress/compress.h" - -#include <assert.h> -#include <string.h> - -struct normalize_filter { - struct filter filter; - - struct Compressor *compressor; - - struct pcm_buffer buffer; -}; - -static inline GQuark -normalize_quark(void) -{ - return g_quark_from_static_string("normalize"); -} - -static struct filter * -normalize_filter_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct normalize_filter *filter = g_new(struct normalize_filter, 1); - - filter_init(&filter->filter, &normalize_filter_plugin); - - return &filter->filter; -} - -static void -normalize_filter_finish(struct filter *filter) -{ - g_free(filter); -} - -static const struct audio_format * -normalize_filter_open(struct filter *_filter, - struct audio_format *audio_format, - G_GNUC_UNUSED GError **error_r) -{ - struct normalize_filter *filter = (struct normalize_filter *)_filter; - - audio_format->format = SAMPLE_FORMAT_S16; - - filter->compressor = Compressor_new(0); - - pcm_buffer_init(&filter->buffer); - - return audio_format; -} - -static void -normalize_filter_close(struct filter *_filter) -{ - struct normalize_filter *filter = (struct normalize_filter *)_filter; - - pcm_buffer_deinit(&filter->buffer); - Compressor_delete(filter->compressor); -} - -static const void * -normalize_filter_filter(struct filter *_filter, - const void *src, size_t src_size, size_t *dest_size_r, - G_GNUC_UNUSED GError **error_r) -{ - struct normalize_filter *filter = (struct normalize_filter *)_filter; - void *dest; - - dest = pcm_buffer_get(&filter->buffer, src_size); - - memcpy(dest, src, src_size); - - Compressor_Process_int16(filter->compressor, dest, src_size / 2); - - *dest_size_r = src_size; - return dest; -} - -const struct filter_plugin normalize_filter_plugin = { - .name = "normalize", - .init = normalize_filter_init, - .finish = normalize_filter_finish, - .open = normalize_filter_open, - .close = normalize_filter_close, - .filter = normalize_filter_filter, -}; diff --git a/src/filter/null_filter_plugin.c b/src/filter/null_filter_plugin.c deleted file mode 100644 index e7c99882..00000000 --- a/src/filter/null_filter_plugin.c +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This filter plugin does nothing. That is not quite useful, except - * for testing the filter core, or as a template for new filter - * plugins. - */ - -#include "config.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" - -#include <assert.h> - -struct null_filter { - struct filter filter; -}; - -static struct filter * -null_filter_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct null_filter *filter = g_new(struct null_filter, 1); - - filter_init(&filter->filter, &null_filter_plugin); - return &filter->filter; -} - -static void -null_filter_finish(struct filter *_filter) -{ - struct null_filter *filter = (struct null_filter *)_filter; - - g_free(filter); -} - -static const struct audio_format * -null_filter_open(struct filter *_filter, struct audio_format *audio_format, - G_GNUC_UNUSED GError **error_r) -{ - struct null_filter *filter = (struct null_filter *)_filter; - (void)filter; - - return audio_format; -} - -static void -null_filter_close(struct filter *_filter) -{ - struct null_filter *filter = (struct null_filter *)_filter; - (void)filter; -} - -static const void * -null_filter_filter(struct filter *_filter, - const void *src, size_t src_size, - size_t *dest_size_r, G_GNUC_UNUSED GError **error_r) -{ - struct null_filter *filter = (struct null_filter *)_filter; - (void)filter; - - /* return the unmodified source buffer */ - *dest_size_r = src_size; - return src; -} - -const struct filter_plugin null_filter_plugin = { - .name = "null", - .init = null_filter_init, - .finish = null_filter_finish, - .open = null_filter_open, - .close = null_filter_close, - .filter = null_filter_filter, -}; diff --git a/src/filter/replay_gain_filter_plugin.c b/src/filter/replay_gain_filter_plugin.c deleted file mode 100644 index 583a09f9..00000000 --- a/src/filter/replay_gain_filter_plugin.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "filter/replay_gain_filter_plugin.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" -#include "audio_format.h" -#include "pcm_buffer.h" -#include "pcm_volume.h" -#include "replay_gain_info.h" -#include "replay_gain_config.h" -#include "mixer_control.h" - -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "replay_gain" - -struct replay_gain_filter { - struct filter filter; - - /** - * If set, then this hardware mixer is used for applying - * replay gain, instead of the software volume library. - */ - struct mixer *mixer; - - /** - * The base volume level for scale=1.0, between 1 and 100 - * (including). - */ - unsigned base; - - enum replay_gain_mode mode; - - struct replay_gain_info info; - - /** - * The current volume, between 0 and a value that may or may not exceed - * #PCM_VOLUME_1. - * - * If the default value of true is used for replaygain_limit, the - * application of the volume to the signal will never cause clipping. - * - * On the other hand, if the user has set replaygain_limit to false, - * the chance of clipping is explicitly preferred if that's required to - * maintain a consistent audio level. Whether clipping will actually - * occur depends on what value the user is using for replaygain_preamp. - */ - unsigned volume; - - struct audio_format audio_format; - - struct pcm_buffer buffer; -}; - -static inline GQuark -replay_gain_quark(void) -{ - return g_quark_from_static_string("replay_gain"); -} - -/** - * Recalculates the new volume after a property was changed. - */ -static void -replay_gain_filter_update(struct replay_gain_filter *filter) -{ - if (filter->mode != REPLAY_GAIN_OFF) { - float scale = replay_gain_tuple_scale(&filter->info.tuples[filter->mode], - replay_gain_preamp, replay_gain_missing_preamp, replay_gain_limit); - g_debug("scale=%f\n", (double)scale); - - filter->volume = pcm_float_to_volume(scale); - } else - filter->volume = PCM_VOLUME_1; - - if (filter->mixer != NULL) { - /* update the hardware mixer volume */ - - unsigned volume = (filter->volume * filter->base) / PCM_VOLUME_1; - if (volume > 100) - volume = 100; - - GError *error = NULL; - if (!mixer_set_volume(filter->mixer, volume, &error)) { - g_warning("Failed to update hardware mixer: %s", - error->message); - g_error_free(error); - } - } -} - -static struct filter * -replay_gain_filter_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct replay_gain_filter *filter = g_new(struct replay_gain_filter, 1); - - filter_init(&filter->filter, &replay_gain_filter_plugin); - filter->mixer = NULL; - - filter->mode = replay_gain_get_real_mode(); - replay_gain_info_init(&filter->info); - filter->volume = PCM_VOLUME_1; - - return &filter->filter; -} - -static void -replay_gain_filter_finish(struct filter *filter) -{ - g_free(filter); -} - -static const struct audio_format * -replay_gain_filter_open(struct filter *_filter, - struct audio_format *audio_format, - G_GNUC_UNUSED GError **error_r) -{ - struct replay_gain_filter *filter = - (struct replay_gain_filter *)_filter; - - filter->audio_format = *audio_format; - pcm_buffer_init(&filter->buffer); - - return &filter->audio_format; -} - -static void -replay_gain_filter_close(struct filter *_filter) -{ - struct replay_gain_filter *filter = - (struct replay_gain_filter *)_filter; - - pcm_buffer_deinit(&filter->buffer); -} - -static const void * -replay_gain_filter_filter(struct filter *_filter, - const void *src, size_t src_size, - size_t *dest_size_r, GError **error_r) -{ - struct replay_gain_filter *filter = - (struct replay_gain_filter *)_filter; - bool success; - void *dest; - enum replay_gain_mode rg_mode; - - /* check if the mode has been changed since the last call */ - rg_mode = replay_gain_get_real_mode(); - - if (filter->mode != rg_mode) { - g_debug("replay gain mode has changed %d->%d\n", filter->mode, rg_mode); - filter->mode = rg_mode; - replay_gain_filter_update(filter); - } - - *dest_size_r = src_size; - - if (filter->volume == PCM_VOLUME_1) - /* optimized special case: 100% volume = no-op */ - return src; - - dest = pcm_buffer_get(&filter->buffer, src_size); - - if (filter->volume <= 0) { - /* optimized special case: 0% volume = memset(0) */ - /* XXX is this valid for all sample formats? What - about floating point? */ - memset(dest, 0, src_size); - return dest; - } - - memcpy(dest, src, src_size); - - success = pcm_volume(dest, src_size, filter->audio_format.format, - filter->volume); - if (!success) { - g_set_error(error_r, replay_gain_quark(), 0, - "pcm_volume() has failed"); - return NULL; - } - - return dest; -} - -const struct filter_plugin replay_gain_filter_plugin = { - .name = "replay_gain", - .init = replay_gain_filter_init, - .finish = replay_gain_filter_finish, - .open = replay_gain_filter_open, - .close = replay_gain_filter_close, - .filter = replay_gain_filter_filter, -}; - -void -replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer, - unsigned base) -{ - struct replay_gain_filter *filter = - (struct replay_gain_filter *)_filter; - - assert(mixer == NULL || (base > 0 && base <= 100)); - - filter->mixer = mixer; - filter->base = base; - - replay_gain_filter_update(filter); -} - -void -replay_gain_filter_set_info(struct filter *_filter, - const struct replay_gain_info *info) -{ - struct replay_gain_filter *filter = - (struct replay_gain_filter *)_filter; - - if (info != NULL) { - filter->info = *info; - replay_gain_info_complete(&filter->info); - } else - replay_gain_info_init(&filter->info); - - replay_gain_filter_update(filter); -} diff --git a/src/filter/volume_filter_plugin.c b/src/filter/volume_filter_plugin.c deleted file mode 100644 index 3260e898..00000000 --- a/src/filter/volume_filter_plugin.c +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "filter/volume_filter_plugin.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" -#include "conf.h" -#include "pcm_buffer.h" -#include "pcm_volume.h" -#include "audio_format.h" - -#include <assert.h> -#include <string.h> - -struct volume_filter { - struct filter filter; - - /** - * The current volume, from 0 to #PCM_VOLUME_1. - */ - unsigned volume; - - struct audio_format audio_format; - - struct pcm_buffer buffer; -}; - -static inline GQuark -volume_quark(void) -{ - return g_quark_from_static_string("pcm_volume"); -} - -static struct filter * -volume_filter_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct volume_filter *filter = g_new(struct volume_filter, 1); - - filter_init(&filter->filter, &volume_filter_plugin); - filter->volume = PCM_VOLUME_1; - - return &filter->filter; -} - -static void -volume_filter_finish(struct filter *filter) -{ - g_free(filter); -} - -static const struct audio_format * -volume_filter_open(struct filter *_filter, struct audio_format *audio_format, - G_GNUC_UNUSED GError **error_r) -{ - struct volume_filter *filter = (struct volume_filter *)_filter; - - filter->audio_format = *audio_format; - pcm_buffer_init(&filter->buffer); - - return &filter->audio_format; -} - -static void -volume_filter_close(struct filter *_filter) -{ - struct volume_filter *filter = (struct volume_filter *)_filter; - - pcm_buffer_deinit(&filter->buffer); -} - -static const void * -volume_filter_filter(struct filter *_filter, const void *src, size_t src_size, - size_t *dest_size_r, GError **error_r) -{ - struct volume_filter *filter = (struct volume_filter *)_filter; - bool success; - void *dest; - - *dest_size_r = src_size; - - if (filter->volume >= PCM_VOLUME_1) - /* optimized special case: 100% volume = no-op */ - return src; - - dest = pcm_buffer_get(&filter->buffer, src_size); - - if (filter->volume <= 0) { - /* optimized special case: 0% volume = memset(0) */ - /* XXX is this valid for all sample formats? What - about floating point? */ - memset(dest, 0, src_size); - return dest; - } - - memcpy(dest, src, src_size); - - success = pcm_volume(dest, src_size, filter->audio_format.format, - filter->volume); - if (!success) { - g_set_error(error_r, volume_quark(), 0, - "pcm_volume() has failed"); - return NULL; - } - - return dest; -} - -const struct filter_plugin volume_filter_plugin = { - .name = "volume", - .init = volume_filter_init, - .finish = volume_filter_finish, - .open = volume_filter_open, - .close = volume_filter_close, - .filter = volume_filter_filter, -}; - -unsigned -volume_filter_get(const struct filter *_filter) -{ - const struct volume_filter *filter = - (const struct volume_filter *)_filter; - - assert(filter->filter.plugin == &volume_filter_plugin); - assert(filter->volume <= PCM_VOLUME_1); - - return filter->volume; -} - -void -volume_filter_set(struct filter *_filter, unsigned volume) -{ - struct volume_filter *filter = (struct volume_filter *)_filter; - - assert(filter->filter.plugin == &volume_filter_plugin); - assert(volume <= PCM_VOLUME_1); - - filter->volume = volume; -} - diff --git a/src/filter_plugin.h b/src/filter_plugin.h deleted file mode 100644 index 58e34dfb..00000000 --- a/src/filter_plugin.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This header declares the filter_plugin class. It describes a - * plugin API for objects which filter raw PCM data. - */ - -#ifndef MPD_FILTER_PLUGIN_H -#define MPD_FILTER_PLUGIN_H - -#include <glib.h> - -#include <stdbool.h> -#include <stddef.h> - -struct config_param; -struct filter; - -struct filter_plugin { - const char *name; - - /** - * Allocates and configures a filter. - */ - struct filter *(*init)(const struct config_param *param, - GError **error_r); - - /** - * Free instance data. - */ - void (*finish)(struct filter *filter); - - /** - * Opens a filter. - * - * @param audio_format the audio format of incoming data; the - * plugin may modify the object to enforce another input - * format - */ - const struct audio_format * - (*open)(struct filter *filter, - struct audio_format *audio_format, - GError **error_r); - - /** - * Closes a filter. - */ - void (*close)(struct filter *filter); - - /** - * Filters a block of PCM data. - */ - const void *(*filter)(struct filter *filter, - const void *src, size_t src_size, - size_t *dest_buffer_r, - GError **error_r); -}; - -/** - * Creates a new instance of the specified filter plugin. - * - * @param plugin the filter plugin - * @param param optional configuration section - * @param error location to store the error occurring, or NULL to - * ignore errors. - * @return a new filter object, or NULL on error - */ -struct filter * -filter_new(const struct filter_plugin *plugin, - const struct config_param *param, GError **error_r); - -/** - * Creates a new filter, loads configuration and the plugin name from - * the specified configuration section. - * - * @param param the configuration section - * @param error location to store the error occurring, or NULL to - * ignore errors. - * @return a new filter object, or NULL on error - */ -struct filter * -filter_configured_new(const struct config_param *param, GError **error_r); - -/** - * Deletes a filter. It must be closed prior to calling this - * function, see filter_close(). - * - * @param filter the filter object - */ -void -filter_free(struct filter *filter); - -/** - * Opens the filter, preparing it for filter_filter(). - * - * @param filter the filter object - * @param audio_format the audio format of incoming data; the plugin - * may modify the object to enforce another input format - * @param error location to store the error occurring, or NULL to - * ignore errors. - * @return the format of outgoing data - */ -const struct audio_format * -filter_open(struct filter *filter, struct audio_format *audio_format, - GError **error_r); - -/** - * Closes the filter. After that, you may call filter_open() again. - * - * @param filter the filter object - */ -void -filter_close(struct filter *filter); - -/** - * Filters a block of PCM data. - * - * @param filter the filter object - * @param src the input buffer - * @param src_size the size of #src_buffer in bytes - * @param dest_size_r the size of the returned buffer - * @param error location to store the error occurring, or NULL to - * ignore errors. - * @return the destination buffer on success (will be invalidated by - * filter_close() or filter_filter()), NULL on error - */ -const void * -filter_filter(struct filter *filter, const void *src, size_t src_size, - size_t *dest_size_r, - GError **error_r); - -#endif diff --git a/src/fs/DirectoryReader.hxx b/src/fs/DirectoryReader.hxx new file mode 100644 index 00000000..caa1e90e --- /dev/null +++ b/src/fs/DirectoryReader.hxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FS_DIRECTORY_READER_HXX +#define MPD_FS_DIRECTORY_READER_HXX + +#include "check.h" +#include "Path.hxx" + +#include <dirent.h> + +/** + * Reader for directory entries. + */ +class DirectoryReader { + DIR *const dirp; + dirent *ent; +public: + /** + * Creates new directory reader for the specified #dir. + */ + explicit DirectoryReader(const Path &dir) + : dirp(opendir(dir.c_str())), + ent(nullptr) { + } + + DirectoryReader(const DirectoryReader &other) = delete; + DirectoryReader &operator=(const DirectoryReader &other) = delete; + + /** + * Destroys this instance. + */ + ~DirectoryReader() { + if (!Failed()) + closedir(dirp); + } + + /** + * Checks if directory failed to open. + */ + bool Failed() const { + return dirp == nullptr; + } + + /** + * Checks if directory entry is available. + */ + bool HasEntry() const { + assert(!Failed()); + return ent != nullptr; + } + + /** + * Reads next directory entry. + */ + bool ReadEntry() { + assert(!Failed()); + ent = readdir(dirp); + return HasEntry(); + } + + /** + * Extracts directory entry that was previously read by #ReadEntry. + */ + Path GetEntry() const { + assert(HasEntry()); + return Path::FromFS(ent->d_name); + } +}; + +#endif diff --git a/src/fs/FileSystem.cxx b/src/fs/FileSystem.cxx new file mode 100644 index 00000000..70ab01fb --- /dev/null +++ b/src/fs/FileSystem.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FileSystem.hxx" + +#include <errno.h> + +Path ReadLink(const Path &path) +{ +#ifdef WIN32 + (void)path; + errno = EINVAL; + return Path::Null(); +#else + char buffer[MPD_PATH_MAX]; + ssize_t size = readlink(path.c_str(), buffer, MPD_PATH_MAX); + if (size < 0) + return Path::Null(); + if (size >= MPD_PATH_MAX) { + errno = ENOMEM; + return Path::Null(); + } + buffer[size] = '\0'; + return Path::FromFS(buffer); +#endif +} diff --git a/src/fs/FileSystem.hxx b/src/fs/FileSystem.hxx new file mode 100644 index 00000000..2e1701c8 --- /dev/null +++ b/src/fs/FileSystem.hxx @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FS_FILESYSTEM_HXX +#define MPD_FS_FILESYSTEM_HXX + +#include "check.h" +#include "fd_util.h" + +#include "Path.hxx" + +#include <sys/stat.h> +#include <unistd.h> +#include <assert.h> +#include <stdio.h> + +namespace FOpenMode { +/** + * Open mode for reading text files. + */ +constexpr Path::const_pointer ReadText = "r"; + +/** + * Open mode for reading binary files. + */ +constexpr Path::const_pointer ReadBinary = "rb"; + +/** + * Open mode for writing text files. + */ +constexpr Path::const_pointer WriteText = "w"; + +/** + * Open mode for writing binary files. + */ +constexpr Path::const_pointer WriteBinary = "wb"; + +/** + * Open mode for appending text files. + */ +constexpr Path::const_pointer AppendText = "a"; + +/** + * Open mode for appending binary files. + */ +constexpr Path::const_pointer AppendBinary = "ab"; +} + +/** + * Wrapper for fopen() that uses #Path names. + */ +static inline FILE *FOpen(const Path &file, Path::const_pointer mode) +{ + return fopen(file.c_str(), mode); +} + +/** + * Wrapper for open_cloexec() that uses #Path names. + */ +static inline int OpenFile(const Path &file, int flags, int mode) +{ + return open_cloexec(file.c_str(), flags, mode); +} + +/** + * Wrapper for rename() that uses #Path names. + */ +static inline bool RenameFile(const Path &oldpath, const Path &newpath) +{ + return rename(oldpath.c_str(), newpath.c_str()) == 0; +} + +/** + * Wrapper for stat() that uses #Path names. + */ +static inline bool StatFile(const Path &file, struct stat &buf, + bool follow_symlinks = true) +{ +#ifdef WIN32 + (void)follow_symlinks; + return stat(file.c_str(), &buf) == 0; +#else + int ret = follow_symlinks + ? stat(file.c_str(), &buf) + : lstat(file.c_str(), &buf); + return ret == 0; +#endif +} + +/** + * Wrapper for unlink() that uses #Path names. + */ +static inline bool RemoveFile(const Path &file) +{ + return unlink(file.c_str()) == 0; +} + +/** + * Wrapper for readlink() that uses #Path names. + */ +Path ReadLink(const Path &path); + +/** + * Wrapper for access() that uses #Path names. + */ +static inline bool CheckAccess(const Path &path, int mode) +{ +#ifdef WIN32 + (void)path; + (void)mode; + return true; +#else + return access(path.c_str(), mode) == 0; +#endif +} + +/** + * Checks if #Path exists and is a regular file. + */ +static inline bool FileExists(const Path &path, + bool follow_symlinks = true) +{ + struct stat buf; + return StatFile(path, buf, follow_symlinks) && S_ISREG(buf.st_mode); +} + +/** + * Checks if #Path exists and is a directory. + */ +static inline bool DirectoryExists(const Path &path, + bool follow_symlinks = true) +{ + struct stat buf; + return StatFile(path, buf, follow_symlinks) && S_ISDIR(buf.st_mode); +} + +/** + * Checks if #Path exists. + */ +static inline bool PathExists(const Path &path, + bool follow_symlinks = true) +{ + struct stat buf; + return StatFile(path, buf, follow_symlinks); +} + +#endif diff --git a/src/path.c b/src/fs/Path.cxx index 59a91a0f..cb808b36 100644 --- a/src/path.c +++ b/src/fs/Path.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,10 @@ */ #include "config.h" -#include "path.h" +#include "fs/Path.hxx" #include "conf.h" #include "mpd_error.h" +#include "gcc.h" #include <glib.h> @@ -35,56 +36,88 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "path" -static char *fs_charset; +/** + * Maximal number of bytes required to represent path name in UTF-8 + * (including nul-terminator). + * This value is a rought estimate of upper bound. + * It's based on path name limit in bytes (MPD_PATH_MAX) + * and assumption that some weird encoding could represent some UTF-8 4 byte + * sequences with single byte. + */ +#define MPD_PATH_MAX_UTF8 ((MPD_PATH_MAX - 1) * 4 + 1) + +std::string fs_charset; -char * -fs_charset_to_utf8(const char *path_fs) +std::string Path::ToUTF8(const_pointer path_fs) { - return g_convert(path_fs, -1, - "utf-8", fs_charset, - NULL, NULL, NULL); + if (path_fs == nullptr) + return std::string(); + + GIConv conv = g_iconv_open("utf-8", fs_charset.c_str()); + if (conv == reinterpret_cast<GIConv>(-1)) + return std::string(); + + // g_iconv() does not need nul-terminator, + // std::string could be created without it too. + char path_utf8[MPD_PATH_MAX_UTF8 - 1]; + char *in = const_cast<char *>(path_fs); + char *out = path_utf8; + size_t in_left = strlen(path_fs); + size_t out_left = sizeof(path_utf8); + + size_t ret = g_iconv(conv, &in, &in_left, &out, &out_left); + + g_iconv_close(conv); + + if (ret == static_cast<size_t>(-1) || in_left > 0) + return std::string(); + + return std::string(path_utf8, sizeof(path_utf8) - out_left); } -char * -utf8_to_fs_charset(const char *path_utf8) +Path Path::FromUTF8(const char *path_utf8) { gchar *p; p = g_convert(path_utf8, -1, - fs_charset, "utf-8", + fs_charset.c_str(), "utf-8", NULL, NULL, NULL); - if (p == NULL) - /* fall back to UTF-8 */ - p = g_strdup(path_utf8); - return p; + return Path(Donate(), p); } -static void -path_set_fs_charset(const char *charset) +gcc_pure +static bool +IsSupportedCharset(const char *charset) { - char *test; + /* convert a space to check if the charset is valid */ + char *test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL); + if (test == NULL) + return false; + g_free(test); + return true; +} + +static void +SetFSCharset(const char *charset) +{ assert(charset != NULL); - /* convert a space to ensure that the charset is valid */ - test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL); - if (test == NULL) + if (!IsSupportedCharset(charset)) MPD_ERROR("invalid filesystem charset: %s", charset); - g_free(test); - g_free(fs_charset); - fs_charset = g_strdup(charset); + fs_charset = charset; - g_debug("path_set_fs_charset: fs charset is: %s", fs_charset); + g_debug("SetFSCharset: fs charset is: %s", fs_charset.c_str()); } -const char *path_get_fs_charset(void) +const std::string &Path::GetFSCharset() { return fs_charset; } -void path_global_init(void) +void Path::GlobalInit() { const char *charset = NULL; @@ -102,21 +135,16 @@ void path_global_init(void) * However this is true only if <gstdio.h> helpers are used. * MPD uses regular <stdio.h> functions. * Those functions use encoding determined by GetACP(). */ - char win_charset[13]; + static char win_charset[13]; sprintf(win_charset, "cp%u", GetACP()); charset = win_charset; #endif } if (charset) { - path_set_fs_charset(charset); + SetFSCharset(charset); } else { g_message("setting filesystem charset to ISO-8859-1"); - path_set_fs_charset("ISO-8859-1"); + SetFSCharset("ISO-8859-1"); } } - -void path_global_finish(void) -{ - g_free(fs_charset); -} diff --git a/src/fs/Path.hxx b/src/fs/Path.hxx new file mode 100644 index 00000000..eaab2bde --- /dev/null +++ b/src/fs/Path.hxx @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FS_PATH_HXX +#define MPD_FS_PATH_HXX + +#include "check.h" +#include "gcc.h" + +#include <glib.h> + +#include <algorithm> +#include <string> + +#include <assert.h> +#include <string.h> +#include <limits.h> + +#if !defined(MPD_PATH_MAX) +# if defined(WIN32) +# define MPD_PATH_MAX 260 +# elif defined(MAXPATHLEN) +# define MPD_PATH_MAX MAXPATHLEN +# elif defined(PATH_MAX) +# define MPD_PATH_MAX PATH_MAX +# else +# define MPD_PATH_MAX 256 +# endif +#endif + +/** + * A path name in the native file system character set. + */ +class Path { +public: + typedef char value_type; + typedef value_type *pointer; + typedef const value_type *const_pointer; + +private: + pointer value; + + struct Donate {}; + + /** + * Donate the allocated pointer to a new #Path object. + */ + constexpr Path(Donate, pointer _value):value(_value) {} + + /** + * Release memory allocated by the value, but do not clear the + * value pointer. + */ + void Free() { + /* free() can be optimized by gcc, while g_free() can + not: when the compiler knows that the value is + nullptr, it will not emit a free() call in the + inlined destructor; however on Windows, we need to + call g_free(), because the value has been allocated + by GLib, and on Windows, this matters */ +#ifdef WIN32 + g_free(value); +#else + free(value); +#endif + } + +public: + /** + * Copy a #Path object. + */ + Path(const Path &other) + :value(g_strdup(other.value)) {} + + /** + * Move a #Path object. + */ + Path(Path &&other):value(other.value) { + other.value = nullptr; + } + + ~Path() { + Free(); + } + + /** + * Return a "nulled" instance. Its IsNull() method will + * return true. Such an object must not be used. + * + * @see IsNull() + */ + gcc_const + static Path Null() { + return Path(Donate(), nullptr); + } + + /** + * Join two path components with the path separator. + */ + gcc_pure gcc_nonnull_all + static Path Build(const_pointer a, const_pointer b) { + return Path(Donate(), g_build_filename(a, b, nullptr)); + } + + gcc_pure gcc_nonnull_all + static Path Build(const_pointer a, const Path &b) { + return Build(a, b.c_str()); + } + + gcc_pure gcc_nonnull_all + static Path Build(const Path &a, const_pointer b) { + return Build(a.c_str(), b); + } + + gcc_pure + static Path Build(const Path &a, const Path &b) { + return Build(a.c_str(), b.c_str()); + } + + /** + * Convert a C string that is already in the filesystem + * character set to a #Path instance. + */ + gcc_pure + static Path FromFS(const_pointer fs) { + return Path(Donate(), g_strdup(fs)); + } + + /** + * Convert a UTF-8 C string to a #Path instance. + * Returns return a "nulled" instance on error. + */ + gcc_pure + static Path FromUTF8(const char *path_utf8); + + /** + * Convert the path to UTF-8. + * Returns empty string on error or if #path_fs is null pointer. + */ + gcc_pure + static std::string ToUTF8(const_pointer path_fs); + + /** + * Performs global one-time initialization of this class. + */ + static void GlobalInit(); + + /** + * Gets file system character set name. + */ + static const std::string &GetFSCharset(); + + /** + * Copy a #Path object. + */ + Path &operator=(const Path &other) { + if (this != &other) { + Free(); + value = g_strdup(other.value); + } + + return *this; + } + + /** + * Move a #Path object. + */ + Path &operator=(Path &&other) { + std::swap(value, other.value); + return *this; + } + + /** + * Steal the allocated value. This object has an undefined + * value, and the caller is response for freeing this method's + * return value. + */ + pointer Steal() { + pointer result = value; + value = nullptr; + return result; + } + + /** + * Check if this is a "nulled" instance. A "nulled" instance + * must not be used. + */ + bool IsNull() const { + return value == nullptr; + } + + /** + * Clear this object's value, make it "nulled". + * + * @see IsNull() + */ + void SetNull() { + Free(); + value = nullptr; + } + + gcc_pure + bool empty() const { + assert(value != nullptr); + + return *value == 0; + } + + /** + * @return the length of this string in number of "value_type" + * elements (which may not be the number of characters). + */ + gcc_pure + size_t length() const { + assert(value != nullptr); + + return strlen(value); + } + + /** + * Returns the value as a const C string. The returned + * pointer is invalidated whenever the value of life of this + * instance ends. + */ + gcc_pure + const_pointer c_str() const { + assert(value != nullptr); + + return value; + } + + /** + * Convert the path to UTF-8. + * Returns empty string on error or if this instance is "nulled" + * (#IsNull returns true). + */ + std::string ToUTF8() const { + return ToUTF8(value); + } + + /** + * Gets directory name of this path. + * Returns a "nulled" instance on error. + */ + Path GetDirectoryName() const { + assert(value != nullptr); + return Path(Donate(), g_path_get_dirname(value)); + } +}; + +#endif @@ -32,6 +32,9 @@ */ #if GCC_CHECK_VERSION(3,0) +# define gcc_const __attribute__((const)) +# define gcc_pure __attribute__((pure)) +# define gcc_malloc __attribute__((malloc)) # define gcc_must_check __attribute__ ((warn_unused_result)) # define gcc_packed __attribute__ ((packed)) /* these are very useful for type checking */ @@ -41,11 +44,21 @@ # define gcc_fprintf__ __attribute__ ((format(printf,4,5))) # define gcc_scanf __attribute__ ((format(scanf,1,2))) # define gcc_used __attribute__ ((used)) +# define gcc_unused __attribute__((unused)) +# define gcc_warn_unused_result __attribute__((warn_unused_result)) /* # define inline inline __attribute__ ((always_inline)) */ # define gcc_noinline __attribute__ ((noinline)) # define gcc_nonnull(...) __attribute__((nonnull(__VA_ARGS__))) # define gcc_nonnull_all __attribute__((nonnull)) + +# define gcc_likely(x) __builtin_expect (!!(x), 1) +# define gcc_unlikely(x) __builtin_expect (!!(x), 0) + #else +# define gcc_unused +# define gcc_const +# define gcc_pure +# define gcc_malloc # define gcc_must_check # define gcc_packed # define gcc_printf @@ -54,10 +67,38 @@ # define gcc_fprintf__ # define gcc_scanf # define gcc_used +# define gcc_unused +# define gcc_warn_unused_result /* # define inline */ # define gcc_noinline # define gcc_nonnull(...) # define gcc_nonnull_all + +# define gcc_likely(x) (x) +# define gcc_unlikely(x) (x) + +#endif + +#ifdef __cplusplus + +#ifdef __GNUC__ +/* "__restrict__" is a GCC extension for C++ */ +#define restrict __restrict__ +#else +/* disable it on other compilers */ +#define restrict +#endif + +#if !defined(__clang__) && defined(__GNUC__) && !GCC_CHECK_VERSION(4,6) +#error Your gcc version is too old. MPD requires gcc 4.6 or newer. +#endif + +/* support for C++11 "override" was added in gcc 4.7 */ +#if !defined(__clang__) && defined(__GNUC__) && !GCC_CHECK_VERSION(4,7) +#define override +#define final +#endif + #endif #endif /* MPD_GCC_H */ diff --git a/src/sig_handlers.h b/src/gerror.h index 32e9bad9..fe4c54da 100644 --- a/src/sig_handlers.h +++ b/src/gerror.h @@ -17,9 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_SIG_HANDLERS_H -#define MPD_SIG_HANDLERS_H +#ifndef MPD_GERROR_H +#define MPD_GERROR_H -void initSigHandlers(void); +typedef struct _GError GError; #endif diff --git a/src/glib_compat.h b/src/glib_compat.h index 97d1fdc0..a16b9c6e 100644 --- a/src/glib_compat.h +++ b/src/glib_compat.h @@ -28,17 +28,6 @@ #include <glib.h> -#if !GLIB_CHECK_VERSION(2,18,0) - -static inline void -g_set_error_literal(GError **err, GQuark domain, gint code, - const gchar *message) -{ - g_set_error(err, domain, code, "%s", message); -} - -#endif - #if !GLIB_CHECK_VERSION(2,28,0) static inline gint64 diff --git a/src/icy_metadata.h b/src/icy_metadata.h deleted file mode 100644 index 9797122c..00000000 --- a/src/icy_metadata.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef ICY_METADATA_H -#define ICY_METADATA_H - -#include <stdbool.h> -#include <stddef.h> - -struct icy_metadata { - size_t data_size, data_rest; - - size_t meta_size, meta_position; - char *meta_data; - - struct tag *tag; -}; - -/** - * Initialize a disabled icy_metadata object. - */ -static inline void -icy_clear(struct icy_metadata *im) -{ - im->data_size = 0; -} - -/** - * Initialize an enabled icy_metadata object with the specified - * data_size (from the icy-metaint HTTP response header). - */ -static inline void -icy_start(struct icy_metadata *im, size_t data_size) -{ - im->data_size = im->data_rest = data_size; - im->meta_size = 0; - im->tag = NULL; -} - -/** - * Resets the icy_metadata. Call this after rewinding the stream. - */ -void -icy_reset(struct icy_metadata *im); - -void -icy_deinit(struct icy_metadata *im); - -/** - * Checks whether the icy_metadata object is enabled. - */ -static inline bool -icy_defined(const struct icy_metadata *im) -{ - return im->data_size > 0; -} - -/** - * Evaluates data. Returns the number of bytes of normal data which - * can be read by the caller, but not more than "length". If the - * return value is smaller than "length", the caller should invoke - * icy_meta(). - */ -size_t -icy_data(struct icy_metadata *im, size_t length); - -/** - * Reads metadata from the stream. Returns the number of bytes - * consumed. If the return value is smaller than "length", the caller - * should invoke icy_data(). - */ -size_t -icy_meta(struct icy_metadata *im, const void *data, size_t length); - -static inline struct tag * -icy_tag(struct icy_metadata *im) -{ - struct tag *tag = im->tag; - im->tag = NULL; - return tag; -} - -#endif diff --git a/src/inotify_source.h b/src/inotify_source.h deleted file mode 100644 index f92e18e3..00000000 --- a/src/inotify_source.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INOTIFY_SOURCE_H -#define MPD_INOTIFY_SOURCE_H - -#include <glib.h> - -typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask, - const char *name, void *ctx); - -struct mpd_inotify_source; - -/** - * Creates a new inotify source and registers it in the GLib main - * loop. - * - * @param a callback invoked for events received from the kernel - */ -struct mpd_inotify_source * -mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx, - GError **error_r); - -void -mpd_inotify_source_free(struct mpd_inotify_source *source); - -/** - * Adds a path to the notify list. - * - * @return a watch descriptor or -1 on error - */ -int -mpd_inotify_source_add(struct mpd_inotify_source *source, - const char *path_fs, unsigned mask, - GError **error_r); - -/** - * Removes a path from the notify list. - * - * @param wd the watch descriptor returned by mpd_inotify_source_add() - */ -void -mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd); - -#endif diff --git a/src/input/archive_input_plugin.c b/src/input/ArchiveInputPlugin.cxx index 4a038b9e..0d856527 100644 --- a/src/input/archive_input_plugin.c +++ b/src/input/ArchiveInputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,12 @@ */ #include "config.h" -#include "input/archive_input_plugin.h" -#include "archive_api.h" -#include "archive_list.h" -#include "input_plugin.h" +#include "ArchiveInputPlugin.hxx" +#include "ArchiveLookup.hxx" +#include "ArchiveList.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "InputPlugin.hxx" #include <glib.h> @@ -35,11 +37,10 @@ */ static struct input_stream * input_archive_open(const char *pathname, - GMutex *mutex, GCond *cond, + Mutex &mutex, Cond &cond, GError **error_r) { const struct archive_plugin *arplug; - struct archive_file *file; char *archive, *filename, *suffix, *pname; struct input_stream *is; @@ -62,20 +63,31 @@ input_archive_open(const char *pathname, return NULL; } - file = archive_file_open(arplug, archive, error_r); - if (file == NULL) + auto file = archive_file_open(arplug, archive, error_r); + if (file == NULL) { + g_free(pname); return NULL; + } //setup fileops - is = archive_file_open_stream(file, filename, mutex, cond, - error_r); - archive_file_close(file); + is = file->OpenStream(filename, mutex, cond, error_r); g_free(pname); + file->Close(); return is; } const struct input_plugin input_plugin_archive = { - .name = "archive", - .open = input_archive_open, + "archive", + nullptr, + nullptr, + input_archive_open, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, }; diff --git a/src/input/archive_input_plugin.h b/src/input/ArchiveInputPlugin.hxx index 51095f37..96fcd0dd 100644 --- a/src/input/archive_input_plugin.h +++ b/src/input/ArchiveInputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INPUT_ARCHIVE_H -#define MPD_INPUT_ARCHIVE_H +#ifndef MPD_INPUT_ARCHIVE_HXX +#define MPD_INPUT_ARCHIVE_HXX extern const struct input_plugin input_plugin_archive; diff --git a/src/input/cdio_paranoia_input_plugin.c b/src/input/CdioParanoiaInputPlugin.cxx index 1de7623a..f0fa835b 100644 --- a/src/input/cdio_paranoia_input_plugin.c +++ b/src/input/CdioParanoiaInputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,10 +22,10 @@ */ #include "config.h" -#include "input/cdio_paranoia_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "refcount.h" +#include "CdioParanoiaInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" #include <stdio.h> #include <stdint.h> @@ -38,7 +38,7 @@ #include <cdio/paranoia.h> #include <cdio/cd_types.h> -struct input_cdio_paranoia { +struct CdioParanoiaInputStream { struct input_stream base; cdrom_drive_t *drv; @@ -52,6 +52,23 @@ struct input_cdio_paranoia { char buffer[CDIO_CD_FRAMESIZE_RAW]; int buffer_lsn; + + CdioParanoiaInputStream(const char *uri, Mutex &mutex, Cond &cond, + int _trackno) + :base(input_plugin_cdio_paranoia, uri, mutex, cond), + drv(nullptr), cdio(nullptr), para(nullptr), + trackno(_trackno) + { + } + + ~CdioParanoiaInputStream() { + if (para != nullptr) + cdio_paranoia_free(para); + if (drv != nullptr) + cdio_cddap_close_no_free_cdio(drv); + if (cdio != nullptr) + cdio_destroy(cdio); + } }; static inline GQuark @@ -63,17 +80,9 @@ cdio_quark(void) static void input_cdio_close(struct input_stream *is) { - struct input_cdio_paranoia *i = (struct input_cdio_paranoia *)is; + CdioParanoiaInputStream *i = (CdioParanoiaInputStream *)is; - if (i->para) - cdio_paranoia_free(i->para); - if (i->drv) - cdio_cddap_close_no_free_cdio( i->drv); - if (i->cdio) - cdio_destroy( i->cdio ); - - input_stream_deinit(&i->base); - g_free(i); + delete i; } struct cdio_uri { @@ -97,7 +106,7 @@ parse_cdio_uri(struct cdio_uri *dest, const char *src, GError **error_r) } const char *slash = strrchr(src, '/'); - if (slash == NULL) { + if (slash == nullptr) { /* play the whole CD in the specified drive */ g_strlcpy(dest->device, src, sizeof(dest->device)); dest->track = -1; @@ -131,9 +140,10 @@ parse_cdio_uri(struct cdio_uri *dest, const char *src, GError **error_r) static char * cdio_detect_device(void) { - char **devices = cdio_get_devices_with_cap(NULL, CDIO_FS_AUDIO, false); - if (devices == NULL) - return NULL; + char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO, + false); + if (devices == nullptr) + return nullptr; char *device = g_strdup(devices[0]); cdio_free_device_list(devices); @@ -143,55 +153,47 @@ cdio_detect_device(void) static struct input_stream * input_cdio_open(const char *uri, - GMutex *mutex, GCond *cond, + Mutex &mutex, Cond &cond, GError **error_r) { - struct input_cdio_paranoia *i; - struct cdio_uri parsed_uri; if (!parse_cdio_uri(&parsed_uri, uri, error_r)) - return NULL; - - i = g_new(struct input_cdio_paranoia, 1); - input_stream_init(&i->base, &input_plugin_cdio_paranoia, uri, - mutex, cond); + return nullptr; - /* initialize everything (should be already) */ - i->drv = NULL; - i->cdio = NULL; - i->para = NULL; - i->trackno = parsed_uri.track; + CdioParanoiaInputStream *i = + new CdioParanoiaInputStream(uri, mutex, cond, + parsed_uri.track); /* get list of CD's supporting CD-DA */ char *device = parsed_uri.device[0] != 0 ? g_strdup(parsed_uri.device) : cdio_detect_device(); - if (device == NULL) { + if (device == nullptr) { g_set_error(error_r, cdio_quark(), 0, "Unable find or access a CD-ROM drive with an audio CD in it."); - input_cdio_close(&i->base); - return NULL; + delete i; + return nullptr; } /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */ i->cdio = cdio_open(device, DRIVER_UNKNOWN); g_free(device); - i->drv = cdio_cddap_identify_cdio(i->cdio, 1, NULL); + i->drv = cdio_cddap_identify_cdio(i->cdio, 1, nullptr); if ( !i->drv ) { g_set_error(error_r, cdio_quark(), 0, "Unable to identify audio CD disc."); - input_cdio_close(&i->base); - return NULL; + delete i; + return nullptr; } cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT); if ( 0 != cdio_cddap_open(i->drv) ) { g_set_error(error_r, cdio_quark(), 0, "Unable to open disc."); - input_cdio_close(&i->base); - return NULL; + delete i; + return nullptr; } bool reverse_endian; @@ -212,8 +214,8 @@ input_cdio_open(const char *uri, g_set_error(error_r, cdio_quark(), 0, "Drive returns unknown data type %d", data_bigendianp(i->drv)); - input_cdio_close(&i->base); - return NULL; + delete i; + return nullptr; } i->lsn_relofs = 0; @@ -250,7 +252,7 @@ static bool input_cdio_seek(struct input_stream *is, goffset offset, int whence, GError **error_r) { - struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; + CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; /* calculate absolute offset */ switch (whence) { @@ -288,7 +290,7 @@ static size_t input_cdio_read(struct input_stream *is, void *ptr, size_t length, GError **error_r) { - struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; + CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; size_t nbytes = 0; int diff; size_t len, maxwrite; @@ -305,7 +307,7 @@ input_cdio_read(struct input_stream *is, void *ptr, size_t length, //current sector was changed ? if (cis->lsn_relofs != cis->buffer_lsn) { - rbuf = cdio_paranoia_read(cis->para, NULL); + rbuf = cdio_paranoia_read(cis->para, nullptr); s_err = cdda_errors(cis->drv); if (s_err) { @@ -356,16 +358,22 @@ input_cdio_read(struct input_stream *is, void *ptr, size_t length, static bool input_cdio_eof(struct input_stream *is) { - struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; + CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to); } const struct input_plugin input_plugin_cdio_paranoia = { - .name = "cdio_paranoia", - .open = input_cdio_open, - .close = input_cdio_close, - .seek = input_cdio_seek, - .read = input_cdio_read, - .eof = input_cdio_eof + "cdio_paranoia", + nullptr, + nullptr, + input_cdio_open, + input_cdio_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_cdio_read, + input_cdio_eof, + input_cdio_seek, }; diff --git a/src/input/cdio_paranoia_input_plugin.h b/src/input/CdioParanoiaInputPlugin.hxx index 71c5cbe8..80d98b4b 100644 --- a/src/input/cdio_paranoia_input_plugin.h +++ b/src/input/CdioParanoiaInputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_CDIO_PARANOIA_INPUT_PLUGIN_H -#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_H +#ifndef MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX +#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX /** * An input plugin based on libcdio_paranoia library. diff --git a/src/input/curl_input_plugin.c b/src/input/CurlInputPlugin.cxx index 3f191141..fe944b75 100644 --- a/src/input/curl_input_plugin.c +++ b/src/input/CurlInputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,16 @@ */ #include "config.h" -#include "input/curl_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" +#include "CurlInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" #include "conf.h" #include "tag.h" -#include "icy_metadata.h" -#include "io_thread.h" -#include "glib_compat.h" +#include "IcyMetaDataParser.hxx" +#include "event/MultiSocketMonitor.hxx" +#include "event/Loop.hxx" +#include "IOThread.hxx" #include <assert.h> @@ -38,9 +40,16 @@ #include <string.h> #include <errno.h> +#include <list> +#include <forward_list> + #include <curl/curl.h> #include <glib.h> +#if LIBCURL_VERSION_NUM < 0x071200 +#error libcurl is too old +#endif + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "input_curl" @@ -59,7 +68,7 @@ static const size_t CURL_RESUME_AT = 384 * 1024; /** * Buffers created by input_curl_writefunction(). */ -struct buffer { +class CurlInputBuffer { /** size of the payload */ size_t size; @@ -67,7 +76,55 @@ struct buffer { size_t consumed; /** the payload */ - unsigned char data[sizeof(long)]; + 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 { @@ -75,40 +132,28 @@ struct input_curl { /* some buffers which were passed to libcurl, which we have too free */ - char *url, *range; + char *range; struct curl_slist *request_headers; /** the curl handles */ CURL *easy; - /** the GMainLoop source used to poll all CURL file - descriptors */ - GSource *source; - - /** the source id of #source */ - guint source_id; - - /** a linked list of all registered GPollFD objects */ - GSList *fds; - /** list of buffers, where input_curl_writefunction() appends to, and input_curl_read() reads from them */ - GQueue *buffers; + std::list<CurlInputBuffer> buffers; -#if LIBCURL_VERSION_NUM >= 0x071200 /** * Is the connection currently paused? That happens when the * buffer was getting too large. It will be unpaused when the * buffer is below the threshold again. */ bool paused; -#endif /** error message provided by libcurl */ char error[CURL_ERROR_SIZE]; /** parser for icy-metadata */ - struct icy_metadata icy_metadata; + IcyMetaDataParser icy; /** the stream name from the icy-name response header */ char *meta_name; @@ -118,6 +163,50 @@ struct input_curl { struct tag *tag; GError *postponed_error; + + input_curl(const char *url, Mutex &mutex, Cond &cond) + :base(input_plugin_curl, url, mutex, cond), + range(nullptr), request_headers(nullptr), + paused(false), + meta_name(nullptr), + tag(nullptr), + postponed_error(nullptr) { + } + + ~input_curl(); + + input_curl(const input_curl &) = delete; + input_curl &operator=(const input_curl &) = delete; +}; + +/** + * This class monitors all CURL file descriptors. + */ +class CurlSockets final : private MultiSocketMonitor { + /** + * Did CURL give us a timeout? If yes, then we need to call + * curl_multi_perform(), even if there was no event on any + * file descriptor. + */ + bool have_timeout; + + /** + * The absolute time stamp when the timeout expires. + */ + gint64 absolute_timeout; + +public: + CurlSockets(EventLoop &_loop) + :MultiSocketMonitor(_loop) {} + + using MultiSocketMonitor::InvalidateSockets; + +private: + void UpdateSockets(); + + virtual void PrepareSockets(gcc_unused gint *timeout_r) override; + virtual bool CheckSockets() const override; + virtual void DispatchSockets() override; }; /** libcurl should accept "ICY 200 OK" */ @@ -134,35 +223,9 @@ static struct { * A linked list of all active HTTP requests. An active * request is one that doesn't have the "eof" flag set. */ - GSList *requests; - - /** - * The GMainLoop source used to poll all CURL file - * descriptors. - */ - GSource *source; + std::forward_list<input_curl *> requests; - /** - * The source id of #source. - */ - guint source_id; - - GSList *fds; - -#if LIBCURL_VERSION_NUM >= 0x070f04 - /** - * Did CURL give us a timeout? If yes, then we need to call - * curl_multi_perform(), even if there was no event on any - * file descriptor. - */ - bool timeout; - - /** - * The absolute time stamp when the timeout expires. This is - * used in the GSource method check(). - */ - gint64 absolute_timeout; -#endif + CurlSockets *sockets; } curl; static inline GQuark @@ -181,23 +244,19 @@ input_curl_find_request(CURL *easy) { assert(io_thread_inside()); - for (GSList *i = curl.requests; i != NULL; i = g_slist_next(i)) { - struct input_curl *c = i->data; + for (auto c : curl.requests) if (c->easy == easy) return c; - } return NULL; } -#if LIBCURL_VERSION_NUM >= 0x071200 - static gpointer input_curl_resume(gpointer data) { assert(io_thread_inside()); - struct input_curl *c = data; + struct input_curl *c = (struct input_curl *)data; if (c->paused) { c->paused = false; @@ -207,13 +266,11 @@ input_curl_resume(gpointer data) return NULL; } -#endif - /** * Calculates the GLib event bit mask for one file descriptor, * obtained from three #fd_set objects filled by curl_multi_fdset(). */ -static gushort +static unsigned input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds) { gushort events = 0; @@ -242,8 +299,8 @@ input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds) * * Runs in the I/O thread. No lock needed. */ -static void -curl_update_fds(void) +void +CurlSockets::UpdateSockets() { assert(io_thread_inside()); @@ -262,42 +319,15 @@ curl_update_fds(void) return; } - GSList *fds = curl.fds; - curl.fds = NULL; - - while (fds != NULL) { - GPollFD *poll_fd = fds->data; - gushort events = input_curl_fd_events(poll_fd->fd, &rfds, - &wfds, &efds); - - assert(poll_fd->events != 0); - - fds = g_slist_remove(fds, poll_fd); - - if (events != poll_fd->events) - g_source_remove_poll(curl.source, poll_fd); - - if (events != 0) { - if (events != poll_fd->events) { - poll_fd->events = events; - g_source_add_poll(curl.source, poll_fd); - } - - curl.fds = g_slist_prepend(curl.fds, poll_fd); - } else { - g_free(poll_fd); - } - } + UpdateSocketList([&rfds, &wfds, &efds](int fd){ + return input_curl_fd_events(fd, &rfds, + &wfds, &efds); + }); for (int fd = 0; fd <= max_fd; ++fd) { - gushort events = input_curl_fd_events(fd, &rfds, &wfds, &efds); - if (events != 0) { - GPollFD *poll_fd = g_new(GPollFD, 1); - poll_fd->fd = fd; - poll_fd->events = events; - g_source_add_poll(curl.source, poll_fd); - curl.fds = g_slist_prepend(curl.fds, poll_fd); - } + unsigned events = input_curl_fd_events(fd, &rfds, &wfds, &efds); + if (events != 0) + AddSocket(fd, events); } } @@ -312,7 +342,7 @@ input_curl_easy_add(struct input_curl *c, GError **error_r) assert(c->easy != NULL); assert(input_curl_find_request(c->easy) == NULL); - curl.requests = g_slist_prepend(curl.requests, c); + curl.requests.push_front(c); CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy); if (mcode != CURLM_OK) { @@ -322,7 +352,7 @@ input_curl_easy_add(struct input_curl *c, GError **error_r) return false; } - curl_update_fds(); + curl.sockets->InvalidateSockets(); return true; } @@ -335,7 +365,8 @@ struct easy_add_params { static gpointer input_curl_easy_add_callback(gpointer data) { - const struct easy_add_params *params = data; + const struct easy_add_params *params = + (const struct easy_add_params *)data; bool success = input_curl_easy_add(params->c, params->error_r); return GUINT_TO_POINTER(success); @@ -352,8 +383,8 @@ input_curl_easy_add_indirect(struct input_curl *c, GError **error_r) assert(c->easy != NULL); struct easy_add_params params = { - .c = c, - .error_r = error_r, + c, + error_r, }; gpointer result = @@ -376,7 +407,7 @@ input_curl_easy_free(struct input_curl *c) if (c->easy == NULL) return; - curl.requests = g_slist_remove(curl.requests, c); + curl.requests.remove(c); curl_multi_remove_handle(curl.multi, c->easy); curl_easy_cleanup(c->easy); @@ -392,10 +423,10 @@ input_curl_easy_free(struct input_curl *c) static gpointer input_curl_easy_free_callback(gpointer data) { - struct input_curl *c = data; + struct input_curl *c = (struct input_curl *)data; input_curl_easy_free(c); - curl_update_fds(); + curl.sockets->InvalidateSockets(); return NULL; } @@ -424,17 +455,18 @@ input_curl_abort_all_requests(GError *error) assert(io_thread_inside()); assert(error != NULL); - while (curl.requests != NULL) { - struct input_curl *c = curl.requests->data; + while (!curl.requests.empty()) { + struct input_curl *c = curl.requests.front(); assert(c->postponed_error == NULL); input_curl_easy_free(c); - g_mutex_lock(c->base.mutex); + const ScopeLock protect(c->base.mutex); + c->postponed_error = g_error_copy(error); c->base.ready = true; - g_cond_broadcast(c->base.cond); - g_mutex_unlock(c->base.mutex); + + c->base.cond.broadcast(); } g_error_free(error); @@ -454,7 +486,7 @@ input_curl_request_done(struct input_curl *c, CURLcode result, long status) assert(c->easy == NULL); assert(c->postponed_error == NULL); - g_mutex_lock(c->base.mutex); + const ScopeLock protect(c->base.mutex); if (result != CURLE_OK) { c->postponed_error = g_error_new(curl_quark(), result, @@ -467,8 +499,8 @@ input_curl_request_done(struct input_curl *c, CURLcode result, long status) } c->base.ready = true; - g_cond_broadcast(c->base.cond); - g_mutex_unlock(c->base.mutex); + + c->base.cond.broadcast(); } static void @@ -532,28 +564,18 @@ input_curl_perform(void) return true; } -/* - * GSource methods - * - */ - -/** - * The GSource prepare() method implementation. - */ -static gboolean -input_curl_source_prepare(G_GNUC_UNUSED GSource *source, gint *timeout_r) +void +CurlSockets::PrepareSockets(gint *timeout_r) { - curl_update_fds(); + UpdateSockets(); -#if LIBCURL_VERSION_NUM >= 0x070f04 - curl.timeout = false; + have_timeout = false; long timeout2; CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2); if (mcode == CURLM_OK) { if (timeout2 >= 0) - curl.absolute_timeout = g_source_get_time(source) - + timeout2 * 1000; + absolute_timeout = GetTime() + timeout2 * 1000; if (timeout2 >= 0 && timeout2 < 10) /* CURL 7.21.1 likes to report "timeout=0", @@ -564,69 +586,28 @@ input_curl_source_prepare(G_GNUC_UNUSED GSource *source, gint *timeout_r) *timeout_r = timeout2; - curl.timeout = timeout2 >= 0; + have_timeout = timeout2 >= 0; } else g_warning("curl_multi_timeout() failed: %s\n", curl_multi_strerror(mcode)); -#else - (void)timeout_r; -#endif - - return false; } -/** - * The GSource check() method implementation. - */ -static gboolean -input_curl_source_check(G_GNUC_UNUSED GSource *source) +bool +CurlSockets::CheckSockets() const { -#if LIBCURL_VERSION_NUM >= 0x070f04 - if (curl.timeout) { - /* when a timeout has expired, we need to call - curl_multi_perform(), even if there was no file - descriptor event */ - - if (g_source_get_time(source) >= curl.absolute_timeout) - return true; - } -#endif - - for (GSList *i = curl.fds; i != NULL; i = i->next) { - GPollFD *poll_fd = i->data; - if (poll_fd->revents != 0) - return true; - } - - return false; + /* when a timeout has expired, we need to call + curl_multi_perform(), even if there was no file descriptor + event */ + return have_timeout && GetTime() >= absolute_timeout; } -/** - * The GSource dispatch() method implementation. The callback isn't - * used, because we're handling all events directly. - */ -static gboolean -input_curl_source_dispatch(G_GNUC_UNUSED GSource *source, - G_GNUC_UNUSED GSourceFunc callback, - G_GNUC_UNUSED gpointer user_data) +void +CurlSockets::DispatchSockets() { if (input_curl_perform()) input_curl_info_read(); - - return true; } -/** - * The vtable for our GSource implementation. Unfortunately, we - * cannot declare it "const", because g_source_new() takes a non-const - * pointer, for whatever reason. - */ -static GSourceFuncs curl_source_funcs = { - .prepare = input_curl_source_prepare, - .check = input_curl_source_check, - .dispatch = input_curl_source_dispatch, -}; - /* * input_plugin methods * @@ -668,8 +649,7 @@ input_curl_init(const struct config_param *param, return false; } - curl.source = g_source_new(&curl_source_funcs, sizeof(*curl.source)); - curl.source_id = g_source_attach(curl.source, io_thread_context()); + curl.sockets = new CurlSockets(io_thread_get()); return true; } @@ -677,7 +657,7 @@ input_curl_init(const struct config_param *param, static gpointer curl_destroy_sources(G_GNUC_UNUSED gpointer data) { - g_source_destroy(curl.source); + delete curl.sockets; return NULL; } @@ -685,7 +665,7 @@ curl_destroy_sources(G_GNUC_UNUSED gpointer data) static void input_curl_finish(void) { - assert(curl.requests == NULL); + assert(curl.requests.empty()); io_thread_call(curl_destroy_sources, NULL); @@ -696,8 +676,6 @@ input_curl_finish(void) curl_global_cleanup(); } -#if LIBCURL_VERSION_NUM >= 0x071200 - /** * Determine the total sizes of all buffers, including portions that * have already been consumed. @@ -710,55 +688,22 @@ curl_total_buffer_size(const struct input_curl *c) { size_t total = 0; - for (GList *i = g_queue_peek_head_link(c->buffers); - i != NULL; i = g_list_next(i)) { - struct buffer *buffer = i->data; - total += buffer->size; - } + for (const auto &i : c->buffers) + total += i.TotalSize(); return total; } -#endif - -static void -buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +input_curl::~input_curl() { - struct buffer *buffer = data; + if (tag != NULL) + tag_free(tag); + g_free(meta_name); - assert(buffer->consumed <= buffer->size); + input_curl_easy_free_indirect(this); - g_free(buffer); -} - -static void -input_curl_flush_buffers(struct input_curl *c) -{ - g_queue_foreach(c->buffers, buffer_free_callback, NULL); - g_queue_clear(c->buffers); -} - -/** - * Frees this stream, including the input_stream struct. - */ -static void -input_curl_free(struct input_curl *c) -{ - if (c->tag != NULL) - tag_free(c->tag); - g_free(c->meta_name); - - input_curl_easy_free_indirect(c); - input_curl_flush_buffers(c); - - g_queue_free(c->buffers); - - if (c->postponed_error != NULL) - g_error_free(c->postponed_error); - - g_free(c->url); - input_stream_deinit(&c->base); - g_free(c); + if (postponed_error != NULL) + g_error_free(postponed_error); } static bool @@ -788,8 +733,8 @@ input_curl_tag(struct input_stream *is) static bool fill_buffer(struct input_curl *c, GError **error_r) { - while (c->easy != NULL && g_queue_is_empty(c->buffers)) - g_cond_wait(c->base.cond, c->base.mutex); + while (c->easy != NULL && c->buffers.empty()) + c->base.cond.wait(c->base.mutex); if (c->postponed_error != NULL) { g_propagate_error(error_r, c->postponed_error); @@ -797,86 +742,63 @@ fill_buffer(struct input_curl *c, GError **error_r) return false; } - return !g_queue_is_empty(c->buffers); -} - -/** - * Mark a part of the buffer object as consumed. - */ -static struct buffer * -consume_buffer(struct buffer *buffer, size_t length) -{ - assert(buffer != NULL); - assert(buffer->consumed < buffer->size); - - buffer->consumed += length; - if (buffer->consumed < buffer->size) - return buffer; - - assert(buffer->consumed == buffer->size); - - g_free(buffer); - - return NULL; + return !c->buffers.empty(); } static size_t -read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers, +read_from_buffer(IcyMetaDataParser &icy, std::list<CurlInputBuffer> &buffers, void *dest0, size_t length) { - struct buffer *buffer = g_queue_pop_head(buffers); - uint8_t *dest = dest0; + auto &buffer = buffers.front(); + uint8_t *dest = (uint8_t *)dest0; size_t nbytes = 0; - assert(buffer->size > 0); - assert(buffer->consumed < buffer->size); - - if (length > buffer->size - buffer->consumed) - length = buffer->size - buffer->consumed; + if (length > buffer.Available()) + length = buffer.Available(); while (true) { size_t chunk; - chunk = icy_data(icy_metadata, length); + chunk = icy.Data(length); if (chunk > 0) { - memcpy(dest, buffer->data + buffer->consumed, - chunk); - buffer = consume_buffer(buffer, chunk); + const bool empty = !buffer.Read(dest, chunk); nbytes += chunk; dest += chunk; length -= chunk; - if (length == 0) + if (empty) { + buffers.pop_front(); break; + } - assert(buffer != NULL); + if (length == 0) + break; } - chunk = icy_meta(icy_metadata, buffer->data + buffer->consumed, - length); + chunk = icy.Meta(buffer.Begin(), length); if (chunk > 0) { - buffer = consume_buffer(buffer, chunk); + const bool empty = !buffer.Consume(chunk); length -= chunk; - if (length == 0) + if (empty) { + buffers.pop_front(); break; + } - assert(buffer != NULL); + if (length == 0) + break; } } - if (buffer != NULL) - g_queue_push_head(buffers, buffer); - return nbytes; } static void copy_icy_tag(struct input_curl *c) { - struct tag *tag = icy_tag(&c->icy_metadata); + struct tag *tag = c->icy.ReadTag(); if (tag == NULL) return; @@ -896,7 +818,7 @@ input_curl_available(struct input_stream *is) struct input_curl *c = (struct input_curl *)is; return c->postponed_error != NULL || c->easy == NULL || - !g_queue_is_empty(c->buffers); + !c->buffers.empty(); } static size_t @@ -906,7 +828,7 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size, struct input_curl *c = (struct input_curl *)is; bool success; size_t nbytes = 0; - char *dest = ptr; + char *dest = (char *)ptr; do { /* fill the buffer */ @@ -917,8 +839,8 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size, /* send buffer contents */ - while (size > 0 && !g_queue_is_empty(c->buffers)) { - size_t copy = read_from_buffer(&c->icy_metadata, c->buffers, + while (size > 0 && !c->buffers.empty()) { + size_t copy = read_from_buffer(c->icy, c->buffers, dest + nbytes, size); nbytes += copy; @@ -926,18 +848,16 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size, } } while (nbytes == 0); - if (icy_defined(&c->icy_metadata)) + if (c->icy.IsDefined()) copy_icy_tag(c); is->offset += (goffset)nbytes; -#if LIBCURL_VERSION_NUM >= 0x071200 if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) { - g_mutex_unlock(c->base.mutex); + c->base.mutex.unlock(); io_thread_call(input_curl_resume, c); - g_mutex_lock(c->base.mutex); + c->base.mutex.lock(); } -#endif return nbytes; } @@ -947,7 +867,7 @@ input_curl_close(struct input_stream *is) { struct input_curl *c = (struct input_curl *)is; - input_curl_free(c); + delete c; } static bool @@ -955,7 +875,7 @@ input_curl_eof(G_GNUC_UNUSED struct input_stream *is) { struct input_curl *c = (struct input_curl *)is; - return c->easy == NULL && g_queue_is_empty(c->buffers); + return c->easy == NULL && c->buffers.empty(); } /** called by curl when new data is available */ @@ -963,13 +883,14 @@ static size_t input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) { struct input_curl *c = (struct input_curl *)stream; - const char *header = ptr, *end, *value; char name[64]; size *= nmemb; - end = header + size; - value = memchr(header, ':', size); + const char *header = (const char *)ptr; + const char *end = header + size; + + const char *value = (const char *)memchr(header, ':', size); if (value == NULL || (size_t)(value - header) >= sizeof(name)) return size; @@ -990,7 +911,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) if (g_ascii_strcasecmp(name, "accept-ranges") == 0) { /* a stream with icy-metadata is not seekable */ - if (!icy_defined(&c->icy_metadata)) + if (!c->icy.IsDefined()) c->base.seekable = true; } else if (g_ascii_strcasecmp(name, "content-length") == 0) { char buffer[64]; @@ -1003,8 +924,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10); } else if (g_ascii_strcasecmp(name, "content-type") == 0) { - g_free(c->base.mime); - c->base.mime = g_strndup(value, end - value); + c->base.mime.assign(value, end); } else if (g_ascii_strcasecmp(name, "icy-name") == 0 || g_ascii_strcasecmp(name, "ice-name") == 0 || g_ascii_strcasecmp(name, "x-audiocast-name") == 0) { @@ -1021,7 +941,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) size_t icy_metaint; if ((size_t)(end - header) >= sizeof(buffer) || - icy_defined(&c->icy_metadata)) + c->icy.IsDefined()) return size; memcpy(buffer, value, end - value); @@ -1031,7 +951,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) g_debug("icy-metaint=%zu", icy_metaint); if (icy_metaint > 0) { - icy_start(&c->icy_metadata, icy_metaint); + c->icy.Start(icy_metaint); /* a stream with icy-metadata is not seekable */ @@ -1047,33 +967,22 @@ static size_t input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) { struct input_curl *c = (struct input_curl *)stream; - struct buffer *buffer; size *= nmemb; if (size == 0) return 0; - g_mutex_lock(c->base.mutex); + const ScopeLock protect(c->base.mutex); -#if LIBCURL_VERSION_NUM >= 0x071200 if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) { c->paused = true; - g_mutex_unlock(c->base.mutex); return CURL_WRITEFUNC_PAUSE; } -#endif - - buffer = g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size); - buffer->size = size; - buffer->consumed = 0; - memcpy(buffer->data, ptr, size); - g_queue_push_tail(c->buffers, buffer); + c->buffers.emplace_back(ptr, size); c->base.ready = true; - g_cond_broadcast(c->base.cond); - g_mutex_unlock(c->base.mutex); - + c->base.cond.broadcast(); return size; } @@ -1120,7 +1029,7 @@ input_curl_easy_init(struct input_curl *c, GError **error_r) g_free(proxy_auth_str); } - code = curl_easy_setopt(c->easy, CURLOPT_URL, c->url); + code = curl_easy_setopt(c->easy, CURLOPT_URL, c->base.uri.c_str()); if (code != CURLE_OK) { g_set_error(error_r, curl_quark(), code, "curl_easy_setopt() failed: %s", @@ -1179,19 +1088,15 @@ input_curl_seek(struct input_stream *is, goffset offset, int whence, /* check if we can fast-forward the buffer */ - while (offset > is->offset && !g_queue_is_empty(c->buffers)) { - struct buffer *buffer; - size_t length; - - buffer = (struct buffer *)g_queue_pop_head(c->buffers); - - length = buffer->size - buffer->consumed; + while (offset > is->offset && !c->buffers.empty()) { + auto &buffer = c->buffers.front(); + size_t length = buffer.Available(); if (offset - is->offset < (goffset)length) length = offset - is->offset; - buffer = consume_buffer(buffer, length); - if (buffer != NULL) - g_queue_push_head(c->buffers, buffer); + const bool empty = !buffer.Consume(length); + if (empty) + c->buffers.pop_front(); is->offset += length; } @@ -1201,10 +1106,10 @@ input_curl_seek(struct input_stream *is, goffset offset, int whence, /* close the old connection and open a new one */ - g_mutex_unlock(c->base.mutex); + c->base.mutex.unlock(); input_curl_easy_free_indirect(c); - input_curl_flush_buffers(c); + c->buffers.clear(); is->offset = offset; if (is->offset == is->size) { @@ -1230,10 +1135,10 @@ input_curl_seek(struct input_stream *is, goffset offset, int whence, if (!input_curl_easy_add_indirect(c, error_r)) return false; - g_mutex_lock(c->base.mutex); + c->base.mutex.lock(); while (!c->base.ready) - g_cond_wait(c->base.cond, c->base.mutex); + c->base.cond.wait(c->base.mutex); if (c->postponed_error != NULL) { g_propagate_error(error_r, c->postponed_error); @@ -1245,40 +1150,21 @@ input_curl_seek(struct input_stream *is, goffset offset, int whence, } static struct input_stream * -input_curl_open(const char *url, GMutex *mutex, GCond *cond, +input_curl_open(const char *url, Mutex &mutex, Cond &cond, GError **error_r) { - assert(mutex != NULL); - assert(cond != NULL); - - struct input_curl *c; - if (strncmp(url, "http://", 7) != 0) return NULL; - c = g_new0(struct input_curl, 1); - input_stream_init(&c->base, &input_plugin_curl, url, - mutex, cond); - - c->url = g_strdup(url); - c->buffers = g_queue_new(); - - icy_clear(&c->icy_metadata); - c->tag = NULL; - - c->postponed_error = NULL; - -#if LIBCURL_VERSION_NUM >= 0x071200 - c->paused = false; -#endif + struct input_curl *c = new input_curl(url, mutex, cond); if (!input_curl_easy_init(c, error_r)) { - input_curl_free(c); + delete c; return NULL; } if (!input_curl_easy_add_indirect(c, error_r)) { - input_curl_free(c); + delete c; return NULL; } @@ -1286,16 +1172,16 @@ input_curl_open(const char *url, GMutex *mutex, GCond *cond, } const struct input_plugin input_plugin_curl = { - .name = "curl", - .init = input_curl_init, - .finish = input_curl_finish, - - .open = input_curl_open, - .close = input_curl_close, - .check = input_curl_check, - .tag = input_curl_tag, - .available = input_curl_available, - .read = input_curl_read, - .eof = input_curl_eof, - .seek = input_curl_seek, + "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/curl_input_plugin.h b/src/input/CurlInputPlugin.hxx index c6e71bf4..20d1309d 100644 --- a/src/input/curl_input_plugin.h +++ b/src/input/CurlInputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INPUT_CURL_H -#define MPD_INPUT_CURL_H +#ifndef MPD_INPUT_CURL_HXX +#define MPD_INPUT_CURL_HXX struct input_stream; diff --git a/src/input/despotify_input_plugin.c b/src/input/DespotifyInputPlugin.cxx index 200a0afd..1e5a8c60 100644 --- a/src/input/despotify_input_plugin.c +++ b/src/input/DespotifyInputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Music Player Daemon Project + * 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 @@ -18,22 +18,26 @@ */ #include "config.h" -#include "input/despotify_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" +#include "DespotifyInputPlugin.hxx" +#include "DespotifyUtils.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" #include "tag.h" -#include "despotify_utils.h" + +extern "C" { +#include <despotify.h> +} #include <glib.h> #include <unistd.h> #include <string.h> #include <errno.h> -#include <despotify.h> #include <stdio.h> -struct input_despotify { +struct DespotifyInputStream { struct input_stream base; struct despotify_session *session; @@ -42,11 +46,33 @@ struct input_despotify { struct ds_pcm_data pcm; size_t len_available; bool eof; -}; + DespotifyInputStream(const char *uri, + Mutex &mutex, Cond &cond, + despotify_session *_session, + ds_track *_track) + :base(input_plugin_despotify, uri, mutex, cond), + session(_session), track(_track), + tag(mpd_despotify_tag_from_track(track)), + len_available(0), eof(false) { + + memset(&pcm, 0, sizeof(pcm)); + + /* Despotify outputs pcm data */ + base.mime = g_strdup("audio/x-mpd-cdda-pcm"); + base.ready = true; + } + + ~DespotifyInputStream() { + if (tag != NULL) + tag_free(tag); + + despotify_free_track(track); + } +}; static void -refill_buffer(struct input_despotify *ctx) +refill_buffer(DespotifyInputStream *ctx) { /* Wait until there is data */ while (1) { @@ -73,7 +99,7 @@ refill_buffer(struct input_despotify *ctx) static void callback(G_GNUC_UNUSED struct despotify_session* ds, int sig, G_GNUC_UNUSED void* data, void* callback_data) { - struct input_despotify *ctx = (struct input_despotify *)callback_data; + DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data; switch (sig) { case DESPOTIFY_NEW_TRACK: @@ -98,10 +124,9 @@ static void callback(G_GNUC_UNUSED struct despotify_session* ds, static struct input_stream * input_despotify_open(const char *url, - GMutex *mutex, GCond *cond, + Mutex &mutex, Cond &cond, G_GNUC_UNUSED GError **error_r) { - struct input_despotify *ctx; struct despotify_session *session; struct ds_link *ds_link; struct ds_track *track; @@ -123,35 +148,23 @@ input_despotify_open(const char *url, return NULL; } - ctx = g_new(struct input_despotify, 1); - memset(ctx, 0, sizeof(*ctx)); - track = despotify_link_get_track(session, ds_link); despotify_free_link(ds_link); - if (!track) { - g_free(ctx); + if (!track) return NULL; - } - input_stream_init(&ctx->base, &input_plugin_despotify, url, - mutex, cond); - ctx->session = session; - ctx->track = track; - ctx->tag = mpd_despotify_tag_from_track(track); - ctx->eof = false; - /* Despotify outputs pcm data */ - ctx->base.mime = g_strdup("audio/x-mpd-cdda-pcm"); - ctx->base.ready = true; + DespotifyInputStream *ctx = + new DespotifyInputStream(url, mutex, cond, + session, track); if (!mpd_despotify_register_callback(callback, ctx)) { - despotify_free_link(ds_link); - + delete ctx; return NULL; } if (despotify_play(ctx->session, ctx->track, false) == false) { - despotify_free_track(ctx->track); - g_free(ctx); + mpd_despotify_unregister_callback(callback); + delete ctx; return NULL; } @@ -162,7 +175,7 @@ static size_t input_despotify_read(struct input_stream *is, void *ptr, size_t size, G_GNUC_UNUSED GError **error_r) { - struct input_despotify *ctx = (struct input_despotify *)is; + DespotifyInputStream *ctx = (DespotifyInputStream *)is; size_t to_cpy = size; if (ctx->len_available == 0) @@ -181,21 +194,16 @@ input_despotify_read(struct input_stream *is, void *ptr, size_t size, static void input_despotify_close(struct input_stream *is) { - struct input_despotify *ctx = (struct input_despotify *)is; - - if (ctx->tag != NULL) - tag_free(ctx->tag); + DespotifyInputStream *ctx = (DespotifyInputStream *)is; mpd_despotify_unregister_callback(callback); - despotify_free_track(ctx->track); - input_stream_deinit(&ctx->base); - g_free(ctx); + delete ctx; } static bool input_despotify_eof(struct input_stream *is) { - struct input_despotify *ctx = (struct input_despotify *)is; + DespotifyInputStream *ctx = (DespotifyInputStream *)is; return ctx->eof; } @@ -211,7 +219,7 @@ input_despotify_seek(G_GNUC_UNUSED struct input_stream *is, static struct tag * input_despotify_tag(struct input_stream *is) { - struct input_despotify *ctx = (struct input_despotify *)is; + DespotifyInputStream *ctx = (DespotifyInputStream *)is; struct tag *tag = ctx->tag; ctx->tag = NULL; @@ -220,11 +228,16 @@ input_despotify_tag(struct input_stream *is) } const struct input_plugin input_plugin_despotify = { - .name = "spt", - .open = input_despotify_open, - .close = input_despotify_close, - .read = input_despotify_read, - .eof = input_despotify_eof, - .seek = input_despotify_seek, + "spt", + nullptr, + nullptr, + input_despotify_open, + input_despotify_close, + nullptr, + nullptr, .tag = input_despotify_tag, + nullptr, + input_despotify_read, + input_despotify_eof, + input_despotify_seek, }; diff --git a/src/input/despotify_input_plugin.h b/src/input/DespotifyInputPlugin.hxx index 4c070d88..00d69940 100644 --- a/src/input/despotify_input_plugin.h +++ b/src/input/DespotifyInputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Music Player Daemon Project + * 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 @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef INPUT_DESPOTIFY_H -#define INPUT_DESPOTIFY_H +#ifndef INPUT_DESPOTIFY_HXX +#define INPUT_DESPOTIFY_HXX extern const struct input_plugin input_plugin_despotify; diff --git a/src/input/ffmpeg_input_plugin.c b/src/input/FfmpegInputPlugin.cxx index 6d339a06..1660f177 100644 --- a/src/input/ffmpeg_input_plugin.c +++ b/src/input/FfmpegInputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,28 +17,49 @@ * 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 "input/ffmpeg_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" +#include "FfmpegInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +extern "C" { #include <libavutil/avutil.h> #include <libavformat/avio.h> #include <libavformat/avformat.h> +} #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "input_ffmpeg" -struct input_ffmpeg { +struct FfmpegInputStream { struct input_stream base; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) AVIOContext *h; -#else - URLContext *h; -#endif bool eof; + + FfmpegInputStream(const char *uri, Mutex &mutex, Cond &cond, + AVIOContext *_h) + :base(input_plugin_ffmpeg, uri, mutex, cond), + h(_h), eof(false) { + base.ready = true; + base.seekable = (h->seekable & AVIO_SEEKABLE_NORMAL) != 0; + base.size = avio_size(h); + + /* hack to make MPD select the "ffmpeg" decoder plugin + - since avio.h doesn't tell us the MIME type of the + resource, we can't select a decoder plugin, but the + "ffmpeg" plugin is quite good at auto-detection */ + base.mime = g_strdup("audio/x-mpd-ffmpeg"); + } + + ~FfmpegInputStream() { + avio_close(h); + } }; static inline GQuark @@ -50,12 +71,8 @@ ffmpeg_quark(void) static inline bool input_ffmpeg_supported(void) { -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - void *opaque = NULL; - return avio_enum_protocols(&opaque, 0) != NULL; -#else - return av_protocol_next(NULL) != NULL; -#endif + void *opaque = nullptr; + return avio_enum_protocols(&opaque, 0) != nullptr; } static bool @@ -76,54 +93,26 @@ input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param, static struct input_stream * input_ffmpeg_open(const char *uri, - GMutex *mutex, GCond *cond, + Mutex &mutex, Cond &cond, GError **error_r) { - struct input_ffmpeg *i; - if (!g_str_has_prefix(uri, "gopher://") && !g_str_has_prefix(uri, "rtp://") && !g_str_has_prefix(uri, "rtsp://") && !g_str_has_prefix(uri, "rtmp://") && !g_str_has_prefix(uri, "rtmpt://") && !g_str_has_prefix(uri, "rtmps://")) - return NULL; - - i = g_new(struct input_ffmpeg, 1); - input_stream_init(&i->base, &input_plugin_ffmpeg, uri, - mutex, cond); - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0) - int ret = avio_open(&i->h, uri, AVIO_FLAG_READ); -#elif LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - int ret = avio_open(&i->h, uri, AVIO_RDONLY); -#else - int ret = url_open(&i->h, uri, URL_RDONLY); -#endif + return nullptr; + + AVIOContext *h; + int ret = avio_open(&h, uri, AVIO_FLAG_READ); if (ret != 0) { - g_free(i); g_set_error(error_r, ffmpeg_quark(), ret, "libavformat failed to open the URI"); - return NULL; + return nullptr; } - i->eof = false; - - i->base.ready = true; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - i->base.seekable = (i->h->seekable & AVIO_SEEKABLE_NORMAL) != 0; - i->base.size = avio_size(i->h); -#else - i->base.seekable = !i->h->is_streamed; - i->base.size = url_filesize(i->h); -#endif - - /* hack to make MPD select the "ffmpeg" decoder plugin - since - avio.h doesn't tell us the MIME type of the resource, we - can't select a decoder plugin, but the "ffmpeg" plugin is - quite good at auto-detection */ - i->base.mime = g_strdup("audio/x-mpd-ffmpeg"); - + auto *i = new FfmpegInputStream(uri, mutex, cond, h); return &i->base; } @@ -131,13 +120,9 @@ static size_t input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size, GError **error_r) { - struct input_ffmpeg *i = (struct input_ffmpeg *)is; + FfmpegInputStream *i = (FfmpegInputStream *)is; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - int ret = avio_read(i->h, ptr, size); -#else - int ret = url_read(i->h, ptr, size); -#endif + int ret = avio_read(i->h, (unsigned char *)ptr, size); if (ret <= 0) { if (ret < 0) g_set_error(error_r, ffmpeg_quark(), 0, @@ -154,21 +139,15 @@ input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size, static void input_ffmpeg_close(struct input_stream *is) { - struct input_ffmpeg *i = (struct input_ffmpeg *)is; - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - avio_close(i->h); -#else - url_close(i->h); -#endif - input_stream_deinit(&i->base); - g_free(i); + FfmpegInputStream *i = (FfmpegInputStream *)is; + + delete i; } static bool input_ffmpeg_eof(struct input_stream *is) { - struct input_ffmpeg *i = (struct input_ffmpeg *)is; + FfmpegInputStream *i = (FfmpegInputStream *)is; return i->eof; } @@ -177,12 +156,8 @@ static bool input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence, G_GNUC_UNUSED GError **error_r) { - struct input_ffmpeg *i = (struct input_ffmpeg *)is; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) + FfmpegInputStream *i = (FfmpegInputStream *)is; int64_t ret = avio_seek(i->h, offset, whence); -#else - int64_t ret = url_seek(i->h, offset, whence); -#endif if (ret >= 0) { i->eof = false; @@ -194,11 +169,16 @@ input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence, } const struct input_plugin input_plugin_ffmpeg = { - .name = "ffmpeg", - .init = input_ffmpeg_init, - .open = input_ffmpeg_open, - .close = input_ffmpeg_close, - .read = input_ffmpeg_read, - .eof = input_ffmpeg_eof, - .seek = input_ffmpeg_seek, + "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/ffmpeg_input_plugin.h b/src/input/FfmpegInputPlugin.hxx index 393836ca..d5e3a8d9 100644 --- a/src/input/ffmpeg_input_plugin.h +++ b/src/input/FfmpegInputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_FFMPEG_INPUT_PLUGIN_H -#define MPD_FFMPEG_INPUT_PLUGIN_H +#ifndef MPD_FFMPEG_INPUT_PLUGIN_HXX +#define MPD_FFMPEG_INPUT_PLUGIN_HXX /** * An input plugin based on libavformat's "avio" library. diff --git a/src/input/file_input_plugin.c b/src/input/FileInputPlugin.cxx index 5ee3f200..2eecf32b 100644 --- a/src/input/file_input_plugin.c +++ b/src/input/FileInputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,13 @@ */ #include "config.h" /* must be first for large file support */ -#include "input/file_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" +#include "FileInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" #include "fd_util.h" #include "open.h" +#include "io_error.h" #include <sys/stat.h> #include <unistd.h> @@ -33,69 +35,67 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "input_file" -struct file_input_stream { +struct FileInputStream { struct input_stream base; int fd; -}; -static inline GQuark -file_quark(void) -{ - return g_quark_from_static_string("file"); -} + FileInputStream(const char *path, int _fd, off_t size, + Mutex &mutex, Cond &cond) + :base(input_plugin_file, path, mutex, cond), + fd(_fd) { + base.size = size; + base.seekable = true; + base.ready = true; + } + + ~FileInputStream() { + close(fd); + } +}; static struct input_stream * input_file_open(const char *filename, - GMutex *mutex, GCond *cond, + Mutex &mutex, Cond &cond, GError **error_r) { int fd, ret; struct stat st; - struct file_input_stream *fis; if (!g_path_is_absolute(filename)) - return NULL; + return nullptr; fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0); if (fd < 0) { if (errno != ENOENT && errno != ENOTDIR) - g_set_error(error_r, file_quark(), errno, + g_set_error(error_r, errno_quark(), errno, "Failed to open \"%s\": %s", filename, g_strerror(errno)); - return NULL; + return nullptr; } ret = fstat(fd, &st); if (ret < 0) { - g_set_error(error_r, file_quark(), errno, + g_set_error(error_r, errno_quark(), errno, "Failed to stat \"%s\": %s", filename, g_strerror(errno)); close(fd); - return NULL; + return nullptr; } if (!S_ISREG(st.st_mode)) { - g_set_error(error_r, file_quark(), 0, + g_set_error(error_r, errno_quark(), 0, "Not a regular file: %s", filename); close(fd); - return NULL; + return nullptr; } #ifdef POSIX_FADV_SEQUENTIAL posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL); #endif - fis = g_new(struct file_input_stream, 1); - input_stream_init(&fis->base, &input_plugin_file, filename, - mutex, cond); - - fis->base.size = st.st_size; - fis->base.seekable = true; - fis->base.ready = true; - - fis->fd = fd; - + FileInputStream *fis = new FileInputStream(filename, fd, st.st_size, + mutex, cond); return &fis->base; } @@ -103,11 +103,11 @@ static bool input_file_seek(struct input_stream *is, goffset offset, int whence, GError **error_r) { - struct file_input_stream *fis = (struct file_input_stream *)is; + FileInputStream *fis = (FileInputStream *)is; offset = (goffset)lseek(fis->fd, (off_t)offset, whence); if (offset < 0) { - g_set_error(error_r, file_quark(), errno, + g_set_error(error_r, errno_quark(), errno, "Failed to seek: %s", g_strerror(errno)); return false; } @@ -120,12 +120,12 @@ static size_t input_file_read(struct input_stream *is, void *ptr, size_t size, GError **error_r) { - struct file_input_stream *fis = (struct file_input_stream *)is; + FileInputStream *fis = (FileInputStream *)is; ssize_t nbytes; nbytes = read(fis->fd, ptr, size); if (nbytes < 0) { - g_set_error(error_r, file_quark(), errno, + g_set_error(error_r, errno_quark(), errno, "Failed to read: %s", g_strerror(errno)); return 0; } @@ -137,11 +137,9 @@ input_file_read(struct input_stream *is, void *ptr, size_t size, static void input_file_close(struct input_stream *is) { - struct file_input_stream *fis = (struct file_input_stream *)is; + FileInputStream *fis = (FileInputStream *)is; - close(fis->fd); - input_stream_deinit(&fis->base); - g_free(fis); + delete fis; } static bool @@ -151,10 +149,16 @@ input_file_eof(struct input_stream *is) } const struct input_plugin input_plugin_file = { - .name = "file", - .open = input_file_open, - .close = input_file_close, - .read = input_file_read, - .eof = input_file_eof, - .seek = input_file_seek, + "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/file_input_plugin.h b/src/input/FileInputPlugin.hxx index f24769d5..aacfd0b5 100644 --- a/src/input/file_input_plugin.h +++ b/src/input/FileInputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INPUT_FILE_H -#define MPD_INPUT_FILE_H +#ifndef MPD_INPUT_FILE_HXX +#define MPD_INPUT_FILE_HXX extern const struct input_plugin input_plugin_file; diff --git a/src/input/mms_input_plugin.c b/src/input/MmsInputPlugin.cxx index cff15125..b347eb92 100644 --- a/src/input/mms_input_plugin.c +++ b/src/input/MmsInputPlugin.cxx @@ -18,9 +18,10 @@ */ #include "config.h" -#include "input/mms_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" +#include "MmsInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" #include <glib.h> #include <libmms/mmsx.h> @@ -31,12 +32,28 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "input_mms" -struct input_mms { +struct MmsInputStream { struct input_stream base; mmsx_t *mms; bool eof; + + MmsInputStream(const char *uri, + Mutex &mutex, Cond &cond, + mmsx_t *_mms) + :base(input_plugin_mms, uri, mutex, cond), + mms(_mms), eof(false) { + /* XX is this correct? at least this selects the ffmpeg + decoder, which seems to work fine*/ + base.mime = g_strdup("audio/x-ms-wma"); + + base.ready = true; + } + + ~MmsInputStream() { + mmsx_close(mms); + } }; static inline GQuark @@ -47,36 +64,22 @@ mms_quark(void) static struct input_stream * input_mms_open(const char *url, - GMutex *mutex, GCond *cond, + Mutex &mutex, Cond &cond, GError **error_r) { - struct input_mms *m; - if (!g_str_has_prefix(url, "mms://") && !g_str_has_prefix(url, "mmsh://") && !g_str_has_prefix(url, "mmst://") && !g_str_has_prefix(url, "mmsu://")) - return NULL; - - m = g_new(struct input_mms, 1); - input_stream_init(&m->base, &input_plugin_mms, url, - mutex, cond); + return nullptr; - m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024); - if (m->mms == NULL) { - g_free(m); + const auto mms = mmsx_connect(nullptr, nullptr, url, 128 * 1024); + if (mms == nullptr) { g_set_error(error_r, mms_quark(), 0, "mmsx_connect() failed"); - return NULL; + return nullptr; } - m->eof = false; - - /* XX is this correct? at least this selects the ffmpeg - decoder, which seems to work fine*/ - m->base.mime = g_strdup("audio/x-ms-wma"); - - m->base.ready = true; - + auto m = new MmsInputStream(url, mutex, cond, mms); return &m->base; } @@ -84,10 +87,10 @@ static size_t input_mms_read(struct input_stream *is, void *ptr, size_t size, GError **error_r) { - struct input_mms *m = (struct input_mms *)is; + MmsInputStream *m = (MmsInputStream *)is; int ret; - ret = mmsx_read(NULL, m->mms, ptr, size); + ret = mmsx_read(nullptr, m->mms, (char *)ptr, size); if (ret <= 0) { if (ret < 0) { g_set_error(error_r, mms_quark(), errno, @@ -107,17 +110,15 @@ input_mms_read(struct input_stream *is, void *ptr, size_t size, static void input_mms_close(struct input_stream *is) { - struct input_mms *m = (struct input_mms *)is; + MmsInputStream *m = (MmsInputStream *)is; - mmsx_close(m->mms); - input_stream_deinit(&m->base); - g_free(m); + delete m; } static bool input_mms_eof(struct input_stream *is) { - struct input_mms *m = (struct input_mms *)is; + MmsInputStream *m = (MmsInputStream *)is; return m->eof; } @@ -131,10 +132,16 @@ input_mms_seek(G_GNUC_UNUSED struct input_stream *is, } const struct input_plugin input_plugin_mms = { - .name = "mms", - .open = input_mms_open, - .close = input_mms_close, - .read = input_mms_read, - .eof = input_mms_eof, - .seek = input_mms_seek, + "mms", + nullptr, + nullptr, + input_mms_open, + input_mms_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_mms_read, + input_mms_eof, + input_mms_seek, }; diff --git a/src/input/mms_input_plugin.h b/src/input/MmsInputPlugin.hxx index d6aa593f..d6aa593f 100644 --- a/src/input/mms_input_plugin.h +++ b/src/input/MmsInputPlugin.hxx diff --git a/src/input/rewind_input_plugin.c b/src/input/RewindInputPlugin.cxx index cf06fc57..d93d7d1c 100644 --- a/src/input/rewind_input_plugin.c +++ b/src/input/RewindInputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,10 @@ */ #include "config.h" -#include "input/rewind_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" +#include "RewindInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" #include "tag.h" #include <glib.h> @@ -31,14 +32,16 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "input_rewind" -struct input_rewind { +extern const struct input_plugin rewind_input_plugin; + +struct RewindInputStream { struct input_stream base; struct input_stream *input; /** * The read position within the buffer. Undefined as long as - * reading_from_buffer() returns false. + * ReadingFromBuffer() returns false. */ size_t head; @@ -56,61 +59,61 @@ struct input_rewind { * stream (offset 0). */ char buffer[64 * 1024]; -}; -/** - * Are we currently reading from the buffer, and does the buffer - * contain more data for the next read operation? - */ -static bool -reading_from_buffer(const struct input_rewind *r) -{ - return r->tail > 0 && r->base.offset < r->input->offset; -} + RewindInputStream(input_stream *_input) + :base(rewind_input_plugin, _input->uri.c_str(), + _input->mutex, _input->cond), + input(_input), tail(0) { + } -/** - * Copy public attributes from the underlying input stream to the - * "rewind" input stream. This function is called when a method of - * the underlying stream has returned, which may have modified these - * attributes. - */ -static void -copy_attributes(struct input_rewind *r) -{ - struct input_stream *dest = &r->base; - const struct input_stream *src = r->input; + ~RewindInputStream() { + input_stream_close(input); + } - assert(dest != src); - assert(src->mime == NULL || dest->mime != src->mime); + /** + * Are we currently reading from the buffer, and does the + * buffer contain more data for the next read operation? + */ + bool ReadingFromBuffer() const { + return tail > 0 && base.offset < input->offset; + } + + /** + * Copy public attributes from the underlying input stream to the + * "rewind" input stream. This function is called when a method of + * the underlying stream has returned, which may have modified these + * attributes. + */ + void CopyAttributes() { + struct input_stream *dest = &base; + const struct input_stream *src = input; + + assert(dest != src); - bool dest_ready = dest->ready; + bool dest_ready = dest->ready; - dest->ready = src->ready; - dest->seekable = src->seekable; - dest->size = src->size; - dest->offset = src->offset; + dest->ready = src->ready; + dest->seekable = src->seekable; + dest->size = src->size; + dest->offset = src->offset; - if (!dest_ready && src->ready) { - g_free(dest->mime); - dest->mime = g_strdup(src->mime); + if (!dest_ready && src->ready) + dest->mime = src->mime; } -} +}; static void input_rewind_close(struct input_stream *is) { - struct input_rewind *r = (struct input_rewind *)is; - - input_stream_close(r->input); + RewindInputStream *r = (RewindInputStream *)is; - input_stream_deinit(&r->base); - g_free(r); + delete r; } static bool input_rewind_check(struct input_stream *is, GError **error_r) { - struct input_rewind *r = (struct input_rewind *)is; + RewindInputStream *r = (RewindInputStream *)is; return input_stream_check(r->input, error_r); } @@ -118,16 +121,16 @@ input_rewind_check(struct input_stream *is, GError **error_r) static void input_rewind_update(struct input_stream *is) { - struct input_rewind *r = (struct input_rewind *)is; + RewindInputStream *r = (RewindInputStream *)is; - if (!reading_from_buffer(r)) - copy_attributes(r); + if (!r->ReadingFromBuffer()) + r->CopyAttributes(); } static struct tag * input_rewind_tag(struct input_stream *is) { - struct input_rewind *r = (struct input_rewind *)is; + RewindInputStream *r = (RewindInputStream *)is; return input_stream_tag(r->input); } @@ -135,7 +138,7 @@ input_rewind_tag(struct input_stream *is) static bool input_rewind_available(struct input_stream *is) { - struct input_rewind *r = (struct input_rewind *)is; + RewindInputStream *r = (RewindInputStream *)is; return input_stream_available(r->input); } @@ -144,9 +147,9 @@ static size_t input_rewind_read(struct input_stream *is, void *ptr, size_t size, GError **error_r) { - struct input_rewind *r = (struct input_rewind *)is; + RewindInputStream *r = (RewindInputStream *)is; - if (reading_from_buffer(r)) { + if (r->ReadingFromBuffer()) { /* buffered read */ assert(r->head == (size_t)is->offset); @@ -177,7 +180,7 @@ input_rewind_read(struct input_stream *is, void *ptr, size_t size, assert(r->tail == (size_t)r->input->offset); } - copy_attributes(r); + r->CopyAttributes(); return nbytes; } @@ -186,23 +189,23 @@ input_rewind_read(struct input_stream *is, void *ptr, size_t size, static bool input_rewind_eof(struct input_stream *is) { - struct input_rewind *r = (struct input_rewind *)is; + RewindInputStream *r = (RewindInputStream *)is; - return !reading_from_buffer(r) && input_stream_eof(r->input); + return !r->ReadingFromBuffer() && input_stream_eof(r->input); } static bool input_rewind_seek(struct input_stream *is, goffset offset, int whence, GError **error_r) { - struct input_rewind *r = (struct input_rewind *)is; + RewindInputStream *r = (RewindInputStream *)is; assert(is->ready); if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) { /* buffered seek */ - assert(!reading_from_buffer(r) || + assert(!r->ReadingFromBuffer() || r->head == (size_t)is->offset); assert(r->tail == (size_t)r->input->offset); @@ -213,7 +216,7 @@ input_rewind_seek(struct input_stream *is, goffset offset, int whence, } else { bool success = input_stream_seek(r->input, offset, whence, error_r); - copy_attributes(r); + r->CopyAttributes(); /* disable the buffer, because r->input has left the buffered range now */ @@ -223,22 +226,24 @@ input_rewind_seek(struct input_stream *is, goffset offset, int whence, } } -static const struct input_plugin rewind_input_plugin = { - .close = input_rewind_close, - .check = input_rewind_check, - .update = input_rewind_update, - .tag = input_rewind_tag, - .available = input_rewind_available, - .read = input_rewind_read, - .eof = input_rewind_eof, - .seek = input_rewind_seek, +const struct input_plugin rewind_input_plugin = { + nullptr, + nullptr, + nullptr, + nullptr, + input_rewind_close, + input_rewind_check, + input_rewind_update, + input_rewind_tag, + input_rewind_available, + input_rewind_read, + input_rewind_eof, + input_rewind_seek, }; struct input_stream * input_rewind_open(struct input_stream *is) { - struct input_rewind *c; - assert(is != NULL); assert(is->offset == 0); @@ -246,11 +251,6 @@ input_rewind_open(struct input_stream *is) /* seekable resources don't need this plugin */ return is; - c = g_new(struct input_rewind, 1); - input_stream_init(&c->base, &rewind_input_plugin, is->uri, - is->mutex, is->cond); - c->tail = 0; - c->input = is; - + RewindInputStream *c = new RewindInputStream(is); return &c->base; } diff --git a/src/input/rewind_input_plugin.h b/src/input/RewindInputPlugin.hxx index 83abe257..cf21e92f 100644 --- a/src/input/rewind_input_plugin.h +++ b/src/input/RewindInputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,8 +24,8 @@ * each decoder plugin peek a portion from the stream). */ -#ifndef MPD_INPUT_REWIND_H -#define MPD_INPUT_REWIND_H +#ifndef MPD_INPUT_REWIND_HXX +#define MPD_INPUT_REWIND_HXX #include "check.h" diff --git a/src/input/soup_input_plugin.c b/src/input/SoupInputPlugin.cxx index fc903b48..e9767c20 100644 --- a/src/input/soup_input_plugin.c +++ b/src/input/SoupInputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,18 @@ */ #include "config.h" -#include "input/soup_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "io_thread.h" +#include "SoupInputPlugin.hxx" +#include "InputPlugin.hxx" +#include "InputStream.hxx" +#include "InputInternal.hxx" +#include "IOThread.hxx" +#include "event/Loop.hxx" #include "conf.h" +extern "C" { #include <libsoup/soup-uri.h> #include <libsoup/soup-session-async.h> +} #include <assert.h> #include <string.h> @@ -48,7 +52,7 @@ static const size_t SOUP_RESUME_AT = 384 * 1024; static SoupURI *soup_proxy; static SoupSession *soup_session; -struct input_soup { +struct SoupInputStream { struct input_stream base; SoupMessage *msg; @@ -68,6 +72,15 @@ struct input_soup { bool completed; GError *postponed_error; + + SoupInputStream(const char *uri, Mutex &mutex, Cond &cond); + ~SoupInputStream(); + + bool CopyError(const SoupMessage *msg); + + bool WaitData(); + + size_t Read(void *ptr, size_t size, GError **error_r); }; static inline GQuark @@ -99,7 +112,7 @@ input_soup_init(const struct config_param *param, GError **error_r) soup_session_async_new_with_options(SOUP_SESSION_PROXY_URI, soup_proxy, SOUP_SESSION_ASYNC_CONTEXT, - io_thread_context(), + io_thread_get().GetContext(), NULL); return true; @@ -123,31 +136,31 @@ input_soup_finish(void) * * @return true if there was no error */ -static bool -input_soup_copy_error(struct input_soup *s, const SoupMessage *msg) +bool +SoupInputStream::CopyError(const SoupMessage *src) { - if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) + if (SOUP_STATUS_IS_SUCCESSFUL(src->status_code)) return true; - if (msg->status_code == SOUP_STATUS_CANCELLED) + if (src->status_code == SOUP_STATUS_CANCELLED) /* failure, but don't generate a GError, because this status was caused by _close() */ return false; - if (s->postponed_error != NULL) + if (postponed_error != nullptr) /* there's already a GError, don't overwrite it */ return false; - if (SOUP_STATUS_IS_TRANSPORT_ERROR(msg->status_code)) - s->postponed_error = - g_error_new(soup_quark(), msg->status_code, + if (SOUP_STATUS_IS_TRANSPORT_ERROR(src->status_code)) + postponed_error = + g_error_new(soup_quark(), src->status_code, "HTTP client error: %s", - msg->reason_phrase); + src->reason_phrase); else - s->postponed_error = - g_error_new(soup_quark(), msg->status_code, + postponed_error = + g_error_new(soup_quark(), src->status_code, "got HTTP status: %d %s", - msg->status_code, msg->reason_phrase); + src->status_code, src->reason_phrase); return false; } @@ -156,33 +169,32 @@ static void input_soup_session_callback(G_GNUC_UNUSED SoupSession *session, SoupMessage *msg, gpointer user_data) { - struct input_soup *s = user_data; + SoupInputStream *s = (SoupInputStream *)user_data; assert(msg == s->msg); assert(!s->completed); - g_mutex_lock(s->base.mutex); + const ScopeLock protect(s->base.mutex); if (!s->base.ready) - input_soup_copy_error(s, msg); + s->CopyError(msg); s->base.ready = true; s->alive = false; s->completed = true; - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); + s->base.cond.broadcast(); } static void input_soup_got_headers(SoupMessage *msg, gpointer user_data) { - struct input_soup *s = user_data; + SoupInputStream *s = (SoupInputStream *)user_data; - g_mutex_lock(s->base.mutex); + s->base.mutex.lock(); - if (!input_soup_copy_error(s, msg)) { - g_mutex_unlock(s->base.mutex); + if (!s->CopyError(msg)) { + s->base.mutex.unlock(); soup_session_cancel_message(soup_session, msg, SOUP_STATUS_CANCELLED); @@ -190,8 +202,8 @@ input_soup_got_headers(SoupMessage *msg, gpointer user_data) } s->base.ready = true; - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); + s->base.cond.broadcast(); + s->base.mutex.unlock(); soup_message_body_set_accumulate(msg->response_body, false); } @@ -199,11 +211,11 @@ input_soup_got_headers(SoupMessage *msg, gpointer user_data) static void input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) { - struct input_soup *s = user_data; + SoupInputStream *s = (SoupInputStream *)user_data; assert(msg == s->msg); - g_mutex_lock(s->base.mutex); + const ScopeLock protect(s->base.mutex); g_queue_push_tail(s->buffers, soup_buffer_copy(chunk)); s->total_buffered += chunk->length; @@ -213,50 +225,50 @@ input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) soup_session_pause_message(soup_session, msg); } - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); + s->base.cond.broadcast(); + s->base.mutex.unlock(); } static void input_soup_got_body(G_GNUC_UNUSED SoupMessage *msg, gpointer user_data) { - struct input_soup *s = user_data; + SoupInputStream *s = (SoupInputStream *)user_data; assert(msg == s->msg); - g_mutex_lock(s->base.mutex); + const ScopeLock protect(s->base.mutex); s->base.ready = true; s->eof = true; s->alive = false; - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); + s->base.cond.broadcast(); + s->base.mutex.unlock(); } -static bool -input_soup_wait_data(struct input_soup *s) +inline bool +SoupInputStream::WaitData() { while (true) { - if (s->eof) + if (eof) return true; - if (!s->alive) + if (!alive) return false; - if (!g_queue_is_empty(s->buffers)) + if (!g_queue_is_empty(buffers)) return true; - assert(s->current_consumed == 0); + assert(current_consumed == 0); - g_cond_wait(s->base.cond, s->base.mutex); + base.cond.wait(base.mutex); } } static gpointer input_soup_queue(gpointer data) { - struct input_soup *s = data; + SoupInputStream *s = (SoupInputStream *)data; soup_session_queue_message(soup_session, s->msg, input_soup_session_callback, s); @@ -264,22 +276,14 @@ input_soup_queue(gpointer data) return NULL; } -static struct input_stream * -input_soup_open(const char *uri, - GMutex *mutex, GCond *cond, - G_GNUC_UNUSED GError **error_r) +SoupInputStream::SoupInputStream(const char *uri, + Mutex &mutex, Cond &cond) + :base(input_plugin_soup, uri, mutex, cond), + buffers(g_queue_new()), + current_consumed(0), total_buffered(0), + alive(false), pause(false), eof(false), completed(false), + postponed_error(nullptr) { - if (strncmp(uri, "http://", 7) != 0) - return NULL; - - struct input_soup *s = g_new(struct input_soup, 1); - input_stream_init(&s->base, &input_plugin_soup, uri, - mutex, cond); - - s->buffers = g_queue_new(); - s->current_consumed = 0; - s->total_buffered = 0; - #if GCC_CHECK_VERSION(4,6) #pragma GCC diagnostic push /* the libsoup macro SOUP_METHOD_GET discards the "const" @@ -288,39 +292,43 @@ input_soup_open(const char *uri, #pragma GCC diagnostic ignored "-Wcast-qual" #endif - s->msg = soup_message_new(SOUP_METHOD_GET, uri); + msg = soup_message_new(SOUP_METHOD_GET, uri); #if GCC_CHECK_VERSION(4,6) #pragma GCC diagnostic pop #endif - soup_message_set_flags(s->msg, SOUP_MESSAGE_NO_REDIRECT); + soup_message_set_flags(msg, SOUP_MESSAGE_NO_REDIRECT); - soup_message_headers_append(s->msg->request_headers, "User-Agent", + soup_message_headers_append(msg->request_headers, "User-Agent", "Music Player Daemon " VERSION); - g_signal_connect(s->msg, "got-headers", - G_CALLBACK(input_soup_got_headers), s); - g_signal_connect(s->msg, "got-chunk", - G_CALLBACK(input_soup_got_chunk), s); - g_signal_connect(s->msg, "got-body", - G_CALLBACK(input_soup_got_body), s); + g_signal_connect(msg, "got-headers", + G_CALLBACK(input_soup_got_headers), this); + g_signal_connect(msg, "got-chunk", + G_CALLBACK(input_soup_got_chunk), this); + g_signal_connect(msg, "got-body", + G_CALLBACK(input_soup_got_body), this); - s->alive = true; - s->pause = false; - s->eof = false; - s->completed = false; - s->postponed_error = NULL; + io_thread_call(input_soup_queue, this); +} - io_thread_call(input_soup_queue, s); +static struct input_stream * +input_soup_open(const char *uri, + Mutex &mutex, Cond &cond, + G_GNUC_UNUSED GError **error_r) +{ + if (strncmp(uri, "http://", 7) != 0) + return NULL; + SoupInputStream *s = new SoupInputStream(uri, mutex, cond); return &s->base; } static gpointer input_soup_cancel(gpointer data) { - struct input_soup *s = data; + SoupInputStream *s = (SoupInputStream *)data; if (!s->completed) soup_session_cancel_message(soup_session, s->msg, @@ -329,44 +337,46 @@ input_soup_cancel(gpointer data) return NULL; } -static void -input_soup_close(struct input_stream *is) +SoupInputStream::~SoupInputStream() { - struct input_soup *s = (struct input_soup *)is; - - g_mutex_lock(s->base.mutex); + base.mutex.lock(); - if (!s->completed) { + if (!completed) { /* the messages's session callback hasn't been invoked yet; cancel it and wait for completion */ - g_mutex_unlock(s->base.mutex); + base.mutex.unlock(); - io_thread_call(input_soup_cancel, s); + io_thread_call(input_soup_cancel, this); - g_mutex_lock(s->base.mutex); - while (!s->completed) - g_cond_wait(s->base.cond, s->base.mutex); + base.mutex.lock(); + while (!completed) + base.cond.wait(base.mutex); } - g_mutex_unlock(s->base.mutex); + base.mutex.unlock(); SoupBuffer *buffer; - while ((buffer = g_queue_pop_head(s->buffers)) != NULL) + while ((buffer = (SoupBuffer *)g_queue_pop_head(buffers)) != NULL) soup_buffer_free(buffer); - g_queue_free(s->buffers); + g_queue_free(buffers); - if (s->postponed_error != NULL) - g_error_free(s->postponed_error); + if (postponed_error != NULL) + g_error_free(postponed_error); +} + +static void +input_soup_close(struct input_stream *is) +{ + SoupInputStream *s = (SoupInputStream *)is; - input_stream_deinit(&s->base); - g_free(s); + delete s; } static bool input_soup_check(struct input_stream *is, GError **error_r) { - struct input_soup *s = (struct input_soup *)is; + SoupInputStream *s = (SoupInputStream *)is; bool success = s->postponed_error == NULL; if (!success) { @@ -380,45 +390,43 @@ input_soup_check(struct input_stream *is, GError **error_r) static bool input_soup_available(struct input_stream *is) { - struct input_soup *s = (struct input_soup *)is; + SoupInputStream *s = (SoupInputStream *)is; return s->eof || !s->alive || !g_queue_is_empty(s->buffers); } -static size_t -input_soup_read(struct input_stream *is, void *ptr, size_t size, - G_GNUC_UNUSED GError **error_r) +inline size_t +SoupInputStream::Read(void *ptr, size_t size, GError **error_r) { - struct input_soup *s = (struct input_soup *)is; + if (!WaitData()) { + assert(!alive); - if (!input_soup_wait_data(s)) { - assert(!s->alive); - - if (s->postponed_error != NULL) { - g_propagate_error(error_r, s->postponed_error); - s->postponed_error = NULL; + if (postponed_error != nullptr) { + g_propagate_error(error_r, postponed_error); + postponed_error = nullptr; } else g_set_error_literal(error_r, soup_quark(), 0, "HTTP failure"); return 0; } - char *p0 = ptr, *p = p0, *p_end = p0 + size; + char *p0 = (char *)ptr, *p = p0, *p_end = p0 + size; while (p < p_end) { - SoupBuffer *buffer = g_queue_pop_head(s->buffers); + SoupBuffer *buffer = (SoupBuffer *) + g_queue_pop_head(buffers); if (buffer == NULL) { - assert(s->current_consumed == 0); + assert(current_consumed == 0); break; } - assert(s->current_consumed < buffer->length); - assert(s->total_buffered >= buffer->length); + assert(current_consumed < buffer->length); + assert(total_buffered >= buffer->length); const char *q = buffer->data; - q += s->current_consumed; + q += current_consumed; - size_t remaining = buffer->length - s->current_consumed; + size_t remaining = buffer->length - current_consumed; size_t nbytes = p_end - p; if (nbytes > remaining) nbytes = remaining; @@ -426,48 +434,59 @@ input_soup_read(struct input_stream *is, void *ptr, size_t size, memcpy(p, q, nbytes); p += nbytes; - s->current_consumed += remaining; - if (s->current_consumed >= buffer->length) { + current_consumed += remaining; + if (current_consumed >= buffer->length) { /* done with this buffer */ - s->total_buffered -= buffer->length; + total_buffered -= buffer->length; soup_buffer_free(buffer); - s->current_consumed = 0; + current_consumed = 0; } else { /* partial read */ assert(p == p_end); - g_queue_push_head(s->buffers, buffer); + g_queue_push_head(buffers, buffer); } } - if (s->pause && s->total_buffered < SOUP_RESUME_AT) { - s->pause = false; - soup_session_unpause_message(soup_session, s->msg); + if (pause && total_buffered < SOUP_RESUME_AT) { + pause = false; + soup_session_unpause_message(soup_session, msg); } size_t nbytes = p - p0; - s->base.offset += nbytes; + base.offset += nbytes; return nbytes; } +static size_t +input_soup_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + SoupInputStream *s = (SoupInputStream *)is; + + return s->Read(ptr, size, error_r); +} + static bool input_soup_eof(G_GNUC_UNUSED struct input_stream *is) { - struct input_soup *s = (struct input_soup *)is; + SoupInputStream *s = (SoupInputStream *)is; return !s->alive && g_queue_is_empty(s->buffers); } const struct input_plugin input_plugin_soup = { - .name = "soup", - .init = input_soup_init, - .finish = input_soup_finish, - - .open = input_soup_open, - .close = input_soup_close, - .check = input_soup_check, - .available = input_soup_available, - .read = input_soup_read, - .eof = input_soup_eof, + "soup", + input_soup_init, + input_soup_finish, + input_soup_open, + input_soup_close, + input_soup_check, + nullptr, + nullptr, + input_soup_available, + input_soup_read, + input_soup_eof, + nullptr, }; diff --git a/src/input/soup_input_plugin.h b/src/input/SoupInputPlugin.hxx index 689b2d97..4c089b39 100644 --- a/src/input/soup_input_plugin.h +++ b/src/input/SoupInputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INPUT_SOUP_H -#define MPD_INPUT_SOUP_H +#ifndef MPD_INPUT_SOUP_HXX +#define MPD_INPUT_SOUP_HXX extern const struct input_plugin input_plugin_soup; diff --git a/src/input_internal.c b/src/input_internal.c deleted file mode 100644 index 92a71856..00000000 --- a/src/input_internal.c +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input_internal.h" -#include "input_stream.h" - -#include <assert.h> - -void -input_stream_init(struct input_stream *is, const struct input_plugin *plugin, - const char *uri, GMutex *mutex, GCond *cond) -{ - assert(is != NULL); - assert(plugin != NULL); - assert(uri != NULL); - - is->plugin = plugin; - is->uri = g_strdup(uri); - is->mutex = mutex; - is->cond = cond; - is->ready = false; - is->seekable = false; - is->size = -1; - is->offset = 0; - is->mime = NULL; -} - -void -input_stream_deinit(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - g_free(is->uri); - g_free(is->mime); -} - -void -input_stream_signal_client(struct input_stream *is) -{ - if (is->cond != NULL) - g_cond_broadcast(is->cond); -} - -void -input_stream_set_ready(struct input_stream *is) -{ - g_mutex_lock(is->mutex); - - if (!is->ready) { - is->ready = true; - input_stream_signal_client(is); - } - - g_mutex_unlock(is->mutex); -} diff --git a/src/input_stream.h b/src/input_stream.h index 10ad9716..24dda1ee 100644 --- a/src/input_stream.h +++ b/src/input_stream.h @@ -29,64 +29,13 @@ #include <stdbool.h> #include <sys/types.h> -struct input_stream { - /** - * the plugin which implements this input stream - */ - const struct input_plugin *plugin; +struct input_stream; - /** - * The absolute URI which was used to open this stream. May - * be NULL if this is unknown. - */ - char *uri; +#ifdef __cplusplus +extern "C" { - /** - * A mutex that protects the mutable attributes of this object - * and its implementation. It must be locked before calling - * any of the public methods. - * - * This object is allocated by the client, and the client is - * responsible for freeing it. - */ - GMutex *mutex; - - /** - * A cond that gets signalled when the state of this object - * changes from the I/O thread. The client of this object may - * wait on it. Optional, may be NULL. - * - * This object is allocated by the client, and the client is - * responsible for freeing it. - */ - GCond *cond; - - /** - * indicates whether the stream is ready for reading and - * whether the other attributes in this struct are valid - */ - bool ready; - - /** - * if true, then the stream is fully seekable - */ - bool seekable; - - /** - * the size of the resource, or -1 if unknown - */ - goffset size; - - /** - * the current offset within the stream - */ - goffset offset; - - /** - * the MIME content type of the resource, or NULL if unknown - */ - char *mime; -}; +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" /** * Opens a new input stream. You may not access it until the "ready" @@ -99,13 +48,15 @@ struct input_stream { * notifications * @return an #input_stream object on success, NULL on error */ -gcc_nonnull(1, 2) +gcc_nonnull(1) G_GNUC_MALLOC struct input_stream * input_stream_open(const char *uri, - GMutex *mutex, GCond *cond, + Mutex &mutex, Cond &cond, GError **error_r); +#endif + /** * Close the input stream and free resources. * @@ -115,20 +66,6 @@ gcc_nonnull(1) void input_stream_close(struct input_stream *is); -gcc_nonnull(1) -static inline void -input_stream_lock(struct input_stream *is) -{ - g_mutex_lock(is->mutex); -} - -gcc_nonnull(1) -static inline void -input_stream_unlock(struct input_stream *is) -{ - g_mutex_unlock(is->mutex); -} - /** * Check for errors that may have occurred in the I/O thread. * @@ -163,6 +100,33 @@ gcc_nonnull(1) void input_stream_lock_wait_ready(struct input_stream *is); +gcc_nonnull_all gcc_pure +const char * +input_stream_get_mime_type(const struct input_stream *is); + +gcc_nonnull_all +void +input_stream_override_mime_type(struct input_stream *is, const char *mime); + +gcc_nonnull_all gcc_pure +goffset +input_stream_get_size(const struct input_stream *is); + +gcc_nonnull_all gcc_pure +goffset +input_stream_get_offset(const struct input_stream *is); + +gcc_nonnull_all gcc_pure +bool +input_stream_is_seekable(const struct input_stream *is); + +/** + * Determines whether seeking is cheap. This is true for local files. + */ +gcc_pure gcc_nonnull(1) +bool +input_stream_cheap_seeking(const struct input_stream *is); + /** * Seeks to the specified position in the stream. This will most * likely fail if the "seekable" flag is false. @@ -264,4 +228,8 @@ size_t input_stream_lock_read(struct input_stream *is, void *ptr, size_t size, GError **error_r); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/io_error.h b/src/io_error.h new file mode 100644 index 00000000..930ced10 --- /dev/null +++ b/src/io_error.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_IO_ERROR_H +#define MPD_IO_ERROR_H + +#include <glib.h> + +#include <errno.h> + +/** + * A GQuark for GError for I/O errors. The code is an errno value. + */ +G_GNUC_CONST +static inline GQuark +errno_quark(void) +{ + return g_quark_from_static_string("errno"); +} + +static inline void +set_error_errno(GError **error_r) +{ + g_set_error_literal(error_r, errno_quark(), errno, + g_strerror(errno)); +} + +static inline GError * +new_error_errno(void) +{ + return g_error_new_literal(errno_quark(), errno, + g_strerror(errno)); +} + +#endif diff --git a/src/locate.c b/src/locate.c deleted file mode 100644 index c9684d2b..00000000 --- a/src/locate.c +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "locate.h" -#include "path.h" -#include "tag.h" -#include "song.h" - -#include <glib.h> - -#include <stdlib.h> - -#define LOCATE_TAG_FILE_KEY "file" -#define LOCATE_TAG_FILE_KEY_OLD "filename" -#define LOCATE_TAG_ANY_KEY "any" - -int -locate_parse_type(const char *str) -{ - if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY) || - 0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY_OLD)) - return LOCATE_TAG_FILE_TYPE; - - if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY)) - return LOCATE_TAG_ANY_TYPE; - - enum tag_type i = tag_name_parse_i(str); - if (i != TAG_NUM_OF_ITEM_TYPES) - return i; - - return -1; -} - -static bool -locate_item_init(struct locate_item *item, - const char *type_string, const char *needle) -{ - item->tag = locate_parse_type(type_string); - - if (item->tag < 0) - return false; - - item->needle = g_strdup(needle); - - return true; -} - -void -locate_item_list_free(struct locate_item_list *list) -{ - for (unsigned i = 0; i < list->length; ++i) - g_free(list->items[i].needle); - - g_free(list); -} - -struct locate_item_list * -locate_item_list_new(unsigned length) -{ - struct locate_item_list *list = - g_malloc0(sizeof(*list) - sizeof(list->items[0]) + - length * sizeof(list->items[0])); - list->length = length; - - return list; -} - -struct locate_item_list * -locate_item_list_parse(char *argv[], int argc) -{ - if (argc % 2 != 0) - return NULL; - - struct locate_item_list *list = locate_item_list_new(argc / 2); - - for (unsigned i = 0; i < list->length; ++i) { - if (!locate_item_init(&list->items[i], argv[i * 2], - argv[i * 2 + 1])) { - locate_item_list_free(list); - return NULL; - } - } - - return list; -} - -struct locate_item_list * -locate_item_list_casefold(const struct locate_item_list *list) -{ - struct locate_item_list *new_list = locate_item_list_new(list->length); - - for (unsigned i = 0; i < list->length; i++){ - new_list->items[i].needle = - g_utf8_casefold(list->items[i].needle, -1); - new_list->items[i].tag = list->items[i].tag; - } - - return new_list; -} - -void -locate_item_free(struct locate_item *item) -{ - g_free(item->needle); - g_free(item); -} - -static bool -locate_tag_search(const struct song *song, enum tag_type type, const char *str) -{ - bool ret = false; - - if (type == LOCATE_TAG_FILE_TYPE || (int)type == LOCATE_TAG_ANY_TYPE) { - char *uri = song_get_uri(song); - char *p = g_utf8_casefold(uri, -1); - g_free(uri); - - if (strstr(p, str)) - ret = true; - g_free(p); - if (ret == 1 || type == LOCATE_TAG_FILE_TYPE) - return ret; - } - - if (!song->tag) - return false; - - bool visited_types[TAG_NUM_OF_ITEM_TYPES]; - memset(visited_types, 0, sizeof(visited_types)); - - for (unsigned i = 0; i < song->tag->num_items && !ret; i++) { - visited_types[song->tag->items[i]->type] = true; - if ((int)type != LOCATE_TAG_ANY_TYPE && - song->tag->items[i]->type != type) { - continue; - } - - char *duplicate = g_utf8_casefold(song->tag->items[i]->value, -1); - if (*str && strstr(duplicate, str)) - ret = true; - g_free(duplicate); - } - - /** If the search critieron was not visited during the sweep - * through the song's tag, it means this field is absent from - * the tag or empty. Thus, if the searched string is also - * empty (first char is a \0), then it's a match as well and - * we should return true. - */ - if (!*str && !visited_types[type]) - return true; - - return ret; -} - -bool -locate_song_search(const struct song *song, - const struct locate_item_list *criteria) -{ - for (unsigned i = 0; i < criteria->length; i++) - if (!locate_tag_search(song, criteria->items[i].tag, - criteria->items[i].needle)) - return false; - - return true; -} - -static bool -locate_tag_match(const struct song *song, enum tag_type type, const char *str) -{ - if (type == LOCATE_TAG_FILE_TYPE || (int)type == LOCATE_TAG_ANY_TYPE) { - char *uri = song_get_uri(song); - bool matches = strcmp(str, uri) == 0; - g_free(uri); - - if (matches) - return true; - - if (type == LOCATE_TAG_FILE_TYPE) - return false; - } - - if (!song->tag) - return false; - - bool visited_types[TAG_NUM_OF_ITEM_TYPES]; - memset(visited_types, 0, sizeof(visited_types)); - - for (unsigned i = 0; i < song->tag->num_items; i++) { - visited_types[song->tag->items[i]->type] = true; - if ((int)type != LOCATE_TAG_ANY_TYPE && - song->tag->items[i]->type != type) { - continue; - } - - if (0 == strcmp(str, song->tag->items[i]->value)) - return true; - } - - /** If the search critieron was not visited during the sweep - * through the song's tag, it means this field is absent from - * the tag or empty. Thus, if the searched string is also - * empty (first char is a \0), then it's a match as well and - * we should return true. - */ - if (!*str && !visited_types[type]) - return true; - - return false; -} - -bool -locate_song_match(const struct song *song, - const struct locate_item_list *criteria) -{ - for (unsigned i = 0; i < criteria->length; i++) - if (!locate_tag_match(song, criteria->items[i].tag, - criteria->items[i].needle)) - return false; - - return true; -} diff --git a/src/locate.h b/src/locate.h deleted file mode 100644 index ec20ded2..00000000 --- a/src/locate.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_LOCATE_H -#define MPD_LOCATE_H - -#include "gcc.h" - -#include <stdint.h> -#include <stdbool.h> - -#define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10 -#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20 - -struct song; - -/* struct used for search, find, list queries */ -struct locate_item { - int8_t tag; - /* what we are looking for */ - char *needle; -}; - -/** - * An array of struct locate_item objects. - */ -struct locate_item_list { - /** number of items */ - unsigned length; - - /** this is a variable length array */ - struct locate_item items[1]; -}; - -int -locate_parse_type(const char *str); - -/** - * Allocates a new struct locate_item_list, and initializes all - * members with zero bytes. - */ -struct locate_item_list * -locate_item_list_new(unsigned length); - -/* return number of items or -1 on error */ -gcc_nonnull(1) -struct locate_item_list * -locate_item_list_parse(char *argv[], int argc); - -/** - * Duplicate the struct locate_item_list object and convert all - * needles with g_utf8_casefold(). - */ -gcc_nonnull(1) -struct locate_item_list * -locate_item_list_casefold(const struct locate_item_list *list); - -gcc_nonnull(1) -void -locate_item_list_free(struct locate_item_list *list); - -gcc_nonnull(1) -void -locate_item_free(struct locate_item *item); - -gcc_nonnull(1,2) -bool -locate_song_search(const struct song *song, - const struct locate_item_list *criteria); - -gcc_nonnull(1,2) -bool -locate_song_match(const struct song *song, - const struct locate_item_list *criteria); - -#endif @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,15 @@ */ #include "config.h" -#include "ls.h" +#include "ls.hxx" + +extern "C" { #include "uri.h" -#include "client.h" +} + +#include "Client.hxx" + +#include <glib.h> #include <assert.h> #include <string.h> @@ -72,7 +78,7 @@ void print_supported_uri_schemes_to_fp(FILE *fp) fprintf(fp,"\n"); } -void print_supported_uri_schemes(struct client *client) +void print_supported_uri_schemes(Client *client) { const char **prefixes = remoteUrlPrefixes; @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_LS_H -#define MPD_LS_H +#ifndef MPD_LS_HXX +#define MPD_LS_HXX -#include <stdbool.h> #include <stdio.h> -struct client; +class Client; /** * Checks whether the scheme of the specified URI is supported by MPD. @@ -36,7 +35,7 @@ bool uri_supported_scheme(const char *url); * Send a list of supported URI schemes to the client. This is the * response to the "urlhandlers" command. */ -void print_supported_uri_schemes(struct client *client); +void print_supported_uri_schemes(Client *client); /** * Send a list of supported URI schemes to a file pointer. diff --git a/src/mapper.c b/src/mapper.c deleted file mode 100644 index 7db74b1a..00000000 --- a/src/mapper.c +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Maps directory and song objects to file system paths. - */ - -#include "config.h" -#include "mapper.h" -#include "directory.h" -#include "song.h" -#include "path.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <dirent.h> - -/** - * The absolute path of the music directory encoded in UTF-8. - */ -static char *music_dir_utf8; -static size_t music_dir_utf8_length; - -/** - * The absolute path of the music directory encoded in the filesystem - * character set. - */ -static char *music_dir_fs; -static size_t music_dir_fs_length; - -/** - * The absolute path of the playlist directory encoded in the - * filesystem character set. - */ -static char *playlist_dir_fs; - -/** - * Duplicate a string, chop all trailing slashes. - */ -static char * -strdup_chop_slash(const char *path_fs) -{ - size_t length = strlen(path_fs); - - while (length > 0 && path_fs[length - 1] == G_DIR_SEPARATOR) - --length; - - return g_strndup(path_fs, length); -} - -static void -check_directory(const char *path) -{ - struct stat st; - if (stat(path, &st) < 0) { - g_warning("Failed to stat directory \"%s\": %s", - path, g_strerror(errno)); - return; - } - - if (!S_ISDIR(st.st_mode)) { - g_warning("Not a directory: %s", path); - return; - } - -#ifndef WIN32 - char *x = g_build_filename(path, ".", NULL); - if (stat(x, &st) < 0 && errno == EACCES) - g_warning("No permission to traverse (\"execute\") directory: %s", - path); - g_free(x); -#endif - - DIR *dir = opendir(path); - if (dir != NULL) - closedir(dir); - else if (errno == EACCES) - g_warning("No permission to read directory: %s", path); -} - -static void -mapper_set_music_dir(const char *path_utf8) -{ - music_dir_utf8 = strdup_chop_slash(path_utf8); - music_dir_utf8_length = strlen(music_dir_utf8); - - music_dir_fs = utf8_to_fs_charset(music_dir_utf8); - check_directory(music_dir_fs); - music_dir_fs_length = strlen(music_dir_fs); -} - -static void -mapper_set_playlist_dir(const char *path_utf8) -{ - playlist_dir_fs = utf8_to_fs_charset(path_utf8); - check_directory(playlist_dir_fs); -} - -void mapper_init(const char *_music_dir, const char *_playlist_dir) -{ - if (_music_dir != NULL) - mapper_set_music_dir(_music_dir); - - if (_playlist_dir != NULL) - mapper_set_playlist_dir(_playlist_dir); -} - -void mapper_finish(void) -{ - g_free(music_dir_utf8); - g_free(music_dir_fs); - g_free(playlist_dir_fs); -} - -const char * -mapper_get_music_directory_utf8(void) -{ - return music_dir_utf8; -} - -const char * -mapper_get_music_directory_fs(void) -{ - return music_dir_fs; -} - -const char * -map_to_relative_path(const char *path_utf8) -{ - return music_dir_utf8 != NULL && - memcmp(path_utf8, music_dir_utf8, - music_dir_utf8_length) == 0 && - G_IS_DIR_SEPARATOR(path_utf8[music_dir_utf8_length]) - ? path_utf8 + music_dir_utf8_length + 1 - : path_utf8; -} - -char * -map_uri_fs(const char *uri) -{ - char *uri_fs, *path_fs; - - assert(uri != NULL); - assert(*uri != '/'); - - if (music_dir_fs == NULL) - return NULL; - - uri_fs = utf8_to_fs_charset(uri); - if (uri_fs == NULL) - return NULL; - - path_fs = g_build_filename(music_dir_fs, uri_fs, NULL); - g_free(uri_fs); - - return path_fs; -} - -char * -map_directory_fs(const struct directory *directory) -{ - assert(music_dir_utf8 != NULL); - assert(music_dir_fs != NULL); - - if (directory_is_root(directory)) - return g_strdup(music_dir_fs); - - return map_uri_fs(directory_get_path(directory)); -} - -char * -map_directory_child_fs(const struct directory *directory, const char *name) -{ - assert(music_dir_utf8 != NULL); - assert(music_dir_fs != NULL); - - char *name_fs, *parent_fs, *path; - - /* check for invalid or unauthorized base names */ - if (*name == 0 || strchr(name, '/') != NULL || - strcmp(name, ".") == 0 || strcmp(name, "..") == 0) - return NULL; - - parent_fs = map_directory_fs(directory); - if (parent_fs == NULL) - return NULL; - - name_fs = utf8_to_fs_charset(name); - if (name_fs == NULL) { - g_free(parent_fs); - return NULL; - } - - path = g_build_filename(parent_fs, name_fs, NULL); - g_free(parent_fs); - g_free(name_fs); - - return path; -} - -char * -map_song_fs(const struct song *song) -{ - assert(song_is_file(song)); - - if (song_in_database(song)) - return map_directory_child_fs(song->parent, song->uri); - else - return utf8_to_fs_charset(song->uri); -} - -char * -map_fs_to_utf8(const char *path_fs) -{ - if (music_dir_fs != NULL && - strncmp(path_fs, music_dir_fs, music_dir_fs_length) == 0 && - G_IS_DIR_SEPARATOR(path_fs[music_dir_fs_length])) - /* remove musicDir prefix */ - path_fs += music_dir_fs_length + 1; - else if (G_IS_DIR_SEPARATOR(path_fs[0])) - /* not within musicDir */ - return NULL; - - while (path_fs[0] == G_DIR_SEPARATOR) - ++path_fs; - - return fs_charset_to_utf8(path_fs); -} - -const char * -map_spl_path(void) -{ - return playlist_dir_fs; -} - -char * -map_spl_utf8_to_fs(const char *name) -{ - char *filename_utf8, *filename_fs, *path; - - if (playlist_dir_fs == NULL) - return NULL; - - filename_utf8 = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL); - filename_fs = utf8_to_fs_charset(filename_utf8); - g_free(filename_utf8); - if (filename_fs == NULL) - return NULL; - - path = g_build_filename(playlist_dir_fs, filename_fs, NULL); - g_free(filename_fs); - - return path; -} diff --git a/src/mixer/alsa_mixer_plugin.c b/src/mixer/AlsaMixerPlugin.cxx index 22e4e22b..d75016c0 100644 --- a/src/mixer/alsa_mixer_plugin.c +++ b/src/mixer/AlsaMixerPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,13 @@ */ #include "config.h" -#include "mixer_api.h" +#include "MixerInternal.hxx" #include "output_api.h" -#include "event_pipe.h" +#include "GlobalEvents.hxx" +#include "Main.hxx" +#include "event/MultiSocketMonitor.hxx" + +#include <algorithm> #include <glib.h> #include <alsa/asoundlib.h> @@ -29,13 +33,16 @@ #define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" #define VOLUME_MIXER_ALSA_INDEX_DEFAULT 0 -struct alsa_mixer_source { - GSource source; +class AlsaMixerMonitor final : private MultiSocketMonitor { + snd_mixer_t *const mixer; - snd_mixer_t *mixer; +public: + AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer) + :MultiSocketMonitor(_loop), mixer(_mixer) {} - /** a linked list of all registered GPollFD objects */ - GSList *fds; +private: + virtual void PrepareSockets(gcc_unused gint *timeout_r) override; + virtual void DispatchSockets() override; }; struct alsa_mixer { @@ -52,7 +59,7 @@ struct alsa_mixer { long volume_max; int volume_set; - struct alsa_mixer_source *source; + AlsaMixerMonitor *monitor; }; /** @@ -64,142 +71,45 @@ alsa_mixer_quark(void) return g_quark_from_static_string("alsa_mixer"); } -/* - * GSource helper functions - * - */ - -static GSList ** -find_fd(GSList **list_r, int fd) -{ - while (true) { - GSList *list = *list_r; - if (list == NULL) - return NULL; - - GPollFD *p = list->data; - if (p->fd == fd) - return list_r; - - list_r = &list->next; - } -} - -static void -alsa_mixer_update_fd(struct alsa_mixer_source *source, const struct pollfd *p, - GSList **old_r) -{ - GSList **found_r = find_fd(old_r, p->fd); - if (found_r == NULL) { - /* new fd */ - GPollFD *q = g_new(GPollFD, 1); - q->fd = p->fd; - q->events = p->events; - g_source_add_poll(&source->source, q); - source->fds = g_slist_prepend(source->fds, q); - return; - } - - GSList *found = *found_r; - *found_r = found->next; - - GPollFD *q = found->data; - if (q->events != p->events) { - /* refresh events */ - g_source_remove_poll(&source->source, q); - q->events = p->events; - g_source_add_poll(&source->source, q); - } - - found->next = source->fds; - source->fds = found; -} - -static void -alsa_mixer_update_fds(struct alsa_mixer_source *source) +void +AlsaMixerMonitor::PrepareSockets(gcc_unused gint *timeout_r) { - int count = snd_mixer_poll_descriptors_count(source->mixer); + int count = snd_mixer_poll_descriptors_count(mixer); if (count < 0) count = 0; struct pollfd *pfds = g_new(struct pollfd, count); - count = snd_mixer_poll_descriptors(source->mixer, pfds, count); + count = snd_mixer_poll_descriptors(mixer, pfds, count); if (count < 0) count = 0; - GSList *old = source->fds; - source->fds = NULL; + struct pollfd *end = pfds + count; - for (int i = 0; i < count; ++i) - alsa_mixer_update_fd(source, &pfds[i], &old); - g_free(pfds); + 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; - for (; old != NULL; old = old->next) { - GPollFD *q = old->data; - g_source_remove_poll(&source->source, q); - g_free(q); - } + auto events = i->events; + i->events = 0; + return events; + }); - g_slist_free(old); -} + for (auto i = pfds; i != end; ++i) + if (i->events != 0) + AddSocket(i->fd, i->events); -/* - * GSource methods - * - */ - -static gboolean -alsa_mixer_source_prepare(GSource *_source, G_GNUC_UNUSED gint *timeout_r) -{ - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - alsa_mixer_update_fds(source); - - return false; -} - -static gboolean -alsa_mixer_source_check(GSource *_source) -{ - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - - for (const GSList *i = source->fds; i != NULL; i = i->next) { - const GPollFD *poll_fd = i->data; - if (poll_fd->revents != 0) - return true; - } - - return false; + g_free(pfds); } -static gboolean -alsa_mixer_source_dispatch(GSource *_source, - G_GNUC_UNUSED GSourceFunc callback, - G_GNUC_UNUSED gpointer user_data) +void +AlsaMixerMonitor::DispatchSockets() { - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - - snd_mixer_handle_events(source->mixer); - return true; + snd_mixer_handle_events(mixer); } -static void -alsa_mixer_source_finalize(GSource *_source) -{ - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - - for (GSList *i = source->fds; i != NULL; i = i->next) - g_free(i->data); - - g_slist_free(source->fds); -} - -static GSourceFuncs alsa_mixer_source_funcs = { - .prepare = alsa_mixer_source_prepare, - .check = alsa_mixer_source_check, - .dispatch = alsa_mixer_source_dispatch, - .finalize = alsa_mixer_source_finalize, -}; - /* * libasound callbacks * @@ -209,7 +119,7 @@ static int alsa_mixer_elem_callback(G_GNUC_UNUSED snd_mixer_elem_t *elem, unsigned mask) { if (mask & SND_CTL_EVENT_MASK_VALUE) - event_pipe_emit(PIPE_EVENT_MIXER); + GlobalEvents::Emit(GlobalEvents::MIXER); return 0; } @@ -304,11 +214,7 @@ alsa_mixer_setup(struct alsa_mixer *am, GError **error_r) snd_mixer_elem_set_callback(am->elem, alsa_mixer_elem_callback); - am->source = (struct alsa_mixer_source *) - g_source_new(&alsa_mixer_source_funcs, sizeof(*am->source)); - am->source->mixer = am->handle; - am->source->fds = NULL; - g_source_attach(&am->source->source, g_main_context_default()); + am->monitor = new AlsaMixerMonitor(*main_loop, am->handle); return true; } @@ -343,8 +249,7 @@ alsa_mixer_close(struct mixer *data) assert(am->handle != NULL); - g_source_destroy(&am->source->source); - g_source_unref(&am->source->source); + delete am->monitor; snd_mixer_elem_set_callback(am->elem, NULL); snd_mixer_close(am->handle); @@ -421,11 +326,11 @@ alsa_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) } const struct mixer_plugin alsa_mixer_plugin = { - .init = alsa_mixer_init, - .finish = alsa_mixer_finish, - .open = alsa_mixer_open, - .close = alsa_mixer_close, - .get_volume = alsa_mixer_get_volume, - .set_volume = alsa_mixer_set_volume, - .global = true, + 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/oss_mixer_plugin.c b/src/mixer/OssMixerPlugin.cxx index 608f1f9b..8d266b40 100644 --- a/src/mixer/oss_mixer_plugin.c +++ b/src/mixer/OssMixerPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "mixer_api.h" +#include "MixerInternal.hxx" #include "output_api.h" #include "fd_util.h" @@ -206,11 +206,11 @@ oss_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) } const struct mixer_plugin oss_mixer_plugin = { - .init = oss_mixer_init, - .finish = oss_mixer_finish, - .open = oss_mixer_open, - .close = oss_mixer_close, - .get_volume = oss_mixer_get_volume, - .set_volume = oss_mixer_set_volume, - .global = true, + 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/pulse_mixer_plugin.c b/src/mixer/PulseMixerPlugin.cxx index a82c032b..d7c6c804 100644 --- a/src/mixer/pulse_mixer_plugin.c +++ b/src/mixer/PulseMixerPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,11 @@ */ #include "config.h" -#include "pulse_mixer_plugin.h" -#include "mixer_api.h" +#include "PulseMixerPlugin.h" +#include "MixerInternal.hxx" #include "output/pulse_output_plugin.h" #include "conf.h" -#include "event_pipe.h" +#include "GlobalEvents.hxx" #include <glib.h> @@ -66,7 +66,7 @@ pulse_mixer_offline(struct pulse_mixer *pm) pm->online = false; - event_pipe_emit(PIPE_EVENT_MIXER); + GlobalEvents::Emit(GlobalEvents::MIXER); } /** @@ -77,7 +77,7 @@ static void pulse_mixer_volume_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i, int eol, void *userdata) { - struct pulse_mixer *pm = userdata; + struct pulse_mixer *pm = (struct pulse_mixer *)userdata; if (eol) return; @@ -90,7 +90,7 @@ pulse_mixer_volume_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_inf pm->online = true; pm->volume = i->volume; - event_pipe_emit(PIPE_EVENT_MIXER); + GlobalEvents::Emit(GlobalEvents::MIXER); } static void @@ -153,16 +153,15 @@ static struct mixer * pulse_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, GError **error_r) { - struct pulse_mixer *pm; - struct pulse_output *po = ao; + struct pulse_output *po = (struct pulse_output *)ao; if (ao == NULL) { g_set_error(error_r, pulse_mixer_quark(), 0, "The pulse mixer cannot work without the audio output"); - return false; + return nullptr; } - pm = g_new(struct pulse_mixer,1); + struct pulse_mixer *pm = g_new(struct pulse_mixer,1); mixer_init(&pm->base, &pulse_mixer_plugin); pm->online = false; @@ -229,8 +228,11 @@ pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) } const struct mixer_plugin pulse_mixer_plugin = { - .init = pulse_mixer_init, - .finish = pulse_mixer_finish, - .get_volume = pulse_mixer_get_volume, - .set_volume = pulse_mixer_set_volume, + pulse_mixer_init, + pulse_mixer_finish, + nullptr, + nullptr, + pulse_mixer_get_volume, + pulse_mixer_set_volume, + false, }; diff --git a/src/mixer/pulse_mixer_plugin.h b/src/mixer/PulseMixerPlugin.h index 461633d3..f432c44a 100644 --- a/src/mixer/pulse_mixer_plugin.h +++ b/src/mixer/PulseMixerPlugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,6 +26,10 @@ struct pulse_mixer; struct pa_context; struct pa_stream; +#ifdef __cplusplus +extern "C" { +#endif + void pulse_mixer_on_connect(struct pulse_mixer *pm, struct pa_context *context); @@ -36,4 +40,8 @@ void pulse_mixer_on_change(struct pulse_mixer *pm, struct pa_context *context, struct pa_stream *stream); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/mixer/roar_mixer_plugin.c b/src/mixer/RoarMixerPlugin.cxx index 47d3c17f..a027f857 100644 --- a/src/mixer/roar_mixer_plugin.c +++ b/src/mixer/RoarMixerPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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 * @@ -20,85 +20,57 @@ #include "config.h" -#include "mixer_api.h" +#include "MixerInternal.hxx" #include "output_api.h" -#include "output/roar_output_plugin.h" +#include "output/RoarOutputPlugin.hxx" -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> -#include <unistd.h> - -typedef struct roar_mpd_mixer -{ +struct RoarMixer { /** the base mixer class */ struct mixer base; - struct roar *self; -} roar_mixer_t; + RoarOutput *self; -/** - * The quark used for GError.domain. - */ -static inline GQuark -roar_mixer_quark(void) -{ - return g_quark_from_static_string("roar_mixer"); -} + RoarMixer(RoarOutput *_output):self(_output) { + mixer_init(&base, &roar_mixer_plugin); + } +}; static struct mixer * -roar_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) +roar_mixer_init(void *ao, gcc_unused const struct config_param *param, + gcc_unused GError **error_r) { - roar_mixer_t *self = g_new(roar_mixer_t, 1); - self->self = ao; - - mixer_init(&self->base, &roar_mixer_plugin); - + RoarMixer *self = new RoarMixer((RoarOutput *)ao); return &self->base; } static void roar_mixer_finish(struct mixer *data) { - roar_mixer_t *self = (roar_mixer_t *) data; + RoarMixer *self = (RoarMixer *) data; - g_free(self); -} - -static void -roar_mixer_close(G_GNUC_UNUSED struct mixer *data) -{ -} - -static bool -roar_mixer_open(G_GNUC_UNUSED struct mixer *data, - G_GNUC_UNUSED GError **error_r) -{ - return true; + delete self; } static int -roar_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) +roar_mixer_get_volume(struct mixer *mixer, gcc_unused GError **error_r) { - roar_mixer_t *self = (roar_mixer_t *)mixer; + RoarMixer *self = (RoarMixer *)mixer; return roar_output_get_volume(self->self); } static bool roar_mixer_set_volume(struct mixer *mixer, unsigned volume, - G_GNUC_UNUSED GError **error_r) + gcc_unused GError **error_r) { - roar_mixer_t *self = (roar_mixer_t *)mixer; + RoarMixer *self = (RoarMixer *)mixer; return roar_output_set_volume(self->self, volume); } const struct mixer_plugin roar_mixer_plugin = { - .init = roar_mixer_init, - .finish = roar_mixer_finish, - .open = roar_mixer_open, - .close = roar_mixer_close, - .get_volume = roar_mixer_get_volume, - .set_volume = roar_mixer_set_volume, - .global = false, + roar_mixer_init, + roar_mixer_finish, + nullptr, + nullptr, + roar_mixer_get_volume, + roar_mixer_set_volume, + false, }; diff --git a/src/mixer/software_mixer_plugin.c b/src/mixer/SoftwareMixerPlugin.cxx index 0206c3b9..6c287ea0 100644 --- a/src/mixer/software_mixer_plugin.c +++ b/src/mixer/SoftwareMixerPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,12 +18,12 @@ */ #include "config.h" -#include "software_mixer_plugin.h" -#include "mixer_api.h" -#include "filter_plugin.h" -#include "filter_registry.h" -#include "filter/volume_filter_plugin.h" -#include "pcm_volume.h" +#include "SoftwareMixerPlugin.hxx" +#include "MixerInternal.hxx" +#include "FilterPlugin.hxx" +#include "FilterRegistry.hxx" +#include "filter/VolumeFilterPlugin.hxx" +#include "PcmVolume.hxx" #include <assert.h> #include <math.h> @@ -32,7 +32,7 @@ struct software_mixer { /** the base mixer class */ struct mixer base; - struct filter *filter; + Filter *filter; unsigned volume; }; @@ -91,14 +91,16 @@ software_mixer_set_volume(struct mixer *mixer, unsigned volume, } const struct mixer_plugin software_mixer_plugin = { - .init = software_mixer_init, - .finish = software_mixer_finish, - .get_volume = software_mixer_get_volume, - .set_volume = software_mixer_set_volume, - .global = true, + software_mixer_init, + software_mixer_finish, + nullptr, + nullptr, + software_mixer_get_volume, + software_mixer_set_volume, + true, }; -struct filter * +Filter * software_mixer_get_filter(struct mixer *mixer) { struct software_mixer *sm = (struct software_mixer *)mixer; diff --git a/src/mixer/software_mixer_plugin.h b/src/mixer/SoftwareMixerPlugin.hxx index ee2b2023..33e9e6c6 100644 --- a/src/mixer/software_mixer_plugin.h +++ b/src/mixer/SoftwareMixerPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,17 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef SOFTWARE_MIXER_PLUGIN_H -#define SOFTWARE_MIXER_PLUGIN_H +#ifndef MPD_SOFTWARE_MIXER_PLUGIN_HXX +#define MPD_SOFTWARE_MIXER_PLUGIN_HXX struct mixer; -struct filter; +class Filter; /** * Returns the (volume) filter associated with this mixer. All users * of this mixer plugin should install this filter. */ -struct filter * +Filter * software_mixer_get_filter(struct mixer *mixer); #endif diff --git a/src/mixer/winmm_mixer_plugin.c b/src/mixer/WinmmMixerPlugin.cxx index ceddf6af..bf315586 100644 --- a/src/mixer/winmm_mixer_plugin.c +++ b/src/mixer/WinmmMixerPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,11 @@ */ #include "config.h" -#include "mixer_api.h" +#include "MixerInternal.hxx" #include "output_api.h" -#include "output/winmm_output_plugin.h" +#include "output/WinmmOutputPlugin.hxx" + +#include <mmsystem.h> #include <assert.h> #include <math.h> @@ -31,7 +33,7 @@ struct winmm_mixer { struct mixer base; - struct winmm_output *output; + WinmmOutput *output; }; static inline GQuark @@ -57,11 +59,11 @@ static struct mixer * winmm_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, G_GNUC_UNUSED GError **error_r) { - assert(ao != NULL); + assert(ao != nullptr); struct winmm_mixer *wm = g_new(struct winmm_mixer, 1); mixer_init(&wm->base, &winmm_mixer_plugin); - wm->output = (struct winmm_output *) ao; + wm->output = (WinmmOutput *) ao; return &wm->base; } @@ -107,8 +109,11 @@ winmm_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) } const struct mixer_plugin winmm_mixer_plugin = { - .init = winmm_mixer_init, - .finish = winmm_mixer_finish, - .get_volume = winmm_mixer_get_volume, - .set_volume = winmm_mixer_set_volume, + winmm_mixer_init, + winmm_mixer_finish, + nullptr, + nullptr, + winmm_mixer_get_volume, + winmm_mixer_set_volume, + false, }; diff --git a/src/mpd_error.h b/src/mpd_error.h index 219738ce..e0b7d29a 100644 --- a/src/mpd_error.h +++ b/src/mpd_error.h @@ -20,6 +20,7 @@ #ifndef MPD_ERROR_H #define MPD_ERROR_H +#include <glib.h> #include <stdlib.h> /* This macro is used as an intermediate step to a proper error handling diff --git a/src/notify.c b/src/notify.c deleted file mode 100644 index 3c0112c9..00000000 --- a/src/notify.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "notify.h" - -void notify_init(struct notify *notify) -{ - notify->mutex = g_mutex_new(); - notify->cond = g_cond_new(); - notify->pending = false; -} - -void notify_deinit(struct notify *notify) -{ - g_mutex_free(notify->mutex); - g_cond_free(notify->cond); -} - -void notify_wait(struct notify *notify) -{ - g_mutex_lock(notify->mutex); - while (!notify->pending) - g_cond_wait(notify->cond, notify->mutex); - notify->pending = false; - g_mutex_unlock(notify->mutex); -} - -void notify_signal(struct notify *notify) -{ - g_mutex_lock(notify->mutex); - notify->pending = true; - g_cond_signal(notify->cond); - g_mutex_unlock(notify->mutex); -} - -void notify_clear(struct notify *notify) -{ - g_mutex_lock(notify->mutex); - notify->pending = false; - g_mutex_unlock(notify->mutex); -} diff --git a/src/notify.cxx b/src/notify.cxx new file mode 100644 index 00000000..64018968 --- /dev/null +++ b/src/notify.cxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "notify.hxx" + +void +notify::Wait() +{ + const ScopeLock protect(mutex); + while (!pending) + cond.wait(mutex); + pending = false; +} + +void +notify::Signal() +{ + const ScopeLock protect(mutex); + pending = true; + cond.signal(); +} + +void +notify::Clear() +{ + const ScopeLock protect(mutex); + pending = false; +} diff --git a/src/notify.hxx b/src/notify.hxx new file mode 100644 index 00000000..6b9e9536 --- /dev/null +++ b/src/notify.hxx @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NOTIFY_HXX +#define MPD_NOTIFY_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +struct notify { + Mutex mutex; + Cond cond; + bool pending; + +#ifndef WIN32 + constexpr +#endif + notify():pending(false) {} + + /** + * Wait for a notification. Return immediately if we have already + * been notified since we last returned from notify_wait(). + */ + void Wait(); + + /** + * Notify the thread. This function never blocks. + */ + void Signal(); + + /** + * Clears a pending notification. + */ + void Clear(); +}; + +#endif diff --git a/src/output/alsa_output_plugin.c b/src/output/AlsaOutputPlugin.cxx index d8b18427..c87c4cb9 100644 --- a/src/output/alsa_output_plugin.c +++ b/src/output/AlsaOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,16 @@ */ #include "config.h" -#include "alsa_output_plugin.h" +#include "AlsaOutputPlugin.hxx" #include "output_api.h" -#include "mixer_list.h" +#include "MixerList.hxx" #include "pcm_export.h" #include <glib.h> #include <alsa/asoundlib.h> +#include <string> + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "alsa" @@ -43,14 +45,16 @@ enum { typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, snd_pcm_uframes_t size); -struct alsa_data { +struct AlsaOutput { struct audio_output base; - struct pcm_export_state export; + struct pcm_export_state pcm_export; - /** the configured name of the ALSA device; NULL for the - default device */ - char *device; + /** + * The configured name of the ALSA device; empty for the + * default device + */ + std::string device; /** use memory mapped I/O? */ bool use_mmap; @@ -101,6 +105,25 @@ struct alsa_data { * The number of frames written in the current period. */ snd_pcm_uframes_t period_position; + + /** + * This buffer gets allocated after opening the ALSA device. + * It contains silence samples, enough to fill one period (see + * #period_frames). + */ + void *silence; + + AlsaOutput():mode(0), writei(snd_pcm_writei) { + } + + bool Init(const config_param *param, GError **error_r) { + return ao_base_init(&base, &alsa_output_plugin, + param, error_r); + } + + void Deinit() { + ao_base_finish(&base); + } }; /** @@ -113,26 +136,15 @@ alsa_output_quark(void) } static const char * -alsa_device(const struct alsa_data *ad) +alsa_device(const AlsaOutput *ad) { - return ad->device != NULL ? ad->device : default_device; -} - -static struct alsa_data * -alsa_data_new(void) -{ - struct alsa_data *ret = g_new(struct alsa_data, 1); - - ret->mode = 0; - ret->writei = snd_pcm_writei; - - return ret; + return ad->device.empty() ? default_device : ad->device.c_str(); } static void -alsa_configure(struct alsa_data *ad, const struct config_param *param) +alsa_configure(AlsaOutput *ad, const struct config_param *param) { - ad->device = config_dup_block_string(param, "device", NULL); + ad->device = config_get_block_string(param, "device", ""); ad->use_mmap = config_get_block_bool(param, "use_mmap", false); @@ -161,10 +173,10 @@ alsa_configure(struct alsa_data *ad, const struct config_param *param) static struct audio_output * alsa_init(const struct config_param *param, GError **error_r) { - struct alsa_data *ad = alsa_data_new(); + AlsaOutput *ad = new AlsaOutput(); - if (!ao_base_init(&ad->base, &alsa_output_plugin, param, error_r)) { - g_free(ad); + if (!ad->Init(param, error_r)) { + delete ad; return NULL; } @@ -176,12 +188,10 @@ alsa_init(const struct config_param *param, GError **error_r) static void alsa_finish(struct audio_output *ao) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; - ao_base_finish(&ad->base); - - g_free(ad->device); - g_free(ad); + ad->Deinit(); + delete ad; /* free libasound's config cache */ snd_config_update_free_global(); @@ -190,18 +200,18 @@ alsa_finish(struct audio_output *ao) static bool alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; - pcm_export_init(&ad->export); + pcm_export_init(&ad->pcm_export); return true; } static void alsa_output_disable(struct audio_output *ao) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; - pcm_export_deinit(&ad->export); + pcm_export_deinit(&ad->pcm_export); } static bool @@ -349,7 +359,8 @@ alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, { /* try the input format first */ - int err = alsa_output_try_format(pcm, hwparams, audio_format->format, + int err = alsa_output_try_format(pcm, hwparams, + sample_format(audio_format->format), packed_r, reverse_endian_r); /* if unsupported by the hardware, try other formats */ @@ -383,15 +394,11 @@ alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, * the configured settings and the audio format. */ static bool -alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, +alsa_setup(AlsaOutput *ad, struct audio_format *audio_format, bool *packed_r, bool *reverse_endian_r, GError **error) { - snd_pcm_hw_params_t *hwparams; - snd_pcm_sw_params_t *swparams; unsigned int sample_rate = audio_format->sample_rate; unsigned int channels = audio_format->channels; - snd_pcm_uframes_t alsa_buffer_size; - snd_pcm_uframes_t alsa_period_size; int err; const char *cmd = NULL; int retry = MPD_ALSA_RETRY_NR; @@ -401,6 +408,7 @@ alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, 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); @@ -434,7 +442,7 @@ configure_hw: g_set_error(error, alsa_output_quark(), err, "ALSA device \"%s\" does not support format %s: %s", alsa_device(ad), - sample_format_to_string(audio_format->format), + sample_format_to_string(sample_format(audio_format->format)), snd_strerror(-err)); return false; } @@ -525,11 +533,13 @@ configure_hw: if (retry != MPD_ALSA_RETRY_NR) g_debug("ALSA period_time set to %d\n", period_time); + snd_pcm_uframes_t alsa_buffer_size; cmd = "snd_pcm_hw_params_get_buffer_size"; err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size); if (err < 0) goto error; + snd_pcm_uframes_t alsa_period_size; cmd = "snd_pcm_hw_params_get_period_size"; err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size, NULL); @@ -537,6 +547,7 @@ configure_hw: goto error; /* configure SW params */ + snd_pcm_sw_params_t *swparams; snd_pcm_sw_params_alloca(&swparams); cmd = "snd_pcm_sw_params_current"; @@ -576,6 +587,11 @@ configure_hw: ad->period_frames = alsa_period_size; ad->period_position = 0; + ad->silence = g_malloc(snd_pcm_frames_to_bytes(ad->pcm, + alsa_period_size)); + snd_pcm_format_set_silence(format, ad->silence, + alsa_period_size * channels); + return true; error: @@ -586,7 +602,7 @@ error: } static bool -alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format, +alsa_setup_dsd(AlsaOutput *ad, struct audio_format *audio_format, bool *shift8_r, bool *packed_r, bool *reverse_endian_r, GError **error_r) { @@ -619,6 +635,7 @@ alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format, g_set_error(error_r, alsa_output_quark(), 0, "Failed to configure DSD-over-USB on ALSA device \"%s\"", alsa_device(ad)); + g_free(ad->silence); return false; } @@ -626,7 +643,7 @@ alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format, } static bool -alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format, +alsa_setup_or_dsd(AlsaOutput *ad, struct audio_format *audio_format, GError **error_r) { bool shift8 = false, packed, reverse_endian; @@ -642,8 +659,9 @@ alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format, if (!success) return false; - pcm_export_open(&ad->export, - audio_format->format, audio_format->channels, + pcm_export_open(&ad->pcm_export, + sample_format(audio_format->format), + audio_format->channels, dsd_usb, shift8, packed, reverse_endian); return true; } @@ -651,12 +669,10 @@ alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format, static bool alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct alsa_data *ad = (struct alsa_data *)ao; - int err; - bool success; + AlsaOutput *ad = (AlsaOutput *)ao; - err = snd_pcm_open(&ad->pcm, alsa_device(ad), - SND_PCM_STREAM_PLAYBACK, ad->mode); + int err = snd_pcm_open(&ad->pcm, alsa_device(ad), + SND_PCM_STREAM_PLAYBACK, ad->mode); if (err < 0) { g_set_error(error, alsa_output_quark(), err, "Failed to open ALSA device \"%s\": %s", @@ -667,20 +683,29 @@ alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **e g_debug("opened %s type=%s", snd_pcm_name(ad->pcm), snd_pcm_type_name(snd_pcm_type(ad->pcm))); - success = alsa_setup_or_dsd(ad, audio_format, error); - if (!success) { + if (!alsa_setup_or_dsd(ad, audio_format, error)) { snd_pcm_close(ad->pcm); return false; } ad->in_frame_size = audio_format_frame_size(audio_format); - ad->out_frame_size = pcm_export_frame_size(&ad->export, audio_format); + ad->out_frame_size = pcm_export_frame_size(&ad->pcm_export, + audio_format); return true; } +/** + * Write silence to the ALSA device. + */ +static void +alsa_write_silence(AlsaOutput *ad, snd_pcm_uframes_t nframes) +{ + ad->writei(ad->pcm, ad->silence, nframes); +} + static int -alsa_recover(struct alsa_data *ad, int err) +alsa_recover(AlsaOutput *ad, int err) { if (err == -EPIPE) { g_debug("Underrun on ALSA device \"%s\"\n", alsa_device(ad)); @@ -701,6 +726,26 @@ alsa_recover(struct alsa_data *ad, int err) case SND_PCM_STATE_XRUN: ad->period_position = 0; err = snd_pcm_prepare(ad->pcm); + + if (err == 0) { + /* this works around a driver bug observed on + the Raspberry Pi: after snd_pcm_drop(), the + whole ring buffer must be invalidated, but + the snd_pcm_prepare() call above makes the + driver play random data that just happens + to be still in the buffer; by adding and + cancelling some silence, this bug does not + occur */ + alsa_write_silence(ad, ad->period_frames); + + /* cancel the silence data right away to avoid + increasing latency; even though this + function call invalidates the portion of + silence, the driver seems to avoid the + bug */ + snd_pcm_reset(ad->pcm); + } + break; case SND_PCM_STATE_DISCONNECTED: break; @@ -719,7 +764,7 @@ alsa_recover(struct alsa_data *ad, int err) static void alsa_drain(struct audio_output *ao) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) return; @@ -729,20 +774,7 @@ alsa_drain(struct audio_output *ao) period */ snd_pcm_uframes_t nframes = ad->period_frames - ad->period_position; - size_t nbytes = nframes * ad->out_frame_size; - void *buffer = g_malloc(nbytes); - snd_pcm_hw_params_t *params; - snd_pcm_format_t format; - unsigned channels; - - snd_pcm_hw_params_alloca(¶ms); - snd_pcm_hw_params_current(ad->pcm, params); - snd_pcm_hw_params_get_format(params, &format); - snd_pcm_hw_params_get_channels(params, &channels); - - snd_pcm_format_set_silence(format, buffer, nframes * channels); - ad->writei(ad->pcm, buffer, nframes); - g_free(buffer); + alsa_write_silence(ad, nframes); } snd_pcm_drain(ad->pcm); @@ -753,7 +785,7 @@ alsa_drain(struct audio_output *ao) static void alsa_cancel(struct audio_output *ao) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; ad->period_position = 0; @@ -763,20 +795,21 @@ alsa_cancel(struct audio_output *ao) static void alsa_close(struct audio_output *ao) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; snd_pcm_close(ad->pcm); + g_free(ad->silence); } static size_t alsa_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; assert(size % ad->in_frame_size == 0); - chunk = pcm_export(&ad->export, chunk, size, &size); + chunk = pcm_export(&ad->pcm_export, chunk, size, &size); assert(size % ad->out_frame_size == 0); @@ -789,7 +822,7 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size, % ad->period_frames; size_t bytes_written = ret * ad->out_frame_size; - return pcm_export_source_size(&ad->export, + return pcm_export_source_size(&ad->pcm_export, bytes_written); } @@ -803,17 +836,20 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size, } const struct audio_output_plugin alsa_output_plugin = { - .name = "alsa", - .test_default_device = alsa_test_default_device, - .init = alsa_init, - .finish = alsa_finish, - .enable = alsa_output_enable, - .disable = alsa_output_disable, - .open = alsa_open, - .play = alsa_play, - .drain = alsa_drain, - .cancel = alsa_cancel, - .close = alsa_close, - - .mixer_plugin = &alsa_mixer_plugin, + "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/alsa_output_plugin.h b/src/output/AlsaOutputPlugin.hxx index daa1f361..dc7e639a 100644 --- a/src/output/alsa_output_plugin.h +++ b/src/output/AlsaOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_ALSA_OUTPUT_PLUGIN_H -#define MPD_ALSA_OUTPUT_PLUGIN_H +#ifndef MPD_ALSA_OUTPUT_PLUGIN_HXX +#define MPD_ALSA_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin alsa_output_plugin; diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx new file mode 100644 index 00000000..0a00ee2f --- /dev/null +++ b/src/output/HttpdClient.cxx @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "HttpdClient.hxx" +#include "HttpdInternal.hxx" +#include "util/fifo_buffer.h" +#include "Page.hxx" +#include "IcyMetaDataServer.hxx" +#include "SocketError.hxx" + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "httpd_output" + +HttpdClient::~HttpdClient() +{ + if (state == RESPONSE) { + if (current_page != nullptr) + current_page->Unref(); + + for (auto page : pages) + page->Unref(); + } + + if (metadata) + metadata->Unref(); +} + +void +HttpdClient::Close() +{ + httpd->RemoveClient(*this); +} + +void +HttpdClient::LockClose() +{ + const ScopeLock protect(httpd->mutex); + Close(); +} + +void +HttpdClient::BeginResponse() +{ + assert(state != RESPONSE); + + state = RESPONSE; + current_page = nullptr; + + httpd->SendHeader(*this); +} + +/** + * Handle a line of the HTTP request. + */ +bool +HttpdClient::HandleLine(const char *line) +{ + assert(state != RESPONSE); + + if (state == REQUEST) { + if (strncmp(line, "GET /", 5) != 0) { + /* only GET is supported */ + g_warning("malformed request line from client"); + return false; + } + + line = strchr(line + 5, ' '); + if (line == nullptr || strncmp(line + 1, "HTTP/", 5) != 0) { + /* HTTP/0.9 without request headers */ + BeginResponse(); + return true; + } + + /* after the request line, request headers follow */ + state = HEADERS; + return true; + } else { + if (*line == 0) { + /* empty line: request is finished */ + BeginResponse(); + return true; + } + + if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) { + /* Send icy metadata */ + metadata_requested = metadata_supported; + return true; + } + + if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { + /* Send as dlna */ + dlna_streaming_requested = true; + /* metadata is not supported by dlna streaming, so disable it */ + metadata_supported = false; + metadata_requested = false; + return true; + } + + /* expect more request headers */ + return true; + } +} + +/** + * Sends the status line and response headers to the client. + */ +bool +HttpdClient::SendResponse() +{ + char buffer[1024]; + assert(state == RESPONSE); + + if (dlna_streaming_requested) { + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 206 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: 10000\r\n" + "Content-RangeX: 0-1000000/1000000\r\n" + "transferMode.dlna.org: Streaming\r\n" + "Accept-Ranges: bytes\r\n" + "Connection: close\r\n" + "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" + "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" + "\r\n", + httpd->content_type); + + } else if (metadata_requested) { + gchar *metadata_header; + + metadata_header = + icy_server_metadata_header(httpd->name, httpd->genre, + httpd->website, + httpd->content_type, + metaint); + + g_strlcpy(buffer, metadata_header, sizeof(buffer)); + + g_free(metadata_header); + + } else { /* revert to a normal HTTP request */ + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + httpd->content_type); + } + + ssize_t nbytes = SocketMonitor::Write(buffer, strlen(buffer)); + if (gcc_unlikely(nbytes < 0)) { + const SocketErrorMessage msg; + g_warning("failed to write to client: %s", (const char *)msg); + Close(); + return false; + } + + return true; +} + +HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop, + bool _metadata_supported) + :BufferedSocket(_fd, _loop), + httpd(_httpd), + state(REQUEST), + dlna_streaming_requested(false), + metadata_supported(_metadata_supported), + metadata_requested(false), metadata_sent(true), + metaint(8192), /*TODO: just a std value */ + metadata(nullptr), + metadata_current_position(0), metadata_fill(0) +{ +} + +size_t +HttpdClient::GetQueueSize() const +{ + if (state != RESPONSE) + return 0; + + size_t size = 0; + for (auto page : pages) + size += page->size; + return size; +} + +void +HttpdClient::CancelQueue() +{ + if (state != RESPONSE) + return; + + for (auto page : pages) + page->Unref(); + pages.clear(); + + if (current_page == nullptr) + CancelWrite(); +} + +ssize_t +HttpdClient::TryWritePage(const Page &page, size_t position) +{ + assert(position < page.size); + + return Write(page.data + position, page.size - position); +} + +ssize_t +HttpdClient::TryWritePageN(const Page &page, size_t position, ssize_t n) +{ + return n >= 0 + ? Write(page.data + position, n) + : TryWritePage(page, position); +} + +ssize_t +HttpdClient::GetBytesTillMetaData() const +{ + if (metadata_requested && + current_page->size - current_position > metaint - metadata_fill) + return metaint - metadata_fill; + + return -1; +} + +inline bool +HttpdClient::TryWrite() +{ + const ScopeLock protect(httpd->mutex); + + assert(state == RESPONSE); + + if (current_page == nullptr) { + if (pages.empty()) { + /* another thread has removed the event source + while this thread was waiting for + httpd->mutex */ + CancelWrite(); + return true; + } + + current_page = pages.front(); + pages.pop_front(); + current_position = 0; + } + + const ssize_t bytes_to_write = GetBytesTillMetaData(); + if (bytes_to_write == 0) { + if (!metadata_sent) { + ssize_t nbytes = TryWritePage(*metadata, + metadata_current_position); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + g_warning("failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + metadata_current_position += nbytes; + + if (metadata->size - metadata_current_position == 0) { + metadata_fill = 0; + metadata_current_position = 0; + metadata_sent = true; + } + } else { + guchar empty_data = 0; + + ssize_t nbytes = Write(&empty_data, 1); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + g_warning("failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + metadata_fill = 0; + metadata_current_position = 0; + } + } else { + ssize_t nbytes = + TryWritePageN(*current_page, current_position, + bytes_to_write); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + g_warning("failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + current_position += nbytes; + assert(current_position <= current_page->size); + + if (metadata_requested) + metadata_fill += nbytes; + + if (current_position >= current_page->size) { + current_page->Unref(); + current_page = nullptr; + + if (pages.empty()) + /* all pages are sent: remove the + event source */ + CancelWrite(); + } + } + + return true; +} + +void +HttpdClient::PushPage(Page *page) +{ + if (state != RESPONSE) + /* the client is still writing the HTTP request */ + return; + + page->Ref(); + pages.push_back(page); + + ScheduleWrite(); +} + +void +HttpdClient::PushMetaData(Page *page) +{ + if (metadata) { + metadata->Unref(); + metadata = nullptr; + } + + g_return_if_fail (page); + + page->Ref(); + metadata = page; + metadata_sent = false; +} + +bool +HttpdClient::OnSocketReady(unsigned flags) +{ + if (!BufferedSocket::OnSocketReady(flags)) + return false; + + if (flags & WRITE) + if (!TryWrite()) + return false; + + return true; +} + +BufferedSocket::InputResult +HttpdClient::OnSocketInput(const void *data, size_t length) +{ + if (state == RESPONSE) { + g_warning("unexpected input from client"); + LockClose(); + return InputResult::CLOSED; + } + + const char *line = (const char *)data; + const char *newline = (const char *)memchr(line, '\n', length); + if (newline == nullptr) + return InputResult::MORE; + + ConsumeInput(newline + 1 - line); + + if (newline > line && newline[-1] == '\r') + --newline; + + /* terminate the string at the end of the line; the const_cast + is a dirty hack */ + *const_cast<char *>(newline) = 0; + + if (!HandleLine(line)) { + assert(state == RESPONSE); + LockClose(); + return InputResult::CLOSED; + } + + if (state == RESPONSE && !SendResponse()) + return InputResult::CLOSED; + + return InputResult::AGAIN; +} + +void +HttpdClient::OnSocketError(GError *error) +{ + g_warning("error on HTTP client: %s", error->message); + g_error_free(error); +} + +void +HttpdClient::OnSocketClosed() +{ + LockClose(); +} diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx new file mode 100644 index 00000000..46196d2e --- /dev/null +++ b/src/output/HttpdClient.hxx @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_HTTPD_CLIENT_HXX +#define MPD_OUTPUT_HTTPD_CLIENT_HXX + +#include "event/BufferedSocket.hxx" +#include "gcc.h" + +#include <list> + +#include <stddef.h> + +struct HttpdOutput; +class Page; + +class HttpdClient final : public BufferedSocket { + /** + * The httpd output object this client is connected to. + */ + HttpdOutput *const httpd; + + /** + * The current state of the client. + */ + enum { + /** reading the request line */ + REQUEST, + + /** reading the request headers */ + HEADERS, + + /** sending the HTTP response */ + RESPONSE, + } state; + + /** + * A queue of #Page objects to be sent to the client. + */ + std::list<Page *> pages; + + /** + * The #page which is currently being sent to the client. + */ + Page *current_page; + + /** + * The amount of bytes which were already sent from + * #current_page. + */ + size_t current_position; + + /** + * If DLNA streaming was an option. + */ + bool dlna_streaming_requested; + + /* ICY */ + + /** + * Do we support sending Icy-Metadata to the client? This is + * disabled if the httpd audio output uses encoder tags. + */ + bool metadata_supported; + + /** + * If we should sent icy metadata. + */ + bool metadata_requested; + + /** + * If the current metadata was already sent to the client. + */ + bool metadata_sent; + + /** + * The amount of streaming data between each metadata block + */ + guint metaint; + + /** + * The metadata as #Page which is currently being sent to the client. + */ + Page *metadata; + + /* + * The amount of bytes which were already sent from the metadata. + */ + size_t metadata_current_position; + + /** + * The amount of streaming data sent to the client + * since the last icy information was sent. + */ + guint metadata_fill; + +public: + /** + * @param httpd the HTTP output device + * @param fd the socket file descriptor + */ + HttpdClient(HttpdOutput *httpd, int _fd, EventLoop &_loop, + bool _metadata_supported); + + /** + * Note: this does not remove the client from the + * #HttpdOutput object. + */ + ~HttpdClient(); + + /** + * Frees the client and removes it from the server's client list. + */ + void Close(); + + void LockClose(); + + /** + * Returns the total size of this client's page queue. + */ + gcc_pure + size_t GetQueueSize() const; + + /** + * Clears the page queue. + */ + void CancelQueue(); + + /** + * Handle a line of the HTTP request. + */ + bool HandleLine(const char *line); + + /** + * Switch the client to the "RESPONSE" state. + */ + void BeginResponse(); + + /** + * Sends the status line and response headers to the client. + */ + bool SendResponse(); + + gcc_pure + ssize_t GetBytesTillMetaData() const; + + ssize_t TryWritePage(const Page &page, size_t position); + ssize_t TryWritePageN(const Page &page, size_t position, ssize_t n); + + bool TryWrite(); + + /** + * Appends a page to the client's queue. + */ + void PushPage(Page *page); + + /** + * Sends the passed metadata. + */ + void PushMetaData(Page *page); + +protected: + virtual bool OnSocketReady(unsigned flags) override; + virtual InputResult OnSocketInput(const void *data, + size_t length) override; + virtual void OnSocketError(GError *error) override; + virtual void OnSocketClosed() override; +}; + +#endif diff --git a/src/output/httpd_internal.h b/src/output/HttpdInternal.hxx index 5dcb8ab9..4b526bcd 100644 --- a/src/output/httpd_internal.h +++ b/src/output/HttpdInternal.hxx @@ -27,14 +27,18 @@ #include "output_internal.h" #include "timer.h" +#include "thread/Mutex.hxx" +#include "event/ServerSocket.hxx" -#include <glib.h> +#include <forward_list> -#include <stdbool.h> +struct config_param; +class EventLoop; +class ServerSocket; +class HttpdClient; +class Page; -struct httpd_client; - -struct httpd_output { +struct HttpdOutput final : private ServerSocket { struct audio_output base; /** @@ -65,7 +69,7 @@ struct httpd_output { * This mutex protects the listener socket and the client * list. */ - GMutex *mutex; + mutable Mutex mutex; /** * A #timer object to synchronize this output with the @@ -74,19 +78,14 @@ struct httpd_output { struct timer *timer; /** - * The listener socket. - */ - struct server_socket *server_socket; - - /** * The header page, which is sent to every client on connect. */ - struct page *header; + Page *header; /** * The metadata, which is sent to every client. */ - struct page *metadata; + Page *metadata; /** * The configured name. @@ -105,7 +104,7 @@ struct httpd_output { * A linked list containing all clients which are currently * connected. */ - GList *clients; + std::forward_list<HttpdClient> clients; /** * A temporary buffer for the httpd_output_read_page() @@ -118,21 +117,88 @@ struct httpd_output { * at the same time. */ guint clients_max, clients_cnt; -}; -/** - * Removes a client from the httpd_output.clients linked list. - */ -void -httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client); + HttpdOutput(EventLoop &_loop); + ~HttpdOutput(); -/** - * Sends the encoder header to the client. This is called right after - * the response headers have been sent. - */ -void -httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client); + bool Configure(const config_param *param, GError **error_r); + + bool Bind(GError **error_r); + void Unbind(); + + /** + * Caller must lock the mutex. + */ + bool OpenEncoder(struct audio_format *audio_format, + GError **error_r); + + /** + * Caller must lock the mutex. + */ + bool Open(struct audio_format *audio_format, GError **error_r); + + /** + * Caller must lock the mutex. + */ + void Close(); + + /** + * Check whether there is at least one client. + * + * Caller must lock the mutex. + */ + gcc_pure + bool HasClients() const { + return !clients.empty(); + } + + /** + * Check whether there is at least one client. + */ + gcc_pure + bool LockHasClients() const { + const ScopeLock protect(mutex); + return HasClients(); + } + + void AddClient(int fd); + + /** + * Removes a client from the httpd_output.clients linked list. + */ + void RemoveClient(HttpdClient &client); + + /** + * Sends the encoder header to the client. This is called + * right after the response headers have been sent. + */ + void SendHeader(HttpdClient &client) const; + + /** + * Reads data from the encoder (as much as available) and + * returns it as a new #page object. + */ + Page *ReadPage(); + + /** + * Broadcasts a page struct to all clients. + * + * Mutext must not be locked. + */ + void BroadcastPage(Page *page); + + /** + * Broadcasts data from the encoder to all clients. + */ + void BroadcastFromEncoder(); + + bool EncodeAndPlay(const void *chunk, size_t size, GError **error_r); + + void SendTag(const struct tag *tag); + +private: + virtual void OnAccept(int fd, const sockaddr &address, + size_t address_length, int uid) override; +}; #endif diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/HttpdOutputPlugin.cxx new file mode 100644 index 00000000..cb515e65 --- /dev/null +++ b/src/output/HttpdOutputPlugin.cxx @@ -0,0 +1,570 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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_api.h" +#include "encoder_plugin.h" +#include "encoder_list.h" +#include "resolver.h" +#include "Page.hxx" +#include "IcyMetaDataServer.hxx" +#include "fd_util.h" +#include "Main.hxx" + +#include <assert.h> + +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> + +#ifdef HAVE_LIBWRAP +#include <sys/socket.h> /* needed for AF_UNIX */ +#include <tcpd.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "httpd_output" + +/** + * The quark used for GError.domain. + */ +static inline GQuark +httpd_output_quark(void) +{ + return g_quark_from_static_string("httpd_output"); +} + +inline +HttpdOutput::HttpdOutput(EventLoop &_loop) + :ServerSocket(_loop), + encoder(nullptr), unflushed_input(0), + metadata(nullptr) +{ +} + +HttpdOutput::~HttpdOutput() +{ + if (metadata != nullptr) + metadata->Unref(); + + if (encoder != nullptr) + encoder_finish(encoder); + +} + +inline bool +HttpdOutput::Bind(GError **error_r) +{ + open = false; + + const ScopeLock protect(mutex); + return ServerSocket::Open(error_r); +} + +inline void +HttpdOutput::Unbind() +{ + assert(!open); + + const ScopeLock protect(mutex); + ServerSocket::Close(); +} + +inline bool +HttpdOutput::Configure(const config_param *param, GError **error_r) +{ + /* read configuration */ + name = config_get_block_string(param, "name", "Set name in config"); + genre = config_get_block_string(param, "genre", "Set genre in config"); + website = config_get_block_string(param, "website", + "Set website in config"); + + guint port = config_get_block_unsigned(param, "port", 8000); + + const char *encoder_name = + config_get_block_string(param, "encoder", "vorbis"); + const struct encoder_plugin *encoder_plugin = + encoder_plugin_get(encoder_name); + if (encoder_plugin == NULL) { + g_set_error(error_r, httpd_output_quark(), 0, + "No such encoder: %s", encoder_name); + return false; + } + + clients_max = config_get_block_unsigned(param,"max_clients", 0); + + /* set up bind_to_address */ + + const char *bind_to_address = + config_get_block_string(param, "bind_to_address", NULL); + bool success = bind_to_address != NULL && + strcmp(bind_to_address, "any") != 0 + ? AddHost(bind_to_address, port, error_r) + : AddPort(port, error_r); + if (!success) + return false; + + /* initialize encoder */ + + encoder = encoder_init(encoder_plugin, param, error_r); + if (encoder == nullptr) + return false; + + /* determine content type */ + content_type = encoder_get_mime_type(encoder); + if (content_type == nullptr) + content_type = "application/octet-stream"; + + return true; +} + +static struct audio_output * +httpd_output_init(const struct config_param *param, + GError **error_r) +{ + HttpdOutput *httpd = new HttpdOutput(*main_loop); + + if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, + error_r)) { + delete httpd; + return nullptr; + } + + if (!httpd->Configure(param, error_r)) { + ao_base_finish(&httpd->base); + delete httpd; + return nullptr; + } + + return &httpd->base; +} + +#if GCC_CHECK_VERSION(4,6) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + +static inline constexpr HttpdOutput * +Cast(audio_output *ao) +{ + return (HttpdOutput *)((char *)ao - offsetof(HttpdOutput, base)); +} + +#if GCC_CHECK_VERSION(4,6) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +static void +httpd_output_finish(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + ao_base_finish(&httpd->base); + delete httpd; +} + +/** + * Creates a new #HttpdClient object and adds it into the + * HttpdOutput.clients linked list. + */ +inline void +HttpdOutput::AddClient(int fd) +{ + clients.emplace_front(this, fd, GetEventLoop(), + encoder->plugin->tag == NULL); + ++clients_cnt; + + /* pass metadata to client */ + if (metadata != nullptr) + clients.front().PushMetaData(metadata); +} + +void +HttpdOutput::OnAccept(int fd, const sockaddr &address, + size_t address_length, gcc_unused int uid) +{ + /* the listener socket has become readable - a client has + connected */ + +#ifdef HAVE_LIBWRAP + if (address.sa_family != AF_UNIX) { + char *hostaddr = sockaddr_to_string(&address, address_length, NULL); + const char *progname = g_get_prgname(); + + struct request_info req; + request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); + + fromhost(&req); + + if (!hosts_access(&req)) { + /* tcp wrappers says no */ + g_warning("libwrap refused connection (libwrap=%s) from %s", + progname, hostaddr); + g_free(hostaddr); + close_socket(fd); + return; + } + + g_free(hostaddr); + } +#else + (void)address; + (void)address_length; +#endif /* HAVE_WRAP */ + + const ScopeLock protect(mutex); + + if (fd >= 0) { + /* can we allow additional client */ + if (open && (clients_max == 0 || clients_cnt < clients_max)) + AddClient(fd); + else + close_socket(fd); + } else if (fd < 0 && errno != EINTR) { + g_warning("accept() failed: %s", g_strerror(errno)); + } +} + +Page * +HttpdOutput::ReadPage() +{ + if (unflushed_input >= 65536) { + /* we have fed a lot of input into the encoder, but it + didn't give anything back yet - flush now to avoid + buffer underruns */ + encoder_flush(encoder, NULL); + unflushed_input = 0; + } + + size_t size = 0; + do { + size_t nbytes = encoder_read(encoder, + buffer + size, + sizeof(buffer) - size); + if (nbytes == 0) + break; + + unflushed_input = 0; + + size += nbytes; + } while (size < sizeof(buffer)); + + if (size == 0) + return NULL; + + return Page::Copy(buffer, size); +} + +static bool +httpd_output_enable(struct audio_output *ao, GError **error_r) +{ + HttpdOutput *httpd = Cast(ao); + + return httpd->Bind(error_r); +} + +static void +httpd_output_disable(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + httpd->Unbind(); +} + +inline bool +HttpdOutput::OpenEncoder(struct audio_format *audio_format, GError **error) +{ + if (!encoder_open(encoder, audio_format, error)) + return false; + + /* we have to remember the encoder header, i.e. the first + bytes of encoder output after opening it, because it has to + be sent to every new client */ + header = ReadPage(); + + unflushed_input = 0; + + return true; +} + +inline bool +HttpdOutput::Open(struct audio_format *audio_format, GError **error_r) +{ + assert(!open); + assert(clients.empty()); + + /* open the encoder */ + + if (!OpenEncoder(audio_format, error_r)) + return false; + + /* initialize other attributes */ + + clients_cnt = 0; + timer = timer_new(audio_format); + + open = true; + + return true; +} + +static bool +httpd_output_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error) +{ + HttpdOutput *httpd = Cast(ao); + + assert(httpd->clients.empty()); + + const ScopeLock protect(httpd->mutex); + return httpd->Open(audio_format, error); +} + +inline void +HttpdOutput::Close() +{ + assert(open); + + open = false; + + timer_free(timer); + + clients.clear(); + + if (header != NULL) + header->Unref(); + + encoder_close(encoder); +} + +static void +httpd_output_close(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + const ScopeLock protect(httpd->mutex); + httpd->Close(); +} + +void +HttpdOutput::RemoveClient(HttpdClient &client) +{ + assert(clients_cnt > 0); + + for (auto prev = clients.before_begin(), i = std::next(prev);; + prev = i, i = std::next(prev)) { + assert(i != clients.end()); + if (&*i == &client) { + clients.erase_after(prev); + clients_cnt--; + break; + } + } +} + +void +HttpdOutput::SendHeader(HttpdClient &client) const +{ + if (header != NULL) + client.PushPage(header); +} + +static unsigned +httpd_output_delay(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + if (!httpd->LockHasClients() && httpd->base.pause) { + /* if there's no client and this output is paused, + then httpd_output_pause() will not do anything, it + will not fill the buffer and it will not update the + timer; therefore, we reset the timer here */ + timer_reset(httpd->timer); + + /* some arbitrary delay that is long enough to avoid + consuming too much CPU, and short enough to notice + new clients quickly enough */ + return 1000; + } + + return httpd->timer->started + ? timer_delay(httpd->timer) + : 0; +} + +void +HttpdOutput::BroadcastPage(Page *page) +{ + assert(page != NULL); + + const ScopeLock protect(mutex); + for (auto &client : clients) + client.PushPage(page); +} + +void +HttpdOutput::BroadcastFromEncoder() +{ + mutex.lock(); + for (auto &client : clients) { + if (client.GetQueueSize() > 256 * 1024) { + g_debug("client is too slow, flushing its queue"); + client.CancelQueue(); + } + } + mutex.unlock(); + + Page *page; + while ((page = ReadPage()) != nullptr) { + BroadcastPage(page); + page->Unref(); + } +} + +inline bool +HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, GError **error_r) +{ + if (!encoder_write(encoder, chunk, size, error_r)) + return false; + + unflushed_input += size; + + BroadcastFromEncoder(); + return true; +} + +static size_t +httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error_r) +{ + HttpdOutput *httpd = Cast(ao); + + if (httpd->LockHasClients()) { + if (!httpd->EncodeAndPlay(chunk, size, error_r)) + return 0; + } + + if (!httpd->timer->started) + timer_start(httpd->timer); + timer_add(httpd->timer, size); + + return size; +} + +static bool +httpd_output_pause(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + if (httpd->LockHasClients()) { + static const char silence[1020] = { 0 }; + return httpd_output_play(ao, silence, sizeof(silence), + NULL) > 0; + } else { + return true; + } +} + +inline void +HttpdOutput::SendTag(const struct tag *tag) +{ + assert(tag != NULL); + + if (encoder->plugin->tag != NULL) { + /* embed encoder tags */ + + /* flush the current stream, and end it */ + + encoder_pre_tag(encoder, NULL); + BroadcastFromEncoder(); + + /* send the tag to the encoder - which starts a new + stream now */ + + encoder_tag(encoder, tag, NULL); + + /* the first page generated by the encoder will now be + used as the new "header" page, which is sent to all + new clients */ + + Page *page = ReadPage(); + if (page != NULL) { + if (header != NULL) + header->Unref(); + header = page; + BroadcastPage(page); + } + } else { + /* use Icy-Metadata */ + + if (metadata != NULL) + metadata->Unref(); + + static constexpr tag_type types[] = { + TAG_ALBUM, TAG_ARTIST, TAG_TITLE, + TAG_NUM_OF_ITEM_TYPES + }; + + metadata = icy_server_metadata_page(tag, &types[0]); + if (metadata != NULL) { + const ScopeLock protect(mutex); + for (auto &client : clients) + client.PushMetaData(metadata); + } + } +} + +static void +httpd_output_tag(struct audio_output *ao, const struct 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/httpd_output_plugin.h b/src/output/HttpdOutputPlugin.hxx index d0eb1533..c74d2bd4 100644 --- a/src/output/httpd_output_plugin.h +++ b/src/output/HttpdOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_HTTPD_OUTPUT_PLUGIN_H -#define MPD_HTTPD_OUTPUT_PLUGIN_H +#ifndef MPD_HTTPD_OUTPUT_PLUGIN_HXX +#define MPD_HTTPD_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin httpd_output_plugin; diff --git a/src/output/null_output_plugin.c b/src/output/NullOutputPlugin.cxx index 9d7588ff..3596cbdc 100644 --- a/src/output/null_output_plugin.c +++ b/src/output/NullOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "null_output_plugin.h" +#include "NullOutputPlugin.hxx" #include "output_api.h" #include "timer.h" @@ -26,7 +26,7 @@ #include <assert.h> -struct null_data { +struct NullOutput { struct audio_output base; bool sync; @@ -37,7 +37,7 @@ struct null_data { static struct audio_output * null_init(const struct config_param *param, GError **error_r) { - struct null_data *nd = g_new(struct null_data, 1); + NullOutput *nd = g_new(NullOutput, 1); if (!ao_base_init(&nd->base, &null_output_plugin, param, error_r)) { g_free(nd); @@ -52,7 +52,7 @@ null_init(const struct config_param *param, GError **error_r) static void null_finish(struct audio_output *ao) { - struct null_data *nd = (struct null_data *)ao; + NullOutput *nd = (NullOutput *)ao; ao_base_finish(&nd->base); g_free(nd); @@ -62,7 +62,7 @@ static bool null_open(struct audio_output *ao, struct audio_format *audio_format, G_GNUC_UNUSED GError **error) { - struct null_data *nd = (struct null_data *)ao; + NullOutput *nd = (NullOutput *)ao; if (nd->sync) nd->timer = timer_new(audio_format); @@ -73,7 +73,7 @@ null_open(struct audio_output *ao, struct audio_format *audio_format, static void null_close(struct audio_output *ao) { - struct null_data *nd = (struct null_data *)ao; + NullOutput *nd = (NullOutput *)ao; if (nd->sync) timer_free(nd->timer); @@ -82,7 +82,7 @@ null_close(struct audio_output *ao) static unsigned null_delay(struct audio_output *ao) { - struct null_data *nd = (struct null_data *)ao; + NullOutput *nd = (NullOutput *)ao; return nd->sync && nd->timer->started ? timer_delay(nd->timer) @@ -93,7 +93,7 @@ static size_t null_play(struct audio_output *ao, G_GNUC_UNUSED const void *chunk, size_t size, G_GNUC_UNUSED GError **error) { - struct null_data *nd = (struct null_data *)ao; + NullOutput *nd = (NullOutput *)ao; struct timer *timer = nd->timer; if (!nd->sync) @@ -109,7 +109,7 @@ null_play(struct audio_output *ao, G_GNUC_UNUSED const void *chunk, size_t size, static void null_cancel(struct audio_output *ao) { - struct null_data *nd = (struct null_data *)ao; + NullOutput *nd = (NullOutput *)ao; if (!nd->sync) return; @@ -118,12 +118,19 @@ null_cancel(struct audio_output *ao) } const struct audio_output_plugin null_output_plugin = { - .name = "null", - .init = null_init, - .finish = null_finish, - .open = null_open, - .close = null_close, - .delay = null_delay, - .play = null_play, - .cancel = null_cancel, + "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/null_output_plugin.h b/src/output/NullOutputPlugin.hxx index 392bf0aa..a58f1cb1 100644 --- a/src/output/null_output_plugin.h +++ b/src/output/NullOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_NULL_OUTPUT_PLUGIN_H -#define MPD_NULL_OUTPUT_PLUGIN_H +#ifndef MPD_NULL_OUTPUT_PLUGIN_HXX +#define MPD_NULL_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin null_output_plugin; diff --git a/src/output/osx_output_plugin.c b/src/output/OSXOutputPlugin.cxx index cd51fca2..5a04fe1d 100644 --- a/src/output/osx_output_plugin.c +++ b/src/output/OSXOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,11 @@ */ #include "config.h" -#include "osx_output_plugin.h" +#include "OSXOutputPlugin.hxx" #include "output_api.h" -#include "fifo_buffer.h" +#include "util/fifo_buffer.h" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" #include <glib.h> #include <CoreAudio/AudioHardware.h> @@ -30,7 +32,7 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "osx" -struct osx_output { +struct OSXOutput { struct audio_output base; /* configuration settings */ @@ -39,8 +41,8 @@ struct osx_output { const char *device_name; AudioUnit au; - GMutex *mutex; - GCond *condition; + Mutex mutex; + Cond condition; struct fifo_buffer *buffer; }; @@ -63,7 +65,7 @@ osx_output_test_default_device(void) } static void -osx_output_configure(struct osx_output *oo, const struct config_param *param) +osx_output_configure(OSXOutput *oo, const struct config_param *param) { const char *device = config_get_block_string(param, "device", NULL); @@ -85,15 +87,13 @@ osx_output_configure(struct osx_output *oo, const struct config_param *param) static struct audio_output * osx_output_init(const struct config_param *param, GError **error_r) { - struct osx_output *oo = g_new(struct osx_output, 1); + OSXOutput *oo = new OSXOutput(); if (!ao_base_init(&oo->base, &osx_output_plugin, param, error_r)) { - g_free(oo); + delete oo; return NULL; } osx_output_configure(oo, param); - oo->mutex = g_mutex_new(); - oo->condition = g_cond_new(); return &oo->base; } @@ -101,15 +101,13 @@ osx_output_init(const struct config_param *param, GError **error_r) static void osx_output_finish(struct audio_output *ao) { - struct osx_output *od = (struct osx_output *)ao; + OSXOutput *oo = (OSXOutput *)ao; - g_mutex_free(od->mutex); - g_cond_free(od->condition); - g_free(od); + delete oo; } static bool -osx_output_set_device(struct osx_output *oo, GError **error) +osx_output_set_device(OSXOutput *oo, GError **error) { bool ret = true; OSStatus status; @@ -135,7 +133,7 @@ osx_output_set_device(struct osx_output *oo, GError **error) /* what are the available audio device IDs? */ numdevices = size / sizeof(AudioDeviceID); - deviceids = g_malloc(size); + deviceids = new AudioDeviceID[numdevices]; status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &size, deviceids); @@ -192,8 +190,7 @@ osx_output_set_device(struct osx_output *oo, GError **error) (unsigned int) deviceids[i], name); done: - if (deviceids != NULL) - g_free(deviceids); + delete[] deviceids; return ret; } @@ -205,13 +202,13 @@ osx_render(void *vdata, G_GNUC_UNUSED UInt32 in_number_frames, AudioBufferList *buffer_list) { - struct osx_output *od = (struct osx_output *) vdata; + OSXOutput *od = (OSXOutput *) vdata; AudioBuffer *buffer = &buffer_list->mBuffers[0]; size_t buffer_size = buffer->mDataByteSize; assert(od->buffer != NULL); - g_mutex_lock(od->mutex); + od->mutex.lock(); size_t nbytes; const void *src = fifo_buffer_read(od->buffer, &nbytes); @@ -225,8 +222,8 @@ osx_render(void *vdata, } else nbytes = 0; - g_cond_signal(od->condition); - g_mutex_unlock(od->mutex); + od->condition.signal(); + od->mutex.unlock(); buffer->mDataByteSize = nbytes; @@ -242,7 +239,7 @@ osx_render(void *vdata, static bool osx_output_enable(struct audio_output *ao, GError **error_r) { - struct osx_output *oo = (struct osx_output *)ao; + OSXOutput *oo = (OSXOutput *)ao; ComponentDescription desc; desc.componentType = kAudioUnitType_Output; @@ -293,7 +290,7 @@ osx_output_enable(struct audio_output *ao, GError **error_r) static void osx_output_disable(struct audio_output *ao) { - struct osx_output *oo = (struct osx_output *)ao; + OSXOutput *oo = (OSXOutput *)ao; CloseComponent(oo->au); } @@ -301,17 +298,16 @@ osx_output_disable(struct audio_output *ao) static void osx_output_cancel(struct audio_output *ao) { - struct osx_output *od = (struct osx_output *)ao; + OSXOutput *od = (OSXOutput *)ao; - g_mutex_lock(od->mutex); + const ScopeLock protect(od->mutex); fifo_buffer_clear(od->buffer); - g_mutex_unlock(od->mutex); } static void osx_output_close(struct audio_output *ao) { - struct osx_output *od = (struct osx_output *)ao; + OSXOutput *od = (OSXOutput *)ao; AudioOutputUnitStop(od->au); AudioUnitUninitialize(od->au); @@ -322,7 +318,7 @@ osx_output_close(struct audio_output *ao) static bool osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct osx_output *od = (struct osx_output *)ao; + OSXOutput *od = (OSXOutput *)ao; AudioStreamBasicDescription stream_description; stream_description.mSampleRate = audio_format->sample_rate; @@ -397,9 +393,9 @@ static size_t osx_output_play(struct audio_output *ao, const void *chunk, size_t size, G_GNUC_UNUSED GError **error) { - struct osx_output *od = (struct osx_output *)ao; + OSXOutput *od = (OSXOutput *)ao; - g_mutex_lock(od->mutex); + const ScopeLock protect(od->mutex); void *dest; size_t max_length; @@ -410,7 +406,7 @@ osx_output_play(struct audio_output *ao, const void *chunk, size_t size, break; /* wait for some free space in the buffer */ - g_cond_wait(od->condition, od->mutex); + od->condition.wait(od->mutex); } if (size > max_length) @@ -419,20 +415,23 @@ osx_output_play(struct audio_output *ao, const void *chunk, size_t size, memcpy(dest, chunk, size); fifo_buffer_append(od->buffer, size); - g_mutex_unlock(od->mutex); - return size; } const struct audio_output_plugin osx_output_plugin = { - .name = "osx", - .test_default_device = osx_output_test_default_device, - .init = osx_output_init, - .finish = osx_output_finish, - .enable = osx_output_enable, - .disable = osx_output_disable, - .open = osx_output_open, - .close = osx_output_close, - .play = osx_output_play, - .cancel = osx_output_cancel, + "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/osx_output_plugin.h b/src/output/OSXOutputPlugin.hxx index 814702d4..2a417288 100644 --- a/src/output/osx_output_plugin.h +++ b/src/output/OSXOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_OSX_OUTPUT_PLUGIN_H -#define MPD_OSX_OUTPUT_PLUGIN_H +#ifndef MPD_OSX_OUTPUT_PLUGIN_HXX +#define MPD_OSX_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin osx_output_plugin; diff --git a/src/output/oss_output_plugin.c b/src/output/OssOutputPlugin.cxx index e366a453..0111b13f 100644 --- a/src/output/oss_output_plugin.c +++ b/src/output/OssOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,10 @@ */ #include "config.h" -#include "oss_output_plugin.h" +#include "OssOutputPlugin.hxx" #include "output_api.h" -#include "mixer_list.h" +#include "MixerList.hxx" #include "fd_util.h" -#include "glib_compat.h" #include <glib.h> @@ -60,7 +59,7 @@ struct oss_data { struct audio_output base; #ifdef AFMT_S24_PACKED - struct pcm_export_state export; + struct pcm_export_state pcm_export; #endif int fd; @@ -163,11 +162,10 @@ oss_output_test_default_device(void) static struct audio_output * oss_open_default(GError **error) { - int i; int err[G_N_ELEMENTS(default_devices)]; enum oss_stat ret[G_N_ELEMENTS(default_devices)]; - for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { + for (int i = G_N_ELEMENTS(default_devices); --i >= 0; ) { ret[i] = oss_stat_device(default_devices[i], &err[i]); if (ret[i] == OSS_STAT_NO_ERROR) { struct oss_data *od = oss_data_new(); @@ -182,7 +180,7 @@ oss_open_default(GError **error) } } - for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { + for (int i = G_N_ELEMENTS(default_devices); --i >= 0; ) { const char *dev = default_devices[i]; switch(ret[i]) { case OSS_STAT_NO_ERROR: @@ -243,7 +241,7 @@ oss_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) { struct oss_data *od = (struct oss_data *)ao; - pcm_export_init(&od->export); + pcm_export_init(&od->pcm_export); return true; } @@ -252,7 +250,7 @@ oss_output_disable(struct audio_output *ao) { struct oss_data *od = (struct oss_data *)ao; - pcm_export_deinit(&od->export); + pcm_export_deinit(&od->pcm_export); } #endif @@ -504,7 +502,7 @@ oss_probe_sample_format(int fd, enum sample_format sample_format, enum sample_format *sample_format_r, int *oss_format_r, #ifdef AFMT_S24_PACKED - struct pcm_export_state *export, + struct pcm_export_state *pcm_export, #endif GError **error_r) { @@ -539,7 +537,7 @@ oss_probe_sample_format(int fd, enum sample_format sample_format, *oss_format_r = oss_format; #ifdef AFMT_S24_PACKED - pcm_export_open(export, sample_format, 0, false, false, + pcm_export_open(pcm_export, sample_format, 0, false, false, oss_format == AFMT_S24_PACKED, oss_format == AFMT_S24_PACKED && G_BYTE_ORDER != G_LITTLE_ENDIAN); @@ -556,16 +554,16 @@ static bool oss_setup_sample_format(int fd, struct audio_format *audio_format, int *oss_format_r, #ifdef AFMT_S24_PACKED - struct pcm_export_state *export, + struct pcm_export_state *pcm_export, #endif GError **error_r) { enum sample_format mpd_format; enum oss_setup_result result = - oss_probe_sample_format(fd, audio_format->format, + oss_probe_sample_format(fd, sample_format(audio_format->format), &mpd_format, oss_format_r, #ifdef AFMT_S24_PACKED - export, + pcm_export, #endif error_r); switch (result) { @@ -603,7 +601,7 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format, result = oss_probe_sample_format(fd, mpd_format, &mpd_format, oss_format_r, #ifdef AFMT_S24_PACKED - export, + pcm_export, #endif error_r); switch (result) { @@ -635,7 +633,7 @@ oss_setup(struct oss_data *od, struct audio_format *audio_format, oss_setup_sample_rate(od->fd, audio_format, error_r) && oss_setup_sample_format(od->fd, audio_format, &od->oss_format, #ifdef AFMT_S24_PACKED - &od->export, + &od->pcm_export, #endif error_r); } @@ -749,14 +747,14 @@ oss_output_play(struct audio_output *ao, const void *chunk, size_t size, return 0; #ifdef AFMT_S24_PACKED - chunk = pcm_export(&od->export, chunk, size, &size); + chunk = pcm_export(&od->pcm_export, chunk, size, &size); #endif while (true) { ret = write(od->fd, chunk, size); if (ret > 0) { #ifdef AFMT_S24_PACKED - ret = pcm_export_source_size(&od->export, ret); + ret = pcm_export_source_size(&od->pcm_export, ret); #endif return ret; } @@ -771,18 +769,25 @@ oss_output_play(struct audio_output *ao, const void *chunk, size_t size, } const struct audio_output_plugin oss_output_plugin = { - .name = "oss", - .test_default_device = oss_output_test_default_device, - .init = oss_output_init, - .finish = oss_output_finish, + "oss", + oss_output_test_default_device, + oss_output_init, + oss_output_finish, #ifdef AFMT_S24_PACKED - .enable = oss_output_enable, - .disable = oss_output_disable, + oss_output_enable, + oss_output_disable, +#else + nullptr, + nullptr, #endif - .open = oss_output_open, - .close = oss_output_close, - .play = oss_output_play, - .cancel = oss_output_cancel, - - .mixer_plugin = &oss_mixer_plugin, + oss_output_open, + oss_output_close, + nullptr, + nullptr, + oss_output_play, + nullptr, + oss_output_cancel, + nullptr, + + &oss_mixer_plugin, }; diff --git a/src/output/oss_output_plugin.h b/src/output/OssOutputPlugin.hxx index 2aecc2b3..6c5c9530 100644 --- a/src/output/oss_output_plugin.h +++ b/src/output/OssOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_OSS_OUTPUT_PLUGIN_H -#define MPD_OSS_OUTPUT_PLUGIN_H +#ifndef MPD_OSS_OUTPUT_PLUGIN_HXX +#define MPD_OSS_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin oss_output_plugin; diff --git a/src/output/roar_output_plugin.c b/src/output/RoarOutputPlugin.cxx index 1c2c4832..9d6c4591 100644 --- a/src/output/roar_output_plugin.c +++ b/src/output/RoarOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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 * @@ -19,25 +19,19 @@ */ #include "config.h" -#include "roar_output_plugin.h" +#include "RoarOutputPlugin.hxx" #include "output_api.h" -#include "mixer_list.h" -#include "roar_output_plugin.h" +#include "MixerList.hxx" +#include "thread/Mutex.hxx" #include <glib.h> -#include <stdint.h> -#include <unistd.h> -#include <stdlib.h> -#include <string.h> -#include <stdint.h> #include <roaraudio.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "roaraudio" -typedef struct roar -{ +struct RoarOutput { struct audio_output base; roar_vs_t * vss; @@ -47,9 +41,18 @@ typedef struct roar int role; struct roar_connection con; struct roar_audio_info info; - GMutex *lock; + Mutex mutex; volatile bool alive; -} roar_t; + + RoarOutput() + :err(ROAR_ERROR_NONE), + host(nullptr), name(nullptr) {} + + ~RoarOutput() { + g_free(host); + g_free(name); + } +}; static inline GQuark roar_output_quark(void) @@ -58,9 +61,9 @@ roar_output_quark(void) } static int -roar_output_get_volume_locked(struct roar *roar) +roar_output_get_volume_locked(RoarOutput *roar) { - if (roar->vss == NULL || !roar->alive) + if (roar->vss == nullptr || !roar->alive) return -1; float l, r; @@ -72,20 +75,18 @@ roar_output_get_volume_locked(struct roar *roar) } int -roar_output_get_volume(struct roar *roar) +roar_output_get_volume(RoarOutput *roar) { - g_mutex_lock(roar->lock); - int volume = roar_output_get_volume_locked(roar); - g_mutex_unlock(roar->lock); - return volume; + const ScopeLock protect(roar->mutex); + return roar_output_get_volume_locked(roar); } static bool -roar_output_set_volume_locked(struct roar *roar, unsigned volume) +roar_output_set_volume_locked(RoarOutput *roar, unsigned volume) { assert(volume <= 100); - if (roar->vss == NULL || !roar->alive) + if (roar->vss == nullptr || !roar->alive) return false; int error; @@ -96,22 +97,20 @@ roar_output_set_volume_locked(struct roar *roar, unsigned volume) } bool -roar_output_set_volume(struct roar *roar, unsigned volume) +roar_output_set_volume(RoarOutput *roar, unsigned volume) { - g_mutex_lock(roar->lock); - bool success = roar_output_set_volume_locked(roar, volume); - g_mutex_unlock(roar->lock); - return success; + const ScopeLock protect(roar->mutex); + return roar_output_set_volume_locked(roar, volume); } static void -roar_configure(struct roar * self, const struct config_param *param) +roar_configure(RoarOutput *self, const struct config_param *param) { - self->host = config_dup_block_string(param, "server", NULL); + self->host = config_dup_block_string(param, "server", nullptr); self->name = config_dup_block_string(param, "name", "MPD"); const char *role = config_get_block_string(param, "role", "music"); - self->role = role != NULL + self->role = role != nullptr ? roar_str2role(role) : ROAR_ROLE_MUSIC; } @@ -119,15 +118,13 @@ roar_configure(struct roar * self, const struct config_param *param) static struct audio_output * roar_init(const struct config_param *param, GError **error_r) { - struct roar *self = g_new0(struct roar, 1); + RoarOutput *self = new RoarOutput(); if (!ao_base_init(&self->base, &roar_output_plugin, param, error_r)) { - g_free(self); - return NULL; + delete self; + return nullptr; } - self->lock = g_mutex_new(); - self->err = ROAR_ERROR_NONE; roar_configure(self, param); return &self->base; } @@ -135,14 +132,10 @@ roar_init(const struct config_param *param, GError **error_r) static void roar_finish(struct audio_output *ao) { - struct roar *self = (struct roar *)ao; - - g_free(self->host); - g_free(self->name); - g_mutex_free(self->lock); + RoarOutput *self = (RoarOutput *)ao; ao_base_finish(&self->base); - g_free(self); + delete self; } static void @@ -181,24 +174,22 @@ roar_use_audio_format(struct roar_audio_info *info, static bool roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct roar *self = (struct roar *)ao; - g_mutex_lock(self->lock); + RoarOutput *self = (RoarOutput *)ao; + const ScopeLock protect(self->mutex); if (roar_simple_connect(&(self->con), self->host, self->name) < 0) { g_set_error(error, roar_output_quark(), 0, "Failed to connect to Roar server"); - g_mutex_unlock(self->lock); return false; } self->vss = roar_vs_new_from_con(&(self->con), &(self->err)); - if (self->vss == NULL || self->err != ROAR_ERROR_NONE) + if (self->vss == nullptr || self->err != ROAR_ERROR_NONE) { g_set_error(error, roar_output_quark(), 0, "Failed to connect to server"); - g_mutex_unlock(self->lock); return false; } @@ -208,43 +199,41 @@ roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **e &(self->err)) < 0) { g_set_error(error, roar_output_quark(), 0, "Failed to start stream"); - g_mutex_unlock(self->lock); return false; } roar_vs_role(self->vss, self->role, &(self->err)); self->alive = true; - g_mutex_unlock(self->lock); return true; } static void roar_close(struct audio_output *ao) { - struct roar *self = (struct roar *)ao; - g_mutex_lock(self->lock); + RoarOutput *self = (RoarOutput *)ao; + const ScopeLock protect(self->mutex); + self->alive = false; - if (self->vss != NULL) + if (self->vss != nullptr) roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err)); - self->vss = NULL; + self->vss = nullptr; roar_disconnect(&(self->con)); - g_mutex_unlock(self->lock); } static void -roar_cancel_locked(struct roar *self) +roar_cancel_locked(RoarOutput *self) { - if (self->vss == NULL) + if (self->vss == nullptr) return; roar_vs_t *vss = self->vss; - self->vss = NULL; + self->vss = nullptr; roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); self->alive = false; vss = roar_vs_new_from_con(&(self->con), &(self->err)); - if (vss == NULL) + if (vss == nullptr) return; if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY, @@ -262,20 +251,19 @@ roar_cancel_locked(struct roar *self) static void roar_cancel(struct audio_output *ao) { - struct roar *self = (struct roar *)ao; + RoarOutput *self = (RoarOutput *)ao; - g_mutex_lock(self->lock); + const ScopeLock protect(self->mutex); roar_cancel_locked(self); - g_mutex_unlock(self->lock); } static size_t roar_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) { - struct roar *self = (struct roar *)ao; + RoarOutput *self = (RoarOutput *)ao; ssize_t rc; - if (self->vss == NULL) + if (self->vss == nullptr) { g_set_error(error, roar_output_quark(), 0, "Connection is invalid"); return 0; @@ -332,19 +320,20 @@ roar_tag_convert(enum tag_type type, bool *is_uuid) return "HASH"; default: - return NULL; + return nullptr; } } static void roar_send_tag(struct audio_output *ao, const struct tag *meta) { - struct roar *self = (struct roar *)ao; + RoarOutput *self = (RoarOutput *)ao; - if (self->vss == NULL) + if (self->vss == nullptr) return; - g_mutex_lock(self->lock); + const ScopeLock protect(self->mutex); + size_t cnt = 1; struct roar_keyval vals[32]; memset(vals, 0, sizeof(vals)); @@ -361,7 +350,7 @@ roar_send_tag(struct audio_output *ao, const struct tag *meta) { bool is_uuid = false; const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid); - if (key != NULL) + if (key != nullptr) { if (is_uuid) { @@ -383,19 +372,22 @@ roar_send_tag(struct audio_output *ao, const struct tag *meta) for (unsigned i = 0; i < 32; i++) g_free(vals[i].key); - - g_mutex_unlock(self->lock); } const struct audio_output_plugin roar_output_plugin = { - .name = "roar", - .init = roar_init, - .finish = roar_finish, - .open = roar_open, - .play = roar_play, - .cancel = roar_cancel, - .close = roar_close, - .send_tag = roar_send_tag, - - .mixer_plugin = &roar_mixer_plugin + "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/roar_output_plugin.h b/src/output/RoarOutputPlugin.hxx index 78b628cc..faa4b4d5 100644 --- a/src/output/roar_output_plugin.h +++ b/src/output/RoarOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,16 +20,14 @@ #ifndef MPD_ROAR_OUTPUT_PLUGIN_H #define MPD_ROAR_OUTPUT_PLUGIN_H -#include <stdbool.h> - -struct roar; +struct RoarOutput; extern const struct audio_output_plugin roar_output_plugin; int -roar_output_get_volume(struct roar *roar); +roar_output_get_volume(RoarOutput *roar); bool -roar_output_set_volume(struct roar *roar, unsigned volume); +roar_output_set_volume(RoarOutput *roar, unsigned volume); #endif diff --git a/src/output/winmm_output_plugin.c b/src/output/WinmmOutputPlugin.cxx index 4d95834b..b9652fc0 100644 --- a/src/output/winmm_output_plugin.c +++ b/src/output/WinmmOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,26 +18,24 @@ */ #include "config.h" -#include "winmm_output_plugin.h" +#include "WinmmOutputPlugin.hxx" #include "output_api.h" #include "pcm_buffer.h" -#include "mixer_list.h" -#include "winmm_output_plugin.h" +#include "MixerList.hxx" #include <stdlib.h> #include <string.h> -#include <windows.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "winmm_output" -struct winmm_buffer { +struct WinmmBuffer { struct pcm_buffer buffer; WAVEHDR hdr; }; -struct winmm_output { +struct WinmmOutput { struct audio_output base; UINT device_id; @@ -49,7 +47,7 @@ struct winmm_output { */ HANDLE event; - struct winmm_buffer buffers[8]; + WinmmBuffer buffers[8]; unsigned next_buffer; }; @@ -63,7 +61,7 @@ winmm_output_quark(void) } HWAVEOUT -winmm_output_get_handle(struct winmm_output* output) +winmm_output_get_handle(WinmmOutput *output) { return output->handle; } @@ -78,7 +76,7 @@ static bool get_device_id(const char *device_name, UINT *device_id, GError **error_r) { /* if device is not specified use wave mapper */ - if (device_name == NULL) { + if (device_name == nullptr) { *device_id = WAVE_MAPPER; return true; } @@ -118,17 +116,17 @@ fail: static struct audio_output * winmm_output_init(const struct config_param *param, GError **error_r) { - struct winmm_output *wo = g_new(struct winmm_output, 1); + WinmmOutput *wo = new WinmmOutput(); if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error_r)) { g_free(wo); - return NULL; + return nullptr; } - const char *device = config_get_block_string(param, "device", NULL); + const char *device = config_get_block_string(param, "device", nullptr); if (!get_device_id(device, &wo->device_id, error_r)) { ao_base_finish(&wo->base); g_free(wo); - return NULL; + return nullptr; } return &wo->base; @@ -137,20 +135,20 @@ winmm_output_init(const struct config_param *param, GError **error_r) static void winmm_output_finish(struct audio_output *ao) { - struct winmm_output *wo = (struct winmm_output *)ao; + WinmmOutput *wo = (WinmmOutput *)ao; ao_base_finish(&wo->base); - g_free(wo); + delete wo; } static bool winmm_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error_r) { - struct winmm_output *wo = (struct winmm_output *)ao; + WinmmOutput *wo = (WinmmOutput *)ao; - wo->event = CreateEvent(NULL, false, false, NULL); - if (wo->event == NULL) { + wo->event = CreateEvent(nullptr, false, false, nullptr); + if (wo->event == nullptr) { g_set_error(error_r, winmm_output_quark(), 0, "CreateEvent() failed"); return false; @@ -204,7 +202,7 @@ winmm_output_open(struct audio_output *ao, struct audio_format *audio_format, static void winmm_output_close(struct audio_output *ao) { - struct winmm_output *wo = (struct winmm_output *)ao; + WinmmOutput *wo = (WinmmOutput *)ao; for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) pcm_buffer_deinit(&wo->buffers[i].buffer); @@ -218,17 +216,17 @@ winmm_output_close(struct audio_output *ao) * Copy data into a buffer, and prepare the wave header. */ static bool -winmm_set_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, +winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer, const void *data, size_t size, GError **error_r) { void *dest = pcm_buffer_get(&buffer->buffer, size); - assert(dest != NULL); + assert(dest != nullptr); memcpy(dest, data, size); memset(&buffer->hdr, 0, sizeof(buffer->hdr)); - buffer->hdr.lpData = dest; + buffer->hdr.lpData = (LPSTR)dest; buffer->hdr.dwBufferLength = size; MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr, @@ -246,7 +244,7 @@ winmm_set_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, * Wait until the buffer is finished. */ static bool -winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, +winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer, GError **error_r) { if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE) @@ -273,10 +271,10 @@ winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, static size_t winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error_r) { - struct winmm_output *wo = (struct winmm_output *)ao; + WinmmOutput *wo = (WinmmOutput *)ao; /* get the next buffer from the ring and prepare it */ - struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer]; + WinmmBuffer *buffer = &wo->buffers[wo->next_buffer]; if (!winmm_drain_buffer(wo, buffer, error_r) || !winmm_set_buffer(wo, buffer, chunk, size, error_r)) return 0; @@ -300,7 +298,7 @@ winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, GErro } static bool -winmm_drain_all_buffers(struct winmm_output *wo, GError **error_r) +winmm_drain_all_buffers(WinmmOutput *wo, GError **error_r) { for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(wo->buffers); ++i) if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r)) @@ -314,12 +312,12 @@ winmm_drain_all_buffers(struct winmm_output *wo, GError **error_r) } static void -winmm_stop(struct winmm_output *wo) +winmm_stop(WinmmOutput *wo) { waveOutReset(wo->handle); for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) { - struct winmm_buffer *buffer = &wo->buffers[i]; + WinmmBuffer *buffer = &wo->buffers[i]; waveOutUnprepareHeader(wo->handle, &buffer->hdr, sizeof(buffer->hdr)); } @@ -328,29 +326,34 @@ winmm_stop(struct winmm_output *wo) static void winmm_output_drain(struct audio_output *ao) { - struct winmm_output *wo = (struct winmm_output *)ao; + WinmmOutput *wo = (WinmmOutput *)ao; - if (!winmm_drain_all_buffers(wo, NULL)) + if (!winmm_drain_all_buffers(wo, nullptr)) winmm_stop(wo); } static void winmm_output_cancel(struct audio_output *ao) { - struct winmm_output *wo = (struct winmm_output *)ao; + WinmmOutput *wo = (WinmmOutput *)ao; winmm_stop(wo); } const struct audio_output_plugin winmm_output_plugin = { - .name = "winmm", - .test_default_device = winmm_output_test_default_device, - .init = winmm_output_init, - .finish = winmm_output_finish, - .open = winmm_output_open, - .close = winmm_output_close, - .play = winmm_output_play, - .drain = winmm_output_drain, - .cancel = winmm_output_cancel, - .mixer_plugin = &winmm_mixer_plugin, + "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/winmm_output_plugin.h b/src/output/WinmmOutputPlugin.hxx index 0605530e..e8688782 100644 --- a/src/output/winmm_output_plugin.h +++ b/src/output/WinmmOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,20 +17,25 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_WINMM_OUTPUT_PLUGIN_H -#define MPD_WINMM_OUTPUT_PLUGIN_H +#ifndef MPD_WINMM_OUTPUT_PLUGIN_HXX +#define MPD_WINMM_OUTPUT_PLUGIN_HXX #include "check.h" #ifdef ENABLE_WINMM_OUTPUT +#include "gcc.h" + #include <windows.h> +#include <mmsystem.h> -struct winmm_output; +struct WinmmOutput; extern const struct audio_output_plugin winmm_output_plugin; -HWAVEOUT winmm_output_get_handle(struct winmm_output*); +gcc_pure +HWAVEOUT +winmm_output_get_handle(WinmmOutput *); #endif diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c index 022be0b4..3d6171ae 100644 --- a/src/output/fifo_output_plugin.c +++ b/src/output/fifo_output_plugin.c @@ -20,7 +20,6 @@ #include "config.h" #include "fifo_output_plugin.h" #include "output_api.h" -#include "utils.h" #include "timer.h" #include "fd_util.h" #include "open.h" diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c deleted file mode 100644 index 72de9045..00000000 --- a/src/output/httpd_client.c +++ /dev/null @@ -1,764 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "httpd_client.h" -#include "httpd_internal.h" -#include "fifo_buffer.h" -#include "page.h" -#include "icy_server.h" -#include "glib_socket.h" - -#include <stdbool.h> -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "httpd_output" - -struct httpd_client { - /** - * The httpd output object this client is connected to. - */ - struct httpd_output *httpd; - - /** - * The TCP socket. - */ - GIOChannel *channel; - - /** - * The GLib main loop source id for reading from the socket, - * and to detect errors. - */ - guint read_source_id; - - /** - * The GLib main loop source id for writing to the socket. If - * 0, then there is no event source currently (because there - * are no queued pages). - */ - guint write_source_id; - - /** - * For buffered reading. This pointer is only valid while the - * HTTP request is read. - */ - struct fifo_buffer *input; - - /** - * The current state of the client. - */ - enum { - /** reading the request line */ - REQUEST, - - /** reading the request headers */ - HEADERS, - - /** sending the HTTP response */ - RESPONSE, - } state; - - /** - * A queue of #page objects to be sent to the client. - */ - GQueue *pages; - - /** - * The #page which is currently being sent to the client. - */ - struct page *current_page; - - /** - * The amount of bytes which were already sent from - * #current_page. - */ - size_t current_position; - - /** - * If DLNA streaming was an option. - */ - bool dlna_streaming_requested; - - /* ICY */ - - /** - * Do we support sending Icy-Metadata to the client? This is - * disabled if the httpd audio output uses encoder tags. - */ - bool metadata_supported; - - /** - * If we should sent icy metadata. - */ - bool metadata_requested; - - /** - * If the current metadata was already sent to the client. - */ - bool metadata_sent; - - /** - * The amount of streaming data between each metadata block - */ - guint metaint; - - /** - * The metadata as #page which is currently being sent to the client. - */ - struct page *metadata; - - /* - * The amount of bytes which were already sent from the metadata. - */ - size_t metadata_current_position; - - /** - * The amount of streaming data sent to the client - * since the last icy information was sent. - */ - guint metadata_fill; -}; - -static void -httpd_client_unref_page(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct page *page = data; - - page_unref(page); -} - -void -httpd_client_free(struct httpd_client *client) -{ - assert(client != NULL); - - if (client->state == RESPONSE) { - if (client->write_source_id != 0) - g_source_remove(client->write_source_id); - - if (client->current_page != NULL) - page_unref(client->current_page); - - g_queue_foreach(client->pages, httpd_client_unref_page, NULL); - g_queue_free(client->pages); - } else - fifo_buffer_free(client->input); - - if (client->metadata) - page_unref (client->metadata); - - g_source_remove(client->read_source_id); - g_io_channel_unref(client->channel); - g_free(client); -} - -/** - * Frees the client and removes it from the server's client list. - */ -static void -httpd_client_close(struct httpd_client *client) -{ - assert(client != NULL); - - httpd_output_remove_client(client->httpd, client); - httpd_client_free(client); -} - -/** - * Switch the client to the "RESPONSE" state. - */ -static void -httpd_client_begin_response(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - client->state = RESPONSE; - client->write_source_id = 0; - client->pages = g_queue_new(); - client->current_page = NULL; - - httpd_output_send_header(client->httpd, client); -} - -/** - * Handle a line of the HTTP request. - */ -static bool -httpd_client_handle_line(struct httpd_client *client, const char *line) -{ - assert(client->state != RESPONSE); - - if (client->state == REQUEST) { - if (strncmp(line, "GET /", 5) != 0) { - /* only GET is supported */ - g_warning("malformed request line from client"); - return false; - } - - line = strchr(line + 5, ' '); - if (line == NULL || strncmp(line + 1, "HTTP/", 5) != 0) { - /* HTTP/0.9 without request headers */ - httpd_client_begin_response(client); - return true; - } - - /* after the request line, request headers follow */ - client->state = HEADERS; - return true; - } else { - if (*line == 0) { - /* empty line: request is finished */ - httpd_client_begin_response(client); - return true; - } - - if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) { - /* Send icy metadata */ - client->metadata_requested = - client->metadata_supported; - return true; - } - - if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { - /* Send as dlna */ - client->dlna_streaming_requested = true; - /* metadata is not supported by dlna streaming, so disable it */ - client->metadata_supported = false; - client->metadata_requested = false; - return true; - } - - /* expect more request headers */ - return true; - } -} - -/** - * Check if a complete line of input is present in the input buffer, - * and duplicates it. It is removed from the input buffer. The - * return value has to be freed with g_free(). - */ -static char * -httpd_client_read_line(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - const char *p, *newline; - size_t length; - char *line; - - p = fifo_buffer_read(client->input, &length); - if (p == NULL) - /* empty input buffer */ - return NULL; - - newline = memchr(p, '\n', length); - if (newline == NULL) - /* incomplete line */ - return NULL; - - line = g_strndup(p, newline - p); - fifo_buffer_consume(client->input, newline - p + 1); - - /* remove trailing whitespace (e.g. '\r') */ - return g_strchomp(line); -} - -/** - * Sends the status line and response headers to the client. - */ -static bool -httpd_client_send_response(struct httpd_client *client) -{ - char buffer[1024]; - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->state == RESPONSE); - - if (client->dlna_streaming_requested) { - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 206 OK\r\n" - "Content-Type: %s\r\n" - "Content-Length: 10000\r\n" - "Content-RangeX: 0-1000000/1000000\r\n" - "transferMode.dlna.org: Streaming\r\n" - "Accept-Ranges: bytes\r\n" - "Connection: close\r\n" - "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" - "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" - "\r\n", - client->httpd->content_type); - - } else if (client->metadata_requested) { - gchar *metadata_header; - - metadata_header = icy_server_metadata_header( - client->httpd->name, - client->httpd->genre, - client->httpd->website, - client->httpd->content_type, - client->metaint); - - g_strlcpy(buffer, metadata_header, sizeof(buffer)); - - g_free(metadata_header); - - } else { /* revert to a normal HTTP request */ - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 200 OK\r\n" - "Content-Type: %s\r\n" - "Connection: close\r\n" - "Pragma: no-cache\r\n" - "Cache-Control: no-cache, no-store\r\n" - "\r\n", - client->httpd->content_type); - } - - status = g_io_channel_write_chars(client->channel, - buffer, strlen(buffer), - &bytes_written, &error); - - switch (status) { - case G_IO_STATUS_NORMAL: - case G_IO_STATUS_AGAIN: - return true; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - httpd_client_close(client); - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - g_warning("failed to write to client: %s", error->message); - g_error_free(error); - - httpd_client_close(client); - return false; - } - - /* unreachable */ - httpd_client_close(client); - return false; -} - -/** - * Data has been received from the client and it is appended to the - * input buffer. - */ -static bool -httpd_client_received(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - char *line; - bool success; - - while ((line = httpd_client_read_line(client)) != NULL) { - success = httpd_client_handle_line(client, line); - g_free(line); - if (!success) { - assert(client->state != RESPONSE); - return false; - } - - if (client->state == RESPONSE) { - if (!fifo_buffer_is_empty(client->input)) { - g_warning("unexpected input from client"); - return false; - } - - fifo_buffer_free(client->input); - - return httpd_client_send_response(client); - } - } - - return true; -} - -static bool -httpd_client_read(struct httpd_client *client) -{ - char *p; - size_t max_length; - GError *error = NULL; - GIOStatus status; - gsize bytes_read; - - if (client->state == RESPONSE) { - /* the client has already sent the request, and he - must not send more */ - char buffer[1]; - - status = g_io_channel_read_chars(client->channel, buffer, - sizeof(buffer), &bytes_read, - NULL); - if (status == G_IO_STATUS_NORMAL) - g_warning("unexpected input from client"); - - return false; - } - - p = fifo_buffer_write(client->input, &max_length); - if (p == NULL) { - g_warning("buffer overflow"); - return false; - } - - status = g_io_channel_read_chars(client->channel, p, max_length, - &bytes_read, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - fifo_buffer_append(client->input, bytes_read); - return httpd_client_received(client); - - case G_IO_STATUS_AGAIN: - /* try again later, after select() */ - return true; - - case G_IO_STATUS_EOF: - /* peer disconnected */ - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - g_warning("failed to read from client: %s", - error->message); - g_error_free(error); - return false; - } - - /* unreachable */ - return false; -} - -static gboolean -httpd_client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct httpd_client *client = data; - struct httpd_output *httpd = client->httpd; - bool ret; - - g_mutex_lock(httpd->mutex); - - if (condition == G_IO_IN && httpd_client_read(client)) { - ret = true; - } else { - httpd_client_close(client); - ret = false; - } - - g_mutex_unlock(httpd->mutex); - - return ret; -} - -struct httpd_client * -httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported) -{ - struct httpd_client *client = g_new(struct httpd_client, 1); - - client->httpd = httpd; - - client->channel = g_io_channel_new_socket(fd); - - /* GLib is responsible for closing the file descriptor */ - g_io_channel_set_close_on_unref(client->channel, true); - /* NULL encoding means the stream is binary safe */ - g_io_channel_set_encoding(client->channel, NULL, NULL); - /* we prefer to do buffering */ - g_io_channel_set_buffered(client->channel, false); - - client->read_source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - httpd_client_in_event, client); - - client->input = fifo_buffer_new(4096); - client->state = REQUEST; - - client->dlna_streaming_requested = false; - client->metadata_supported = metadata_supported; - client->metadata_requested = false; - client->metadata_sent = true; - client->metaint = 8192; /*TODO: just a std value */ - client->metadata = NULL; - client->metadata_current_position = 0; - client->metadata_fill = 0; - - return client; -} - -static void -httpd_client_add_page_size(gpointer data, gpointer user_data) -{ - struct page *page = data; - size_t *size = user_data; - - *size += page->size; -} - -size_t -httpd_client_queue_size(const struct httpd_client *client) -{ - size_t size = 0; - - if (client->state != RESPONSE) - return 0; - - g_queue_foreach(client->pages, httpd_client_add_page_size, &size); - return size; -} - -void -httpd_client_cancel(struct httpd_client *client) -{ - if (client->state != RESPONSE) - return; - - g_queue_foreach(client->pages, httpd_client_unref_page, NULL); - g_queue_clear(client->pages); - - if (client->write_source_id != 0 && client->current_page == NULL) { - g_source_remove(client->write_source_id); - client->write_source_id = 0; - } -} - -static GIOStatus -write_page_to_channel(GIOChannel *channel, - const struct page *page, size_t position, - gsize *bytes_written_r, GError **error) -{ - assert(channel != NULL); - assert(page != NULL); - assert(position < page->size); - - return g_io_channel_write_chars(channel, - (const gchar*)page->data + position, - page->size - position, - bytes_written_r, error); -} - -static GIOStatus -write_n_bytes_to_channel(GIOChannel *channel, const struct page *page, - size_t position, gint n, - gsize *bytes_written_r, GError **error) -{ - GIOStatus status; - - assert(channel != NULL); - assert(page != NULL); - assert(position < page->size); - - if (n == -1) { - status = write_page_to_channel (channel, page, position, - bytes_written_r, error); - } else { - status = g_io_channel_write_chars(channel, - (const gchar*)page->data + position, - n, bytes_written_r, error); - } - - return status; -} - -static gint -bytes_left_till_metadata (struct httpd_client *client) -{ - assert(client != NULL); - - if (client->metadata_requested && - client->current_page->size - client->current_position - > client->metaint - client->metadata_fill) - return client->metaint - client->metadata_fill; - - return -1; -} - -static gboolean -httpd_client_out_event(GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, gpointer data) -{ - struct httpd_client *client = data; - struct httpd_output *httpd = client->httpd; - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - gint bytes_to_write; - - g_mutex_lock(httpd->mutex); - - assert(condition == G_IO_OUT); - assert(client->state == RESPONSE); - - if (client->write_source_id == 0) { - /* another thread has removed the event source while - this thread was waiting for httpd->mutex */ - g_mutex_unlock(httpd->mutex); - return false; - } - - if (client->current_page == NULL) { - client->current_page = g_queue_pop_head(client->pages); - client->current_position = 0; - } - - bytes_to_write = bytes_left_till_metadata(client); - - if (bytes_to_write == 0) { - gint metadata_to_write; - - metadata_to_write = client->metadata_current_position; - - if (!client->metadata_sent) { - status = write_page_to_channel(source, - client->metadata, - metadata_to_write, - &bytes_written, &error); - - client->metadata_current_position += bytes_written; - - if (client->metadata->size - - client->metadata_current_position == 0) { - client->metadata_fill = 0; - client->metadata_current_position = 0; - client->metadata_sent = true; - } - } else { - struct page *empty_meta; - guchar empty_data = 0; - - empty_meta = page_new_copy(&empty_data, 1); - - status = write_page_to_channel(source, - empty_meta, - metadata_to_write, - &bytes_written, &error); - - client->metadata_current_position += bytes_written; - - if (empty_meta->size - - client->metadata_current_position == 0) { - client->metadata_fill = 0; - client->metadata_current_position = 0; - } - } - - bytes_written = 0; - } else { - status = write_n_bytes_to_channel(source, client->current_page, - client->current_position, bytes_to_write, - &bytes_written, &error); - } - - switch (status) { - case G_IO_STATUS_NORMAL: - client->current_position += bytes_written; - assert(client->current_position <= client->current_page->size); - - if (client->metadata_requested) - client->metadata_fill += bytes_written; - - if (client->current_position >= client->current_page->size) { - page_unref(client->current_page); - client->current_page = NULL; - - if (g_queue_is_empty(client->pages)) { - /* all pages are sent: remove the - event source */ - client->write_source_id = 0; - - g_mutex_unlock(httpd->mutex); - return false; - } - } - - g_mutex_unlock(httpd->mutex); - return true; - - case G_IO_STATUS_AGAIN: - g_mutex_unlock(httpd->mutex); - return true; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - g_warning("failed to write to client: %s", error->message); - g_error_free(error); - - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; - } - - /* unreachable */ - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; -} - -void -httpd_client_send(struct httpd_client *client, struct page *page) -{ - if (client->state != RESPONSE) - /* the client is still writing the HTTP request */ - return; - - page_ref(page); - g_queue_push_tail(client->pages, page); - - if (client->write_source_id == 0) - client->write_source_id = - g_io_add_watch(client->channel, G_IO_OUT, - httpd_client_out_event, client); -} - -void -httpd_client_send_metadata(struct httpd_client *client, struct page *page) -{ - if (client->metadata) { - page_unref(client->metadata); - client->metadata = NULL; - } - - g_return_if_fail (page); - - page_ref(page); - client->metadata = page; - client->metadata_sent = false; -} diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h deleted file mode 100644 index 739163f4..00000000 --- a/src/output/httpd_client.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_HTTPD_CLIENT_H -#define MPD_OUTPUT_HTTPD_CLIENT_H - -#include <glib.h> - -#include <stdbool.h> - -struct httpd_client; -struct httpd_output; -struct page; - -/** - * Creates a new #httpd_client object - * - * @param httpd the HTTP output device - * @param fd the socket file descriptor - */ -struct httpd_client * -httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported); - -/** - * Frees memory and resources allocated by the #httpd_client object. - * This does not remove it from the #httpd_output object. - */ -void -httpd_client_free(struct httpd_client *client); - -/** - * Returns the total size of this client's page queue. - */ -size_t -httpd_client_queue_size(const struct httpd_client *client); - -/** - * Clears the page queue. - */ -void -httpd_client_cancel(struct httpd_client *client); - -/** - * Appends a page to the client's queue. - */ -void -httpd_client_send(struct httpd_client *client, struct page *page); - -/** - * Sends the passed metadata. - */ -void -httpd_client_send_metadata(struct httpd_client *client, struct page *page); - -#endif diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c deleted file mode 100644 index 1d730df7..00000000 --- a/src/output/httpd_output_plugin.c +++ /dev/null @@ -1,623 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "httpd_output_plugin.h" -#include "httpd_internal.h" -#include "httpd_client.h" -#include "output_api.h" -#include "encoder_plugin.h" -#include "encoder_list.h" -#include "resolver.h" -#include "page.h" -#include "icy_server.h" -#include "fd_util.h" -#include "server_socket.h" - -#include <assert.h> - -#include <sys/types.h> -#include <unistd.h> -#include <errno.h> - -#ifdef HAVE_LIBWRAP -#include <sys/socket.h> /* needed for AF_UNIX */ -#include <tcpd.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "httpd_output" - -/** - * The quark used for GError.domain. - */ -static inline GQuark -httpd_output_quark(void) -{ - return g_quark_from_static_string("httpd_output"); -} - -/** - * Check whether there is at least one client. - * - * Caller must lock the mutex. - */ -G_GNUC_PURE -static bool -httpd_output_has_clients(const struct httpd_output *httpd) -{ - return httpd->clients != NULL; -} - -/** - * Check whether there is at least one client. - */ -G_GNUC_PURE -static bool -httpd_output_lock_has_clients(const struct httpd_output *httpd) -{ - g_mutex_lock(httpd->mutex); - bool result = httpd_output_has_clients(httpd); - g_mutex_unlock(httpd->mutex); - return result; -} - -static void -httpd_listen_in_event(int fd, const struct sockaddr *address, - size_t address_length, int uid, void *ctx); - -static bool -httpd_output_bind(struct httpd_output *httpd, GError **error_r) -{ - httpd->open = false; - - g_mutex_lock(httpd->mutex); - bool success = server_socket_open(httpd->server_socket, error_r); - g_mutex_unlock(httpd->mutex); - - return success; -} - -static void -httpd_output_unbind(struct httpd_output *httpd) -{ - assert(!httpd->open); - - g_mutex_lock(httpd->mutex); - server_socket_close(httpd->server_socket); - g_mutex_unlock(httpd->mutex); -} - -static struct audio_output * -httpd_output_init(const struct config_param *param, - GError **error) -{ - struct httpd_output *httpd = g_new(struct httpd_output, 1); - if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) { - g_free(httpd); - return NULL; - } - - /* read configuration */ - httpd->name = - config_get_block_string(param, "name", "Set name in config"); - httpd->genre = - config_get_block_string(param, "genre", "Set genre in config"); - httpd->website = - config_get_block_string(param, "website", "Set website in config"); - - guint port = config_get_block_unsigned(param, "port", 8000); - - const char *encoder_name = - config_get_block_string(param, "encoder", "vorbis"); - const struct encoder_plugin *encoder_plugin = - encoder_plugin_get(encoder_name); - if (encoder_plugin == NULL) { - g_set_error(error, httpd_output_quark(), 0, - "No such encoder: %s", encoder_name); - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0); - - /* set up bind_to_address */ - - httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd); - - const char *bind_to_address = - config_get_block_string(param, "bind_to_address", NULL); - bool success = bind_to_address != NULL && - strcmp(bind_to_address, "any") != 0 - ? server_socket_add_host(httpd->server_socket, bind_to_address, - port, error) - : server_socket_add_port(httpd->server_socket, port, error); - if (!success) { - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - /* initialize metadata */ - httpd->metadata = NULL; - httpd->unflushed_input = 0; - - /* initialize encoder */ - - httpd->encoder = encoder_init(encoder_plugin, param, error); - if (httpd->encoder == NULL) { - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - /* determine content type */ - httpd->content_type = encoder_get_mime_type(httpd->encoder); - if (httpd->content_type == NULL) { - httpd->content_type = "application/octet-stream"; - } - - httpd->mutex = g_mutex_new(); - - return &httpd->base; -} - -static void -httpd_output_finish(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (httpd->metadata) - page_unref(httpd->metadata); - - encoder_finish(httpd->encoder); - server_socket_free(httpd->server_socket); - g_mutex_free(httpd->mutex); - ao_base_finish(&httpd->base); - g_free(httpd); -} - -/** - * Creates a new #httpd_client object and adds it into the - * httpd_output.clients linked list. - */ -static void -httpd_client_add(struct httpd_output *httpd, int fd) -{ - struct httpd_client *client = - httpd_client_new(httpd, fd, - httpd->encoder->plugin->tag == NULL); - - httpd->clients = g_list_prepend(httpd->clients, client); - httpd->clients_cnt++; - - /* pass metadata to client */ - if (httpd->metadata) - httpd_client_send_metadata(client, httpd->metadata); -} - -static void -httpd_listen_in_event(int fd, const struct sockaddr *address, - size_t address_length, G_GNUC_UNUSED int uid, void *ctx) -{ - struct httpd_output *httpd = ctx; - - /* the listener socket has become readable - a client has - connected */ - -#ifdef HAVE_LIBWRAP - if (address->sa_family != AF_UNIX) { - char *hostaddr = sockaddr_to_string(address, address_length, NULL); - const char *progname = g_get_prgname(); - - struct request_info req; - request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); - - fromhost(&req); - - if (!hosts_access(&req)) { - /* tcp wrappers says no */ - g_warning("libwrap refused connection (libwrap=%s) from %s", - progname, hostaddr); - g_free(hostaddr); - close_socket(fd); - g_mutex_unlock(httpd->mutex); - return; - } - - g_free(hostaddr); - } -#else - (void)address; - (void)address_length; -#endif /* HAVE_WRAP */ - - g_mutex_lock(httpd->mutex); - - if (fd >= 0) { - /* can we allow additional client */ - if (httpd->open && - (httpd->clients_max == 0 || - httpd->clients_cnt < httpd->clients_max)) - httpd_client_add(httpd, fd); - else - close_socket(fd); - } else if (fd < 0 && errno != EINTR) { - g_warning("accept() failed: %s", g_strerror(errno)); - } - - g_mutex_unlock(httpd->mutex); -} - -/** - * Reads data from the encoder (as much as available) and returns it - * as a new #page object. - */ -static struct page * -httpd_output_read_page(struct httpd_output *httpd) -{ - if (httpd->unflushed_input >= 65536) { - /* we have fed a lot of input into the encoder, but it - didn't give anything back yet - flush now to avoid - buffer underruns */ - encoder_flush(httpd->encoder, NULL); - httpd->unflushed_input = 0; - } - - size_t size = 0; - do { - size_t nbytes = encoder_read(httpd->encoder, - httpd->buffer + size, - sizeof(httpd->buffer) - size); - if (nbytes == 0) - break; - - httpd->unflushed_input = 0; - - size += nbytes; - } while (size < sizeof(httpd->buffer)); - - if (size == 0) - return NULL; - - return page_new_copy(httpd->buffer, size); -} - -static bool -httpd_output_encoder_open(struct httpd_output *httpd, - struct audio_format *audio_format, - GError **error) -{ - if (!encoder_open(httpd->encoder, audio_format, error)) - return false; - - /* we have to remember the encoder header, i.e. the first - bytes of encoder output after opening it, because it has to - be sent to every new client */ - httpd->header = httpd_output_read_page(httpd); - - httpd->unflushed_input = 0; - - return true; -} - -static bool -httpd_output_enable(struct audio_output *ao, GError **error_r) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - return httpd_output_bind(httpd, error_r); -} - -static void -httpd_output_disable(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - httpd_output_unbind(httpd); -} - -static bool -httpd_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - g_mutex_lock(httpd->mutex); - - /* open the encoder */ - - if (!httpd_output_encoder_open(httpd, audio_format, error)) { - g_mutex_unlock(httpd->mutex); - return false; - } - - /* initialize other attributes */ - - httpd->clients = NULL; - httpd->clients_cnt = 0; - httpd->timer = timer_new(audio_format); - - httpd->open = true; - - g_mutex_unlock(httpd->mutex); - return true; -} - -static void -httpd_client_delete(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - httpd_client_free(client); -} - -static void -httpd_output_close(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - g_mutex_lock(httpd->mutex); - - httpd->open = false; - - timer_free(httpd->timer); - - g_list_foreach(httpd->clients, httpd_client_delete, NULL); - g_list_free(httpd->clients); - - if (httpd->header != NULL) - page_unref(httpd->header); - - encoder_close(httpd->encoder); - - g_mutex_unlock(httpd->mutex); -} - -void -httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client) -{ - assert(httpd != NULL); - assert(client != NULL); - - httpd->clients = g_list_remove(httpd->clients, client); - httpd->clients_cnt--; -} - -void -httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client) -{ - if (httpd->header != NULL) - httpd_client_send(client, httpd->header); -} - -static unsigned -httpd_output_delay(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (!httpd_output_lock_has_clients(httpd) && httpd->base.pause) { - /* if there's no client and this output is paused, - then httpd_output_pause() will not do anything, it - will not fill the buffer and it will not update the - timer; therefore, we reset the timer here */ - timer_reset(httpd->timer); - - /* some arbitrary delay that is long enough to avoid - consuming too much CPU, and short enough to notice - new clients quickly enough */ - return 1000; - } - - return httpd->timer->started - ? timer_delay(httpd->timer) - : 0; -} - -static void -httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - if (httpd_client_queue_size(client) > 256 * 1024) { - g_debug("client is too slow, flushing its queue"); - httpd_client_cancel(client); - } -} - -static void -httpd_client_send_page(gpointer data, gpointer user_data) -{ - struct httpd_client *client = data; - struct page *page = user_data; - - httpd_client_send(client, page); -} - -/** - * Broadcasts a page struct to all clients. - */ -static void -httpd_output_broadcast_page(struct httpd_output *httpd, struct page *page) -{ - assert(page != NULL); - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_send_page, page); - g_mutex_unlock(httpd->mutex); -} - -/** - * Broadcasts data from the encoder to all clients. - */ -static void -httpd_output_encoder_to_clients(struct httpd_output *httpd) -{ - struct page *page; - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_check_queue, NULL); - g_mutex_unlock(httpd->mutex); - - while ((page = httpd_output_read_page(httpd)) != NULL) { - httpd_output_broadcast_page(httpd, page); - page_unref(page); - } -} - -static bool -httpd_output_encode_and_play(struct httpd_output *httpd, - const void *chunk, size_t size, GError **error) -{ - if (!encoder_write(httpd->encoder, chunk, size, error)) - return false; - - httpd->unflushed_input += size; - - httpd_output_encoder_to_clients(httpd); - - return true; -} - -static size_t -httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error_r) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (httpd_output_lock_has_clients(httpd)) { - if (!httpd_output_encode_and_play(httpd, chunk, size, error_r)) - return 0; - } - - if (!httpd->timer->started) - timer_start(httpd->timer); - timer_add(httpd->timer, size); - - return size; -} - -static bool -httpd_output_pause(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (httpd_output_lock_has_clients(httpd)) { - static const char silence[1020]; - return httpd_output_play(ao, silence, sizeof(silence), - NULL) > 0; - } else { - return true; - } -} - -static void -httpd_send_metadata(gpointer data, gpointer user_data) -{ - struct httpd_client *client = data; - struct page *icy_metadata = user_data; - - httpd_client_send_metadata(client, icy_metadata); -} - -static void -httpd_output_tag(struct audio_output *ao, const struct tag *tag) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - assert(tag != NULL); - - if (httpd->encoder->plugin->tag != NULL) { - /* embed encoder tags */ - - /* flush the current stream, and end it */ - - encoder_pre_tag(httpd->encoder, NULL); - httpd_output_encoder_to_clients(httpd); - - /* send the tag to the encoder - which starts a new - stream now */ - - encoder_tag(httpd->encoder, tag, NULL); - - /* the first page generated by the encoder will now be - used as the new "header" page, which is sent to all - new clients */ - - struct page *page = httpd_output_read_page(httpd); - if (page != NULL) { - if (httpd->header != NULL) - page_unref(httpd->header); - httpd->header = page; - httpd_output_broadcast_page(httpd, page); - } - } else { - /* use Icy-Metadata */ - - if (httpd->metadata != NULL) - page_unref (httpd->metadata); - - httpd->metadata = - icy_server_metadata_page(tag, TAG_ALBUM, - TAG_ARTIST, TAG_TITLE, - TAG_NUM_OF_ITEM_TYPES); - if (httpd->metadata != NULL) { - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, - httpd_send_metadata, httpd->metadata); - g_mutex_unlock(httpd->mutex); - } - } -} - -static void -httpd_client_cancel_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - httpd_client_cancel(client); -} - -static void -httpd_output_cancel(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL); - g_mutex_unlock(httpd->mutex); -} - -const struct audio_output_plugin httpd_output_plugin = { - .name = "httpd", - .init = httpd_output_init, - .finish = httpd_output_finish, - .enable = httpd_output_enable, - .disable = httpd_output_disable, - .open = httpd_output_open, - .close = httpd_output_close, - .delay = httpd_output_delay, - .send_tag = httpd_output_tag, - .play = httpd_output_play, - .pause = httpd_output_pause, - .cancel = httpd_output_cancel, -}; diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c index e267427d..e82d3d3f 100644 --- a/src/output/pulse_output_plugin.c +++ b/src/output/pulse_output_plugin.c @@ -20,8 +20,8 @@ #include "config.h" #include "pulse_output_plugin.h" #include "output_api.h" -#include "mixer_list.h" -#include "mixer/pulse_mixer_plugin.h" +#include "MixerList.hxx" +#include "mixer/PulseMixerPlugin.h" #include <glib.h> diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h index 02a51f27..bcc8004a 100644 --- a/src/output/pulse_output_plugin.h +++ b/src/output/pulse_output_plugin.h @@ -20,9 +20,9 @@ #ifndef MPD_PULSE_OUTPUT_PLUGIN_H #define MPD_PULSE_OUTPUT_PLUGIN_H -#include <stdbool.h> +#include "gerror.h" -#include <glib.h> +#include <stdbool.h> struct pulse_output; struct pulse_mixer; @@ -30,6 +30,10 @@ struct pa_cvolume; extern const struct audio_output_plugin pulse_output_plugin; +#ifdef __cplusplus +extern "C" { +#endif + void pulse_output_lock(struct pulse_output *po); @@ -46,4 +50,8 @@ bool pulse_output_set_volume(struct pulse_output *po, const struct pa_cvolume *volume, GError **error_r); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/output/shout_output_plugin.c b/src/output/shout_output_plugin.c index 56456a0e..56d7a88b 100644 --- a/src/output/shout_output_plugin.c +++ b/src/output/shout_output_plugin.c @@ -97,23 +97,22 @@ static void free_shout_data(struct shout_data *sd) g_free(sd); } -#define check_block_param(name) { \ - block_param = config_get_block_param(param, name); \ - if (!block_param) { \ - MPD_ERROR("no \"%s\" defined for shout device defined at line " \ - "%i\n", name, param->line); \ - } \ - } +gcc_pure +static const char * +require_block_string(const struct config_param *param, const char *name) +{ + const char *value = config_get_block_string(param, name, NULL); + if (value == NULL) + MPD_ERROR("no \"%s\" defined for shout device defined at line " \ + "%i\n", name, param->line); \ -static struct audio_output * -my_shout_init_driver(const struct config_param *param, - GError **error) + return value; +} + +static bool +my_shout_configure(struct shout_data *sd, const struct config_param *param, + GError **error) { - struct shout_data *sd = new_shout_data(); - if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) { - free_shout_data(sd); - return NULL; - } const struct audio_format *audio_format = &sd->base.config_audio_format; @@ -125,30 +124,18 @@ my_shout_init_driver(const struct config_param *param, return NULL; } - if (shout_init_count == 0) - shout_init(); - - shout_init_count++; - - const struct block_param *block_param; - check_block_param("host"); - char *host = block_param->value; - - check_block_param("mount"); - char *mount = block_param->value; + const char *host = require_block_string(param, "host"); + const char *mount = require_block_string(param, "mount"); unsigned port = config_get_block_unsigned(param, "port", 0); if (port == 0) { g_set_error(error, shout_output_quark(), 0, "shout port must be configured"); - goto failure; + return false; } - check_block_param("password"); - const char *passwd = block_param->value; - - check_block_param("name"); - const char *name = block_param->value; + const char *passwd = require_block_string(param, "password"); + const char *name = require_block_string(param, "name"); bool public = config_get_block_bool(param, "public", false); @@ -164,21 +151,21 @@ my_shout_init_driver(const struct config_param *param, "shout quality \"%s\" is not a number in the " "range -1 to 10, line %i", value, param->line); - goto failure; + return false; } if (config_get_block_string(param, "bitrate", NULL) != NULL) { g_set_error(error, shout_output_quark(), 0, "quality and bitrate are " "both defined"); - goto failure; + return false; } } else { value = config_get_block_string(param, "bitrate", NULL); if (value == NULL) { g_set_error(error, shout_output_quark(), 0, "neither bitrate nor quality defined"); - goto failure; + return false; } char *test; @@ -187,7 +174,7 @@ my_shout_init_driver(const struct config_param *param, if (*test != '\0' || sd->bitrate <= 0) { g_set_error(error, shout_output_quark(), 0, "bitrate must be a positive integer"); - goto failure; + return false; } } @@ -199,12 +186,12 @@ my_shout_init_driver(const struct config_param *param, g_set_error(error, shout_output_quark(), 0, "couldn't find shout encoder plugin \"%s\"", encoding); - goto failure; + return false; } sd->encoder = encoder_init(encoder_plugin, param, error); if (sd->encoder == NULL) - goto failure; + return false; unsigned shout_format; if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0) @@ -220,7 +207,7 @@ my_shout_init_driver(const struct config_param *param, g_set_error(error, shout_output_quark(), 0, "you cannot stream \"%s\" to shoutcast, use mp3", encoding); - goto failure; + return false; } else if (0 == strcmp(value, "shoutcast")) protocol = SHOUT_PROTOCOL_ICY; else if (0 == strcmp(value, "icecast1")) @@ -232,7 +219,7 @@ my_shout_init_driver(const struct config_param *param, "shout protocol \"%s\" is not \"shoutcast\" or " "\"icecast1\"or \"icecast2\"", value); - goto failure; + return false; } } else { protocol = SHOUT_PROTOCOL_HTTP; @@ -251,7 +238,7 @@ my_shout_init_driver(const struct config_param *param, shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } /* optional paramters */ @@ -262,21 +249,21 @@ my_shout_init_driver(const struct config_param *param, if (value != NULL && shout_set_genre(sd->shout_conn, value)) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } value = config_get_block_string(param, "description", NULL); if (value != NULL && shout_set_description(sd->shout_conn, value)) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } value = config_get_block_string(param, "url", NULL); if (value != NULL && shout_set_url(sd->shout_conn, value)) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } { @@ -301,12 +288,31 @@ my_shout_init_driver(const struct config_param *param, } } - return &sd->base; + return true; +} -failure: - ao_base_finish(&sd->base); - free_shout_data(sd); - return NULL; +static struct audio_output * +my_shout_init_driver(const struct config_param *param, + GError **error) +{ + struct shout_data *sd = new_shout_data(); + if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) { + free_shout_data(sd); + return NULL; + } + + if (!my_shout_configure(sd, param, error)) { + ao_base_finish(&sd->base); + free_shout_data(sd); + return NULL; + } + + if (shout_init_count == 0) + shout_init(); + + shout_init_count++; + + return &sd->base; } static bool diff --git a/src/output_internal.h b/src/output_internal.h index 9d975d78..201962a7 100644 --- a/src/output_internal.h +++ b/src/output_internal.h @@ -27,6 +27,12 @@ #include <time.h> +#ifdef __cplusplus +class Filter; +#else +typedef void *Filter; +#endif + struct config_param; enum audio_output_command { @@ -73,6 +79,13 @@ struct audio_output { struct 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? */ @@ -149,13 +162,13 @@ struct audio_output { * The filter object of this audio output. This is an * instance of chain_filter_plugin. */ - struct filter *filter; + Filter *filter; /** * The replay_gain_filter_plugin instance of this audio * output. */ - struct filter *replay_gain_filter; + Filter *replay_gain_filter; /** * The serial number of the last replay gain info. 0 means no @@ -168,7 +181,7 @@ struct audio_output { * output, to be applied to the second chunk during * cross-fading. */ - struct filter *other_replay_gain_filter; + Filter *other_replay_gain_filter; /** * The serial number of the last replay gain info by the @@ -182,7 +195,7 @@ struct audio_output { * for converting the input data into the appropriate format * for this audio output. */ - struct filter *convert_filter; + Filter *convert_filter; /** * The thread handle, or NULL if the output thread isn't @@ -250,6 +263,10 @@ audio_output_command_is_finished(const struct audio_output *ao) return ao->command == AO_COMMAND_NONE; } +#ifdef __cplusplus +extern "C" { +#endif + struct audio_output * audio_output_new(const struct config_param *param, struct player_control *pc, @@ -266,4 +283,8 @@ ao_base_finish(struct audio_output *ao); void audio_output_free(struct audio_output *ao); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/output_plugin.h b/src/output_plugin.h index 209ca622..2b71ba6a 100644 --- a/src/output_plugin.h +++ b/src/output_plugin.h @@ -20,7 +20,8 @@ #ifndef MPD_OUTPUT_PLUGIN_H #define MPD_OUTPUT_PLUGIN_H -#include <glib.h> +#include "gcc.h" +#include "gerror.h" #include <stdbool.h> #include <stddef.h> @@ -165,7 +166,11 @@ ao_plugin_test_default_device(const struct audio_output_plugin *plugin) : false; } -G_GNUC_MALLOC +#ifdef __cplusplus +extern "C" { +#endif + +gcc_malloc struct audio_output * ao_plugin_init(const struct audio_output_plugin *plugin, const struct config_param *param, @@ -187,7 +192,7 @@ ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format, void ao_plugin_close(struct audio_output *ao); -G_GNUC_PURE +gcc_pure unsigned ao_plugin_delay(struct audio_output *ao); @@ -207,4 +212,8 @@ ao_plugin_cancel(struct audio_output *ao); bool ao_plugin_pause(struct audio_output *ao); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/page.c b/src/page.c deleted file mode 100644 index e2e22791..00000000 --- a/src/page.c +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "page.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -/** - * Allocates a new #page object, without filling the data element. - */ -static struct page * -page_new(size_t size) -{ - struct page *page = g_malloc(sizeof(*page) + size - - sizeof(page->data)); - - assert(size > 0); - - page->ref = 1; - page->size = size; - return page; -} - -struct page * -page_new_copy(const void *data, size_t size) -{ - struct page *page = page_new(size); - - assert(data != NULL); - - memcpy(page->data, data, size); - return page; -} - -struct page * -page_new_concat(const struct page *a, const struct page *b) -{ - struct page *page = page_new(a->size + b->size); - - memcpy(page->data, a->data, a->size); - memcpy(page->data + a->size, b->data, b->size); - - return page; -} - -void -page_ref(struct page *page) -{ - g_atomic_int_inc(&page->ref); -} - -static void -page_free(struct page *page) -{ - assert(page->ref == 0); - - g_free(page); -} - -bool -page_unref(struct page *page) -{ - bool unused = g_atomic_int_dec_and_test(&page->ref); - - if (unused) - page_free(page); - - return unused; -} diff --git a/src/path.h b/src/path.h deleted file mode 100644 index 00c368e7..00000000 --- a/src/path.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PATH_H -#define MPD_PATH_H - -#include <limits.h> - -#if !defined(MPD_PATH_MAX) -# if defined(MAXPATHLEN) -# define MPD_PATH_MAX MAXPATHLEN -# elif defined(PATH_MAX) -# define MPD_PATH_MAX PATH_MAX -# else -# define MPD_PATH_MAX 256 -# endif -#endif - -void path_global_init(void); - -void path_global_finish(void); - -/** - * Converts a file name in the filesystem charset to UTF-8. Returns - * NULL on failure. - */ -char * -fs_charset_to_utf8(const char *path_fs); - -/** - * Converts a file name in UTF-8 to the filesystem charset. Returns a - * duplicate of the UTF-8 string on failure. - */ -char * -utf8_to_fs_charset(const char *path_utf8); - -const char *path_get_fs_charset(void); - -#endif diff --git a/src/pcm_buffer.h b/src/pcm_buffer.h index 4502976f..5d6382d5 100644 --- a/src/pcm_buffer.h +++ b/src/pcm_buffer.h @@ -62,6 +62,10 @@ pcm_buffer_deinit(struct pcm_buffer *buffer) buffer->buffer = NULL; } +#ifdef __cplusplus +extern "C" { +#endif + /** * Get the buffer, and guarantee a minimum size. This buffer becomes * invalid with the next pcm_buffer_get() call. @@ -74,4 +78,8 @@ G_GNUC_MALLOC void * pcm_buffer_get(struct pcm_buffer *buffer, size_t size); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/pcm_convert.c b/src/pcm_convert.c deleted file mode 100644 index 63f9a1b9..00000000 --- a/src/pcm_convert.c +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_convert.h" -#include "pcm_channels.h" -#include "pcm_format.h" -#include "pcm_pack.h" -#include "audio_format.h" -#include "glib_compat.h" - -#include <assert.h> -#include <string.h> -#include <math.h> -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pcm" - -void pcm_convert_init(struct pcm_convert_state *state) -{ - memset(state, 0, sizeof(*state)); - - pcm_dsd_init(&state->dsd); - pcm_resample_init(&state->resample); - pcm_dither_24_init(&state->dither); - - pcm_buffer_init(&state->format_buffer); - pcm_buffer_init(&state->channels_buffer); -} - -void pcm_convert_deinit(struct pcm_convert_state *state) -{ - pcm_dsd_deinit(&state->dsd); - pcm_resample_deinit(&state->resample); - - pcm_buffer_deinit(&state->format_buffer); - pcm_buffer_deinit(&state->channels_buffer); -} - -void -pcm_convert_reset(struct pcm_convert_state *state) -{ - pcm_dsd_reset(&state->dsd); - pcm_resample_reset(&state->resample); -} - -static const void * -pcm_convert_channels(struct pcm_buffer *buffer, enum sample_format format, - uint8_t dest_channels, - uint8_t src_channels, const void *src, - size_t src_size, size_t *dest_size_r, - GError **error_r) -{ - const void *dest = NULL; - - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_S8: - case SAMPLE_FORMAT_FLOAT: - case SAMPLE_FORMAT_DSD: - g_set_error(error_r, pcm_convert_quark(), 0, - "Channel conversion not implemented for format '%s'", - sample_format_to_string(format)); - return NULL; - - case SAMPLE_FORMAT_S16: - dest = pcm_convert_channels_16(buffer, dest_channels, - src_channels, src, - src_size, dest_size_r); - break; - - case SAMPLE_FORMAT_S24_P32: - dest = pcm_convert_channels_24(buffer, dest_channels, - src_channels, src, - src_size, dest_size_r); - break; - - case SAMPLE_FORMAT_S32: - dest = pcm_convert_channels_32(buffer, dest_channels, - src_channels, src, - src_size, dest_size_r); - break; - } - - if (dest == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %u to %u channels " - "is not implemented", - src_channels, dest_channels); - return NULL; - } - - return dest; -} - -static const int16_t * -pcm_convert_16(struct pcm_convert_state *state, - const struct audio_format *src_format, - const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, size_t *dest_size_r, - GError **error_r) -{ - const int16_t *buf; - size_t len; - - assert(dest_format->format == SAMPLE_FORMAT_S16); - - buf = pcm_convert_to_16(&state->format_buffer, &state->dither, - src_format->format, src_buffer, src_size, - &len); - if (buf == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %s to 16 bit is not implemented", - sample_format_to_string(src_format->format)); - return NULL; - } - - if (src_format->channels != dest_format->channels) { - buf = pcm_convert_channels_16(&state->channels_buffer, - dest_format->channels, - src_format->channels, - buf, len, &len); - if (buf == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %u to %u channels " - "is not implemented", - src_format->channels, - dest_format->channels); - return NULL; - } - } - - if (src_format->sample_rate != dest_format->sample_rate) { - buf = pcm_resample_16(&state->resample, - dest_format->channels, - src_format->sample_rate, buf, len, - dest_format->sample_rate, &len, - error_r); - if (buf == NULL) - return NULL; - } - - *dest_size_r = len; - return buf; -} - -static const int32_t * -pcm_convert_24(struct pcm_convert_state *state, - const struct audio_format *src_format, - const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, size_t *dest_size_r, - GError **error_r) -{ - const int32_t *buf; - size_t len; - - assert(dest_format->format == SAMPLE_FORMAT_S24_P32); - - buf = pcm_convert_to_24(&state->format_buffer, src_format->format, - src_buffer, src_size, &len); - if (buf == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %s to 24 bit is not implemented", - sample_format_to_string(src_format->format)); - return NULL; - } - - if (src_format->channels != dest_format->channels) { - buf = pcm_convert_channels_24(&state->channels_buffer, - dest_format->channels, - src_format->channels, - buf, len, &len); - if (buf == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %u to %u channels " - "is not implemented", - src_format->channels, - dest_format->channels); - return NULL; - } - } - - if (src_format->sample_rate != dest_format->sample_rate) { - buf = pcm_resample_24(&state->resample, - dest_format->channels, - src_format->sample_rate, buf, len, - dest_format->sample_rate, &len, - error_r); - if (buf == NULL) - return NULL; - } - - *dest_size_r = len; - return buf; -} - -static const int32_t * -pcm_convert_32(struct pcm_convert_state *state, - const struct audio_format *src_format, - const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, size_t *dest_size_r, - GError **error_r) -{ - const int32_t *buf; - size_t len; - - assert(dest_format->format == SAMPLE_FORMAT_S32); - - buf = pcm_convert_to_32(&state->format_buffer, src_format->format, - src_buffer, src_size, &len); - if (buf == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %s to 32 bit is not implemented", - sample_format_to_string(src_format->format)); - return NULL; - } - - if (src_format->channels != dest_format->channels) { - buf = pcm_convert_channels_32(&state->channels_buffer, - dest_format->channels, - src_format->channels, - buf, len, &len); - if (buf == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %u to %u channels " - "is not implemented", - src_format->channels, - dest_format->channels); - return NULL; - } - } - - if (src_format->sample_rate != dest_format->sample_rate) { - buf = pcm_resample_32(&state->resample, - dest_format->channels, - src_format->sample_rate, buf, len, - dest_format->sample_rate, &len, - error_r); - if (buf == NULL) - return buf; - } - - *dest_size_r = len; - return buf; -} - -static const float * -pcm_convert_float(struct pcm_convert_state *state, - const struct audio_format *src_format, - const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, size_t *dest_size_r, - GError **error_r) -{ - const float *buffer = src_buffer; - size_t size = src_size; - - assert(dest_format->format == SAMPLE_FORMAT_FLOAT); - - /* convert channels first, hoping the source format is - supported (float is not) */ - - if (dest_format->channels != src_format->channels) { - buffer = pcm_convert_channels(&state->channels_buffer, - src_format->format, - dest_format->channels, - src_format->channels, - buffer, size, &size, error_r); - if (buffer == NULL) - return NULL; - } - - /* convert to float now */ - - buffer = pcm_convert_to_float(&state->format_buffer, - src_format->format, - buffer, size, &size); - if (buffer == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %s to float is not implemented", - sample_format_to_string(src_format->format)); - return NULL; - } - - /* resample with float, because this is the best format for - libsamplerate */ - - if (src_format->sample_rate != dest_format->sample_rate) { - buffer = pcm_resample_float(&state->resample, - dest_format->channels, - src_format->sample_rate, - buffer, size, - dest_format->sample_rate, &size, - error_r); - if (buffer == NULL) - return NULL; - } - - *dest_size_r = size; - return buffer; -} - -const void * -pcm_convert(struct pcm_convert_state *state, - const struct audio_format *src_format, - const void *src, size_t src_size, - const struct audio_format *dest_format, - size_t *dest_size_r, - GError **error_r) -{ - struct audio_format float_format; - if (src_format->format == SAMPLE_FORMAT_DSD) { - size_t f_size; - const float *f = pcm_dsd_to_float(&state->dsd, - src_format->channels, - false, src, src_size, - &f_size); - if (f == NULL) { - g_set_error_literal(error_r, pcm_convert_quark(), 0, - "DSD to PCM conversion failed"); - return NULL; - } - - float_format = *src_format; - float_format.format = SAMPLE_FORMAT_FLOAT; - - src_format = &float_format; - src = f; - src_size = f_size; - } - - switch (dest_format->format) { - case SAMPLE_FORMAT_S16: - return pcm_convert_16(state, - src_format, src, src_size, - dest_format, dest_size_r, - error_r); - - case SAMPLE_FORMAT_S24_P32: - return pcm_convert_24(state, - src_format, src, src_size, - dest_format, dest_size_r, - error_r); - - case SAMPLE_FORMAT_S32: - return pcm_convert_32(state, - src_format, src, src_size, - dest_format, dest_size_r, - error_r); - - case SAMPLE_FORMAT_FLOAT: - return pcm_convert_float(state, - src_format, src, src_size, - dest_format, dest_size_r, - error_r); - - default: - g_set_error(error_r, pcm_convert_quark(), 0, - "PCM conversion to %s is not implemented", - sample_format_to_string(dest_format->format)); - return NULL; - } -} diff --git a/src/pcm_convert.h b/src/pcm_convert.h deleted file mode 100644 index be11a6e4..00000000 --- a/src/pcm_convert.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PCM_CONVERT_H -#define PCM_CONVERT_H - -#include "pcm_dsd.h" -#include "pcm_resample.h" -#include "pcm_dither.h" -#include "pcm_buffer.h" - -struct audio_format; - -/** - * This object is statically allocated (within another struct), and - * holds buffer allocations and the state for all kinds of PCM - * conversions. - */ -struct pcm_convert_state { - struct pcm_dsd dsd; - - struct pcm_resample_state resample; - - struct pcm_dither dither; - - /** the buffer for converting the sample format */ - struct pcm_buffer format_buffer; - - /** the buffer for converting the channel count */ - struct pcm_buffer channels_buffer; -}; - -static inline GQuark -pcm_convert_quark(void) -{ - return g_quark_from_static_string("pcm_convert"); -} - -/** - * Initializes a pcm_convert_state object. - */ -void pcm_convert_init(struct pcm_convert_state *state); - -/** - * Deinitializes a pcm_convert_state object and frees allocated - * memory. - */ -void pcm_convert_deinit(struct pcm_convert_state *state); - -/** - * Reset the pcm_convert_state object. Use this at the boundary - * between two distinct songs and each time the format changes. - */ -void -pcm_convert_reset(struct pcm_convert_state *state); - -/** - * Converts PCM data between two audio formats. - * - * @param state an initialized pcm_convert_state object - * @param src_format the source audio format - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_format the requested destination audio format - * @param dest_size_r returns the number of bytes of the destination buffer - * @param error_r location to store the error occurring, or NULL to - * ignore errors - * @return the destination buffer, or NULL on error - */ -const void * -pcm_convert(struct pcm_convert_state *state, - const struct audio_format *src_format, - const void *src, size_t src_size, - const struct audio_format *dest_format, - size_t *dest_size_r, - GError **error_r); - -#endif diff --git a/src/pcm_dither.c b/src/pcm_dither.c deleted file mode 100644 index 4811946c..00000000 --- a/src/pcm_dither.c +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_dither.h" -#include "pcm_prng.h" - -static int16_t -pcm_dither_sample_24_to_16(int32_t sample, struct pcm_dither *dither) -{ - int32_t output, rnd; - - enum { - from_bits = 24, - to_bits = 16, - scale_bits = from_bits - to_bits, - round = 1 << (scale_bits - 1), - mask = (1 << scale_bits) - 1, - ONE = 1 << (from_bits - 1), - MIN = -ONE, - MAX = ONE - 1 - }; - - sample += dither->error[0] - dither->error[1] + dither->error[2]; - - dither->error[2] = dither->error[1]; - dither->error[1] = dither->error[0] / 2; - - /* round */ - output = sample + round; - - rnd = pcm_prng(dither->random); - output += (rnd & mask) - (dither->random & mask); - - dither->random = rnd; - - /* clip */ - if (output > MAX) { - output = MAX; - - if (sample > MAX) - sample = MAX; - } else if (output < MIN) { - output = MIN; - - if (sample < MIN) - sample = MIN; - } - - output &= ~mask; - - dither->error[0] = sample - output; - - return (int16_t)(output >> scale_bits); -} - -void -pcm_dither_24_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, const int32_t *src_end) -{ - while (src < src_end) - *dest++ = pcm_dither_sample_24_to_16(*src++, dither); -} - -static int16_t -pcm_dither_sample_32_to_16(int32_t sample, struct pcm_dither *dither) -{ - return pcm_dither_sample_24_to_16(sample >> 8, dither); -} - -void -pcm_dither_32_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, const int32_t *src_end) -{ - while (src < src_end) - *dest++ = pcm_dither_sample_32_to_16(*src++, dither); -} diff --git a/src/pcm_export.h b/src/pcm_export.h index a7e7c3f6..005db48e 100644 --- a/src/pcm_export.h +++ b/src/pcm_export.h @@ -87,6 +87,10 @@ struct pcm_export_state { uint8_t reverse_endian; }; +#ifdef __cplusplus +extern "C" { +#endif + /** * Initialize a #pcm_export_state object. */ @@ -144,4 +148,8 @@ G_GNUC_PURE size_t pcm_export_source_size(const struct pcm_export_state *state, size_t dest_size); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/pcm_mix.c b/src/pcm_mix.c deleted file mode 100644 index 6c6d1b4a..00000000 --- a/src/pcm_mix.c +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_mix.h" -#include "pcm_volume.h" -#include "pcm_utils.h" -#include "audio_format.h" - -#include <glib.h> - -#include <math.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pcm" - -static void -pcm_add_vol_8(int8_t *buffer1, const int8_t *buffer2, - unsigned num_samples, int volume1, int volume2) -{ - while (num_samples > 0) { - int32_t sample1 = *buffer1; - int32_t sample2 = *buffer2++; - - sample1 = ((sample1 * volume1 + sample2 * volume2) + - pcm_volume_dither() + PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer1++ = pcm_range(sample1, 8); - --num_samples; - } -} - -static void -pcm_add_vol_16(int16_t *buffer1, const int16_t *buffer2, - unsigned num_samples, int volume1, int volume2) -{ - while (num_samples > 0) { - int32_t sample1 = *buffer1; - int32_t sample2 = *buffer2++; - - sample1 = ((sample1 * volume1 + sample2 * volume2) + - pcm_volume_dither() + PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer1++ = pcm_range(sample1, 16); - --num_samples; - } -} - -static void -pcm_add_vol_24(int32_t *buffer1, const int32_t *buffer2, - unsigned num_samples, unsigned volume1, unsigned volume2) -{ - while (num_samples > 0) { - int64_t sample1 = *buffer1; - int64_t sample2 = *buffer2++; - - sample1 = ((sample1 * volume1 + sample2 * volume2) + - pcm_volume_dither() + PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer1++ = pcm_range(sample1, 24); - --num_samples; - } -} - -static void -pcm_add_vol_32(int32_t *buffer1, const int32_t *buffer2, - unsigned num_samples, unsigned volume1, unsigned volume2) -{ - while (num_samples > 0) { - int64_t sample1 = *buffer1; - int64_t sample2 = *buffer2++; - - sample1 = ((sample1 * volume1 + sample2 * volume2) + - pcm_volume_dither() + PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer1++ = pcm_range_64(sample1, 32); - --num_samples; - } -} - -static void -pcm_add_vol_float(float *buffer1, const float *buffer2, - unsigned num_samples, float volume1, float volume2) -{ - while (num_samples > 0) { - float sample1 = *buffer1; - float sample2 = *buffer2++; - - sample1 = (sample1 * volume1 + sample2 * volume2); - *buffer1++ = sample1; - --num_samples; - } -} - -static bool -pcm_add_vol(void *buffer1, const void *buffer2, size_t size, - int vol1, int vol2, - enum sample_format format) -{ - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - /* not implemented */ - return false; - - case SAMPLE_FORMAT_S8: - pcm_add_vol_8((int8_t *)buffer1, (const int8_t *)buffer2, - size, vol1, vol2); - return true; - - case SAMPLE_FORMAT_S16: - pcm_add_vol_16((int16_t *)buffer1, (const int16_t *)buffer2, - size / 2, vol1, vol2); - return true; - - case SAMPLE_FORMAT_S24_P32: - pcm_add_vol_24((int32_t *)buffer1, (const int32_t *)buffer2, - size / 4, vol1, vol2); - return true; - - case SAMPLE_FORMAT_S32: - pcm_add_vol_32((int32_t *)buffer1, (const int32_t *)buffer2, - size / 4, vol1, vol2); - return true; - - case SAMPLE_FORMAT_FLOAT: - pcm_add_vol_float(buffer1, buffer2, size / 4, - pcm_volume_to_float(vol1), - pcm_volume_to_float(vol2)); - return true; - } - - /* unreachable */ - assert(false); - return false; -} - -static void -pcm_add_8(int8_t *buffer1, const int8_t *buffer2, unsigned num_samples) -{ - while (num_samples > 0) { - int32_t sample1 = *buffer1; - int32_t sample2 = *buffer2++; - - sample1 += sample2; - - *buffer1++ = pcm_range(sample1, 8); - --num_samples; - } -} - -static void -pcm_add_16(int16_t *buffer1, const int16_t *buffer2, unsigned num_samples) -{ - while (num_samples > 0) { - int32_t sample1 = *buffer1; - int32_t sample2 = *buffer2++; - - sample1 += sample2; - - *buffer1++ = pcm_range(sample1, 16); - --num_samples; - } -} - -static void -pcm_add_24(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples) -{ - while (num_samples > 0) { - int64_t sample1 = *buffer1; - int64_t sample2 = *buffer2++; - - sample1 += sample2; - - *buffer1++ = pcm_range(sample1, 24); - --num_samples; - } -} - -static void -pcm_add_32(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples) -{ - while (num_samples > 0) { - int64_t sample1 = *buffer1; - int64_t sample2 = *buffer2++; - - sample1 += sample2; - - *buffer1++ = pcm_range_64(sample1, 32); - --num_samples; - } -} - -static void -pcm_add_float(float *buffer1, const float *buffer2, unsigned num_samples) -{ - while (num_samples > 0) { - float sample1 = *buffer1; - float sample2 = *buffer2++; - *buffer1++ = sample1 + sample2; - --num_samples; - } -} - -static bool -pcm_add(void *buffer1, const void *buffer2, size_t size, - enum sample_format format) -{ - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - /* not implemented */ - return false; - - case SAMPLE_FORMAT_S8: - pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2, size); - return true; - - case SAMPLE_FORMAT_S16: - pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2, size / 2); - return true; - - case SAMPLE_FORMAT_S24_P32: - pcm_add_24((int32_t *)buffer1, (const int32_t *)buffer2, size / 4); - return true; - - case SAMPLE_FORMAT_S32: - pcm_add_32((int32_t *)buffer1, (const int32_t *)buffer2, size / 4); - return true; - - case SAMPLE_FORMAT_FLOAT: - pcm_add_float(buffer1, buffer2, size / 4); - return true; - } - - /* unreachable */ - assert(false); - return false; -} - -bool -pcm_mix(void *buffer1, const void *buffer2, size_t size, - enum sample_format format, float portion1) -{ - int vol1; - float s; - - /* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses NaN - * to signal mixing rather than fading */ - if (isnan(portion1)) - return pcm_add(buffer1, buffer2, size, format); - - s = sin(M_PI_2 * portion1); - s *= s; - - vol1 = s * PCM_VOLUME_1 + 0.5; - vol1 = vol1 > PCM_VOLUME_1 ? PCM_VOLUME_1 : (vol1 < 0 ? 0 : vol1); - - return pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format); -} diff --git a/src/pcm_utils.h b/src/pcm_utils.h deleted file mode 100644 index 4ad89657..00000000 --- a/src/pcm_utils.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PCM_UTILS_H -#define MPD_PCM_UTILS_H - -#include <glib.h> - -#include <stdint.h> - -/** - * Add a byte count to the specified pointer. This is a utility - * function to convert a source pointer and a byte count to an "end" - * pointer for use in loops. - */ -static inline const void * -pcm_end_pointer(const void *p, size_t size) -{ - return (const char *)p + size; -} - -/** - * Check if the value is within the range of the provided bit size, - * and caps it if necessary. - */ -static inline int32_t -pcm_range(int32_t sample, unsigned bits) -{ - if (G_UNLIKELY(sample < (-1 << (bits - 1)))) - return -1 << (bits - 1); - if (G_UNLIKELY(sample >= (1 << (bits - 1)))) - return (1 << (bits - 1)) - 1; - return sample; -} - -/** - * Check if the value is within the range of the provided bit size, - * and caps it if necessary. - */ -static inline int64_t -pcm_range_64(int64_t sample, unsigned bits) -{ - if (G_UNLIKELY(sample < ((int64_t)-1 << (bits - 1)))) - return (int64_t)-1 << (bits - 1); - if (G_UNLIKELY(sample >= ((int64_t)1 << (bits - 1)))) - return ((int64_t)1 << (bits - 1)) - 1; - return sample; -} - -G_GNUC_CONST -static inline int16_t -pcm_clamp_16(int x) -{ - static const int32_t MIN_VALUE = G_MININT16; - static const int32_t MAX_VALUE = G_MAXINT16; - - if (G_UNLIKELY(x < MIN_VALUE)) - return MIN_VALUE; - if (G_UNLIKELY(x > MAX_VALUE)) - return MAX_VALUE; - return x; -} - -G_GNUC_CONST -static inline int32_t -pcm_clamp_24(int x) -{ - static const int32_t MIN_VALUE = -(1 << 23); - static const int32_t MAX_VALUE = (1 << 23) - 1; - - if (G_UNLIKELY(x < MIN_VALUE)) - return MIN_VALUE; - if (G_UNLIKELY(x > MAX_VALUE)) - return MAX_VALUE; - return x; -} - -#endif diff --git a/src/player_control.c b/src/player_control.c deleted file mode 100644 index 90f616d7..00000000 --- a/src/player_control.c +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "player_control.h" -#include "decoder_control.h" -#include "path.h" -#include "log.h" -#include "tag.h" -#include "song.h" -#include "idle.h" -#include "pcm_volume.h" -#include "main.h" - -#include <assert.h> -#include <stdio.h> -#include <math.h> - -static void -pc_enqueue_song_locked(struct player_control *pc, struct song *song); - -struct player_control * -pc_new(unsigned buffer_chunks, unsigned int buffered_before_play) -{ - struct player_control *pc = g_new0(struct player_control, 1); - - pc->buffer_chunks = buffer_chunks; - pc->buffered_before_play = buffered_before_play; - - pc->mutex = g_mutex_new(); - pc->cond = g_cond_new(); - - pc->command = PLAYER_COMMAND_NONE; - pc->error = PLAYER_ERROR_NOERROR; - pc->state = PLAYER_STATE_STOP; - pc->cross_fade_seconds = 0; - pc->mixramp_db = 0; - pc->mixramp_delay_seconds = nanf(""); - - return pc; -} - -void -pc_free(struct player_control *pc) -{ - g_cond_free(pc->cond); - g_mutex_free(pc->mutex); - g_free(pc); -} - -void -player_wait_decoder(struct player_control *pc, struct decoder_control *dc) -{ - assert(pc != NULL); - assert(dc != NULL); - assert(dc->client_cond == pc->cond); - - /* during this function, the decoder lock is held, because - we're waiting for the decoder thread */ - g_cond_wait(pc->cond, dc->mutex); -} - -void -pc_song_deleted(struct player_control *pc, const struct song *song) -{ - if (pc->errored_song == song) { - pc->error = PLAYER_ERROR_NOERROR; - pc->errored_song = NULL; - } -} - -static void -player_command_wait_locked(struct player_control *pc) -{ - while (pc->command != PLAYER_COMMAND_NONE) - g_cond_wait(main_cond, pc->mutex); -} - -static void -player_command_locked(struct player_control *pc, enum player_command cmd) -{ - assert(pc->command == PLAYER_COMMAND_NONE); - - pc->command = cmd; - player_signal(pc); - player_command_wait_locked(pc); -} - -static void -player_command(struct player_control *pc, enum player_command cmd) -{ - player_lock(pc); - player_command_locked(pc, cmd); - player_unlock(pc); -} - -void -pc_play(struct player_control *pc, struct song *song) -{ - assert(song != NULL); - - player_lock(pc); - - if (pc->state != PLAYER_STATE_STOP) - player_command_locked(pc, PLAYER_COMMAND_STOP); - - assert(pc->next_song == NULL); - - pc_enqueue_song_locked(pc, song); - - assert(pc->next_song == NULL); - - player_unlock(pc); - - idle_add(IDLE_PLAYER); -} - -void -pc_cancel(struct player_control *pc) -{ - player_command(pc, PLAYER_COMMAND_CANCEL); - assert(pc->next_song == NULL); -} - -void -pc_stop(struct player_control *pc) -{ - player_command(pc, PLAYER_COMMAND_CLOSE_AUDIO); - assert(pc->next_song == NULL); - - idle_add(IDLE_PLAYER); -} - -void -pc_update_audio(struct player_control *pc) -{ - player_command(pc, PLAYER_COMMAND_UPDATE_AUDIO); -} - -void -pc_kill(struct player_control *pc) -{ - assert(pc->thread != NULL); - - player_command(pc, PLAYER_COMMAND_EXIT); - g_thread_join(pc->thread); - pc->thread = NULL; - - idle_add(IDLE_PLAYER); -} - -void -pc_pause(struct player_control *pc) -{ - player_lock(pc); - - if (pc->state != PLAYER_STATE_STOP) { - player_command_locked(pc, PLAYER_COMMAND_PAUSE); - idle_add(IDLE_PLAYER); - } - - player_unlock(pc); -} - -static void -pc_pause_locked(struct player_control *pc) -{ - if (pc->state != PLAYER_STATE_STOP) { - player_command_locked(pc, PLAYER_COMMAND_PAUSE); - idle_add(IDLE_PLAYER); - } -} - -void -pc_set_pause(struct player_control *pc, bool pause_flag) -{ - player_lock(pc); - - switch (pc->state) { - case PLAYER_STATE_STOP: - break; - - case PLAYER_STATE_PLAY: - if (pause_flag) - pc_pause_locked(pc); - break; - - case PLAYER_STATE_PAUSE: - if (!pause_flag) - pc_pause_locked(pc); - break; - } - - player_unlock(pc); -} - -void -pc_set_border_pause(struct player_control *pc, bool border_pause) -{ - player_lock(pc); - pc->border_pause = border_pause; - player_unlock(pc); -} - -void -pc_get_status(struct player_control *pc, struct player_status *status) -{ - player_lock(pc); - player_command_locked(pc, PLAYER_COMMAND_REFRESH); - - status->state = pc->state; - - if (pc->state != PLAYER_STATE_STOP) { - status->bit_rate = pc->bit_rate; - status->audio_format = pc->audio_format; - status->total_time = pc->total_time; - status->elapsed_time = pc->elapsed_time; - } - - player_unlock(pc); -} - -enum player_state -pc_get_state(struct player_control *pc) -{ - return pc->state; -} - -void -pc_clear_error(struct player_control *pc) -{ - player_lock(pc); - pc->error = PLAYER_ERROR_NOERROR; - pc->errored_song = NULL; - player_unlock(pc); -} - -enum player_error -pc_get_error(struct player_control *pc) -{ - return pc->error; -} - -static char * -pc_errored_song_uri(struct player_control *pc) -{ - return song_get_uri(pc->errored_song); -} - -char * -pc_get_error_message(struct player_control *pc) -{ - char *error; - char *uri; - - switch (pc->error) { - case PLAYER_ERROR_NOERROR: - return NULL; - - case PLAYER_ERROR_FILENOTFOUND: - uri = pc_errored_song_uri(pc); - error = g_strdup_printf("file \"%s\" does not exist or is inaccessible", uri); - g_free(uri); - return error; - - case PLAYER_ERROR_FILE: - uri = pc_errored_song_uri(pc); - error = g_strdup_printf("problems decoding \"%s\"", uri); - g_free(uri); - return error; - - case PLAYER_ERROR_AUDIO: - return g_strdup("problems opening audio device"); - - case PLAYER_ERROR_SYSTEM: - return g_strdup("system error occurred"); - - case PLAYER_ERROR_UNKTYPE: - uri = pc_errored_song_uri(pc); - error = g_strdup_printf("file type of \"%s\" is unknown", uri); - g_free(uri); - return error; - } - - assert(false); - return NULL; -} - -static void -pc_enqueue_song_locked(struct player_control *pc, struct song *song) -{ - assert(song != NULL); - assert(pc->next_song == NULL); - - pc->next_song = song; - player_command_locked(pc, PLAYER_COMMAND_QUEUE); -} - -void -pc_enqueue_song(struct player_control *pc, struct song *song) -{ - assert(song != NULL); - - player_lock(pc); - pc_enqueue_song_locked(pc, song); - player_unlock(pc); -} - -bool -pc_seek(struct player_control *pc, struct song *song, float seek_time) -{ - assert(song != NULL); - - player_lock(pc); - pc->next_song = song; - pc->seek_where = seek_time; - player_command_locked(pc, PLAYER_COMMAND_SEEK); - player_unlock(pc); - - assert(pc->next_song == NULL); - - idle_add(IDLE_PLAYER); - - return true; -} - -float -pc_get_cross_fade(const struct player_control *pc) -{ - return pc->cross_fade_seconds; -} - -void -pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds) -{ - if (cross_fade_seconds < 0) - cross_fade_seconds = 0; - pc->cross_fade_seconds = cross_fade_seconds; - - idle_add(IDLE_OPTIONS); -} - -float -pc_get_mixramp_db(const struct player_control *pc) -{ - return pc->mixramp_db; -} - -void -pc_set_mixramp_db(struct player_control *pc, float mixramp_db) -{ - pc->mixramp_db = mixramp_db; - - idle_add(IDLE_OPTIONS); -} - -float -pc_get_mixramp_delay(const struct player_control *pc) -{ - return pc->mixramp_delay_seconds; -} - -void -pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds) -{ - pc->mixramp_delay_seconds = mixramp_delay_seconds; - - idle_add(IDLE_OPTIONS); -} - -double -pc_get_total_play_time(const struct player_control *pc) -{ - return pc->total_play_time; -} diff --git a/src/player_control.h b/src/player_control.h deleted file mode 100644 index a77d31ec..00000000 --- a/src/player_control.h +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYER_H -#define MPD_PLAYER_H - -#include "audio_format.h" - -#include <glib.h> - -#include <stdint.h> - -struct decoder_control; - -enum player_state { - PLAYER_STATE_STOP = 0, - PLAYER_STATE_PAUSE, - PLAYER_STATE_PLAY -}; - -enum player_command { - PLAYER_COMMAND_NONE = 0, - PLAYER_COMMAND_EXIT, - PLAYER_COMMAND_STOP, - PLAYER_COMMAND_PAUSE, - PLAYER_COMMAND_SEEK, - PLAYER_COMMAND_CLOSE_AUDIO, - - /** - * At least one audio_output.enabled flag has been modified; - * commit those changes to the output threads. - */ - PLAYER_COMMAND_UPDATE_AUDIO, - - /** player_control.next_song has been updated */ - PLAYER_COMMAND_QUEUE, - - /** - * cancel pre-decoding player_control.next_song; if the player - * has already started playing this song, it will completely - * stop - */ - PLAYER_COMMAND_CANCEL, - - /** - * Refresh status information in the #player_control struct, - * e.g. elapsed_time. - */ - PLAYER_COMMAND_REFRESH, -}; - -enum player_error { - PLAYER_ERROR_NOERROR = 0, - PLAYER_ERROR_FILE, - PLAYER_ERROR_AUDIO, - PLAYER_ERROR_SYSTEM, - PLAYER_ERROR_UNKTYPE, - PLAYER_ERROR_FILENOTFOUND, -}; - -struct player_status { - enum player_state state; - uint16_t bit_rate; - struct audio_format audio_format; - float total_time; - float elapsed_time; -}; - -struct player_control { - unsigned buffer_chunks; - - unsigned int buffered_before_play; - - /** the handle of the player thread, or NULL if the player - thread isn't running */ - GThread *thread; - - /** - * This lock protects #command, #state, #error. - */ - GMutex *mutex; - - /** - * Trigger this object after you have modified #command. - */ - GCond *cond; - - enum player_command command; - enum player_state state; - enum player_error error; - uint16_t bit_rate; - struct audio_format audio_format; - float total_time; - float elapsed_time; - struct song *next_song; - const struct song *errored_song; - double seek_where; - float cross_fade_seconds; - float mixramp_db; - float mixramp_delay_seconds; - double total_play_time; - - /** - * If this flag is set, then the player will be auto-paused at - * the end of the song, before the next song starts to play. - * - * This is a copy of the queue's "single" flag most of the - * time. - */ - bool border_pause; -}; - -struct player_control * -pc_new(unsigned buffer_chunks, unsigned buffered_before_play); - -void -pc_free(struct player_control *pc); - -/** - * Locks the #player_control object. - */ -static inline void -player_lock(struct player_control *pc) -{ - g_mutex_lock(pc->mutex); -} - -/** - * Unlocks the #player_control object. - */ -static inline void -player_unlock(struct player_control *pc) -{ - g_mutex_unlock(pc->mutex); -} - -/** - * Waits for a signal on the #player_control object. This function is - * only valid in the player thread. The object must be locked prior - * to calling this function. - */ -static inline void -player_wait(struct player_control *pc) -{ - g_cond_wait(pc->cond, pc->mutex); -} - -/** - * Waits for a signal on the #player_control object. This function is - * only valid in the player thread. The #decoder_control object must - * be locked prior to calling this function. - * - * Note the small difference to the player_wait() function! - */ -void -player_wait_decoder(struct player_control *pc, struct decoder_control *dc); - -/** - * Signals the #player_control object. The object should be locked - * prior to calling this function. - */ -static inline void -player_signal(struct player_control *pc) -{ - g_cond_signal(pc->cond); -} - -/** - * Signals the #player_control object. The object is temporarily - * locked by this function. - */ -static inline void -player_lock_signal(struct player_control *pc) -{ - player_lock(pc); - player_signal(pc); - player_unlock(pc); -} - -/** - * Call this function when the specified song pointer is about to be - * invalidated. This makes sure that player_control.errored_song does - * not point to an invalid pointer. - */ -void -pc_song_deleted(struct player_control *pc, const struct song *song); - -void -pc_play(struct player_control *pc, struct song *song); - -/** - * see PLAYER_COMMAND_CANCEL - */ -void -pc_cancel(struct player_control *pc); - -void -pc_set_pause(struct player_control *pc, bool pause_flag); - -void -pc_pause(struct player_control *pc); - -/** - * Set the player's #border_pause flag. - */ -void -pc_set_border_pause(struct player_control *pc, bool border_pause); - -void -pc_kill(struct player_control *pc); - -void -pc_get_status(struct player_control *pc, struct player_status *status); - -enum player_state -pc_get_state(struct player_control *pc); - -void -pc_clear_error(struct player_control *pc); - -/** - * Returns the human-readable message describing the last error during - * playback, NULL if no error occurred. The caller has to free the - * returned string. - */ -char * -pc_get_error_message(struct player_control *pc); - -enum player_error -pc_get_error(struct player_control *pc); - -void -pc_stop(struct player_control *pc); - -void -pc_update_audio(struct player_control *pc); - -void -pc_enqueue_song(struct player_control *pc, struct song *song); - -/** - * Makes the player thread seek the specified song to a position. - * - * @return true on success, false on failure (e.g. if MPD isn't - * playing currently) - */ -bool -pc_seek(struct player_control *pc, struct song *song, float seek_time); - -void -pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds); - -float -pc_get_cross_fade(const struct player_control *pc); - -void -pc_set_mixramp_db(struct player_control *pc, float mixramp_db); - -float -pc_get_mixramp_db(const struct player_control *pc); - -void -pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds); - -float -pc_get_mixramp_delay(const struct player_control *pc); - -double -pc_get_total_play_time(const struct player_control *pc); - -#endif diff --git a/src/playlist.c b/src/playlist.c deleted file mode 100644 index dc6d8c34..00000000 --- a/src/playlist.c +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_internal.h" -#include "playlist_save.h" -#include "player_control.h" -#include "command.h" -#include "tag.h" -#include "song.h" -#include "conf.h" -#include "stored_playlist.h" -#include "idle.h" - -#include <glib.h> - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "playlist" - -void -playlist_increment_version_all(struct playlist *playlist) -{ - queue_modify_all(&playlist->queue); - idle_add(IDLE_PLAYLIST); -} - -void -playlist_tag_changed(struct playlist *playlist) -{ - if (!playlist->playing) - return; - - assert(playlist->current >= 0); - - queue_modify(&playlist->queue, playlist->current); - idle_add(IDLE_PLAYLIST); -} - -void -playlist_init(struct playlist *playlist) -{ - queue_init(&playlist->queue, - config_get_positive(CONF_MAX_PLAYLIST_LENGTH, - DEFAULT_PLAYLIST_MAX_LENGTH)); - - playlist->queued = -1; - playlist->current = -1; -} - -void -playlist_finish(struct playlist *playlist) -{ - queue_finish(&playlist->queue); -} - -/** - * Queue a song, addressed by its order number. - */ -static void -playlist_queue_song_order(struct playlist *playlist, struct player_control *pc, - unsigned order) -{ - struct song *song; - char *uri; - - assert(queue_valid_order(&playlist->queue, order)); - - playlist->queued = order; - - song = queue_get_order(&playlist->queue, order); - uri = song_get_uri(song); - g_debug("queue song %i:\"%s\"", playlist->queued, uri); - g_free(uri); - - pc_enqueue_song(pc, song); -} - -/** - * Called if the player thread has started playing the "queued" song. - */ -static void -playlist_song_started(struct playlist *playlist, struct player_control *pc) -{ - assert(pc->next_song == NULL); - assert(playlist->queued >= -1); - - /* queued song has started: copy queued to current, - and notify the clients */ - - int current = playlist->current; - playlist->current = playlist->queued; - playlist->queued = -1; - - if(playlist->queue.consume) - playlist_delete(playlist, pc, - queue_order_to_position(&playlist->queue, - current)); - - idle_add(IDLE_PLAYER); -} - -const struct song * -playlist_get_queued_song(struct playlist *playlist) -{ - if (!playlist->playing || playlist->queued < 0) - return NULL; - - return queue_get_order(&playlist->queue, playlist->queued); -} - -void -playlist_update_queued_song(struct playlist *playlist, - struct player_control *pc, - const struct song *prev) -{ - int next_order; - const struct song *next_song; - - if (!playlist->playing) - return; - - assert(!queue_is_empty(&playlist->queue)); - assert((playlist->queued < 0) == (prev == NULL)); - - next_order = playlist->current >= 0 - ? queue_next_order(&playlist->queue, playlist->current) - : 0; - - if (next_order == 0 && playlist->queue.random && - !playlist->queue.single) { - /* shuffle the song order again, so we get a different - order each time the playlist is played - completely */ - unsigned current_position = - queue_order_to_position(&playlist->queue, - playlist->current); - - queue_shuffle_order(&playlist->queue); - - /* make sure that the playlist->current still points to - the current song, after the song order has been - shuffled */ - playlist->current = - queue_position_to_order(&playlist->queue, - current_position); - } - - if (next_order >= 0) - next_song = queue_get_order(&playlist->queue, next_order); - else - next_song = NULL; - - if (prev != NULL && next_song != prev) { - /* clear the currently queued song */ - pc_cancel(pc); - playlist->queued = -1; - } - - if (next_order >= 0) { - if (next_song != prev) - playlist_queue_song_order(playlist, pc, next_order); - else - playlist->queued = next_order; - } -} - -void -playlist_play_order(struct playlist *playlist, struct player_control *pc, - int orderNum) -{ - struct song *song; - char *uri; - - playlist->playing = true; - playlist->queued = -1; - - song = queue_get_order(&playlist->queue, orderNum); - - uri = song_get_uri(song); - g_debug("play %i:\"%s\"", orderNum, uri); - g_free(uri); - - pc_play(pc, song); - playlist->current = orderNum; -} - -static void -playlist_resume_playback(struct playlist *playlist, struct player_control *pc); - -/** - * This is the "PLAYLIST" event handler. It is invoked by the player - * thread whenever it requests a new queued song, or when it exits. - */ -void -playlist_sync(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - /* this event has reached us out of sync: we aren't - playing anymore; ignore the event */ - return; - - player_lock(pc); - enum player_state pc_state = pc_get_state(pc); - const struct song *pc_next_song = pc->next_song; - player_unlock(pc); - - if (pc_state == PLAYER_STATE_STOP) - /* the player thread has stopped: check if playback - should be restarted with the next song. That can - happen if the playlist isn't filling the queue fast - enough */ - playlist_resume_playback(playlist, pc); - else { - /* check if the player thread has already started - playing the queued song */ - if (pc_next_song == NULL && playlist->queued != -1) - playlist_song_started(playlist, pc); - - player_lock(pc); - pc_next_song = pc->next_song; - player_unlock(pc); - - /* make sure the queued song is always set (if - possible) */ - if (pc_next_song == NULL && playlist->queued < 0) - playlist_update_queued_song(playlist, pc, NULL); - } -} - -/** - * The player has stopped for some reason. Check the error, and - * decide whether to re-start playback - */ -static void -playlist_resume_playback(struct playlist *playlist, struct player_control *pc) -{ - enum player_error error; - - assert(playlist->playing); - assert(pc_get_state(pc) == PLAYER_STATE_STOP); - - error = pc_get_error(pc); - if (error == PLAYER_ERROR_NOERROR) - playlist->error_count = 0; - else - ++playlist->error_count; - - if ((playlist->stop_on_error && error != PLAYER_ERROR_NOERROR) || - error == PLAYER_ERROR_AUDIO || error == PLAYER_ERROR_SYSTEM || - playlist->error_count >= queue_length(&playlist->queue)) - /* too many errors, or critical error: stop - playback */ - playlist_stop(playlist, pc); - else - /* continue playback at the next song */ - playlist_next(playlist, pc); -} - -bool -playlist_get_repeat(const struct playlist *playlist) -{ - return playlist->queue.repeat; -} - -bool -playlist_get_random(const struct playlist *playlist) -{ - return playlist->queue.random; -} - -bool -playlist_get_single(const struct playlist *playlist) -{ - return playlist->queue.single; -} - -bool -playlist_get_consume(const struct playlist *playlist) -{ - return playlist->queue.consume; -} - -void -playlist_set_repeat(struct playlist *playlist, struct player_control *pc, - bool status) -{ - if (status == playlist->queue.repeat) - return; - - struct queue *queue = &playlist->queue; - - queue->repeat = status; - - pc_set_border_pause(pc, queue->single && !queue->repeat); - - /* if the last song is currently being played, the "next song" - might change when repeat mode is toggled */ - playlist_update_queued_song(playlist, pc, - playlist_get_queued_song(playlist)); - - idle_add(IDLE_OPTIONS); -} - -static void -playlist_order(struct playlist *playlist) -{ - if (playlist->current >= 0) - /* update playlist.current, order==position now */ - playlist->current = queue_order_to_position(&playlist->queue, - playlist->current); - - queue_restore_order(&playlist->queue); -} - -void -playlist_set_single(struct playlist *playlist, struct player_control *pc, - bool status) -{ - if (status == playlist->queue.single) - return; - - struct queue *queue = &playlist->queue; - - queue->single = status; - - pc_set_border_pause(pc, queue->single && !queue->repeat); - - /* if the last song is currently being played, the "next song" - might change when single mode is toggled */ - playlist_update_queued_song(playlist, pc, - playlist_get_queued_song(playlist)); - - idle_add(IDLE_OPTIONS); -} - -void -playlist_set_consume(struct playlist *playlist, bool status) -{ - if (status == playlist->queue.consume) - return; - - playlist->queue.consume = status; - idle_add(IDLE_OPTIONS); -} - -void -playlist_set_random(struct playlist *playlist, struct player_control *pc, - bool status) -{ - const struct song *queued; - - if (status == playlist->queue.random) - return; - - queued = playlist_get_queued_song(playlist); - - playlist->queue.random = status; - - if (playlist->queue.random) { - /* shuffle the queue order, but preserve - playlist->current */ - - int current_position = - playlist->playing && playlist->current >= 0 - ? (int)queue_order_to_position(&playlist->queue, - playlist->current) - : -1; - - queue_shuffle_order(&playlist->queue); - - if (current_position >= 0) { - /* make sure the current song is the first in - the order list, so the whole rest of the - playlist is played after that */ - unsigned current_order = - queue_position_to_order(&playlist->queue, - current_position); - queue_swap_order(&playlist->queue, 0, current_order); - playlist->current = 0; - } else - playlist->current = -1; - } else - playlist_order(playlist); - - playlist_update_queued_song(playlist, pc, queued); - - idle_add(IDLE_OPTIONS); -} - -int -playlist_get_current_song(const struct playlist *playlist) -{ - if (playlist->current >= 0) - return queue_order_to_position(&playlist->queue, - playlist->current); - - return -1; -} - -int -playlist_get_next_song(const struct playlist *playlist) -{ - if (playlist->current >= 0) - { - if (playlist->queue.single == 1 && playlist->queue.repeat == 1) - return queue_order_to_position(&playlist->queue, - playlist->current); - else if (playlist->current + 1 < (int)queue_length(&playlist->queue)) - return queue_order_to_position(&playlist->queue, - playlist->current + 1); - else if (playlist->queue.repeat == 1) - return queue_order_to_position(&playlist->queue, 0); - } - - return -1; -} - -unsigned long -playlist_get_version(const struct playlist *playlist) -{ - return playlist->queue.version; -} - -int -playlist_get_length(const struct playlist *playlist) -{ - return queue_length(&playlist->queue); -} - -unsigned -playlist_get_song_id(const struct playlist *playlist, unsigned song) -{ - return queue_position_to_id(&playlist->queue, song); -} diff --git a/src/playlist.h b/src/playlist.h deleted file mode 100644 index a21bdf24..00000000 --- a/src/playlist.h +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_H -#define MPD_PLAYLIST_H - -#include "queue.h" -#include "playlist_error.h" - -#include <stdbool.h> - -struct player_control; - -struct playlist { - /** - * The song queue - it contains the "real" playlist. - */ - struct queue queue; - - /** - * This value is true if the player is currently playing (or - * should be playing). - */ - bool playing; - - /** - * If true, then any error is fatal; if false, MPD will - * attempt to play the next song on non-fatal errors. During - * seeking, this flag is set. - */ - bool stop_on_error; - - /** - * Number of errors since playback was started. If this - * number exceeds the length of the playlist, MPD gives up, - * because all songs have been tried. - */ - unsigned error_count; - - /** - * The "current song pointer". This is the song which is - * played when we get the "play" command. It is also the song - * which is currently being played. - */ - int current; - - /** - * The "next" song to be played, when the current one - * finishes. The decoder thread may start decoding and - * buffering it, while the "current" song is still playing. - * - * This variable is only valid if #playing is true. - */ - int queued; -}; - -/** the global playlist object */ -extern struct playlist g_playlist; - -void -playlist_global_init(void); - -void -playlist_global_finish(void); - -void -playlist_init(struct playlist *playlist); - -void -playlist_finish(struct playlist *playlist); - -void -playlist_tag_changed(struct playlist *playlist); - -/** - * Returns the "queue" object of the global playlist instance. - */ -static inline const struct queue * -playlist_get_queue(const struct playlist *playlist) -{ - return &playlist->queue; -} - -void -playlist_clear(struct playlist *playlist, struct player_control *pc); - -/** - * Appends a local file (outside the music database) to the playlist. - * - * Note: the caller is responsible for checking permissions. - */ -enum playlist_result -playlist_append_file(struct playlist *playlist, struct player_control *pc, - const char *path_fs, unsigned *added_id); - -enum playlist_result -playlist_append_uri(struct playlist *playlist, struct player_control *pc, - const char *file, unsigned *added_id); - -enum playlist_result -playlist_append_song(struct playlist *playlist, struct player_control *pc, - struct song *song, unsigned *added_id); - -enum playlist_result -playlist_delete(struct playlist *playlist, struct player_control *pc, - unsigned song); - -/** - * Deletes a range of songs from the playlist. - * - * @param start the position of the first song to delete - * @param end the position after the last song to delete - */ -enum playlist_result -playlist_delete_range(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end); - -enum playlist_result -playlist_delete_id(struct playlist *playlist, struct player_control *pc, - unsigned song); - -void -playlist_stop(struct playlist *playlist, struct player_control *pc); - -enum playlist_result -playlist_play(struct playlist *playlist, struct player_control *pc, - int song); - -enum playlist_result -playlist_play_id(struct playlist *playlist, struct player_control *pc, - int song); - -void -playlist_next(struct playlist *playlist, struct player_control *pc); - -void -playlist_sync(struct playlist *playlist, struct player_control *pc); - -void -playlist_previous(struct playlist *playlist, struct player_control *pc); - -void -playlist_shuffle(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end); - -void -playlist_delete_song(struct playlist *playlist, struct player_control *pc, - const struct song *song); - -enum playlist_result -playlist_move_range(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end, int to); - -enum playlist_result -playlist_move_id(struct playlist *playlist, struct player_control *pc, - unsigned id, int to); - -enum playlist_result -playlist_swap_songs(struct playlist *playlist, struct player_control *pc, - unsigned song1, unsigned song2); - -enum playlist_result -playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc, - unsigned id1, unsigned id2); - -enum playlist_result -playlist_set_priority(struct playlist *playlist, struct player_control *pc, - unsigned start_position, unsigned end_position, - uint8_t priority); - -enum playlist_result -playlist_set_priority_id(struct playlist *playlist, struct player_control *pc, - unsigned song_id, uint8_t priority); - -bool -playlist_get_repeat(const struct playlist *playlist); - -void -playlist_set_repeat(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_random(const struct playlist *playlist); - -void -playlist_set_random(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_single(const struct playlist *playlist); - -void -playlist_set_single(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_consume(const struct playlist *playlist); - -void -playlist_set_consume(struct playlist *playlist, bool status); - -int -playlist_get_current_song(const struct playlist *playlist); - -int -playlist_get_next_song(const struct playlist *playlist); - -unsigned -playlist_get_song_id(const struct playlist *playlist, unsigned song); - -int -playlist_get_length(const struct playlist *playlist); - -unsigned long -playlist_get_version(const struct playlist *playlist); - -enum playlist_result -playlist_seek_song(struct playlist *playlist, struct player_control *pc, - unsigned song, float seek_time); - -enum playlist_result -playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, - unsigned id, float seek_time); - -/** - * Seek within the current song. Fails if MPD is not currently - * playing. - * - * @param time the time in seconds - * @param relative if true, then the specified time is relative to the - * current position - */ -enum playlist_result -playlist_seek_current(struct playlist *playlist, struct player_control *pc, - float seek_time, bool relative); - -void -playlist_increment_version_all(struct playlist *playlist); - -#endif diff --git a/src/playlist/asx_playlist_plugin.c b/src/playlist/AsxPlaylistPlugin.cxx index 29868785..25319ca6 100644 --- a/src/playlist/asx_playlist_plugin.c +++ b/src/playlist/AsxPlaylistPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,8 @@ */ #include "config.h" -#include "playlist/asx_playlist_plugin.h" -#include "playlist_plugin.h" +#include "AsxPlaylistPlugin.hxx" +#include "MemoryPlaylistProvider.hxx" #include "input_stream.h" #include "song.h" #include "tag.h" @@ -35,12 +35,12 @@ /** * This is the state object for the GLib XML parser. */ -struct asx_parser { +struct AsxParser { /** * The list of songs (in reverse order because that's faster * while adding). */ - GSList *songs; + std::forward_list<SongPointer> songs; /** * The current position in the XML file. @@ -61,6 +61,10 @@ struct asx_parser { * element. */ struct song *song; + + AsxParser() + :state(ROOT) {} + }; static const gchar * @@ -81,19 +85,19 @@ asx_start_element(G_GNUC_UNUSED GMarkupParseContext *context, const gchar **attribute_values, gpointer user_data, G_GNUC_UNUSED GError **error) { - struct asx_parser *parser = user_data; + AsxParser *parser = (AsxParser *)user_data; switch (parser->state) { - case ROOT: + case AsxParser::ROOT: if (g_ascii_strcasecmp(element_name, "entry") == 0) { - parser->state = ENTRY; + parser->state = AsxParser::ENTRY; parser->song = song_remote_new("asx:"); parser->tag = TAG_NUM_OF_ITEM_TYPES; } break; - case ENTRY: + case AsxParser::ENTRY: if (g_ascii_strcasecmp(element_name, "ref") == 0) { const gchar *href = get_attribute(attribute_names, attribute_values, @@ -130,21 +134,20 @@ asx_end_element(G_GNUC_UNUSED GMarkupParseContext *context, const gchar *element_name, gpointer user_data, G_GNUC_UNUSED GError **error) { - struct asx_parser *parser = user_data; + AsxParser *parser = (AsxParser *)user_data; switch (parser->state) { - case ROOT: + case AsxParser::ROOT: break; - case ENTRY: + case AsxParser::ENTRY: if (g_ascii_strcasecmp(element_name, "entry") == 0) { if (strcmp(parser->song->uri, "asx:") != 0) - parser->songs = g_slist_prepend(parser->songs, - parser->song); + parser->songs.emplace_front(parser->song); else song_free(parser->song); - parser->state = ROOT; + parser->state = AsxParser::ROOT; } else parser->tag = TAG_NUM_OF_ITEM_TYPES; @@ -157,13 +160,13 @@ asx_text(G_GNUC_UNUSED GMarkupParseContext *context, const gchar *text, gsize text_len, gpointer user_data, G_GNUC_UNUSED GError **error) { - struct asx_parser *parser = user_data; + AsxParser *parser = (AsxParser *)user_data; switch (parser->state) { - case ROOT: + case AsxParser::ROOT: break; - case ENTRY: + case AsxParser::ENTRY: if (parser->tag != TAG_NUM_OF_ITEM_TYPES) { if (parser->song->tag == NULL) parser->song->tag = tag_new(); @@ -176,29 +179,20 @@ asx_text(G_GNUC_UNUSED GMarkupParseContext *context, } static const GMarkupParser asx_parser = { - .start_element = asx_start_element, - .end_element = asx_end_element, - .text = asx_text, + asx_start_element, + asx_end_element, + asx_text, + nullptr, + nullptr, }; static void -song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct song *song = data; - - song_free(song); -} - -static void asx_parser_destroy(gpointer data) { - struct asx_parser *parser = data; + AsxParser *parser = (AsxParser *)data; - if (parser->state >= ENTRY) + if (parser->state >= AsxParser::ENTRY) song_free(parser->song); - - g_slist_foreach(parser->songs, song_free_callback, NULL); - g_slist_free(parser->songs); } /* @@ -206,20 +200,10 @@ asx_parser_destroy(gpointer data) * */ -struct asx_playlist { - struct playlist_provider base; - - GSList *songs; -}; - static struct playlist_provider * asx_open_stream(struct input_stream *is) { - struct asx_parser parser = { - .songs = NULL, - .state = ROOT, - }; - struct asx_playlist *playlist; + AsxParser parser; GMarkupParseContext *context; char buffer[1024]; size_t nbytes; @@ -264,41 +248,13 @@ asx_open_stream(struct input_stream *is) return NULL; } - /* create a #asx_playlist object from the parsed song list */ - - playlist = g_new(struct asx_playlist, 1); - playlist_provider_init(&playlist->base, &asx_playlist_plugin); - playlist->songs = g_slist_reverse(parser.songs); - parser.songs = NULL; + parser.songs.reverse(); + MemoryPlaylistProvider *playlist = + new MemoryPlaylistProvider(std::move(parser.songs)); g_markup_parse_context_free(context); - return &playlist->base; -} - -static void -asx_close(struct playlist_provider *_playlist) -{ - struct asx_playlist *playlist = (struct asx_playlist *)_playlist; - - g_slist_foreach(playlist->songs, song_free_callback, NULL); - g_slist_free(playlist->songs); - g_free(playlist); -} - -static struct song * -asx_read(struct playlist_provider *_playlist) -{ - struct asx_playlist *playlist = (struct asx_playlist *)_playlist; - struct song *song; - - if (playlist->songs == NULL) - return NULL; - - song = playlist->songs->data; - playlist->songs = g_slist_remove(playlist->songs, song); - - return song; + return playlist; } static const char *const asx_suffixes[] = { @@ -312,12 +268,16 @@ static const char *const asx_mime_types[] = { }; const struct playlist_plugin asx_playlist_plugin = { - .name = "asx", + "asx", - .open_stream = asx_open_stream, - .close = asx_close, - .read = asx_read, + nullptr, + nullptr, + nullptr, + asx_open_stream, + nullptr, + nullptr, - .suffixes = asx_suffixes, - .mime_types = asx_mime_types, + nullptr, + asx_suffixes, + asx_mime_types, }; diff --git a/src/playlist/asx_playlist_plugin.h b/src/playlist/AsxPlaylistPlugin.hxx index 6c01c120..240c1824 100644 --- a/src/playlist/asx_playlist_plugin.h +++ b/src/playlist/AsxPlaylistPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H +#ifndef MPD_ASX_PLAYLIST_PLUGIN_HXX +#define MPD_ASX_PLAYLIST_PLUGIN_HXX extern const struct playlist_plugin asx_playlist_plugin; diff --git a/src/playlist/cue_playlist_plugin.c b/src/playlist/CuePlaylistPlugin.cxx index b85de77d..07eb5e24 100644 --- a/src/playlist/cue_playlist_plugin.c +++ b/src/playlist/CuePlaylistPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,16 @@ */ #include "config.h" -#include "playlist/cue_playlist_plugin.h" -#include "playlist_plugin.h" +#include "CuePlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" #include "tag.h" #include "song.h" -#include "cue/cue_parser.h" #include "input_stream.h" + +extern "C" { #include "text_input_stream.h" +#include "cue/cue_parser.h" +} #include <glib.h> #include <assert.h> @@ -33,7 +36,7 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "cue" -struct cue_playlist { +struct CuePlaylist { struct playlist_provider base; struct input_stream *is; @@ -44,21 +47,20 @@ struct cue_playlist { static struct playlist_provider * cue_playlist_open_stream(struct input_stream *is) { - struct cue_playlist *playlist = g_new(struct cue_playlist, 1); + CuePlaylist *playlist = g_new(CuePlaylist, 1); playlist_provider_init(&playlist->base, &cue_playlist_plugin); playlist->is = is; playlist->tis = text_input_stream_new(is); playlist->parser = cue_parser_new(); - return &playlist->base; } static void cue_playlist_close(struct playlist_provider *_playlist) { - struct cue_playlist *playlist = (struct cue_playlist *)_playlist; + CuePlaylist *playlist = (CuePlaylist *)_playlist; cue_parser_free(playlist->parser); text_input_stream_free(playlist->tis); @@ -68,7 +70,7 @@ cue_playlist_close(struct playlist_provider *_playlist) static struct song * cue_playlist_read(struct playlist_provider *_playlist) { - struct cue_playlist *playlist = (struct cue_playlist *)_playlist; + CuePlaylist *playlist = (CuePlaylist *)_playlist; struct song *song = cue_parser_get(playlist->parser); if (song != NULL) @@ -97,12 +99,16 @@ static const char *const cue_playlist_mime_types[] = { }; const struct playlist_plugin cue_playlist_plugin = { - .name = "cue", + "cue", - .open_stream = cue_playlist_open_stream, - .close = cue_playlist_close, - .read = cue_playlist_read, + nullptr, + nullptr, + nullptr, + cue_playlist_open_stream, + cue_playlist_close, + cue_playlist_read, - .suffixes = cue_playlist_suffixes, - .mime_types = cue_playlist_mime_types, + nullptr, + cue_playlist_suffixes, + cue_playlist_mime_types, }; diff --git a/src/playlist/cue_playlist_plugin.h b/src/playlist/CuePlaylistPlugin.hxx index c02e2235..cf5e3a8f 100644 --- a/src/playlist/cue_playlist_plugin.h +++ b/src/playlist/CuePlaylistPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H +#ifndef MPD_CUE_PLAYLIST_PLUGIN_HXX +#define MPD_CUE_PLAYLIST_PLUGIN_HXX extern const struct playlist_plugin cue_playlist_plugin; diff --git a/src/playlist/DespotifyPlaylistPlugin.cxx b/src/playlist/DespotifyPlaylistPlugin.cxx new file mode 100644 index 00000000..25f12785 --- /dev/null +++ b/src/playlist/DespotifyPlaylistPlugin.cxx @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DespotifyPlaylistPlugin.hxx" +#include "DespotifyUtils.hxx" +#include "MemoryPlaylistProvider.hxx" +#include "tag.h" +#include "song.h" + +extern "C" { +#include <despotify.h> +} + +#include <glib.h> + +#include <string.h> +#include <stdlib.h> + +static void +add_song(std::forward_list<SongPointer> &songs, struct ds_track *track) +{ + const char *dsp_scheme = despotify_playlist_plugin.schemes[0]; + struct song *song; + char uri[128]; + char *ds_uri; + + /* Create a spt://... URI for MPD */ + g_snprintf(uri, sizeof(uri), "%s://", dsp_scheme); + ds_uri = uri + strlen(dsp_scheme) + 3; + + if (despotify_track_to_uri(track, ds_uri) != ds_uri) { + /* Should never really fail, but let's be sure */ + g_debug("Can't add track %s\n", track->title); + return; + } + + song = song_remote_new(uri); + song->tag = mpd_despotify_tag_from_track(track); + + songs.emplace_front(song); +} + +static bool +parse_track(struct despotify_session *session, + std::forward_list<SongPointer> &songs, + struct ds_link *link) +{ + struct ds_track *track = despotify_link_get_track(session, link); + if (track == nullptr) + return false; + + add_song(songs, track); + return true; +} + +static bool +parse_playlist(struct despotify_session *session, + std::forward_list<SongPointer> &songs, + struct ds_link *link) +{ + ds_playlist *playlist = despotify_link_get_playlist(session, link); + if (playlist == nullptr) + return false; + + for (ds_track *track = playlist->tracks; track != nullptr; + track = track->next) + add_song(songs, track); + + return true; +} + +static struct playlist_provider * +despotify_playlist_open_uri(const char *url, + gcc_unused Mutex &mutex, gcc_unused Cond &cond) +{ + despotify_session *session = mpd_despotify_get_session(); + if (session == nullptr) + return nullptr; + + /* Get link without spt:// */ + ds_link *link = + despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3); + if (link == nullptr) { + g_debug("Can't find %s\n", url); + return nullptr; + } + + std::forward_list<SongPointer> songs; + + bool parse_result; + switch (link->type) { + case LINK_TYPE_TRACK: + parse_result = parse_track(session, songs, link); + break; + case LINK_TYPE_PLAYLIST: + parse_result = parse_playlist(session, songs, link); + break; + default: + parse_result = false; + break; + } + + despotify_free_link(link); + if (!parse_result) + return nullptr; + + songs.reverse(); + return new MemoryPlaylistProvider(std::move(songs)); +} + +static const char *const despotify_schemes[] = { + "spt", + nullptr +}; + +const struct playlist_plugin despotify_playlist_plugin = { + "despotify", + + nullptr, + nullptr, + despotify_playlist_open_uri, + nullptr, + nullptr, + nullptr, + + despotify_schemes, + nullptr, + nullptr, +}; diff --git a/src/playlist/despotify_playlist_plugin.h b/src/playlist/DespotifyPlaylistPlugin.hxx index f8ee20de..c1e5b7f3 100644 --- a/src/playlist/despotify_playlist_plugin.h +++ b/src/playlist/DespotifyPlaylistPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Music Player Daemon Project + * 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 @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_H +#ifndef MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX +#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX extern const struct playlist_plugin despotify_playlist_plugin; diff --git a/src/playlist/embcue_playlist_plugin.c b/src/playlist/EmbeddedCuePlaylistPlugin.cxx index 6d9a957f..04cb12ec 100644 --- a/src/playlist/embcue_playlist_plugin.c +++ b/src/playlist/EmbeddedCuePlaylistPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,15 +24,18 @@ */ #include "config.h" -#include "playlist/embcue_playlist_plugin.h" -#include "playlist_plugin.h" +#include "EmbeddedCuePlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" #include "tag.h" #include "tag_handler.h" -#include "tag_file.h" +#include "song.h" +#include "TagFile.hxx" + +extern "C" { #include "tag_ape.h" #include "tag_id3.h" -#include "song.h" #include "cue/cue_parser.h" +} #include <glib.h> #include <assert.h> @@ -67,7 +70,7 @@ struct embcue_playlist { static void embcue_tag_pair(const char *name, const char *value, void *ctx) { - struct embcue_playlist *playlist = ctx; + struct embcue_playlist *playlist = (struct embcue_playlist *)ctx; if (playlist->cuesheet == NULL && g_ascii_strcasecmp(name, "cuesheet") == 0) @@ -75,13 +78,15 @@ embcue_tag_pair(const char *name, const char *value, void *ctx) } static const struct tag_handler embcue_tag_handler = { - .pair = embcue_tag_pair, + nullptr, + nullptr, + embcue_tag_pair, }; static struct playlist_provider * embcue_playlist_open_uri(const char *uri, - G_GNUC_UNUSED GMutex *mutex, - G_GNUC_UNUSED GCond *cond) + gcc_unused Mutex &mutex, + gcc_unused Cond &cond) { if (!g_path_is_absolute(uri)) /* only local files supported */ @@ -170,12 +175,16 @@ static const char *const embcue_playlist_suffixes[] = { }; const struct playlist_plugin embcue_playlist_plugin = { - .name = "cue", - - .open_uri = embcue_playlist_open_uri, - .close = embcue_playlist_close, - .read = embcue_playlist_read, - - .suffixes = embcue_playlist_suffixes, - .mime_types = NULL, + "cue", + + nullptr, + nullptr, + embcue_playlist_open_uri, + nullptr, + embcue_playlist_close, + embcue_playlist_read, + + embcue_playlist_suffixes, + nullptr, + nullptr, }; diff --git a/src/playlist/embcue_playlist_plugin.h b/src/playlist/EmbeddedCuePlaylistPlugin.hxx index c5f21b27..e306730f 100644 --- a/src/playlist/embcue_playlist_plugin.h +++ b/src/playlist/EmbeddedCuePlaylistPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_EMBCUE_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_EMBCUE_PLAYLIST_PLUGIN_H +#ifndef MPD_EMBCUE_PLAYLIST_PLUGIN_HXX +#define MPD_EMBCUE_PLAYLIST_PLUGIN_HXX extern const struct playlist_plugin embcue_playlist_plugin; diff --git a/src/playlist/extm3u_playlist_plugin.c b/src/playlist/ExtM3uPlaylistPlugin.cxx index 19be8d1c..ce026dab 100644 --- a/src/playlist/extm3u_playlist_plugin.c +++ b/src/playlist/ExtM3uPlaylistPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,20 +18,22 @@ */ #include "config.h" -#include "playlist/extm3u_playlist_plugin.h" -#include "playlist_plugin.h" -#include "text_input_stream.h" -#include "uri.h" +#include "ExtM3uPlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" #include "song.h" #include "tag.h" #include "string_util.h" +extern "C" { +#include "text_input_stream.h" +} + #include <glib.h> #include <string.h> #include <stdlib.h> -struct extm3u_playlist { +struct ExtM3uPlaylist { struct playlist_provider base; struct text_input_stream *tis; @@ -40,13 +42,10 @@ struct extm3u_playlist { static struct playlist_provider * extm3u_open_stream(struct input_stream *is) { - struct extm3u_playlist *playlist; - const char *line; - - playlist = g_new(struct extm3u_playlist, 1); + ExtM3uPlaylist *playlist = g_new(ExtM3uPlaylist, 1); playlist->tis = text_input_stream_new(is); - line = text_input_stream_read(playlist->tis); + const char *line = text_input_stream_read(playlist->tis); if (line == NULL || strcmp(line, "#EXTM3U") != 0) { /* no EXTM3U header: fall back to the plain m3u plugin */ @@ -62,7 +61,7 @@ extm3u_open_stream(struct input_stream *is) static void extm3u_close(struct playlist_provider *_playlist) { - struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist; + ExtM3uPlaylist *playlist = (ExtM3uPlaylist *)_playlist; text_input_stream_free(playlist->tis); g_free(playlist); @@ -111,7 +110,7 @@ extm3u_parse_tag(const char *line) static struct song * extm3u_read(struct playlist_provider *_playlist) { - struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist; + ExtM3uPlaylist *playlist = (ExtM3uPlaylist *)_playlist; struct tag *tag = NULL; const char *line; struct song *song; @@ -151,12 +150,16 @@ static const char *const extm3u_mime_types[] = { }; const struct playlist_plugin extm3u_playlist_plugin = { - .name = "extm3u", - - .open_stream = extm3u_open_stream, - .close = extm3u_close, - .read = extm3u_read, - - .suffixes = extm3u_suffixes, - .mime_types = extm3u_mime_types, + "extm3u", + + nullptr, + nullptr, + nullptr, + extm3u_open_stream, + extm3u_close, + extm3u_read, + + nullptr, + extm3u_suffixes, + extm3u_mime_types, }; diff --git a/src/playlist/extm3u_playlist_plugin.h b/src/playlist/ExtM3uPlaylistPlugin.hxx index 5f611ac9..844fba15 100644 --- a/src/playlist/extm3u_playlist_plugin.h +++ b/src/playlist/ExtM3uPlaylistPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H +#ifndef MPD_EXTM3U_PLAYLIST_PLUGIN_HXX +#define MPD_EXTM3U_PLAYLIST_PLUGIN_HXX extern const struct playlist_plugin extm3u_playlist_plugin; diff --git a/src/playlist/lastfm_playlist_plugin.c b/src/playlist/LastFMPlaylistPlugin.cxx index ead14dea..49638840 100644 --- a/src/playlist/lastfm_playlist_plugin.c +++ b/src/playlist/LastFMPlaylistPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,10 @@ */ #include "config.h" -#include "playlist/lastfm_playlist_plugin.h" -#include "playlist_plugin.h" -#include "playlist_list.h" +#include "LastFMPlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" +#include "PlaylistRegistry.hxx" #include "conf.h" -#include "uri.h" #include "song.h" #include "input_stream.h" @@ -80,7 +79,7 @@ lastfm_finish(void) * @return data fetched, or NULL on error. Must be freed with g_free. */ static char * -lastfm_get(const char *url, GMutex *mutex, GCond *cond) +lastfm_get(const char *url, Mutex &mutex, Cond &cond) { struct input_stream *input_stream; GError *error = NULL; @@ -97,7 +96,7 @@ lastfm_get(const char *url, GMutex *mutex, GCond *cond) return NULL; } - g_mutex_lock(mutex); + mutex.lock(); input_stream_wait_ready(input_stream); @@ -114,7 +113,7 @@ lastfm_get(const char *url, GMutex *mutex, GCond *cond) break; /* I/O error */ - g_mutex_unlock(mutex); + mutex.unlock(); input_stream_close(input_stream); return NULL; } @@ -122,7 +121,7 @@ lastfm_get(const char *url, GMutex *mutex, GCond *cond) length += nbytes; } while (length < sizeof(buffer)); - g_mutex_unlock(mutex); + mutex.unlock(); input_stream_close(input_stream); return g_strndup(buffer, length); @@ -155,7 +154,7 @@ lastfm_find(const char *response, const char *name) } static struct playlist_provider * -lastfm_open_uri(const char *uri, GMutex *mutex, GCond *cond) +lastfm_open_uri(const char *uri, Mutex &mutex, Cond &cond) { struct lastfm_playlist *playlist; GError *error = NULL; @@ -236,16 +235,15 @@ lastfm_open_uri(const char *uri, GMutex *mutex, GCond *cond) return NULL; } - g_mutex_lock(mutex); + mutex.lock(); input_stream_wait_ready(playlist->is); /* last.fm does not send a MIME type, we have to fake it here :-( */ - g_free(playlist->is->mime); - playlist->is->mime = g_strdup("application/xspf+xml"); + input_stream_override_mime_type(playlist->is, "application/xspf+xml"); - g_mutex_unlock(mutex); + mutex.unlock(); /* parse the XSPF playlist */ @@ -284,13 +282,16 @@ static const char *const lastfm_schemes[] = { }; const struct playlist_plugin lastfm_playlist_plugin = { - .name = "lastfm", + "lastfm", - .init = lastfm_init, - .finish = lastfm_finish, - .open_uri = lastfm_open_uri, - .close = lastfm_close, - .read = lastfm_read, + lastfm_init, + lastfm_finish, + lastfm_open_uri, + nullptr, + lastfm_close, + lastfm_read, - .schemes = lastfm_schemes, + lastfm_schemes, + nullptr, + nullptr, }; diff --git a/src/playlist/lastfm_playlist_plugin.h b/src/playlist/LastFMPlaylistPlugin.hxx index 46a8b0ca..fe0e206d 100644 --- a/src/playlist/lastfm_playlist_plugin.h +++ b/src/playlist/LastFMPlaylistPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H +#ifndef MPD_LASTFM_PLAYLIST_PLUGIN_HXX +#define MPD_LASTFM_PLAYLIST_PLUGIN_HXX extern const struct playlist_plugin lastfm_playlist_plugin; diff --git a/src/playlist/m3u_playlist_plugin.c b/src/playlist/M3uPlaylistPlugin.cxx index 45b70d2b..eeecd277 100644 --- a/src/playlist/m3u_playlist_plugin.c +++ b/src/playlist/M3uPlaylistPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,15 +18,17 @@ */ #include "config.h" -#include "playlist/m3u_playlist_plugin.h" -#include "playlist_plugin.h" -#include "text_input_stream.h" -#include "uri.h" +#include "M3uPlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" #include "song.h" +extern "C" { +#include "text_input_stream.h" +} + #include <glib.h> -struct m3u_playlist { +struct M3uPlaylist { struct playlist_provider base; struct text_input_stream *tis; @@ -35,7 +37,7 @@ struct m3u_playlist { static struct playlist_provider * m3u_open_stream(struct input_stream *is) { - struct m3u_playlist *playlist = g_new(struct m3u_playlist, 1); + M3uPlaylist *playlist = g_new(M3uPlaylist, 1); playlist_provider_init(&playlist->base, &m3u_playlist_plugin); playlist->tis = text_input_stream_new(is); @@ -46,7 +48,7 @@ m3u_open_stream(struct input_stream *is) static void m3u_close(struct playlist_provider *_playlist) { - struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist; + M3uPlaylist *playlist = (M3uPlaylist *)_playlist; text_input_stream_free(playlist->tis); g_free(playlist); @@ -55,7 +57,7 @@ m3u_close(struct playlist_provider *_playlist) static struct song * m3u_read(struct playlist_provider *_playlist) { - struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist; + M3uPlaylist *playlist = (M3uPlaylist *)_playlist; const char *line; do { @@ -81,12 +83,16 @@ static const char *const m3u_mime_types[] = { }; const struct playlist_plugin m3u_playlist_plugin = { - .name = "m3u", + "m3u", - .open_stream = m3u_open_stream, - .close = m3u_close, - .read = m3u_read, + nullptr, + nullptr, + nullptr, + m3u_open_stream, + m3u_close, + m3u_read, - .suffixes = m3u_suffixes, - .mime_types = m3u_mime_types, + nullptr, + m3u_suffixes, + m3u_mime_types, }; diff --git a/src/playlist/m3u_playlist_plugin.h b/src/playlist/M3uPlaylistPlugin.hxx index 3890a5fc..a2058bb2 100644 --- a/src/playlist/m3u_playlist_plugin.h +++ b/src/playlist/M3uPlaylistPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H +#ifndef MPD_M3U_PLAYLIST_PLUGIN_HXX +#define MPD_M3U_PLAYLIST_PLUGIN_HXX extern const struct playlist_plugin m3u_playlist_plugin; diff --git a/src/playlist/MemoryPlaylistProvider.cxx b/src/playlist/MemoryPlaylistProvider.cxx new file mode 100644 index 00000000..4fe3d6ce --- /dev/null +++ b/src/playlist/MemoryPlaylistProvider.cxx @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MemoryPlaylistProvider.hxx" +#include "song.h" + +static void +memory_playlist_close(struct playlist_provider *_playlist) +{ + MemoryPlaylistProvider *playlist = (MemoryPlaylistProvider *)_playlist; + + delete playlist; +} + +static struct song * +memory_playlist_read(struct playlist_provider *_playlist) +{ + MemoryPlaylistProvider *playlist = (MemoryPlaylistProvider *)_playlist; + + return playlist->Read(); +} + +static constexpr struct playlist_plugin memory_playlist_plugin = { + nullptr, + + nullptr, + nullptr, + nullptr, + nullptr, + memory_playlist_close, + memory_playlist_read, + + nullptr, + nullptr, + nullptr, +}; + +MemoryPlaylistProvider::MemoryPlaylistProvider(std::forward_list<SongPointer> &&_songs) + :songs(std::move(_songs)) { + playlist_provider_init(this, &memory_playlist_plugin); +} + +inline song * +MemoryPlaylistProvider::Read() +{ + if (songs.empty()) + return NULL; + + auto result = songs.front().Steal(); + songs.pop_front(); + return result; +} diff --git a/src/db/simple_db_plugin.h b/src/playlist/MemoryPlaylistProvider.hxx index 51150584..246ffd10 100644 --- a/src/db/simple_db_plugin.h +++ b/src/playlist/MemoryPlaylistProvider.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,26 +17,23 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_SIMPLE_DB_PLUGIN_H -#define MPD_SIMPLE_DB_PLUGIN_H +#ifndef MPD_MEMORY_PLAYLIST_PROVIDER_HXX +#define MPD_MEMORY_PLAYLIST_PROVIDER_HXX -#include <glib.h> -#include <stdbool.h> -#include <time.h> +#include "PlaylistPlugin.hxx" +#include "SongPointer.hxx" -extern const struct db_plugin simple_db_plugin; +#include <forward_list> -struct db; +struct song; -G_GNUC_PURE -struct directory * -simple_db_get_root(struct db *db); +class MemoryPlaylistProvider : public playlist_provider { + std::forward_list<SongPointer> songs; -bool -simple_db_save(struct db *db, GError **error_r); +public: + MemoryPlaylistProvider(std::forward_list<SongPointer> &&_songs); -G_GNUC_PURE -time_t -simple_db_get_mtime(const struct db *db); + song *Read(); +}; #endif diff --git a/src/playlist/pls_playlist_plugin.c b/src/playlist/PlsPlaylistPlugin.cxx index c4e5492a..3cf5f46e 100644 --- a/src/playlist/pls_playlist_plugin.c +++ b/src/playlist/PlsPlaylistPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,21 +18,17 @@ */ #include "config.h" -#include "playlist/pls_playlist_plugin.h" -#include "playlist_plugin.h" +#include "PlsPlaylistPlugin.hxx" +#include "MemoryPlaylistProvider.hxx" #include "input_stream.h" #include "uri.h" #include "song.h" #include "tag.h" -#include <glib.h> - -struct pls_playlist { - struct playlist_provider base; - GSList *songs; -}; +#include <glib.h> -static void pls_parser(GKeyFile *keyfile, struct pls_playlist *playlist) +static void +pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs) { gchar *key; gchar *value; @@ -97,7 +93,7 @@ static void pls_parser(GKeyFile *keyfile, struct pls_playlist *playlist) if(error) g_error_free(error); error = NULL; - playlist->songs = g_slist_prepend(playlist->songs, song); + songs.emplace_front(song); num_entries--; } @@ -111,7 +107,6 @@ pls_open_stream(struct input_stream *is) char buffer[1024]; bool success; GKeyFile *keyfile; - struct pls_playlist *playlist; GString *kf_data = g_string_new(""); do { @@ -152,50 +147,12 @@ pls_open_stream(struct input_stream *is) return NULL; } - playlist = g_new(struct pls_playlist, 1); - playlist_provider_init(&playlist->base, &pls_playlist_plugin); - playlist->songs = NULL; - - pls_parser(keyfile, playlist); - + std::forward_list<SongPointer> songs; + pls_parser(keyfile, songs); g_key_file_free(keyfile); - return &playlist->base; -} - -static void -song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct song *song = data; - - song_free(song); -} - -static void -pls_close(struct playlist_provider *_playlist) -{ - struct pls_playlist *playlist = (struct pls_playlist *)_playlist; - - g_slist_foreach(playlist->songs, song_free_callback, NULL); - g_slist_free(playlist->songs); - - g_free(playlist); - -} - -static struct song * -pls_read(struct playlist_provider *_playlist) -{ - struct pls_playlist *playlist = (struct pls_playlist *)_playlist; - struct song *song; - - if (playlist->songs == NULL) - return NULL; - - song = playlist->songs->data; - playlist->songs = g_slist_remove(playlist->songs, song); - - return song; + songs.reverse(); + return new MemoryPlaylistProvider(std::move(songs)); } static const char *const pls_suffixes[] = { @@ -209,12 +166,16 @@ static const char *const pls_mime_types[] = { }; const struct playlist_plugin pls_playlist_plugin = { - .name = "pls", + "pls", - .open_stream = pls_open_stream, - .close = pls_close, - .read = pls_read, + nullptr, + nullptr, + nullptr, + pls_open_stream, + nullptr, + nullptr, - .suffixes = pls_suffixes, - .mime_types = pls_mime_types, + nullptr, + pls_suffixes, + pls_mime_types, }; diff --git a/src/playlist/pls_playlist_plugin.h b/src/playlist/PlsPlaylistPlugin.hxx index d03435f6..3fafd36d 100644 --- a/src/playlist/pls_playlist_plugin.h +++ b/src/playlist/PlsPlaylistPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H +#ifndef MPD_PLS_PLAYLIST_PLUGIN_HXX +#define MPD_PLS_PLAYLIST_PLUGIN_HXX extern const struct playlist_plugin pls_playlist_plugin; diff --git a/src/playlist/rss_playlist_plugin.c b/src/playlist/RssPlaylistPlugin.cxx index 6740cba7..3b69202e 100644 --- a/src/playlist/rss_playlist_plugin.c +++ b/src/playlist/RssPlaylistPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,8 @@ */ #include "config.h" -#include "playlist/rss_playlist_plugin.h" -#include "playlist_plugin.h" +#include "RssPlaylistPlugin.hxx" +#include "MemoryPlaylistProvider.hxx" #include "input_stream.h" #include "song.h" #include "tag.h" @@ -35,12 +35,12 @@ /** * This is the state object for the GLib XML parser. */ -struct rss_parser { +struct RssParser { /** * The list of songs (in reverse order because that's faster * while adding). */ - GSList *songs; + std::forward_list<SongPointer> songs; /** * The current position in the XML file. @@ -61,6 +61,9 @@ struct rss_parser { * element. */ struct song *song; + + RssParser() + :state(ROOT) {} }; static const gchar * @@ -81,19 +84,19 @@ rss_start_element(G_GNUC_UNUSED GMarkupParseContext *context, const gchar **attribute_values, gpointer user_data, G_GNUC_UNUSED GError **error) { - struct rss_parser *parser = user_data; + RssParser *parser = (RssParser *)user_data; switch (parser->state) { - case ROOT: + case RssParser::ROOT: if (g_ascii_strcasecmp(element_name, "item") == 0) { - parser->state = ITEM; + parser->state = RssParser::ITEM; parser->song = song_remote_new("rss:"); parser->tag = TAG_NUM_OF_ITEM_TYPES; } break; - case ITEM: + case RssParser::ITEM: if (g_ascii_strcasecmp(element_name, "enclosure") == 0) { const gchar *href = get_attribute(attribute_names, attribute_values, @@ -128,21 +131,20 @@ rss_end_element(G_GNUC_UNUSED GMarkupParseContext *context, const gchar *element_name, gpointer user_data, G_GNUC_UNUSED GError **error) { - struct rss_parser *parser = user_data; + RssParser *parser = (RssParser *)user_data; switch (parser->state) { - case ROOT: + case RssParser::ROOT: break; - case ITEM: + case RssParser::ITEM: if (g_ascii_strcasecmp(element_name, "item") == 0) { if (strcmp(parser->song->uri, "rss:") != 0) - parser->songs = g_slist_prepend(parser->songs, - parser->song); + parser->songs.emplace_front(parser->song); else song_free(parser->song); - parser->state = ROOT; + parser->state = RssParser::ROOT; } else parser->tag = TAG_NUM_OF_ITEM_TYPES; @@ -155,13 +157,13 @@ rss_text(G_GNUC_UNUSED GMarkupParseContext *context, const gchar *text, gsize text_len, gpointer user_data, G_GNUC_UNUSED GError **error) { - struct rss_parser *parser = user_data; + RssParser *parser = (RssParser *)user_data; switch (parser->state) { - case ROOT: + case RssParser::ROOT: break; - case ITEM: + case RssParser::ITEM: if (parser->tag != TAG_NUM_OF_ITEM_TYPES) { if (parser->song->tag == NULL) parser->song->tag = tag_new(); @@ -174,29 +176,20 @@ rss_text(G_GNUC_UNUSED GMarkupParseContext *context, } static const GMarkupParser rss_parser = { - .start_element = rss_start_element, - .end_element = rss_end_element, - .text = rss_text, + rss_start_element, + rss_end_element, + rss_text, + nullptr, + nullptr, }; static void -song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct song *song = data; - - song_free(song); -} - -static void rss_parser_destroy(gpointer data) { - struct rss_parser *parser = data; + RssParser *parser = (RssParser *)data; - if (parser->state >= ITEM) + if (parser->state >= RssParser::ITEM) song_free(parser->song); - - g_slist_foreach(parser->songs, song_free_callback, NULL); - g_slist_free(parser->songs); } /* @@ -204,20 +197,10 @@ rss_parser_destroy(gpointer data) * */ -struct rss_playlist { - struct playlist_provider base; - - GSList *songs; -}; - static struct playlist_provider * rss_open_stream(struct input_stream *is) { - struct rss_parser parser = { - .songs = NULL, - .state = ROOT, - }; - struct rss_playlist *playlist; + RssParser parser; GMarkupParseContext *context; char buffer[1024]; size_t nbytes; @@ -262,41 +245,13 @@ rss_open_stream(struct input_stream *is) return NULL; } - /* create a #rss_playlist object from the parsed song list */ - - playlist = g_new(struct rss_playlist, 1); - playlist_provider_init(&playlist->base, &rss_playlist_plugin); - playlist->songs = g_slist_reverse(parser.songs); - parser.songs = NULL; + parser.songs.reverse(); + MemoryPlaylistProvider *playlist = + new MemoryPlaylistProvider(std::move(parser.songs)); g_markup_parse_context_free(context); - return &playlist->base; -} - -static void -rss_close(struct playlist_provider *_playlist) -{ - struct rss_playlist *playlist = (struct rss_playlist *)_playlist; - - g_slist_foreach(playlist->songs, song_free_callback, NULL); - g_slist_free(playlist->songs); - g_free(playlist); -} - -static struct song * -rss_read(struct playlist_provider *_playlist) -{ - struct rss_playlist *playlist = (struct rss_playlist *)_playlist; - struct song *song; - - if (playlist->songs == NULL) - return NULL; - - song = playlist->songs->data; - playlist->songs = g_slist_remove(playlist->songs, song); - - return song; + return playlist; } static const char *const rss_suffixes[] = { @@ -311,12 +266,16 @@ static const char *const rss_mime_types[] = { }; const struct playlist_plugin rss_playlist_plugin = { - .name = "rss", + "rss", - .open_stream = rss_open_stream, - .close = rss_close, - .read = rss_read, + nullptr, + nullptr, + nullptr, + rss_open_stream, + nullptr, + nullptr, - .suffixes = rss_suffixes, - .mime_types = rss_mime_types, + nullptr, + rss_suffixes, + rss_mime_types, }; diff --git a/src/playlist/rss_playlist_plugin.h b/src/playlist/RssPlaylistPlugin.hxx index 3b376de7..f49f7e9c 100644 --- a/src/playlist/rss_playlist_plugin.h +++ b/src/playlist/RssPlaylistPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_RSS_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_RSS_PLAYLIST_PLUGIN_H +#ifndef MPD_RSS_PLAYLIST_PLUGIN_HXX +#define MPD_RSS_PLAYLIST_PLUGIN_HXX extern const struct playlist_plugin rss_playlist_plugin; diff --git a/src/playlist/soundcloud_playlist_plugin.c b/src/playlist/SoundCloudPlaylistPlugin.cxx index 7c79f880..5a258865 100644 --- a/src/playlist/soundcloud_playlist_plugin.c +++ b/src/playlist/SoundCloudPlaylistPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,10 @@ */ #include "config.h" -#include "playlist/soundcloud_playlist_plugin.h" +#include "SoundCloudPlaylistPlugin.hxx" +#include "MemoryPlaylistProvider.hxx" #include "conf.h" #include "input_stream.h" -#include "playlist_plugin.h" #include "song.h" #include "tag.h" @@ -30,12 +30,6 @@ #include <string.h> -struct soundcloud_playlist { - struct playlist_provider base; - - GSList *songs; -}; - static struct { char *apikey; } soundcloud_config; @@ -107,7 +101,8 @@ struct parse_data { long duration; char* title; int got_url; /* nesting level of last stream_url */ - GSList* songs; + + std::forward_list<SongPointer> songs; }; static int handle_integer(void *ctx, @@ -221,7 +216,7 @@ static int handle_end_map(void *ctx) tag_add_item(t, TAG_NAME, data->title); s->tag = t; - data->songs = g_slist_prepend(data->songs, s); + data->songs.emplace_front(s); return 1; } @@ -247,7 +242,8 @@ static yajl_callbacks parse_callbacks = { * @return -1 on error, 0 on success. */ static int -soundcloud_parse_json(const char *url, yajl_handle hand, GMutex* mutex, GCond* cond) +soundcloud_parse_json(const char *url, yajl_handle hand, + Mutex &mutex, Cond &cond) { struct input_stream *input_stream; GError *error = NULL; @@ -264,7 +260,7 @@ soundcloud_parse_json(const char *url, yajl_handle hand, GMutex* mutex, GCond* c return -1; } - g_mutex_lock(mutex); + mutex.lock(); input_stream_wait_ready(input_stream); yajl_status stat; @@ -280,7 +276,7 @@ soundcloud_parse_json(const char *url, yajl_handle hand, GMutex* mutex, GCond* c if (input_stream_eof(input_stream)) { done = true; } else { - g_mutex_unlock(mutex); + mutex.unlock(); input_stream_close(input_stream); return -1; } @@ -308,7 +304,7 @@ soundcloud_parse_json(const char *url, yajl_handle hand, GMutex* mutex, GCond* c } } - g_mutex_unlock(mutex); + mutex.unlock(); input_stream_close(input_stream); return 0; @@ -323,10 +319,8 @@ soundcloud_parse_json(const char *url, yajl_handle hand, GMutex* mutex, GCond* c */ static struct playlist_provider * -soundcloud_open_uri(const char *uri, GMutex *mutex, GCond *cond) +soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond) { - struct soundcloud_playlist *playlist = NULL; - char *s, *p; char *scheme, *arg, *rest; s = g_strdup(uri); @@ -377,7 +371,6 @@ soundcloud_open_uri(const char *uri, GMutex *mutex, GCond *cond) struct parse_data data; data.got_url = 0; - data.songs = NULL; data.title = NULL; data.stream_url = NULL; #ifdef HAVE_YAJL1 @@ -398,34 +391,8 @@ soundcloud_open_uri(const char *uri, GMutex *mutex, GCond *cond) if (ret == -1) return NULL; - playlist = g_new(struct soundcloud_playlist, 1); - playlist_provider_init(&playlist->base, &soundcloud_playlist_plugin); - playlist->songs = g_slist_reverse(data.songs); - - return &playlist->base; -} - -static void -soundcloud_close(struct playlist_provider *_playlist) -{ - struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist; - - g_free(playlist); -} - - -static struct song * -soundcloud_read(struct playlist_provider *_playlist) -{ - struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist; - - if (playlist->songs == NULL) - return NULL; - - struct song* s; - s = (struct song *)playlist->songs->data; - playlist->songs = g_slist_remove(playlist->songs, s); - return s; + data.songs.reverse(); + return new MemoryPlaylistProvider(std::move(data.songs)); } static const char *const soundcloud_schemes[] = { @@ -434,15 +401,18 @@ static const char *const soundcloud_schemes[] = { }; const struct playlist_plugin soundcloud_playlist_plugin = { - .name = "soundcloud", + "soundcloud", - .init = soundcloud_init, - .finish = soundcloud_finish, - .open_uri = soundcloud_open_uri, - .close = soundcloud_close, - .read = soundcloud_read, + soundcloud_init, + soundcloud_finish, + soundcloud_open_uri, + nullptr, + nullptr, + nullptr, - .schemes = soundcloud_schemes, + soundcloud_schemes, + nullptr, + nullptr, }; diff --git a/src/playlist/soundcloud_playlist_plugin.h b/src/playlist/SoundCloudPlaylistPlugin.hxx index e09e2dd4..7c121328 100644 --- a/src/playlist/soundcloud_playlist_plugin.h +++ b/src/playlist/SoundCloudPlaylistPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_SOUNDCLOUD_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_SOUNDCLOUD_PLAYLIST_PLUGIN_H +#ifndef MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX +#define MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX extern const struct playlist_plugin soundcloud_playlist_plugin; diff --git a/src/playlist/xspf_playlist_plugin.c b/src/playlist/XspfPlaylistPlugin.cxx index 17d9040e..bd84d86b 100644 --- a/src/playlist/xspf_playlist_plugin.c +++ b/src/playlist/XspfPlaylistPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,10 @@ */ #include "config.h" -#include "playlist/xspf_playlist_plugin.h" -#include "playlist_plugin.h" +#include "XspfPlaylistPlugin.hxx" +#include "MemoryPlaylistProvider.hxx" #include "input_stream.h" #include "uri.h" -#include "song.h" #include "tag.h" #include <glib.h> @@ -36,12 +35,12 @@ /** * This is the state object for the GLib XML parser. */ -struct xspf_parser { +struct XspfParser { /** * The list of songs (in reverse order because that's faster * while adding). */ - GSList *songs; + std::forward_list<SongPointer> songs; /** * The current position in the XML file. @@ -63,6 +62,9 @@ struct xspf_parser { * element. */ struct song *song; + + XspfParser() + :state(ROOT) {} }; static void @@ -72,33 +74,33 @@ xspf_start_element(G_GNUC_UNUSED GMarkupParseContext *context, G_GNUC_UNUSED const gchar **attribute_values, gpointer user_data, G_GNUC_UNUSED GError **error) { - struct xspf_parser *parser = user_data; + XspfParser *parser = (XspfParser *)user_data; switch (parser->state) { - case ROOT: + case XspfParser::ROOT: if (strcmp(element_name, "playlist") == 0) - parser->state = PLAYLIST; + parser->state = XspfParser::PLAYLIST; break; - case PLAYLIST: + case XspfParser::PLAYLIST: if (strcmp(element_name, "trackList") == 0) - parser->state = TRACKLIST; + parser->state = XspfParser::TRACKLIST; break; - case TRACKLIST: + case XspfParser::TRACKLIST: if (strcmp(element_name, "track") == 0) { - parser->state = TRACK; + parser->state = XspfParser::TRACK; parser->song = NULL; parser->tag = TAG_NUM_OF_ITEM_TYPES; } break; - case TRACK: + case XspfParser::TRACK: if (strcmp(element_name, "location") == 0) - parser->state = LOCATION; + parser->state = XspfParser::LOCATION; else if (strcmp(element_name, "title") == 0) parser->tag = TAG_TITLE; else if (strcmp(element_name, "creator") == 0) @@ -114,7 +116,7 @@ xspf_start_element(G_GNUC_UNUSED GMarkupParseContext *context, break; - case LOCATION: + case XspfParser::LOCATION: break; } } @@ -124,38 +126,37 @@ xspf_end_element(G_GNUC_UNUSED GMarkupParseContext *context, const gchar *element_name, gpointer user_data, G_GNUC_UNUSED GError **error) { - struct xspf_parser *parser = user_data; + XspfParser *parser = (XspfParser *)user_data; switch (parser->state) { - case ROOT: + case XspfParser::ROOT: break; - case PLAYLIST: + case XspfParser::PLAYLIST: if (strcmp(element_name, "playlist") == 0) - parser->state = ROOT; + parser->state = XspfParser::ROOT; break; - case TRACKLIST: + case XspfParser::TRACKLIST: if (strcmp(element_name, "tracklist") == 0) - parser->state = PLAYLIST; + parser->state = XspfParser::PLAYLIST; break; - case TRACK: + case XspfParser::TRACK: if (strcmp(element_name, "track") == 0) { if (parser->song != NULL) - parser->songs = g_slist_prepend(parser->songs, - parser->song); + parser->songs.emplace_front(parser->song); - parser->state = TRACKLIST; + parser->state = XspfParser::TRACKLIST; } else parser->tag = TAG_NUM_OF_ITEM_TYPES; break; - case LOCATION: - parser->state = TRACK; + case XspfParser::LOCATION: + parser->state = XspfParser::TRACK; break; } } @@ -165,15 +166,15 @@ xspf_text(G_GNUC_UNUSED GMarkupParseContext *context, const gchar *text, gsize text_len, gpointer user_data, G_GNUC_UNUSED GError **error) { - struct xspf_parser *parser = user_data; + XspfParser *parser = (XspfParser *)user_data; switch (parser->state) { - case ROOT: - case PLAYLIST: - case TRACKLIST: + case XspfParser::ROOT: + case XspfParser::PLAYLIST: + case XspfParser::TRACKLIST: break; - case TRACK: + case XspfParser::TRACK: if (parser->song != NULL && parser->tag != TAG_NUM_OF_ITEM_TYPES) { if (parser->song->tag == NULL) @@ -184,7 +185,7 @@ xspf_text(G_GNUC_UNUSED GMarkupParseContext *context, break; - case LOCATION: + case XspfParser::LOCATION: if (parser->song == NULL) { char *uri = g_strndup(text, text_len); parser->song = song_remote_new(uri); @@ -196,29 +197,20 @@ xspf_text(G_GNUC_UNUSED GMarkupParseContext *context, } static const GMarkupParser xspf_parser = { - .start_element = xspf_start_element, - .end_element = xspf_end_element, - .text = xspf_text, + xspf_start_element, + xspf_end_element, + xspf_text, + nullptr, + nullptr, }; static void -song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct song *song = data; - - song_free(song); -} - -static void xspf_parser_destroy(gpointer data) { - struct xspf_parser *parser = data; + XspfParser *parser = (XspfParser *)data; - if (parser->state >= TRACK && parser->song != NULL) + if (parser->state >= XspfParser::TRACK && parser->song != NULL) song_free(parser->song); - - g_slist_foreach(parser->songs, song_free_callback, NULL); - g_slist_free(parser->songs); } /* @@ -226,20 +218,10 @@ xspf_parser_destroy(gpointer data) * */ -struct xspf_playlist { - struct playlist_provider base; - - GSList *songs; -}; - static struct playlist_provider * xspf_open_stream(struct input_stream *is) { - struct xspf_parser parser = { - .songs = NULL, - .state = ROOT, - }; - struct xspf_playlist *playlist; + XspfParser parser; GMarkupParseContext *context; char buffer[1024]; size_t nbytes; @@ -284,41 +266,13 @@ xspf_open_stream(struct input_stream *is) return NULL; } - /* create a #xspf_playlist object from the parsed song list */ - - playlist = g_new(struct xspf_playlist, 1); - playlist_provider_init(&playlist->base, &xspf_playlist_plugin); - playlist->songs = g_slist_reverse(parser.songs); - parser.songs = NULL; + parser.songs.reverse(); + MemoryPlaylistProvider *playlist = + new MemoryPlaylistProvider(std::move(parser.songs)); g_markup_parse_context_free(context); - return &playlist->base; -} - -static void -xspf_close(struct playlist_provider *_playlist) -{ - struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist; - - g_slist_foreach(playlist->songs, song_free_callback, NULL); - g_slist_free(playlist->songs); - g_free(playlist); -} - -static struct song * -xspf_read(struct playlist_provider *_playlist) -{ - struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist; - struct song *song; - - if (playlist->songs == NULL) - return NULL; - - song = playlist->songs->data; - playlist->songs = g_slist_remove(playlist->songs, song); - - return song; + return playlist; } static const char *const xspf_suffixes[] = { @@ -332,12 +286,16 @@ static const char *const xspf_mime_types[] = { }; const struct playlist_plugin xspf_playlist_plugin = { - .name = "xspf", + "xspf", - .open_stream = xspf_open_stream, - .close = xspf_close, - .read = xspf_read, + nullptr, + nullptr, + nullptr, + xspf_open_stream, + nullptr, + nullptr, - .suffixes = xspf_suffixes, - .mime_types = xspf_mime_types, + nullptr, + xspf_suffixes, + xspf_mime_types, }; diff --git a/src/playlist/xspf_playlist_plugin.h b/src/playlist/XspfPlaylistPlugin.hxx index 4636d7e8..fc9bbd2c 100644 --- a/src/playlist/xspf_playlist_plugin.h +++ b/src/playlist/XspfPlaylistPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H +#ifndef MPD_XSPF_PLAYLIST_PLUGIN_HXX +#define MPD_XSPF_PLAYLIST_PLUGIN_HXX extern const struct playlist_plugin xspf_playlist_plugin; diff --git a/src/playlist/despotify_playlist_plugin.c b/src/playlist/despotify_playlist_plugin.c deleted file mode 100644 index 30b852c7..00000000 --- a/src/playlist/despotify_playlist_plugin.c +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/despotify_playlist_plugin.h" -#include "playlist_plugin.h" -#include "playlist_list.h" -#include "conf.h" -#include "uri.h" -#include "tag.h" -#include "song.h" -#include "input_stream.h" -#include "despotify_utils.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <stdlib.h> -#include <despotify.h> - -struct despotify_playlist { - struct playlist_provider base; - - struct despotify_session *session; - GSList *list; -}; - -static void -add_song(struct despotify_playlist *ctx, struct ds_track *track) -{ - const char *dsp_scheme = despotify_playlist_plugin.schemes[0]; - struct song *song; - char uri[128]; - char *ds_uri; - - /* Create a spt://... URI for MPD */ - g_snprintf(uri, sizeof(uri), "%s://", dsp_scheme); - ds_uri = uri + strlen(dsp_scheme) + 3; - - if (despotify_track_to_uri(track, ds_uri) != ds_uri) { - /* Should never really fail, but let's be sure */ - g_debug("Can't add track %s\n", track->title); - return; - } - - song = song_remote_new(uri); - song->tag = mpd_despotify_tag_from_track(track); - - ctx->list = g_slist_prepend(ctx->list, song); -} - -static bool -parse_track(struct despotify_playlist *ctx, - struct ds_link *link) -{ - struct ds_track *track; - - track = despotify_link_get_track(ctx->session, link); - if (!track) - return false; - add_song(ctx, track); - - return true; -} - -static bool -parse_playlist(struct despotify_playlist *ctx, - struct ds_link *link) -{ - struct ds_playlist *playlist; - struct ds_track *track; - - playlist = despotify_link_get_playlist(ctx->session, link); - if (!playlist) - return false; - - for (track = playlist->tracks; track; track = track->next) - add_song(ctx, track); - - return true; -} - -static bool -despotify_playlist_init(G_GNUC_UNUSED const struct config_param *param) -{ - return true; -} - -static void -despotify_playlist_finish(void) -{ -} - - -static struct playlist_provider * -despotify_playlist_open_uri(const char *url, G_GNUC_UNUSED GMutex *mutex, - G_GNUC_UNUSED GCond *cond) -{ - struct despotify_playlist *ctx; - struct despotify_session *session; - struct ds_link *link; - bool parse_result; - - session = mpd_despotify_get_session(); - if (!session) - goto clean_none; - - /* Get link without spt:// */ - link = despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3); - if (!link) { - g_debug("Can't find %s\n", url); - goto clean_none; - } - - ctx = g_new(struct despotify_playlist, 1); - - ctx->list = NULL; - ctx->session = session; - playlist_provider_init(&ctx->base, &despotify_playlist_plugin); - - switch (link->type) - { - case LINK_TYPE_TRACK: - parse_result = parse_track(ctx, link); - break; - case LINK_TYPE_PLAYLIST: - parse_result = parse_playlist(ctx, link); - break; - default: - parse_result = false; - break; - } - despotify_free_link(link); - if (!parse_result) - goto clean_playlist; - - ctx->list = g_slist_reverse(ctx->list); - - return &ctx->base; - -clean_playlist: - g_slist_free(ctx->list); -clean_none: - - return NULL; -} - -static void -track_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct song *song = (struct song *)data; - - song_free(song); -} - -static void -despotify_playlist_close(struct playlist_provider *_playlist) -{ - struct despotify_playlist *ctx = (struct despotify_playlist *)_playlist; - - g_slist_foreach(ctx->list, track_free_callback, NULL); - g_slist_free(ctx->list); - - g_free(ctx); -} - - -static struct song * -despotify_playlist_read(struct playlist_provider *_playlist) -{ - struct despotify_playlist *ctx = (struct despotify_playlist *)_playlist; - struct song *out; - - if (!ctx->list) - return NULL; - - /* Remove the current track */ - out = ctx->list->data; - ctx->list = g_slist_remove(ctx->list, out); - - return out; -} - - -static const char *const despotify_schemes[] = { - "spt", - NULL -}; - -const struct playlist_plugin despotify_playlist_plugin = { - .name = "despotify", - - .init = despotify_playlist_init, - .finish = despotify_playlist_finish, - .open_uri = despotify_playlist_open_uri, - .read = despotify_playlist_read, - .close = despotify_playlist_close, - - .schemes = despotify_schemes, -}; diff --git a/src/playlist_control.c b/src/playlist_control.c deleted file mode 100644 index 0dea7676..00000000 --- a/src/playlist_control.c +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Functions for controlling playback on the playlist level. - * - */ - -#include "config.h" -#include "playlist_internal.h" -#include "player_control.h" -#include "idle.h" - -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "playlist" - -void -playlist_stop(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - return; - - assert(playlist->current >= 0); - - g_debug("stop"); - pc_stop(pc); - playlist->queued = -1; - playlist->playing = false; - - if (playlist->queue.random) { - /* shuffle the playlist, so the next playback will - result in a new random order */ - - unsigned current_position = - queue_order_to_position(&playlist->queue, - playlist->current); - - queue_shuffle_order(&playlist->queue); - - /* make sure that "current" stays valid, and the next - "play" command plays the same song again */ - playlist->current = - queue_position_to_order(&playlist->queue, - current_position); - } -} - -enum playlist_result -playlist_play(struct playlist *playlist, struct player_control *pc, - int song) -{ - unsigned i = song; - - pc_clear_error(pc); - - if (song == -1) { - /* play any song ("current" song, or the first song */ - - if (queue_is_empty(&playlist->queue)) - return PLAYLIST_RESULT_SUCCESS; - - if (playlist->playing) { - /* already playing: unpause playback, just in - case it was paused, and return */ - pc_set_pause(pc, false); - return PLAYLIST_RESULT_SUCCESS; - } - - /* select a song: "current" song, or the first one */ - i = playlist->current >= 0 - ? playlist->current - : 0; - } else if (!queue_valid_position(&playlist->queue, song)) - return PLAYLIST_RESULT_BAD_RANGE; - - if (playlist->queue.random) { - if (song >= 0) - /* "i" is currently the song position (which - would be equal to the order number in - no-random mode); convert it to a order - number, because random mode is enabled */ - i = queue_position_to_order(&playlist->queue, song); - - if (!playlist->playing) - playlist->current = 0; - - /* swap the new song with the previous "current" one, - so playback continues as planned */ - queue_swap_order(&playlist->queue, - i, playlist->current); - i = playlist->current; - } - - playlist->stop_on_error = false; - playlist->error_count = 0; - - playlist_play_order(playlist, pc, i); - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_play_id(struct playlist *playlist, struct player_control *pc, - int id) -{ - int song; - - if (id == -1) { - return playlist_play(playlist, pc, id); - } - - song = queue_id_to_position(&playlist->queue, id); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_play(playlist, pc, song); -} - -void -playlist_next(struct playlist *playlist, struct player_control *pc) -{ - int next_order; - int current; - - if (!playlist->playing) - return; - - assert(!queue_is_empty(&playlist->queue)); - assert(queue_valid_order(&playlist->queue, playlist->current)); - - current = playlist->current; - playlist->stop_on_error = false; - - /* determine the next song from the queue's order list */ - - next_order = queue_next_order(&playlist->queue, playlist->current); - if (next_order < 0) { - /* no song after this one: stop playback */ - playlist_stop(playlist, pc); - - /* reset "current song" */ - playlist->current = -1; - } - else - { - if (next_order == 0 && playlist->queue.random) { - /* The queue told us that the next song is the first - song. This means we are in repeat mode. Shuffle - the queue order, so this time, the user hears the - songs in a different than before */ - assert(playlist->queue.repeat); - - queue_shuffle_order(&playlist->queue); - - /* note that playlist->current and playlist->queued are - now invalid, but playlist_play_order() will - discard them anyway */ - } - - playlist_play_order(playlist, pc, next_order); - } - - /* Consume mode removes each played songs. */ - if(playlist->queue.consume) - playlist_delete(playlist, pc, - queue_order_to_position(&playlist->queue, - current)); -} - -void -playlist_previous(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - return; - - assert(queue_length(&playlist->queue) > 0); - - if (playlist->current > 0) { - /* play the preceding song */ - playlist_play_order(playlist, pc, - playlist->current - 1); - } else if (playlist->queue.repeat) { - /* play the last song in "repeat" mode */ - playlist_play_order(playlist, pc, - queue_length(&playlist->queue) - 1); - } else { - /* re-start playing the current song if it's - the first one */ - playlist_play_order(playlist, pc, playlist->current); - } -} - -enum playlist_result -playlist_seek_song(struct playlist *playlist, struct player_control *pc, - unsigned song, float seek_time) -{ - const struct song *queued; - unsigned i; - bool success; - - if (!queue_valid_position(&playlist->queue, song)) - return PLAYLIST_RESULT_BAD_RANGE; - - queued = playlist_get_queued_song(playlist); - - if (playlist->queue.random) - i = queue_position_to_order(&playlist->queue, song); - else - i = song; - - pc_clear_error(pc); - playlist->stop_on_error = true; - playlist->error_count = 0; - - if (!playlist->playing || (unsigned)playlist->current != i) { - /* seeking is not within the current song - prepare - song change */ - - playlist->playing = true; - playlist->current = i; - - queued = NULL; - } - - success = pc_seek(pc, queue_get_order(&playlist->queue, i), seek_time); - if (!success) { - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_NOT_PLAYING; - } - - playlist->queued = -1; - playlist_update_queued_song(playlist, pc, NULL); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, - unsigned id, float seek_time) -{ - int song = queue_id_to_position(&playlist->queue, id); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_seek_song(playlist, pc, song, seek_time); -} - -enum playlist_result -playlist_seek_current(struct playlist *playlist, struct player_control *pc, - float seek_time, bool relative) -{ - if (!playlist->playing) - return PLAYLIST_RESULT_NOT_PLAYING; - - if (relative) { - struct player_status status; - pc_get_status(pc, &status); - - if (status.state != PLAYER_STATE_PLAY && - status.state != PLAYER_STATE_PAUSE) - return PLAYLIST_RESULT_NOT_PLAYING; - - seek_time += (int)status.elapsed_time; - } - - if (seek_time < 0) - seek_time = 0; - - return playlist_seek_song(playlist, pc, playlist->current, seek_time); -} diff --git a/src/playlist_edit.c b/src/playlist_edit.c deleted file mode 100644 index d10f4945..00000000 --- a/src/playlist_edit.c +++ /dev/null @@ -1,487 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Functions for editing the playlist (adding, removing, reordering - * songs in the queue). - * - */ - -#include "config.h" -#include "playlist_internal.h" -#include "player_control.h" -#include "database.h" -#include "uri.h" -#include "song.h" -#include "idle.h" - -#include <stdlib.h> - -static void playlist_increment_version(struct playlist *playlist) -{ - queue_increment_version(&playlist->queue); - - idle_add(IDLE_PLAYLIST); -} - -void -playlist_clear(struct playlist *playlist, struct player_control *pc) -{ - playlist_stop(playlist, pc); - - /* make sure there are no references to allocated songs - anymore */ - for (unsigned i = 0; i < queue_length(&playlist->queue); i++) { - const struct song *song = queue_get(&playlist->queue, i); - if (!song_in_database(song)) - pc_song_deleted(pc, song); - } - - queue_clear(&playlist->queue); - - playlist->current = -1; - - playlist_increment_version(playlist); -} - -enum playlist_result -playlist_append_file(struct playlist *playlist, struct player_control *pc, - const char *path_fs, unsigned *added_id) -{ - struct song *song = song_file_load(path_fs, NULL); - if (song == NULL) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_append_song(playlist, pc, song, added_id); -} - -enum playlist_result -playlist_append_song(struct playlist *playlist, struct player_control *pc, - struct song *song, unsigned *added_id) -{ - const struct song *queued; - unsigned id; - - if (queue_is_full(&playlist->queue)) - return PLAYLIST_RESULT_TOO_LARGE; - - queued = playlist_get_queued_song(playlist); - - id = queue_append(&playlist->queue, song, 0); - - if (playlist->queue.random) { - /* shuffle the new song into the list of remaining - songs to play */ - - unsigned start; - if (playlist->queued >= 0) - start = playlist->queued + 1; - else - start = playlist->current + 1; - if (start < queue_length(&playlist->queue)) - queue_shuffle_order_last(&playlist->queue, start, - queue_length(&playlist->queue)); - } - - playlist_increment_version(playlist); - - playlist_update_queued_song(playlist, pc, queued); - - if (added_id) - *added_id = id; - - return PLAYLIST_RESULT_SUCCESS; -} - -static struct song * -song_by_uri(const char *uri) -{ - struct song *song; - - song = db_get_song(uri); - if (song != NULL) - return song; - - if (uri_has_scheme(uri)) - return song_remote_new(uri); - - return NULL; -} - -enum playlist_result -playlist_append_uri(struct playlist *playlist, struct player_control *pc, - const char *uri, unsigned *added_id) -{ - struct song *song; - - g_debug("add to playlist: %s", uri); - - song = song_by_uri(uri); - if (song == NULL) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_append_song(playlist, pc, song, added_id); -} - -enum playlist_result -playlist_swap_songs(struct playlist *playlist, struct player_control *pc, - unsigned song1, unsigned song2) -{ - const struct song *queued; - - if (!queue_valid_position(&playlist->queue, song1) || - !queue_valid_position(&playlist->queue, song2)) - return PLAYLIST_RESULT_BAD_RANGE; - - queued = playlist_get_queued_song(playlist); - - queue_swap(&playlist->queue, song1, song2); - - if (playlist->queue.random) { - /* update the queue order, so that playlist->current - still points to the current song order */ - - queue_swap_order(&playlist->queue, - queue_position_to_order(&playlist->queue, - song1), - queue_position_to_order(&playlist->queue, - song2)); - } else { - /* correct the "current" song order */ - - if (playlist->current == (int)song1) - playlist->current = song2; - else if (playlist->current == (int)song2) - playlist->current = song1; - } - - playlist_increment_version(playlist); - - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc, - unsigned id1, unsigned id2) -{ - int song1 = queue_id_to_position(&playlist->queue, id1); - int song2 = queue_id_to_position(&playlist->queue, id2); - - if (song1 < 0 || song2 < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_swap_songs(playlist, pc, song1, song2); -} - -enum playlist_result -playlist_set_priority(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end, - uint8_t priority) -{ - if (start >= queue_length(&playlist->queue)) - return PLAYLIST_RESULT_BAD_RANGE; - - if (end > queue_length(&playlist->queue)) - end = queue_length(&playlist->queue); - - if (start >= end) - return PLAYLIST_RESULT_SUCCESS; - - /* remember "current" and "queued" */ - - int current_position = playlist->current >= 0 - ? (int)queue_order_to_position(&playlist->queue, - playlist->current) - : -1; - - const struct song *queued = playlist_get_queued_song(playlist); - - /* apply the priority changes */ - - queue_set_priority_range(&playlist->queue, start, end, priority, - playlist->current); - - playlist_increment_version(playlist); - - /* restore "current" and choose a new "queued" */ - - if (current_position >= 0) - playlist->current = queue_position_to_order(&playlist->queue, - current_position); - - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_set_priority_id(struct playlist *playlist, struct player_control *pc, - unsigned song_id, uint8_t priority) -{ - int song_position = queue_id_to_position(&playlist->queue, song_id); - if (song_position < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_set_priority(playlist, pc, - song_position, song_position + 1, - priority); - -} - -static void -playlist_delete_internal(struct playlist *playlist, struct player_control *pc, - unsigned song, const struct song **queued_p) -{ - unsigned songOrder; - - assert(song < queue_length(&playlist->queue)); - - songOrder = queue_position_to_order(&playlist->queue, song); - - if (playlist->playing && playlist->current == (int)songOrder) { - bool paused = pc_get_state(pc) == PLAYER_STATE_PAUSE; - - /* the current song is going to be deleted: stop the player */ - - pc_stop(pc); - playlist->playing = false; - - /* see which song is going to be played instead */ - - playlist->current = queue_next_order(&playlist->queue, - playlist->current); - if (playlist->current == (int)songOrder) - playlist->current = -1; - - if (playlist->current >= 0 && !paused) - /* play the song after the deleted one */ - playlist_play_order(playlist, pc, playlist->current); - else - /* no songs left to play, stop playback - completely */ - playlist_stop(playlist, pc); - - *queued_p = NULL; - } else if (playlist->current == (int)songOrder) - /* there's a "current song" but we're not playing - currently - clear "current" */ - playlist->current = -1; - - /* now do it: remove the song */ - - if (!song_in_database(queue_get(&playlist->queue, song))) - pc_song_deleted(pc, queue_get(&playlist->queue, song)); - - queue_delete(&playlist->queue, song); - - /* update the "current" and "queued" variables */ - - if (playlist->current > (int)songOrder) { - playlist->current--; - } -} - -enum playlist_result -playlist_delete(struct playlist *playlist, struct player_control *pc, - unsigned song) -{ - const struct song *queued; - - if (song >= queue_length(&playlist->queue)) - return PLAYLIST_RESULT_BAD_RANGE; - - queued = playlist_get_queued_song(playlist); - - playlist_delete_internal(playlist, pc, song, &queued); - - playlist_increment_version(playlist); - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_delete_range(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end) -{ - const struct song *queued; - - if (start >= queue_length(&playlist->queue)) - return PLAYLIST_RESULT_BAD_RANGE; - - if (end > queue_length(&playlist->queue)) - end = queue_length(&playlist->queue); - - if (start >= end) - return PLAYLIST_RESULT_SUCCESS; - - queued = playlist_get_queued_song(playlist); - - do { - playlist_delete_internal(playlist, pc, --end, &queued); - } while (end != start); - - playlist_increment_version(playlist); - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_delete_id(struct playlist *playlist, struct player_control *pc, - unsigned id) -{ - int song = queue_id_to_position(&playlist->queue, id); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_delete(playlist, pc, song); -} - -void -playlist_delete_song(struct playlist *playlist, struct player_control *pc, - const struct song *song) -{ - for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i) - if (song == queue_get(&playlist->queue, i)) - playlist_delete(playlist, pc, i); - - pc_song_deleted(pc, song); -} - -enum playlist_result -playlist_move_range(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end, int to) -{ - const struct song *queued; - int currentSong; - - if (!queue_valid_position(&playlist->queue, start) || - !queue_valid_position(&playlist->queue, end - 1)) - return PLAYLIST_RESULT_BAD_RANGE; - - if ((to >= 0 && to + end - start - 1 >= queue_length(&playlist->queue)) || - (to < 0 && abs(to) > (int)queue_length(&playlist->queue))) - return PLAYLIST_RESULT_BAD_RANGE; - - if ((int)start == to) - /* nothing happens */ - return PLAYLIST_RESULT_SUCCESS; - - queued = playlist_get_queued_song(playlist); - - /* - * (to < 0) => move to offset from current song - * (-playlist.length == to) => move to position BEFORE current song - */ - currentSong = playlist->current >= 0 - ? (int)queue_order_to_position(&playlist->queue, - playlist->current) - : -1; - if (to < 0 && playlist->current >= 0) { - if (start <= (unsigned)currentSong && (unsigned)currentSong < end) - /* no-op, can't be moved to offset of itself */ - return PLAYLIST_RESULT_SUCCESS; - to = (currentSong + abs(to)) % queue_length(&playlist->queue); - if (start < (unsigned)to) - to--; - } - - queue_move_range(&playlist->queue, start, end, to); - - if (!playlist->queue.random) { - /* update current/queued */ - if ((int)start <= playlist->current && - (unsigned)playlist->current < end) - playlist->current += to - start; - else if (playlist->current >= (int)end && - playlist->current <= to) { - playlist->current -= end - start; - } else if (playlist->current >= to && - playlist->current < (int)start) { - playlist->current += end - start; - } - } - - playlist_increment_version(playlist); - - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_move_id(struct playlist *playlist, struct player_control *pc, - unsigned id1, int to) -{ - int song = queue_id_to_position(&playlist->queue, id1); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_move_range(playlist, pc, song, song+1, to); -} - -void -playlist_shuffle(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end) -{ - const struct song *queued; - - if (end > queue_length(&playlist->queue)) - /* correct the "end" offset */ - end = queue_length(&playlist->queue); - - if ((start+1) >= end) - /* needs at least two entries. */ - return; - - queued = playlist_get_queued_song(playlist); - if (playlist->playing && playlist->current >= 0) { - unsigned current_position; - current_position = queue_order_to_position(&playlist->queue, - playlist->current); - - if (current_position >= start && current_position < end) - { - /* put current playing song first */ - queue_swap(&playlist->queue, start, current_position); - - if (playlist->queue.random) { - playlist->current = - queue_position_to_order(&playlist->queue, start); - } else - playlist->current = start; - - /* start shuffle after the current song */ - start++; - } - } else { - /* no playback currently: reset playlist->current */ - - playlist->current = -1; - } - - queue_shuffle_range(&playlist->queue, start, end); - - playlist_increment_version(playlist); - - playlist_update_queued_song(playlist, pc, queued); -} diff --git a/src/playlist_internal.h b/src/playlist_internal.h deleted file mode 100644 index 81b17517..00000000 --- a/src/playlist_internal.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Internal header for the components of the playlist code. - * - */ - -#ifndef PLAYLIST_INTERNAL_H -#define PLAYLIST_INTERNAL_H - -#include "playlist.h" - -struct player_control; - -/** - * Returns the song object which is currently queued. Returns none if - * there is none (yet?) or if MPD isn't playing. - */ -const struct song * -playlist_get_queued_song(struct playlist *playlist); - -/** - * Updates the "queued song". Calculates the next song according to - * the current one (if MPD isn't playing, it takes the first song), - * and queues this song. Clears the old queued song if there was one. - * - * @param prev the song which was previously queued, as determined by - * playlist_get_queued_song() - */ -void -playlist_update_queued_song(struct playlist *playlist, - struct player_control *pc, - const struct song *prev); - -void -playlist_play_order(struct playlist *playlist, struct player_control *pc, - int orderNum); - -#endif diff --git a/src/playlist_vector.c b/src/playlist_vector.c deleted file mode 100644 index 74c7bf08..00000000 --- a/src/playlist_vector.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_vector.h" -#include "db_lock.h" - -#include <assert.h> -#include <string.h> -#include <glib.h> - -static struct playlist_metadata * -playlist_metadata_new(const char *name, time_t mtime) -{ - assert(name != NULL); - - struct playlist_metadata *pm = g_slice_new(struct playlist_metadata); - pm->name = g_strdup(name); - pm->mtime = mtime; - return pm; -} - -static void -playlist_metadata_free(struct playlist_metadata *pm) -{ - assert(pm != NULL); - assert(pm->name != NULL); - - g_free(pm->name); - g_slice_free(struct playlist_metadata, pm); -} - -void -playlist_vector_deinit(struct list_head *pv) -{ - assert(pv != NULL); - - struct playlist_metadata *pm, *n; - playlist_vector_for_each_safe(pm, n, pv) - playlist_metadata_free(pm); -} - -struct playlist_metadata * -playlist_vector_find(struct list_head *pv, const char *name) -{ - assert(holding_db_lock()); - assert(pv != NULL); - assert(name != NULL); - - struct playlist_metadata *pm; - playlist_vector_for_each(pm, pv) - if (strcmp(pm->name, name) == 0) - return pm; - - return NULL; -} - -void -playlist_vector_add(struct list_head *pv, - const char *name, time_t mtime) -{ - assert(holding_db_lock()); - - struct playlist_metadata *pm = playlist_metadata_new(name, mtime); - list_add_tail(&pm->siblings, pv); -} - -bool -playlist_vector_update_or_add(struct list_head *pv, - const char *name, time_t mtime) -{ - assert(holding_db_lock()); - - struct playlist_metadata *pm = playlist_vector_find(pv, name); - if (pm != NULL) { - if (mtime == pm->mtime) - return false; - - pm->mtime = mtime; - } else - playlist_vector_add(pv, name, mtime); - - return true; -} - -bool -playlist_vector_remove(struct list_head *pv, const char *name) -{ - assert(holding_db_lock()); - - struct playlist_metadata *pm = playlist_vector_find(pv, name); - if (pm == NULL) - return false; - - list_del(&pm->siblings); - playlist_metadata_free(pm); - return true; -} diff --git a/src/playlist_vector.h b/src/playlist_vector.h deleted file mode 100644 index 0af6df8b..00000000 --- a/src/playlist_vector.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_VECTOR_H -#define MPD_PLAYLIST_VECTOR_H - -#include "util/list.h" - -#include <stdbool.h> -#include <stddef.h> -#include <sys/time.h> - -#define playlist_vector_for_each(pos, head) \ - list_for_each_entry(pos, head, siblings) - -#define playlist_vector_for_each_safe(pos, n, head) \ - list_for_each_entry_safe(pos, n, head, siblings) - -/** - * A directory entry pointing to a playlist file. - */ -struct playlist_metadata { - struct list_head siblings; - - /** - * The UTF-8 encoded name of the playlist file. - */ - char *name; - - time_t mtime; -}; - -void -playlist_vector_deinit(struct list_head *pv); - -/** - * Caller must lock the #db_mutex. - */ -struct playlist_metadata * -playlist_vector_find(struct list_head *pv, const char *name); - -/** - * Caller must lock the #db_mutex. - */ -void -playlist_vector_add(struct list_head *pv, - const char *name, time_t mtime); - -/** - * Caller must lock the #db_mutex. - * - * @return true if the vector or one of its items was modified - */ -bool -playlist_vector_update_or_add(struct list_head *pv, - const char *name, time_t mtime); - -/** - * Caller must lock the #db_mutex. - */ -bool -playlist_vector_remove(struct list_head *pv, const char *name); - -#endif /* SONGVEC_H */ diff --git a/src/protocol/argparser.c b/src/protocol/ArgParser.cxx index d20437cb..6bd53a35 100644 --- a/src/protocol/argparser.c +++ b/src/protocol/ArgParser.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,14 @@ */ #include "config.h" -#include "argparser.h" -#include "result.h" +#include "ArgParser.hxx" +#include "Result.hxx" #include <glib.h> #include <stdlib.h> bool -check_uint32(struct client *client, uint32_t *dst, const char *s) +check_uint32(Client *client, uint32_t *dst, const char *s) { char *test; @@ -39,7 +39,7 @@ check_uint32(struct client *client, uint32_t *dst, const char *s) } bool -check_int(struct client *client, int *value_r, const char *s) +check_int(Client *client, int *value_r, const char *s) { char *test; long value; @@ -64,7 +64,7 @@ check_int(struct client *client, int *value_r, const char *s) } bool -check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, +check_range(Client *client, unsigned *value_r1, unsigned *value_r2, const char *s) { char *test, *test2; @@ -134,7 +134,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, } bool -check_unsigned(struct client *client, unsigned *value_r, const char *s) +check_unsigned(Client *client, unsigned *value_r, const char *s) { unsigned long value; char *endptr; @@ -157,7 +157,7 @@ check_unsigned(struct client *client, unsigned *value_r, const char *s) } bool -check_bool(struct client *client, bool *value_r, const char *s) +check_bool(Client *client, bool *value_r, const char *s) { long value; char *endptr; @@ -174,7 +174,7 @@ check_bool(struct client *client, bool *value_r, const char *s) } bool -check_float(struct client *client, float *value_r, const char *s) +check_float(Client *client, float *value_r, const char *s) { float value; char *endptr; diff --git a/src/protocol/argparser.h b/src/protocol/ArgParser.hxx index e88aea47..b6feb3e6 100644 --- a/src/protocol/argparser.h +++ b/src/protocol/ArgParser.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,33 +17,33 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PROTOCOL_ARGPARSER_H -#define MPD_PROTOCOL_ARGPARSER_H +#ifndef MPD_PROTOCOL_ARGPARSER_HXX +#define MPD_PROTOCOL_ARGPARSER_HXX #include "check.h" #include <stdbool.h> #include <stdint.h> -struct client; +class Client; bool -check_uint32(struct client *client, uint32_t *dst, const char *s); +check_uint32(Client *client, uint32_t *dst, const char *s); bool -check_int(struct client *client, int *value_r, const char *s); +check_int(Client *client, int *value_r, const char *s); bool -check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, +check_range(Client *client, unsigned *value_r1, unsigned *value_r2, const char *s); bool -check_unsigned(struct client *client, unsigned *value_r, const char *s); +check_unsigned(Client *client, unsigned *value_r, const char *s); bool -check_bool(struct client *client, bool *value_r, const char *s); +check_bool(Client *client, bool *value_r, const char *s); bool -check_float(struct client *client, float *value_r, const char *s); +check_float(Client *client, float *value_r, const char *s); #endif diff --git a/src/protocol/result.c b/src/protocol/Result.cxx index 30cd0a26..e10a731c 100644 --- a/src/protocol/result.c +++ b/src/protocol/Result.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,8 @@ */ #include "config.h" -#include "result.h" -#include "client.h" +#include "Result.hxx" +#include "Client.hxx" #include <assert.h> @@ -27,13 +27,13 @@ const char *current_command; int command_list_num; void -command_success(struct client *client) +command_success(Client *client) { client_puts(client, "OK\n"); } void -command_error_v(struct client *client, enum ack error, +command_error_v(Client *client, enum ack error, const char *fmt, va_list args) { assert(client != NULL); @@ -48,7 +48,7 @@ command_error_v(struct client *client, enum ack error, } void -command_error(struct client *client, enum ack error, const char *fmt, ...) +command_error(Client *client, enum ack error, const char *fmt, ...) { va_list args; va_start(args, fmt); diff --git a/src/protocol/result.h b/src/protocol/Result.hxx index 8b9e44bf..99d9a2fa 100644 --- a/src/protocol/result.h +++ b/src/protocol/Result.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,28 +17,27 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PROTOCOL_RESULT_H -#define MPD_PROTOCOL_RESULT_H +#ifndef MPD_PROTOCOL_RESULT_HXX +#define MPD_PROTOCOL_RESULT_HXX #include "check.h" +#include "gcc.h" #include "ack.h" -#include <glib.h> - -struct client; +class Client; extern const char *current_command; extern int command_list_num; void -command_success(struct client *client); +command_success(Client *client); void -command_error_v(struct client *client, enum ack error, +command_error_v(Client *client, enum ack error, const char *fmt, va_list args); -G_GNUC_PRINTF(3, 4) +gcc_fprintf_ void -command_error(struct client *client, enum ack error, const char *fmt, ...); +command_error(Client *client, enum ack error, const char *fmt, ...); #endif diff --git a/src/queue.c b/src/queue.c deleted file mode 100644 index 4fe564a3..00000000 --- a/src/queue.c +++ /dev/null @@ -1,603 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "queue.h" -#include "song.h" - -#include <stdlib.h> - -/** - * Generate a non-existing id number. - */ -static unsigned -queue_generate_id(const struct queue *queue) -{ - static unsigned cur = (unsigned)-1; - - do { - cur++; - - if (cur >= queue->max_length * QUEUE_HASH_MULT) - cur = 0; - } while (queue->id_to_position[cur] != -1); - - return cur; -} - -int -queue_next_order(const struct queue *queue, unsigned order) -{ - assert(order < queue->length); - - if (queue->single && queue->repeat && !queue->consume) - return order; - else if (order + 1 < queue->length) - return order + 1; - else if (queue->repeat && (order > 0 || !queue->consume)) - /* restart at first song */ - return 0; - else - /* end of queue */ - return -1; -} - -void -queue_increment_version(struct queue *queue) -{ - static unsigned long max = ((uint32_t) 1 << 31) - 1; - - queue->version++; - - if (queue->version >= max) { - for (unsigned i = 0; i < queue->length; i++) - queue->items[i].version = 0; - - queue->version = 1; - } -} - -void -queue_modify(struct queue *queue, unsigned order) -{ - unsigned position; - - assert(order < queue->length); - - position = queue->order[order]; - queue->items[position].version = queue->version; - - queue_increment_version(queue); -} - -void -queue_modify_all(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; i++) - queue->items[i].version = queue->version; - - queue_increment_version(queue); -} - -unsigned -queue_append(struct queue *queue, struct song *song, uint8_t priority) -{ - unsigned id = queue_generate_id(queue); - - assert(!queue_is_full(queue)); - - queue->items[queue->length] = (struct queue_item){ - .song = song, - .id = id, - .version = queue->version, - .priority = priority, - }; - - queue->order[queue->length] = queue->length; - queue->id_to_position[id] = queue->length; - - ++queue->length; - - return id; -} - -void -queue_swap(struct queue *queue, unsigned position1, unsigned position2) -{ - struct queue_item tmp; - unsigned id1 = queue->items[position1].id; - unsigned id2 = queue->items[position2].id; - - tmp = queue->items[position1]; - queue->items[position1] = queue->items[position2]; - queue->items[position2] = tmp; - - queue->items[position1].version = queue->version; - queue->items[position2].version = queue->version; - - queue->id_to_position[id1] = position2; - queue->id_to_position[id2] = position1; -} - -static void -queue_move_song_to(struct queue *queue, unsigned from, unsigned to) -{ - unsigned from_id = queue->items[from].id; - - queue->items[to] = queue->items[from]; - queue->items[to].version = queue->version; - queue->id_to_position[from_id] = to; -} - -void -queue_move(struct queue *queue, unsigned from, unsigned to) -{ - struct queue_item item = queue->items[from]; - - /* move songs to one less in from->to */ - - for (unsigned i = from; i < to; i++) - queue_move_song_to(queue, i + 1, i); - - /* move songs to one more in to->from */ - - for (unsigned i = from; i > to; i--) - queue_move_song_to(queue, i - 1, i); - - /* put song at _to_ */ - - queue->id_to_position[item.id] = to; - queue->items[to] = item; - queue->items[to].version = queue->version; - - /* now deal with order */ - - if (queue->random) { - for (unsigned i = 0; i < queue->length; i++) { - if (queue->order[i] > from && queue->order[i] <= to) - queue->order[i]--; - else if (queue->order[i] < from && - queue->order[i] >= to) - queue->order[i]++; - else if (from == queue->order[i]) - queue->order[i] = to; - } - } -} - -void -queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to) -{ - struct queue_item items[end - start]; - // Copy the original block [start,end-1] - for (unsigned i = start; i < end; i++) - items[i - start] = queue->items[i]; - - // If to > start, we need to move to-start items to start, starting from end - for (unsigned i = end; i < end + to - start; i++) - queue_move_song_to(queue, i, start + i - end); - - // If to < start, we need to move start-to items to newend (= end + to - start), starting from to - // This is the same as moving items from start-1 to to (decreasing), with start-1 going to end-1 - // We have to iterate in this order to avoid writing over something we haven't yet moved - for (unsigned i = start - 1; i >= to && i != G_MAXUINT; i--) - queue_move_song_to(queue, i, i + end - start); - - // Copy the original block back in, starting at to. - for (unsigned i = start; i< end; i++) - { - queue->id_to_position[items[i-start].id] = to + i - start; - queue->items[to + i - start] = items[i-start]; - queue->items[to + i - start].version = queue->version; - } - - if (queue->random) { - // Update the positions in the queue. - // Note that the ranges for these cases are the same as the ranges of - // the loops above. - for (unsigned i = 0; i < queue->length; i++) { - if (queue->order[i] >= end && queue->order[i] < to + end - start) - queue->order[i] -= end - start; - else if (queue->order[i] < start && - queue->order[i] >= to) - queue->order[i] += end - start; - else if (start <= queue->order[i] && queue->order[i] < end) - queue->order[i] += to - start; - } - } -} - -/** - * Moves a song to a new position in the "order" list. - */ -static void -queue_move_order(struct queue *queue, unsigned from_order, unsigned to_order) -{ - assert(queue != NULL); - assert(from_order < queue->length); - assert(to_order <= queue->length); - - const unsigned from_position = - queue_order_to_position(queue, from_order); - - if (from_order < to_order) { - for (unsigned i = from_order; i < to_order; ++i) - queue->order[i] = queue->order[i + 1]; - } else { - for (unsigned i = from_order; i > to_order; --i) - queue->order[i] = queue->order[i - 1]; - } - - queue->order[to_order] = from_position; -} - -void -queue_delete(struct queue *queue, unsigned position) -{ - struct song *song; - unsigned id, order; - - assert(position < queue->length); - - song = queue_get(queue, position); - if (!song_in_database(song)) - song_free(song); - - id = queue_position_to_id(queue, position); - order = queue_position_to_order(queue, position); - - --queue->length; - - /* release the song id */ - - queue->id_to_position[id] = -1; - - /* delete song from songs array */ - - for (unsigned i = position; i < queue->length; i++) - queue_move_song_to(queue, i + 1, i); - - /* delete the entry from the order array */ - - for (unsigned i = order; i < queue->length; i++) - queue->order[i] = queue->order[i + 1]; - - /* readjust values in the order array */ - - for (unsigned i = 0; i < queue->length; i++) - if (queue->order[i] > position) - --queue->order[i]; -} - -void -queue_clear(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; i++) { - struct queue_item *item = &queue->items[i]; - - if (!song_in_database(item->song)) - song_free(item->song); - - queue->id_to_position[item->id] = -1; - } - - queue->length = 0; -} - -void -queue_init(struct queue *queue, unsigned max_length) -{ - queue->max_length = max_length; - queue->length = 0; - queue->version = 1; - queue->repeat = false; - queue->random = false; - queue->single = false; - queue->consume = false; - - queue->items = g_new(struct queue_item, max_length); - queue->order = g_malloc(sizeof(queue->order[0]) * - max_length); - queue->id_to_position = g_malloc(sizeof(queue->id_to_position[0]) * - max_length * QUEUE_HASH_MULT); - - for (unsigned i = 0; i < max_length * QUEUE_HASH_MULT; ++i) - queue->id_to_position[i] = -1; - - queue->rand = g_rand_new(); -} - -void -queue_finish(struct queue *queue) -{ - queue_clear(queue); - - g_free(queue->items); - g_free(queue->order); - g_free(queue->id_to_position); - - g_rand_free(queue->rand); -} - -static const struct queue_item * -queue_get_order_item_const(const struct queue *queue, unsigned order) -{ - assert(queue != NULL); - assert(order < queue->length); - - return &queue->items[queue->order[order]]; -} - -static uint8_t -queue_get_order_priority(const struct queue *queue, unsigned order) -{ - return queue_get_order_item_const(queue, order)->priority; -} - -static gint -queue_item_compare_order_priority(gconstpointer av, gconstpointer bv, - gpointer user_data) -{ - const struct queue *queue = user_data; - const unsigned *const ap = av; - const unsigned *const bp = bv; - assert(ap >= queue->order && ap < queue->order + queue->length); - assert(bp >= queue->order && bp < queue->order + queue->length); - uint8_t a = queue->items[*ap].priority; - uint8_t b = queue->items[*bp].priority; - - if (G_LIKELY(a == b)) - return 0; - else if (a > b) - return -1; - else - return 1; -} - -static void -queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - g_qsort_with_data(&queue->order[start], end - start, - sizeof(queue->order[0]), - queue_item_compare_order_priority, - queue); -} - -/** - * Shuffle the order of items in the specified range, ignoring their - * priorities. - */ -static void -queue_shuffle_order_range(struct queue *queue, unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - for (unsigned i = start; i < end; ++i) - queue_swap_order(queue, i, - g_rand_int_range(queue->rand, i, end)); -} - -/** - * Sort the "order" of items by priority, and then shuffle each - * priority group. - */ -void -queue_shuffle_order_range_with_priority(struct queue *queue, - unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - if (start == end) - return; - - /* first group the range by priority */ - queue_sort_order_by_priority(queue, start, end); - - /* now shuffle each priority group */ - unsigned group_start = start; - uint8_t group_priority = queue_get_order_priority(queue, start); - - for (unsigned i = start + 1; i < end; ++i) { - uint8_t priority = queue_get_order_priority(queue, i); - assert(priority <= group_priority); - - if (priority != group_priority) { - /* start of a new group - shuffle the one that - has just ended */ - queue_shuffle_order_range(queue, group_start, i); - group_start = i; - group_priority = priority; - } - } - - /* shuffle the last group */ - queue_shuffle_order_range(queue, group_start, end); -} - -void -queue_shuffle_order(struct queue *queue) -{ - queue_shuffle_order_range_with_priority(queue, 0, queue->length); -} - -static void -queue_shuffle_order_first(struct queue *queue, unsigned start, unsigned end) -{ - queue_swap_order(queue, start, - g_rand_int_range(queue->rand, start, end)); -} - -void -queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end) -{ - queue_swap_order(queue, end - 1, - g_rand_int_range(queue->rand, start, end)); -} - -void -queue_shuffle_range(struct queue *queue, unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue->length); - - for (unsigned i = start; i < end; i++) { - unsigned ri = g_rand_int_range(queue->rand, i, end); - queue_swap(queue, i, ri); - } -} - -/** - * Find the first item that has this specified priority or higher. - */ -G_GNUC_PURE -static unsigned -queue_find_priority_order(const struct queue *queue, unsigned start_order, - uint8_t priority, unsigned exclude_order) -{ - assert(queue != NULL); - assert(queue->random); - assert(start_order <= queue->length); - - for (unsigned order = start_order; order < queue->length; ++order) { - const unsigned position = queue_order_to_position(queue, order); - const struct queue_item *item = &queue->items[position]; - if (item->priority <= priority && order != exclude_order) - return order; - } - - return queue->length; -} - -G_GNUC_PURE -static unsigned -queue_count_same_priority(const struct queue *queue, unsigned start_order, - uint8_t priority) -{ - assert(queue != NULL); - assert(queue->random); - assert(start_order <= queue->length); - - for (unsigned order = start_order; order < queue->length; ++order) { - const unsigned position = queue_order_to_position(queue, order); - const struct queue_item *item = &queue->items[position]; - if (item->priority != priority) - return order - start_order; - } - - return queue->length - start_order; -} - -bool -queue_set_priority(struct queue *queue, unsigned position, uint8_t priority, - int after_order) -{ - assert(queue != NULL); - assert(position < queue->length); - - struct queue_item *item = &queue->items[position]; - uint8_t old_priority = item->priority; - if (old_priority == priority) - return false; - - item->version = queue->version; - item->priority = priority; - - if (!queue->random) - /* don't reorder if not in random mode */ - return true; - - unsigned order = queue_position_to_order(queue, position); - if (after_order >= 0) { - if (order == (unsigned)after_order) - /* don't reorder the current song */ - return true; - - if (order < (unsigned)after_order) { - /* the specified song has been played already - - enqueue it only if its priority has just - become bigger than the current one's */ - - const unsigned after_position = - queue_order_to_position(queue, after_order); - const struct queue_item *after_item = - &queue->items[after_position]; - if (old_priority > after_item->priority || - priority <= after_item->priority) - /* priority hasn't become bigger */ - return true; - } - } - - /* move the item to the beginning of the priority group (or - create a new priority group) */ - - const unsigned before_order = - queue_find_priority_order(queue, after_order + 1, priority, - order); - const unsigned new_order = before_order > order - ? before_order - 1 - : before_order; - queue_move_order(queue, order, new_order); - - /* shuffle the song within that priority group */ - - const unsigned priority_count = - queue_count_same_priority(queue, new_order, priority); - assert(priority_count >= 1); - queue_shuffle_order_first(queue, new_order, - new_order + priority_count); - - return true; -} - -bool -queue_set_priority_range(struct queue *queue, - unsigned start_position, unsigned end_position, - uint8_t priority, int after_order) -{ - assert(queue != NULL); - assert(start_position <= end_position); - assert(end_position <= queue->length); - - bool modified = false; - int after_position = after_order >= 0 - ? (int)queue_order_to_position(queue, after_order) - : -1; - for (unsigned i = start_position; i < end_position; ++i) { - after_order = after_position >= 0 - ? (int)queue_position_to_order(queue, after_position) - : -1; - - modified |= queue_set_priority(queue, i, priority, - after_order); - } - - return modified; -} diff --git a/src/queue.h b/src/queue.h deleted file mode 100644 index e4bfcdff..00000000 --- a/src/queue.h +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef QUEUE_H -#define QUEUE_H - -#include <glib.h> - -#include <assert.h> -#include <stdbool.h> -#include <stdint.h> - -enum { - /** - * reserve max_length * QUEUE_HASH_MULT elements in the id - * number space - */ - QUEUE_HASH_MULT = 4, -}; - -/** - * One element of the queue: basically a song plus some queue specific - * information attached. - */ -struct queue_item { - struct song *song; - - /** the unique id of this item in the queue */ - unsigned id; - - /** when was this item last changed? */ - uint32_t version; - - /** - * The priority of this item, between 0 and 255. High - * priority value means that this song gets played first in - * "random" mode. - */ - uint8_t priority; -}; - -/** - * A queue of songs. This is the backend of the playlist: it contains - * an ordered list of songs. - * - * Songs can be addressed in three possible ways: - * - * - the position in the queue - * - the unique id (which stays the same, regardless of moves) - * - the order number (which only differs from "position" in random mode) - */ -struct queue { - /** configured maximum length of the queue */ - unsigned max_length; - - /** number of songs in the queue */ - unsigned length; - - /** the current version number */ - uint32_t version; - - /** all songs in "position" order */ - struct queue_item *items; - - /** map order numbers to positions */ - unsigned *order; - - /** map song ids to positions */ - int *id_to_position; - - /** repeat playback when the end of the queue has been - reached? */ - bool repeat; - - /** play only current song. */ - bool single; - - /** remove each played files. */ - bool consume; - - /** play back songs in random order? */ - bool random; - - /** random number generator for shuffle and random mode */ - GRand *rand; -}; - -static inline unsigned -queue_length(const struct queue *queue) -{ - assert(queue->length <= queue->max_length); - - return queue->length; -} - -/** - * Determine if the queue is empty, i.e. there are no songs. - */ -static inline bool -queue_is_empty(const struct queue *queue) -{ - return queue->length == 0; -} - -/** - * Determine if the maximum number of songs has been reached. - */ -static inline bool -queue_is_full(const struct queue *queue) -{ - assert(queue->length <= queue->max_length); - - return queue->length >= queue->max_length; -} - -/** - * Is that a valid position number? - */ -static inline bool -queue_valid_position(const struct queue *queue, unsigned position) -{ - return position < queue->length; -} - -/** - * Is that a valid order number? - */ -static inline bool -queue_valid_order(const struct queue *queue, unsigned order) -{ - return order < queue->length; -} - -static inline int -queue_id_to_position(const struct queue *queue, unsigned id) -{ - if (id >= queue->max_length * QUEUE_HASH_MULT) - return -1; - - assert(queue->id_to_position[id] >= -1); - assert(queue->id_to_position[id] < (int)queue->length); - - return queue->id_to_position[id]; -} - -static inline int -queue_position_to_id(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].id; -} - -static inline unsigned -queue_order_to_position(const struct queue *queue, unsigned order) -{ - assert(order < queue->length); - - return queue->order[order]; -} - -static inline unsigned -queue_position_to_order(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - for (unsigned i = 0;; ++i) { - assert(i < queue->length); - - if (queue->order[i] == position) - return i; - } -} - -G_GNUC_PURE -static inline uint8_t -queue_get_priority_at_position(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].priority; -} - -/** - * Returns the song at the specified position. - */ -static inline struct song * -queue_get(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].song; -} - -/** - * Returns the song at the specified order number. - */ -static inline struct song * -queue_get_order(const struct queue *queue, unsigned order) -{ - return queue_get(queue, queue_order_to_position(queue, order)); -} - -/** - * Is the song at the specified position newer than the specified - * version? - */ -static inline bool -queue_song_newer(const struct queue *queue, unsigned position, - uint32_t version) -{ - assert(position < queue->length); - - return version > queue->version || - queue->items[position].version >= version || - queue->items[position].version == 0; -} - -/** - * Initialize a queue object. - */ -void -queue_init(struct queue *queue, unsigned max_length); - -/** - * Deinitializes a queue object. It does not free the queue pointer - * itself. - */ -void -queue_finish(struct queue *queue); - -/** - * Returns the order number following the specified one. This takes - * end of queue and "repeat" mode into account. - * - * @return the next order number, or -1 to stop playback - */ -int -queue_next_order(const struct queue *queue, unsigned order); - -/** - * Increments the queue's version number. This handles integer - * overflow well. - */ -void -queue_increment_version(struct queue *queue); - -/** - * Marks the specified song as "modified" and increments the version - * number. - */ -void -queue_modify(struct queue *queue, unsigned order); - -/** - * Marks all songs as "modified" and increments the version number. - */ -void -queue_modify_all(struct queue *queue); - -/** - * Appends a song to the queue and returns its position. Prior to - * that, the caller must check if the queue is already full. - * - * If a song is not in the database (determined by - * song_in_database()), it is freed when removed from the queue. - * - * @param priority the priority of this new queue item - */ -unsigned -queue_append(struct queue *queue, struct song *song, uint8_t priority); - -/** - * Swaps two songs, addressed by their position. - */ -void -queue_swap(struct queue *queue, unsigned position1, unsigned position2); - -/** - * Swaps two songs, addressed by their order number. - */ -static inline void -queue_swap_order(struct queue *queue, unsigned order1, unsigned order2) -{ - unsigned tmp = queue->order[order1]; - queue->order[order1] = queue->order[order2]; - queue->order[order2] = tmp; -} - -/** - * Moves a song to a new position. - */ -void -queue_move(struct queue *queue, unsigned from, unsigned to); - -/** - * Moves a range of songs to a new position. - */ -void -queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to); - -/** - * Removes a song from the playlist. - */ -void -queue_delete(struct queue *queue, unsigned position); - -/** - * Removes all songs from the playlist. - */ -void -queue_clear(struct queue *queue); - -/** - * Initializes the "order" array, and restores "normal" order. - */ -static inline void -queue_restore_order(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; ++i) - queue->order[i] = i; -} - -/** - * Shuffle the order of items in the specified range, taking their - * priorities into account. - */ -void -queue_shuffle_order_range_with_priority(struct queue *queue, - unsigned start, unsigned end); - -/** - * Shuffles the virtual order of songs, but does not move them - * physically. This is used in random mode. - */ -void -queue_shuffle_order(struct queue *queue); - -/** - * Shuffles the virtual order of the last song in the specified - * (order) range. This is used in random mode after a song has been - * appended by queue_append(). - */ -void -queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end); - -/** - * Shuffles a (position) range in the queue. The songs are physically - * shuffled, not by using the "order" mapping. - */ -void -queue_shuffle_range(struct queue *queue, unsigned start, unsigned end); - -bool -queue_set_priority(struct queue *queue, unsigned position, - uint8_t priority, int after_order); - -bool -queue_set_priority_range(struct queue *queue, - unsigned start_position, unsigned end_position, - uint8_t priority, int after_order); - -#endif diff --git a/src/queue_print.c b/src/queue_print.c deleted file mode 100644 index d149e8b6..00000000 --- a/src/queue_print.c +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "queue_print.h" -#include "queue.h" -#include "song.h" -#include "song_print.h" -#include "locate.h" -#include "client.h" -#include "mapper.h" - -/** - * Send detailed information about a range of songs in the queue to a - * client. - * - * @param client the client which has requested information - * @param start the index of the first song (including) - * @param end the index of the last song (excluding) - */ -static void -queue_print_song_info(struct client *client, const struct queue *queue, - unsigned position) -{ - song_print_info(client, queue_get(queue, position)); - client_printf(client, "Pos: %u\nId: %u\n", - position, queue_position_to_id(queue, position)); - - uint8_t priority = queue_get_priority_at_position(queue, position); - if (priority != 0) - client_printf(client, "Prio: %u\n", priority); -} - -void -queue_print_info(struct client *client, const struct queue *queue, - unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue_length(queue)); - - for (unsigned i = start; i < end; ++i) - queue_print_song_info(client, queue, i); -} - -void -queue_print_uris(struct client *client, const struct queue *queue, - unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue_length(queue)); - - for (unsigned i = start; i < end; ++i) { - client_printf(client, "%i:", i); - song_print_uri(client, queue_get(queue, i)); - } -} - -void -queue_print_changes_info(struct client *client, const struct queue *queue, - uint32_t version) -{ - for (unsigned i = 0; i < queue_length(queue); i++) { - if (queue_song_newer(queue, i, version)) - queue_print_song_info(client, queue, i); - } -} - -void -queue_print_changes_position(struct client *client, const struct queue *queue, - uint32_t version) -{ - for (unsigned i = 0; i < queue_length(queue); i++) - if (queue_song_newer(queue, i, version)) - client_printf(client, "cpos: %i\nId: %i\n", - i, queue_position_to_id(queue, i)); -} - -void -queue_search(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria) -{ - unsigned i; - struct locate_item_list *new_list = - locate_item_list_casefold(criteria); - - for (i = 0; i < queue_length(queue); i++) { - const struct song *song = queue_get(queue, i); - - if (locate_song_search(song, new_list)) - queue_print_song_info(client, queue, i); - } - - locate_item_list_free(new_list); -} - -void -queue_find(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria) -{ - for (unsigned i = 0; i < queue_length(queue); i++) { - const struct song *song = queue_get(queue, i); - - if (locate_song_match(song, criteria)) - queue_print_song_info(client, queue, i); - } -} diff --git a/src/replay_gain_config.h b/src/replay_gain_config.h index 18747cef..4b59334c 100644 --- a/src/replay_gain_config.h +++ b/src/replay_gain_config.h @@ -30,6 +30,10 @@ extern float replay_gain_preamp; extern float replay_gain_missing_preamp; extern bool replay_gain_limit; +#ifdef __cplusplus +extern "C" { +#endif + void replay_gain_global_init(void); /** @@ -50,6 +54,10 @@ replay_gain_set_mode_string(const char *p); * Returns the "real" mode according to the "auto" setting" */ enum replay_gain_mode -replay_gain_get_real_mode(void); +replay_gain_get_real_mode(bool random_mode); + +#ifdef __cplusplus +} +#endif #endif diff --git a/src/replay_gain_info.h b/src/replay_gain_info.h index 9097c3e0..b06dc6cf 100644 --- a/src/replay_gain_info.h +++ b/src/replay_gain_info.h @@ -22,8 +22,12 @@ #include "check.h" +#ifdef __cplusplus +#include <cmath> +#else #include <stdbool.h> #include <math.h> +#endif enum replay_gain_mode { REPLAY_GAIN_AUTO = -2, @@ -58,9 +62,17 @@ replay_gain_info_init(struct replay_gain_info *info) static inline bool replay_gain_tuple_defined(const struct replay_gain_tuple *tuple) { +#ifdef __cplusplus + return !std::isinf(tuple->gain); +#else return !isinf(tuple->gain); +#endif } +#ifdef __cplusplus +extern "C" { +#endif + float replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, float missing_preamp, bool peak_limit); @@ -71,4 +83,8 @@ replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, flo void replay_gain_info_complete(struct replay_gain_info *info); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/resolver.h b/src/resolver.h index e5ad0675..af14f5f2 100644 --- a/src/resolver.h +++ b/src/resolver.h @@ -20,18 +20,24 @@ #ifndef MPD_RESOLVER_H #define MPD_RESOLVER_H +#include "gcc.h" + #include <glib.h> struct sockaddr; struct addrinfo; -G_GNUC_CONST +gcc_const static inline GQuark resolver_quark(void) { return g_quark_from_static_string("resolver"); } +#ifdef __cplusplus +extern "C" { +#endif + /** * Converts the specified socket address into a string in the form * "IP:PORT". The return value must be freed with g_free() when you @@ -42,7 +48,7 @@ resolver_quark(void) * @param error location to store the error occurring, or NULL to * ignore errors */ -G_GNUC_MALLOC +gcc_malloc char * sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error); @@ -61,4 +67,8 @@ resolve_host_port(const char *host_port, unsigned default_port, int flags, int socktype, GError **error_r); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/server_socket.c b/src/server_socket.c deleted file mode 100644 index 39639959..00000000 --- a/src/server_socket.c +++ /dev/null @@ -1,483 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" - -#ifdef HAVE_STRUCT_UCRED -#define _GNU_SOURCE 1 -#endif - -#include "server_socket.h" -#include "socket_util.h" -#include "resolver.h" -#include "fd_util.h" -#include "glib_socket.h" - -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <string.h> -#include <errno.h> -#include <unistd.h> -#include <stdlib.h> -#include <assert.h> - -#ifdef WIN32 -#include <ws2tcpip.h> -#include <winsock.h> -#else -#include <netinet/in.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <netdb.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "listen" - -#define DEFAULT_PORT 6600 - -struct one_socket { - struct one_socket *next; - struct server_socket *parent; - - unsigned serial; - - int fd; - guint source_id; - - char *path; - - size_t address_length; - struct sockaddr address; -}; - -struct server_socket { - server_socket_callback_t callback; - void *callback_ctx; - - struct one_socket *sockets, **sockets_tail_r; - unsigned next_serial; -}; - -static GQuark -server_socket_quark(void) -{ - return g_quark_from_static_string("server_socket"); -} - -struct server_socket * -server_socket_new(server_socket_callback_t callback, void *callback_ctx) -{ - struct server_socket *ss = g_new(struct server_socket, 1); - ss->callback = callback; - ss->callback_ctx = callback_ctx; - ss->sockets = NULL; - ss->sockets_tail_r = &ss->sockets; - ss->next_serial = 1; - return ss; -} - -void -server_socket_free(struct server_socket *ss) -{ - server_socket_close(ss); - - while (ss->sockets != NULL) { - struct one_socket *s = ss->sockets; - ss->sockets = s->next; - - assert(s->fd < 0); - - g_free(s->path); - g_free(s); - } - - g_free(ss); -} - -/** - * Wraper for sockaddr_to_string() which never fails. - */ -static char * -one_socket_to_string(const struct one_socket *s) -{ - char *p = sockaddr_to_string(&s->address, s->address_length, NULL); - if (p == NULL) - p = g_strdup("[unknown]"); - return p; -} - -static int -get_remote_uid(int fd) -{ -#ifdef HAVE_STRUCT_UCRED - struct ucred cred; - socklen_t len = sizeof (cred); - - if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0) - return 0; - - return cred.uid; -#else -#ifdef HAVE_GETPEEREID - uid_t euid; - gid_t egid; - - if (getpeereid(fd, &euid, &egid) == 0) - return euid; -#else - (void)fd; -#endif - return -1; -#endif -} - -static gboolean -server_socket_in_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - gpointer data) -{ - struct one_socket *s = data; - - struct sockaddr_storage address; - size_t address_length = sizeof(address); - int fd = accept_cloexec_nonblock(s->fd, (struct sockaddr*)&address, - &address_length); - if (fd >= 0) { - if (socket_keepalive(fd)) - g_warning("Could not set TCP keepalive option: %s", - g_strerror(errno)); - s->parent->callback(fd, (const struct sockaddr*)&address, - address_length, get_remote_uid(fd), - s->parent->callback_ctx); - } else { - g_warning("accept() failed: %s", g_strerror(errno)); - } - - return true; -} - -static void -set_fd(struct one_socket *s, int fd) -{ - assert(s != NULL); - assert(s->fd < 0); - assert(fd >= 0); - - s->fd = fd; - - GIOChannel *channel = g_io_channel_new_socket(s->fd); - s->source_id = g_io_add_watch(channel, G_IO_IN, - server_socket_in_event, s); - g_io_channel_unref(channel); -} - -bool -server_socket_open(struct server_socket *ss, GError **error_r) -{ - struct one_socket *good = NULL, *bad = NULL; - GError *last_error = NULL; - - for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) { - assert(s->serial > 0); - assert(good == NULL || s->serial >= good->serial); - assert(s->fd < 0); - - if (bad != NULL && s->serial != bad->serial) { - server_socket_close(ss); - g_propagate_error(error_r, last_error); - return false; - } - - GError *error = NULL; - int fd = socket_bind_listen(s->address.sa_family, - SOCK_STREAM, 0, - &s->address, s->address_length, 5, - &error); - if (fd < 0) { - if (good != NULL && good->serial == s->serial) { - char *address_string = one_socket_to_string(s); - char *good_string = one_socket_to_string(good); - g_warning("bind to '%s' failed: %s " - "(continuing anyway, because " - "binding to '%s' succeeded)", - address_string, error->message, - good_string); - g_free(address_string); - g_free(good_string); - g_error_free(error); - } else if (bad == NULL) { - bad = s; - - char *address_string = one_socket_to_string(s); - g_propagate_prefixed_error(&last_error, error, - "Failed to bind to '%s': ", - address_string); - g_free(address_string); - } else - g_error_free(error); - continue; - } - - /* allow everybody to connect */ - - if (s->path != NULL) - chmod(s->path, 0666); - - /* register in the GLib main loop */ - - set_fd(s, fd); - - /* mark this socket as "good", and clear previous - errors */ - - good = s; - - if (bad != NULL) { - bad = NULL; - g_error_free(last_error); - last_error = NULL; - } - } - - if (bad != NULL) { - server_socket_close(ss); - g_propagate_error(error_r, last_error); - return false; - } - - return true; -} - -void -server_socket_close(struct server_socket *ss) -{ - for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) { - if (s->fd < 0) - continue; - - g_source_remove(s->source_id); - close_socket(s->fd); - s->fd = -1; - } -} - -static struct one_socket * -one_socket_new(unsigned serial, const struct sockaddr *address, - size_t address_length) -{ - assert(address != NULL); - assert(address_length > 0); - - struct one_socket *s = g_malloc(sizeof(*s) - sizeof(s->address) + - address_length); - s->next = NULL; - s->serial = serial; - s->fd = -1; - s->path = NULL; - s->address_length = address_length; - memcpy(&s->address, address, address_length); - - return s; -} - -bool -server_socket_add_fd(struct server_socket *ss, int fd, GError **error_r) -{ - assert(ss != NULL); - assert(ss->sockets_tail_r != NULL); - assert(*ss->sockets_tail_r == NULL); - assert(fd >= 0); - - struct sockaddr_storage address; - socklen_t address_length; - if (getsockname(fd, (struct sockaddr *)&address, - &address_length) < 0) { - g_set_error(error_r, server_socket_quark(), errno, - "Failed to get socket address: %s", - g_strerror(errno)); - return false; - } - - struct one_socket *s = one_socket_new(ss->next_serial, - (struct sockaddr *)&address, - address_length); - s->parent = ss; - *ss->sockets_tail_r = s; - ss->sockets_tail_r = &s->next; - - set_fd(s, fd); - - return true; -} - -static struct one_socket * -server_socket_add_address(struct server_socket *ss, - const struct sockaddr *address, - size_t address_length) -{ - assert(ss != NULL); - assert(ss->sockets_tail_r != NULL); - assert(*ss->sockets_tail_r == NULL); - - struct one_socket *s = one_socket_new(ss->next_serial, - address, address_length); - s->parent = ss; - *ss->sockets_tail_r = s; - ss->sockets_tail_r = &s->next; - - return s; -} - -#ifdef HAVE_TCP - -/** - * Add a listener on a port on all IPv4 interfaces. - * - * @param port the TCP port - */ -static void -server_socket_add_port_ipv4(struct server_socket *ss, unsigned port) -{ - struct sockaddr_in sin; - memset(&sin, 0, sizeof(sin)); - sin.sin_port = htons(port); - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = INADDR_ANY; - - server_socket_add_address(ss, (const struct sockaddr *)&sin, - sizeof(sin)); -} - -#ifdef HAVE_IPV6 -/** - * Add a listener on a port on all IPv6 interfaces. - * - * @param port the TCP port - */ -static void -server_socket_add_port_ipv6(struct server_socket *ss, unsigned port) -{ - struct sockaddr_in6 sin; - memset(&sin, 0, sizeof(sin)); - sin.sin6_port = htons(port); - sin.sin6_family = AF_INET6; - - server_socket_add_address(ss, (const struct sockaddr *)&sin, - sizeof(sin)); -} -#endif /* HAVE_IPV6 */ - -#endif /* HAVE_TCP */ - -bool -server_socket_add_port(struct server_socket *ss, unsigned port, - GError **error_r) -{ -#ifdef HAVE_TCP - if (port == 0 || port > 0xffff) { - g_set_error(error_r, server_socket_quark(), 0, - "Invalid TCP port"); - return false; - } - -#ifdef HAVE_IPV6 - server_socket_add_port_ipv6(ss, port); -#endif - server_socket_add_port_ipv4(ss, port); - - ++ss->next_serial; - - return true; -#else /* HAVE_TCP */ - (void)ss; - (void)port; - - g_set_error(error_r, server_socket_quark(), 0, - "TCP support is disabled"); - return false; -#endif /* HAVE_TCP */ -} - -bool -server_socket_add_host(struct server_socket *ss, const char *hostname, - unsigned port, GError **error_r) -{ -#ifdef HAVE_TCP - struct addrinfo *ai = resolve_host_port(hostname, port, - AI_PASSIVE, SOCK_STREAM, - error_r); - if (ai == NULL) - return false; - - for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next) - server_socket_add_address(ss, i->ai_addr, i->ai_addrlen); - - freeaddrinfo(ai); - - ++ss->next_serial; - - return true; -#else /* HAVE_TCP */ - (void)ss; - (void)hostname; - (void)port; - - g_set_error(error_r, server_socket_quark(), 0, - "TCP support is disabled"); - return false; -#endif /* HAVE_TCP */ -} - -bool -server_socket_add_path(struct server_socket *ss, const char *path, - GError **error_r) -{ -#ifdef HAVE_UN - struct sockaddr_un s_un; - - size_t path_length = strlen(path); - if (path_length >= sizeof(s_un.sun_path)) { - g_set_error(error_r, server_socket_quark(), 0, - "UNIX socket path is too long"); - return false; - } - - unlink(path); - - s_un.sun_family = AF_UNIX; - memcpy(s_un.sun_path, path, path_length + 1); - - struct one_socket *s = - server_socket_add_address(ss, (const struct sockaddr *)&s_un, - sizeof(s_un)); - s->path = g_strdup(path); - - return true; -#else /* !HAVE_UN */ - (void)ss; - (void)path; - - g_set_error(error_r, server_socket_quark(), 0, - "UNIX domain socket support is disabled"); - return false; -#endif /* !HAVE_UN */ -} - diff --git a/src/server_socket.h b/src/server_socket.h deleted file mode 100644 index 7caa4bbf..00000000 --- a/src/server_socket.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SERVER_SOCKET_H -#define MPD_SERVER_SOCKET_H - -#include <stdbool.h> - -#include <glib.h> - -struct sockaddr; - -typedef void (*server_socket_callback_t)(int fd, - const struct sockaddr *address, - size_t address_length, int uid, - void *ctx); - -struct server_socket * -server_socket_new(server_socket_callback_t callback, void *callback_ctx); - -void -server_socket_free(struct server_socket *ss); - -bool -server_socket_open(struct server_socket *ss, GError **error_r); - -void -server_socket_close(struct server_socket *ss); - -/** - * Add a socket descriptor that is accepting connections. After this - * has been called, don't call server_socket_open(), because the - * socket is already open. - */ -bool -server_socket_add_fd(struct server_socket *ss, int fd, GError **error_r); - -/** - * Add a listener on a port on all interfaces. - * - * @param port the TCP port - * @param error_r location to store the error occurring, or NULL to - * ignore errors - * @return true on success - */ -bool -server_socket_add_port(struct server_socket *ss, unsigned port, - GError **error_r); - -/** - * Resolves a host name, and adds listeners on all addresses in the - * result set. - * - * @param hostname the host name to be resolved - * @param port the TCP port - * @param error_r location to store the error occurring, or NULL to - * ignore errors - * @return true on success - */ -bool -server_socket_add_host(struct server_socket *ss, const char *hostname, - unsigned port, GError **error_r); - -/** - * Add a listener on a Unix domain socket. - * - * @param path the absolute socket path - * @param error_r location to store the error occurring, or NULL to - * ignore errors - * @return true on success - */ -bool -server_socket_add_path(struct server_socket *ss, const char *path, - GError **error_r); - -#endif @@ -21,7 +21,9 @@ #define MPD_SONG_H #include "util/list.h" +#include "gcc.h" +#include <assert.h> #include <stddef.h> #include <stdbool.h> #include <sys/time.h> @@ -41,7 +43,7 @@ struct song { struct list_head siblings; struct tag *tag; - struct directory *parent; + struct Directory *parent; time_t mtime; /** @@ -58,13 +60,23 @@ struct song { char uri[sizeof(int)]; }; +/** + * A dummy #directory instance that is used for "detached" song + * copies. + */ +extern struct Directory detached_root; + +#ifdef __cplusplus +extern "C" { +#endif + /** allocate a new song with a remote URL */ struct song * song_remote_new(const char *uri); /** allocate a new song with a local file name */ struct song * -song_file_new(const char *path, struct directory *parent); +song_file_new(const char *path_utf8, struct Directory *parent); /** * allocate a new song structure with a local file name and attempt to @@ -72,7 +84,7 @@ song_file_new(const char *path, struct directory *parent); * data, NULL is returned. */ struct song * -song_file_load(const char *path, struct directory *parent); +song_file_load(const char *path_utf8, struct Directory *parent); /** * Replaces the URI of a song object. The given song object is @@ -83,9 +95,52 @@ song_file_load(const char *path, struct directory *parent); struct song * song_replace_uri(struct song *song, const char *uri); +/** + * Creates a "detached" song object. + */ +struct song * +song_detached_new(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_is_detached(). + */ +gcc_malloc +struct song * +song_dup_detached(const struct song *src); + void song_free(struct song *song); +static inline bool +song_in_database(const struct song *song) +{ + return song->parent != NULL; +} + +static inline bool +song_is_file(const struct song *song) +{ + return song_in_database(song) || song->uri[0] == '/'; +} + +static inline bool +song_is_detached(const struct song *song) +{ + assert(song != NULL); + assert(song_in_database(song)); + + return song->parent == &detached_root; +} + +/** + * Returns true if both objects refer to the same physical song. + */ +gcc_pure +bool +song_equals(const struct song *a, const struct song *b); + bool song_file_update(struct song *song); @@ -105,16 +160,8 @@ song_get_uri(const struct song *song); double song_get_duration(const struct song *song); -static inline bool -song_in_database(const struct song *song) -{ - return song->parent != NULL; -} - -static inline bool -song_is_file(const struct song *song) -{ - return song_in_database(song) || song->uri[0] == '/'; +#ifdef __cplusplus } +#endif #endif diff --git a/src/state_file.c b/src/state_file.c deleted file mode 100644 index de0e7053..00000000 --- a/src/state_file.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "state_file.h" -#include "output_state.h" -#include "playlist.h" -#include "playlist_state.h" -#include "volume.h" -#include "text_file.h" - -#include <glib.h> -#include <assert.h> -#include <string.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "state_file" - -static char *state_file_path; - -/** the GLib source id for the save timer */ -static guint save_state_source_id; - -/** - * These version numbers determine whether we need to save the state - * file. If nothing has changed, we won't let the hard drive spin up. - */ -static unsigned prev_volume_version, prev_output_version, - prev_playlist_version; - -static void -state_file_write(struct player_control *pc) -{ - FILE *fp; - - assert(state_file_path != NULL); - - g_debug("Saving state file %s", state_file_path); - - fp = fopen(state_file_path, "w"); - if (G_UNLIKELY(!fp)) { - g_warning("failed to create %s: %s", - state_file_path, g_strerror(errno)); - return; - } - - save_sw_volume_state(fp); - audio_output_state_save(fp); - playlist_state_save(fp, &g_playlist, pc); - - fclose(fp); - - prev_volume_version = sw_volume_state_get_hash(); - prev_output_version = audio_output_state_get_version(); - prev_playlist_version = playlist_state_get_hash(&g_playlist, pc); -} - -static void -state_file_read(struct player_control *pc) -{ - FILE *fp; - bool success; - - assert(state_file_path != NULL); - - g_debug("Loading state file %s", state_file_path); - - fp = fopen(state_file_path, "r"); - if (G_UNLIKELY(!fp)) { - g_warning("failed to open %s: %s", - state_file_path, g_strerror(errno)); - return; - } - - GString *buffer = g_string_sized_new(1024); - const char *line; - while ((line = read_text_line(fp, buffer)) != NULL) { - success = read_sw_volume_state(line) || - audio_output_state_read(line) || - playlist_state_restore(line, fp, buffer, - &g_playlist, pc); - if (!success) - g_warning("Unrecognized line in state file: %s", line); - } - - fclose(fp); - - prev_volume_version = sw_volume_state_get_hash(); - prev_output_version = audio_output_state_get_version(); - prev_playlist_version = playlist_state_get_hash(&g_playlist, pc); - - - g_string_free(buffer, true); -} - -/** - * This function is called every 5 minutes by the GLib main loop, and - * saves the state file. - */ -static gboolean -timer_save_state_file(gpointer data) -{ - struct player_control *pc = data; - - if (prev_volume_version == sw_volume_state_get_hash() && - prev_output_version == audio_output_state_get_version() && - prev_playlist_version == playlist_state_get_hash(&g_playlist, pc)) - /* nothing has changed - don't save the state file, - don't spin up the hard disk */ - return true; - - state_file_write(pc); - return true; -} - -void -state_file_init(const char *path, struct player_control *pc) -{ - assert(state_file_path == NULL); - - if (path == NULL) - return; - - state_file_path = g_strdup(path); - state_file_read(pc); - - save_state_source_id = g_timeout_add_seconds(5 * 60, - timer_save_state_file, - pc); -} - -void -state_file_finish(struct player_control *pc) -{ - if (state_file_path == NULL) - /* no state file configured, no cleanup required */ - return; - - if (save_state_source_id != 0) - g_source_remove(save_state_source_id); - - state_file_write(pc); - - g_free(state_file_path); -} diff --git a/src/stats.c b/src/stats.c deleted file mode 100644 index fe6a064a..00000000 --- a/src/stats.c +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "stats.h" -#include "database.h" -#include "db_visitor.h" -#include "tag.h" -#include "song.h" -#include "client.h" -#include "player_control.h" -#include "strset.h" -#include "client_internal.h" - -struct stats stats; - -void stats_global_init(void) -{ - stats.timer = g_timer_new(); -} - -void stats_global_finish(void) -{ - g_timer_destroy(stats.timer); -} - -struct visit_data { - struct strset *artists; - struct strset *albums; -}; - -static void -visit_tag(struct visit_data *data, const struct tag *tag) -{ - if (tag->time > 0) - stats.song_duration += tag->time; - - for (unsigned i = 0; i < tag->num_items; ++i) { - const struct tag_item *item = tag->items[i]; - - switch (item->type) { - case TAG_ARTIST: - strset_add(data->artists, item->value); - break; - - case TAG_ALBUM: - strset_add(data->albums, item->value); - break; - - default: - break; - } - } -} - -static bool -collect_stats_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct visit_data *data = _data; - - ++stats.song_count; - - if (song->tag != NULL) - visit_tag(data, song->tag); - - return true; -} - -static const struct db_visitor collect_stats_visitor = { - .song = collect_stats_song, -}; - -void stats_update(void) -{ - struct visit_data data; - - stats.song_count = 0; - stats.song_duration = 0; - stats.artist_count = 0; - - data.artists = strset_new(); - data.albums = strset_new(); - - db_walk("", &collect_stats_visitor, &data, NULL); - - stats.artist_count = strset_size(data.artists); - stats.album_count = strset_size(data.albums); - - strset_free(data.artists); - strset_free(data.albums); -} - -int stats_print(struct client *client) -{ - client_printf(client, - "artists: %u\n" - "albums: %u\n" - "songs: %i\n" - "uptime: %li\n" - "playtime: %li\n" - "db_playtime: %li\n" - "db_update: %li\n", - stats.artist_count, - stats.album_count, - stats.song_count, - (long)g_timer_elapsed(stats.timer, NULL), - (long)(pc_get_total_play_time(client->player_control) + 0.5), - stats.song_duration, - (long)db_get_mtime()); - return 0; -} diff --git a/src/stats.h b/src/stats.h index a686477d..eb723bcf 100644 --- a/src/stats.h +++ b/src/stats.h @@ -22,7 +22,7 @@ #include <glib.h> -struct client; +class Client; struct stats { GTimer *timer; @@ -49,6 +49,7 @@ void stats_global_finish(void); void stats_update(void); -int stats_print(struct client *client); +void +stats_print(Client *client); #endif diff --git a/src/string_util.c b/src/string_util.c index 6e542907..5d9feccf 100644 --- a/src/string_util.c +++ b/src/string_util.c @@ -20,6 +20,8 @@ #include "config.h" #include "string_util.h" +#include <stdlib.h> /* for malloc() */ +#include <string.h> /* for strnlen() */ #include <glib.h> #include <assert.h> @@ -45,3 +47,37 @@ string_array_contains(const char *const* haystack, const char *needle) return false; } + +#ifndef HAVE_STRNLEN + +size_t +strnlen(const char *s, size_t max) +{ + assert(s != NULL); + + const char *t = memchr(s, 0, max); + return t != NULL + ? (size_t)(t - s) + : max; +} + +#endif + +#if !defined(HAVE_STRNDUP) + +char * +strndup(const char *str, size_t n) +{ + assert(str != NULL); + + size_t len = strnlen(str, n); + char* ret = (char *) malloc(len + 1); + if (ret == NULL) + return NULL; + + memcpy(ret, str, len); + ret[len] = '\0'; + return ret; +} + +#endif diff --git a/src/string_util.h b/src/string_util.h index dc80a46e..62de5387 100644 --- a/src/string_util.h +++ b/src/string_util.h @@ -20,18 +20,26 @@ #ifndef MPD_STRING_UTIL_H #define MPD_STRING_UTIL_H -#include <glib.h> +#include "gcc.h" #include <stdbool.h> +#include <stdlib.h> /* for size_t */ + +#ifdef __cplusplus +extern "C" { +#endif /** * Remove the "const" attribute from a string pointer. This is a * dirty hack, don't use it unless you know what you're doing! */ -G_GNUC_CONST +gcc_const static inline char * deconst_string(const char *p) { +#ifdef __cplusplus + return const_cast<char *>(p); +#else union { const char *in; char *out; @@ -40,6 +48,7 @@ deconst_string(const char *p) }; return u.out; +#endif } /** @@ -49,14 +58,14 @@ deconst_string(const char *p) * This is a faster version of g_strchug(), because it does not move * data. */ -G_GNUC_PURE +gcc_pure const char * strchug_fast_c(const char *p); /** * Same as strchug_fast_c(), but works with a writable pointer. */ -G_GNUC_PURE +gcc_pure static inline char * strchug_fast(char *p) { @@ -74,4 +83,33 @@ strchug_fast(char *p) bool string_array_contains(const char *const* haystack, const char *needle); +#ifndef HAVE_STRNLEN + +gcc_pure +size_t +strnlen(const char *s, size_t max); + +#endif + +#if !defined(HAVE_STRNDUP) + +/** + * Duplicates the string to a newly allocated buffer + * copying at most n characters. + * + * @param str a string to duplicate + * @param n maximal number of characters to copy + * @return a pointer to the duplicated string, + * or NULL if memory allocation failed. + */ +gcc_malloc +char * +strndup(const char *str, size_t n); + +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif diff --git a/src/strset.c b/src/strset.c deleted file mode 100644 index 5862e407..00000000 --- a/src/strset.c +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "strset.h" - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -#define NUM_SLOTS 16384 - -struct strset_slot { - struct strset_slot *next; - const char *value; -}; - -struct strset { - unsigned size; - - struct strset_slot *current_slot; - unsigned next_slot; - - struct strset_slot slots[NUM_SLOTS]; -}; - -static unsigned calc_hash(const char *p) { - unsigned hash = 5381; - - assert(p != NULL); - - while (*p != 0) - hash = (hash << 5) + hash + *p++; - - return hash; -} - -G_GNUC_MALLOC struct strset *strset_new(void) -{ - struct strset *set = g_new0(struct strset, 1); - return set; -} - -void strset_free(struct strset *set) -{ - unsigned i; - - for (i = 0; i < NUM_SLOTS; ++i) { - struct strset_slot *slot = set->slots[i].next, *next; - - while (slot != NULL) { - next = slot->next; - g_free(slot); - slot = next; - } - } - - g_free(set); -} - -void strset_add(struct strset *set, const char *value) -{ - struct strset_slot *base_slot - = &set->slots[calc_hash(value) % NUM_SLOTS]; - struct strset_slot *slot = base_slot; - - if (base_slot->value == NULL) { - /* empty slot - put into base_slot */ - assert(base_slot->next == NULL); - - base_slot->value = value; - ++set->size; - return; - } - - for (slot = base_slot; slot != NULL; slot = slot->next) - if (strcmp(slot->value, value) == 0) - /* found it - do nothing */ - return; - - /* insert it into the slot chain */ - slot = g_new(struct strset_slot, 1); - slot->next = base_slot->next; - slot->value = value; - base_slot->next = slot; - ++set->size; -} - -int strset_get(const struct strset *set, const char *value) -{ - const struct strset_slot *slot = &set->slots[calc_hash(value)]; - - if (slot->value == NULL) - return 0; - - for (slot = slot->next; slot != NULL; slot = slot->next) - if (strcmp(slot->value, value) == 0) - /* found it - do nothing */ - return 1; - - return 0; -} - -unsigned strset_size(const struct strset *set) -{ - return set->size; -} - -void strset_rewind(struct strset *set) -{ - set->current_slot = NULL; - set->next_slot = 0; -} - -const char *strset_next(struct strset *set) -{ - if (set->current_slot != NULL && set->current_slot->next != NULL) { - set->current_slot = set->current_slot->next; - return set->current_slot->value; - } - - while (set->next_slot < NUM_SLOTS && - set->slots[set->next_slot].value == NULL) - ++set->next_slot; - - if (set->next_slot >= NUM_SLOTS) - return NULL; - - set->current_slot = &set->slots[set->next_slot++]; - return set->current_slot->value; -} - diff --git a/src/strset.h b/src/strset.h deleted file mode 100644 index 5382e59b..00000000 --- a/src/strset.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * "struct strset" is a hashed string set: you can add strings to this - * library, and it stores them as a set of unique strings. You can - * get the size of the set, and you can enumerate through all values. - * - * It is important to note that the strset does not copy the string - * values - it stores the exact pointers it was given in strset_add(). - */ - -#ifndef MPD_STRSET_H -#define MPD_STRSET_H - -#include <glib.h> - -struct strset; - -G_GNUC_MALLOC struct strset *strset_new(void); - -void strset_free(struct strset *set); - -void strset_add(struct strset *set, const char *value); - -int strset_get(const struct strset *set, const char *value); - -unsigned strset_size(const struct strset *set); - -void strset_rewind(struct strset *set); - -const char *strset_next(struct strset *set); - -#endif @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -101,6 +101,10 @@ struct tag { unsigned num_items; }; +#ifdef __cplusplus +extern "C" { +#endif + /** * Parse the string, and convert it into a #tag_type. Returns * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized. @@ -236,4 +240,8 @@ bool tag_has_type(const struct tag *tag, enum tag_type type); */ bool tag_equal(const struct tag *tag1, const struct tag *tag2); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/tag_id3.c b/src/tag_id3.c index 0971829f..5744e0e6 100644 --- a/src/tag_id3.c +++ b/src/tag_id3.c @@ -25,6 +25,7 @@ #include "riff.h" #include "aiff.h" #include "conf.h" +#include "io_error.h" #include <glib.h> #include <id3tag.h> @@ -343,7 +344,7 @@ tag_id3_import_ufid(struct id3_tag *id3_tag, } } -static void +void scan_id3_tag(struct id3_tag *tag, const struct tag_handler *handler, void *handler_ctx) { @@ -546,7 +547,7 @@ tag_id3_load(const char *path_fs, GError **error_r) { FILE *file = fopen(path_fs, "rb"); if (file == NULL) { - g_set_error(error_r, g_file_error_quark(), errno, + g_set_error(error_r, errno_quark(), errno, "Failed to open file %s: %s", path_fs, g_strerror(errno)); return NULL; diff --git a/src/tag_id3.h b/src/tag_id3.h index 049c53ad..1907c13f 100644 --- a/src/tag_id3.h +++ b/src/tag_id3.h @@ -21,8 +21,8 @@ #define MPD_TAG_ID3_H #include "check.h" - -#include <glib.h> +#include "gcc.h" +#include "gerror.h" #include <stdbool.h> @@ -48,12 +48,20 @@ struct tag *tag_id3_import(struct id3_tag *); struct id3_tag * tag_id3_load(const char *path_fs, GError **error_r); +/** + * Import all tags from the provided id3_tag *tag + * + */ +void +scan_id3_tag(struct id3_tag *tag, + const struct tag_handler *handler, void *handler_ctx); + #else static inline bool -tag_id3_scan(G_GNUC_UNUSED const char *path_fs, - G_GNUC_UNUSED const struct tag_handler *handler, - G_GNUC_UNUSED void *handler_ctx) +tag_id3_scan(gcc_unused const char *path_fs, + gcc_unused const struct tag_handler *handler, + gcc_unused void *handler_ctx) { return false; } diff --git a/src/tag_table.h b/src/tag_table.h index d87d4869..8e2172c1 100644 --- a/src/tag_table.h +++ b/src/tag_table.h @@ -21,6 +21,7 @@ #define MPD_TAG_TABLE_H #include "tag.h" +#include "gcc.h" #include <glib.h> @@ -35,7 +36,7 @@ struct tag_table { * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found * in the table. */ -G_GNUC_PURE +gcc_pure static inline enum tag_type tag_table_lookup(const struct tag_table *table, const char *name) { @@ -51,7 +52,7 @@ tag_table_lookup(const struct tag_table *table, const char *name) * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found * in the table. */ -G_GNUC_PURE +gcc_pure static inline enum tag_type tag_table_lookup_i(const struct tag_table *table, const char *name) { diff --git a/src/text_file.h b/src/text_file.h deleted file mode 100644 index 9dd81094..00000000 --- a/src/text_file.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TEXT_FILE_H -#define MPD_TEXT_FILE_H - -#include <glib.h> - -#include <stdio.h> - -/** - * Reads a line from the input file, and strips trailing space. There - * is a reasonable maximum line length, only to prevent denial of - * service. - * - * @param file the source file, opened in text mode - * @param buffer an allocator for the buffer - * @return a pointer to the line, or NULL on end-of-file or error - */ -char * -read_text_line(FILE *file, GString *buffer); - -#endif diff --git a/src/text_input_stream.c b/src/text_input_stream.c index 4a858fc8..4a2eeb81 100644 --- a/src/text_input_stream.c +++ b/src/text_input_stream.c @@ -20,7 +20,7 @@ #include "config.h" #include "text_input_stream.h" #include "input_stream.h" -#include "fifo_buffer.h" +#include "util/fifo_buffer.h" #include <glib.h> diff --git a/src/thread/Cond.hxx b/src/thread/Cond.hxx new file mode 100644 index 00000000..bbaabddc --- /dev/null +++ b/src/thread/Cond.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_THREAD_COND_HXX +#define MPD_THREAD_COND_HXX + +#ifdef WIN32 + +/* mingw-w64 4.6.3 lacks a std::cond implementation */ + +#include "WindowsCond.hxx" +typedef WindowsCond Cond; + +#else + +#include "PosixCond.hxx" +typedef PosixCond Cond; + +#endif + +#endif diff --git a/src/thread/CriticalSection.hxx b/src/thread/CriticalSection.hxx new file mode 100644 index 00000000..8bc05b8f --- /dev/null +++ b/src/thread/CriticalSection.hxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009-2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_CRITICAL_SECTION_HXX +#define MPD_THREAD_CRITICAL_SECTION_HXX + +#include <windows.h> + +/** + * Wrapper for a CRITICAL_SECTION, backend for the Mutex class. + */ +class CriticalSection { + friend class WindowsCond; + + CRITICAL_SECTION critical_section; + +public: + CriticalSection() { + ::InitializeCriticalSection(&critical_section); + } + + ~CriticalSection() { + ::DeleteCriticalSection(&critical_section); + } + + CriticalSection(const CriticalSection &other) = delete; + CriticalSection &operator=(const CriticalSection &other) = delete; + + void lock() { + ::EnterCriticalSection(&critical_section); + }; + + bool try_lock() { + return ::TryEnterCriticalSection(&critical_section) != 0; + }; + + void unlock() { + ::LeaveCriticalSection(&critical_section); + } +}; + +#endif diff --git a/src/thread/GLibCond.hxx b/src/thread/GLibCond.hxx new file mode 100644 index 00000000..9ab08e9f --- /dev/null +++ b/src/thread/GLibCond.hxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_GLIB_COND_HXX +#define MPD_THREAD_GLIB_COND_HXX + +#include "GLibMutex.hxx" + +/** + * A wrapper for GCond. + */ +class GLibCond { +#if GLIB_CHECK_VERSION(2,32,0) + GCond cond; +#else + GCond *cond; +#endif + +public: + GLibCond() { +#if GLIB_CHECK_VERSION(2,32,0) + g_cond_init(&cond); +#else + cond = g_cond_new(); +#endif + } + + ~GLibCond() { +#if GLIB_CHECK_VERSION(2,32,0) + g_cond_clear(&cond); +#else + g_cond_free(cond); +#endif + } + + GLibCond(const GLibCond &other) = delete; + GLibCond &operator=(const GLibCond &other) = delete; + +private: + GCond *GetNative() { +#if GLIB_CHECK_VERSION(2,32,0) + return &cond; +#else + return cond; +#endif + } + +public: + void signal() { + g_cond_signal(GetNative()); + } + + void broadcast() { + g_cond_broadcast(GetNative()); + } + + void wait(GLibMutex &mutex) { + g_cond_wait(GetNative(), mutex.GetNative()); + } +}; + +#endif diff --git a/src/thread/GLibMutex.hxx b/src/thread/GLibMutex.hxx new file mode 100644 index 00000000..2c666c1e --- /dev/null +++ b/src/thread/GLibMutex.hxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_GLIB_MUTEX_HXX +#define MPD_THREAD_GLIB_MUTEX_HXX + +#include <glib.h> + +/** + * A wrapper for GMutex. + */ +class GLibMutex { + friend class GLibCond; + +#if GLIB_CHECK_VERSION(2,32,0) + GMutex mutex; +#else + GMutex *mutex; +#endif + +public: + GLibMutex() { +#if GLIB_CHECK_VERSION(2,32,0) + g_mutex_init(&mutex); +#else + mutex = g_mutex_new(); +#endif + } + + ~GLibMutex() { +#if GLIB_CHECK_VERSION(2,32,0) + g_mutex_clear(&mutex); +#else + g_mutex_free(mutex); +#endif + } + + GLibMutex(const GLibMutex &other) = delete; + GLibMutex &operator=(const GLibMutex &other) = delete; + +private: + GMutex *GetNative() { +#if GLIB_CHECK_VERSION(2,32,0) + return &mutex; +#else + return mutex; +#endif + } + +public: + void lock() { + g_mutex_lock(GetNative()); + } + + bool try_lock() { + return g_mutex_trylock(GetNative()); + } + + void unlock() { + g_mutex_lock(GetNative()); + } +}; + +#endif diff --git a/src/thread/Mutex.hxx b/src/thread/Mutex.hxx new file mode 100644 index 00000000..675af74b --- /dev/null +++ b/src/thread/Mutex.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_THREAD_MUTEX_HXX +#define MPD_THREAD_MUTEX_HXX + +#ifdef WIN32 + +/* mingw-w64 4.6.3 lacks a std::mutex implementation */ + +#include "CriticalSection.hxx" +typedef CriticalSection Mutex; + +#else + +#include "PosixMutex.hxx" + +typedef PosixMutex Mutex; + +#endif + +class ScopeLock { + Mutex &mutex; + +public: + ScopeLock(Mutex &_mutex):mutex(_mutex) { + mutex.lock(); + }; + + ~ScopeLock() { + mutex.unlock(); + }; + + ScopeLock(const ScopeLock &other) = delete; + ScopeLock &operator=(const ScopeLock &other) = delete; +}; + +#endif diff --git a/src/thread/PosixCond.hxx b/src/thread/PosixCond.hxx new file mode 100644 index 00000000..acdc05ed --- /dev/null +++ b/src/thread/PosixCond.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2009-2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_POSIX_COND_HXX +#define MPD_THREAD_POSIX_COND_HXX + +#include "PosixMutex.hxx" + +/** + * Low-level wrapper for a pthread_cond_t. + */ +class PosixCond { + pthread_cond_t cond; + +public: + constexpr PosixCond():cond(PTHREAD_COND_INITIALIZER) {} + + PosixCond(const PosixCond &other) = delete; + PosixCond &operator=(const PosixCond &other) = delete; + + void signal() { + pthread_cond_signal(&cond); + } + + void broadcast() { + pthread_cond_broadcast(&cond); + } + + void wait(PosixMutex &mutex) { + pthread_cond_wait(&cond, &mutex.mutex); + } +}; + +#endif diff --git a/src/thread/PosixMutex.hxx b/src/thread/PosixMutex.hxx new file mode 100644 index 00000000..d50764af --- /dev/null +++ b/src/thread/PosixMutex.hxx @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2009-2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_POSIX_MUTEX_HXX +#define MPD_THREAD_POSIX_MUTEX_HXX + +#include <pthread.h> + +/** + * Low-level wrapper for a pthread_mutex_t. + */ +class PosixMutex { + friend class PosixCond; + + pthread_mutex_t mutex; + +public: + constexpr PosixMutex():mutex(PTHREAD_MUTEX_INITIALIZER) {} + + PosixMutex(const PosixMutex &other) = delete; + PosixMutex &operator=(const PosixMutex &other) = delete; + + void lock() { + pthread_mutex_lock(&mutex); + } + + bool try_lock() { + return pthread_mutex_trylock(&mutex) == 0; + } + + void unlock() { + pthread_mutex_unlock(&mutex); + } +}; + +#endif diff --git a/src/thread/WindowsCond.hxx b/src/thread/WindowsCond.hxx new file mode 100644 index 00000000..f4e909c7 --- /dev/null +++ b/src/thread/WindowsCond.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009-2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_WINDOWS_COND_HXX +#define MPD_THREAD_WINDOWS_COND_HXX + +#include "CriticalSection.hxx" + +/** + * Wrapper for a CONDITION_VARIABLE, backend for the Cond class. + */ +class WindowsCond { + CONDITION_VARIABLE cond; + +public: + WindowsCond() { + InitializeConditionVariable(&cond); + } + + WindowsCond(const WindowsCond &other) = delete; + WindowsCond &operator=(const WindowsCond &other) = delete; + + void signal() { + WakeConditionVariable(&cond); + } + + void broadcast() { + WakeAllConditionVariable(&cond); + } + + void wait(CriticalSection &mutex) { + SleepConditionVariableCS(&cond, &mutex.critical_section, + INFINITE); + } +}; + +#endif diff --git a/src/timer.h b/src/timer.h index 18488124..1506c917 100644 --- a/src/timer.h +++ b/src/timer.h @@ -30,6 +30,10 @@ struct timer { int rate; }; +#ifdef __cplusplus +extern "C" { +#endif + struct timer *timer_new(const struct audio_format *af); void timer_free(struct timer *timer); @@ -48,4 +52,8 @@ timer_delay(const struct timer *timer); void timer_sync(struct timer *timer); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/tokenizer.c b/src/tokenizer.c index bbb34e10..4a98e882 100644 --- a/src/tokenizer.c +++ b/src/tokenizer.c @@ -21,6 +21,8 @@ #include "tokenizer.h" #include "string_util.h" +#include <glib.h> + #include <stdbool.h> #include <assert.h> #include <string.h> diff --git a/src/tokenizer.h b/src/tokenizer.h index d55eb3ca..2026e5ad 100644 --- a/src/tokenizer.h +++ b/src/tokenizer.h @@ -20,7 +20,7 @@ #ifndef MPD_TOKENIZER_H #define MPD_TOKENIZER_H -#include <glib.h> +#include "gerror.h" /** * Reads the next word from the input string. This function modifies diff --git a/src/update_io.c b/src/update_io.c deleted file mode 100644 index c6a540a0..00000000 --- a/src/update_io.c +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_io.h" -#include "mapper.h" -#include "directory.h" -#include "glib_compat.h" - -#include <glib.h> - -#include <errno.h> -#include <unistd.h> - -int -stat_directory(const struct directory *directory, struct stat *st) -{ - char *path_fs = map_directory_fs(directory); - if (path_fs == NULL) - return -1; - - int ret = stat(path_fs, st); - if (ret < 0) - g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno)); - - g_free(path_fs); - return ret; -} - -int -stat_directory_child(const struct directory *parent, const char *name, - struct stat *st) -{ - char *path_fs = map_directory_child_fs(parent, name); - if (path_fs == NULL) - return -1; - - int ret = stat(path_fs, st); - if (ret < 0) - g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno)); - - g_free(path_fs); - return ret; -} - -bool -directory_exists(const struct directory *directory) -{ - char *path_fs = map_directory_fs(directory); - if (path_fs == NULL) - /* invalid path: cannot exist */ - return false; - - GFileTest test = directory->device == DEVICE_INARCHIVE || - directory->device == DEVICE_CONTAINER - ? G_FILE_TEST_IS_REGULAR - : G_FILE_TEST_IS_DIR; - - bool exists = g_file_test(path_fs, test); - g_free(path_fs); - - return exists; -} - -bool -directory_child_is_regular(const struct directory *directory, - const char *name_utf8) -{ - char *path_fs = map_directory_child_fs(directory, name_utf8); - if (path_fs == NULL) - return false; - - struct stat st; - bool is_regular = stat(path_fs, &st) == 0 && S_ISREG(st.st_mode); - g_free(path_fs); - - return is_regular; -} - -bool -directory_child_access(const struct directory *directory, - const char *name, int mode) -{ -#ifdef WIN32 - /* access() is useless on WIN32 */ - (void)directory; - (void)name; - (void)mode; - return true; -#else - char *path = map_directory_child_fs(directory, name); - if (path == NULL) - /* something went wrong, but that isn't a permission - problem */ - return true; - - bool success = access(path, mode) == 0 || errno != EACCES; - g_free(path); - return success; -#endif -} @@ -20,7 +20,7 @@ #ifndef MPD_URI_H #define MPD_URI_H -#include <glib.h> +#include "gcc.h" #include <stdbool.h> @@ -28,10 +28,10 @@ * Checks whether the specified URI has a scheme in the form * "scheme://". */ -G_GNUC_PURE +gcc_pure bool uri_has_scheme(const char *uri); -G_GNUC_PURE +gcc_pure const char * uri_get_suffix(const char *uri); @@ -43,7 +43,7 @@ uri_get_suffix(const char *uri); * - no double slashes * - no path component begins with a dot */ -G_GNUC_PURE +gcc_pure bool uri_safe_local(const char *uri); @@ -53,7 +53,7 @@ uri_safe_local(const char *uri); * NULL if nothing needs to be removed, or if the URI is not * recognized. */ -G_GNUC_MALLOC +gcc_malloc char * uri_remove_auth(const char *uri); diff --git a/src/util/HugeAllocator.cxx b/src/util/HugeAllocator.cxx new file mode 100644 index 00000000..d1c55c96 --- /dev/null +++ b/src/util/HugeAllocator.cxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "HugeAllocator.hxx" + +#ifdef __linux__ +#include <sys/mman.h> +#include <unistd.h> +#else +#include <stdlib.h> +#endif + +#ifdef __linux__ + +/** + * Round up the parameter, make it page-aligned. + */ +gcc_const +static size_t +AlignToPageSize(size_t size) +{ + static const long page_size = sysconf(_SC_PAGESIZE); + if (page_size > 0) + return size; + + size_t ps(page_size); + return (size + ps - 1) / ps * ps; +} + +void * +HugeAllocate(size_t size) +{ + size = AlignToPageSize(size); + + constexpr int flags = MAP_ANONYMOUS|MAP_PRIVATE|MAP_NORESERVE; + void *p = mmap(nullptr, size, + PROT_READ|PROT_WRITE, flags, + -1, 0); + if (p == (void *)-1) + return nullptr; + +#ifdef MADV_HUGEPAGE + /* allow the Linux kernel to use "Huge Pages", which reduces page + table overhead for this big chunk of data */ + madvise(p, size, MADV_HUGEPAGE); +#endif + +#ifdef MADV_DONTFORK + /* just in case MPD needs to fork, don't copy this allocation + to the child process, to reduce overhead */ + madvise(p, size, MADV_DONTFORK); +#endif + + return p; +} + +void +HugeFree(void *p, size_t size) +{ + munmap(p, AlignToPageSize(size)); +} + +void +HugeDiscard(void *p, size_t size) +{ +#ifdef MADV_DONTNEED + madvise(p, AlignToPageSize(size), MADV_DONTNEED); +#endif +} + +#endif diff --git a/src/util/HugeAllocator.hxx b/src/util/HugeAllocator.hxx new file mode 100644 index 00000000..01c92cd4 --- /dev/null +++ b/src/util/HugeAllocator.hxx @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_HUGE_ALLOCATOR_HXX +#define MPD_HUGE_ALLOCATOR_HXX + +#include "gcc.h" + +#include <stddef.h> + +#ifdef __linux__ + +/** + * Allocate a huge amount of memory. This will be done in a way that + * allows giving the memory back to the kernel as soon as we don't + * need it anymore. On the downside, this call is expensive. + */ +gcc_malloc +void * +HugeAllocate(size_t size); + +/** + * @param p an allocation returned by HugeAllocate() + * @param size the allocation's size as passed to HugeAllocate() + */ +void +HugeFree(void *p, size_t size); + +/** + * Discard any data stored in the allocation and give the memory back + * to the kernel. After returning, the allocation still exists and + * can be reused at any time, but its contents are undefined. + * + * @param p an allocation returned by HugeAllocate() + * @param size the allocation's size as passed to HugeAllocate() + */ +void +HugeDiscard(void *p, size_t size); + +#else + +/* not Linux: fall back to standard C calls */ + +#include <stdlib.h> + +gcc_malloc +static inline void * +HugeAllocate(size_t size) +{ + return malloc(size); +} + +static inline void +HugeFree(void *p, size_t) +{ + free(p); +} + +static inline void +HugeDiscard(void *, size_t) +{ +} + +#endif + +#endif diff --git a/src/util/LazyRandomEngine.cxx b/src/util/LazyRandomEngine.cxx new file mode 100644 index 00000000..0f90ebb2 --- /dev/null +++ b/src/util/LazyRandomEngine.cxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "LazyRandomEngine.hxx" + +void +LazyRandomEngine::AutoCreate() +{ + if (engine != nullptr) + return; + + std::random_device rd; + engine = new std::mt19937(rd()); +} diff --git a/src/util/LazyRandomEngine.hxx b/src/util/LazyRandomEngine.hxx new file mode 100644 index 00000000..8afe1d1c --- /dev/null +++ b/src/util/LazyRandomEngine.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_LAZY_RANDOM_ENGINE_HXX +#define MPD_LAZY_RANDOM_ENGINE_HXX + +#include "check.h" + +#include <random> + +#include <assert.h> + +/** + * A random engine that will be created and seeded on demand. + */ +class LazyRandomEngine { + std::mt19937 *engine; + +public: + typedef std::mt19937::result_type result_type; + + LazyRandomEngine():engine(nullptr) {} + ~LazyRandomEngine() { + delete engine; + } + + LazyRandomEngine(const LazyRandomEngine &other) = delete; + LazyRandomEngine &operator=(const LazyRandomEngine &other) = delete; + + /** + * Create and seed the real engine. Call this before any + * other method. + */ + void AutoCreate(); + + result_type min() const { + return engine->min(); + } + + result_type max() const { + return engine->max(); + } + + result_type operator()() { + assert(engine != nullptr); + + return engine->operator()(); + } +}; + +#endif diff --git a/src/util/Manual.hxx b/src/util/Manual.hxx new file mode 100644 index 00000000..ecd2c52b --- /dev/null +++ b/src/util/Manual.hxx @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_MANUAL_HXX +#define MPD_MANUAL_HXX + +#include "gcc.h" + +#include <new> + +#if !defined(__clang__) && __GNUC__ && !GCC_CHECK_VERSION(4,8) +#include <type_traits> +#endif + +#include <assert.h> + +/** + * Container for an object that gets constructed and destructed + * manually. The object is constructed in-place, and therefore + * without allocation overhead. It can be constructed and destructed + * repeatedly. + */ +template<class T> +class Manual { +#if !defined(__clang__) && __GNUC__ && !GCC_CHECK_VERSION(4,8) + /* no alignas() on gcc < 4.8: apply worst-case fallback */ + __attribute__((aligned(8))) +#else + alignas(T) +#endif + char data[sizeof(T)]; + +#ifndef NDEBUG + bool initialized; +#endif + +public: +#ifndef NDEBUG + Manual():initialized(false) {} + ~Manual() { + assert(!initialized); + } +#endif + + template<typename... Args> + void Construct(Args&&... args) { + assert(!initialized); + + void *p = data; + new(p) T(std::forward<Args>(args)...); + +#ifndef NDEBUG + initialized = true; +#endif + } + + void Destruct() { + assert(initialized); + + T *t = (T *)data; + t->T::~T(); + +#ifndef NDEBUG + initialized = false; +#endif + } + + operator T &() { + return *(T *)data; + } + + operator const T &() const { + return *(const T *)data; + } + + T *operator->() { + return (T *)data; + } + + const T *operator->() const { + return (T *)data; + } +}; + +#endif diff --git a/src/util/PeakBuffer.cxx b/src/util/PeakBuffer.cxx new file mode 100644 index 00000000..a3659b8f --- /dev/null +++ b/src/util/PeakBuffer.cxx @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "PeakBuffer.hxx" +#include "HugeAllocator.hxx" +#include "fifo_buffer.h" + +#include <algorithm> + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +PeakBuffer::~PeakBuffer() +{ + if (normal_buffer != nullptr) + fifo_buffer_free(normal_buffer); + + if (peak_buffer != nullptr) + HugeFree(peak_buffer, peak_size); +} + +bool +PeakBuffer::IsEmpty() const +{ + return (normal_buffer == nullptr || + fifo_buffer_is_empty(normal_buffer)) && + (peak_buffer == nullptr || + fifo_buffer_is_empty(peak_buffer)); +} + +const void * +PeakBuffer::Read(size_t *length_r) const +{ + if (normal_buffer != nullptr) { + const void *p = fifo_buffer_read(normal_buffer, length_r); + if (p != nullptr) + return p; + } + + if (peak_buffer != nullptr) { + const void *p = fifo_buffer_read(peak_buffer, length_r); + if (p != nullptr) + return p; + } + + return nullptr; +} + +void +PeakBuffer::Consume(size_t length) +{ + if (normal_buffer != nullptr && !fifo_buffer_is_empty(normal_buffer)) { + fifo_buffer_consume(normal_buffer, length); + return; + } + + if (peak_buffer != nullptr && !fifo_buffer_is_empty(peak_buffer)) { + fifo_buffer_consume(peak_buffer, length); + if (fifo_buffer_is_empty(peak_buffer)) { + HugeFree(peak_buffer, peak_size); + peak_buffer = nullptr; + } + + return; + } +} + +static size_t +AppendTo(fifo_buffer *buffer, const void *data, size_t length) +{ + assert(data != nullptr); + assert(length > 0); + + size_t total = 0; + + do { + size_t max_length; + void *p = fifo_buffer_write(buffer, &max_length); + if (p == nullptr) + break; + + const size_t nbytes = std::min(length, max_length); + memcpy(p, data, nbytes); + fifo_buffer_append(buffer, nbytes); + + data = (const uint8_t *)data + nbytes; + length -= nbytes; + total += nbytes; + } while (length > 0); + + return total; +} + +bool +PeakBuffer::Append(const void *data, size_t length) +{ + if (length == 0) + return true; + + if (peak_buffer != nullptr && !fifo_buffer_is_empty(peak_buffer)) { + size_t nbytes = AppendTo(peak_buffer, data, length); + return nbytes == length; + } + + if (normal_buffer == nullptr) + normal_buffer = fifo_buffer_new(normal_size); + + size_t nbytes = AppendTo(normal_buffer, data, length); + if (nbytes > 0) { + data = (const uint8_t *)data + nbytes; + length -= nbytes; + if (length == 0) + return true; + } + + if (peak_buffer == nullptr && peak_size > 0) { + peak_buffer = (fifo_buffer *)HugeAllocate(peak_size); + if (peak_buffer == nullptr) + return false; + + fifo_buffer_init(peak_buffer, peak_size); + } + + nbytes = AppendTo(peak_buffer, data, length); + return nbytes == length; +} diff --git a/src/util/PeakBuffer.hxx b/src/util/PeakBuffer.hxx new file mode 100644 index 00000000..0fbba8d7 --- /dev/null +++ b/src/util/PeakBuffer.hxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PEAK_BUFFER_HXX +#define MPD_PEAK_BUFFER_HXX + +#include "gcc.h" + +#include <stddef.h> + +struct fifo_buffer; + +/** + * A FIFO-like buffer that will allocate more memory on demand to + * allow large peaks. This second buffer will be given back to the + * kernel when it has been consumed. + */ +class PeakBuffer { + size_t normal_size, peak_size; + + fifo_buffer *normal_buffer, *peak_buffer; + +public: + PeakBuffer(size_t _normal_size, size_t _peak_size) + :normal_size(_normal_size), peak_size(_peak_size), + normal_buffer(nullptr), peak_buffer(nullptr) {} + + PeakBuffer(PeakBuffer &&other) + :normal_size(other.normal_size), peak_size(other.peak_size), + normal_buffer(other.normal_buffer), + peak_buffer(other.peak_buffer) { + other.normal_buffer = nullptr; + other.peak_buffer = nullptr; + } + + ~PeakBuffer(); + + PeakBuffer(const PeakBuffer &) = delete; + PeakBuffer &operator=(const PeakBuffer &) = delete; + + gcc_pure + bool IsEmpty() const; + + const void *Read(size_t *length_r) const; + void Consume(size_t length); + + bool Append(const void *data, size_t length); +}; + +#endif diff --git a/src/refcount.h b/src/util/RefCount.hxx index a882d76b..9a45a585 100644 --- a/src/refcount.h +++ b/src/util/RefCount.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without @@ -33,35 +33,27 @@ * A very simple reference counting library. */ -#ifndef MPD_REFCOUNT_H -#define MPD_REFCOUNT_H +#ifndef MPD_REFCOUNT_HXX +#define MPD_REFCOUNT_HXX -#include <glib.h> -#include <stdbool.h> +#include <atomic> -struct refcount { - gint n; -}; +class RefCount { + std::atomic_uint n; -static inline void -refcount_init(struct refcount *r) -{ - r->n = 1; -} +public: + constexpr RefCount():n(1) {} -static inline void -refcount_inc(struct refcount *r) -{ - g_atomic_int_inc(&r->n); -} + void Increment() { + ++n; + } -/** - * @return true if the number of references has been dropped to 0 - */ -static inline bool -refcount_dec(struct refcount *r) -{ - return g_atomic_int_dec_and_test(&r->n); -} + /** + * @return true if the number of references has been dropped to 0 + */ + bool Decrement() { + return --n == 0; + } +}; #endif diff --git a/src/util/SliceBuffer.hxx b/src/util/SliceBuffer.hxx new file mode 100644 index 00000000..c61f164f --- /dev/null +++ b/src/util/SliceBuffer.hxx @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SLICE_BUFFER_HXX +#define MPD_SLICE_BUFFER_HXX + +#include "HugeAllocator.hxx" +#include "gcc.h" + +#include <utility> +#include <new> + +#include <assert.h> +#include <stddef.h> + +/** + * This class pre-allocates a certain number of objects, and allows + * callers to allocate and free these objects ("slices"). + */ +template<typename T> +class SliceBuffer { + union Slice { + Slice *next; + + T value; + }; + + /** + * The maximum number of slices in this container. + */ + const unsigned n_max; + + /** + * The number of slices that are initialized. This is used to + * avoid page faulting on the new allocation, so the kernel + * does not need to reserve physical memory pages. + */ + unsigned n_initialized; + + /** + * The number of slices currently allocated. + */ + unsigned n_allocated; + + Slice *const data; + + /** + * Pointer to the first free element in the chain. + */ + Slice *available; + + size_t CalcAllocationSize() const { + return n_max * sizeof(Slice); + } + +public: + SliceBuffer(unsigned _count) + :n_max(_count), n_initialized(0), n_allocated(0), + data((Slice *)HugeAllocate(CalcAllocationSize())), + available(nullptr) { + assert(n_max > 0); + } + + ~SliceBuffer() { + /* all slices must be freed explicitly, and this + assertion checks for leaks */ + assert(n_allocated == 0); + + HugeFree(data, CalcAllocationSize()); + } + + SliceBuffer(const SliceBuffer &other) = delete; + SliceBuffer &operator=(const SliceBuffer &other) = delete; + + /** + * @return true if buffer allocation (by the constructor) has failed + */ + bool IsOOM() { + return data == nullptr; + } + + unsigned GetCapacity() const { + return n_max; + } + + bool IsEmpty() const { + return n_allocated == 0; + } + + bool IsFull() const { + return n_allocated == n_max; + } + + template<typename... Args> + T *Allocate(Args&&... args) { + assert(n_initialized <= n_max); + assert(n_allocated <= n_initialized); + + if (available == nullptr) { + if (n_initialized == n_max) { + /* out of (internal) memory, buffer is full */ + assert(n_allocated == n_max); + return nullptr; + } + + available = &data[n_initialized++]; + available->next = nullptr; + } + + /* allocate a slice */ + T *value = &available->value; + available = available->next; + ++n_allocated; + + /* construct the object */ + return ::new((void *)value) T(std::forward<Args>(args)...); + } + + void Free(T *value) { + assert(n_initialized <= n_max); + assert(n_allocated > 0); + assert(n_allocated <= n_initialized); + + Slice *slice = reinterpret_cast<Slice *>(value); + assert(slice >= data && slice < data + n_max); + + /* destruct the object */ + value->~T(); + + /* insert the slice in the "available" linked list */ + slice->next = available; + available = slice; + --n_allocated; + + /* give memory back to the kernel when the last slice + was freed */ + if (n_allocated == 0) { + HugeDiscard(data, CalcAllocationSize()); + n_initialized = 0; + available = nullptr; + } + } +}; + +#endif diff --git a/src/util/bit_reverse.h b/src/util/bit_reverse.h index e44693b1..54cb789b 100644 --- a/src/util/bit_reverse.h +++ b/src/util/bit_reverse.h @@ -20,12 +20,13 @@ #ifndef MPD_BIT_REVERSE_H #define MPD_BIT_REVERSE_H -#include <glib.h> +#include "gcc.h" + #include <stdint.h> extern const uint8_t bit_reverse_table[256]; -G_GNUC_CONST +gcc_const static inline uint8_t bit_reverse(uint8_t x) { diff --git a/src/fifo_buffer.c b/src/util/fifo_buffer.c index 915fb057..162ddf94 100644 --- a/src/fifo_buffer.c +++ b/src/util/fifo_buffer.c @@ -58,6 +58,14 @@ fifo_buffer_new(size_t size) 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); diff --git a/src/fifo_buffer.h b/src/util/fifo_buffer.h index 3bdb2393..ccea97d8 100644 --- a/src/fifo_buffer.h +++ b/src/util/fifo_buffer.h @@ -46,6 +46,10 @@ struct fifo_buffer; +#ifdef __cplusplus +extern "C" { +#endif + /** * Creates a new #fifo_buffer object. Free this object with * fifo_buffer_free(). @@ -56,6 +60,9 @@ struct fifo_buffer; 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. @@ -150,4 +157,8 @@ fifo_buffer_is_empty(struct fifo_buffer *buffer); bool fifo_buffer_is_full(struct fifo_buffer *buffer); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/growing_fifo.c b/src/util/growing_fifo.c index 88431f60..88431f60 100644 --- a/src/growing_fifo.c +++ b/src/util/growing_fifo.c diff --git a/src/growing_fifo.h b/src/util/growing_fifo.h index 723c3b3f..723c3b3f 100644 --- a/src/growing_fifo.h +++ b/src/util/growing_fifo.h diff --git a/src/util/list.h b/src/util/list.h index fdab4767..73d99bef 100644 --- a/src/util/list.h +++ b/src/util/list.h @@ -25,8 +25,6 @@ #ifndef _LINUX_LIST_H #define _LINUX_LIST_H -#include <glib.h> - #ifdef __clang__ /* allow typeof() */ #pragma GCC diagnostic ignored "-Wlanguage-extension-token" @@ -40,15 +38,15 @@ * */ #define container_of(ptr, type, member) \ - (&G_STRUCT_MEMBER(type, ptr, -G_STRUCT_OFFSET(type, member))) + ((type *)((uint8_t *)ptr - offsetof(type, member))) /* * These are non-NULL pointers that will result in page faults * under normal circumstances, used to verify that nobody uses * non-initialized list entries. */ -#define LIST_POISON1 ((void *) 0x00100100) -#define LIST_POISON2 ((void *) 0x00200200) +#define LIST_POISON1 ((struct list_head *)(void *) 0x00100100) +#define LIST_POISON2 ((struct list_head *)(void *) 0x00200200) /* * Simple doubly linked list implementation. @@ -82,46 +80,47 @@ static inline void INIT_LIST_HEAD(struct list_head *list) * the prev/next entries already! */ #ifndef CONFIG_DEBUG_LIST -static inline void __list_add(struct list_head *new, +static inline void __list_add(struct list_head *new_item, struct list_head *prev, struct list_head *next) { - next->prev = new; - new->next = next; - new->prev = prev; - prev->next = new; + next->prev = new_item; + new_item->next = next; + new_item->prev = prev; + prev->next = new_item; } #else -extern void __list_add(struct list_head *new, - struct list_head *prev, - struct list_head *next); +extern void __list_add(struct list_head *new_item, + struct list_head *prev, + struct list_head *next); #endif /** * list_add - add a new entry - * @new: new entry to be added + * @new_item: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ -static inline void list_add(struct list_head *new, struct list_head *head) +static inline void list_add(struct list_head *new_item, struct list_head *head) { - __list_add(new, head, head->next); + __list_add(new_item, head, head->next); } /** * list_add_tail - add a new entry - * @new: new entry to be added + * @new_item: new entry to be added * @head: list head to add it before * * Insert a new entry before the specified head. * This is useful for implementing queues. */ -static inline void list_add_tail(struct list_head *new, struct list_head *head) +static inline void +list_add_tail(struct list_head *new_item, struct list_head *head) { - __list_add(new, head->prev, head); + __list_add(new_item, head->prev, head); } /* @@ -163,23 +162,23 @@ extern void list_del(struct list_head *entry); /** * list_replace - replace old entry by new one * @old : the element to be replaced - * @new : the new element to insert + * @new_item : the new element to insert * * If @old was empty, it will be overwritten. */ static inline void list_replace(struct list_head *old, - struct list_head *new) + struct list_head *new_item) { - new->next = old->next; - new->next->prev = new; - new->prev = old->prev; - new->prev->next = new; + new_item->next = old->next; + new_item->next->prev = new_item; + new_item->prev = old->prev; + new_item->prev->next = new_item; } static inline void list_replace_init(struct list_head *old, - struct list_head *new) + struct list_head *new_item) { - list_replace(old, new); + list_replace(old, new_item); INIT_LIST_HEAD(old); } diff --git a/src/utils.c b/src/utils.c index a2de3212..776813c4 100644 --- a/src/utils.c +++ b/src/utils.c @@ -19,7 +19,6 @@ #include "config.h" #include "utils.h" -#include "glib_compat.h" #include "conf.h" #include <glib.h> diff --git a/src/utils.h b/src/utils.h index f8d6657f..059d44fa 100644 --- a/src/utils.h +++ b/src/utils.h @@ -20,17 +20,7 @@ #ifndef MPD_UTILS_H #define MPD_UTILS_H -#include <glib.h> -#include <stdbool.h> - -#ifndef assert_static -/* Compile time assertion developed by Ralf Holly */ -/* http://pera-software.com/articles/compile-time-assertions.pdf */ -#define assert_static(e) \ - do { \ - enum { assert_static__ = 1/(e) }; \ - } while (0) -#endif /* !assert_static */ +#include "gerror.h" char * parsePath(const char *path, GError **error_r); diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx new file mode 100644 index 00000000..ba0e74e4 --- /dev/null +++ b/test/DumpDatabase.cxx @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseRegistry.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseSelection.hxx" +#include "Directory.hxx" +#include "song.h" +#include "PlaylistVector.hxx" +#include "conf.h" +#include "tag.h" +#include "fs/Path.hxx" + +#include <iostream> +using std::cout; +using std::cerr; +using std::endl; + +#include <stdlib.h> + +static void +my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, + const gchar *message, G_GNUC_UNUSED gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +static bool +DumpDirectory(const Directory &directory, GError **) +{ + cout << "D " << directory.path << endl; + return true; +} + +static bool +DumpSong(song &song, GError **) +{ + cout << "S " << song.parent->path << "/" << song.uri << endl; + return true; +} + +static bool +DumpPlaylist(const PlaylistInfo &playlist, + const Directory &directory, GError **) +{ + cout << "P " << directory.path << "/" << playlist.name.c_str() << endl; + return true; +} + +int +main(int argc, char **argv) +{ + GError *error = nullptr; + + if (argc != 3) { + cerr << "Usage: DumpDatabase CONFIG PLUGIN" << endl; + return 1; + } + + const Path config_path = Path::FromFS(argv[1]); + const char *const plugin_name = argv[2]; + + const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name); + if (plugin == NULL) { + cerr << "No such database plugin: " << plugin_name << endl; + return EXIT_FAILURE; + } + + /* initialize GLib */ + + g_thread_init(nullptr); + g_log_set_default_handler(my_log_func, nullptr); + + /* initialize MPD */ + + config_global_init(); + + if (!ReadConfigFile(config_path, &error)) { + cerr << error->message << endl; + g_error_free(error); + return EXIT_FAILURE; + } + + tag_lib_init(); + + /* do it */ + + const struct config_param *path = config_get_param(CONF_DB_FILE); + config_param param("database", path->line); + if (path != nullptr) + param.AddBlockParam("path", path->value, path->line); + + Database *db = plugin->create(¶m, &error); + + if (db == nullptr) { + cerr << error->message << endl; + g_error_free(error); + return EXIT_FAILURE; + } + + if (!db->Open(&error)) { + delete db; + cerr << error->message << endl; + g_error_free(error); + return EXIT_FAILURE; + } + + const DatabaseSelection selection("", true); + + if (!db->Visit(selection, DumpDirectory, DumpSong, DumpPlaylist, + &error)) { + db->Close(); + delete db; + cerr << error->message << endl; + g_error_free(error); + return EXIT_FAILURE; + } + + db->Close(); + delete db; + + /* deinitialize everything */ + + config_global_finish(); + + return EXIT_SUCCESS; +} diff --git a/test/FakeReplayGainConfig.cxx b/test/FakeReplayGainConfig.cxx new file mode 100644 index 00000000..9c2431bf --- /dev/null +++ b/test/FakeReplayGainConfig.cxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "replay_gain_config.h" + +float replay_gain_preamp = 1.0; +float replay_gain_missing_preamp = 1.0; +bool replay_gain_limit = true; diff --git a/test/FakeSong.cxx b/test/FakeSong.cxx new file mode 100644 index 00000000..927a0765 --- /dev/null +++ b/test/FakeSong.cxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "song.h" +#include "directory.h" +#include "gcc.h" + +#include <stdlib.h> + +struct directory detached_root; + +struct song * +song_dup_detached(gcc_unused const struct song *src) +{ + abort(); +} diff --git a/test/dump_playlist.c b/test/dump_playlist.cxx index 84ac6904..6e1f4858 100644 --- a/test/dump_playlist.c +++ b/test/dump_playlist.cxx @@ -18,23 +18,27 @@ */ #include "config.h" -#include "io_thread.h" -#include "input_init.h" +#include "TagSave.hxx" +#include "song.h" +#include "Directory.hxx" #include "input_stream.h" -#include "tag_pool.h" -#include "tag_save.h" #include "conf.h" -#include "song.h" #include "decoder_api.h" -#include "decoder_list.h" -#include "playlist_list.h" -#include "playlist_plugin.h" +#include "DecoderList.hxx" +#include "InputInit.hxx" +#include "IOThread.hxx" +#include "PlaylistRegistry.hxx" +#include "PlaylistPlugin.hxx" +#include "fs/Path.hxx" #include <glib.h> #include <unistd.h> #include <stdlib.h> +Directory::Directory() {} +Directory::~Directory() {} + static void my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, const gchar *message, G_GNUC_UNUSED gpointer user_data) @@ -107,7 +111,7 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, return DECODE_COMMAND_NONE; } -float +void decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, const struct replay_gain_info *replay_gain_info) { @@ -121,13 +125,10 @@ decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, if (replay_gain_tuple_defined(tuple)) g_printerr("replay_gain[track]: gain=%f peak=%f\n", tuple->gain, tuple->peak); - - return 0.0; } void decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED float replay_gain_db, char *mixramp_start, char *mixramp_end) { g_free(mixramp_start); @@ -138,7 +139,6 @@ int main(int argc, char **argv) { const char *uri; struct input_stream *is = NULL; - bool success; GError *error = NULL; struct playlist_provider *playlist; struct song *song; @@ -148,6 +148,7 @@ int main(int argc, char **argv) return 1; } + const Path config_path = Path::FromFS(argv[1]); uri = argv[2]; /* initialize GLib */ @@ -157,10 +158,8 @@ int main(int argc, char **argv) /* initialize MPD */ - tag_pool_init(); config_global_init(); - success = config_read_file(argv[1], &error); - if (!success) { + if (!ReadConfigFile(config_path, &error)) { g_printerr("%s\n", error->message); g_error_free(error); return 1; @@ -184,8 +183,8 @@ int main(int argc, char **argv) /* open the playlist */ - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); + Mutex mutex; + Cond cond; playlist = playlist_list_open_uri(uri, mutex, cond); if (playlist == NULL) { @@ -241,15 +240,11 @@ int main(int argc, char **argv) if (is != NULL) input_stream_close(is); - g_cond_free(cond); - g_mutex_free(mutex); - decoder_plugin_deinit_all(); playlist_list_global_finish(); input_stream_global_finish(); io_thread_deinit(); config_global_finish(); - tag_pool_deinit(); return 0; } diff --git a/test/dump_rva2.c b/test/dump_rva2.c index 4a726518..6e978c42 100644 --- a/test/dump_rva2.c +++ b/test/dump_rva2.c @@ -26,6 +26,8 @@ #include <id3tag.h> +#include <glib.h> + #ifdef HAVE_LOCALE_H #include <locale.h> #endif @@ -33,7 +35,8 @@ #include <stdlib.h> const char * -config_get_string(G_GNUC_UNUSED const char *name, const char *default_value) +config_get_string(gcc_unused enum ConfigOption option, + const char *default_value) { return default_value; } @@ -45,8 +48,8 @@ tag_new(void) } void -tag_add_item_n(G_GNUC_UNUSED struct tag *tag, G_GNUC_UNUSED enum tag_type type, - G_GNUC_UNUSED const char *value, G_GNUC_UNUSED size_t len) +tag_add_item_n(gcc_unused struct tag *tag, gcc_unused enum tag_type type, + gcc_unused const char *value, gcc_unused size_t len) { } diff --git a/test/dump_text_file.c b/test/dump_text_file.cxx index f1437144..93b0d018 100644 --- a/test/dump_text_file.c +++ b/test/dump_text_file.cxx @@ -18,16 +18,18 @@ */ #include "config.h" -#include "io_thread.h" -#include "input_init.h" -#include "input_stream.h" -#include "text_input_stream.h" -#include "tag_pool.h" +#include "IOThread.hxx" +#include "InputInit.hxx" +#include "InputStream.hxx" #include "conf.h" #include "stdbin.h" +extern "C" { +#include "text_input_stream.h" +} + #ifdef ENABLE_ARCHIVE -#include "archive_list.h" +#include "ArchiveList.hxx" #endif #include <glib.h> @@ -112,7 +114,6 @@ int main(int argc, char **argv) /* initialize MPD */ - tag_pool_init(); config_global_init(); io_thread_init(); @@ -134,8 +135,8 @@ int main(int argc, char **argv) /* open the stream and dump it */ - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); + Mutex mutex; + Cond cond; is = input_stream_open(argv[1], mutex, cond, &error); if (is != NULL) { @@ -150,9 +151,6 @@ int main(int argc, char **argv) ret = 2; } - g_cond_free(cond); - g_mutex_free(mutex); - /* deinitialize everything */ input_stream_global_finish(); @@ -164,7 +162,6 @@ int main(int argc, char **argv) io_thread_deinit(); config_global_finish(); - tag_pool_deinit(); return ret; } diff --git a/test/read_conf.c b/test/read_conf.cxx index 4f6005c6..8759398d 100644 --- a/test/read_conf.c +++ b/test/read_conf.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "config.h" #include "conf.h" +#include "fs/Path.hxx" #include <glib.h> @@ -42,7 +43,7 @@ int main(int argc, char **argv) return 1; } - const char *path = argv[1]; + const Path config_path = Path::FromFS(argv[1]); const char *name = argv[2]; g_log_set_default_handler(my_log_func, NULL); @@ -50,14 +51,16 @@ int main(int argc, char **argv) config_global_init(); GError *error = NULL; - bool success = config_read_file(path, &error); - if (!success) { + if (!ReadConfigFile(config_path, &error)) { g_printerr("%s:", error->message); g_error_free(error); return 1; } - const char *value = config_get_string(name, NULL); + ConfigOption option = ParseConfigOptionName(name); + const char *value = option != CONF_MAX + ? config_get_string(option, nullptr) + : nullptr; int ret; if (value != NULL) { g_print("%s\n", value); diff --git a/test/read_mixer.c b/test/read_mixer.cxx index f6de8177..45344a2f 100644 --- a/test/read_mixer.c +++ b/test/read_mixer.cxx @@ -18,11 +18,13 @@ */ #include "config.h" -#include "mixer_control.h" -#include "mixer_list.h" -#include "filter_registry.h" -#include "pcm_volume.h" -#include "event_pipe.h" +#include "MixerControl.hxx" +#include "MixerList.hxx" +#include "FilterRegistry.hxx" +#include "PcmVolume.hxx" +#include "GlobalEvents.hxx" +#include "Main.hxx" +#include "event/Loop.hxx" #include <glib.h> @@ -30,6 +32,8 @@ #include <string.h> #include <unistd.h> +EventLoop *main_loop; + #ifdef HAVE_PULSE #include "output/pulse_output_plugin.h" @@ -66,16 +70,16 @@ pulse_output_set_volume(G_GNUC_UNUSED struct pulse_output *po, #endif #ifdef HAVE_ROAR -#include "output/roar_output_plugin.h" +#include "output/RoarOutputPlugin.hxx" int -roar_output_get_volume(G_GNUC_UNUSED struct roar *roar) +roar_output_get_volume(gcc_unused RoarOutput *roar) { return -1; } bool -roar_output_set_volume(G_GNUC_UNUSED struct roar *roar, +roar_output_set_volume(gcc_unused RoarOutput *roar, G_GNUC_UNUSED unsigned volume) { return true; @@ -84,7 +88,7 @@ roar_output_set_volume(G_GNUC_UNUSED struct roar *roar, #endif void -event_pipe_emit(G_GNUC_UNUSED enum pipe_event event) +GlobalEvents::Emit(gcc_unused Event event) { } @@ -118,6 +122,8 @@ int main(int argc, G_GNUC_UNUSED char **argv) g_thread_init(NULL); + main_loop = new EventLoop(EventLoop::Default()); + mixer = mixer_new(&alsa_mixer_plugin, NULL, NULL, &error); if (mixer == NULL) { g_printerr("mixer_new() failed: %s\n", error->message); @@ -137,6 +143,8 @@ int main(int argc, G_GNUC_UNUSED char **argv) mixer_close(mixer); mixer_free(mixer); + delete main_loop; + assert(volume >= -1 && volume <= 100); if (volume < 0) { diff --git a/test/read_tags.c b/test/read_tags.cxx index faf9a45c..1ceddf1d 100644 --- a/test/read_tags.c +++ b/test/read_tags.cxx @@ -18,17 +18,17 @@ */ #include "config.h" -#include "io_thread.h" -#include "decoder_list.h" +#include "IOThread.hxx" +#include "DecoderList.hxx" #include "decoder_api.h" -#include "input_init.h" -#include "input_stream.h" +#include "InputInit.hxx" +#include "InputStream.hxx" #include "audio_format.h" -#include "pcm_volume.h" +extern "C" { #include "tag_ape.h" #include "tag_id3.h" +} #include "tag_handler.h" -#include "idle.h" #include <glib.h> @@ -40,25 +40,6 @@ #include <locale.h> #endif -/** - * No-op dummy. - */ -void -idle_add(G_GNUC_UNUSED unsigned flags) -{ -} - -/** - * No-op dummy. - */ -bool -pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length, - G_GNUC_UNUSED enum sample_format format, - G_GNUC_UNUSED int volume) -{ - return true; -} - void decoder_initialized(G_GNUC_UNUSED struct decoder *decoder, G_GNUC_UNUSED const struct audio_format *audio_format, @@ -118,16 +99,14 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, return DECODE_COMMAND_NONE; } -float +void decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, G_GNUC_UNUSED const struct replay_gain_info *replay_gain_info) { - return 0.0; } void decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED float replay_gain_db, char *mixramp_start, char *mixramp_end) { g_free(mixramp_start); @@ -156,9 +135,9 @@ print_pair(const char *name, const char *value, G_GNUC_UNUSED void *ctx) } static const struct tag_handler print_handler = { - .duration = print_duration, - .tag = print_tag, - .pair = print_pair, + print_duration, + print_tag, + print_pair, }; int main(int argc, char **argv) @@ -205,8 +184,8 @@ int main(int argc, char **argv) bool success = decoder_plugin_scan_file(plugin, path, &print_handler, NULL); if (!success && plugin->scan_stream != NULL) { - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); + Mutex mutex; + Cond cond; struct input_stream *is = input_stream_open(path, mutex, cond, &error); @@ -218,12 +197,28 @@ int main(int argc, char **argv) return 1; } + mutex.lock(); + + while (!is->ready) { + cond.wait(mutex); + input_stream_update(is); + } + + if (!input_stream_check(is, &error)) { + mutex.unlock(); + + g_printerr("Failed to read %s: %s\n", + path, error->message); + g_error_free(error); + + return EXIT_FAILURE; + } + + mutex.unlock(); + success = decoder_plugin_scan_stream(plugin, is, &print_handler, NULL); input_stream_close(is); - - g_cond_free(cond); - g_mutex_free(mutex); } decoder_plugin_deinit_all(); diff --git a/test/run_convert.c b/test/run_convert.cxx index 4f57400f..e66d0be2 100644 --- a/test/run_convert.c +++ b/test/run_convert.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,11 +24,11 @@ */ #include "config.h" -#include "audio_parser.h" +#include "AudioParser.hxx" #include "audio_format.h" -#include "pcm_convert.h" +#include "PcmConvert.hxx" #include "conf.h" -#include "fifo_buffer.h" +#include "util/fifo_buffer.h" #include "stdbin.h" #include <glib.h> @@ -48,7 +48,8 @@ my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, } const char * -config_get_string(G_GNUC_UNUSED const char *name, const char *default_value) +config_get_string(gcc_unused enum ConfigOption option, + const char *default_value) { return default_value; } @@ -57,7 +58,6 @@ int main(int argc, char **argv) { GError *error = NULL; struct audio_format in_audio_format, out_audio_format; - struct pcm_convert_state state; const void *output; ssize_t nbytes; size_t length; @@ -89,7 +89,7 @@ int main(int argc, char **argv) const size_t in_frame_size = audio_format_frame_size(&in_audio_format); - pcm_convert_init(&state); + PcmConvert state; struct fifo_buffer *buffer = fifo_buffer_new(4096); @@ -112,8 +112,8 @@ int main(int argc, char **argv) fifo_buffer_consume(buffer, length); - output = pcm_convert(&state, &in_audio_format, src, length, - &out_audio_format, &length, &error); + output = state.Convert(&in_audio_format, src, length, + &out_audio_format, &length, &error); if (output == NULL) { g_printerr("Failed to convert: %s\n", error->message); return 2; @@ -122,5 +122,5 @@ int main(int argc, char **argv) G_GNUC_UNUSED ssize_t ignored = write(1, output, length); } - pcm_convert_deinit(&state); + return EXIT_SUCCESS; } diff --git a/test/run_decoder.c b/test/run_decoder.cxx index e6712c75..d5c7c313 100644 --- a/test/run_decoder.c +++ b/test/run_decoder.cxx @@ -18,15 +18,12 @@ */ #include "config.h" -#include "io_thread.h" -#include "decoder_list.h" +#include "IOThread.hxx" +#include "DecoderList.hxx" #include "decoder_api.h" -#include "tag_pool.h" -#include "input_init.h" +#include "InputInit.hxx" #include "input_stream.h" #include "audio_format.h" -#include "pcm_volume.h" -#include "idle.h" #include "stdbin.h" #include <glib.h> @@ -45,25 +42,6 @@ my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, g_printerr("%s\n", message); } -/** - * No-op dummy. - */ -void -idle_add(G_GNUC_UNUSED unsigned flags) -{ -} - -/** - * No-op dummy. - */ -bool -pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length, - G_GNUC_UNUSED enum sample_format format, - G_GNUC_UNUSED int volume) -{ - return true; -} - struct decoder { const char *uri; @@ -140,7 +118,7 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, return DECODE_COMMAND_NONE; } -float +void decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, const struct replay_gain_info *replay_gain_info) { @@ -154,13 +132,10 @@ decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, if (replay_gain_tuple_defined(tuple)) g_printerr("replay_gain[track]: gain=%f peak=%f\n", tuple->gain, tuple->peak); - - return 0.0; } void decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED float replay_gain_db, char *mixramp_start, char *mixramp_end) { g_free(mixramp_start); @@ -191,8 +166,6 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - tag_pool_init(); - if (!input_stream_global_init(&error)) { g_warning("%s", error->message); g_error_free(error); @@ -213,8 +186,8 @@ int main(int argc, char **argv) decoder_plugin_file_decode(decoder.plugin, &decoder, decoder.uri); } else if (decoder.plugin->stream_decode != NULL) { - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); + Mutex mutex; + Cond cond; struct input_stream *is = input_stream_open(decoder.uri, mutex, cond, &error); @@ -231,9 +204,6 @@ int main(int argc, char **argv) decoder_plugin_stream_decode(decoder.plugin, &decoder, is); input_stream_close(is); - - g_cond_free(cond); - g_mutex_free(mutex); } else { g_printerr("Decoder plugin is not usable\n"); return 1; @@ -248,7 +218,5 @@ int main(int argc, char **argv) return 1; } - tag_pool_deinit(); - return 0; } diff --git a/test/run_encoder.c b/test/run_encoder.cxx index db4d3af9..9039f2db 100644 --- a/test/run_encoder.c +++ b/test/run_encoder.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 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 "encoder_list.h" #include "encoder_plugin.h" #include "audio_format.h" -#include "audio_parser.h" +#include "AudioParser.hxx" #include "conf.h" #include "stdbin.h" @@ -49,9 +49,7 @@ int main(int argc, char **argv) const char *encoder_name; const struct encoder_plugin *plugin; struct encoder *encoder; - struct config_param *param; static char buffer[32768]; - ssize_t nbytes; /* parse command line */ @@ -75,10 +73,10 @@ int main(int argc, char **argv) return 1; } - param = config_new_param(NULL, -1); - config_add_block_param(param, "quality", "5.0", -1); + config_param param; + param.AddBlockParam("quality", "5.0", -1); - encoder = encoder_init(plugin, param, &error); + encoder = encoder_init(plugin, ¶m, &error); if (encoder == NULL) { g_printerr("Failed to initialize encoder: %s\n", error->message); @@ -110,6 +108,7 @@ int main(int argc, char **argv) /* do it */ + ssize_t nbytes; while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { ret = encoder_write(encoder, buffer, nbytes, &error); if (!ret) { diff --git a/test/run_filter.c b/test/run_filter.cxx index 8e793b76..9ea50ff5 100644 --- a/test/run_filter.c +++ b/test/run_filter.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,13 +19,13 @@ #include "config.h" #include "conf.h" -#include "audio_parser.h" +#include "fs/Path.hxx" +#include "AudioParser.hxx" #include "audio_format.h" -#include "filter_plugin.h" -#include "pcm_volume.h" -#include "idle.h" -#include "mixer_control.h" -#include "playlist.h" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "PcmVolume.hxx" +#include "MixerControl.hxx" #include "stdbin.h" #include <glib.h> @@ -35,13 +35,6 @@ #include <errno.h> #include <unistd.h> -struct playlist g_playlist; - -void -idle_add(G_GNUC_UNUSED unsigned flags) -{ -} - bool mixer_set_volume(G_GNUC_UNUSED struct mixer *mixer, G_GNUC_UNUSED unsigned volume, G_GNUC_UNUSED GError **error_r) @@ -60,11 +53,11 @@ my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, } static const struct config_param * -find_named_config_block(const char *block, const char *name) +find_named_config_block(ConfigOption option, const char *name) { const struct config_param *param = NULL; - while ((param = config_get_next_param(block, param)) != NULL) { + while ((param = config_get_next_param(option, param)) != NULL) { const char *current_name = config_get_block_string(param, "name", NULL); if (current_name != NULL && strcmp(current_name, name) == 0) @@ -74,20 +67,19 @@ find_named_config_block(const char *block, const char *name) return NULL; } -static struct filter * +static Filter * load_filter(const char *name) { const struct config_param *param; - struct filter *filter; GError *error = NULL; - param = find_named_config_block("filter", name); + param = find_named_config_block(CONF_AUDIO_FILTER, name); if (param == NULL) { g_printerr("No such configured filter: %s\n", name); - return false; + return nullptr; } - filter = filter_configured_new(param, &error); + Filter *filter = filter_configured_new(param, &error); if (filter == NULL) { g_printerr("Failed to load filter: %s\n", error->message); g_error_free(error); @@ -103,7 +95,6 @@ int main(int argc, char **argv) struct audio_format_string af_string; bool success; GError *error = NULL; - struct filter *filter; const struct audio_format *out_audio_format; char buffer[4096]; @@ -112,6 +103,8 @@ int main(int argc, char **argv) return 1; } + const Path config_path = Path::FromFS(argv[1]); + audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); /* initialize GLib */ @@ -122,8 +115,7 @@ int main(int argc, char **argv) /* read configuration file (mpd.conf) */ config_global_init(); - success = config_read_file(argv[1], &error); - if (!success) { + if (!ReadConfigFile(config_path, &error)) { g_printerr("%s:", error->message); g_error_free(error); return 1; @@ -144,17 +136,17 @@ int main(int argc, char **argv) /* initialize the filter */ - filter = load_filter(argv[2]); + Filter *filter = load_filter(argv[2]); if (filter == NULL) return 1; /* open the filter */ - out_audio_format = filter_open(filter, &audio_format, &error); + out_audio_format = filter->Open(audio_format, &error); if (out_audio_format == NULL) { g_printerr("Failed to open filter: %s\n", error->message); g_error_free(error); - filter_free(filter); + delete filter; return 1; } @@ -172,28 +164,28 @@ int main(int argc, char **argv) if (nbytes <= 0) break; - dest = filter_filter(filter, buffer, (size_t)nbytes, - &length, &error); + dest = filter->FilterPCM(buffer, (size_t)nbytes, + &length, &error); if (dest == NULL) { g_printerr("Filter failed: %s\n", error->message); - filter_close(filter); - filter_free(filter); + filter->Close(); + delete filter; return 1; } nbytes = write(1, dest, length); if (nbytes < 0) { g_printerr("Failed to write: %s\n", g_strerror(errno)); - filter_close(filter); - filter_free(filter); + filter->Close(); + delete filter; return 1; } } /* cleanup and exit */ - filter_close(filter); - filter_free(filter); + filter->Close(); + delete filter; config_global_finish(); diff --git a/test/run_inotify.c b/test/run_inotify.cxx index 3e7c70db..29fcf70a 100644 --- a/test/run_inotify.c +++ b/test/run_inotify.cxx @@ -18,18 +18,20 @@ */ #include "config.h" -#include "inotify_source.h" +#include "InotifySource.hxx" +#include "event/Loop.hxx" + +#include <glib.h> -#include <stdbool.h> #include <sys/inotify.h> #include <signal.h> -static GMainLoop *main_loop; +static EventLoop *event_loop; static void exit_signal_handler(G_GNUC_UNUSED int signum) { - g_main_loop_quit(main_loop); + event_loop->Break(); } enum { @@ -59,26 +61,25 @@ int main(int argc, char **argv) path = argv[1]; - struct mpd_inotify_source *source = - mpd_inotify_source_new(my_inotify_callback, NULL, - &error); + event_loop = new EventLoop(EventLoop::Default()); + + InotifySource *source = InotifySource::Create(*event_loop, + my_inotify_callback, + nullptr, &error); if (source == NULL) { g_warning("%s", error->message); g_error_free(error); return 2; } - int descriptor = mpd_inotify_source_add(source, path, - IN_MASK, &error); + int descriptor = source->Add(path, IN_MASK, &error); if (descriptor < 0) { - mpd_inotify_source_free(source); + delete source; g_warning("%s", error->message); g_error_free(error); return 2; } - main_loop = g_main_loop_new(NULL, false); - struct sigaction sa; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); @@ -86,8 +87,8 @@ int main(int argc, char **argv) sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); - g_main_loop_run(main_loop); - g_main_loop_unref(main_loop); + event_loop->Run(); - mpd_inotify_source_free(source); + delete source; + delete event_loop; } diff --git a/test/run_input.c b/test/run_input.cxx index 676e4e61..157613db 100644 --- a/test/run_input.c +++ b/test/run_input.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,16 +18,17 @@ */ #include "config.h" -#include "io_thread.h" -#include "input_init.h" -#include "input_stream.h" -#include "tag_pool.h" -#include "tag_save.h" -#include "conf.h" +#include "TagSave.hxx" #include "stdbin.h" +#include "tag.h" +#include "conf.h" +#include "input_stream.h" +#include "InputStream.hxx" +#include "InputInit.hxx" +#include "IOThread.hxx" #ifdef ENABLE_ARCHIVE -#include "archive_list.h" +#include "ArchiveList.hxx" #endif #include <glib.h> @@ -68,8 +69,8 @@ dump_input_stream(struct input_stream *is) /* print meta data */ - if (is->mime != NULL) - g_printerr("MIME type: %s\n", is->mime); + if (!is->mime.empty()) + g_printerr("MIME type: %s\n", is->mime.c_str()); /* read data and tags from the stream */ @@ -127,7 +128,6 @@ int main(int argc, char **argv) /* initialize MPD */ - tag_pool_init(); config_global_init(); io_thread_init(); @@ -149,8 +149,8 @@ int main(int argc, char **argv) /* open the stream and dump it */ - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); + Mutex mutex; + Cond cond; is = input_stream_open(argv[1], mutex, cond, &error); if (is != NULL) { @@ -165,9 +165,6 @@ int main(int argc, char **argv) ret = 2; } - g_cond_free(cond); - g_mutex_free(mutex); - /* deinitialize everything */ input_stream_global_finish(); @@ -179,7 +176,6 @@ int main(int argc, char **argv) io_thread_deinit(); config_global_finish(); - tag_pool_deinit(); return ret; } diff --git a/test/run_normalize.c b/test/run_normalize.cxx index fc26829e..070dbaf5 100644 --- a/test/run_normalize.c +++ b/test/run_normalize.cxx @@ -25,7 +25,7 @@ #include "config.h" #include "AudioCompress/compress.h" -#include "audio_parser.h" +#include "AudioParser.hxx" #include "audio_format.h" #include "stdbin.h" @@ -58,7 +58,7 @@ int main(int argc, char **argv) return 1; } } else - audio_format_init(&audio_format, 48000, 16, 2); + audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2); compressor = Compressor_new(0); diff --git a/test/run_output.c b/test/run_output.cxx index bbb1be7d..5b6d54a1 100644 --- a/test/run_output.c +++ b/test/run_output.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,18 +18,24 @@ */ #include "config.h" -#include "io_thread.h" +#include "OutputControl.hxx" +#include "conf.h" +#include "Idle.hxx" +#include "Main.hxx" +#include "event/Loop.hxx" +#include "GlobalEvents.hxx" +#include "IOThread.hxx" +#include "fs/Path.hxx" +#include "AudioParser.hxx" +#include "PcmConvert.hxx" + +extern "C" { #include "output_plugin.h" #include "output_internal.h" -#include "output_control.h" -#include "conf.h" -#include "audio_parser.h" -#include "filter_registry.h" -#include "pcm_convert.h" -#include "event_pipe.h" -#include "idle.h" -#include "playlist.h" -#include "player_control.h" +} + +#include "FilterRegistry.hxx" +#include "PlayerControl.hxx" #include "stdbin.h" #include <glib.h> @@ -39,33 +45,22 @@ #include <unistd.h> #include <stdlib.h> -struct playlist g_playlist; +EventLoop *main_loop; void -idle_add(G_GNUC_UNUSED unsigned flags) -{ -} - -void -event_pipe_emit(G_GNUC_UNUSED enum pipe_event event) -{ -} - -void pcm_convert_init(G_GNUC_UNUSED struct pcm_convert_state *state) +GlobalEvents::Emit(gcc_unused Event event) { } -void pcm_convert_deinit(G_GNUC_UNUSED struct pcm_convert_state *state) -{ -} +PcmConvert::PcmConvert() {} +PcmConvert::~PcmConvert() {} const void * -pcm_convert(G_GNUC_UNUSED struct pcm_convert_state *state, - G_GNUC_UNUSED const struct audio_format *src_format, - G_GNUC_UNUSED const void *src, G_GNUC_UNUSED size_t src_size, - G_GNUC_UNUSED const struct audio_format *dest_format, - G_GNUC_UNUSED size_t *dest_size_r, - GError **error_r) +PcmConvert::Convert(gcc_unused const audio_format *src_format, + gcc_unused const void *src, gcc_unused size_t src_size, + gcc_unused const audio_format *dest_format, + gcc_unused size_t *dest_size_r, + gcc_unused GError **error_r) { g_set_error(error_r, pcm_convert_quark(), 0, "Not implemented"); @@ -80,11 +75,11 @@ filter_plugin_by_name(G_GNUC_UNUSED const char *name) } static const struct config_param * -find_named_config_block(const char *block, const char *name) +find_named_config_block(ConfigOption option, const char *name) { const struct config_param *param = NULL; - while ((param = config_get_next_param(block, param)) != NULL) { + while ((param = config_get_next_param(option, param)) != NULL) { const char *current_name = config_get_block_string(param, "name", NULL); if (current_name != NULL && strcmp(current_name, name) == 0) @@ -94,6 +89,10 @@ find_named_config_block(const char *block, const char *name) return NULL; } +player_control::player_control(gcc_unused unsigned _buffer_chunks, + gcc_unused unsigned _buffered_before_play) {} +player_control::~player_control() {} + static struct audio_output * load_audio_output(const char *name) { @@ -103,10 +102,10 @@ load_audio_output(const char *name) param = find_named_config_block(CONF_AUDIO_OUTPUT, name); if (param == NULL) { g_printerr("No such configured audio output: %s\n", name); - return false; + return nullptr; } - static struct player_control dummy_player_control; + static struct player_control dummy_player_control(32, 4); struct audio_output *ao = audio_output_new(param, &dummy_player_control, &error); @@ -197,6 +196,8 @@ int main(int argc, char **argv) return 1; } + const Path config_path = Path::FromFS(argv[1]); + audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); g_thread_init(NULL); @@ -204,13 +205,14 @@ int main(int argc, char **argv) /* read configuration file (mpd.conf) */ config_global_init(); - success = config_read_file(argv[1], &error); - if (!success) { + if (!ReadConfigFile(config_path, &error)) { g_printerr("%s:", error->message); g_error_free(error); return 1; } + main_loop = new EventLoop(EventLoop::Default()); + io_thread_init(); if (!io_thread_start(&error)) { g_warning("%s", error->message); @@ -247,6 +249,8 @@ int main(int argc, char **argv) io_thread_deinit(); + delete main_loop; + config_global_finish(); return success ? EXIT_SUCCESS : EXIT_FAILURE; diff --git a/test/run_tcp_connect.c b/test/run_tcp_connect.c deleted file mode 100644 index bf8d9b82..00000000 --- a/test/run_tcp_connect.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "resolver.h" -#include "io_thread.h" -#include "tcp_connect.h" -#include "fd_util.h" - -#include <assert.h> -#include <stdlib.h> - -#ifdef WIN32 -#include <ws2tcpip.h> -#include <winsock.h> -#else -#include <sys/socket.h> -#include <netdb.h> -#endif - -static struct tcp_connect *handle; -static GMutex *mutex; -static GCond *cond; -static bool done, success; - -static void -my_tcp_connect_success(int fd, G_GNUC_UNUSED void *ctx) -{ - assert(!done); - assert(!success); - - close_socket(fd); - g_print("success\n"); - - g_mutex_lock(mutex); - done = success = true; - g_cond_signal(cond); - g_mutex_unlock(mutex); -} - -static void -my_tcp_connect_error(GError *error, G_GNUC_UNUSED void *ctx) -{ - assert(!done); - assert(!success); - - g_printerr("error: %s\n", error->message); - g_error_free(error); - - g_mutex_lock(mutex); - done = true; - g_cond_signal(cond); - g_mutex_unlock(mutex); -} - -static void -my_tcp_connect_timeout(G_GNUC_UNUSED void *ctx) -{ - assert(!done); - assert(!success); - - g_printerr("timeout\n"); - - g_mutex_lock(mutex); - done = true; - g_cond_signal(cond); - g_mutex_unlock(mutex); -} - -static void -my_tcp_connect_canceled(G_GNUC_UNUSED void *ctx) -{ - assert(!done); - assert(!success); - - g_printerr("canceled\n"); - - g_mutex_lock(mutex); - done = true; - g_cond_signal(cond); - g_mutex_unlock(mutex); -} - -static const struct tcp_connect_handler my_tcp_connect_handler = { - .success = my_tcp_connect_success, - .error = my_tcp_connect_error, - .timeout = my_tcp_connect_timeout, - .canceled = my_tcp_connect_canceled, -}; - -int main(int argc, char **argv) -{ - if (argc != 2) { - g_printerr("Usage: run_tcp_connect IP:PORT\n"); - return 1; - } - - GError *error = NULL; - struct addrinfo *ai = resolve_host_port(argv[1], 80, 0, SOCK_STREAM, - &error); - if (ai == NULL) { - g_printerr("%s\n", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - /* initialize GLib */ - - g_thread_init(NULL); - - /* initialize MPD */ - - io_thread_init(); - if (!io_thread_start(&error)) { - freeaddrinfo(ai); - g_printerr("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - /* open the connection */ - - mutex = g_mutex_new(); - cond = g_cond_new(); - - tcp_connect_address(ai->ai_addr, ai->ai_addrlen, 5000, - &my_tcp_connect_handler, NULL, - &handle); - freeaddrinfo(ai); - - if (handle != NULL) { - g_mutex_lock(mutex); - while (!done) - g_cond_wait(cond, mutex); - g_mutex_unlock(mutex); - - tcp_connect_free(handle); - } - - g_cond_free(cond); - g_mutex_free(mutex); - - /* deinitialize everything */ - - io_thread_deinit(); - - return EXIT_SUCCESS; -} diff --git a/test/software_volume.c b/test/software_volume.cxx index 2357da67..92993239 100644 --- a/test/software_volume.c +++ b/test/software_volume.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,8 +24,8 @@ */ #include "config.h" -#include "pcm_volume.h" -#include "audio_parser.h" +#include "PcmVolume.hxx" +#include "AudioParser.hxx" #include "audio_format.h" #include "stdbin.h" @@ -59,7 +59,8 @@ int main(int argc, char **argv) audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2); while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { - if (!pcm_volume(buffer, nbytes, audio_format.format, + if (!pcm_volume(buffer, nbytes, + sample_format(audio_format.format), PCM_VOLUME_1 / 2)) { g_printerr("pcm_volume() has failed\n"); return 2; diff --git a/test/test_pcm_all.h b/test/test_pcm_all.hxx index 493619b5..18202454 100644 --- a/test/test_pcm_all.h +++ b/test/test_pcm_all.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,40 +17,64 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_TEST_PCM_ALL_H -#define MPD_TEST_PCM_ALL_H +#ifndef MPD_TEST_PCM_ALL_HXX +#define MPD_TEST_PCM_ALL_HXX void -test_pcm_dither_24(void); +test_pcm_dither_24(); void -test_pcm_dither_32(void); +test_pcm_dither_32(); void -test_pcm_pack_24(void); +test_pcm_pack_24(); void -test_pcm_unpack_24(void); +test_pcm_unpack_24(); void -test_pcm_channels_16(void); +test_pcm_channels_16(); void -test_pcm_channels_32(void); +test_pcm_channels_32(); void -test_pcm_volume_8(void); +test_pcm_volume_8(); void -test_pcm_volume_16(void); +test_pcm_volume_16(); void -test_pcm_volume_24(void); +test_pcm_volume_24(); void -test_pcm_volume_32(void); +test_pcm_volume_32(); void -test_pcm_volume_float(void); +test_pcm_volume_float(); + +void +test_pcm_format_8_to_16(); + +void +test_pcm_format_16_to_24(); + +void +test_pcm_format_16_to_32(); + +void +test_pcm_format_float(); + +void +test_pcm_mix_8(); + +void +test_pcm_mix_16(); + +void +test_pcm_mix_24(); + +void +test_pcm_mix_32(); #endif diff --git a/test/test_pcm_channels.c b/test/test_pcm_channels.cxx index fb3ec5c3..8e013d5a 100644 --- a/test/test_pcm_channels.c +++ b/test/test_pcm_channels.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,20 +18,18 @@ */ #include "config.h" -#include "test_pcm_all.h" -#include "pcm_channels.h" +#include "test_pcm_all.hxx" +#include "test_pcm_util.hxx" +#include "PcmChannels.hxx" #include "pcm_buffer.h" #include <glib.h> void -test_pcm_channels_16(void) +test_pcm_channels_16() { - enum { N = 256 }; - int16_t src[N * 2]; - - for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i) - src[i] = g_random_int(); + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int16_t, N * 2>(); struct pcm_buffer buffer; pcm_buffer_init(&buffer); @@ -63,13 +61,10 @@ test_pcm_channels_16(void) } void -test_pcm_channels_32(void) +test_pcm_channels_32() { - enum { N = 256 }; - int32_t src[N * 2]; - - for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i) - src[i] = g_random_int(); + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int32_t, N * 2>(); struct pcm_buffer buffer; pcm_buffer_init(&buffer); diff --git a/test/test_pcm_dither.c b/test/test_pcm_dither.cxx index 44d10520..2fb976db 100644 --- a/test/test_pcm_dither.c +++ b/test/test_pcm_dither.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,38 +17,21 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "test_pcm_all.h" -#include "pcm_dither.h" +#include "test_pcm_all.hxx" +#include "test_pcm_util.hxx" +#include "PcmDither.hxx" #include <glib.h> -/** - * Generate a random 24 bit PCM sample. - */ -static int32_t -random24(void) -{ - int32_t x = g_random_int() & 0xffffff; - if (x & 0x800000) - x |= 0xff000000; - return x; -} - void -test_pcm_dither_24(void) +test_pcm_dither_24() { - struct pcm_dither dither; - - pcm_dither_24_init(&dither); - - enum { N = 256 }; - int32_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = random24(); + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int32_t, N>(GlibRandomInt24()); int16_t dest[N]; - - pcm_dither_24_to_16(&dither, dest, src, src + N); + PcmDither dither; + dither.Dither24To16(dest, src.begin(), src.end()); for (unsigned i = 0; i < N; ++i) { g_assert_cmpint(dest[i], >=, (src[i] >> 8) - 8); @@ -57,20 +40,14 @@ test_pcm_dither_24(void) } void -test_pcm_dither_32(void) +test_pcm_dither_32() { - struct pcm_dither dither; - - pcm_dither_24_init(&dither); - - enum { N = 256 }; - int32_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_int(); + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int32_t, N>(); int16_t dest[N]; - - pcm_dither_32_to_16(&dither, dest, src, src + N); + PcmDither dither; + dither.Dither32To16(dest, src.begin(), src.end()); for (unsigned i = 0; i < N; ++i) { g_assert_cmpint(dest[i], >=, (src[i] >> 16) - 8); diff --git a/test/test_pcm_format.cxx b/test/test_pcm_format.cxx new file mode 100644 index 00000000..f175a850 --- /dev/null +++ b/test/test_pcm_format.cxx @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "test_pcm_all.hxx" +#include "test_pcm_util.hxx" +#include "PcmFormat.hxx" +#include "PcmDither.hxx" +#include "PcmUtils.hxx" +#include "pcm_buffer.h" +#include "audio_format.h" + +#include <glib.h> + +void +test_pcm_format_8_to_16() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int8_t, N>(); + + struct pcm_buffer buffer; + pcm_buffer_init(&buffer); + + size_t d_size; + PcmDither dither; + auto d = pcm_convert_to_16(&buffer, dither, SAMPLE_FORMAT_S8, + src, sizeof(src), &d_size); + auto d_end = pcm_end_pointer(d, d_size); + g_assert_cmpint(d_end - d, ==, N); + + for (size_t i = 0; i < N; ++i) + g_assert_cmpint(src[i], ==, d[i] >> 8); + + pcm_buffer_deinit(&buffer); +} + +void +test_pcm_format_16_to_24() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int16_t, N>(); + + struct pcm_buffer buffer; + pcm_buffer_init(&buffer); + + size_t d_size; + auto d = pcm_convert_to_24(&buffer, SAMPLE_FORMAT_S16, + src, sizeof(src), &d_size); + auto d_end = pcm_end_pointer(d, d_size); + g_assert_cmpint(d_end - d, ==, N); + + for (size_t i = 0; i < N; ++i) + g_assert_cmpint(src[i], ==, d[i] >> 8); + + pcm_buffer_deinit(&buffer); +} + +void +test_pcm_format_16_to_32() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int16_t, N>(); + + struct pcm_buffer buffer; + pcm_buffer_init(&buffer); + + size_t d_size; + auto d = pcm_convert_to_32(&buffer, SAMPLE_FORMAT_S16, + src, sizeof(src), &d_size); + auto d_end = pcm_end_pointer(d, d_size); + g_assert_cmpint(d_end - d, ==, N); + + for (size_t i = 0; i < N; ++i) + g_assert_cmpint(src[i], ==, d[i] >> 16); + + pcm_buffer_deinit(&buffer); +} + +void +test_pcm_format_float() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int16_t, N>(); + + struct pcm_buffer buffer1, buffer2; + pcm_buffer_init(&buffer1); + pcm_buffer_init(&buffer2); + + size_t f_size; + auto f = pcm_convert_to_float(&buffer1, SAMPLE_FORMAT_S16, + src, sizeof(src), &f_size); + auto f_end = pcm_end_pointer(f, f_size); + g_assert_cmpint(f_end - f, ==, N); + + for (auto i = f; i != f_end; ++i) { + g_assert(*i >= -1.); + g_assert(*i <= 1.); + } + + PcmDither dither; + + size_t d_size; + auto d = pcm_convert_to_16(&buffer2, dither, + SAMPLE_FORMAT_FLOAT, + f, f_size, &d_size); + auto d_end = pcm_end_pointer(d, d_size); + g_assert_cmpint(d_end - d, ==, N); + + for (size_t i = 0; i < N; ++i) + g_assert_cmpint(src[i], ==, d[i]); + + pcm_buffer_deinit(&buffer1); + pcm_buffer_deinit(&buffer2); +} diff --git a/test/test_pcm_main.c b/test/test_pcm_main.cxx index a483baaa..a221b26a 100644 --- a/test/test_pcm_main.c +++ b/test/test_pcm_main.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,7 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "test_pcm_all.h" +#include "test_pcm_all.hxx" #include <glib.h> @@ -38,5 +38,15 @@ main(int argc, char **argv) g_test_add_func("/pcm/volume/32", test_pcm_volume_32); g_test_add_func("/pcm/volume/float", test_pcm_volume_float); + g_test_add_func("/pcm/format/8_to_16", test_pcm_format_8_to_16); + g_test_add_func("/pcm/format/16_to_24", test_pcm_format_16_to_24); + g_test_add_func("/pcm/format/16_to_32", test_pcm_format_16_to_32); + g_test_add_func("/pcm/format/float", test_pcm_format_float); + + g_test_add_func("/pcm/mix/8", test_pcm_mix_8); + g_test_add_func("/pcm/mix/16", test_pcm_mix_16); + g_test_add_func("/pcm/mix/24", test_pcm_mix_24); + g_test_add_func("/pcm/mix/32", test_pcm_mix_32); + g_test_run(); } diff --git a/test/test_pcm_mix.cxx b/test/test_pcm_mix.cxx new file mode 100644 index 00000000..a6e01d20 --- /dev/null +++ b/test/test_pcm_mix.cxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "test_pcm_all.hxx" +#include "test_pcm_util.hxx" +#include "PcmMix.hxx" + +#include <glib.h> + +template<typename T, sample_format format, typename G=GlibRandomInt<T>> +void +TestPcmMix(G g=G()) +{ + constexpr unsigned N = 256; + const auto src1 = TestDataBuffer<T, N>(g); + const auto src2 = TestDataBuffer<T, N>(g); + + /* portion1=1.0: result must be equal to src1 */ + auto result = src1; + bool success = pcm_mix(result.begin(), src2.begin(), sizeof(result), + format, 1.0); + g_assert(success); + AssertEqualWithTolerance(result, src1, 1); + + /* portion1=0.0: result must be equal to src2 */ + result = src1; + success = pcm_mix(result.begin(), src2.begin(), sizeof(result), + format, 0.0); + g_assert(success); + AssertEqualWithTolerance(result, src2, 1); + + /* portion1=0.5 */ + result = src1; + success = pcm_mix(result.begin(), src2.begin(), sizeof(result), + format, 0.5); + g_assert(success); + + auto expected = src1; + for (unsigned i = 0; i < N; ++i) + expected[i] = (int64_t(src1[i]) + int64_t(src2[i])) / 2; + + AssertEqualWithTolerance(result, expected, 1); +} + +void +test_pcm_mix_8() +{ + TestPcmMix<int8_t, SAMPLE_FORMAT_S8>(); +} + +void +test_pcm_mix_16() +{ + TestPcmMix<int16_t, SAMPLE_FORMAT_S16>(); +} + +void +test_pcm_mix_24() +{ + TestPcmMix<int32_t, SAMPLE_FORMAT_S24_P32>(GlibRandomInt24()); +} + +void +test_pcm_mix_32() +{ + TestPcmMix<int32_t, SAMPLE_FORMAT_S32>(); +} diff --git a/test/test_pcm_pack.c b/test/test_pcm_pack.cxx index 5127536f..e23acad5 100644 --- a/test/test_pcm_pack.c +++ b/test/test_pcm_pack.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,34 +17,23 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "test_pcm_all.h" +#include "test_pcm_all.hxx" +#include "test_pcm_util.hxx" + +extern "C" { #include "pcm_pack.h" +} #include <glib.h> -/** - * Generate a random 24 bit PCM sample. - */ -static int32_t -random24(void) -{ - int32_t x = g_random_int() & 0xffffff; - if (x & 0x800000) - x |= 0xff000000; - return x; -} - void -test_pcm_pack_24(void) +test_pcm_pack_24() { - enum { N = 256 }; - int32_t src[N * 3]; - for (unsigned i = 0; i < N; ++i) - src[i] = random24(); + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int32_t, N>(GlibRandomInt24()); uint8_t dest[N * 3]; - - pcm_pack_24(dest, src, src + N); + pcm_pack_24(dest, src.begin(), src.end()); for (unsigned i = 0; i < N; ++i) { int32_t d; @@ -62,16 +51,13 @@ test_pcm_pack_24(void) } void -test_pcm_unpack_24(void) +test_pcm_unpack_24() { - enum { N = 256 }; - uint8_t src[N * 3]; - for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i) - src[i] = g_random_int_range(0, 256); + constexpr unsigned N = 256; + const auto src = TestDataBuffer<uint8_t, N * 3>(); int32_t dest[N]; - - pcm_unpack_24(dest, src, src + G_N_ELEMENTS(src)); + pcm_unpack_24(dest, src.begin(), src.end()); for (unsigned i = 0; i < N; ++i) { int32_t s; diff --git a/test/test_pcm_util.hxx b/test/test_pcm_util.hxx new file mode 100644 index 00000000..84ba074f --- /dev/null +++ b/test/test_pcm_util.hxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <glib.h> + +#include <array> + +#include <stddef.h> +#include <stdint.h> + +template<typename T> +struct GlibRandomInt { + T operator()() const { + return T(g_random_int()); + } +}; + +struct GlibRandomInt24 : GlibRandomInt<int32_t> { + int32_t operator()() const { + auto t = GlibRandomInt::operator()(); + t &= 0xffffff; + if (t & 0x800000) + t |= 0xff000000; + return t; + } +}; + +struct GlibRandomFloat { + float operator()() const { + return g_random_double_range(-1.0, 1.0); + } +}; + +template<typename T, size_t N> +class TestDataBuffer : std::array<T, N> { +public: + using typename std::array<T, N>::const_pointer; + using std::array<T, N>::size; + using std::array<T, N>::begin; + using std::array<T, N>::end; + using std::array<T, N>::operator[]; + + template<typename G=GlibRandomInt<T>> + TestDataBuffer(G g=G()):std::array<T, N>() { + for (auto &i : *this) + i = g(); + + } + + operator typename std::array<T, N>::const_pointer() const { + return begin(); + } +}; + +template<typename T> +bool +AssertEqualWithTolerance(const T &a, const T &b, unsigned tolerance) +{ + g_assert_cmpint(a.size(), ==, b.size()); + + for (unsigned i = 0; i < a.size(); ++i) { + int64_t x = a[i], y = b[i]; + + g_assert_cmpint(x, >=, y - int64_t(tolerance)); + g_assert_cmpint(x, <=, y + int64_t(tolerance)); + } + + return true; +} diff --git a/test/test_pcm_volume.c b/test/test_pcm_volume.cxx index 713645cf..1ab59049 100644 --- a/test/test_pcm_volume.c +++ b/test/test_pcm_volume.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,35 +17,36 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "test_pcm_all.h" -#include "pcm_volume.h" +#include "test_pcm_all.hxx" +#include "PcmVolume.hxx" +#include "test_pcm_util.hxx" #include <glib.h> +#include <algorithm> + #include <string.h> void -test_pcm_volume_8(void) +test_pcm_volume_8() { - enum { N = 256 }; - static const int8_t zero[N]; - int8_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_int(); + constexpr unsigned N = 256; + static int8_t zero[N]; + const auto src = TestDataBuffer<int8_t, N>(); int8_t dest[N]; - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8, 0), ==, true); g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8, PCM_VOLUME_1), ==, true); g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8, PCM_VOLUME_1 / 2), ==, true); @@ -56,27 +57,25 @@ test_pcm_volume_8(void) } void -test_pcm_volume_16(void) +test_pcm_volume_16() { - enum { N = 256 }; - static const int16_t zero[N]; - int16_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_int(); + constexpr unsigned N = 256; + static int16_t zero[N]; + const auto src = TestDataBuffer<int16_t, N>(); int16_t dest[N]; - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16, 0), ==, true); g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16, PCM_VOLUME_1), ==, true); g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16, PCM_VOLUME_1 / 2), ==, true); @@ -86,40 +85,26 @@ test_pcm_volume_16(void) } } -/** - * Generate a random 24 bit PCM sample. - */ -static int32_t -random24(void) -{ - int32_t x = g_random_int() & 0xffffff; - if (x & 0x800000) - x |= 0xff000000; - return x; -} - void -test_pcm_volume_24(void) +test_pcm_volume_24() { - enum { N = 256 }; - static const int32_t zero[N]; - int32_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = random24(); + constexpr unsigned N = 256; + static int32_t zero[N]; + const auto src = TestDataBuffer<int32_t, N>(GlibRandomInt24()); int32_t dest[N]; - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32, 0), ==, true); g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32, PCM_VOLUME_1), ==, true); g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32, PCM_VOLUME_1 / 2), ==, true); @@ -130,27 +115,25 @@ test_pcm_volume_24(void) } void -test_pcm_volume_32(void) +test_pcm_volume_32() { - enum { N = 256 }; - static const int32_t zero[N]; - int32_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_int(); + constexpr unsigned N = 256; + static int32_t zero[N]; + const auto src = TestDataBuffer<int32_t, N>(); int32_t dest[N]; - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32, 0), ==, true); g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32, PCM_VOLUME_1), ==, true); g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32, PCM_VOLUME_1 / 2), ==, true); @@ -161,27 +144,25 @@ test_pcm_volume_32(void) } void -test_pcm_volume_float(void) +test_pcm_volume_float() { - enum { N = 256 }; - static const float zero[N]; - float src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_double_range(-1.0, 1.0); + constexpr unsigned N = 256; + static float zero[N]; + const auto src = TestDataBuffer<float, N>(GlibRandomFloat()); float dest[N]; - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT, 0), ==, true); g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT, PCM_VOLUME_1), ==, true); g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - memcpy(dest, src, sizeof(src)); + std::copy(src.begin(), src.end(), dest); g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT, PCM_VOLUME_1 / 2), ==, true); diff --git a/test/test_queue_priority.c b/test/test_queue_priority.c deleted file mode 100644 index 5543edbb..00000000 --- a/test/test_queue_priority.c +++ /dev/null @@ -1,175 +0,0 @@ -#include "queue.h" -#include "song.h" - -void -song_free(G_GNUC_UNUSED struct song *song) -{ -} - -G_GNUC_UNUSED -static void -dump_order(const struct queue *queue) -{ - g_printerr("queue length=%u, order:\n", queue_length(queue)); - for (unsigned i = 0; i < queue_length(queue); ++i) - g_printerr(" [%u] -> %u (prio=%u)\n", i, queue->order[i], - queue->items[queue->order[i]].priority); -} - -static void -check_descending_priority(G_GNUC_UNUSED const struct queue *queue, - unsigned start_order) -{ - assert(start_order < queue_length(queue)); - - uint8_t last_priority = 0xff; - for (unsigned order = start_order; order < queue_length(queue); ++order) { - unsigned position = queue_order_to_position(queue, order); - uint8_t priority = queue->items[position].priority; - assert(priority <= last_priority); - (void)last_priority; - last_priority = priority; - } -} - -int -main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) -{ - struct song songs[16]; - - struct queue queue; - queue_init(&queue, 32); - - for (unsigned i = 0; i < G_N_ELEMENTS(songs); ++i) - queue_append(&queue, &songs[i], 0); - - assert(queue_length(&queue) == G_N_ELEMENTS(songs)); - - /* priority=10 for 4 items */ - - queue_set_priority_range(&queue, 4, 8, 10, -1); - - queue.random = true; - queue_shuffle_order(&queue); - check_descending_priority(&queue, 0); - - for (unsigned i = 0; i < 4; ++i) { - assert(queue_position_to_order(&queue, i) >= 4); - } - - for (unsigned i = 4; i < 8; ++i) { - assert(queue_position_to_order(&queue, i) < 4); - } - - for (unsigned i = 8; i < G_N_ELEMENTS(songs); ++i) { - assert(queue_position_to_order(&queue, i) >= 4); - } - - /* priority=50 one more item */ - - queue_set_priority_range(&queue, 15, 16, 50, -1); - check_descending_priority(&queue, 0); - - assert(queue_position_to_order(&queue, 15) == 0); - - for (unsigned i = 0; i < 4; ++i) { - assert(queue_position_to_order(&queue, i) >= 4); - } - - for (unsigned i = 4; i < 8; ++i) { - assert(queue_position_to_order(&queue, i) >= 1 && - queue_position_to_order(&queue, i) < 5); - } - - for (unsigned i = 8; i < 15; ++i) { - assert(queue_position_to_order(&queue, i) >= 5); - } - - /* priority=20 for one of the 4 priority=10 items */ - - queue_set_priority_range(&queue, 3, 4, 20, -1); - check_descending_priority(&queue, 0); - - assert(queue_position_to_order(&queue, 3) == 1); - assert(queue_position_to_order(&queue, 15) == 0); - - for (unsigned i = 0; i < 3; ++i) { - assert(queue_position_to_order(&queue, i) >= 5); - } - - for (unsigned i = 4; i < 8; ++i) { - assert(queue_position_to_order(&queue, i) >= 2 && - queue_position_to_order(&queue, i) < 6); - } - - for (unsigned i = 8; i < 15; ++i) { - assert(queue_position_to_order(&queue, i) >= 6); - } - - /* priority=20 for another one of the 4 priority=10 items; - pass "after_order" (with priority=10) and see if it's moved - after that one */ - - unsigned current_order = 4; - unsigned current_position = - queue_order_to_position(&queue, current_order); - - unsigned a_order = 3; - unsigned a_position = queue_order_to_position(&queue, a_order); - assert(queue.items[a_position].priority == 10); - queue_set_priority(&queue, a_position, 20, current_order); - - current_order = queue_position_to_order(&queue, current_position); - assert(current_order == 3); - - a_order = queue_position_to_order(&queue, a_position); - assert(a_order == 4); - - check_descending_priority(&queue, current_order + 1); - - /* priority=70 for one of the last items; must be inserted - right after the current song, before the priority=20 one we - just created */ - - unsigned b_order = 10; - unsigned b_position = queue_order_to_position(&queue, b_order); - assert(queue.items[b_position].priority == 0); - queue_set_priority(&queue, b_position, 70, current_order); - - current_order = queue_position_to_order(&queue, current_position); - assert(current_order == 3); - - b_order = queue_position_to_order(&queue, b_position); - assert(b_order == 4); - - check_descending_priority(&queue, current_order + 1); - - /* priority=60 for the old prio50 item; must not be moved, - because it's before the current song, and it's status - hasn't changed (it was already higher before) */ - - unsigned c_order = 0; - unsigned c_position = queue_order_to_position(&queue, c_order); - assert(queue.items[c_position].priority == 50); - queue_set_priority(&queue, c_position, 60, current_order); - - current_order = queue_position_to_order(&queue, current_position); - assert(current_order == 3); - - c_order = queue_position_to_order(&queue, c_position); - assert(c_order == 0); - - /* move the prio=20 item back */ - - a_order = queue_position_to_order(&queue, a_position); - assert(a_order == 5); - assert(queue.items[a_position].priority == 20); - queue_set_priority(&queue, a_position, 5, current_order); - - - current_order = queue_position_to_order(&queue, current_position); - assert(current_order == 3); - - a_order = queue_position_to_order(&queue, a_position); - assert(a_order == 6); -} diff --git a/test/test_queue_priority.cxx b/test/test_queue_priority.cxx new file mode 100644 index 00000000..8dd1c3f5 --- /dev/null +++ b/test/test_queue_priority.cxx @@ -0,0 +1,188 @@ +#include "config.h" +#include "Queue.hxx" +#include "song.h" +#include "Directory.hxx" + +#include <glib.h> + +Directory detached_root; + +Directory::Directory() {} +Directory::~Directory() {} + +struct song * +song_dup_detached(const struct song *src) +{ + return const_cast<song *>(src); +} + +void +song_free(gcc_unused struct song *song) +{ +} + +gcc_unused +static void +dump_order(const struct queue *queue) +{ + g_printerr("queue length=%u, order:\n", queue->GetLength()); + for (unsigned i = 0; i < queue->GetLength(); ++i) + g_printerr(" [%u] -> %u (prio=%u)\n", i, queue->order[i], + queue->items[queue->order[i]].priority); +} + +static void +check_descending_priority(const struct queue *queue, + unsigned start_order) +{ + assert(start_order < queue->GetLength()); + + uint8_t last_priority = 0xff; + for (unsigned order = start_order; order < queue->GetLength(); ++order) { + unsigned position = queue->OrderToPosition(order); + uint8_t priority = queue->items[position].priority; + assert(priority <= last_priority); + (void)last_priority; + last_priority = priority; + } +} + +int +main(gcc_unused int argc, gcc_unused char **argv) +{ + static struct song songs[16]; + + struct queue queue(32); + + for (unsigned i = 0; i < G_N_ELEMENTS(songs); ++i) + queue.Append(&songs[i], 0); + + assert(queue.GetLength() == G_N_ELEMENTS(songs)); + + /* priority=10 for 4 items */ + + queue.SetPriorityRange(4, 8, 10, -1); + + queue.random = true; + queue.ShuffleOrder(); + check_descending_priority(&queue, 0); + + for (unsigned i = 0; i < 4; ++i) { + assert(queue.PositionToOrder(i) >= 4); + } + + for (unsigned i = 4; i < 8; ++i) { + assert(queue.PositionToOrder(i) < 4); + } + + for (unsigned i = 8; i < G_N_ELEMENTS(songs); ++i) { + assert(queue.PositionToOrder(i) >= 4); + } + + /* priority=50 one more item */ + + queue.SetPriorityRange(15, 16, 50, -1); + check_descending_priority(&queue, 0); + + assert(queue.PositionToOrder(15) == 0); + + for (unsigned i = 0; i < 4; ++i) { + assert(queue.PositionToOrder(i) >= 4); + } + + for (unsigned i = 4; i < 8; ++i) { + assert(queue.PositionToOrder(i) >= 1 && + queue.PositionToOrder(i) < 5); + } + + for (unsigned i = 8; i < 15; ++i) { + assert(queue.PositionToOrder(i) >= 5); + } + + /* priority=20 for one of the 4 priority=10 items */ + + queue.SetPriorityRange(3, 4, 20, -1); + check_descending_priority(&queue, 0); + + assert(queue.PositionToOrder(3) == 1); + assert(queue.PositionToOrder(15) == 0); + + for (unsigned i = 0; i < 3; ++i) { + assert(queue.PositionToOrder(i) >= 5); + } + + for (unsigned i = 4; i < 8; ++i) { + assert(queue.PositionToOrder(i) >= 2 && + queue.PositionToOrder(i) < 6); + } + + for (unsigned i = 8; i < 15; ++i) { + assert(queue.PositionToOrder(i) >= 6); + } + + /* priority=20 for another one of the 4 priority=10 items; + pass "after_order" (with priority=10) and see if it's moved + after that one */ + + unsigned current_order = 4; + unsigned current_position = + queue.OrderToPosition(current_order); + + unsigned a_order = 3; + unsigned a_position = queue.OrderToPosition(a_order); + assert(queue.items[a_position].priority == 10); + queue.SetPriority(a_position, 20, current_order); + + current_order = queue.PositionToOrder(current_position); + assert(current_order == 3); + + a_order = queue.PositionToOrder(a_position); + assert(a_order == 4); + + check_descending_priority(&queue, current_order + 1); + + /* priority=70 for one of the last items; must be inserted + right after the current song, before the priority=20 one we + just created */ + + unsigned b_order = 10; + unsigned b_position = queue.OrderToPosition(b_order); + assert(queue.items[b_position].priority == 0); + queue.SetPriority(b_position, 70, current_order); + + current_order = queue.PositionToOrder(current_position); + assert(current_order == 3); + + b_order = queue.PositionToOrder(b_position); + assert(b_order == 4); + + check_descending_priority(&queue, current_order + 1); + + /* priority=60 for the old prio50 item; must not be moved, + because it's before the current song, and it's status + hasn't changed (it was already higher before) */ + + unsigned c_order = 0; + unsigned c_position = queue.OrderToPosition(c_order); + assert(queue.items[c_position].priority == 50); + queue.SetPriority(c_position, 60, current_order); + + current_order = queue.PositionToOrder(current_position); + assert(current_order == 3); + + c_order = queue.PositionToOrder(c_position); + assert(c_order == 0); + + /* move the prio=20 item back */ + + a_order = queue.PositionToOrder(a_position); + assert(a_order == 5); + assert(queue.items[a_position].priority == 20); + queue.SetPriority(a_position, 5, current_order); + + current_order = queue.PositionToOrder(current_position); + assert(current_order == 3); + + a_order = queue.PositionToOrder(a_position); + assert(a_order == 6); +} diff --git a/test/test_vorbis_encoder.c b/test/test_vorbis_encoder.cxx index 61939915..0e502b15 100644 --- a/test/test_vorbis_encoder.c +++ b/test/test_vorbis_encoder.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -53,10 +53,10 @@ main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) const struct encoder_plugin *plugin = encoder_plugin_get("vorbis"); assert(plugin != NULL); - struct config_param *param = config_new_param(NULL, -1); - config_add_block_param(param, "quality", "5.0", -1); + config_param param; + param.AddBlockParam("quality", "5.0", -1); - struct encoder *encoder = encoder_init(plugin, param, NULL); + struct encoder *encoder = encoder_init(plugin, ¶m, NULL); assert(encoder != NULL); /* open the encoder */ diff --git a/test/visit_archive.cxx b/test/visit_archive.cxx new file mode 100644 index 00000000..6faf4f3a --- /dev/null +++ b/test/visit_archive.cxx @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "stdbin.h" +#include "tag.h" +#include "conf.h" +#include "IOThread.hxx" +#include "InputInit.hxx" +#include "ArchiveList.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "ArchiveVisitor.hxx" +#include "fs/Path.hxx" + +#include <glib.h> + +#include <unistd.h> +#include <stdlib.h> + +static void +my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, + const gchar *message, G_GNUC_UNUSED gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +class MyArchiveVisitor final : public ArchiveVisitor { + public: + virtual void VisitArchiveEntry(const char *path_utf8) override { + printf("%s\n", path_utf8); + } +}; + +int +main(int argc, char **argv) +{ + GError *error = nullptr; + + if (argc != 3) { + fprintf(stderr, "Usage: visit_archive PLUGIN PATH\n"); + return EXIT_FAILURE; + } + + const char *plugin_name = argv[1]; + const Path path = Path::FromFS(argv[2]); + + /* initialize GLib */ + + g_thread_init(NULL); + g_log_set_default_handler(my_log_func, NULL); + + /* initialize MPD */ + + config_global_init(); + + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + archive_plugin_init_all(); + + if (!input_stream_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + + /* open the archive and dump it */ + + const archive_plugin *plugin = archive_plugin_from_name(plugin_name); + if (plugin == nullptr) { + fprintf(stderr, "No such plugin: %s\n", plugin_name); + return EXIT_FAILURE; + } + + int result = EXIT_SUCCESS; + + ArchiveFile *file = archive_file_open(plugin, path.c_str(), &error); + if (file != nullptr) { + MyArchiveVisitor visitor; + file->Visit(visitor); + file->Close(); + } else { + fprintf(stderr, "%s\n", error->message); + g_error_free(error); + result = EXIT_FAILURE; + } + + /* deinitialize everything */ + + input_stream_global_finish(); + + archive_plugin_deinit_all(); + + io_thread_deinit(); + + config_global_finish(); + + return result; +} diff --git a/valgrind.suppressions b/valgrind.suppressions index 2cce3418..56bb8e62 100644 --- a/valgrind.suppressions +++ b/valgrind.suppressions @@ -10,6 +10,15 @@ ... fun:g_random_int } +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + fun:g_mutex_impl_new + fun:g_mutex_get_impl + fun:g_mutex_lock + fun:g_main_context_new +} { g_main_context_dispatch @@ -98,7 +107,7 @@ } { - g_static_private_set + <insert_a_suppression_name_here> Memcheck:Leak fun:*alloc ... @@ -106,6 +115,14 @@ } { + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + ... + fun:g_intern_string +} + +{ g_get_language_names Memcheck:Leak fun:*alloc @@ -309,6 +326,29 @@ } { + <insert_a_suppression_name_here> + Memcheck:Leak + fun:malloc + fun:strdup + ... + fun:ao_initialize +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:calloc + fun:ao_initialize +} + +{ + <insert_a_suppression_name_here> + Memcheck:Addr4 + ... + fun:WildMidi_Init +} + +{ g_quark_from_string Memcheck:Leak fun:*alloc |