diff options
994 files changed, 69504 insertions, 62988 deletions
diff --git a/.gitignore b/.gitignore index 11ac5f9a8..05904c1e6 100644 --- a/.gitignore +++ b/.gitignore @@ -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 a0b32f8be..fd4ff377d 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,72 @@ 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 +114,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 +275,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 +352,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 +382,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 +403,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 +467,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 +481,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 +500,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 +520,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 +591,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 +603,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 +627,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 +635,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 +677,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 +695,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 +721,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,20 +777,21 @@ 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 \ @@ -792,25 +800,27 @@ MIXER_LIBS = \ MIXER_API_SRC = \ src/mixer_control.c \ src/mixer_type.c \ - src/mixer_all.c \ + src/MixerAll.cxx src/MixerAll.hxx \ src/mixer_api.c 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 +855,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 +867,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 +890,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 @@ -899,16 +913,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 +942,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 +965,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 +1031,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 +1043,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 +1057,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 +1148,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 +1177,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 +1203,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 +1231,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 +1324,48 @@ 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/OutputInit.cxx src/OutputFinish.cxx src/OutputList.cxx \ + src/OutputPlugin.cxx \ src/mixer_api.c \ src/mixer_control.c \ src/mixer_type.c \ - src/filter_plugin.c \ - src/filter_config.c \ + 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 \ +test_read_mixer_SOURCES = test/read_mixer.cxx \ + src/tokenizer.c src/utils.c src/string_util.c \ src/mixer_control.c src/mixer_api.c \ - src/filter_plugin.c \ - src/filter/volume_filter_plugin.c \ + src/FilterPlugin.cxx \ + src/filter/VolumeFilterPlugin.cxx \ src/fd_util.c if ENABLE_BZIP2_TEST @@ -1283,11 +1382,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 +1398,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 +1427,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) @@ -6,7 +23,6 @@ ver 0.17.4 (2013/??/??) * player: - implement missing "idle" events on output errors - ver 0.17.3 (2013/01/06) * output: - osx: fix pops during playback diff --git a/configure.ac b/configure.ac index 05f288822..192469801 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 38d8a9d85..c83c625a2 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 000000000..4fc433700 --- /dev/null +++ b/m4/ax_append_link_flags.m4 @@ -0,0 +1,61 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_append_link_flags.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_APPEND_LINK_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS]) +# +# DESCRIPTION +# +# For every FLAG1, FLAG2 it is checked whether the linker works with the +# flag. If it does, the flag is added FLAGS-VARIABLE +# +# If FLAGS-VARIABLE is not specified, the linker's flags (LDFLAGS) is +# used. During the check the flag is always added to the linker's flags. +# +# If EXTRA-FLAGS is defined, it is added to the linker's default flags +# when the check is done. The check is thus made with the flags: "LDFLAGS +# EXTRA-FLAGS FLAG". This can for example be used to force the linker to +# issue an error when a bad flag is given. +# +# NOTE: This macro depends on the AX_APPEND_FLAG and AX_CHECK_LINK_FLAG. +# Please keep this macro in sync with AX_APPEND_COMPILE_FLAGS. +# +# LICENSE +# +# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 2 + +AC_DEFUN([AX_APPEND_LINK_FLAGS], +[for flag in $1; do + AX_CHECK_LINK_FLAG([$flag], [AX_APPEND_FLAG([$flag], [m4_default([$2], [LDFLAGS])])], [], [$3]) +done +])dnl AX_APPEND_LINK_FLAGS diff --git a/m4/ax_check_link_flag.m4 b/m4/ax_check_link_flag.m4 new file mode 100644 index 000000000..e2d0d363e --- /dev/null +++ b/m4/ax_check_link_flag.m4 @@ -0,0 +1,71 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_link_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_LINK_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the linker or gives an error. +# (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the linker's default flags +# when the check is done. The check is thus made with the flags: "LDFLAGS +# EXTRA-FLAGS FLAG". This can for example be used to force the linker to +# issue an error when a bad flag is given. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,COMPILE}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de> +# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 2 + +AC_DEFUN([AX_CHECK_LINK_FLAG], +[AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_ldflags_$4_$1])dnl +AC_CACHE_CHECK([whether the linker accepts $1], CACHEVAR, [ + ax_check_save_flags=$LDFLAGS + LDFLAGS="$LDFLAGS $4 $1" + AC_LINK_IFELSE([AC_LANG_PROGRAM()], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + LDFLAGS=$ax_check_save_flags]) +AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes], + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_LINK_FLAGS diff --git a/m4/ax_cxx_compile_stdcxx_0x.m4 b/m4/ax_cxx_compile_stdcxx_0x.m4 new file mode 100644 index 000000000..a4e556ff9 --- /dev/null +++ b/m4/ax_cxx_compile_stdcxx_0x.m4 @@ -0,0 +1,107 @@ +# ============================================================================ +# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_0x.html +# ============================================================================ +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX_0X +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the C++0x +# standard. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 7 + +AU_ALIAS([AC_CXX_COMPILE_STDCXX_0X], [AX_CXX_COMPILE_STDCXX_0X]) +AC_DEFUN([AX_CXX_COMPILE_STDCXX_0X], [ + AC_CACHE_CHECK(if g++ supports C++0x features without additional flags, + ax_cv_cxx_compile_cxx0x_native, + [AC_LANG_SAVE + AC_LANG_CPLUSPLUS + AC_TRY_COMPILE([ + template <typename T> + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + typedef check<check<bool>> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check<int> check_type; + check_type c; + check_type&& cr = static_cast<check_type&&>(c);],, + ax_cv_cxx_compile_cxx0x_native=yes, ax_cv_cxx_compile_cxx0x_native=no) + AC_LANG_RESTORE + ]) + + AC_CACHE_CHECK(if g++ supports C++0x features with -std=c++0x, + ax_cv_cxx_compile_cxx0x_cxx, + [AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -std=c++0x" + AC_TRY_COMPILE([ + template <typename T> + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + typedef check<check<bool>> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check<int> check_type; + check_type c; + check_type&& cr = static_cast<check_type&&>(c);],, + ax_cv_cxx_compile_cxx0x_cxx=yes, ax_cv_cxx_compile_cxx0x_cxx=no) + CXXFLAGS="$ac_save_CXXFLAGS" + AC_LANG_RESTORE + ]) + + AC_CACHE_CHECK(if g++ supports C++0x features with -std=gnu++0x, + ax_cv_cxx_compile_cxx0x_gxx, + [AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -std=gnu++0x" + AC_TRY_COMPILE([ + template <typename T> + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + typedef check<check<bool>> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check<int> check_type; + check_type c; + check_type&& cr = static_cast<check_type&&>(c);],, + ax_cv_cxx_compile_cxx0x_gxx=yes, ax_cv_cxx_compile_cxx0x_gxx=no) + CXXFLAGS="$ac_save_CXXFLAGS" + AC_LANG_RESTORE + ]) + + if test "$ax_cv_cxx_compile_cxx0x_native" = yes || + test "$ax_cv_cxx_compile_cxx0x_cxx" = yes || + test "$ax_cv_cxx_compile_cxx0x_gxx" = yes; then + AC_DEFINE(HAVE_STDCXX_0X,,[Define if g++ supports C++0x features. ]) + fi +]) diff --git a/m4/faad.m4 b/m4/faad.m4 index 1048c566c..5ca520e79 100644 --- a/m4/faad.m4 +++ b/m4/faad.m4 @@ -8,38 +8,14 @@ AC_ARG_ENABLE(aac, [disable AAC support (default: enable)]),, enable_aac=yes) -AC_ARG_WITH(faad, - AS_HELP_STRING([--with-faad=PFX], - [prefix where faad2 is installed (optional)]),, - faad_prefix="") -AC_ARG_WITH(faad-libraries, - AS_HELP_STRING([--with-faad-libraries=DIR], - [directory where faad2 library is installed (optional)]),, - faad_libraries="") -AC_ARG_WITH(faad-includes, - AS_HELP_STRING([--with-faad-includes=DIR], - [directory where faad2 header files are installed (optional)]),, - faad_includes="") - if test x$enable_aac = xyes; then - if test "x$faad_libraries" != "x" ; then - FAAD_LIBS="-L$faad_libraries" - elif test "x$faad_prefix" != "x" ; then - FAAD_LIBS="-L$faad_prefix/lib" - fi - - FAAD_LIBS="$FAAD_LIBS -lfaad" - - if test "x$faad_includes" != "x" ; then - FAAD_CFLAGS="-I$faad_includes" - elif test "x$faad_prefix" != "x" ; then - FAAD_CFLAGS="-I$faad_prefix/include" - fi + FAAD_LIBS="-lfaad" + FAAD_CFLAGS="" oldcflags=$CFLAGS oldlibs=$LIBS oldcppflags=$CPPFLAGS - CFLAGS="$CFLAGS $FAAD_CFLAGS -I." + CFLAGS="$CFLAGS $FAAD_CFLAGS" LIBS="$LIBS $FAAD_LIBS" CPPFLAGS=$CFLAGS AC_CHECK_HEADER(faad.h,,enable_aac=no) @@ -47,77 +23,36 @@ if test x$enable_aac = xyes; then AC_CHECK_DECL(FAAD2_VERSION,,enable_aac=no,[#include <faad.h>]) fi if test x$enable_aac = xyes; then - AC_CHECK_DECL(faacDecInit2,,enable_aac=no,[#include <faad.h>]) - fi - if test x$enable_aac = xyes; then - AC_CHECK_LIB(faad,faacDecInit2,,enable_aac=no) - if test x$enable_aac = xno; then - enable_aac=yes - AC_CHECK_LIB(faad,NeAACDecInit2,,enable_aac=no) - fi + AC_CHECK_LIB(faad,NeAACDecInit2,,enable_aac=no) fi if test x$enable_aac = xyes; then - AC_MSG_CHECKING(that FAAD2 uses buffer and bufferlen) - AC_COMPILE_IFELSE([AC_LANG_SOURCE([ -#include <faad.h> - -int main() { - char buffer; - long bufferlen = 0; - faacDecHandle decoder; - faacDecFrameInfo frameInfo; - faacDecConfigurationPtr config; - unsigned char channels; - long sampleRate; - mp4AudioSpecificConfig mp4ASC; - - decoder = faacDecOpen(); - config = faacDecGetCurrentConfiguration(decoder); - config->outputFormat = FAAD_FMT_16BIT; - faacDecSetConfiguration(decoder,config); - AudioSpecificConfig(&buffer, bufferlen, &mp4ASC); - faacDecInit(decoder,&buffer,bufferlen,&sampleRate,&channels); - faacDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels); - faacDecDecode(decoder,&frameInfo,&buffer,bufferlen); - - return 0; -} -])],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no); AC_MSG_CHECKING(that FAAD2 can even be used) AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include <faad.h> int main() { char buffer; - faacDecHandle decoder; - faacDecFrameInfo frameInfo; - faacDecConfigurationPtr config; + NeAACDecHandle decoder; + NeAACDecFrameInfo frameInfo; + NeAACDecConfigurationPtr config; unsigned char channels; long sampleRate; long bufferlen = 0; - unsigned long dummy1_32; - unsigned char dummy2_8, dummy3_8, dummy4_8, dummy5_8, dummy6_8, - dummy7_8, dummy8_8; - decoder = faacDecOpen(); - config = faacDecGetCurrentConfiguration(decoder); + decoder = NeAACDecOpen(); + config = NeAACDecGetCurrentConfiguration(decoder); config->outputFormat = FAAD_FMT_16BIT; - faacDecSetConfiguration(decoder,config); - AudioSpecificConfig(&buffer,&dummy1_32,&dummy2_8, - &dummy3_8,&dummy4_8,&dummy5_8, - &dummy6_8,&dummy7_8,&dummy8_8); - faacDecInit(decoder,&buffer,&sampleRate,&channels); - faacDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels); - faacDecDecode(decoder,&frameInfo,&buffer); - faacDecClose(decoder); + NeAACDecSetConfiguration(decoder,config); + NeAACDecInit(decoder,&buffer,bufferlen,&sampleRate,&channels); + NeAACDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels); + NeAACDecDecode(decoder,&frameInfo,&buffer,bufferlen); + NeAACDecClose(decoder); return 0; } ])],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no]) - ]) fi if test x$enable_aac = xyes; then - AC_CHECK_MEMBERS([faacDecConfiguration.downMatrix,faacDecConfiguration.dontUpSampleImplicitSBR,faacDecFrameInfo.samplerate],,,[#include <faad.h>]) AC_DEFINE(HAVE_FAAD,1,[Define to use FAAD2 for AAC decoding]) else AC_MSG_WARN([faad2 lib needed for MP4/AAC support -- disabling MP4/AAC support]) @@ -145,7 +80,7 @@ int main() { unsigned char channels; uint32_t sample_rate; - faacDecInit2(NULL, NULL, 0, &sample_rate, &channels); + NeAACDecInit2(NULL, NULL, 0, &sample_rate, &channels); return 0; } ])], @@ -156,40 +91,9 @@ int main() { CFLAGS=$oldcflags LIBS=$oldlibs CPPFLAGS=$oldcppflags -fi - -if test x$enable_aac = xyes; then - enable_mp4=yes - MP4FF_LIBS="-lmp4ff" - - oldcflags=$CFLAGS - oldlibs=$LIBS - oldcppflags=$CPPFLAGS - CFLAGS="$CFLAGS $FAAD_CFLAGS" - LIBS="$LIBS $FAAD_LIBS $MP4FF_LIBS" - CPPFLAGS=$CFLAGS - - AC_CHECK_HEADER(mp4ff.h,,enable_mp4=no) - - if test x$enable_mp4 = xyes; then - AC_CHECK_LIB(mp4ff,mp4ff_open_read,,enable_mp4=no) - fi - - if test x$enable_mp4 = xyes; then - AC_SUBST(MP4FF_LIBS) - AC_DEFINE(HAVE_MP4, 1, [Define to use FAAD2+mp4ff for MP4 decoding]) - else - AC_MSG_WARN([libmp4ff needed for MP4 support -- disabling MP4 support]) - unset MP4FF_LIBS - fi - - CFLAGS=$oldcflags - LIBS=$oldlibs - CPPFLAGS=$oldcppflags else - enable_mp4=no - FAAD_CFLAGS="" FAAD_LIBS="" + FAAD_CFLAGS="" fi AC_SUBST(FAAD_CFLAGS) diff --git a/src/AllCommands.cxx b/src/AllCommands.cxx new file mode 100644 index 000000000..58dcf4dba --- /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/AllCommands.hxx b/src/AllCommands.hxx new file mode 100644 index 000000000..a55eb5a3b --- /dev/null +++ b/src/AllCommands.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ALL_COMMANDS_HXX +#define MPD_ALL_COMMANDS_HXX + +#include "command.h" + +class Client; + +void command_init(void); + +void command_finish(void); + +enum command_return +command_process(Client *client, unsigned num, char *line); + +#endif diff --git a/src/ArchiveFile.hxx b/src/ArchiveFile.hxx new file mode 100644 index 000000000..c7933ebd1 --- /dev/null +++ b/src/ArchiveFile.hxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_FILE_HXX +#define MPD_ARCHIVE_FILE_HXX + +class ArchiveFile { +public: + const struct archive_plugin &plugin; + + ArchiveFile(const struct archive_plugin &_plugin) + :plugin(_plugin) {} + +protected: + /** + * Use Close() instead of delete. + */ + ~ArchiveFile() {} + +public: + virtual void Close() = 0; + + /** + * Visit all entries inside this archive. + */ + virtual void Visit(ArchiveVisitor &visitor) = 0; + + /** + * Opens an input_stream of a file within the archive. + * + * @param path the path within the archive + * @param error_r location to store the error occurring, or + * NULL to ignore errors + */ + virtual input_stream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + GError **error_r) = 0; +}; + +#endif diff --git a/src/ArchiveList.cxx b/src/ArchiveList.cxx new file mode 100644 index 000000000..02b19ce79 --- /dev/null +++ b/src/ArchiveList.cxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ArchiveList.hxx" +#include "ArchivePlugin.hxx" +#include "string_util.h" +#include "archive/Bzip2ArchivePlugin.hxx" +#include "archive/Iso9660ArchivePlugin.hxx" +#include "archive/ZzipArchivePlugin.hxx" + +#include <string.h> +#include <glib.h> + +const struct archive_plugin *const archive_plugins[] = { +#ifdef HAVE_BZ2 + &bz2_archive_plugin, +#endif +#ifdef HAVE_ZZIP + &zzip_archive_plugin, +#endif +#ifdef HAVE_ISO9660 + &iso9660_archive_plugin, +#endif + NULL +}; + +/** which plugins have been initialized successfully? */ +static bool archive_plugins_enabled[G_N_ELEMENTS(archive_plugins) - 1]; + +#define archive_plugins_for_each_enabled(plugin) \ + archive_plugins_for_each(plugin) \ + if (archive_plugins_enabled[archive_plugin_iterator - archive_plugins]) + +const struct archive_plugin * +archive_plugin_from_suffix(const char *suffix) +{ + if (suffix == NULL) + return NULL; + + archive_plugins_for_each_enabled(plugin) + if (plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) + return plugin; + + return NULL; +} + +const struct archive_plugin * +archive_plugin_from_name(const char *name) +{ + archive_plugins_for_each_enabled(plugin) + if (strcmp(plugin->name, name) == 0) + return plugin; + + return NULL; +} + +void archive_plugin_init_all(void) +{ + for (unsigned i = 0; archive_plugins[i] != NULL; ++i) { + const struct archive_plugin *plugin = archive_plugins[i]; + if (plugin->init == NULL || archive_plugins[i]->init()) + archive_plugins_enabled[i] = true; + } +} + +void archive_plugin_deinit_all(void) +{ + archive_plugins_for_each_enabled(plugin) + if (plugin->finish != NULL) + plugin->finish(); +} + diff --git a/src/ArchiveList.hxx b/src/ArchiveList.hxx new file mode 100644 index 000000000..057c351de --- /dev/null +++ b/src/ArchiveList.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_LIST_HXX +#define MPD_ARCHIVE_LIST_HXX + +struct archive_plugin; + +extern const struct archive_plugin *const archive_plugins[]; + +#define archive_plugins_for_each(plugin) \ + for (const struct archive_plugin *plugin, \ + *const*archive_plugin_iterator = &archive_plugins[0]; \ + (plugin = *archive_plugin_iterator) != NULL; \ + ++archive_plugin_iterator) + +/* interface for using plugins */ + +const struct archive_plugin * +archive_plugin_from_suffix(const char *suffix); + +const struct archive_plugin * +archive_plugin_from_name(const char *name); + +/* this is where we "load" all the "plugins" ;-) */ +void archive_plugin_init_all(void); + +/* this is where we "unload" all the "plugins" */ +void archive_plugin_deinit_all(void); + +#endif diff --git a/src/ArchiveLookup.cxx b/src/ArchiveLookup.cxx new file mode 100644 index 000000000..747f5c7e5 --- /dev/null +++ b/src/ArchiveLookup.cxx @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "ArchiveLookup.hxx" + +#include <stdio.h> + +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <glib.h> + +/** + * + * archive_lookup is used to determine if part of pathname refers to an regular + * file (archive). If so then its also used to split pathname into archive file + * and path used to locate file in archive. It also returns suffix of the file. + * How it works: + * We do stat of the parent of input pathname as long as we find an regular file + * Normally this should never happen. When routine returns true pathname modified + * and split into archive, inpath and suffix. Otherwise nothing happens + * + * For example: + * + * /music/path/Talco.zip/Talco - Combat Circus/12 - A la pachenka.mp3 + * is split into archive: /music/path/Talco.zip + * inarchive pathname: Talco - Combat Circus/12 - A la pachenka.mp3 + * and suffix: zip + */ + +bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix) +{ + char *pathdupe; + int len, idx; + struct stat st_info; + bool ret = false; + + *archive = NULL; + *inpath = NULL; + *suffix = NULL; + + pathdupe = g_strdup(pathname); + len = idx = strlen(pathname); + + while (idx > 0) { + //try to stat if its real directory + if (stat(pathdupe, &st_info) == -1) { + if (errno != ENOTDIR) { + g_warning("stat %s failed (errno=%d)\n", pathdupe, errno); + break; + } + } else { + //is something found ins original path (is not an archive) + if (idx == len) { + break; + } + //its a file ? + if (S_ISREG(st_info.st_mode)) { + //so the upper should be file + pathname[idx] = 0; + ret = true; + *archive = pathname; + *inpath = pathname + idx+1; + + //try to get suffix + *suffix = NULL; + while (idx > 0) { + if (pathname[idx] == '.') { + *suffix = pathname + idx + 1; + break; + } + idx--; + } + break; + } else { + g_warning("not a regular file %s\n", pathdupe); + break; + } + } + //find one dir up + while (idx > 0) { + if (pathdupe[idx] == '/') { + pathdupe[idx] = 0; + break; + } + idx--; + } + } + g_free(pathdupe); + return ret; +} + diff --git a/src/ArchiveLookup.hxx b/src/ArchiveLookup.hxx new file mode 100644 index 000000000..6e7669cb0 --- /dev/null +++ b/src/ArchiveLookup.hxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_LOOKUP_HXX +#define MPD_ARCHIVE_LOOKUP_HXX + +/* + * This is the public API which is used by archive plugins to + * provide transparent archive decompression layer for mpd + * + */ + +bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix); + +#endif + diff --git a/src/ArchivePlugin.cxx b/src/ArchivePlugin.cxx new file mode 100644 index 000000000..7c5164220 --- /dev/null +++ b/src/ArchivePlugin.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" + +#include <assert.h> + +ArchiveFile * +archive_file_open(const struct archive_plugin *plugin, const char *path, + GError **error_r) +{ + assert(plugin != NULL); + assert(plugin->open != NULL); + assert(path != NULL); + assert(error_r == NULL || *error_r == NULL); + + ArchiveFile *file = plugin->open(path, error_r); + + if (file != NULL) { + assert(error_r == NULL || *error_r == NULL); + } else { + assert(error_r == NULL || *error_r != NULL); + } + + return file; +} diff --git a/src/ArchivePlugin.hxx b/src/ArchivePlugin.hxx new file mode 100644 index 000000000..13952940f --- /dev/null +++ b/src/ArchivePlugin.hxx @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_PLUGIN_HXX +#define MPD_ARCHIVE_PLUGIN_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "gerror.h" + +struct input_stream; +class ArchiveFile; +class ArchiveVisitor; + +struct archive_plugin { + const char *name; + + /** + * optional, set this to NULL if the archive plugin doesn't + * have/need one this must false if there is an error and + * true otherwise + */ + bool (*init)(void); + + /** + * optional, set this to NULL if the archive plugin doesn't + * have/need one + */ + void (*finish)(void); + + /** + * tryes to open archive file and associates handle with archive + * returns pointer to handle used is all operations with this archive + * or NULL when opening fails + */ + ArchiveFile *(*open)(const char *path_fs, GError **error_r); + + /** + * suffixes handled by this plugin. + * last element in these arrays must always be a NULL + */ + const char *const*suffixes; +}; + +ArchiveFile * +archive_file_open(const struct archive_plugin *plugin, const char *path, + GError **error_r); + +#endif diff --git a/src/ArchiveVisitor.hxx b/src/ArchiveVisitor.hxx new file mode 100644 index 000000000..e951cb5e9 --- /dev/null +++ b/src/ArchiveVisitor.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_VISITOR_HXX +#define MPD_ARCHIVE_VISITOR_HXX + +class ArchiveVisitor { +public: + virtual void VisitArchiveEntry(const char *path_utf8) = 0; +}; + +#endif diff --git a/src/AudioCompress/compress.h b/src/AudioCompress/compress.h index 073d4af9a..8556d16be 100644 --- a/src/AudioCompress/compress.h +++ b/src/AudioCompress/compress.h @@ -19,6 +19,10 @@ struct CompressorConfig { struct Compressor; +#ifdef __cplusplus +extern "C" { +#endif + //! Create a new compressor (use history value of 0 for default) struct Compressor *Compressor_new(unsigned int history); @@ -34,7 +38,12 @@ struct CompressorConfig *Compressor_getConfig(struct Compressor *); //! Process 16-bit signed data void Compressor_Process_int16(struct Compressor *, int16_t *data, unsigned int count); +#ifdef __cplusplus +} +#endif + //! TODO: Compressor_Process_int32, Compressor_Process_float, others as needed //! TODO: functions for getting at the peak/gain/clip history buffers (for monitoring) + #endif diff --git a/src/AudioConfig.cxx b/src/AudioConfig.cxx new file mode 100644 index 000000000..e546aed27 --- /dev/null +++ b/src/AudioConfig.cxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "AudioConfig.hxx" +#include "audio_format.h" +#include "AudioParser.hxx" +#include "conf.h" +#include "mpd_error.h" + +static struct audio_format configured_audio_format; + +void getOutputAudioFormat(const struct audio_format *inAudioFormat, + struct audio_format *outAudioFormat) +{ + *outAudioFormat = *inAudioFormat; + audio_format_mask_apply(outAudioFormat, &configured_audio_format); +} + +void initAudioConfig(void) +{ + const struct config_param *param = config_get_param(CONF_AUDIO_OUTPUT_FORMAT); + GError *error = NULL; + bool ret; + + if (param == NULL) + return; + + ret = audio_format_parse(&configured_audio_format, param->value, + true, &error); + if (!ret) + MPD_ERROR("error parsing line %i: %s", + param->line, error->message); +} diff --git a/src/AudioConfig.hxx b/src/AudioConfig.hxx new file mode 100644 index 000000000..717a8e2e7 --- /dev/null +++ b/src/AudioConfig.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_AUDIO_CONFIG_HXX +#define MPD_AUDIO_CONFIG_HXX + +struct audio_format; + +void getOutputAudioFormat(const struct audio_format *inFormat, + struct audio_format *outFormat); + +/* make sure initPlayerData is called before this function!! */ +void initAudioConfig(void); + +#endif diff --git a/src/AudioParser.cxx b/src/AudioParser.cxx new file mode 100644 index 000000000..9178c3e1a --- /dev/null +++ b/src/AudioParser.cxx @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Parser functions for audio related objects. + * + */ + +#include "config.h" +#include "AudioParser.hxx" +#include "audio_format.h" +#include "audio_check.h" +#include "gcc.h" + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +/** + * The GLib quark used for errors reported by this library. + */ +static inline GQuark +audio_parser_quark(void) +{ + return g_quark_from_static_string("audio_parser"); +} + +static bool +parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r, + const char **endptr_r, GError **error_r) +{ + unsigned long value; + char *endptr; + + if (mask && *src == '*') { + *sample_rate_r = 0; + *endptr_r = src + 1; + return true; + } + + value = strtoul(src, &endptr, 10); + if (endptr == src) { + g_set_error(error_r, audio_parser_quark(), 0, + "Failed to parse the sample rate"); + return false; + } else if (!audio_check_sample_rate(value, error_r)) + return false; + + *sample_rate_r = value; + *endptr_r = endptr; + return true; +} + +static bool +parse_sample_format(const char *src, bool mask, + enum sample_format *sample_format_r, + const char **endptr_r, GError **error_r) +{ + unsigned long value; + char *endptr; + enum sample_format sample_format; + + if (mask && *src == '*') { + *sample_format_r = SAMPLE_FORMAT_UNDEFINED; + *endptr_r = src + 1; + return true; + } + + if (*src == 'f') { + *sample_format_r = SAMPLE_FORMAT_FLOAT; + *endptr_r = src + 1; + return true; + } + + if (memcmp(src, "dsd", 3) == 0) { + *sample_format_r = SAMPLE_FORMAT_DSD; + *endptr_r = src + 3; + return true; + } + + value = strtoul(src, &endptr, 10); + if (endptr == src) { + g_set_error(error_r, audio_parser_quark(), 0, + "Failed to parse the sample format"); + return false; + } + + switch (value) { + case 8: + sample_format = SAMPLE_FORMAT_S8; + break; + + case 16: + sample_format = SAMPLE_FORMAT_S16; + break; + + case 24: + if (memcmp(endptr, "_3", 2) == 0) + /* for backwards compatibility */ + endptr += 2; + + sample_format = SAMPLE_FORMAT_S24_P32; + break; + + case 32: + sample_format = SAMPLE_FORMAT_S32; + break; + + default: + g_set_error(error_r, audio_parser_quark(), 0, + "Invalid sample format: %lu", value); + return false; + } + + assert(audio_valid_sample_format(sample_format)); + + *sample_format_r = sample_format; + *endptr_r = endptr; + return true; +} + +static bool +parse_channel_count(const char *src, bool mask, uint8_t *channels_r, + const char **endptr_r, GError **error_r) +{ + unsigned long value; + char *endptr; + + if (mask && *src == '*') { + *channels_r = 0; + *endptr_r = src + 1; + return true; + } + + value = strtoul(src, &endptr, 10); + if (endptr == src) { + g_set_error(error_r, audio_parser_quark(), 0, + "Failed to parse the channel count"); + return false; + } else if (!audio_check_channel_count(value, error_r)) + return false; + + *channels_r = value; + *endptr_r = endptr; + return true; +} + +bool +audio_format_parse(struct audio_format *dest, const char *src, + bool mask, GError **error_r) +{ + uint32_t rate; + enum sample_format sample_format; + uint8_t channels; + + audio_format_clear(dest); + + /* parse sample rate */ + +#if GCC_CHECK_VERSION(4,7) + /* workaround -Wmaybe-uninitialized false positive */ + rate = 0; +#endif + + if (!parse_sample_rate(src, mask, &rate, &src, error_r)) + return false; + + if (*src++ != ':') { + g_set_error(error_r, audio_parser_quark(), 0, + "Sample format missing"); + return false; + } + + /* parse sample format */ + +#if GCC_CHECK_VERSION(4,7) + /* workaround -Wmaybe-uninitialized false positive */ + sample_format = SAMPLE_FORMAT_UNDEFINED; +#endif + + if (!parse_sample_format(src, mask, &sample_format, &src, error_r)) + return false; + + if (*src++ != ':') { + g_set_error(error_r, audio_parser_quark(), 0, + "Channel count missing"); + return false; + } + + /* parse channel count */ + + if (!parse_channel_count(src, mask, &channels, &src, error_r)) + return false; + + if (*src != 0) { + g_set_error(error_r, audio_parser_quark(), 0, + "Extra data after channel count: %s", src); + return false; + } + + audio_format_init(dest, rate, sample_format, channels); + assert(mask ? audio_format_mask_valid(dest) + : audio_format_valid(dest)); + + return true; +} diff --git a/src/AudioParser.hxx b/src/AudioParser.hxx new file mode 100644 index 000000000..f7855e8e3 --- /dev/null +++ b/src/AudioParser.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Parser functions for audio related objects. + */ + +#ifndef MPD_AUDIO_PARSER_HXX +#define MPD_AUDIO_PARSER_HXX + +#include "gerror.h" + +struct audio_format; + +/** + * Parses a string in the form "SAMPLE_RATE:BITS:CHANNELS" into an + * #audio_format. + * + * @param dest the destination #audio_format struct + * @param src the input string + * @param mask if true, then "*" is allowed for any number of items + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return true on success + */ +bool +audio_format_parse(struct audio_format *dest, const char *src, + bool mask, GError **error_r); + +#endif diff --git a/src/Client.cxx b/src/Client.cxx new file mode 100644 index 000000000..be121dfe8 --- /dev/null +++ b/src/Client.cxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" + +int client_get_uid(const Client *client) +{ + return client->uid; +} + +unsigned client_get_permission(const Client *client) +{ + return client->permission; +} + +void client_set_permission(Client *client, unsigned permission) +{ + client->permission = permission; +} diff --git a/src/Client.hxx b/src/Client.hxx new file mode 100644 index 000000000..1456f1b7d --- /dev/null +++ b/src/Client.hxx @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_H +#define MPD_CLIENT_H + +#include "gcc.h" + +#include <stddef.h> +#include <stdarg.h> + +struct sockaddr; +class EventLoop; +struct Partition; +class Client; + +void client_manager_init(void); + +void +client_new(EventLoop &loop, Partition &partition, + int fd, const struct sockaddr *sa, size_t sa_length, int uid); + +/** + * returns the uid of the client process, or a negative value if the + * uid is unknown + */ +gcc_pure +int client_get_uid(const Client *client); + +/** + * Is this client running on the same machine, connected with a local + * (UNIX domain) socket? + */ +gcc_pure +static inline bool +client_is_local(const Client *client) +{ + return client_get_uid(client) > 0; +} + +gcc_pure +unsigned client_get_permission(const Client *client); + +void client_set_permission(Client *client, unsigned permission); + +/** + * Write a C string to the client. + */ +void client_puts(Client *client, const char *s); + +/** + * Write a printf-like formatted string to the client. + */ +void client_vprintf(Client *client, const char *fmt, va_list args); + +/** + * Write a printf-like formatted string to the client. + */ +gcc_fprintf +void +client_printf(Client *client, const char *fmt, ...); + +#endif diff --git a/src/ClientEvent.cxx b/src/ClientEvent.cxx new file mode 100644 index 000000000..905cf0c0a --- /dev/null +++ b/src/ClientEvent.cxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" + +void +Client::OnSocketError(GError *error) +{ + g_warning("error on client %d: %s", num, error->message); + g_error_free(error); + + SetExpired(); +} + +void +Client::OnSocketClosed() +{ + SetExpired(); +} diff --git a/src/ClientExpire.cxx b/src/ClientExpire.cxx new file mode 100644 index 000000000..6bb0a43ae --- /dev/null +++ b/src/ClientExpire.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 "ClientInternal.hxx" + +void +Client::SetExpired() +{ + if (IsExpired()) + return; + + FullyBufferedSocket::Close(); + TimeoutMonitor::Schedule(0); +} + +bool +Client::OnTimeout() +{ + if (!IsExpired()) { + assert(!idle_waiting); + g_debug("[%u] timeout", num); + } + + Close(); + return false; +} diff --git a/src/ClientFile.cxx b/src/ClientFile.cxx new file mode 100644 index 000000000..ca5acb229 --- /dev/null +++ b/src/ClientFile.cxx @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ClientFile.hxx" +#include "Client.hxx" +#include "ack.h" +#include "io_error.h" + +#include <sys/stat.h> +#include <sys/types.h> +#include <errno.h> +#include <unistd.h> + +bool +client_allow_file(const Client *client, const char *path_fs, + GError **error_r) +{ +#ifdef WIN32 + (void)client; + (void)path_fs; + + g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION, + "Access denied"); + return false; +#else + const int uid = client_get_uid(client); + if (uid >= 0 && (uid_t)uid == geteuid()) + /* always allow access if user runs his own MPD + instance */ + return true; + + if (uid <= 0) { + /* unauthenticated client */ + g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION, + "Access denied"); + return false; + } + + struct stat st; + if (stat(path_fs, &st) < 0) { + set_error_errno(error_r); + return false; + } + + if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) { + /* client is not owner */ + g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION, + "Access denied"); + return false; + } + + return true; +#endif +} diff --git a/src/ClientFile.hxx b/src/ClientFile.hxx new file mode 100644 index 000000000..48e00c44f --- /dev/null +++ b/src/ClientFile.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_FILE_HXX +#define MPD_CLIENT_FILE_HXX + +#include "gerror.h" + +#include <stdbool.h> + +class Client; + +/** + * Is this client allowed to use the specified local file? + * + * Note that this function is vulnerable to timing/symlink attacks. + * We cannot fix this as long as there are plugins that open a file by + * its name, and not by file descriptor / callbacks. + * + * @param path_fs the absolute path name in filesystem encoding + * @return true if access is allowed + */ +bool +client_allow_file(const Client *client, const char *path_fs, + GError **error_r); + +#endif diff --git a/src/ClientGlobal.cxx b/src/ClientGlobal.cxx new file mode 100644 index 000000000..6115a7856 --- /dev/null +++ b/src/ClientGlobal.cxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" +#include "ClientList.hxx" +#include "conf.h" + +#include <assert.h> + +#define CLIENT_TIMEOUT_DEFAULT (60) +#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024) +#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024) + +int client_timeout; +size_t client_max_command_list_size; +size_t client_max_output_buffer_size; + +void client_manager_init(void) +{ + client_timeout = config_get_positive(CONF_CONN_TIMEOUT, + CLIENT_TIMEOUT_DEFAULT); + client_max_command_list_size = + config_get_positive(CONF_MAX_COMMAND_LIST_SIZE, + CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024) + * 1024; + + client_max_output_buffer_size = + config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE, + CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024) + * 1024; +} diff --git a/src/ClientIdle.cxx b/src/ClientIdle.cxx new file mode 100644 index 000000000..714438123 --- /dev/null +++ b/src/ClientIdle.cxx @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" +#include "Idle.hxx" + +#include <assert.h> + +void +Client::IdleNotify() +{ + assert(idle_waiting); + assert(idle_flags != 0); + + unsigned flags = idle_flags; + idle_flags = 0; + idle_waiting = false; + + const char *const*idle_names = idle_get_names(); + for (unsigned i = 0; idle_names[i]; ++i) { + if (flags & (1 << i) & idle_subscriptions) + client_printf(this, "changed: %s\n", + idle_names[i]); + } + + client_puts(this, "OK\n"); + + TimeoutMonitor::ScheduleSeconds(client_timeout); +} + +void +Client::IdleAdd(unsigned flags) +{ + if (IsExpired()) + return; + + idle_flags |= flags; + if (idle_waiting && (idle_flags & idle_subscriptions)) + IdleNotify(); +} + +bool +Client::IdleWait(unsigned flags) +{ + assert(!idle_waiting); + + idle_waiting = true; + idle_subscriptions = flags; + + if (idle_flags & idle_subscriptions) { + IdleNotify(); + return true; + } else { + /* disable timeouts while in "idle" */ + TimeoutMonitor::Cancel(); + return false; + } +} diff --git a/src/ClientInternal.hxx b/src/ClientInternal.hxx new file mode 100644 index 000000000..c31118680 --- /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 000000000..37e6f1289 --- /dev/null +++ b/src/ClientList.cxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientList.hxx" +#include "ClientInternal.hxx" + +#include <algorithm> + +#include <assert.h> + +void +ClientList::Remove(Client &client) +{ + assert(size > 0); + assert(!list.empty()); + + auto i = std::find(list.begin(), list.end(), &client); + assert(i != list.end()); + list.erase(i); + --size; +} + +void +ClientList::CloseAll() +{ + while (!list.empty()) + list.front()->Close(); + + assert(size == 0); +} + +void +ClientList::IdleAdd(unsigned flags) +{ + assert(flags != 0); + + for (const auto &client : list) + client->IdleAdd(flags); +} diff --git a/src/ClientList.hxx b/src/ClientList.hxx new file mode 100644 index 000000000..e8560af78 --- /dev/null +++ b/src/ClientList.hxx @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_LIST_HXX +#define MPD_CLIENT_LIST_HXX + +#include <list> + +class Client; + +class ClientList { + const unsigned max_size; + + unsigned size; + std::list<Client *> list; + +public: + ClientList(unsigned _max_size) + :max_size(_max_size), size(0) {} + + std::list<Client *>::iterator begin() { + return list.begin(); + } + + std::list<Client *>::iterator end() { + return list.end(); + } + + bool IsFull() const { + return size >= max_size; + } + + void Add(Client &client) { + list.push_front(&client); + ++size; + } + + void Remove(Client &client); + + void CloseAll(); + + void IdleAdd(unsigned flags); +}; + +#endif diff --git a/src/ClientMessage.cxx b/src/ClientMessage.cxx new file mode 100644 index 000000000..619964b3c --- /dev/null +++ b/src/ClientMessage.cxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ClientMessage.hxx" + +#include <glib.h> + +G_GNUC_PURE +static bool +valid_channel_char(const char ch) +{ + return g_ascii_isalnum(ch) || + ch == '_' || ch == '-' || ch == '.' || ch == ':'; +} + +bool +client_message_valid_channel_name(const char *name) +{ + do { + if (!valid_channel_char(*name)) + return false; + } while (*++name != 0); + + return true; +} diff --git a/src/ClientMessage.hxx b/src/ClientMessage.hxx new file mode 100644 index 000000000..2a929d445 --- /dev/null +++ b/src/ClientMessage.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_MESSAGE_H +#define MPD_CLIENT_MESSAGE_H + +#include "gcc.h" + +#include <string> + +/** + * A client-to-client message. + */ +class ClientMessage { + std::string channel, message; + +public: + template<typename T, typename U> + ClientMessage(T &&_channel, U &&_message) + :channel(std::forward<T>(_channel)), + message(std::forward<U>(_message)) {} + + const char *GetChannel() const { + return channel.c_str(); + } + + const char *GetMessage() const { + return message.c_str(); + } +}; + +gcc_pure +bool +client_message_valid_channel_name(const char *name); + +#endif diff --git a/src/ClientNew.cxx b/src/ClientNew.cxx new file mode 100644 index 000000000..a416c1f83 --- /dev/null +++ b/src/ClientNew.cxx @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" +#include "ClientList.hxx" +#include "Partition.hxx" +#include "Main.hxx" +#include "fd_util.h" +extern "C" { +#include "resolver.h" +} +#include "Permission.hxx" + +#include <assert.h> +#include <sys/types.h> +#ifdef WIN32 +#include <winsock2.h> +#else +#include <sys/socket.h> +#endif +#include <unistd.h> + +#ifdef HAVE_LIBWRAP +#include <tcpd.h> +#endif + + +#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO + +static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n"; + +Client::Client(EventLoop &_loop, Partition &_partition, + int _fd, int _uid, int _num) + :FullyBufferedSocket(_fd, _loop, 16384, client_max_output_buffer_size), + TimeoutMonitor(_loop), + partition(_partition), + playlist(partition.playlist), player_control(&partition.pc), + permission(getDefaultPermissions()), + uid(_uid), + num(_num), + idle_waiting(false), idle_flags(0), + num_subscriptions(0) +{ + TimeoutMonitor::ScheduleSeconds(client_timeout); +} + +void +client_new(EventLoop &loop, Partition &partition, + int fd, const struct sockaddr *sa, size_t sa_length, int uid) +{ + static unsigned int next_client_num; + char *remote; + + assert(fd >= 0); + +#ifdef HAVE_LIBWRAP + if (sa->sa_family != AF_UNIX) { + char *hostaddr = sockaddr_to_string(sa, sa_length, NULL); + const char *progname = g_get_prgname(); + + struct request_info req; + request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); + + fromhost(&req); + + if (!hosts_access(&req)) { + /* tcp wrappers says no */ + g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, + "libwrap refused connection (libwrap=%s) from %s", + progname, hostaddr); + + g_free(hostaddr); + close_socket(fd); + return; + } + + g_free(hostaddr); + } +#endif /* HAVE_WRAP */ + + if (client_list->IsFull()) { + g_warning("Max Connections Reached!"); + close_socket(fd); + return; + } + + Client *client = new Client(loop, partition, fd, uid, + next_client_num++); + + (void)send(fd, GREETING, sizeof(GREETING) - 1, 0); + + client_list->Add(*client); + + remote = sockaddr_to_string(sa, sa_length, NULL); + g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, + "[%u] opened from %s", client->num, remote); + g_free(remote); +} + +void +Client::Close() +{ + client_list->Remove(*this); + + SetExpired(); + + g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, "[%u] closed", num); + delete this; +} diff --git a/src/ClientProcess.cxx b/src/ClientProcess.cxx new file mode 100644 index 000000000..bcd20d1b7 --- /dev/null +++ b/src/ClientProcess.cxx @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" +#include "protocol/Result.hxx" +#include "AllCommands.hxx" + +#include <string.h> + +#define CLIENT_LIST_MODE_BEGIN "command_list_begin" +#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin" +#define CLIENT_LIST_MODE_END "command_list_end" + +static enum command_return +client_process_command_list(Client *client, bool list_ok, + std::list<std::string> &&list) +{ + enum command_return ret = COMMAND_RETURN_OK; + unsigned num = 0; + + for (auto &&i : list) { + char *cmd = &*i.begin(); + + g_debug("command_process_list: process command \"%s\"", + cmd); + ret = command_process(client, num++, cmd); + g_debug("command_process_list: command returned %i", ret); + if (ret != COMMAND_RETURN_OK || client->IsExpired()) + break; + else if (list_ok) + client_puts(client, "list_OK\n"); + } + + return ret; +} + +enum command_return +client_process_line(Client *client, char *line) +{ + enum command_return ret; + + if (strcmp(line, "noidle") == 0) { + if (client->idle_waiting) { + /* send empty idle response and leave idle mode */ + client->idle_waiting = false; + command_success(client); + } + + /* do nothing if the client wasn't idling: the client + has already received the full idle response from + client_idle_notify(), which he can now evaluate */ + + return COMMAND_RETURN_OK; + } else if (client->idle_waiting) { + /* during idle mode, clients must not send anything + except "noidle" */ + g_warning("[%u] command \"%s\" during idle", + client->num, line); + return COMMAND_RETURN_CLOSE; + } + + if (client->cmd_list.IsActive()) { + if (strcmp(line, CLIENT_LIST_MODE_END) == 0) { + g_debug("[%u] process command list", + client->num); + + auto &&cmd_list = client->cmd_list.Commit(); + + ret = client_process_command_list(client, + client->cmd_list.IsOKMode(), + std::move(cmd_list)); + g_debug("[%u] process command " + "list returned %i", client->num, ret); + + if (ret == COMMAND_RETURN_CLOSE || + client->IsExpired()) + return COMMAND_RETURN_CLOSE; + + if (ret == COMMAND_RETURN_OK) + command_success(client); + + client->cmd_list.Reset(); + } else { + if (!client->cmd_list.Add(line)) { + g_warning("[%u] command list size " + "is larger than the max (%lu)", + client->num, + (unsigned long)client_max_command_list_size); + return COMMAND_RETURN_CLOSE; + } + + ret = COMMAND_RETURN_OK; + } + } else { + if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) { + client->cmd_list.Begin(false); + ret = COMMAND_RETURN_OK; + } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) { + client->cmd_list.Begin(true); + ret = COMMAND_RETURN_OK; + } else { + g_debug("[%u] process command \"%s\"", + client->num, line); + ret = command_process(client, 0, line); + g_debug("[%u] command returned %i", + client->num, ret); + + if (ret == COMMAND_RETURN_CLOSE || + client->IsExpired()) + return COMMAND_RETURN_CLOSE; + + if (ret == COMMAND_RETURN_OK) + command_success(client); + } + } + + return ret; +} diff --git a/src/ClientRead.cxx b/src/ClientRead.cxx new file mode 100644 index 000000000..49c698bc1 --- /dev/null +++ b/src/ClientRead.cxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" +#include "Main.hxx" +#include "event/Loop.hxx" + +#include <assert.h> +#include <string.h> + +BufferedSocket::InputResult +Client::OnSocketInput(const void *data, size_t length) +{ + const char *p = (const char *)data; + const char *newline = (const char *)memchr(p, '\n', length); + if (newline == NULL) + return InputResult::MORE; + + TimeoutMonitor::ScheduleSeconds(client_timeout); + + char *line = g_strndup(p, newline - p); + BufferedSocket::ConsumeInput(newline + 1 - p); + + enum command_return result = client_process_line(this, line); + g_free(line); + + switch (result) { + case COMMAND_RETURN_OK: + case COMMAND_RETURN_IDLE: + case COMMAND_RETURN_ERROR: + break; + + case COMMAND_RETURN_KILL: + Close(); + main_loop->Break(); + return InputResult::CLOSED; + + case COMMAND_RETURN_CLOSE: + Close(); + return InputResult::CLOSED; + } + + if (IsExpired()) { + Close(); + return InputResult::CLOSED; + } + + return InputResult::AGAIN; +} diff --git a/src/ClientSubscribe.cxx b/src/ClientSubscribe.cxx new file mode 100644 index 000000000..918a621db --- /dev/null +++ b/src/ClientSubscribe.cxx @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientSubscribe.hxx" +#include "ClientInternal.hxx" +#include "Idle.hxx" + +#include <assert.h> +#include <string.h> + +enum client_subscribe_result +client_subscribe(Client *client, const char *channel) +{ + assert(client != NULL); + assert(channel != NULL); + + if (!client_message_valid_channel_name(channel)) + return CLIENT_SUBSCRIBE_INVALID; + + if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) + return CLIENT_SUBSCRIBE_FULL; + + auto r = client->subscriptions.insert(channel); + if (!r.second) + return CLIENT_SUBSCRIBE_ALREADY; + + ++client->num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + return CLIENT_SUBSCRIBE_OK; +} + +bool +client_unsubscribe(Client *client, const char *channel) +{ + const auto i = client->subscriptions.find(channel); + if (i == client->subscriptions.end()) + return false; + + assert(client->num_subscriptions > 0); + + client->subscriptions.erase(i); + --client->num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + assert((client->num_subscriptions == 0) == + client->subscriptions.empty()); + + return true; +} + +void +client_unsubscribe_all(Client *client) +{ + client->subscriptions.clear(); + client->num_subscriptions = 0; +} + +bool +client_push_message(Client *client, const ClientMessage &msg) +{ + assert(client != NULL); + + if (client->messages.size() >= CLIENT_MAX_MESSAGES || + !client->IsSubscribed(msg.GetChannel())) + return false; + + if (client->messages.empty()) + client->IdleAdd(IDLE_MESSAGE); + + client->messages.push_back(msg); + return true; +} diff --git a/src/ClientSubscribe.hxx b/src/ClientSubscribe.hxx new file mode 100644 index 000000000..83c234db6 --- /dev/null +++ b/src/ClientSubscribe.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_SUBSCRIBE_HXX +#define MPD_CLIENT_SUBSCRIBE_HXX + +#include "gcc.h" + +class Client; +class ClientMessage; + +enum client_subscribe_result { + /** success */ + CLIENT_SUBSCRIBE_OK, + + /** invalid channel name */ + CLIENT_SUBSCRIBE_INVALID, + + /** already subscribed to this channel */ + CLIENT_SUBSCRIBE_ALREADY, + + /** too many subscriptions */ + CLIENT_SUBSCRIBE_FULL, +}; + +enum client_subscribe_result +client_subscribe(Client *client, const char *channel); + +bool +client_unsubscribe(Client *client, const char *channel); + +void +client_unsubscribe_all(Client *client); + +bool +client_push_message(Client *client, const ClientMessage &msg); + +#endif diff --git a/src/ClientWrite.cxx b/src/ClientWrite.cxx new file mode 100644 index 000000000..23b515a3d --- /dev/null +++ b/src/ClientWrite.cxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" + +#include <string.h> +#include <stdio.h> + +/** + * Write a block of data to the client. + */ +static void +client_write(Client *client, const char *data, size_t length) +{ + /* if the client is going to be closed, do nothing */ + if (client->IsExpired() || length == 0) + return; + + client->Write(data, length); +} + +void +client_puts(Client *client, const char *s) +{ + client_write(client, s, strlen(s)); +} + +void +client_vprintf(Client *client, const char *fmt, va_list args) +{ +#ifndef G_OS_WIN32 + va_list tmp; + int length; + + va_copy(tmp, args); + length = vsnprintf(NULL, 0, fmt, tmp); + va_end(tmp); + + if (length <= 0) + /* wtf.. */ + return; + + char *buffer = (char *)g_malloc(length + 1); + vsnprintf(buffer, length + 1, fmt, args); + client_write(client, buffer, length); + g_free(buffer); +#else + /* On mingw32, snprintf() expects a 64 bit integer instead of + a "long int" for "%li". This is not consistent with our + expectation, so we're using plain sprintf() here, hoping + the static buffer is large enough. Sorry for this hack, + but WIN32 development is so painful, I'm not in the mood to + do it properly now. */ + + static char buffer[4096]; + vsprintf(buffer, fmt, args); + client_write(client, buffer, strlen(buffer)); +#endif +} + +G_GNUC_PRINTF(2, 3) +void +client_printf(Client *client, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + client_vprintf(client, fmt, args); + va_end(args); +} diff --git a/src/CommandError.cxx b/src/CommandError.cxx new file mode 100644 index 000000000..7e777d82a --- /dev/null +++ b/src/CommandError.cxx @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CommandError.hxx" +#include "db_error.h" +#include "io_error.h" +#include "protocol/Result.hxx" + +#include <assert.h> +#include <errno.h> + +enum command_return +print_playlist_result(Client *client, enum playlist_result result) +{ + switch (result) { + case PLAYLIST_RESULT_SUCCESS: + return COMMAND_RETURN_OK; + + case PLAYLIST_RESULT_ERRNO: + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(errno)); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_DENIED: + command_error(client, ACK_ERROR_PERMISSION, "Access denied"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NO_SUCH_SONG: + command_error(client, ACK_ERROR_NO_EXIST, "No such song"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NO_SUCH_LIST: + command_error(client, ACK_ERROR_NO_EXIST, "No such playlist"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_LIST_EXISTS: + command_error(client, ACK_ERROR_EXIST, + "Playlist already exists"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_BAD_NAME: + command_error(client, ACK_ERROR_ARG, + "playlist name is invalid: " + "playlist names may not contain slashes," + " newlines or carriage returns"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_BAD_RANGE: + command_error(client, ACK_ERROR_ARG, "Bad song index"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NOT_PLAYING: + command_error(client, ACK_ERROR_PLAYER_SYNC, "Not playing"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_TOO_LARGE: + command_error(client, ACK_ERROR_PLAYLIST_MAX, + "playlist is at the max size"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_DISABLED: + command_error(client, ACK_ERROR_UNKNOWN, + "stored playlist support is disabled"); + return COMMAND_RETURN_ERROR; + } + + assert(0); + return COMMAND_RETURN_ERROR; +} + +/** + * Send the GError to the client and free the GError. + */ +enum command_return +print_error(Client *client, GError *error) +{ + assert(client != NULL); + assert(error != NULL); + + g_warning("%s", error->message); + + if (error->domain == playlist_quark()) { + enum playlist_result result = (playlist_result)error->code; + g_error_free(error); + return print_playlist_result(client, result); + } else if (error->domain == ack_quark()) { + command_error(client, (ack)error->code, "%s", error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } else if (error->domain == db_quark()) { + switch ((enum db_error)error->code) { + case DB_DISABLED: + command_error(client, ACK_ERROR_NO_EXIST, "%s", + error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + + case DB_NOT_FOUND: + g_error_free(error); + command_error(client, ACK_ERROR_NO_EXIST, "Not found"); + return COMMAND_RETURN_ERROR; + } + } else if (error->domain == errno_quark()) { + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(error->code)); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } else if (error->domain == g_file_error_quark()) { + command_error(client, ACK_ERROR_SYSTEM, "%s", error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } + + g_error_free(error); + command_error(client, ACK_ERROR_UNKNOWN, "error"); + return COMMAND_RETURN_ERROR; +} diff --git a/src/CommandError.hxx b/src/CommandError.hxx new file mode 100644 index 000000000..5fb021220 --- /dev/null +++ b/src/CommandError.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_COMMAND_ERROR_HXX +#define MPD_COMMAND_ERROR_HXX + +#include "command.h" +#include "playlist_error.h" +#include "gerror.h" + +class Client; + +enum command_return +print_playlist_result(Client *client, enum playlist_result result); + +/** + * Send the GError to the client and free the GError. + */ +enum command_return +print_error(Client *client, GError *error); + +#endif diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx new file mode 100644 index 000000000..0de211fd7 --- /dev/null +++ b/src/CommandLine.cxx @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CommandLine.hxx" +#include "ls.hxx" +#include "Log.hxx" +#include "conf.h" +#include "DecoderList.hxx" +#include "decoder_plugin.h" +#include "OutputList.hxx" +#include "output_plugin.h" +#include "InputRegistry.hxx" +#include "InputPlugin.hxx" +#include "PlaylistRegistry.hxx" +#include "PlaylistPlugin.hxx" +#include "mpd_error.h" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" + +#ifdef ENABLE_ENCODER +#include "encoder_list.h" +#include "encoder_plugin.h" +#endif + +#ifdef ENABLE_ARCHIVE +#include "ArchiveList.hxx" +#include "ArchivePlugin.hxx" +#endif + +#include <glib.h> + +#include <stdio.h> +#include <stdlib.h> + +#ifdef G_OS_WIN32 +#define CONFIG_FILE_LOCATION "\\mpd\\mpd.conf" +#else /* G_OS_WIN32 */ +#define USER_CONFIG_FILE_LOCATION1 ".mpdconf" +#define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf" +#endif + +static GQuark +cmdline_quark(void) +{ + return g_quark_from_static_string("cmdline"); +} + +G_GNUC_NORETURN +static void version(void) +{ + puts(PACKAGE " (MPD: Music Player Daemon) " VERSION " \n" + "\n" + "Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n" + "Copyright (C) 2008-2012 Max Kellermann <max@duempel.org>\n" + "This is free software; see the source for copying conditions. There is NO\n" + "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" + "\n" + "Decoders plugins:"); + + decoder_plugins_for_each(plugin) { + printf(" [%s]", plugin->name); + + const char *const*suffixes = plugin->suffixes; + if (suffixes != NULL) + for (; *suffixes != NULL; ++suffixes) + printf(" %s", *suffixes); + + puts(""); + } + + puts("\n" + "Output plugins:"); + audio_output_plugins_for_each(plugin) + printf(" %s", plugin->name); + puts(""); + +#ifdef ENABLE_ENCODER + puts("\n" + "Encoder plugins:"); + encoder_plugins_for_each(plugin) + printf(" %s", plugin->name); + puts(""); +#endif + +#ifdef ENABLE_ARCHIVE + puts("\n" + "Archive plugins:"); + archive_plugins_for_each(plugin) { + printf(" [%s]", plugin->name); + + const char *const*suffixes = plugin->suffixes; + if (suffixes != NULL) + for (; *suffixes != NULL; ++suffixes) + printf(" %s", *suffixes); + + puts(""); + } +#endif + + puts("\n" + "Input plugins:"); + input_plugins_for_each(plugin) + printf(" %s", plugin->name); + + puts("\n\n" + "Playlist plugins:"); + playlist_plugins_for_each(plugin) + printf(" %s", plugin->name); + + puts("\n\n" + "Protocols:"); + print_supported_uri_schemes_to_fp(stdout); + + exit(EXIT_SUCCESS); +} + +static const char *summary = + "Music Player Daemon - a daemon for playing music."; + +gcc_pure +static Path +PathBuildChecked(const Path &a, Path::const_pointer b) +{ + if (a.IsNull()) + return Path::Null(); + + return Path::Build(a, b); +} + +bool +parse_cmdline(int argc, char **argv, struct options *options, + GError **error_r) +{ + GError *error = NULL; + GOptionContext *context; + bool ret; + static gboolean option_version, + option_no_daemon, + option_no_config; + const GOptionEntry entries[] = { + { "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill, + "kill the currently running mpd session", NULL }, + { "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config, + "don't read from config", NULL }, + { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon, + "don't detach from console", NULL }, + { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr, + NULL, NULL }, + { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr, + "print messages to stderr", NULL }, + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose, + "verbose logging", NULL }, + { "version", 'V', 0, G_OPTION_ARG_NONE, &option_version, + "print version number", NULL }, + { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } + }; + + options->kill = false; + options->daemon = true; + options->log_stderr = false; + options->verbose = false; + + context = g_option_context_new("[path/to/mpd.conf]"); + g_option_context_add_main_entries(context, entries, NULL); + + g_option_context_set_summary(context, summary); + + ret = g_option_context_parse(context, &argc, &argv, &error); + g_option_context_free(context); + + if (!ret) + MPD_ERROR("option parsing failed: %s\n", error->message); + + if (option_version) + version(); + + /* initialize the logging library, so the configuration file + parser can use it already */ + log_early_init(options->verbose); + + options->daemon = !option_no_daemon; + + if (option_no_config) { + g_debug("Ignoring config, using daemon defaults\n"); + return true; + } else if (argc <= 1) { + /* default configuration file path */ + +#ifdef G_OS_WIN32 + Path path = PathBuildChecked(Path::FromUTF8(g_get_user_config_dir()), + CONFIG_FILE_LOCATION); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error_r); + + const char *const*system_config_dirs = + g_get_system_config_dirs(); + + for (unsigned i = 0; system_config_dirs[i] != nullptr; ++i) { + path = PathBuildChecked(Path::FromUTF8(system_config_dirs[i]), + CONFIG_FILE_LOCATION); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error_r); + } +#else /* G_OS_WIN32 */ + Path path = PathBuildChecked(Path::FromUTF8(g_get_home_dir()), + USER_CONFIG_FILE_LOCATION1); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error_r); + + path = PathBuildChecked(Path::FromUTF8(g_get_home_dir()), + USER_CONFIG_FILE_LOCATION2); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error_r); + + path = Path::FromUTF8(SYSTEM_CONFIG_FILE_LOCATION); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error_r); +#endif + + g_set_error(error_r, cmdline_quark(), 0, + "No configuration file found"); + return false; + } else if (argc == 2) { + /* specified configuration file */ + return ReadConfigFile(Path::FromFS(argv[1]), error_r); + } else { + g_set_error(error_r, cmdline_quark(), 0, + "too many arguments"); + return false; + } +} diff --git a/src/CommandLine.hxx b/src/CommandLine.hxx new file mode 100644 index 000000000..7a8731f82 --- /dev/null +++ b/src/CommandLine.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_COMMAND_LINE_HXX +#define MPD_COMMAND_LINE_HXX + +#include <glib.h> + +struct options { + gboolean kill; + gboolean daemon; + gboolean log_stderr; + gboolean verbose; +}; + +bool +parse_cmdline(int argc, char **argv, struct options *options, + GError **error_r); + +#endif diff --git a/src/CommandListBuilder.cxx b/src/CommandListBuilder.cxx new file mode 100644 index 000000000..cc10f7205 --- /dev/null +++ b/src/CommandListBuilder.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CommandListBuilder.hxx" +#include "ClientInternal.hxx" + +#include <string.h> + +void +CommandListBuilder::Reset() +{ + list.clear(); + mode = Mode::DISABLED; +} + +bool +CommandListBuilder::Add(const char *cmd) +{ + size_t len = strlen(cmd) + 1; + size += len; + if (size > client_max_command_list_size) + return false; + + list.emplace_back(cmd); + return true; +} diff --git a/src/CommandListBuilder.hxx b/src/CommandListBuilder.hxx new file mode 100644 index 000000000..a112ac33b --- /dev/null +++ b/src/CommandListBuilder.hxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_COMMAND_LIST_BUILDER_HXX +#define MPD_COMMAND_LIST_BUILDER_HXX + +#include <list> +#include <string> + +#include <assert.h> + +class CommandListBuilder { + /** + * print OK after each command execution + */ + enum class Mode { + /** + * Not active. + */ + DISABLED = -1, + + /** + * Enabled in normal list mode. + */ + ENABLED = false, + + /** + * Enabled in "list_OK" mode. + */ + OK = true, + } mode; + + /** + * for when in list mode + */ + std::list<std::string> list; + + /** + * Memory consumed by the list. + */ + size_t size; + +public: + CommandListBuilder() + :mode(Mode::DISABLED), size(0) {} + + /** + * Is a command list currently being built? + */ + bool IsActive() const { + return mode != Mode::DISABLED; + } + + /** + * Is the object in "list_OK" mode? + */ + bool IsOKMode() const { + assert(IsActive()); + + return (bool)mode; + } + + /** + * Reset the object: delete the list and clear the mode. + */ + void Reset(); + + /** + * Begin building a command list. + */ + void Begin(bool ok) { + assert(list.empty()); + assert(mode == Mode::DISABLED); + + mode = (Mode)ok; + } + + /** + * @return false if the list is full + */ + bool Add(const char *cmd); + + /** + * Finishes the list and returns it. + */ + std::list<std::string> &&Commit() { + assert(IsActive()); + + return std::move(list); + } +}; + +#endif diff --git a/src/ConfigData.cxx b/src/ConfigData.cxx new file mode 100644 index 000000000..48e9612d4 --- /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 000000000..f3e661b2b --- /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 000000000..e94f3f238 --- /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 000000000..49c0d31ec --- /dev/null +++ b/src/ConfigFile.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIG_FILE_HXX +#define MPD_CONFIG_FILE_HXX + +#include "gerror.h" + +class Path; +struct ConfigData; + +bool +ReadConfigFile(ConfigData &data, const Path &path, GError **error_r); + +#endif diff --git a/src/ConfigGlobal.cxx b/src/ConfigGlobal.cxx new file mode 100644 index 000000000..9786690d0 --- /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 000000000..9abfb2b5d --- /dev/null +++ b/src/ConfigGlobal.hxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIG_GLOBAL_HXX +#define MPD_CONFIG_GLOBAL_HXX + +#include "ConfigOption.hxx" +#include "gerror.h" +#include "gcc.h" + +#include <stdbool.h> +#include <stddef.h> + +#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16) +#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false + +#ifdef __cplusplus +class Path; +#endif + +void config_global_init(void); +void config_global_finish(void); + +/** + * Call this function after all configuration has been evaluated. It + * checks for unused parameters, and logs warnings. + */ +void config_global_check(void); + +#ifdef __cplusplus + +bool +ReadConfigFile(const Path &path, GError **error_r); + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* don't free the returned value + set _last_ to NULL to get first entry */ +gcc_pure +const struct config_param * +config_get_next_param(enum ConfigOption option, + const struct config_param *last); + +gcc_pure +static inline const struct config_param * +config_get_param(enum ConfigOption option) +{ + return config_get_next_param(option, NULL); +} + +/* Note on gcc_pure: Some of the functions declared pure are not + really pure in strict sense. They have side effect such that they + validate parameter's value and signal an error if it's invalid. + However, if the argument was already validated or we don't care + about the argument at all, this may be ignored so in the end, we + should be fine with calling those functions pure. */ + +gcc_pure +const char * +config_get_string(enum ConfigOption option, const char *default_value); + +/** + * Returns an optional configuration variable which contains an + * absolute path. If there is a tilde prefix, it is expanded. + * Returns NULL if the value is not present. If the path could not be + * parsed, returns NULL and sets the error. + * + * The return value must be freed with g_free(). + */ +gcc_malloc +char * +config_dup_path(enum ConfigOption option, GError **error_r); + +gcc_pure +unsigned +config_get_unsigned(enum ConfigOption option, unsigned default_value); + +gcc_pure +unsigned +config_get_positive(enum ConfigOption option, unsigned default_value); + +gcc_pure +bool config_get_bool(enum ConfigOption option, bool default_value); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/ConfigOption.hxx b/src/ConfigOption.hxx new file mode 100644 index 000000000..21a3a02e4 --- /dev/null +++ b/src/ConfigOption.hxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIG_OPTION_HXX +#define MPD_CONFIG_OPTION_HXX + +#include "gcc.h" + +enum ConfigOption { + CONF_MUSIC_DIR, + CONF_PLAYLIST_DIR, + CONF_FOLLOW_INSIDE_SYMLINKS, + CONF_FOLLOW_OUTSIDE_SYMLINKS, + CONF_DB_FILE, + CONF_STICKER_FILE, + CONF_LOG_FILE, + CONF_PID_FILE, + CONF_STATE_FILE, + CONF_RESTORE_PAUSED, + CONF_USER, + CONF_GROUP, + CONF_BIND_TO_ADDRESS, + CONF_PORT, + CONF_LOG_LEVEL, + CONF_ZEROCONF_NAME, + CONF_ZEROCONF_ENABLED, + CONF_PASSWORD, + CONF_DEFAULT_PERMS, + CONF_AUDIO_OUTPUT, + CONF_AUDIO_OUTPUT_FORMAT, + CONF_MIXER_TYPE, + CONF_REPLAYGAIN, + CONF_REPLAYGAIN_PREAMP, + CONF_REPLAYGAIN_MISSING_PREAMP, + CONF_REPLAYGAIN_LIMIT, + CONF_VOLUME_NORMALIZATION, + CONF_SAMPLERATE_CONVERTER, + CONF_AUDIO_BUFFER_SIZE, + CONF_BUFFER_BEFORE_PLAY, + CONF_HTTP_PROXY_HOST, + CONF_HTTP_PROXY_PORT, + CONF_HTTP_PROXY_USER, + CONF_HTTP_PROXY_PASSWORD, + CONF_CONN_TIMEOUT, + CONF_MAX_CONN, + CONF_MAX_PLAYLIST_LENGTH, + CONF_MAX_COMMAND_LIST_SIZE, + CONF_MAX_OUTPUT_BUFFER_SIZE, + CONF_FS_CHARSET, + CONF_ID3V1_ENCODING, + CONF_METADATA_TO_USE, + CONF_SAVE_ABSOLUTE_PATHS, + CONF_DECODER, + CONF_INPUT, + CONF_GAPLESS_MP3_PLAYBACK, + CONF_PLAYLIST_PLUGIN, + CONF_AUTO_UPDATE, + CONF_AUTO_UPDATE_DEPTH, + CONF_DESPOTIFY_USER, + CONF_DESPOTIFY_PASSWORD, + CONF_DESPOTIFY_HIGH_BITRATE, + CONF_AUDIO_FILTER, + CONF_DATABASE, + CONF_MAX +}; + +/** + * @return #CONF_MAX if not found + */ +gcc_pure +enum ConfigOption +ParseConfigOptionName(const char *name); + +#endif diff --git a/src/ConfigParser.cxx b/src/ConfigParser.cxx new file mode 100644 index 000000000..9798b6edd --- /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 000000000..00fd42fe3 --- /dev/null +++ b/src/ConfigParser.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIG_PARSER_HXX +#define MPD_CONFIG_PARSER_HXX + +bool +get_bool(const char *value, bool *value_r); + +#endif diff --git a/src/ConfigQuark.hxx b/src/ConfigQuark.hxx new file mode 100644 index 000000000..11594f998 --- /dev/null +++ b/src/ConfigQuark.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIG_QUARK_HXX +#define MPD_CONFIG_QUARK_HXX + +#include <glib.h> + +/** + * A GQuark for GError instances, resulting from malformed + * configuration. + */ +G_GNUC_CONST +static inline GQuark +config_quark(void) +{ + return g_quark_from_static_string("config"); +} + +#endif diff --git a/src/ConfigTemplates.cxx b/src/ConfigTemplates.cxx new file mode 100644 index 000000000..6c6bf1689 --- /dev/null +++ b/src/ConfigTemplates.cxx @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ConfigTemplates.hxx" +#include "ConfigOption.hxx" + +#include <string.h> + +const ConfigTemplate config_templates[] = { + { "music_directory", false, false }, + { "playlist_directory", false, false }, + { "follow_inside_symlinks", false, false }, + { "follow_outside_symlinks", false, false }, + { "db_file", false, false }, + { "sticker_file", false, false }, + { "log_file", false, false }, + { "pid_file", false, false }, + { "state_file", false, false }, + { "restore_paused", false, false }, + { "user", false, false }, + { "group", false, false }, + { "bind_to_address", true, false }, + { "port", false, false }, + { "log_level", false, false }, + { "zeroconf_name", false, false }, + { "zeroconf_enabled", false, false }, + { "password", true, false }, + { "default_permissions", false, false }, + { "audio_output", true, true }, + { "audio_output_format", false, false }, + { "mixer_type", false, false }, + { "replaygain", false, false }, + { "replaygain_preamp", false, false }, + { "replaygain_missing_preamp", false, false }, + { "replaygain_limit", false, false }, + { "volume_normalization", false, false }, + { "samplerate_converter", false, false }, + { "audio_buffer_size", false, false }, + { "buffer_before_play", false, false }, + { "http_proxy_host", false, false }, + { "http_proxy_port", false, false }, + { "http_proxy_user", false, false }, + { "http_proxy_password", false, false }, + { "connection_timeout", false, false }, + { "max_connections", false, false }, + { "max_playlist_length", false, false }, + { "max_command_list_size", false, false }, + { "max_output_buffer_size", false, false }, + { "filesystem_charset", false, false }, + { "id3v1_encoding", false, false }, + { "metadata_to_use", false, false }, + { "save_absolute_paths_in_playlists", false, false }, + { "decoder", true, true }, + { "input", true, true }, + { "gapless_mp3_playback", false, false }, + { "playlist_plugin", true, true }, + { "auto_update", false, false }, + { "auto_update_depth", false, false }, + { "despotify_user", false, false }, + { "despotify_password", false, false}, + { "despotify_high_bitrate", false, false }, + { "filter", true, true }, + { "database", false, true }, +}; + +static constexpr unsigned n_config_templates = + sizeof(config_templates) / sizeof(config_templates[0]); + +static_assert(n_config_templates == unsigned(CONF_MAX), + "Wrong number of config_templates"); + +ConfigOption +ParseConfigOptionName(const char *name) +{ + for (unsigned i = 0; i < n_config_templates; ++i) + if (strcmp(config_templates[i].name, name) == 0) + return ConfigOption(i); + + return CONF_MAX; +} diff --git a/src/ConfigTemplates.hxx b/src/ConfigTemplates.hxx new file mode 100644 index 000000000..4f5460460 --- /dev/null +++ b/src/ConfigTemplates.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIG_TEMPLATES_HXX +#define MPD_CONFIG_TEMPLATES_HXX + +#include "ConfigOption.hxx" + +struct ConfigTemplate { + const char *const name; + const bool repeatable; + const bool block; +}; + +extern const ConfigTemplate config_templates[]; + +#endif diff --git a/src/CrossFade.cxx b/src/CrossFade.cxx new file mode 100644 index 000000000..0bdcc43d6 --- /dev/null +++ b/src/CrossFade.cxx @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CrossFade.hxx" +#include "MusicChunk.hxx" +#include "audio_format.h" +#include "tag.h" + +#include <cmath> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <glib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "crossfade" + +#ifdef G_OS_WIN32 +static char * +strtok_r(char *str, const char *delim, G_GNUC_UNUSED char **saveptr) +{ + return strtok(str, delim); +} +#endif + +static float mixramp_interpolate(char *ramp_list, float required_db) +{ + float db, secs, last_db = nan(""), last_secs = 0; + char *ramp_str, *save_str = NULL; + + /* ramp_list is a string of pairs of dBs and seconds that describe the + * volume profile. Delimiters are semi-colons between pairs and spaces + * between the dB and seconds of a pair. + * The dB values must be monotonically increasing for this to work. */ + + while (1) { + /* Parse the dB tokens out of the input string. */ + ramp_str = strtok_r(ramp_list, " ", &save_str); + + /* Tell strtok to continue next time round. */ + ramp_list = NULL; + + /* Parse the dB value. */ + if (NULL == ramp_str) { + return nan(""); + } + db = (float)atof(ramp_str); + + /* Parse the time. */ + ramp_str = strtok_r(NULL, ";", &save_str); + if (NULL == ramp_str) { + return nan(""); + } + secs = (float)atof(ramp_str); + + /* Check for exact match. */ + if (db == required_db) { + return secs; + } + + /* Save if too quiet. */ + if (db < required_db) { + last_db = db; + last_secs = secs; + continue; + } + + /* If required db < any stored value, use the least. */ + if (std::isnan(last_db)) + return secs; + + /* Finally, interpolate linearly. */ + secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db); + return secs; + } +} + +unsigned cross_fade_calc(float duration, float total_time, + float mixramp_db, float mixramp_delay, + float replay_gain_db, float replay_gain_prev_db, + char *mixramp_start, char *mixramp_prev_end, + const struct audio_format *af, + const struct audio_format *old_format, + unsigned max_chunks) +{ + unsigned int chunks = 0; + float chunks_f; + float mixramp_overlap; + + if (duration < 0 || duration >= total_time || + /* we can't crossfade when the audio formats are different */ + !audio_format_equals(af, old_format)) + return 0; + + assert(duration >= 0); + assert(audio_format_valid(af)); + + chunks_f = (float)audio_format_time_to_size(af) / (float)CHUNK_SIZE; + + if (std::isnan(mixramp_delay) || !mixramp_start || !mixramp_prev_end) { + chunks = (chunks_f * duration + 0.5); + } else { + /* Calculate mixramp overlap. */ + mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db - replay_gain_db) + + mixramp_interpolate(mixramp_prev_end, mixramp_db - replay_gain_prev_db); + if (!std::isnan(mixramp_overlap) && + mixramp_delay <= mixramp_overlap) { + chunks = (chunks_f * (mixramp_overlap - mixramp_delay)); + g_debug("will overlap %d chunks, %fs", chunks, + mixramp_overlap - mixramp_delay); + } + } + + if (chunks > max_chunks) { + chunks = max_chunks; + g_warning("audio_buffer_size too small for computed MixRamp overlap"); + } + + return chunks; +} diff --git a/src/CrossFade.hxx b/src/CrossFade.hxx new file mode 100644 index 000000000..1c4670758 --- /dev/null +++ b/src/CrossFade.hxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CROSSFADE_HXX +#define MPD_CROSSFADE_HXX + +struct audio_format; +struct music_chunk; + +/** + * Calculate how many music pipe chunks should be used for crossfading. + * + * @param duration the requested crossfade duration + * @param total_time total_time the duration of the new song + * @param mixramp_db the current mixramp_db setting + * @param mixramp_delay the current mixramp_delay setting + * @param replay_gain_db the ReplayGain adjustment used for this song + * @param replay_gain_prev_db the ReplayGain adjustment used on the last song + * @param mixramp_start the next songs mixramp_start tag + * @param mixramp_prev_end the last songs mixramp_end setting + * @param af the audio format of the new song + * @param old_format the audio format of the current song + * @param max_chunks the maximum number of chunks + * @return the number of chunks for crossfading, or 0 if cross fading + * should be disabled for this song change + */ +unsigned cross_fade_calc(float duration, float total_time, + float mixramp_db, float mixramp_delay, + float replay_gain_db, float replay_gain_prev_db, + char *mixramp_start, char *mixramp_prev_end, + const struct audio_format *af, + const struct audio_format *old_format, + unsigned max_chunks); + +#endif diff --git a/src/DatabaseCommands.cxx b/src/DatabaseCommands.cxx new file mode 100644 index 000000000..bd5a48b35 --- /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 000000000..335adc4d6 --- /dev/null +++ b/src/DatabaseCommands.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_COMMANDS_HXX +#define MPD_DATABASE_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_lsinfo2(Client *client, int argc, char *argv[]); + +enum command_return +handle_find(Client *client, int argc, char *argv[]); + +enum command_return +handle_findadd(Client *client, int argc, char *argv[]); + +enum command_return +handle_search(Client *client, int argc, char *argv[]); + +enum command_return +handle_searchadd(Client *client, int argc, char *argv[]); + +enum command_return +handle_searchaddpl(Client *client, int argc, char *argv[]); + +enum command_return +handle_count(Client *client, int argc, char *argv[]); + +enum command_return +handle_listall(Client *client, int argc, char *argv[]); + +enum command_return +handle_list(Client *client, int argc, char *argv[]); + +enum command_return +handle_listallinfo(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/DatabaseGlue.cxx b/src/DatabaseGlue.cxx new file mode 100644 index 000000000..db7d4b9f3 --- /dev/null +++ b/src/DatabaseGlue.cxx @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseGlue.hxx" +#include "DatabaseSimple.hxx" +#include "DatabaseRegistry.hxx" +#include "DatabaseSave.hxx" +#include "Directory.hxx" +#include "conf.h" + +extern "C" { +#include "db_error.h" +#include "stats.h" +} + +#include "DatabasePlugin.hxx" +#include "db/SimpleDatabasePlugin.hxx" + +#include <glib.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <assert.h> +#include <string.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "database" + +static Database *db; +static bool db_is_open; +static bool is_simple; + +bool +DatabaseGlobalInit(const config_param *param, GError **error_r) +{ + assert(db == NULL); + assert(!db_is_open); + + const char *plugin_name = + config_get_block_string(param, "plugin", "simple"); + is_simple = strcmp(plugin_name, "simple") == 0; + + const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name); + if (plugin == NULL) { + g_set_error(error_r, db_quark(), 0, + "No such database plugin: %s", plugin_name); + return false; + } + + db = plugin->create(param, error_r); + return db != NULL; +} + +void +DatabaseGlobalDeinit(void) +{ + if (db_is_open) + db->Close(); + + if (db != NULL) + delete db; +} + +const Database * +GetDatabase() +{ + assert(db == NULL || db_is_open); + + return db; +} + +const Database * +GetDatabase(GError **error_r) +{ + assert(db == nullptr || db_is_open); + + if (db == nullptr) + g_set_error_literal(error_r, db_quark(), DB_DISABLED, + "No database"); + + return db; +} + +bool +db_is_simple(void) +{ + assert(db == NULL || db_is_open); + + return is_simple; +} + +Directory * +db_get_root(void) +{ + assert(db != NULL); + assert(db_is_simple()); + + return ((SimpleDatabase *)db)->GetRoot(); +} + +Directory * +db_get_directory(const char *name) +{ + if (db == NULL) + return NULL; + + Directory *music_root = db_get_root(); + if (name == NULL) + return music_root; + + return music_root->LookupDirectory(name); +} + +bool +db_save(GError **error_r) +{ + assert(db != NULL); + assert(db_is_open); + assert(db_is_simple()); + + return ((SimpleDatabase *)db)->Save(error_r); +} + +bool +DatabaseGlobalOpen(GError **error) +{ + assert(db != NULL); + assert(!db_is_open); + + if (!db->Open(error)) + return false; + + db_is_open = true; + + stats_update(); + + return true; +} + +time_t +db_get_mtime(void) +{ + assert(db != NULL); + assert(db_is_open); + assert(db_is_simple()); + + return ((SimpleDatabase *)db)->GetLastModified(); +} diff --git a/src/DatabaseGlue.hxx b/src/DatabaseGlue.hxx new file mode 100644 index 000000000..ea26f3242 --- /dev/null +++ b/src/DatabaseGlue.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_GLUE_HXX +#define MPD_DATABASE_GLUE_HXX + +#include "gcc.h" +#include "gerror.h" + +struct config_param; +class Database; + +/** + * Initialize the database library. + * + * @param param the database configuration block + */ +bool +DatabaseGlobalInit(const config_param *param, GError **error_r); + +void +DatabaseGlobalDeinit(void); + +bool +DatabaseGlobalOpen(GError **error); + +/** + * Returns the global #Database instance. May return NULL if this MPD + * configuration has no database (no music_directory was configured). + */ +gcc_pure +const Database * +GetDatabase(); + +/** + * Returns the global #Database instance. May return NULL if this MPD + * configuration has no database (no music_directory was configured). + */ +gcc_pure +const Database * +GetDatabase(GError **error_r); + +#endif diff --git a/src/DatabaseHelpers.cxx b/src/DatabaseHelpers.cxx new file mode 100644 index 000000000..dc31a4bc2 --- /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 000000000..cfcc94ac7 --- /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 000000000..398e5aebb --- /dev/null +++ b/src/DatabaseLock.cxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseLock.hxx" +#include "gcc.h" + +Mutex db_mutex; + +#ifndef NDEBUG +GThread *db_mutex_holder; +#endif diff --git a/src/DatabaseLock.hxx b/src/DatabaseLock.hxx new file mode 100644 index 000000000..371a7d7b2 --- /dev/null +++ b/src/DatabaseLock.hxx @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Support for locking data structures from the database, for safe + * multi-threading. + */ + +#ifndef MPD_DB_LOCK_HXX +#define MPD_DB_LOCK_HXX + +#include "check.h" +#include "thread/Mutex.hxx" + +#include <glib.h> +#include <assert.h> + +extern Mutex db_mutex; + +#ifndef NDEBUG + +extern GThread *db_mutex_holder; + +/** + * Does the current thread hold the database lock? + */ +G_GNUC_PURE +static inline bool +holding_db_lock(void) +{ + return db_mutex_holder == g_thread_self(); +} + +#endif + +/** + * Obtain the global database lock. This is needed before + * dereferencing a #song or #directory. It is not recursive. + */ +static inline void +db_lock(void) +{ + assert(!holding_db_lock()); + + db_mutex.lock(); + + assert(db_mutex_holder == NULL); +#ifndef NDEBUG + db_mutex_holder = g_thread_self(); +#endif +} + +/** + * Release the global database lock. + */ +static inline void +db_unlock(void) +{ + assert(holding_db_lock()); +#ifndef NDEBUG + db_mutex_holder = NULL; +#endif + + db_mutex.unlock(); +} + +#ifdef __cplusplus + +class ScopeDatabaseLock { +public: + ScopeDatabaseLock() { + db_lock(); + } + + ~ScopeDatabaseLock() { + db_unlock(); + } +}; + +#endif + +#endif diff --git a/src/DatabasePlaylist.cxx b/src/DatabasePlaylist.cxx new file mode 100644 index 000000000..fb477e83b --- /dev/null +++ b/src/DatabasePlaylist.cxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabasePlaylist.hxx" +#include "DatabaseSelection.hxx" +#include "PlaylistFile.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +#include <functional> + +static bool +AddSong(const char *playlist_path_utf8, + song &song, GError **error_r) +{ + return spl_append_song(playlist_path_utf8, &song, error_r); +} + +bool +search_add_to_playlist(const char *uri, const char *playlist_path_utf8, + const SongFilter *filter, + GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + const DatabaseSelection selection(uri, true, filter); + + using namespace std::placeholders; + const auto f = std::bind(AddSong, playlist_path_utf8, _1, _2); + return db->Visit(selection, f, error_r); +} diff --git a/src/DatabasePlaylist.hxx b/src/DatabasePlaylist.hxx new file mode 100644 index 000000000..7c6952ffa --- /dev/null +++ b/src/DatabasePlaylist.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_PLAYLIST_HXX +#define MPD_DATABASE_PLAYLIST_HXX + +#include "gcc.h" +#include "gerror.h" + +class SongFilter; + +gcc_nonnull(1,2) +bool +search_add_to_playlist(const char *uri, const char *path_utf8, + const SongFilter *filter, + GError **error_r); + +#endif diff --git a/src/DatabasePlugin.hxx b/src/DatabasePlugin.hxx new file mode 100644 index 000000000..a175b3cd9 --- /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 000000000..2384d5c14 --- /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/DatabasePrint.hxx b/src/DatabasePrint.hxx new file mode 100644 index 000000000..68551b63c --- /dev/null +++ b/src/DatabasePrint.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DB_PRINT_H +#define MPD_DB_PRINT_H + +#include "gcc.h" +#include "gerror.h" + +class SongFilter; +struct DatabaseSelection; +struct db_visitor; +class Client; + +gcc_nonnull(1) +bool +db_selection_print(Client *client, const DatabaseSelection &selection, + bool full, GError **error_r); + +gcc_nonnull(1,2) +bool +printAllIn(Client *client, const char *uri_utf8, GError **error_r); + +gcc_nonnull(1,2) +bool +printInfoForAllIn(Client *client, const char *uri_utf8, + GError **error_r); + +gcc_nonnull(1,2) +bool +searchStatsForSongsIn(Client *client, const char *name, + const SongFilter *filter, + GError **error_r); + +gcc_nonnull(1) +bool +listAllUniqueTags(Client *client, int type, + const SongFilter *filter, + GError **error_r); + +#endif diff --git a/src/DatabaseQueue.cxx b/src/DatabaseQueue.cxx new file mode 100644 index 000000000..e22144c07 --- /dev/null +++ b/src/DatabaseQueue.cxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseQueue.hxx" +#include "DatabaseSelection.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "Partition.hxx" + +#include <functional> + +static bool +AddToQueue(Partition &partition, song &song, GError **error_r) +{ + enum playlist_result result = + partition.playlist.AppendSong(partition.pc, &song, NULL); + if (result != PLAYLIST_RESULT_SUCCESS) { + g_set_error(error_r, playlist_quark(), result, + "Playlist error"); + return false; + } + + return true; +} + +bool +AddFromDatabase(Partition &partition, const DatabaseSelection &selection, + GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + using namespace std::placeholders; + const auto f = std::bind(AddToQueue, std::ref(partition), _1, _2); + return db->Visit(selection, f, error_r); +} diff --git a/src/DatabaseQueue.hxx b/src/DatabaseQueue.hxx new file mode 100644 index 000000000..bae5b1f05 --- /dev/null +++ b/src/DatabaseQueue.hxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_QUEUE_HXX +#define MPD_DATABASE_QUEUE_HXX + +#include "gerror.h" + +struct Partition; +struct DatabaseSelection; + +bool +AddFromDatabase(Partition &partition, const DatabaseSelection &selection, + GError **error_r); + +#endif diff --git a/src/DatabaseRegistry.cxx b/src/DatabaseRegistry.cxx new file mode 100644 index 000000000..cf01decdd --- /dev/null +++ b/src/DatabaseRegistry.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseRegistry.hxx" +#include "db/SimpleDatabasePlugin.hxx" +#include "db/ProxyDatabasePlugin.hxx" + +#include <string.h> + +const DatabasePlugin *const database_plugins[] = { + &simple_db_plugin, +#ifdef HAVE_LIBMPDCLIENT + &proxy_db_plugin, +#endif + NULL +}; + +const DatabasePlugin * +GetDatabasePluginByName(const char *name) +{ + for (auto i = database_plugins; *i != nullptr; ++i) + if (strcmp((*i)->name, name) == 0) + return *i; + + return nullptr; +} diff --git a/src/DatabaseRegistry.hxx b/src/DatabaseRegistry.hxx new file mode 100644 index 000000000..4be581573 --- /dev/null +++ b/src/DatabaseRegistry.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_REGISTRY_HXX +#define MPD_DATABASE_REGISTRY_HXX + +#include "gcc.h" + +struct DatabasePlugin; + +/** + * NULL terminated list of all database plugins which were enabled at + * compile time. + */ +extern const DatabasePlugin *const database_plugins[]; + +gcc_pure +const DatabasePlugin * +GetDatabasePluginByName(const char *name); + +#endif diff --git a/src/DatabaseSave.cxx b/src/DatabaseSave.cxx new file mode 100644 index 000000000..dc87c8ddb --- /dev/null +++ b/src/DatabaseSave.cxx @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseSave.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" +#include "DirectorySave.hxx" +#include "song.h" +#include "TextFile.hxx" +#include "TagInternal.hxx" +#include "tag.h" +#include "fs/Path.hxx" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "database" + +#define DIRECTORY_INFO_BEGIN "info_begin" +#define DIRECTORY_INFO_END "info_end" +#define DB_FORMAT_PREFIX "format: " +#define DIRECTORY_MPD_VERSION "mpd_version: " +#define DIRECTORY_FS_CHARSET "fs_charset: " +#define DB_TAG_PREFIX "tag: " + +enum { + DB_FORMAT = 1, +}; + +G_GNUC_CONST +static GQuark +db_quark(void) +{ + return g_quark_from_static_string("database"); +} + +void +db_save_internal(FILE *fp, const Directory *music_root) +{ + assert(music_root != NULL); + + fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN); + fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT); + fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION); + fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, + Path::GetFSCharset().c_str()); + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (!ignore_tag_items[i]) + fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]); + + fprintf(fp, "%s\n", DIRECTORY_INFO_END); + + directory_save(fp, music_root); +} + +bool +db_load_internal(TextFile &file, Directory *music_root, GError **error) +{ + char *line; + int format = 0; + bool found_charset = false, found_version = false; + bool success; + bool tags[TAG_NUM_OF_ITEM_TYPES]; + + assert(music_root != NULL); + + /* get initial info */ + line = file.ReadLine(); + if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) { + g_set_error(error, db_quark(), 0, "Database corrupted"); + return false; + } + + memset(tags, false, sizeof(tags)); + + while ((line = file.ReadLine()) != NULL && + strcmp(line, DIRECTORY_INFO_END) != 0) { + if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) { + format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1); + } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) { + if (found_version) { + g_set_error(error, db_quark(), 0, + "Duplicate version line"); + return false; + } + + found_version = true; + } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) { + const char *new_charset; + + if (found_charset) { + g_set_error(error, db_quark(), 0, + "Duplicate charset line"); + return false; + } + + found_charset = true; + + new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1; + const std::string &old_charset = Path::GetFSCharset(); + if (!old_charset.empty() + && strcmp(new_charset, old_charset.c_str())) { + g_set_error(error, db_quark(), 0, + "Existing database has charset " + "\"%s\" instead of \"%s\"; " + "discarding database file", + new_charset, old_charset.c_str()); + return false; + } + } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) { + const char *name = line + sizeof(DB_TAG_PREFIX) - 1; + enum tag_type tag = tag_name_parse(name); + if (tag == TAG_NUM_OF_ITEM_TYPES) { + g_set_error(error, db_quark(), 0, + "Unrecognized tag '%s', " + "discarding database file", + name); + return false; + } + + tags[tag] = true; + } else { + g_set_error(error, db_quark(), 0, + "Malformed line: %s", line); + return false; + } + } + + if (format != DB_FORMAT) { + g_set_error(error, db_quark(), 0, + "Database format mismatch, " + "discarding database file"); + return false; + } + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + if (!ignore_tag_items[i] && !tags[i]) { + g_set_error(error, db_quark(), 0, + "Tag list mismatch, " + "discarding database file"); + return false; + } + } + + g_debug("reading DB"); + + db_lock(); + success = directory_load(file, music_root, error); + db_unlock(); + + return success; +} diff --git a/src/DatabaseSave.hxx b/src/DatabaseSave.hxx new file mode 100644 index 000000000..40048f261 --- /dev/null +++ b/src/DatabaseSave.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_SAVE_HXX +#define MPD_DATABASE_SAVE_HXX + +#include "gerror.h" + +#include <stdio.h> + +struct Directory; +class TextFile; + +void +db_save_internal(FILE *file, const Directory *root); + +bool +db_load_internal(TextFile &file, Directory *root, GError **error); + +#endif diff --git a/src/DatabaseSelection.cxx b/src/DatabaseSelection.cxx new file mode 100644 index 000000000..bd756f5f9 --- /dev/null +++ b/src/DatabaseSelection.cxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "DatabaseSelection.hxx" +#include "SongFilter.hxx" + +bool +DatabaseSelection::Match(const song &song) const +{ + return filter == nullptr || filter->Match(song); +} diff --git a/src/DatabaseSelection.hxx b/src/DatabaseSelection.hxx new file mode 100644 index 000000000..3a81c01ec --- /dev/null +++ b/src/DatabaseSelection.hxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_SELECTION_HXX +#define MPD_DATABASE_SELECTION_HXX + +#include "gcc.h" + +#include <assert.h> +#include <stddef.h> + +class SongFilter; +struct song; + +struct DatabaseSelection { + /** + * The base URI of the search (UTF-8). Must not begin or end + * with a slash. NULL or an empty string searches the whole + * database. + */ + const char *uri; + + /** + * Recursively search all sub directories? + */ + bool recursive; + + const SongFilter *filter; + + DatabaseSelection(const char *_uri, bool _recursive, + const SongFilter *_filter=nullptr) + :uri(_uri), recursive(_recursive), filter(_filter) { + assert(uri != NULL); + } + + gcc_pure + bool Match(const song &song) const; +}; + +#endif diff --git a/src/DatabaseSimple.hxx b/src/DatabaseSimple.hxx new file mode 100644 index 000000000..c387a64f9 --- /dev/null +++ b/src/DatabaseSimple.hxx @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_SIMPLE_HXX +#define MPD_DATABASE_SIMPLE_HXX + +#include "gcc.h" +#include "gerror.h" + +#include <sys/time.h> + +struct config_param; +struct Directory; +struct db_selection; +struct db_visitor; + +/** + * Check whether the default #SimpleDatabasePlugin is used. This + * allows using db_get_root(), db_save(), db_get_mtime() and + * db_exists(). + */ +bool +db_is_simple(void); + +/** + * Returns the root directory object. Returns NULL if there is no + * configured music directory. + * + * May only be used if db_is_simple() returns true. + */ +gcc_pure +Directory * +db_get_root(void); + +/** + * Caller must lock the #db_mutex. + */ +gcc_nonnull(1) +gcc_pure +Directory * +db_get_directory(const char *name); + +/** + * May only be used if db_is_simple() returns true. + */ +bool +db_save(GError **error_r); + +/** + * May only be used if db_is_simple() returns true. + */ +gcc_pure +time_t +db_get_mtime(void); + +/** + * Returns true if there is a valid database file on the disk. + * + * May only be used if db_is_simple() returns true. + */ +gcc_pure +static inline bool +db_exists(void) +{ + /* mtime is set only if the database file was loaded or saved + successfully */ + return db_get_mtime() > 0; +} + +#endif diff --git a/src/DatabaseVisitor.hxx b/src/DatabaseVisitor.hxx new file mode 100644 index 000000000..c90441415 --- /dev/null +++ b/src/DatabaseVisitor.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_VISITOR_HXX +#define MPD_DATABASE_VISITOR_HXX + +#include "gerror.h" + +#include <functional> + +struct Directory; +struct song; +struct PlaylistInfo; + +typedef std::function<bool(const Directory &, GError **)> VisitDirectory; +typedef std::function<bool(struct song &, GError **)> VisitSong; +typedef std::function<bool(const PlaylistInfo &, const Directory &, + GError **)> VisitPlaylist; + +typedef std::function<bool(const char *, GError **)> VisitString; + +#endif diff --git a/src/DecoderAPI.cxx b/src/DecoderAPI.cxx new file mode 100644 index 000000000..d86b93fb4 --- /dev/null +++ b/src/DecoderAPI.cxx @@ -0,0 +1,567 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "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> + +#include <assert.h> +#include <stdlib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "decoder" + +void +decoder_initialized(struct decoder *decoder, + const struct audio_format *audio_format, + bool seekable, float total_time) +{ + struct decoder_control *dc = decoder->dc; + struct audio_format_string af_string; + + assert(dc->state == DECODE_STATE_START); + assert(dc->pipe != NULL); + assert(decoder != NULL); + assert(decoder->stream_tag == NULL); + assert(decoder->decoder_tag == NULL); + assert(!decoder->seeking); + assert(audio_format != NULL); + assert(audio_format_defined(audio_format)); + assert(audio_format_valid(audio_format)); + + dc->in_audio_format = *audio_format; + getOutputAudioFormat(audio_format, &dc->out_audio_format); + + dc->seekable = seekable; + dc->total_time = total_time; + + dc->Lock(); + dc->state = DECODE_STATE_DECODE; + dc->client_cond.signal(); + dc->Unlock(); + + g_debug("audio_format=%s, seekable=%s", + audio_format_to_string(&dc->in_audio_format, &af_string), + seekable ? "true" : "false"); + + if (!audio_format_equals(&dc->in_audio_format, + &dc->out_audio_format)) + g_debug("converting to %s", + audio_format_to_string(&dc->out_audio_format, + &af_string)); +} + +/** + * Checks if we need an "initial seek". If so, then the initial seek + * is prepared, and the function returns true. + */ +G_GNUC_PURE +static bool +decoder_prepare_initial_seek(struct decoder *decoder) +{ + const struct decoder_control *dc = decoder->dc; + assert(dc->pipe != NULL); + + if (dc->state != DECODE_STATE_DECODE) + /* wait until the decoder has finished initialisation + (reading file headers etc.) before emitting the + virtual "SEEK" command */ + return false; + + if (decoder->initial_seek_running) + /* initial seek has already begun - override any other + command */ + return true; + + if (decoder->initial_seek_pending) { + if (!dc->seekable) { + /* seeking is not possible */ + decoder->initial_seek_pending = false; + return false; + } + + if (dc->command == DECODE_COMMAND_NONE) { + /* begin initial seek */ + + decoder->initial_seek_pending = false; + decoder->initial_seek_running = true; + return true; + } + + /* skip initial seek when there's another command + (e.g. STOP) */ + + decoder->initial_seek_pending = false; + } + + return false; +} + +/** + * Returns the current decoder command. May return a "virtual" + * synthesized command, e.g. to seek to the beginning of the CUE + * track. + */ +G_GNUC_PURE +static enum decoder_command +decoder_get_virtual_command(struct decoder *decoder) +{ + const struct decoder_control *dc = decoder->dc; + assert(dc->pipe != NULL); + + if (decoder_prepare_initial_seek(decoder)) + return DECODE_COMMAND_SEEK; + + return dc->command; +} + +enum decoder_command +decoder_get_command(struct decoder *decoder) +{ + return decoder_get_virtual_command(decoder); +} + +void +decoder_command_finished(struct decoder *decoder) +{ + struct decoder_control *dc = decoder->dc; + + dc->Lock(); + + assert(dc->command != DECODE_COMMAND_NONE || + decoder->initial_seek_running); + assert(dc->command != DECODE_COMMAND_SEEK || + decoder->initial_seek_running || + dc->seek_error || decoder->seeking); + assert(dc->pipe != NULL); + + if (decoder->initial_seek_running) { + assert(!decoder->seeking); + assert(decoder->chunk == NULL); + assert(music_pipe_empty(dc->pipe)); + + decoder->initial_seek_running = false; + decoder->timestamp = dc->start_ms / 1000.; + dc->Unlock(); + return; + } + + if (decoder->seeking) { + decoder->seeking = false; + + /* delete frames from the old song position */ + + if (decoder->chunk != NULL) { + music_buffer_return(dc->buffer, decoder->chunk); + decoder->chunk = NULL; + } + + music_pipe_clear(dc->pipe, dc->buffer); + + decoder->timestamp = dc->seek_where; + } + + dc->command = DECODE_COMMAND_NONE; + dc->client_cond.signal(); + dc->Unlock(); +} + +double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder) +{ + const struct decoder_control *dc = decoder->dc; + + assert(dc->pipe != NULL); + + if (decoder->initial_seek_running) + return dc->start_ms / 1000.; + + assert(dc->command == DECODE_COMMAND_SEEK); + + decoder->seeking = true; + + return dc->seek_where; +} + +void decoder_seek_error(struct decoder * decoder) +{ + struct decoder_control *dc = decoder->dc; + + assert(dc->pipe != NULL); + + if (decoder->initial_seek_running) { + /* d'oh, we can't seek to the sub-song start position, + what now? - no idea, ignoring the problem for now. */ + decoder->initial_seek_running = false; + return; + } + + assert(dc->command == DECODE_COMMAND_SEEK); + + dc->seek_error = true; + decoder->seeking = false; + + decoder_command_finished(decoder); +} + +/** + * Should be read operation be cancelled? That is the case when the + * player thread has sent a command such as "STOP". + */ +G_GNUC_PURE +static inline bool +decoder_check_cancel_read(const struct decoder *decoder) +{ + if (decoder == NULL) + return false; + + const struct decoder_control *dc = decoder->dc; + if (dc->command == DECODE_COMMAND_NONE) + return false; + + /* ignore the SEEK command during initialization, the plugin + should handle that after it has initialized successfully */ + if (dc->command == DECODE_COMMAND_SEEK && + (dc->state == DECODE_STATE_START || decoder->seeking)) + return false; + + return true; +} + +size_t decoder_read(struct decoder *decoder, + struct input_stream *is, + void *buffer, size_t length) +{ + /* XXX don't allow decoder==NULL */ + GError *error = NULL; + size_t nbytes; + + assert(decoder == NULL || + decoder->dc->state == DECODE_STATE_START || + decoder->dc->state == DECODE_STATE_DECODE); + assert(is != NULL); + assert(buffer != NULL); + + if (length == 0) + return 0; + + input_stream_lock(is); + + while (true) { + if (decoder_check_cancel_read(decoder)) { + input_stream_unlock(is); + return 0; + } + + if (input_stream_available(is)) + break; + + is->cond.wait(is->mutex); + } + + nbytes = input_stream_read(is, buffer, length, &error); + assert(nbytes == 0 || error == NULL); + assert(nbytes > 0 || error != NULL || input_stream_eof(is)); + + if (G_UNLIKELY(nbytes == 0 && error != NULL)) { + g_warning("%s", error->message); + g_error_free(error); + } + + input_stream_unlock(is); + + return nbytes; +} + +void +decoder_timestamp(struct decoder *decoder, double t) +{ + assert(decoder != NULL); + assert(t >= 0); + + decoder->timestamp = t; +} + +/** + * Sends a #tag as-is to the music pipe. Flushes the current chunk + * (decoder.chunk) if there is one. + */ +static enum decoder_command +do_send_tag(struct decoder *decoder, const struct tag *tag) +{ + struct music_chunk *chunk; + + if (decoder->chunk != NULL) { + /* there is a partial chunk - flush it, we want the + tag in a new chunk */ + decoder_flush_chunk(decoder); + decoder->dc->client_cond.signal(); + } + + assert(decoder->chunk == NULL); + + chunk = decoder_get_chunk(decoder); + if (chunk == NULL) { + assert(decoder->dc->command != DECODE_COMMAND_NONE); + return decoder->dc->command; + } + + chunk->tag = tag_dup(tag); + return DECODE_COMMAND_NONE; +} + +static bool +update_stream_tag(struct decoder *decoder, struct input_stream *is) +{ + struct tag *tag; + + tag = is != NULL + ? input_stream_lock_tag(is) + : NULL; + if (tag == NULL) { + tag = decoder->song_tag; + if (tag == NULL) + return false; + + /* no stream tag present - submit the song tag + instead */ + decoder->song_tag = NULL; + } + + if (decoder->stream_tag != NULL) + tag_free(decoder->stream_tag); + + decoder->stream_tag = tag; + return true; +} + +enum decoder_command +decoder_data(struct decoder *decoder, + struct input_stream *is, + const void *data, size_t length, + uint16_t kbit_rate) +{ + struct decoder_control *dc = decoder->dc; + GError *error = NULL; + enum decoder_command cmd; + + assert(dc->state == DECODE_STATE_DECODE); + assert(dc->pipe != NULL); + assert(length % audio_format_frame_size(&dc->in_audio_format) == 0); + + dc->Lock(); + cmd = decoder_get_virtual_command(decoder); + dc->Unlock(); + + if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK || + length == 0) + return cmd; + + /* send stream tags */ + + if (update_stream_tag(decoder, is)) { + if (decoder->decoder_tag != NULL) { + /* merge with tag from decoder plugin */ + struct tag *tag; + + tag = tag_merge(decoder->decoder_tag, + decoder->stream_tag); + cmd = do_send_tag(decoder, tag); + tag_free(tag); + } else + /* send only the stream tag */ + cmd = do_send_tag(decoder, decoder->stream_tag); + + if (cmd != DECODE_COMMAND_NONE) + return cmd; + } + + if (!audio_format_equals(&dc->in_audio_format, &dc->out_audio_format)) { + data = decoder->conv_state.Convert(&dc->in_audio_format, + data, length, + &dc->out_audio_format, + &length, + &error); + if (data == NULL) { + /* the PCM conversion has failed - stop + playback, since we have no better way to + bail out */ + g_warning("%s", error->message); + return DECODE_COMMAND_STOP; + } + } + + while (length > 0) { + struct music_chunk *chunk; + size_t nbytes; + bool full; + + chunk = decoder_get_chunk(decoder); + if (chunk == NULL) { + assert(dc->command != DECODE_COMMAND_NONE); + return dc->command; + } + + void *dest = chunk->Write(dc->out_audio_format, + decoder->timestamp - + dc->song->start_ms / 1000.0, + kbit_rate, &nbytes); + if (dest == NULL) { + /* the chunk is full, flush it */ + decoder_flush_chunk(decoder); + dc->client_cond.signal(); + continue; + } + + assert(nbytes > 0); + + if (nbytes > length) + nbytes = length; + + /* copy the buffer */ + + memcpy(dest, data, nbytes); + + /* expand the music pipe chunk */ + + full = chunk->Expand(dc->out_audio_format, nbytes); + if (full) { + /* the chunk is full, flush it */ + decoder_flush_chunk(decoder); + dc->client_cond.signal(); + } + + data = (const uint8_t *)data + nbytes; + length -= nbytes; + + decoder->timestamp += (double)nbytes / + audio_format_time_to_size(&dc->out_audio_format); + + if (dc->end_ms > 0 && + decoder->timestamp >= dc->end_ms / 1000.0) + /* the end of this range has been reached: + stop decoding */ + return DECODE_COMMAND_STOP; + } + + return DECODE_COMMAND_NONE; +} + +enum decoder_command +decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, + const struct tag *tag) +{ + G_GNUC_UNUSED const struct decoder_control *dc = decoder->dc; + enum decoder_command cmd; + + assert(dc->state == DECODE_STATE_DECODE); + assert(dc->pipe != NULL); + assert(tag != NULL); + + /* save the tag */ + + if (decoder->decoder_tag != NULL) + tag_free(decoder->decoder_tag); + decoder->decoder_tag = tag_dup(tag); + + /* check for a new stream tag */ + + update_stream_tag(decoder, is); + + /* check if we're seeking */ + + if (decoder_prepare_initial_seek(decoder)) + /* during initial seek, no music chunk must be created + until seeking is finished; skip the rest of the + function here */ + return DECODE_COMMAND_SEEK; + + /* send tag to music pipe */ + + if (decoder->stream_tag != NULL) { + /* merge with tag from input stream */ + struct tag *merged; + + merged = tag_merge(decoder->stream_tag, decoder->decoder_tag); + cmd = do_send_tag(decoder, merged); + tag_free(merged); + } else + /* send only the decoder tag */ + cmd = do_send_tag(decoder, tag); + + return cmd; +} + +void +decoder_replay_gain(struct decoder *decoder, + const struct replay_gain_info *replay_gain_info) +{ + assert(decoder != NULL); + + if (replay_gain_info != NULL) { + static unsigned serial; + if (++serial == 0) + serial = 1; + + if (REPLAY_GAIN_OFF != replay_gain_mode) { + enum replay_gain_mode rgm = replay_gain_mode; + if (rgm != REPLAY_GAIN_ALBUM) + rgm = REPLAY_GAIN_TRACK; + + decoder->dc->replay_gain_db = 20.0 * log10f( + replay_gain_tuple_scale( + &replay_gain_info->tuples[rgm], + replay_gain_preamp, replay_gain_missing_preamp, + replay_gain_limit)); + } + + decoder->replay_gain_info = *replay_gain_info; + decoder->replay_gain_serial = serial; + + if (decoder->chunk != NULL) { + /* flush the current chunk because the new + replay gain values affect the following + samples */ + decoder_flush_chunk(decoder); + decoder->dc->client_cond.signal(); + } + } else + decoder->replay_gain_serial = 0; +} + +void +decoder_mixramp(struct decoder *decoder, + char *mixramp_start, char *mixramp_end) +{ + assert(decoder != NULL); + struct decoder_control *dc = decoder->dc; + assert(dc != NULL); + + dc->MixRampStart(mixramp_start); + dc->MixRampEnd(mixramp_end); +} diff --git a/src/DecoderControl.cxx b/src/DecoderControl.cxx new file mode 100644 index 000000000..c2331105d --- /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 000000000..c2d7b33aa --- /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/DecoderInternal.cxx b/src/DecoderInternal.cxx new file mode 100644 index 000000000..e390fdfd7 --- /dev/null +++ b/src/DecoderInternal.cxx @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#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. + */ +static enum decoder_command +need_chunks(struct decoder_control *dc, bool do_wait) +{ + if (dc->command == DECODE_COMMAND_STOP || + dc->command == DECODE_COMMAND_SEEK) + return dc->command; + + if (do_wait) { + dc->Wait(); + dc->client_cond.signal(); + + return dc->command; + } + + return DECODE_COMMAND_NONE; +} + +struct music_chunk * +decoder_get_chunk(struct decoder *decoder) +{ + struct decoder_control *dc = decoder->dc; + enum decoder_command cmd; + + assert(decoder != NULL); + + if (decoder->chunk != NULL) + return decoder->chunk; + + do { + decoder->chunk = music_buffer_allocate(dc->buffer); + if (decoder->chunk != NULL) { + decoder->chunk->replay_gain_serial = + decoder->replay_gain_serial; + if (decoder->replay_gain_serial != 0) + decoder->chunk->replay_gain_info = + decoder->replay_gain_info; + + return decoder->chunk; + } + + dc->Lock(); + cmd = need_chunks(dc, true); + dc->Unlock(); + } while (cmd == DECODE_COMMAND_NONE); + + return NULL; +} + +void +decoder_flush_chunk(struct decoder *decoder) +{ + struct decoder_control *dc = decoder->dc; + + assert(decoder != NULL); + assert(decoder->chunk != NULL); + + if (decoder->chunk->IsEmpty()) + music_buffer_return(dc->buffer, decoder->chunk); + else + music_pipe_push(dc->pipe, decoder->chunk); + + decoder->chunk = NULL; +} diff --git a/src/DecoderInternal.hxx b/src/DecoderInternal.hxx new file mode 100644 index 000000000..3423e3f95 --- /dev/null +++ b/src/DecoderInternal.hxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_INTERNAL_HXX +#define MPD_DECODER_INTERNAL_HXX + +#include "decoder_command.h" +#include "PcmConvert.hxx" +#include "replay_gain_info.h" + +struct input_stream; + +struct decoder { + struct decoder_control *dc; + + PcmConvert conv_state; + + /** + * The time stamp of the next data chunk, in seconds. + */ + double timestamp; + + /** + * Is the initial seek (to the start position of the sub-song) + * pending, or has it been performed already? + */ + bool initial_seek_pending; + + /** + * Is the initial seek currently running? During this time, + * the decoder command is SEEK. This flag is set by + * decoder_get_virtual_command(), when the virtual SEEK + * command is generated for the first time. + */ + bool initial_seek_running; + + /** + * This flag is set by decoder_seek_where(), and checked by + * decoder_command_finished(). It is used to clean up after + * seeking. + */ + bool seeking; + + /** + * The tag from the song object. This is only used for local + * files, because we expect the stream server to send us a new + * tag each time we play it. + */ + struct tag *song_tag; + + /** the last tag received from the stream */ + struct tag *stream_tag; + + /** the last tag received from the decoder plugin */ + struct tag *decoder_tag; + + /** the chunk currently being written to */ + struct music_chunk *chunk; + + struct replay_gain_info replay_gain_info; + + /** + * A positive serial number for checking if replay gain info + * has changed since the last check. + */ + unsigned replay_gain_serial; + + 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(); +}; + +/** + * Returns the current chunk the decoder writes to, or allocates a new + * chunk if there is none. + * + * @return the chunk, or NULL if we have received a decoder command + */ +struct music_chunk * +decoder_get_chunk(struct decoder *decoder); + +/** + * Flushes the current chunk. + */ +void +decoder_flush_chunk(struct decoder *decoder); + +#endif diff --git a/src/DecoderList.cxx b/src/DecoderList.cxx new file mode 100644 index 000000000..da9c3fcc6 --- /dev/null +++ b/src/DecoderList.cxx @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DecoderList.hxx" +#include "decoder_plugin.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> + +#include <string.h> + +extern const struct decoder_plugin mad_decoder_plugin; +extern const struct decoder_plugin mpg123_decoder_plugin; +extern const struct decoder_plugin sndfile_decoder_plugin; +extern const struct decoder_plugin audiofile_decoder_plugin; +extern const struct decoder_plugin faad_decoder_plugin; +extern const struct decoder_plugin mpcdec_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 gme_decoder_plugin; + +const struct decoder_plugin *const decoder_plugins[] = { +#ifdef HAVE_MAD + &mad_decoder_plugin, +#endif +#ifdef HAVE_MPG123 + &mpg123_decoder_plugin, +#endif +#ifdef ENABLE_VORBIS_DECODER + &vorbis_decoder_plugin, +#endif +#if defined(HAVE_FLAC) + &oggflac_decoder_plugin, +#endif +#ifdef HAVE_FLAC + &flac_decoder_plugin, +#endif +#ifdef HAVE_OPUS + &opus_decoder_plugin, +#endif +#ifdef ENABLE_SNDFILE + &sndfile_decoder_plugin, +#endif +#ifdef HAVE_AUDIOFILE + &audiofile_decoder_plugin, +#endif + &dsdiff_decoder_plugin, + &dsf_decoder_plugin, +#ifdef HAVE_FAAD + &faad_decoder_plugin, +#endif +#ifdef HAVE_MPCDEC + &mpcdec_decoder_plugin, +#endif +#ifdef HAVE_WAVPACK + &wavpack_decoder_plugin, +#endif +#ifdef HAVE_MODPLUG + &modplug_decoder_plugin, +#endif +#ifdef ENABLE_MIKMOD_DECODER + &mikmod_decoder_plugin, +#endif +#ifdef ENABLE_SIDPLAY + &sidplay_decoder_plugin, +#endif +#ifdef ENABLE_WILDMIDI + &wildmidi_decoder_plugin, +#endif +#ifdef ENABLE_FLUIDSYNTH + &fluidsynth_decoder_plugin, +#endif +#ifdef HAVE_ADPLUG + &adplug_decoder_plugin, +#endif +#ifdef HAVE_FFMPEG + &ffmpeg_decoder_plugin, +#endif +#ifdef HAVE_GME + &gme_decoder_plugin, +#endif + &pcm_decoder_plugin, + NULL +}; + +enum { + num_decoder_plugins = G_N_ELEMENTS(decoder_plugins) - 1, +}; + +/** which plugins have been initialized successfully? */ +bool decoder_plugins_enabled[num_decoder_plugins]; + +static unsigned +decoder_plugin_index(const struct decoder_plugin *plugin) +{ + unsigned i = 0; + + while (decoder_plugins[i] != plugin) + ++i; + + return i; +} + +static unsigned +decoder_plugin_next_index(const struct decoder_plugin *plugin) +{ + return plugin == 0 + ? 0 /* start with first plugin */ + : decoder_plugin_index(plugin) + 1; +} + +const struct decoder_plugin * +decoder_plugin_from_suffix(const char *suffix, + const struct decoder_plugin *plugin) +{ + if (suffix == NULL) + return NULL; + + for (unsigned i = decoder_plugin_next_index(plugin); + decoder_plugins[i] != NULL; ++i) { + plugin = decoder_plugins[i]; + if (decoder_plugins_enabled[i] && + decoder_plugin_supports_suffix(plugin, suffix)) + return plugin; + } + + return NULL; +} + +const struct decoder_plugin * +decoder_plugin_from_mime_type(const char *mimeType, unsigned int next) +{ + static unsigned i = num_decoder_plugins; + + if (mimeType == NULL) + return NULL; + + if (!next) + i = 0; + for (; decoder_plugins[i] != NULL; ++i) { + const struct decoder_plugin *plugin = decoder_plugins[i]; + if (decoder_plugins_enabled[i] && + decoder_plugin_supports_mime_type(plugin, mimeType)) { + ++i; + return plugin; + } + } + + return NULL; +} + +const struct decoder_plugin * +decoder_plugin_from_name(const char *name) +{ + decoder_plugins_for_each_enabled(plugin) + if (strcmp(plugin->name, name) == 0) + return plugin; + + return NULL; +} + +/** + * Find the "decoder" configuration block for the specified plugin. + * + * @param plugin_name the name of the decoder plugin + * @return the configuration block, or NULL if none was configured + */ +static const struct config_param * +decoder_plugin_config(const char *plugin_name) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_DECODER, param)) != NULL) { + const char *name = + config_get_block_string(param, "plugin", NULL); + if (name == NULL) + MPD_ERROR("decoder configuration without 'plugin' name in line %d", + param->line); + + if (strcmp(name, plugin_name) == 0) + return param; + } + + return NULL; +} + +void decoder_plugin_init_all(void) +{ + for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) { + const struct decoder_plugin *plugin = decoder_plugins[i]; + const struct config_param *param = + decoder_plugin_config(plugin->name); + + if (!config_get_block_bool(param, "enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + if (decoder_plugin_init(plugin, param)) + decoder_plugins_enabled[i] = true; + } +} + +void decoder_plugin_deinit_all(void) +{ + decoder_plugins_for_each_enabled(plugin) + decoder_plugin_finish(plugin); +} diff --git a/src/DecoderList.hxx b/src/DecoderList.hxx new file mode 100644 index 000000000..8dab8724e --- /dev/null +++ b/src/DecoderList.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_LIST_HXX +#define MPD_DECODER_LIST_HXX + +struct decoder_plugin; + +extern const struct decoder_plugin *const decoder_plugins[]; +extern bool decoder_plugins_enabled[]; + +#define decoder_plugins_for_each(plugin) \ + for (const struct decoder_plugin *plugin, \ + *const*decoder_plugin_iterator = &decoder_plugins[0]; \ + (plugin = *decoder_plugin_iterator) != NULL; \ + ++decoder_plugin_iterator) + +#define decoder_plugins_for_each_enabled(plugin) \ + decoder_plugins_for_each(plugin) \ + if (decoder_plugins_enabled[decoder_plugin_iterator - decoder_plugins]) + +/* interface for using plugins */ + +/** + * Find the next enabled decoder plugin which supports the specified suffix. + * + * @param suffix the file name suffix + * @param plugin the previous plugin, or NULL to find the first plugin + * @return a plugin, or NULL if none matches + */ +const struct decoder_plugin * +decoder_plugin_from_suffix(const char *suffix, + const struct decoder_plugin *plugin); + +const struct decoder_plugin * +decoder_plugin_from_mime_type(const char *mimeType, unsigned int next); + +const struct decoder_plugin * +decoder_plugin_from_name(const char *name); + +/* this is where we "load" all the "plugins" ;-) */ +void decoder_plugin_init_all(void); + +/* this is where we "unload" all the "plugins" */ +void decoder_plugin_deinit_all(void); + +#endif diff --git a/src/DecoderPrint.cxx b/src/DecoderPrint.cxx new file mode 100644 index 000000000..719a499ec --- /dev/null +++ b/src/DecoderPrint.cxx @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DecoderPrint.hxx" +#include "DecoderList.hxx" +#include "decoder_plugin.h" +#include "Client.hxx" + +#include <assert.h> + +static void +decoder_plugin_print(Client *client, + const struct decoder_plugin *plugin) +{ + const char *const*p; + + assert(plugin != NULL); + assert(plugin->name != NULL); + + client_printf(client, "plugin: %s\n", plugin->name); + + if (plugin->suffixes != NULL) + for (p = plugin->suffixes; *p != NULL; ++p) + client_printf(client, "suffix: %s\n", *p); + + if (plugin->mime_types != NULL) + for (p = plugin->mime_types; *p != NULL; ++p) + client_printf(client, "mime_type: %s\n", *p); +} + +void +decoder_list_print(Client *client) +{ + decoder_plugins_for_each_enabled(plugin) + decoder_plugin_print(client, plugin); +} diff --git a/src/DecoderPrint.hxx b/src/DecoderPrint.hxx new file mode 100644 index 000000000..d94ba2cef --- /dev/null +++ b/src/DecoderPrint.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_PRINT_HXX +#define MPD_DECODER_PRINT_HXX + +class Client; + +void +decoder_list_print(Client *client); + +#endif diff --git a/src/DecoderThread.cxx b/src/DecoderThread.cxx new file mode 100644 index 000000000..b7b0bf78c --- /dev/null +++ b/src/DecoderThread.cxx @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DecoderThread.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "decoder_error.h" +#include "decoder_plugin.h" +#include "song.h" +#include "mpd_error.h" +#include "Mapper.hxx" +#include "fs/Path.hxx" +#include "decoder_api.h" +#include "tag.h" +#include "InputStream.hxx" +#include "DecoderList.hxx" + +extern "C" { +#include "replay_gain_ape.h" +#include "uri.h" +} + +#include <glib.h> + +#include <unistd.h> +#include <stdio.h> /* for SEEK_SET */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "decoder_thread" + +/** + * Marks the current decoder command as "finished" and notifies the + * player thread. + * + * @param dc the #decoder_control object; must be locked + */ +static void +decoder_command_finished_locked(struct decoder_control *dc) +{ + assert(dc->command != DECODE_COMMAND_NONE); + + dc->command = DECODE_COMMAND_NONE; + + dc->client_cond.signal(); +} + +/** + * Opens the input stream with input_stream_open(), and waits until + * the stream gets ready. If a decoder STOP command is received + * during that, it cancels the operation (but does not close the + * stream). + * + * Unlock the decoder before calling this function. + * + * @return an input_stream on success or if #DECODE_COMMAND_STOP is + * received, NULL on error + */ +static struct input_stream * +decoder_input_stream_open(struct decoder_control *dc, const char *uri) +{ + GError *error = NULL; + struct input_stream *is; + + is = input_stream_open(uri, dc->mutex, dc->cond, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + return NULL; + } + + /* wait for the input stream to become ready; its metadata + will be available then */ + + dc->Lock(); + + input_stream_update(is); + while (!is->ready && + dc->command != DECODE_COMMAND_STOP) { + dc->Wait(); + + input_stream_update(is); + } + + if (!input_stream_check(is, &error)) { + dc->Unlock(); + + g_warning("%s", error->message); + g_error_free(error); + + return NULL; + } + + dc->Unlock(); + + return is; +} + +static bool +decoder_stream_decode(const struct decoder_plugin *plugin, + struct decoder *decoder, + struct input_stream *input_stream) +{ + assert(plugin != NULL); + assert(plugin->stream_decode != NULL); + assert(decoder != NULL); + assert(decoder->stream_tag == NULL); + assert(decoder->decoder_tag == NULL); + assert(input_stream != NULL); + assert(input_stream->ready); + assert(decoder->dc->state == DECODE_STATE_START); + + g_debug("probing plugin %s", plugin->name); + + if (decoder->dc->command == DECODE_COMMAND_STOP) + return true; + + /* rewind the stream, so each plugin gets a fresh start */ + input_stream_seek(input_stream, 0, SEEK_SET, NULL); + + decoder->dc->Unlock(); + + decoder_plugin_stream_decode(plugin, decoder, input_stream); + + decoder->dc->Lock(); + + assert(decoder->dc->state == DECODE_STATE_START || + decoder->dc->state == DECODE_STATE_DECODE); + + return decoder->dc->state != DECODE_STATE_START; +} + +static bool +decoder_file_decode(const struct decoder_plugin *plugin, + struct decoder *decoder, const char *path) +{ + assert(plugin != NULL); + assert(plugin->file_decode != NULL); + assert(decoder != NULL); + assert(decoder->stream_tag == NULL); + assert(decoder->decoder_tag == NULL); + assert(path != NULL); + assert(g_path_is_absolute(path)); + assert(decoder->dc->state == DECODE_STATE_START); + + g_debug("probing plugin %s", plugin->name); + + if (decoder->dc->command == DECODE_COMMAND_STOP) + return true; + + decoder->dc->Unlock(); + + decoder_plugin_file_decode(plugin, decoder, path); + + decoder->dc->Lock(); + + assert(decoder->dc->state == DECODE_STATE_START || + decoder->dc->state == DECODE_STATE_DECODE); + + return decoder->dc->state != DECODE_STATE_START; +} + +/** + * Hack to allow tracking const decoder plugins in a GSList. + */ +static inline gpointer +deconst_plugin(const struct decoder_plugin *plugin) +{ + return const_cast<struct decoder_plugin *>(plugin); +} + +/** + * Try decoding a stream, using plugins matching the stream's MIME type. + * + * @param tried_r a list of plugins which were tried + */ +static bool +decoder_run_stream_mime_type(struct decoder *decoder, struct input_stream *is, + GSList **tried_r) +{ + assert(tried_r != NULL); + + const struct decoder_plugin *plugin; + unsigned int next = 0; + + if (is->mime.empty()) + return false; + + while ((plugin = decoder_plugin_from_mime_type(is->mime.c_str(), + next++))) { + if (plugin->stream_decode == NULL) + continue; + + if (g_slist_find(*tried_r, plugin) != NULL) + /* don't try a plugin twice */ + continue; + + if (decoder_stream_decode(plugin, decoder, is)) + return true; + + *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin)); + } + + return false; +} + +/** + * Try decoding a stream, using plugins matching the stream's URI + * suffix. + * + * @param tried_r a list of plugins which were tried + */ +static bool +decoder_run_stream_suffix(struct decoder *decoder, struct input_stream *is, + const char *uri, GSList **tried_r) +{ + assert(tried_r != NULL); + + const char *suffix = uri_get_suffix(uri); + const struct decoder_plugin *plugin = NULL; + + if (suffix == NULL) + return false; + + while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) { + if (plugin->stream_decode == NULL) + continue; + + if (g_slist_find(*tried_r, plugin) != NULL) + /* don't try a plugin twice */ + continue; + + if (decoder_stream_decode(plugin, decoder, is)) + return true; + + *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin)); + } + + return false; +} + +/** + * Try decoding a stream, using the fallback plugin. + */ +static bool +decoder_run_stream_fallback(struct decoder *decoder, struct input_stream *is) +{ + const struct decoder_plugin *plugin; + + plugin = decoder_plugin_from_name("mad"); + return plugin != NULL && plugin->stream_decode != NULL && + decoder_stream_decode(plugin, decoder, is); +} + +/** + * Try decoding a stream. + */ +static bool +decoder_run_stream(struct decoder *decoder, const char *uri) +{ + struct decoder_control *dc = decoder->dc; + struct input_stream *input_stream; + bool success; + + dc->Unlock(); + + input_stream = decoder_input_stream_open(dc, uri); + if (input_stream == NULL) { + dc->Lock(); + return false; + } + + dc->Lock(); + + GSList *tried = NULL; + + success = dc->command == DECODE_COMMAND_STOP || + /* first we try mime types: */ + decoder_run_stream_mime_type(decoder, input_stream, &tried) || + /* if that fails, try suffix matching the URL: */ + decoder_run_stream_suffix(decoder, input_stream, uri, + &tried) || + /* fallback to mp3: this is needed for bastard streams + that don't have a suffix or set the mimeType */ + (tried == NULL && + decoder_run_stream_fallback(decoder, input_stream)); + + g_slist_free(tried); + + dc->Unlock(); + input_stream_close(input_stream); + dc->Lock(); + + return success; +} + +/** + * Attempt to load replay gain data, and pass it to + * decoder_replay_gain(). + */ +static void +decoder_load_replay_gain(struct decoder *decoder, const char *path_fs) +{ + struct replay_gain_info info; + if (replay_gain_ape_read(path_fs, &info)) + decoder_replay_gain(decoder, &info); +} + +/** + * Try decoding a file. + */ +static bool +decoder_run_file(struct decoder *decoder, const char *path_fs) +{ + struct decoder_control *dc = decoder->dc; + const char *suffix = uri_get_suffix(path_fs); + const struct decoder_plugin *plugin = NULL; + + if (suffix == NULL) + return false; + + dc->Unlock(); + + decoder_load_replay_gain(decoder, path_fs); + + while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) { + if (plugin->file_decode != NULL) { + dc->Lock(); + + if (decoder_file_decode(plugin, decoder, path_fs)) + return true; + + dc->Unlock(); + } else if (plugin->stream_decode != NULL) { + struct input_stream *input_stream; + bool success; + + input_stream = decoder_input_stream_open(dc, path_fs); + if (input_stream == NULL) + continue; + + dc->Lock(); + + success = decoder_stream_decode(plugin, decoder, + input_stream); + + dc->Unlock(); + + input_stream_close(input_stream); + + if (success) { + dc->Lock(); + return true; + } + } + } + + dc->Lock(); + return false; +} + +static void +decoder_run_song(struct decoder_control *dc, + const struct song *song, const char *uri) +{ + decoder decoder(dc, dc->start_ms > 0, + song->tag != NULL && song_is_file(song) + ? tag_dup(song->tag) : nullptr); + int ret; + + dc->state = DECODE_STATE_START; + + decoder_command_finished_locked(dc); + + ret = song_is_file(song) + ? decoder_run_file(&decoder, uri) + : decoder_run_stream(&decoder, uri); + + dc->Unlock(); + + /* flush the last chunk */ + + if (decoder.chunk != NULL) + decoder_flush_chunk(&decoder); + + dc->Lock(); + + if (ret) + dc->state = DECODE_STATE_STOP; + else { + dc->state = DECODE_STATE_ERROR; + + const char *error_uri = song->uri; + char *allocated = uri_remove_auth(error_uri); + if (allocated != NULL) + error_uri = allocated; + + dc->error = g_error_new(decoder_quark(), 0, + "Failed to decode %s", error_uri); + g_free(allocated); + } + + dc->client_cond.signal(); +} + +static void +decoder_run(struct decoder_control *dc) +{ + dc->ClearError(); + + const struct song *song = dc->song; + char *uri; + + assert(song != NULL); + + if (song_is_file(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; + } + + decoder_run_song(dc, song, uri); + g_free(uri); + +} + +static gpointer +decoder_task(gpointer arg) +{ + struct decoder_control *dc = (struct decoder_control *)arg; + + dc->Lock(); + + do { + assert(dc->state == DECODE_STATE_STOP || + dc->state == DECODE_STATE_ERROR); + + switch (dc->command) { + case DECODE_COMMAND_START: + dc->MixRampStart(nullptr); + dc->MixRampPrevEnd(dc->mixramp_end); + dc->mixramp_end = NULL; /* Don't free, it's copied above. */ + dc->replay_gain_prev_db = dc->replay_gain_db; + dc->replay_gain_db = 0; + + /* fall through */ + + case DECODE_COMMAND_SEEK: + decoder_run(dc); + break; + + case DECODE_COMMAND_STOP: + decoder_command_finished_locked(dc); + break; + + case DECODE_COMMAND_NONE: + dc->Wait(); + break; + } + } while (dc->command != DECODE_COMMAND_NONE || !dc->quit); + + dc->Unlock(); + + return NULL; +} + +void +decoder_thread_start(struct decoder_control *dc) +{ + GError *e = NULL; + + assert(dc->thread == NULL); + + dc->quit = false; + + dc->thread = g_thread_create(decoder_task, dc, true, &e); + if (dc->thread == NULL) + MPD_ERROR("Failed to spawn decoder task: %s", e->message); +} diff --git a/src/DecoderThread.hxx b/src/DecoderThread.hxx new file mode 100644 index 000000000..8efaa2fca --- /dev/null +++ b/src/DecoderThread.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_THREAD_HXX +#define MPD_DECODER_THREAD_HXX + +struct decoder_control; + +void +decoder_thread_start(struct decoder_control *dc); + +#endif diff --git a/src/DespotifyUtils.cxx b/src/DespotifyUtils.cxx new file mode 100644 index 000000000..c9a1edf0c --- /dev/null +++ b/src/DespotifyUtils.cxx @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "DespotifyUtils.hxx" +#include "tag.h" +#include "conf.h" + +#include <glib.h> + +extern "C" { +#include <despotify.h> +} + +static struct despotify_session *g_session; +static void (*registered_callbacks[8])(struct despotify_session *, + int, void *, void *); +static void *registered_callback_data[8]; + +static void callback(struct despotify_session* ds, int sig, + void* data, G_GNUC_UNUSED void* callback_data) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i]; + void *cb_data = registered_callback_data[i]; + + if (cb) + cb(ds, sig, data, cb_data); + } +} + +bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), + void *cb_data) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + + if (!registered_callbacks[i]) { + registered_callbacks[i] = cb; + registered_callback_data[i] = cb_data; + + return true; + } + } + + return false; +} + +void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + + if (registered_callbacks[i] == cb) { + registered_callbacks[i] = NULL; + } + } +} + + +struct tag *mpd_despotify_tag_from_track(struct ds_track *track) +{ + char tracknum[20]; + char comment[80]; + char date[20]; + struct tag *tag; + + tag = tag_new(); + + if (!track->has_meta_data) + return tag; + + g_snprintf(tracknum, sizeof(tracknum), "%d", track->tracknumber); + g_snprintf(date, sizeof(date), "%d", track->year); + g_snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted", + track->file_bitrate / 1000, track->geo_restricted ? "" : "not "); + tag_add_item(tag, TAG_TITLE, track->title); + tag_add_item(tag, TAG_ARTIST, track->artist->name); + tag_add_item(tag, TAG_TRACK, tracknum); + tag_add_item(tag, TAG_ALBUM, track->album); + tag_add_item(tag, TAG_DATE, date); + tag_add_item(tag, TAG_COMMENT, comment); + tag->time = track->length / 1000; + + return tag; +} + +struct despotify_session *mpd_despotify_get_session(void) +{ + const char *user; + const char *passwd; + bool high_bitrate; + + if (g_session) + return g_session; + + user = config_get_string(CONF_DESPOTIFY_USER, NULL); + passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, NULL); + high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true); + + if (user == NULL || passwd == NULL) { + g_debug("disabling despotify because account is not configured"); + return nullptr; + } + + if (!despotify_init()) { + g_debug("Can't initialize despotify\n"); + return nullptr; + } + + g_session = despotify_init_client(callback, NULL, + high_bitrate, true); + if (!g_session) { + g_debug("Can't initialize despotify client\n"); + return nullptr; + } + + if (!despotify_authenticate(g_session, user, passwd)) { + g_debug("Can't authenticate despotify session\n"); + despotify_exit(g_session); + return nullptr; + } + + return g_session; +} diff --git a/src/despotify_utils.h b/src/DespotifyUtils.hxx index 7e35edc3c..7e35edc3c 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 000000000..2b1a34cbf --- /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 000000000..8fa5c2352 --- /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/DirectorySave.cxx b/src/DirectorySave.cxx new file mode 100644 index 000000000..6a5efb058 --- /dev/null +++ b/src/DirectorySave.cxx @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DirectorySave.hxx" +#include "Directory.hxx" +#include "song.h" +#include "SongSave.hxx" +#include "PlaylistDatabase.hxx" +#include "TextFile.hxx" + +#include <assert.h> +#include <string.h> + +#define DIRECTORY_DIR "directory: " +#define DIRECTORY_MTIME "mtime: " +#define DIRECTORY_BEGIN "begin: " +#define DIRECTORY_END "end: " + +/** + * The quark used for GError.domain. + */ +static inline GQuark +directory_quark(void) +{ + return g_quark_from_static_string("directory"); +} + +void +directory_save(FILE *fp, const Directory *directory) +{ + if (!directory->IsRoot()) { + fprintf(fp, DIRECTORY_MTIME "%lu\n", + (unsigned long)directory->mtime); + + fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, directory->GetPath()); + } + + Directory *cur; + directory_for_each_child(cur, directory) { + char *base = g_path_get_basename(cur->path); + + fprintf(fp, DIRECTORY_DIR "%s\n", base); + g_free(base); + + directory_save(fp, cur); + + if (ferror(fp)) + return; + } + + struct song *song; + directory_for_each_song(song, directory) + song_save(fp, song); + + playlist_vector_save(fp, directory->playlists); + + if (!directory->IsRoot()) + fprintf(fp, DIRECTORY_END "%s\n", directory->GetPath()); +} + +static Directory * +directory_load_subdir(TextFile &file, Directory *parent, const char *name, + GError **error_r) +{ + bool success; + + if (parent->FindChild(name) != nullptr) { + g_set_error(error_r, directory_quark(), 0, + "Duplicate subdirectory '%s'", name); + return NULL; + } + + Directory *directory = parent->CreateChild(name); + + const char *line = file.ReadLine(); + if (line == NULL) { + g_set_error(error_r, directory_quark(), 0, + "Unexpected end of file"); + directory->Delete(); + return NULL; + } + + if (g_str_has_prefix(line, DIRECTORY_MTIME)) { + directory->mtime = + g_ascii_strtoull(line + sizeof(DIRECTORY_MTIME) - 1, + NULL, 10); + + line = file.ReadLine(); + if (line == NULL) { + g_set_error(error_r, directory_quark(), 0, + "Unexpected end of file"); + directory->Delete(); + return NULL; + } + } + + if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) { + g_set_error(error_r, directory_quark(), 0, + "Malformed line: %s", line); + directory->Delete(); + return NULL; + } + + success = directory_load(file, directory, error_r); + if (!success) { + directory->Delete(); + return NULL; + } + + return directory; +} + +bool +directory_load(TextFile &file, Directory *directory, GError **error) +{ + const char *line; + + while ((line = file.ReadLine()) != NULL && + !g_str_has_prefix(line, DIRECTORY_END)) { + if (g_str_has_prefix(line, DIRECTORY_DIR)) { + Directory *subdir = + directory_load_subdir(file, directory, + line + sizeof(DIRECTORY_DIR) - 1, + error); + if (subdir == NULL) + return false; + } else if (g_str_has_prefix(line, SONG_BEGIN)) { + const char *name = line + sizeof(SONG_BEGIN) - 1; + struct song *song; + + if (directory->FindSong(name) != nullptr) { + g_set_error(error, directory_quark(), 0, + "Duplicate song '%s'", name); + return false; + } + + song = song_load(file, directory, name, error); + if (song == NULL) + return false; + + directory->AddSong(song); + } else if (g_str_has_prefix(line, PLAYLIST_META_BEGIN)) { + /* duplicate the name, because + playlist_metadata_load() will overwrite the + buffer */ + char *name = g_strdup(line + sizeof(PLAYLIST_META_BEGIN) - 1); + + if (!playlist_metadata_load(file, directory->playlists, + name, error)) { + g_free(name); + return false; + } + + g_free(name); + } else { + g_set_error(error, directory_quark(), 0, + "Malformed line: %s", line); + return false; + } + } + + return true; +} diff --git a/src/DirectorySave.hxx b/src/DirectorySave.hxx new file mode 100644 index 000000000..f4b4816f7 --- /dev/null +++ b/src/DirectorySave.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DIRECTORY_SAVE_HXX +#define MPD_DIRECTORY_SAVE_HXX + +#include "gerror.h" + +#include <stdio.h> + +struct Directory; +class TextFile; + +void +directory_save(FILE *fp, const Directory *directory); + +bool +directory_load(TextFile &file, Directory *directory, GError **error); + +#endif diff --git a/src/ExcludeList.cxx b/src/ExcludeList.cxx new file mode 100644 index 000000000..0daa432dd --- /dev/null +++ b/src/ExcludeList.cxx @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * The .mpdignore backend code. + * + */ + +#include "config.h" +#include "ExcludeList.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" + +#include <assert.h> +#include <string.h> +#include <errno.h> + +bool +ExcludeList::LoadFile(const Path &path_fs) +{ + FILE *file = FOpen(path_fs, FOpenMode::ReadText); + if (file == NULL) { + if (errno != ENOENT) { + const char *msg = g_strerror(errno); + const auto path_utf8 = path_fs.ToUTF8(); + g_debug("Failed to open %s: %s", + path_utf8.c_str(), msg); + } + + return false; + } + + char line[1024]; + while (fgets(line, sizeof(line), file) != NULL) { + char *p = strchr(line, '#'); + if (p != NULL) + *p = 0; + + p = g_strstrip(line); + if (*p != 0) + patterns.emplace_front(p); + } + + fclose(file); + + return true; +} + +bool +ExcludeList::Check(const char *name_fs) const +{ + assert(name_fs != NULL); + + /* XXX include full path name in check */ + + 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 000000000..f3dc1f057 --- /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/FilterConfig.cxx b/src/FilterConfig.cxx new file mode 100644 index 000000000..e56c5a988 --- /dev/null +++ b/src/FilterConfig.cxx @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FilterConfig.hxx" +#include "conf.h" +#include "filter/ChainFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" + +#include <glib.h> + +#include <string.h> + +static GQuark +filter_quark(void) +{ + return g_quark_from_static_string("filter"); +} + +/** + * Find the "filter" configuration block for the specified name. + * + * @param filter_template_name the name of the filter template + * @param error_r space to return an error description + * @return the configuration block, or NULL if none was configured + */ +static const struct config_param * +filter_plugin_config(const char *filter_template_name, GError **error_r) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_AUDIO_FILTER, param)) != NULL) { + const char *name = + config_get_block_string(param, "name", NULL); + if (name == NULL) { + g_set_error(error_r, filter_quark(), 1, + "filter configuration without 'name' name in line %d", + param->line); + return NULL; + } + + if (strcmp(name, filter_template_name) == 0) + return param; + } + + g_set_error(error_r, filter_quark(), 1, + "filter template not found: %s", + filter_template_name); + + return NULL; +} + +/** + * Builds a filter chain from a configuration string on the form + * "name1, name2, name3, ..." by looking up each name among the + * configured filter sections. + * @param chain the chain to append filters on + * @param spec the filter chain specification + * @param error_r space to return an error description + * @return the number of filters which were successfully added + */ +unsigned int +filter_chain_parse(Filter &chain, const char *spec, GError **error_r) +{ + + // Split on comma + gchar** tokens = g_strsplit_set(spec, ",", 255); + + unsigned added_filters = 0; + + // Add each name to the filter chain by instantiating an actual filter + char **template_names = tokens; + while (*template_names != NULL) { + // Squeeze whitespace + g_strstrip(*template_names); + + const struct config_param *cfg = + filter_plugin_config(*template_names, error_r); + if (cfg == NULL) { + // The error has already been set, just stop. + break; + } + + // Instantiate one of those filter plugins with the template name as a hint + Filter *f = filter_configured_new(cfg, error_r); + if (f == NULL) { + // The error has already been set, just stop. + break; + } + + const char *plugin_name = + config_get_block_string(cfg, "plugin", "unknown"); + + filter_chain_append(chain, plugin_name, f); + ++added_filters; + + ++template_names; + } + + g_strfreev(tokens); + + return added_filters; +} diff --git a/src/FilterConfig.hxx b/src/FilterConfig.hxx new file mode 100644 index 000000000..bad186354 --- /dev/null +++ b/src/FilterConfig.hxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Utility functions for filter configuration + */ + +#ifndef MPD_FILTER_CONFIG_HXX +#define MPD_FILTER_CONFIG_HXX + +#include "gerror.h" + +class Filter; + +/** + * Builds a filter chain from a configuration string on the form + * "name1, name2, name3, ..." by looking up each name among the + * configured filter sections. + * @param chain the chain to append filters on + * @param spec the filter chain specification + * @param error_r space to return an error description + * @return the number of filters which were successfully added + */ +unsigned int +filter_chain_parse(Filter &chain, const char *spec, GError **error_r); + +#endif diff --git a/src/FilterInternal.hxx b/src/FilterInternal.hxx new file mode 100644 index 000000000..cdc2d0ea1 --- /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/FilterPlugin.cxx b/src/FilterPlugin.cxx new file mode 100644 index 000000000..953f404ed --- /dev/null +++ b/src/FilterPlugin.cxx @@ -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. + */ + +#include "config.h" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "conf.h" +#include "ConfigQuark.hxx" + +#include <assert.h> + +Filter * +filter_new(const struct filter_plugin *plugin, + const struct config_param *param, GError **error_r) +{ + assert(plugin != NULL); + assert(error_r == NULL || *error_r == NULL); + + return plugin->init(param, error_r); +} + +Filter * +filter_configured_new(const struct config_param *param, GError **error_r) +{ + const char *plugin_name; + const struct filter_plugin *plugin; + + assert(param != NULL); + assert(error_r == NULL || *error_r == NULL); + + plugin_name = config_get_block_string(param, "plugin", NULL); + if (plugin_name == NULL) { + g_set_error(error_r, config_quark(), 0, + "No filter plugin specified"); + return NULL; + } + + plugin = filter_plugin_by_name(plugin_name); + if (plugin == NULL) { + g_set_error(error_r, config_quark(), 0, + "No such filter plugin: %s", plugin_name); + return NULL; + } + + return filter_new(plugin, param, error_r); +} diff --git a/src/FilterPlugin.hxx b/src/FilterPlugin.hxx new file mode 100644 index 000000000..80bb8a0b6 --- /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/FilterRegistry.cxx b/src/FilterRegistry.cxx new file mode 100644 index 000000000..c8aff8298 --- /dev/null +++ b/src/FilterRegistry.cxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FilterRegistry.hxx" +#include "FilterPlugin.hxx" + +#include <stddef.h> +#include <string.h> + +const struct filter_plugin *const filter_plugins[] = { + &null_filter_plugin, + &route_filter_plugin, + &normalize_filter_plugin, + &volume_filter_plugin, + &replay_gain_filter_plugin, + NULL, +}; + +const struct filter_plugin * +filter_plugin_by_name(const char *name) +{ + for (unsigned i = 0; filter_plugins[i] != NULL; ++i) + if (strcmp(filter_plugins[i]->name, name) == 0) + return filter_plugins[i]; + + return NULL; +} diff --git a/src/FilterRegistry.hxx b/src/FilterRegistry.hxx new file mode 100644 index 000000000..c21d8a597 --- /dev/null +++ b/src/FilterRegistry.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This library manages all filter plugins which are enabled at + * compile time. + */ + +#ifndef MPD_FILTER_REGISTRY_HXX +#define MPD_FILTER_REGISTRY_HXX + +extern const struct filter_plugin null_filter_plugin; +extern const struct filter_plugin chain_filter_plugin; +extern const struct filter_plugin convert_filter_plugin; +extern const struct filter_plugin route_filter_plugin; +extern const struct filter_plugin normalize_filter_plugin; +extern const struct filter_plugin volume_filter_plugin; +extern const struct filter_plugin replay_gain_filter_plugin; + +const struct filter_plugin * +filter_plugin_by_name(const char *name); + +#endif diff --git a/src/GlobalEvents.cxx b/src/GlobalEvents.cxx new file mode 100644 index 000000000..e4f335c9e --- /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 000000000..2e549305b --- /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/IOThread.cxx b/src/IOThread.cxx new file mode 100644 index 000000000..192d4cc49 --- /dev/null +++ b/src/IOThread.cxx @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "IOThread.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "event/Loop.hxx" + +#include <assert.h> + +static struct { + Mutex mutex; + Cond cond; + + EventLoop *loop; + GThread *thread; +} io; + +void +io_thread_run(void) +{ + assert(io_thread_inside()); + assert(io.loop != NULL); + + io.loop->Run(); +} + +static gpointer +io_thread_func(G_GNUC_UNUSED gpointer arg) +{ + /* lock+unlock to synchronize with io_thread_start(), to be + sure that io.thread is set */ + io.mutex.lock(); + io.mutex.unlock(); + + io_thread_run(); + return NULL; +} + +void +io_thread_init(void) +{ + assert(io.loop == NULL); + assert(io.thread == NULL); + + io.loop = new EventLoop(); +} + +bool +io_thread_start(GError **error_r) +{ + assert(io.loop != NULL); + assert(io.thread == NULL); + + io.mutex.lock(); + io.thread = g_thread_create(io_thread_func, NULL, true, error_r); + io.mutex.unlock(); + if (io.thread == NULL) + return false; + + return true; +} + +void +io_thread_quit(void) +{ + assert(io.loop != NULL); + + io.loop->Break(); +} + +void +io_thread_deinit(void) +{ + if (io.thread != NULL) { + io_thread_quit(); + + g_thread_join(io.thread); + } + + delete io.loop; +} + +EventLoop & +io_thread_get() +{ + assert(io.loop != nullptr); + + return *io.loop; +} + +bool +io_thread_inside(void) +{ + return io.thread != NULL && g_thread_self() == io.thread; +} + +struct call_data { + GThreadFunc function; + gpointer data; + bool done; + gpointer result; +}; + +static gboolean +io_thread_call_func(gpointer _data) +{ + struct call_data *data = (struct call_data *)_data; + + gpointer result = data->function(data->data); + + io.mutex.lock(); + data->done = true; + data->result = result; + io.cond.broadcast(); + io.mutex.unlock(); + + return false; +} + +gpointer +io_thread_call(GThreadFunc function, gpointer _data) +{ + assert(io.thread != NULL); + + if (io_thread_inside()) + /* we're already in the I/O thread - no + synchronization needed */ + return function(_data); + + struct call_data data = { + function, + _data, + false, + nullptr, + }; + + io.loop->AddIdle(io_thread_call_func, &data); + + io.mutex.lock(); + while (!data.done) + io.cond.wait(io.mutex); + io.mutex.unlock(); + + return data.result; +} diff --git a/src/IOThread.hxx b/src/IOThread.hxx new file mode 100644 index 000000000..a9401dc7f --- /dev/null +++ b/src/IOThread.hxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_IO_THREAD_HXX +#define MPD_IO_THREAD_HXX + +#include "gcc.h" + +#include <glib.h> + +class EventLoop; + +void +io_thread_init(void); + +bool +io_thread_start(GError **error_r); + +/** + * Run the I/O event loop synchronously in the current thread. This + * can be called instead of io_thread_start(). For testing purposes + * only. + */ +void +io_thread_run(void); + +/** + * Ask the I/O thread to quit, but does not wait for it. Usually, you + * don't need to call this function, because io_thread_deinit() + * includes this. + */ +void +io_thread_quit(void); + +void +io_thread_deinit(void); + +gcc_pure +EventLoop & +io_thread_get(); + +/** + * Is the current thread the I/O thread? + */ +gcc_pure +bool +io_thread_inside(void); + +/** + * Call a function synchronously in the I/O thread. + */ +gpointer +io_thread_call(GThreadFunc function, gpointer data); + +#endif diff --git a/src/IcyMetaDataParser.cxx b/src/IcyMetaDataParser.cxx new file mode 100644 index 000000000..cda63da44 --- /dev/null +++ b/src/IcyMetaDataParser.cxx @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "IcyMetaDataParser.hxx" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "icy_metadata" + +void +IcyMetaDataParser::Reset() +{ + if (!IsDefined()) + return; + + if (data_rest == 0 && meta_size > 0) + g_free(meta_data); + + if (tag != nullptr) + tag_free(tag); + + data_rest = data_size; + meta_size = 0; +} + +size_t +IcyMetaDataParser::Data(size_t length) +{ + assert(length > 0); + + if (!IsDefined()) + return length; + + if (data_rest == 0) + return 0; + + if (length >= data_rest) { + length = data_rest; + data_rest = 0; + } else + data_rest -= length; + + return length; +} + +static void +icy_add_item(struct tag *tag, enum tag_type type, const char *value) +{ + size_t length = strlen(value); + + if (length >= 2 && value[0] == '\'' && value[length - 1] == '\'') { + /* strip the single quotes */ + ++value; + length -= 2; + } + + if (length > 0) + tag_add_item_n(tag, type, value, length); +} + +static void +icy_parse_tag_item(struct tag *tag, const char *item) +{ + gchar **p = g_strsplit(item, "=", 0); + + if (p[0] != nullptr && p[1] != nullptr) { + if (strcmp(p[0], "StreamTitle") == 0) + icy_add_item(tag, TAG_TITLE, p[1]); + else + g_debug("unknown icy-tag: '%s'", p[0]); + } + + g_strfreev(p); +} + +static struct tag * +icy_parse_tag(const char *p) +{ + struct tag *tag = tag_new(); + gchar **items = g_strsplit(p, ";", 0); + + for (unsigned i = 0; items[i] != nullptr; ++i) + icy_parse_tag_item(tag, items[i]); + + g_strfreev(items); + + return tag; +} + +size_t +IcyMetaDataParser::Meta(const void *data, size_t length) +{ + const unsigned char *p = (const unsigned char *)data; + + assert(IsDefined()); + assert(data_rest == 0); + assert(length > 0); + + if (meta_size == 0) { + /* read meta_size from the first byte of a meta + block */ + meta_size = *p++ * 16; + if (meta_size == 0) { + /* special case: no metadata */ + data_rest = data_size; + return 1; + } + + /* 1 byte was consumed (must be re-added later for the + return value */ + --length; + + /* initialize metadata reader, allocate enough + memory (+1 for the null terminator) */ + meta_position = 0; + meta_data = (char *)g_malloc(meta_size + 1); + } + + assert(meta_position < meta_size); + + if (length > meta_size - meta_position) + length = meta_size - meta_position; + + memcpy(meta_data + meta_position, p, length); + meta_position += length; + + if (p != data) + /* re-add the first byte (which contained meta_size) */ + ++length; + + if (meta_position == meta_size) { + /* null-terminate the string */ + + meta_data[meta_size] = 0; + + /* parse */ + + if (tag != nullptr) + tag_free(tag); + + tag = icy_parse_tag(meta_data); + g_free(meta_data); + + /* change back to normal data mode */ + + meta_size = 0; + data_rest = data_size; + } + + return length; +} diff --git a/src/IcyMetaDataParser.hxx b/src/IcyMetaDataParser.hxx new file mode 100644 index 000000000..6ccc73f52 --- /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/IcyMetaDataServer.cxx b/src/IcyMetaDataServer.cxx new file mode 100644 index 000000000..b1d075582 --- /dev/null +++ b/src/IcyMetaDataServer.cxx @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "IcyMetaDataServer.hxx" +#include "Page.hxx" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "icy_server" + +char* +icy_server_metadata_header(const char *name, + const char *genre, const char *url, + const char *content_type, int metaint) +{ + return g_strdup_printf("ICY 200 OK\r\n" + "icy-notice1:<BR>This stream requires an audio player!<BR>\r\n" /* TODO */ + "icy-notice2:MPD - The music player daemon<BR>\r\n" + "icy-name: %s\r\n" /* TODO */ + "icy-genre: %s\r\n" /* TODO */ + "icy-url: %s\r\n" /* TODO */ + "icy-pub:1\r\n" + "icy-metaint:%d\r\n" + /* TODO "icy-br:%d\r\n" */ + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + name, + genre, + url, + metaint, + /* bitrate, */ + content_type); +} + +static char * +icy_server_metadata_string(const char *stream_title, const char* stream_url) +{ + gchar *icy_metadata; + guint meta_length; + + // The leading n is a placeholder for the length information + icy_metadata = g_strdup_printf("nStreamTitle='%s';" + "StreamUrl='%s';", + stream_title, + stream_url); + + g_return_val_if_fail(icy_metadata, NULL); + + meta_length = strlen(icy_metadata); + + meta_length--; // subtract placeholder + + meta_length = ((int)meta_length / 16) + 1; + + icy_metadata[0] = meta_length; + + if (meta_length > 255) { + g_free(icy_metadata); + return NULL; + } + + return icy_metadata; +} + +Page * +icy_server_metadata_page(const struct tag *tag, const enum tag_type *types) +{ + const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES]; + gint last_item, item; + guint position; + gchar *icy_string; + gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata - + // "StreamTitle='';StreamUrl='';" + // = 4081 - 28 + stream_title[0] = '\0'; + + last_item = -1; + + while (*types != TAG_NUM_OF_ITEM_TYPES) { + const gchar *tag_item = tag_get_value(tag, *types); + if (tag_item) + tag_items[++last_item] = tag_item; + } + + position = item = 0; + while (position < sizeof(stream_title) && item <= last_item) { + gint length = 0; + + length = g_strlcpy(stream_title + position, + tag_items[item++], + sizeof(stream_title) - position); + + position += length; + + if (item <= last_item) { + length = g_strlcpy(stream_title + position, + " - ", + sizeof(stream_title) - position); + + position += length; + } + } + + icy_string = icy_server_metadata_string(stream_title, ""); + + if (icy_string == NULL) + return NULL; + + Page *icy_metadata = Page::Copy(icy_string, (icy_string[0] * 16) + 1); + + g_free(icy_string); + + return icy_metadata; +} diff --git a/src/IcyMetaDataServer.hxx b/src/IcyMetaDataServer.hxx new file mode 100644 index 000000000..b344c61f2 --- /dev/null +++ b/src/IcyMetaDataServer.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_ICY_META_DATA_SERVER_HXX +#define MPD_ICY_META_DATA_SERVER_HXX + +#include "tag.h" + +class Page; + +char* +icy_server_metadata_header(const char *name, + const char *genre, const char *url, + const char *content_type, int metaint); + +Page * +icy_server_metadata_page(const struct tag *tag, const enum tag_type *types); + +#endif diff --git a/src/IdTable.hxx b/src/IdTable.hxx new file mode 100644 index 000000000..8925fe8ab --- /dev/null +++ b/src/IdTable.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ID_TABLE_HXX +#define MPD_ID_TABLE_HXX + +#include "gcc.h" + +#include <algorithm> + +#include <assert.h> + +/** + * A table that maps id numbers to position numbers. + */ +class IdTable { + unsigned size; + + unsigned next; + + int *data; + +public: + IdTable(unsigned _size):size(_size), next(1), data(new int[size]) { + std::fill(data, data + size, -1); + } + + ~IdTable() { + delete[] data; + } + + int IdToPosition(unsigned id) const { + return id < size + ? data[id] + : -1; + } + + unsigned GenerateId() { + assert(next > 0); + assert(next < size); + + while (true) { + unsigned id = next; + + ++next; + if (next == size) + next = 1; + + if (data[id] < 0) + return id; + } + } + + unsigned Insert(unsigned position) { + unsigned id = GenerateId(); + data[id] = position; + return id; + } + + void Move(unsigned id, unsigned position) { + assert(id < size); + assert(data[id] >= 0); + + data[id] = position; + } + + void Erase(unsigned id) { + assert(id < size); + assert(data[id] >= 0); + + data[id] = -1; + } +}; + +#endif diff --git a/src/Idle.cxx b/src/Idle.cxx new file mode 100644 index 000000000..fefbd2fe9 --- /dev/null +++ b/src/Idle.cxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Support library for the "idle" command. + * + */ + +#include "config.h" +#include "Idle.hxx" +#include "GlobalEvents.hxx" + +#include <atomic> + +#include <assert.h> + +static std::atomic_uint idle_flags; + +static const char *const idle_names[] = { + "database", + "stored_playlist", + "playlist", + "player", + "mixer", + "output", + "options", + "sticker", + "update", + "subscription", + "message", + nullptr +}; + +void +idle_add(unsigned flags) +{ + assert(flags != 0); + + unsigned old_flags = idle_flags.fetch_or(flags); + + if ((old_flags & flags) != flags) + GlobalEvents::Emit(GlobalEvents::IDLE); +} + +unsigned +idle_get(void) +{ + return idle_flags.fetch_and(0); +} + +const char*const* +idle_get_names(void) +{ + return idle_names; +} diff --git a/src/Idle.hxx b/src/Idle.hxx new file mode 100644 index 000000000..6e4fbf787 --- /dev/null +++ b/src/Idle.hxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Support library for the "idle" command. + * + */ + +#ifndef MPD_IDLE_HXX +#define MPD_IDLE_HXX + +enum { + /** song database has been updated*/ + IDLE_DATABASE = 0x1, + + /** a stored playlist has been modified, created, deleted or + renamed */ + IDLE_STORED_PLAYLIST = 0x2, + + /** the current playlist has been modified */ + IDLE_PLAYLIST = 0x4, + + /** the player state has changed: play, stop, pause, seek, ... */ + IDLE_PLAYER = 0x8, + + /** the volume has been modified */ + IDLE_MIXER = 0x10, + + /** an audio output device has been enabled or disabled */ + IDLE_OUTPUT = 0x20, + + /** options have changed: crossfade, random, repeat, ... */ + IDLE_OPTIONS = 0x40, + + /** a sticker has been modified. */ + IDLE_STICKER = 0x80, + + /** a database update has started or finished. */ + IDLE_UPDATE = 0x100, + + /** a client has subscribed or unsubscribed to/from a channel */ + IDLE_SUBSCRIPTION = 0x200, + + /** a message on the subscribed channel was receivedd */ + IDLE_MESSAGE = 0x400, +}; + +/** + * Adds idle flag (with bitwise "or") and queues notifications to all + * clients. + */ +void +idle_add(unsigned flags); + +/** + * Atomically reads and resets the global idle flags value. + */ +unsigned +idle_get(void); + +/** + * Get idle names + */ +const char*const* +idle_get_names(void); + +#endif diff --git a/src/InotifyQueue.cxx b/src/InotifyQueue.cxx new file mode 100644 index 000000000..3212f95f9 --- /dev/null +++ b/src/InotifyQueue.cxx @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "InotifyQueue.hxx" +#include "UpdateGlue.hxx" +#include "event/Loop.hxx" + +#include <glib.h> + +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "inotify" + +enum { + /** + * Wait this long after the last change before calling + * update_enqueue(). This increases the probability that + * updates can be bundled. + */ + INOTIFY_UPDATE_DELAY_S = 5, +}; + +bool +InotifyQueue::OnTimeout() +{ + unsigned id; + + while (!queue.empty()) { + const char *uri_utf8 = queue.front().c_str(); + + id = update_enqueue(uri_utf8, false); + if (id == 0) + /* retry later */ + return true; + + g_debug("updating '%s' job=%u", uri_utf8, id); + + queue.pop_front(); + } + + /* done, remove the timer event by returning false */ + return false; +} + +static bool +path_in(const char *path, const char *possible_parent) +{ + size_t length = strlen(possible_parent); + + return path[0] == 0 || + (memcmp(possible_parent, path, length) == 0 && + (path[length] == 0 || path[length] == '/')); +} + +void +InotifyQueue::Enqueue(const char *uri_utf8) +{ + ScheduleSeconds(INOTIFY_UPDATE_DELAY_S); + + for (auto i = queue.begin(), end = queue.end(); i != end;) { + const char *current_uri = i->c_str(); + + if (path_in(uri_utf8, current_uri)) + /* already enqueued */ + return; + + if (path_in(current_uri, uri_utf8)) + /* existing path is a sub-path of the new + path; we can dequeue the existing path and + update the new path instead */ + i = queue.erase(i); + else + ++i; + } + + queue.emplace_back(uri_utf8); +} diff --git a/src/InotifyQueue.hxx b/src/InotifyQueue.hxx new file mode 100644 index 000000000..761df574a --- /dev/null +++ b/src/InotifyQueue.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INOTIFY_QUEUE_HXX +#define MPD_INOTIFY_QUEUE_HXX + +#include "event/TimeoutMonitor.hxx" +#include "gcc.h" + +#include <list> +#include <string> + +class InotifyQueue final : private TimeoutMonitor { + std::list<std::string> queue; + +public: + InotifyQueue(EventLoop &_loop):TimeoutMonitor(_loop) {} + + void Enqueue(const char *uri_utf8); + +private: + virtual bool OnTimeout() override; +}; + +#endif diff --git a/src/InotifySource.cxx b/src/InotifySource.cxx new file mode 100644 index 000000000..5552ed578 --- /dev/null +++ b/src/InotifySource.cxx @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "InotifySource.hxx" +#include "util/fifo_buffer.h" +#include "fd_util.h" +#include "mpd_error.h" + +#include <glib.h> + +#include <sys/inotify.h> +#include <unistd.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "inotify" + +/** + * A GQuark for GError instances. + */ +static inline GQuark +mpd_inotify_quark(void) +{ + return g_quark_from_static_string("inotify"); +} + +bool +InotifySource::OnSocketReady(gcc_unused unsigned flags) +{ + void *dest; + size_t length; + ssize_t nbytes; + + dest = fifo_buffer_write(buffer, &length); + if (dest == NULL) + MPD_ERROR("buffer full"); + + nbytes = read(Get(), dest, length); + if (nbytes < 0) + MPD_ERROR("failed to read from inotify: %s", + g_strerror(errno)); + if (nbytes == 0) + MPD_ERROR("end of file from inotify"); + + fifo_buffer_append(buffer, nbytes); + + while (true) { + const char *name; + + const struct inotify_event *event = + (const struct inotify_event *) + fifo_buffer_read(buffer, &length); + if (event == NULL || length < sizeof(*event) || + length < sizeof(*event) + event->len) + break; + + if (event->len > 0 && event->name[event->len - 1] == 0) + name = event->name; + else + name = NULL; + + callback(event->wd, event->mask, name, callback_ctx); + fifo_buffer_consume(buffer, sizeof(*event) + event->len); + } + + return true; +} + +inline +InotifySource::InotifySource(EventLoop &_loop, + mpd_inotify_callback_t _callback, void *_ctx, + int _fd) + :SocketMonitor(_fd, _loop), + callback(_callback), callback_ctx(_ctx), + buffer(fifo_buffer_new(4096)) +{ + ScheduleRead(); + +} + +InotifySource * +InotifySource::Create(EventLoop &loop, + mpd_inotify_callback_t callback, void *callback_ctx, + GError **error_r) +{ + int fd = inotify_init_cloexec(); + if (fd < 0) { + g_set_error(error_r, mpd_inotify_quark(), errno, + "inotify_init() has failed: %s", + g_strerror(errno)); + return NULL; + } + + return new InotifySource(loop, callback, callback_ctx, fd); +} + +InotifySource::~InotifySource() +{ + fifo_buffer_free(buffer); +} + +int +InotifySource::Add(const char *path_fs, unsigned mask, GError **error_r) +{ + int wd = inotify_add_watch(Get(), path_fs, mask); + if (wd < 0) + g_set_error(error_r, mpd_inotify_quark(), errno, + "inotify_add_watch() has failed: %s", + g_strerror(errno)); + + return wd; +} + +void +InotifySource::Remove(unsigned wd) +{ + int ret = inotify_rm_watch(Get(), wd); + if (ret < 0 && errno != EINVAL) + g_warning("inotify_rm_watch() has failed: %s", + g_strerror(errno)); + + /* EINVAL may happen here when the file has been deleted; the + kernel seems to auto-unregister deleted files */ +} diff --git a/src/InotifySource.hxx b/src/InotifySource.hxx new file mode 100644 index 000000000..e8f9ff03c --- /dev/null +++ b/src/InotifySource.hxx @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INOTIFY_SOURCE_HXX +#define MPD_INOTIFY_SOURCE_HXX + +#include "event/SocketMonitor.hxx" +#include "gerror.h" +#include "gcc.h" + +typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask, + const char *name, void *ctx); + +class InotifySource final : private SocketMonitor { + mpd_inotify_callback_t callback; + void *callback_ctx; + + struct fifo_buffer *buffer; + + InotifySource(EventLoop &_loop, + mpd_inotify_callback_t callback, void *ctx, int fd); + +public: + /** + * Creates a new inotify source and registers it in the GLib main + * loop. + * + * @param a callback invoked for events received from the kernel + */ + static InotifySource *Create(EventLoop &_loop, + mpd_inotify_callback_t callback, + void *ctx, + GError **error_r); + + ~InotifySource(); + + + /** + * Adds a path to the notify list. + * + * @return a watch descriptor or -1 on error + */ + int Add(const char *path_fs, unsigned mask, GError **error_r); + + /** + * Removes a path from the notify list. + * + * @param wd the watch descriptor returned by mpd_inotify_source_add() + */ + void Remove(unsigned wd); + +private: + virtual bool OnSocketReady(unsigned flags) override; +}; + +#endif diff --git a/src/InotifyUpdate.cxx b/src/InotifyUpdate.cxx new file mode 100644 index 000000000..2de3bf627 --- /dev/null +++ b/src/InotifyUpdate.cxx @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "InotifyUpdate.hxx" +#include "InotifySource.hxx" +#include "InotifyQueue.hxx" +#include "Mapper.hxx" +#include "Main.hxx" +#include "fs/Path.hxx" + +#include <glib.h> + +#include <map> +#include <forward_list> + +#include <assert.h> +#include <sys/inotify.h> +#include <sys/stat.h> +#include <string.h> +#include <dirent.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "inotify" + +enum { + IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF + |IN_MOVE|IN_MOVE_SELF +#ifdef IN_ONLYDIR + |IN_ONLYDIR +#endif +}; + +struct WatchDirectory { + WatchDirectory *parent; + + char *name; + + int descriptor; + + std::forward_list<WatchDirectory> children; + + WatchDirectory(WatchDirectory *_parent, const char *_name, + int _descriptor) + :parent(_parent), name(g_strdup(_name)), + descriptor(_descriptor) {} + + WatchDirectory(const WatchDirectory &) = delete; + WatchDirectory &operator=(const WatchDirectory &) = delete; + + ~WatchDirectory() { + g_free(name); + } +}; + +static InotifySource *inotify_source; +static InotifyQueue *inotify_queue; + +static unsigned inotify_max_depth; +static WatchDirectory *inotify_root; +static std::map<int, WatchDirectory *> inotify_directories; + +static void +tree_add_watch_directory(WatchDirectory *directory) +{ + inotify_directories.insert(std::make_pair(directory->descriptor, + directory)); +} + +static void +tree_remove_watch_directory(WatchDirectory *directory) +{ + auto i = inotify_directories.find(directory->descriptor); + assert(i != inotify_directories.end()); + inotify_directories.erase(i); +} + +static WatchDirectory * +tree_find_watch_directory(int wd) +{ + auto i = inotify_directories.find(wd); + if (i == inotify_directories.end()) + return nullptr; + + return i->second; +} + +static void +disable_watch_directory(WatchDirectory &directory) +{ + tree_remove_watch_directory(&directory); + + for (WatchDirectory &child : directory.children) + disable_watch_directory(child); + + inotify_source->Remove(directory.descriptor); +} + +static void +remove_watch_directory(WatchDirectory *directory) +{ + assert(directory != NULL); + + if (directory->parent == NULL) { + g_warning("music directory was removed - " + "cannot continue to watch it"); + return; + } + + disable_watch_directory(*directory); + + /* remove it from the parent, which effectively deletes it */ + directory->parent->children.remove_if([directory](const WatchDirectory &child){ + return &child == directory; + }); +} + +static char * +watch_directory_get_uri_fs(const WatchDirectory *directory) +{ + char *parent_uri, *uri; + + if (directory->parent == NULL) + return NULL; + + parent_uri = watch_directory_get_uri_fs(directory->parent); + if (parent_uri == NULL) + return g_strdup(directory->name); + + uri = g_strconcat(parent_uri, "/", directory->name, NULL); + g_free(parent_uri); + + return uri; +} + +/* we don't look at "." / ".." nor files with newlines in their name */ +static bool skip_path(const char *path) +{ + return (path[0] == '.' && path[1] == 0) || + (path[0] == '.' && path[1] == '.' && path[2] == 0) || + strchr(path, '\n') != NULL; +} + +static void +recursive_watch_subdirectories(WatchDirectory *directory, + const char *path_fs, unsigned depth) +{ + GError *error = NULL; + DIR *dir; + struct dirent *ent; + + assert(directory != NULL); + assert(depth <= inotify_max_depth); + assert(path_fs != NULL); + + ++depth; + + if (depth > inotify_max_depth) + return; + + dir = opendir(path_fs); + if (dir == NULL) { + g_warning("Failed to open directory %s: %s", + path_fs, g_strerror(errno)); + return; + } + + while ((ent = readdir(dir))) { + char *child_path_fs; + struct stat st; + int ret; + + if (skip_path(ent->d_name)) + continue; + + child_path_fs = g_strconcat(path_fs, "/", ent->d_name, NULL); + ret = stat(child_path_fs, &st); + if (ret < 0) { + g_warning("Failed to stat %s: %s", + child_path_fs, g_strerror(errno)); + g_free(child_path_fs); + continue; + } + + if (!S_ISDIR(st.st_mode)) { + g_free(child_path_fs); + continue; + } + + ret = inotify_source->Add(child_path_fs, IN_MASK, &error); + if (ret < 0) { + g_warning("Failed to register %s: %s", + child_path_fs, error->message); + g_error_free(error); + error = NULL; + g_free(child_path_fs); + continue; + } + + WatchDirectory *child = tree_find_watch_directory(ret); + if (child != NULL) { + /* already being watched */ + g_free(child_path_fs); + continue; + } + + directory->children.emplace_front(directory, ent->d_name, ret); + child = &directory->children.front(); + + tree_add_watch_directory(child); + + recursive_watch_subdirectories(child, child_path_fs, depth); + g_free(child_path_fs); + } + + closedir(dir); +} + +G_GNUC_PURE +static unsigned +watch_directory_depth(const WatchDirectory *d) +{ + assert(d != NULL); + + unsigned depth = 0; + while ((d = d->parent) != NULL) + ++depth; + + return depth; +} + +static void +mpd_inotify_callback(int wd, unsigned mask, + G_GNUC_UNUSED const char *name, G_GNUC_UNUSED void *ctx) +{ + WatchDirectory *directory; + char *uri_fs; + + /*g_debug("wd=%d mask=0x%x name='%s'", wd, mask, name);*/ + + directory = tree_find_watch_directory(wd); + if (directory == NULL) + return; + + uri_fs = watch_directory_get_uri_fs(directory); + + if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) { + g_free(uri_fs); + remove_watch_directory(directory); + return; + } + + if ((mask & (IN_ATTRIB|IN_CREATE|IN_MOVE)) != 0 && + (mask & IN_ISDIR) != 0) { + /* a sub directory was changed: register those in + inotify */ + const char *root = mapper_get_music_directory_fs().c_str(); + const char *path_fs; + char *allocated = NULL; + + if (uri_fs != NULL) + path_fs = allocated = + g_strconcat(root, "/", uri_fs, NULL); + else + path_fs = root; + + recursive_watch_subdirectories(directory, path_fs, + watch_directory_depth(directory)); + g_free(allocated); + } + + if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0 || + /* at the maximum depth, we watch out for newly created + directories */ + (watch_directory_depth(directory) == inotify_max_depth && + (mask & (IN_CREATE|IN_ISDIR)) == (IN_CREATE|IN_ISDIR))) { + /* a file was changed, or a directory was + moved/deleted: queue a database update */ + + if (uri_fs != nullptr) { + const std::string uri_utf8 = Path::ToUTF8(uri_fs); + if (!uri_utf8.empty()) + inotify_queue->Enqueue(uri_utf8.c_str()); + } + else + inotify_queue->Enqueue(""); + } + + g_free(uri_fs); +} + +void +mpd_inotify_init(unsigned max_depth) +{ + GError *error = NULL; + + g_debug("initializing inotify"); + + const Path &path = mapper_get_music_directory_fs(); + if (path.IsNull()) { + g_debug("no music directory configured"); + return; + } + + inotify_source = InotifySource::Create(*main_loop, + mpd_inotify_callback, nullptr, + &error); + if (inotify_source == NULL) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + + inotify_max_depth = max_depth; + + int descriptor = inotify_source->Add(path.c_str(), IN_MASK, &error); + if (descriptor < 0) { + g_warning("%s", error->message); + g_error_free(error); + delete inotify_source; + inotify_source = NULL; + return; + } + + inotify_root = new WatchDirectory(nullptr, path.c_str(), descriptor); + + tree_add_watch_directory(inotify_root); + + recursive_watch_subdirectories(inotify_root, path.c_str(), 0); + + inotify_queue = new InotifyQueue(*main_loop); + + g_debug("watching music directory"); +} + +void +mpd_inotify_finish(void) +{ + if (inotify_source == NULL) + return; + + delete inotify_queue; + delete inotify_source; + delete inotify_root; + inotify_directories.clear(); +} diff --git a/src/InotifyUpdate.hxx b/src/InotifyUpdate.hxx new file mode 100644 index 000000000..ceb421553 --- /dev/null +++ b/src/InotifyUpdate.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INOTIFY_UPDATE_HXX +#define MPD_INOTIFY_UPDATE_HXX + +#include "check.h" + +#ifdef HAVE_INOTIFY_INIT + +void +mpd_inotify_init(unsigned max_depth); + +void +mpd_inotify_finish(void); + +#else /* !HAVE_INOTIFY_INIT */ + +static inline void +mpd_inotify_init(G_GNUC_UNUSED unsigned max_depth) +{ +} + +static inline void +mpd_inotify_finish(void) +{ +} + +#endif /* !HAVE_INOTIFY_INIT */ + +#endif diff --git a/src/InputInit.cxx b/src/InputInit.cxx new file mode 100644 index 000000000..64cdfc7af --- /dev/null +++ b/src/InputInit.cxx @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "InputInit.hxx" +#include "InputRegistry.hxx" +#include "InputPlugin.hxx" +#include "conf.h" + +#include <assert.h> +#include <string.h> + +static inline GQuark +input_quark(void) +{ + return g_quark_from_static_string("input"); +} + +/** + * Find the "input" configuration block for the specified plugin. + * + * @param plugin_name the name of the input plugin + * @return the configuration block, or NULL if none was configured + */ +static const struct config_param * +input_plugin_config(const char *plugin_name, GError **error_r) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) { + const char *name = + config_get_block_string(param, "plugin", NULL); + if (name == NULL) { + g_set_error(error_r, input_quark(), 0, + "input configuration without 'plugin' name in line %d", + param->line); + return NULL; + } + + if (strcmp(name, plugin_name) == 0) + return param; + } + + return NULL; +} + +bool +input_stream_global_init(GError **error_r) +{ + GError *error = NULL; + + for (unsigned i = 0; input_plugins[i] != NULL; ++i) { + const struct input_plugin *plugin = input_plugins[i]; + + assert(plugin->name != NULL); + assert(*plugin->name != 0); + assert(plugin->open != NULL); + + const struct config_param *param = + input_plugin_config(plugin->name, &error); + if (param == NULL && error != NULL) { + g_propagate_error(error_r, error); + return false; + } + + if (!config_get_block_bool(param, "enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + if (plugin->init == NULL || plugin->init(param, &error)) + input_plugins_enabled[i] = true; + else { + g_propagate_prefixed_error(error_r, error, + "Failed to initialize input plugin '%s': ", + plugin->name); + return false; + } + } + + return true; +} + +void input_stream_global_finish(void) +{ + input_plugins_for_each_enabled(plugin) + if (plugin->finish != NULL) + plugin->finish(); +} diff --git a/src/InputInit.hxx b/src/InputInit.hxx new file mode 100644 index 000000000..9d503e5a8 --- /dev/null +++ b/src/InputInit.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_INIT_HXX +#define MPD_INPUT_INIT_HXX + +#include "gerror.h" + +/** + * Initializes this library and all input_stream implementations. + * + * @param error_r location to store the error occurring, or NULL to + * ignore errors + */ +bool +input_stream_global_init(GError **error_r); + +/** + * Deinitializes this library and all input_stream implementations. + */ +void input_stream_global_finish(void); + +#endif diff --git a/src/InputInternal.cxx b/src/InputInternal.cxx new file mode 100644 index 000000000..e110ebf0a --- /dev/null +++ b/src/InputInternal.cxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "InputInternal.hxx" +#include "InputStream.hxx" + +void +input_stream_signal_client(struct input_stream *is) +{ + is->cond.broadcast(); +} + +void +input_stream_set_ready(struct input_stream *is) +{ + const ScopeLock protect(is->mutex); + + if (!is->ready) { + is->ready = true; + input_stream_signal_client(is); + } +} diff --git a/src/InputInternal.hxx b/src/InputInternal.hxx new file mode 100644 index 000000000..019ac09c6 --- /dev/null +++ b/src/InputInternal.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_INTERNAL_HXX +#define MPD_INPUT_INTERNAL_HXX + +#include "check.h" + +struct input_stream; + +void +input_stream_signal_client(struct input_stream *is); + +void +input_stream_set_ready(struct input_stream *is); + +#endif diff --git a/src/InputPlugin.hxx b/src/InputPlugin.hxx new file mode 100644 index 000000000..c16600810 --- /dev/null +++ b/src/InputPlugin.hxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_PLUGIN_HXX +#define MPD_INPUT_PLUGIN_HXX + +#include "input_stream.h" + +#include <stddef.h> +#include <sys/types.h> + +struct config_param; +struct input_stream; + +struct input_plugin { + const char *name; + + /** + * Global initialization. This method is called when MPD starts. + * + * @param error_r location to store the error occurring, or + * NULL to ignore errors + * @return true on success, false if the plugin should be + * disabled + */ + bool (*init)(const struct config_param *param, GError **error_r); + + /** + * Global deinitialization. Called once before MPD shuts + * down (only if init() has returned true). + */ + void (*finish)(void); + + struct input_stream *(*open)(const char *uri, + Mutex &mutex, Cond &cond, + GError **error_r); + void (*close)(struct input_stream *is); + + /** + * Check for errors that may have occurred in the I/O thread. + * May be unimplemented for synchronous plugins. + * + * @return false on error + */ + bool (*check)(struct input_stream *is, GError **error_r); + + /** + * Update the public attributes. Call before access. Can be + * NULL if the plugin always keeps its attributes up to date. + */ + void (*update)(struct input_stream *is); + + struct tag *(*tag)(struct input_stream *is); + + /** + * Returns true if the next read operation will not block: + * either data is available, or end-of-stream has been + * reached, or an error has occurred. + * + * If this method is unimplemented, then it is assumed that + * reading will never block. + */ + bool (*available)(struct input_stream *is); + + size_t (*read)(struct input_stream *is, void *ptr, size_t size, + GError **error_r); + bool (*eof)(struct input_stream *is); + bool (*seek)(struct input_stream *is, goffset offset, int whence, + GError **error_r); +}; + +#endif diff --git a/src/InputRegistry.cxx b/src/InputRegistry.cxx new file mode 100644 index 000000000..342720d2a --- /dev/null +++ b/src/InputRegistry.cxx @@ -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. + */ + +#include "config.h" +#include "InputRegistry.hxx" +#include "input/FileInputPlugin.hxx" + +#ifdef ENABLE_ARCHIVE +#include "input/ArchiveInputPlugin.hxx" +#endif + +#ifdef ENABLE_CURL +#include "input/CurlInputPlugin.hxx" +#endif + +#ifdef ENABLE_SOUP +#include "input/SoupInputPlugin.hxx" +#endif + +#ifdef HAVE_FFMPEG +#include "input/FfmpegInputPlugin.hxx" +#endif + +#ifdef ENABLE_MMS +#include "input/MmsInputPlugin.hxx" +#endif + +#ifdef ENABLE_CDIO_PARANOIA +#include "input/CdioParanoiaInputPlugin.hxx" +#endif + +#ifdef ENABLE_DESPOTIFY +#include "input/DespotifyInputPlugin.hxx" +#endif + +#include <glib.h> + +const struct input_plugin *const input_plugins[] = { + &input_plugin_file, +#ifdef ENABLE_ARCHIVE + &input_plugin_archive, +#endif +#ifdef ENABLE_CURL + &input_plugin_curl, +#endif +#ifdef ENABLE_SOUP + &input_plugin_soup, +#endif +#ifdef HAVE_FFMPEG + &input_plugin_ffmpeg, +#endif +#ifdef ENABLE_MMS + &input_plugin_mms, +#endif +#ifdef ENABLE_CDIO_PARANOIA + &input_plugin_cdio_paranoia, +#endif +#ifdef ENABLE_DESPOTIFY + &input_plugin_despotify, +#endif + NULL +}; + +bool input_plugins_enabled[G_N_ELEMENTS(input_plugins) - 1]; diff --git a/src/InputRegistry.hxx b/src/InputRegistry.hxx new file mode 100644 index 000000000..a080d108b --- /dev/null +++ b/src/InputRegistry.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_REGISTRY_HXX +#define MPD_INPUT_REGISTRY_HXX + +#include "check.h" + +/** + * NULL terminated list of all input plugins which were enabled at + * compile time. + */ +extern const struct input_plugin *const input_plugins[]; + +extern bool input_plugins_enabled[]; + +#define input_plugins_for_each(plugin) \ + for (const struct input_plugin *plugin, \ + *const*input_plugin_iterator = &input_plugins[0]; \ + (plugin = *input_plugin_iterator) != NULL; \ + ++input_plugin_iterator) + +#define input_plugins_for_each_enabled(plugin) \ + input_plugins_for_each(plugin) \ + if (input_plugins_enabled[input_plugin_iterator - input_plugins]) + +#endif diff --git a/src/InputStream.cxx b/src/InputStream.cxx new file mode 100644 index 000000000..913fcf637 --- /dev/null +++ b/src/InputStream.cxx @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "InputStream.hxx" +#include "InputRegistry.hxx" +#include "InputPlugin.hxx" +#include "input/RewindInputPlugin.hxx" + +extern "C" { +#include "uri.h" +} + +#include <glib.h> +#include <assert.h> + +static inline GQuark +input_quark(void) +{ + return g_quark_from_static_string("input"); +} + +struct input_stream * +input_stream_open(const char *url, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + GError *error = NULL; + + assert(error_r == NULL || *error_r == NULL); + + input_plugins_for_each_enabled(plugin) { + struct input_stream *is; + + is = plugin->open(url, mutex, cond, &error); + if (is != NULL) { + assert(is->plugin.close != NULL); + assert(is->plugin.read != NULL); + assert(is->plugin.eof != NULL); + assert(!is->seekable || is->plugin.seek != NULL); + + is = input_rewind_open(is); + + return is; + } else if (error != NULL) { + g_propagate_error(error_r, error); + return NULL; + } + } + + g_set_error(error_r, input_quark(), 0, "Unrecognized URI"); + return NULL; +} + +bool +input_stream_check(struct input_stream *is, GError **error_r) +{ + assert(is != NULL); + + return is->plugin.check == NULL || + is->plugin.check(is, error_r); +} + +void +input_stream_update(struct input_stream *is) +{ + assert(is != NULL); + + if (is->plugin.update != NULL) + is->plugin.update(is); +} + +void +input_stream_wait_ready(struct input_stream *is) +{ + assert(is != NULL); + + while (true) { + input_stream_update(is); + if (is->ready) + break; + + is->cond.wait(is->mutex); + } +} + +void +input_stream_lock_wait_ready(struct input_stream *is) +{ + assert(is != NULL); + + const ScopeLock protect(is->mutex); + input_stream_wait_ready(is); +} + +const char * +input_stream_get_mime_type(const struct input_stream *is) +{ + assert(is != NULL); + assert(is->ready); + + return is->mime.empty() ? nullptr : is->mime.c_str(); +} + +void +input_stream_override_mime_type(struct input_stream *is, const char *mime) +{ + assert(is != NULL); + assert(is->ready); + + is->mime = mime; +} + +goffset +input_stream_get_size(const struct input_stream *is) +{ + assert(is != NULL); + assert(is->ready); + + return is->size; +} + +goffset +input_stream_get_offset(const struct input_stream *is) +{ + assert(is != NULL); + assert(is->ready); + + return is->offset; +} + +bool +input_stream_is_seekable(const struct input_stream *is) +{ + assert(is != NULL); + assert(is->ready); + + return is->seekable; +} + +bool +input_stream_cheap_seeking(const struct input_stream *is) +{ + return is->seekable && !uri_has_scheme(is->uri.c_str()); +} + +bool +input_stream_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) +{ + assert(is != NULL); + + if (is->plugin.seek == NULL) + return false; + + return is->plugin.seek(is, offset, whence, error_r); +} + +bool +input_stream_lock_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) +{ + assert(is != NULL); + + if (is->plugin.seek == NULL) + return false; + + const ScopeLock protect(is->mutex); + return input_stream_seek(is, offset, whence, error_r); +} + +struct tag * +input_stream_tag(struct input_stream *is) +{ + assert(is != NULL); + + return is->plugin.tag != NULL + ? is->plugin.tag(is) + : NULL; +} + +struct tag * +input_stream_lock_tag(struct input_stream *is) +{ + assert(is != NULL); + + if (is->plugin.tag == NULL) + return nullptr; + + const ScopeLock protect(is->mutex); + return input_stream_tag(is); +} + +bool +input_stream_available(struct input_stream *is) +{ + assert(is != NULL); + + return is->plugin.available != NULL + ? is->plugin.available(is) + : true; +} + +size_t +input_stream_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + assert(ptr != NULL); + assert(size > 0); + + return is->plugin.read(is, ptr, size, error_r); +} + +size_t +input_stream_lock_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + assert(ptr != NULL); + assert(size > 0); + + const ScopeLock protect(is->mutex); + return input_stream_read(is, ptr, size, error_r); +} + +void input_stream_close(struct input_stream *is) +{ + is->plugin.close(is); +} + +bool input_stream_eof(struct input_stream *is) +{ + return is->plugin.eof(is); +} + +bool +input_stream_lock_eof(struct input_stream *is) +{ + assert(is != NULL); + + const ScopeLock protect(is->mutex); + return input_stream_eof(is); +} + diff --git a/src/InputStream.hxx b/src/InputStream.hxx new file mode 100644 index 000000000..96172723a --- /dev/null +++ b/src/InputStream.hxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_STREAM_HXX +#define MPD_INPUT_STREAM_HXX + +#include "input_stream.h" +#include "check.h" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "gcc.h" + +#include <string> + +#include <assert.h> + +struct input_stream { + /** + * the plugin which implements this input stream + */ + const struct input_plugin &plugin; + + /** + * The absolute URI which was used to open this stream. + */ + std::string uri; + + /** + * A mutex that protects the mutable attributes of this object + * and its implementation. It must be locked before calling + * any of the public methods. + * + * This object is allocated by the client, and the client is + * responsible for freeing it. + */ + Mutex &mutex; + + /** + * A cond that gets signalled when the state of this object + * changes from the I/O thread. The client of this object may + * wait on it. Optional, may be NULL. + * + * This object is allocated by the client, and the client is + * responsible for freeing it. + */ + Cond &cond; + + /** + * indicates whether the stream is ready for reading and + * whether the other attributes in this struct are valid + */ + bool ready; + + /** + * if true, then the stream is fully seekable + */ + bool seekable; + + /** + * the size of the resource, or -1 if unknown + */ + goffset size; + + /** + * the current offset within the stream + */ + goffset offset; + + /** + * the MIME content type of the resource, or empty if unknown. + */ + std::string mime; + + input_stream(const input_plugin &_plugin, + const char *_uri, Mutex &_mutex, Cond &_cond) + :plugin(_plugin), uri(_uri), + mutex(_mutex), cond(_cond), + ready(false), seekable(false), + size(-1), offset(0) { + assert(_uri != NULL); + } +}; + +gcc_nonnull(1) +static inline void +input_stream_lock(struct input_stream *is) +{ + is->mutex.lock(); +} + +gcc_nonnull(1) +static inline void +input_stream_unlock(struct input_stream *is) +{ + is->mutex.unlock(); +} + +#endif diff --git a/src/Listen.cxx b/src/Listen.cxx new file mode 100644 index 000000000..26c9e100d --- /dev/null +++ b/src/Listen.cxx @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Listen.hxx" +#include "Main.hxx" +#include "Client.hxx" +#include "conf.h" +#include "event/ServerSocket.hxx" + +#include <string.h> +#include <assert.h> + +#ifdef ENABLE_SYSTEMD_DAEMON +#include <systemd/sd-daemon.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "listen" + +#define DEFAULT_PORT 6600 + +class ClientListener final : public ServerSocket { +public: + ClientListener():ServerSocket(*main_loop) {} + +private: + virtual void OnAccept(int fd, const sockaddr &address, + size_t address_length, int uid) { + client_new(*main_loop, *global_partition, + fd, &address, address_length, uid); + } +}; + +static ClientListener *listen_socket; +int listen_port; + +static bool +listen_add_config_param(unsigned int port, + const struct config_param *param, + GError **error_r) +{ + assert(param != NULL); + + if (0 == strcmp(param->value, "any")) { + return listen_socket->AddPort(port, error_r); + } else if (param->value[0] == '/') { + return listen_socket->AddPath(param->value, error_r); + } else { + return listen_socket->AddHost(param->value, port, error_r); + } +} + +static bool +listen_systemd_activation(GError **error_r) +{ +#ifdef ENABLE_SYSTEMD_DAEMON + int n = sd_listen_fds(true); + if (n <= 0) { + if (n < 0) + g_warning("sd_listen_fds() failed: %s", + g_strerror(-n)); + return false; + } + + for (int i = SD_LISTEN_FDS_START, end = SD_LISTEN_FDS_START + n; + i != end; ++i) + if (!listen_socket->AddFD(i, error_r)) + return false; + + return true; +#else + (void)error_r; + return false; +#endif +} + +bool +listen_global_init(GError **error_r) +{ + assert(main_loop != nullptr); + + int port = config_get_positive(CONF_PORT, DEFAULT_PORT); + const struct config_param *param = + config_get_next_param(CONF_BIND_TO_ADDRESS, NULL); + bool success; + GError *error = NULL; + + listen_socket = new ClientListener(); + + if (listen_systemd_activation(&error)) + return true; + + if (error != NULL) { + g_propagate_error(error_r, error); + return false; + } + + if (param != NULL) { + /* "bind_to_address" is configured, create listeners + for all values */ + + do { + success = listen_add_config_param(port, param, &error); + if (!success) { + delete listen_socket; + g_propagate_prefixed_error(error_r, error, + "Failed to listen on %s (line %i): ", + param->value, param->line); + return false; + } + + param = config_get_next_param(CONF_BIND_TO_ADDRESS, + param); + } while (param != NULL); + } else { + /* no "bind_to_address" configured, bind the + configured port on all interfaces */ + + success = listen_socket->AddPort(port, error_r); + if (!success) { + delete listen_socket; + g_propagate_prefixed_error(error_r, error, + "Failed to listen on *:%d: ", + port); + return false; + } + } + + if (!listen_socket->Open(error_r)) { + delete listen_socket; + return false; + } + + listen_port = port; + return true; +} + +void listen_global_finish(void) +{ + g_debug("listen_global_finish called"); + + assert(listen_socket != NULL); + + delete listen_socket; +} diff --git a/src/Listen.hxx b/src/Listen.hxx new file mode 100644 index 000000000..fd553477b --- /dev/null +++ b/src/Listen.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_LISTEN_HXX +#define MPD_LISTEN_HXX + +#include "gerror.h" + +#include <stdbool.h> + +extern int listen_port; + +bool +listen_global_init(GError **error_r); + +void listen_global_finish(void); + +#endif diff --git a/src/Log.cxx b/src/Log.cxx new file mode 100644 index 000000000..8a50e1efb --- /dev/null +++ b/src/Log.cxx @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Log.hxx" +#include "conf.h" +#include "fd_util.h" +#include "mpd_error.h" + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <stdarg.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include <errno.h> +#include <glib.h> + +#ifdef HAVE_SYSLOG +#include <syslog.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "log" + +#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO + +#define LOG_DATE_BUF_SIZE 16 +#define LOG_DATE_LEN (LOG_DATE_BUF_SIZE - 1) + +static GLogLevelFlags log_threshold = G_LOG_LEVEL_MESSAGE; + +static const char *log_charset; + +static bool stdout_mode = true; +static int out_fd; +static char *out_filename; + +static void redirect_logs(int fd) +{ + assert(fd >= 0); + if (dup2(fd, STDOUT_FILENO) < 0) + MPD_ERROR("problems dup2 stdout : %s\n", strerror(errno)); + if (dup2(fd, STDERR_FILENO) < 0) + MPD_ERROR("problems dup2 stderr : %s\n", strerror(errno)); +} + +static const char *log_date(void) +{ + static char buf[LOG_DATE_BUF_SIZE]; + time_t t = time(NULL); + strftime(buf, LOG_DATE_BUF_SIZE, "%b %d %H:%M : ", localtime(&t)); + return buf; +} + +/** + * Determines the length of the string excluding trailing whitespace + * characters. + */ +static int +chomp_length(const char *p) +{ + size_t length = strlen(p); + + while (length > 0 && g_ascii_isspace(p[length - 1])) + --length; + + return (int)length; +} + +static void +file_log_func(const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, G_GNUC_UNUSED gpointer user_data) +{ + char *converted; + + if (log_level > log_threshold) + return; + + if (log_charset != NULL) { + converted = g_convert_with_fallback(message, -1, + log_charset, "utf-8", + NULL, NULL, NULL, NULL); + if (converted != NULL) + message = converted; + } else + converted = NULL; + + if (log_domain == NULL) + log_domain = ""; + + fprintf(stderr, "%s%s%s%.*s\n", + stdout_mode ? "" : log_date(), + log_domain, *log_domain == 0 ? "" : ": ", + chomp_length(message), message); + + g_free(converted); +} + +static void +log_init_stdout(void) +{ + g_log_set_default_handler(file_log_func, NULL); +} + +static int +open_log_file(void) +{ + assert(out_filename != NULL); + + return open_cloexec(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); +} + +static bool +log_init_file(unsigned line, GError **error_r) +{ + assert(out_filename != NULL); + + out_fd = open_log_file(); + if (out_fd < 0) { + g_set_error(error_r, log_quark(), errno, + "failed to open log file \"%s\" (config line %u): %s", + out_filename, line, g_strerror(errno)); + return false; + } + + g_log_set_default_handler(file_log_func, NULL); + return true; +} + +#ifdef HAVE_SYSLOG + +static int +glib_to_syslog_level(GLogLevelFlags log_level) +{ + switch (log_level & G_LOG_LEVEL_MASK) { + case G_LOG_LEVEL_ERROR: + case G_LOG_LEVEL_CRITICAL: + return LOG_ERR; + + case G_LOG_LEVEL_WARNING: + return LOG_WARNING; + + case G_LOG_LEVEL_MESSAGE: + return LOG_NOTICE; + + case G_LOG_LEVEL_INFO: + return LOG_INFO; + + case G_LOG_LEVEL_DEBUG: + return LOG_DEBUG; + + default: + return LOG_NOTICE; + } +} + +static void +syslog_log_func(const gchar *log_domain, + GLogLevelFlags log_level, const gchar *message, + G_GNUC_UNUSED gpointer user_data) +{ + if (stdout_mode) { + /* fall back to the file log function during + startup */ + file_log_func(log_domain, log_level, + message, user_data); + return; + } + + if (log_level > log_threshold) + return; + + if (log_domain == NULL) + log_domain = ""; + + syslog(glib_to_syslog_level(log_level), "%s%s%.*s", + log_domain, *log_domain == 0 ? "" : ": ", + chomp_length(message), message); +} + +static void +log_init_syslog(void) +{ + assert(out_filename == NULL); + + openlog(PACKAGE, 0, LOG_DAEMON); + g_log_set_default_handler(syslog_log_func, NULL); +} + +#endif + +static inline GLogLevelFlags +parse_log_level(const char *value, unsigned line) +{ + if (0 == strcmp(value, "default")) + return G_LOG_LEVEL_MESSAGE; + if (0 == strcmp(value, "secure")) + return LOG_LEVEL_SECURE; + else if (0 == strcmp(value, "verbose")) + return G_LOG_LEVEL_DEBUG; + else { + MPD_ERROR("unknown log level \"%s\" at line %u\n", + value, line); + return G_LOG_LEVEL_MESSAGE; + } +} + +void +log_early_init(bool verbose) +{ + if (verbose) + log_threshold = G_LOG_LEVEL_DEBUG; + + log_init_stdout(); +} + +bool +log_init(bool verbose, bool use_stdout, GError **error_r) +{ + const struct config_param *param; + + g_get_charset(&log_charset); + + if (verbose) + log_threshold = G_LOG_LEVEL_DEBUG; + else if ((param = config_get_param(CONF_LOG_LEVEL)) != NULL) + log_threshold = parse_log_level(param->value, param->line); + + if (use_stdout) { + log_init_stdout(); + return true; + } else { + param = config_get_param(CONF_LOG_FILE); + if (param == NULL) { +#ifdef HAVE_SYSLOG + /* no configuration: default to syslog (if + available) */ + log_init_syslog(); + return true; +#else + g_set_error(error_r, log_quark(), 0, + "config parameter 'log_file' not found"); + return false; +#endif +#ifdef HAVE_SYSLOG + } else if (strcmp(param->value, "syslog") == 0) { + log_init_syslog(); + return true; +#endif + } else { + out_filename = config_dup_path(CONF_LOG_FILE, error_r); + return out_filename != NULL && + log_init_file(param->line, error_r); + } + } +} + +static void +close_log_files(void) +{ + if (stdout_mode) + return; + +#ifdef HAVE_SYSLOG + if (out_filename == NULL) + closelog(); +#endif +} + +void +log_deinit(void) +{ + close_log_files(); + g_free(out_filename); +} + + +void setup_log_output(bool use_stdout) +{ + fflush(NULL); + if (!use_stdout) { +#ifndef WIN32 + if (out_filename == NULL) + out_fd = open("/dev/null", O_WRONLY); +#endif + + if (out_fd >= 0) { + redirect_logs(out_fd); + close(out_fd); + } + + stdout_mode = false; + log_charset = NULL; + } +} + +int cycle_log_files(void) +{ + int fd; + + if (stdout_mode || out_filename == NULL) + return 0; + assert(out_filename); + + g_debug("Cycling log files...\n"); + close_log_files(); + + fd = open_log_file(); + if (fd < 0) { + g_warning("error re-opening log file: %s\n", out_filename); + return -1; + } + + redirect_logs(fd); + g_debug("Done cycling log files\n"); + return 0; +} diff --git a/src/Log.hxx b/src/Log.hxx new file mode 100644 index 000000000..cfcece104 --- /dev/null +++ b/src/Log.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_LOG_HXX +#define MPD_LOG_HXX + +#include <glib.h> +#include <stdbool.h> + +G_GNUC_CONST +static inline GQuark +log_quark(void) +{ + return g_quark_from_static_string("log"); +} + +/** + * Configure a logging destination for daemon startup, before the + * configuration file is read. This allows the daemon to use the + * logging library (and the command line verbose level) before it's + * daemonized. + * + * @param verbose true when the program is started with --verbose + */ +void +log_early_init(bool verbose); + +bool +log_init(bool verbose, bool use_stdout, GError **error_r); + +void +log_deinit(void); + +void setup_log_output(bool use_stdout); + +int cycle_log_files(void); + +#endif /* LOG_H */ diff --git a/src/Main.cxx b/src/Main.cxx new file mode 100644 index 000000000..9b6630d60 --- /dev/null +++ b/src/Main.cxx @@ -0,0 +1,588 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Main.hxx" +#include "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 "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 "pcm_resample.h" +} + +#include "mpd_error.h" + +#ifdef ENABLE_INOTIFY +#include "InotifyUpdate.hxx" +#endif + +#ifdef ENABLE_SQLITE +#include "StickerDatabase.hxx" +#endif + +#ifdef ENABLE_ARCHIVE +#include "ArchiveList.hxx" +#endif + +#include <glib.h> + +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif + +#ifdef WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#endif + +enum { + DEFAULT_BUFFER_SIZE = 2048, + DEFAULT_BUFFER_BEFORE_PLAY = 10, +}; + +GThread *main_task; +EventLoop *main_loop; + +ClientList *client_list; + +Partition *global_partition; + +static StateFile *state_file; + +static inline GQuark main_quark() +{ + return g_quark_from_static_string ("main"); +} + +static bool +glue_daemonize_init(const struct options *options, GError **error_r) +{ + GError *error = NULL; + + char *pid_file = config_dup_path(CONF_PID_FILE, &error); + if (pid_file == NULL && error != NULL) { + g_propagate_error(error_r, error); + return false; + } + + daemonize_init(config_get_string(CONF_USER, NULL), + config_get_string(CONF_GROUP, NULL), + pid_file); + g_free(pid_file); + + if (options->kill) + daemonize_kill(); + + return true; +} + +static bool +glue_mapper_init(GError **error_r) +{ + GError *error = NULL; + char *music_dir = config_dup_path(CONF_MUSIC_DIR, &error); + if (music_dir == NULL && error != NULL) { + g_propagate_error(error_r, error); + return false; + } + + char *playlist_dir = config_dup_path(CONF_PLAYLIST_DIR, &error); + if (playlist_dir == NULL && error != NULL) { + g_propagate_error(error_r, error); + return false; + } + + if (music_dir == NULL) + music_dir = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC)); + + if (!mapper_init(music_dir, playlist_dir, &error)) { + g_propagate_error(error_r, error); + return false; + } + + g_free(music_dir); + g_free(playlist_dir); + return true; +} + +/** + * Returns the database. If this function returns false, this has not + * succeeded, and the caller should create the database after the + * process has been daemonized. + */ +static bool +glue_db_init_and_load(void) +{ + const struct config_param *param = config_get_param(CONF_DATABASE); + const struct config_param *path = config_get_param(CONF_DB_FILE); + + if (param != NULL && path != NULL) + g_message("Found both 'database' and 'db_file' setting - ignoring the latter"); + + GError *error = NULL; + bool ret; + + if (!mapper_has_music_directory()) { + if (param != NULL) + g_message("Found database setting without " + "music_directory - disabling database"); + if (path != NULL) + g_message("Found db_file setting without " + "music_directory - disabling database"); + return true; + } + + struct config_param *allocated = NULL; + + if (param == NULL && path != NULL) { + allocated = new config_param("database", path->line); + allocated->AddBlockParam("path", path->value, path->line); + param = allocated; + } + + if (!DatabaseGlobalInit(param, &error)) + MPD_ERROR("%s", error->message); + + delete allocated; + + ret = DatabaseGlobalOpen(&error); + if (!ret) + MPD_ERROR("%s", error->message); + + /* run database update after daemonization? */ + return !db_is_simple() || db_exists(); +} + +/** + * Configure and initialize the sticker subsystem. + */ +static void +glue_sticker_init(void) +{ +#ifdef ENABLE_SQLITE + GError *error = NULL; + char *sticker_file = config_dup_path(CONF_STICKER_FILE, &error); + if (sticker_file == NULL && error != NULL) + MPD_ERROR("%s", error->message); + + if (!sticker_global_init(sticker_file, &error)) + MPD_ERROR("%s", error->message); + + g_free(sticker_file); +#endif +} + +static bool +glue_state_file_init(GError **error_r) +{ + GError *error = NULL; + + char *path = config_dup_path(CONF_STATE_FILE, &error); + if (path == nullptr) { + if (error != nullptr) { + g_propagate_error(error_r, error); + return false; + } + + return true; + } + + Path path_fs = Path::FromUTF8(path); + + if (path_fs.IsNull()) { + g_free(path); + g_set_error(error_r, main_quark(), 0, + "Failed to convert state file path to FS encoding"); + return false; + } + + state_file = new StateFile(std::move(path_fs), path, + *global_partition, *main_loop); + g_free(path); + + state_file->Read(); + return true; +} + +/** + * Windows-only initialization of the Winsock2 library. + */ +static void winsock_init(void) +{ +#ifdef WIN32 + WSADATA sockinfo; + int retval; + + retval = WSAStartup(MAKEWORD(2, 2), &sockinfo); + if(retval != 0) + { + MPD_ERROR("Attempt to open Winsock2 failed; error code %d\n", + retval); + } + + if (LOBYTE(sockinfo.wVersion) != 2) + { + MPD_ERROR("We use Winsock2 but your version is either too new " + "or old; please install Winsock 2.x\n"); + } +#endif +} + +/** + * Initialize the decoder and player core, including the music pipe. + */ +static void +initialize_decoder_and_player(void) +{ + const struct config_param *param; + char *test; + size_t buffer_size; + float perc; + unsigned buffered_chunks; + unsigned buffered_before_play; + + param = config_get_param(CONF_AUDIO_BUFFER_SIZE); + if (param != NULL) { + long tmp = strtol(param->value, &test, 10); + if (*test != '\0' || tmp <= 0 || tmp == LONG_MAX) + MPD_ERROR("buffer size \"%s\" is not a positive integer, " + "line %i\n", param->value, param->line); + buffer_size = tmp; + } else + buffer_size = DEFAULT_BUFFER_SIZE; + + buffer_size *= 1024; + + buffered_chunks = buffer_size / CHUNK_SIZE; + + if (buffered_chunks >= 1 << 15) + MPD_ERROR("buffer size \"%li\" is too big\n", (long)buffer_size); + + param = config_get_param(CONF_BUFFER_BEFORE_PLAY); + if (param != NULL) { + perc = strtod(param->value, &test); + if (*test != '%' || perc < 0 || perc > 100) { + MPD_ERROR("buffered before play \"%s\" is not a positive " + "percentage and less than 100 percent, line %i", + param->value, param->line); + } + } else + perc = DEFAULT_BUFFER_BEFORE_PLAY; + + buffered_before_play = (perc / 100) * buffered_chunks; + if (buffered_before_play > buffered_chunks) + buffered_before_play = buffered_chunks; + + const unsigned max_length = + config_get_positive(CONF_MAX_PLAYLIST_LENGTH, + DEFAULT_PLAYLIST_MAX_LENGTH); + + global_partition = new Partition(max_length, + buffered_chunks, + buffered_before_play); +} + +/** + * Handler for GlobalEvents::IDLE. + */ +static void +idle_event_emitted(void) +{ + /* send "idle" notifications to all subscribed + clients */ + unsigned flags = idle_get(); + if (flags != 0) + client_list->IdleAdd(flags); +} + +/** + * Handler for GlobalEvents::SHUTDOWN. + */ +static void +shutdown_event_emitted(void) +{ + main_loop->Break(); +} + +int main(int argc, char *argv[]) +{ +#ifdef WIN32 + return win32_main(argc, argv); +#else + return mpd_main(argc, argv); +#endif +} + +int mpd_main(int argc, char *argv[]) +{ + struct options options; + clock_t start; + bool create_db; + GError *error = NULL; + bool success; + + daemonize_close_stdin(); + +#ifdef HAVE_LOCALE_H + /* initialize locale */ + setlocale(LC_CTYPE,""); +#endif + + g_set_application_name("Music Player Daemon"); + + /* enable GLib's thread safety code */ + g_thread_init(NULL); + + io_thread_init(); + winsock_init(); + config_global_init(); + + success = parse_cmdline(argc, argv, &options, &error); + if (!success) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + if (!glue_daemonize_init(&options, &error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + stats_global_init(); + tag_lib_init(); + + if (!log_init(options.verbose, options.log_stderr, &error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + main_task = g_thread_self(); + main_loop = new EventLoop(EventLoop::Default()); + + 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); + g_error_free(error); + return EXIT_FAILURE; + } + + daemonize_set_user(); + + GlobalEvents::Initialize(); + GlobalEvents::Register(GlobalEvents::IDLE, idle_event_emitted); + GlobalEvents::Register(GlobalEvents::SHUTDOWN, shutdown_event_emitted); + + Path::GlobalInit(); + + if (!glue_mapper_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + initPermissions(); + playlist_global_init(); + spl_global_init(); +#ifdef ENABLE_ARCHIVE + archive_plugin_init_all(); +#endif + + if (!pcm_resample_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + decoder_plugin_init_all(); + update_global_init(); + + create_db = !glue_db_init_and_load(); + + glue_sticker_init(); + + command_init(); + initialize_decoder_and_player(); + volume_init(); + initAudioConfig(); + audio_output_all_init(&global_partition->pc); + client_manager_init(); + replay_gain_global_init(); + + if (!input_stream_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + playlist_list_global_init(); + + daemonize(options.daemon); + + setup_log_output(options.log_stderr); + + initSigHandlers(); + + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + ZeroconfInit(*main_loop); + + player_create(&global_partition->pc); + + if (create_db) { + /* the database failed to load: recreate the + database */ + unsigned job = update_enqueue(NULL, true); + if (job == 0) + MPD_ERROR("directory update failed"); + } + + if (!glue_state_file_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(global_partition->playlist.queue.random)); + + success = config_get_bool(CONF_AUTO_UPDATE, false); +#ifdef ENABLE_INOTIFY + if (success && mapper_has_music_directory()) + mpd_inotify_init(config_get_unsigned(CONF_AUTO_UPDATE_DEPTH, + G_MAXUINT)); +#else + if (success) + g_warning("inotify: auto_update was disabled. enable during compilation phase"); +#endif + + config_global_check(); + + /* enable all audio outputs (if not already done by + playlist_state_restore() */ + global_partition->pc.UpdateAudio(); + +#ifdef WIN32 + win32_app_started(); +#endif + + /* run the main loop */ + main_loop->Run(); + +#ifdef WIN32 + win32_app_stopping(); +#endif + + /* cleanup */ + +#ifdef ENABLE_INOTIFY + mpd_inotify_finish(); +#endif + + if (state_file != nullptr) { + state_file->Write(); + delete state_file; + } + + global_partition->pc.Kill(); + ZeroconfDeinit(); + listen_global_finish(); + delete client_list; + + start = clock(); + DatabaseGlobalDeinit(); + g_debug("db_finish took %f seconds", + ((float)(clock()-start))/CLOCKS_PER_SEC); + +#ifdef ENABLE_SQLITE + sticker_global_finish(); +#endif + + GlobalEvents::Deinitialize(); + + playlist_list_global_finish(); + input_stream_global_finish(); + audio_output_all_finish(); + volume_finish(); + mapper_finish(); + delete global_partition; + command_finish(); + update_global_finish(); + decoder_plugin_deinit_all(); +#ifdef ENABLE_ARCHIVE + archive_plugin_deinit_all(); +#endif + config_global_finish(); + stats_global_finish(); + io_thread_deinit(); + delete main_loop; + daemonize_finish(); +#ifdef WIN32 + WSACleanup(); +#endif + + log_deinit(); + return EXIT_SUCCESS; +} diff --git a/src/Main.hxx b/src/Main.hxx new file mode 100644 index 000000000..b2768600c --- /dev/null +++ b/src/Main.hxx @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MAIN_HXX +#define MPD_MAIN_HXX + +#include <glib.h> + +class EventLoop; + +extern GThread *main_task; + +extern EventLoop *main_loop; + +extern class ClientList *client_list; + +extern struct Partition *global_partition; + +/** + * A entry point for application. + * On non-Windows platforms this is called directly from main() + * On Windows platform this is called from win32_main() + * after doing some initialization. + */ +int mpd_main(int argc, char *argv[]); + +#ifdef WIN32 + +/** + * If program is run as windows service performs nessesary initialization + * and then calls mpd_main() with specified arguments. + * If program is run as a regular application calls mpd_main() immediately. + */ +int +win32_main(int argc, char *argv[]); + +/** + * When running as a service reports to service control manager + * that our service is started. + * When running as a console application enables console handler that will + * trigger GlobalEvents::SHUTDOWN when user closes console window + * or presses Ctrl+C. + * This function should be called just before entering main loop. + */ +void +win32_app_started(void); + +/** + * When running as a service reports to service control manager + * that our service is about to stop. + * When running as a console application enables console handler that will + * catch all shutdown requests and ignore them. + * This function should be called just after leaving main loop. + */ +void +win32_app_stopping(void); + +#endif + +#endif diff --git a/src/Mapper.cxx b/src/Mapper.cxx new file mode 100644 index 000000000..559df0d23 --- /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.hxx b/src/Mapper.hxx new file mode 100644 index 000000000..af6c84cc8 --- /dev/null +++ b/src/Mapper.hxx @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Maps directory and song objects to file system paths. + */ + +#ifndef MPD_MAPPER_HXX +#define MPD_MAPPER_HXX + +#include "gcc.h" +#include "gerror.h" + +#define PLAYLIST_FILE_SUFFIX ".m3u" + +class Path; +struct Directory; +struct song; + +bool mapper_init(const char *_music_dir, const char *_playlist_dir, + GError **error_r); + +void mapper_finish(void); + +/** + * Return the absolute path of the music directory encoded in UTF-8. + */ +gcc_const +const char * +mapper_get_music_directory_utf8(void); + +/** + * Return the absolute path of the music directory encoded in the + * filesystem character set. + */ +gcc_const +const Path & +mapper_get_music_directory_fs(void); + +/** + * Returns true if a music directory was configured. + */ +gcc_const +static inline bool +mapper_has_music_directory(void) +{ + return mapper_get_music_directory_utf8() != nullptr; +} + +/** + * If the specified absolute path points inside the music directory, + * this function converts it to a relative path. If not, it returns + * the unmodified string pointer. + */ +gcc_pure +const char * +map_to_relative_path(const char *path_utf8); + +/** + * Determines the absolute file system path of a relative URI. This + * is basically done by converting the URI to the file system charset + * and prepending the music directory. + */ +gcc_pure +Path +map_uri_fs(const char *uri); + +/** + * Determines the file system path of a directory object. + * + * @param directory the directory object + * @return the path in file system encoding, or nullptr if mapping failed + */ +gcc_pure +Path +map_directory_fs(const Directory *directory); + +/** + * Determines the file system path of a directory's child (may be a + * sub directory or a song). + * + * @param directory the parent directory object + * @param name the child's name in UTF-8 + * @return the path in file system encoding, or nullptr if mapping failed + */ +gcc_pure +Path +map_directory_child_fs(const Directory *directory, const char *name); + +/** + * Determines the file system path of a song. This must not be a + * remote song. + * + * @param song the song object + * @return the path in file system encoding, or nullptr if mapping failed + */ +gcc_pure +Path +map_song_fs(const struct song *song); + +/** + * Maps a file system path (relative to the music directory or + * absolute) to a relative path in UTF-8 encoding. + * + * @param path_fs a path in file system encoding + * @return the relative path in UTF-8, or nullptr if mapping failed + */ +gcc_malloc +char * +map_fs_to_utf8(const char *path_fs); + +/** + * Returns the playlist directory. + */ +gcc_const +const Path & +map_spl_path(void); + +/** + * Maps a playlist name (without the ".m3u" suffix) to a file system + * path. The return value is allocated on the heap and must be freed + * with g_free(). + * + * @return the path in file system encoding, or nullptr if mapping failed + */ +gcc_pure +Path +map_spl_utf8_to_fs(const char *name); + +#endif diff --git a/src/MessageCommands.cxx b/src/MessageCommands.cxx new file mode 100644 index 000000000..f19a1b5d4 --- /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 000000000..b10f3d8e8 --- /dev/null +++ b/src/MessageCommands.hxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MESSAGE_COMMANDS_HXX +#define MPD_MESSAGE_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_subscribe(Client *client, int argc, char *argv[]); + +enum command_return +handle_unsubscribe(Client *client, int argc, char *argv[]); + +enum command_return +handle_channels(Client *client, int argc, char *argv[]); + +enum command_return +handle_read_messages(Client *client, int argc, char *argv[]); + +enum command_return +handle_send_message(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/MixerAll.cxx b/src/MixerAll.cxx new file mode 100644 index 000000000..b38005520 --- /dev/null +++ b/src/MixerAll.cxx @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MixerAll.hxx" +#include "OutputAll.hxx" +#include "PcmVolume.hxx" + +extern "C" { +#include "mixer_control.h" +#include "output_internal.h" +#include "mixer_api.h" +} + +#include <glib.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mixer" + +static int +output_mixer_get_volume(unsigned i) +{ + struct audio_output *output; + struct mixer *mixer; + int volume; + GError *error = NULL; + + assert(i < audio_output_count()); + + output = audio_output_get(i); + if (!output->enabled) + return -1; + + mixer = output->mixer; + if (mixer == NULL) + return -1; + + volume = mixer_get_volume(mixer, &error); + if (volume < 0 && error != NULL) { + g_warning("Failed to read mixer for '%s': %s", + output->name, error->message); + g_error_free(error); + } + + return volume; +} + +int +mixer_all_get_volume(void) +{ + unsigned count = audio_output_count(), ok = 0; + int volume, total = 0; + + for (unsigned i = 0; i < count; i++) { + volume = output_mixer_get_volume(i); + if (volume >= 0) { + total += volume; + ++ok; + } + } + + if (ok == 0) + return -1; + + return total / ok; +} + +static bool +output_mixer_set_volume(unsigned i, unsigned volume) +{ + struct audio_output *output; + struct mixer *mixer; + bool success; + GError *error = NULL; + + assert(i < audio_output_count()); + assert(volume <= 100); + + output = audio_output_get(i); + if (!output->enabled) + return false; + + mixer = output->mixer; + if (mixer == NULL) + return false; + + success = mixer_set_volume(mixer, volume, &error); + if (!success && error != NULL) { + g_warning("Failed to set mixer for '%s': %s", + output->name, error->message); + g_error_free(error); + } + + return success; +} + +bool +mixer_all_set_volume(unsigned volume) +{ + bool success = false; + unsigned count = audio_output_count(); + + assert(volume <= 100); + + for (unsigned i = 0; i < count; i++) + success = output_mixer_set_volume(i, volume) + || success; + + return success; +} + +static int +output_mixer_get_software_volume(unsigned i) +{ + struct audio_output *output; + struct mixer *mixer; + + assert(i < audio_output_count()); + + output = audio_output_get(i); + if (!output->enabled) + return -1; + + mixer = output->mixer; + if (mixer == NULL || mixer->plugin != &software_mixer_plugin) + return -1; + + return mixer_get_volume(mixer, NULL); +} + +int +mixer_all_get_software_volume(void) +{ + unsigned count = audio_output_count(), ok = 0; + int volume, total = 0; + + for (unsigned i = 0; i < count; i++) { + volume = output_mixer_get_software_volume(i); + if (volume >= 0) { + total += volume; + ++ok; + } + } + + if (ok == 0) + return -1; + + return total / ok; +} + +void +mixer_all_set_software_volume(unsigned volume) +{ + unsigned count = audio_output_count(); + + assert(volume <= PCM_VOLUME_1); + + for (unsigned i = 0; i < count; i++) { + struct audio_output *output = audio_output_get(i); + if (output->mixer != NULL && + output->mixer->plugin == &software_mixer_plugin) + mixer_set_volume(output->mixer, volume, NULL); + } +} diff --git a/src/MixerAll.hxx b/src/MixerAll.hxx new file mode 100644 index 000000000..23350a843 --- /dev/null +++ b/src/MixerAll.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Functions which affect the mixers of all audio outputs. + */ + +#ifndef MPD_MIXER_ALL_HXX +#define MPD_MIXER_ALL_HXX + +/** + * Returns the average volume of all available mixers (range 0..100). + * Returns -1 if no mixer can be queried. + */ +int +mixer_all_get_volume(void); + +/** + * Sets the volume on all available mixers. + * + * @param volume the volume (range 0..100) + * @return true on success, false on failure + */ +bool +mixer_all_set_volume(unsigned volume); + +/** + * Similar to mixer_all_get_volume(), but gets the volume only for + * software mixers. See #software_mixer_plugin. This function fails + * if no software mixer is configured. + */ +int +mixer_all_get_software_volume(void); + +/** + * Similar to mixer_all_set_volume(), but sets the volume only for + * software mixers. See #software_mixer_plugin. This function cannot + * fail, because the underlying software mixers cannot fail either. + */ +void +mixer_all_set_software_volume(unsigned volume); + +#endif diff --git a/src/MusicBuffer.cxx b/src/MusicBuffer.cxx new file mode 100644 index 000000000..ea03fc0b9 --- /dev/null +++ b/src/MusicBuffer.cxx @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "thread/Mutex.hxx" +#include "util/SliceBuffer.hxx" +#include "mpd_error.h" + +#include <assert.h> + +struct music_buffer : public SliceBuffer<music_chunk> { + /** a mutex which protects #available */ + Mutex mutex; + + music_buffer(unsigned num_chunks) + :SliceBuffer(num_chunks) { + if (IsOOM()) + MPD_ERROR("Failed to allocate buffer"); + } +}; + +struct music_buffer * +music_buffer_new(unsigned num_chunks) +{ + return new music_buffer(num_chunks); +} + +void +music_buffer_free(struct music_buffer *buffer) +{ + delete buffer; +} + +unsigned +music_buffer_size(const struct music_buffer *buffer) +{ + return buffer->GetCapacity(); +} + +struct music_chunk * +music_buffer_allocate(struct music_buffer *buffer) +{ + const ScopeLock protect(buffer->mutex); + return buffer->Allocate(); +} + +void +music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk) +{ + assert(buffer != NULL); + assert(chunk != NULL); + + const ScopeLock protect(buffer->mutex); + + if (chunk->other != nullptr) { + assert(chunk->other->other == nullptr); + buffer->Free(chunk->other); + } + + buffer->Free(chunk); +} diff --git a/src/MusicBuffer.hxx b/src/MusicBuffer.hxx new file mode 100644 index 000000000..cc03dfcb3 --- /dev/null +++ b/src/MusicBuffer.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MUSIC_BUFFER_HXX +#define MPD_MUSIC_BUFFER_HXX + +/** + * An allocator for #music_chunk objects. + */ +struct music_buffer; + +/** + * Creates a new #music_buffer object. + * + * @param num_chunks the number of #music_chunk reserved in this + * buffer + */ +struct music_buffer * +music_buffer_new(unsigned num_chunks); + +/** + * Frees the #music_buffer object + */ +void +music_buffer_free(struct music_buffer *buffer); + +/** + * Returns the total number of reserved chunks in this buffer. This + * is the same value which was passed to the constructor + * music_buffer_new(). + */ +unsigned +music_buffer_size(const struct music_buffer *buffer); + +/** + * Allocates a chunk from the buffer. When it is not used anymore, + * call music_buffer_return(). + * + * @return an empty chunk or NULL if there are no chunks available + */ +struct music_chunk * +music_buffer_allocate(struct music_buffer *buffer); + +/** + * Returns a chunk to the buffer. It can be reused by + * music_buffer_allocate() then. + */ +void +music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk); + +#endif diff --git a/src/MusicChunk.cxx b/src/MusicChunk.cxx new file mode 100644 index 000000000..eefda24b5 --- /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/MusicChunk.hxx b/src/MusicChunk.hxx new file mode 100644 index 000000000..c03e45517 --- /dev/null +++ b/src/MusicChunk.hxx @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MUSIC_CHUNK_HXX +#define MPD_MUSIC_CHUNK_HXX + +#include "replay_gain_info.h" + +#ifndef NDEBUG +#include "audio_format.h" +#endif + +#include <stdint.h> +#include <stddef.h> + +enum { + CHUNK_SIZE = 4096, +}; + +struct audio_format; + +/** + * A chunk of music data. Its format is defined by the + * music_pipe_append() caller. + */ +struct music_chunk { + /** the next chunk in a linked list */ + struct music_chunk *next; + + /** + * An optional chunk which should be mixed into this chunk. + * This is used for cross-fading. + */ + struct music_chunk *other; + + /** + * The current mix ratio for cross-fading: 1.0 means play 100% + * of this chunk, 0.0 means play 100% of the "other" chunk. + */ + float mix_ratio; + + /** number of bytes stored in this chunk */ + uint16_t length; + + /** current bit rate of the source file */ + uint16_t bit_rate; + + /** the time stamp within the song */ + float times; + + /** + * An optional tag associated with this chunk (and the + * following chunks); appears at song boundaries. The tag + * object is owned by this chunk, and must be freed when this + * chunk is deinitialized in music_chunk_free() + */ + struct tag *tag; + + /** + * Replay gain information associated with this chunk. + * Only valid if the serial is not 0. + */ + struct replay_gain_info replay_gain_info; + + /** + * A serial number for checking if replay gain info has + * changed since the last chunk. The magic value 0 indicates + * that there is no replay gain info available. + */ + unsigned replay_gain_serial; + + /** the data (probably PCM) */ + char data[CHUNK_SIZE]; + +#ifndef NDEBUG + struct audio_format audio_format; +#endif + + music_chunk() + :other(nullptr), + length(0), + tag(nullptr), + replay_gain_serial(0) {} + + ~music_chunk(); + + bool IsEmpty() const { + return length == 0 && tag == nullptr; + } + +#ifndef NDEBUG + /** + * Checks if the audio format if the chunk is equal to the + * specified audio_format. + */ + gcc_pure + bool CheckFormat(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 *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 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/MusicPipe.cxx b/src/MusicPipe.cxx new file mode 100644 index 000000000..6f25eff82 --- /dev/null +++ b/src/MusicPipe.cxx @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MusicPipe.hxx" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "thread/Mutex.hxx" + +#include <glib.h> + +#include <assert.h> + +struct music_pipe { + /** the first chunk */ + struct music_chunk *head; + + /** a pointer to the tail of the chunk */ + struct music_chunk **tail_r; + + /** the current number of chunks */ + unsigned size; + + /** a mutex which protects #head and #tail_r */ + mutable Mutex mutex; + +#ifndef NDEBUG + 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) +{ + return new music_pipe(); +} + +void +music_pipe_free(struct music_pipe *mp) +{ + delete mp; +} + +#ifndef NDEBUG + +bool +music_pipe_check_format(const struct music_pipe *pipe, + const struct audio_format *audio_format) +{ + assert(pipe != NULL); + assert(audio_format != NULL); + + return !audio_format_defined(&pipe->audio_format) || + audio_format_equals(&pipe->audio_format, audio_format); +} + +bool +music_pipe_contains(const struct music_pipe *mp, + const struct music_chunk *chunk) +{ + const ScopeLock protect(mp->mutex); + + for (const struct music_chunk *i = mp->head; + i != NULL; i = i->next) + if (i == chunk) + return true; + + return false; +} + +#endif + +const struct music_chunk * +music_pipe_peek(const struct music_pipe *mp) +{ + return mp->head; +} + +struct music_chunk * +music_pipe_shift(struct music_pipe *mp) +{ + const ScopeLock protect(mp->mutex); + + struct music_chunk *chunk = mp->head; + if (chunk != NULL) { + assert(!chunk->IsEmpty()); + + mp->head = chunk->next; + --mp->size; + + if (mp->head == NULL) { + assert(mp->size == 0); + assert(mp->tail_r == &chunk->next); + + mp->tail_r = &mp->head; + } else { + assert(mp->size > 0); + assert(mp->tail_r != &chunk->next); + } + +#ifndef NDEBUG + /* poison the "next" reference */ + chunk->next = (struct music_chunk *)(void *)0x01010101; + + if (mp->size == 0) + audio_format_clear(&mp->audio_format); +#endif + } + + return chunk; +} + +void +music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer) +{ + struct music_chunk *chunk; + + while ((chunk = music_pipe_shift(mp)) != NULL) + music_buffer_return(buffer, chunk); +} + +void +music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk) +{ + assert(!chunk->IsEmpty()); + assert(chunk->length == 0 || audio_format_valid(&chunk->audio_format)); + + const ScopeLock protect(mp->mutex); + + assert(mp->size > 0 || !audio_format_defined(&mp->audio_format)); + assert(!audio_format_defined(&mp->audio_format) || + chunk->CheckFormat(mp->audio_format)); + +#ifndef NDEBUG + if (!audio_format_defined(&mp->audio_format) && chunk->length > 0) + mp->audio_format = chunk->audio_format; +#endif + + chunk->next = NULL; + *mp->tail_r = chunk; + mp->tail_r = &chunk->next; + + ++mp->size; +} + +unsigned +music_pipe_size(const struct music_pipe *mp) +{ + const ScopeLock protect(mp->mutex); + return mp->size; +} diff --git a/src/MusicPipe.hxx b/src/MusicPipe.hxx new file mode 100644 index 000000000..99561ca62 --- /dev/null +++ b/src/MusicPipe.hxx @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PIPE_H +#define MPD_PIPE_H + +#include "gcc.h" + +#ifndef NDEBUG +struct audio_format; +#endif + +struct music_chunk; +struct music_buffer; + +/** + * A queue of #music_chunk objects. One party appends chunks at the + * tail, and the other consumes them from the head. + */ +struct music_pipe; + +/** + * Creates a new #music_pipe object. It is empty. + */ +gcc_malloc +struct music_pipe * +music_pipe_new(void); + +/** + * Frees the object. It must be empty now. + */ +void +music_pipe_free(struct music_pipe *mp); + +#ifndef NDEBUG + +/** + * Checks if the audio format if the chunk is equal to the specified + * audio_format. + */ +bool +music_pipe_check_format(const struct music_pipe *pipe, + const struct audio_format *audio_format); + +/** + * Checks if the specified chunk is enqueued in the music pipe. + */ +bool +music_pipe_contains(const struct music_pipe *mp, + const struct music_chunk *chunk); + +#endif + +/** + * Returns the first #music_chunk from the pipe. Returns NULL if the + * pipe is empty. + */ +gcc_pure +const struct music_chunk * +music_pipe_peek(const struct music_pipe *mp); + +/** + * Removes the first chunk from the head, and returns it. + */ +struct music_chunk * +music_pipe_shift(struct music_pipe *mp); + +/** + * Clears the whole pipe and returns the chunks to the buffer. + * + * @param buffer the buffer object to return the chunks to + */ +void +music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer); + +/** + * Pushes a chunk to the tail of the pipe. + */ +void +music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk); + +/** + * Returns the number of chunks currently in this pipe. + */ +gcc_pure +unsigned +music_pipe_size(const struct music_pipe *mp); + +gcc_pure +static inline bool +music_pipe_empty(const struct music_pipe *mp) +{ + return music_pipe_size(mp) == 0; +} + +#endif diff --git a/src/OtherCommands.cxx b/src/OtherCommands.cxx new file mode 100644 index 000000000..4909f37f5 --- /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 000000000..564ad38e7 --- /dev/null +++ b/src/OtherCommands.hxx @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OTHER_COMMANDS_HXX +#define MPD_OTHER_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_urlhandlers(Client *client, int argc, char *argv[]); + +enum command_return +handle_decoders(Client *client, int argc, char *argv[]); + +enum command_return +handle_tagtypes(Client *client, int argc, char *argv[]); + +enum command_return +handle_kill(Client *client, int argc, char *argv[]); + +enum command_return +handle_close(Client *client, int argc, char *argv[]); + +enum command_return +handle_lsinfo(Client *client, int argc, char *argv[]); + +enum command_return +handle_update(Client *client, int argc, char *argv[]); + +enum command_return +handle_rescan(Client *client, int argc, char *argv[]); + +enum command_return +handle_setvol(Client *client, int argc, char *argv[]); + +enum command_return +handle_stats(Client *client, int argc, char *argv[]); + +enum command_return +handle_ping(Client *client, int argc, char *argv[]); + +enum command_return +handle_password(Client *client, int argc, char *argv[]); + +enum command_return +handle_config(Client *client, int argc, char *argv[]); + +enum command_return +handle_idle(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/OutputAll.cxx b/src/OutputAll.cxx new file mode 100644 index 000000000..4cdcc84e7 --- /dev/null +++ b/src/OutputAll.cxx @@ -0,0 +1,604 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputAll.hxx" + +extern "C" { +#include "output_internal.h" +} + +#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> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "output" + +static struct audio_format input_audio_format; + +static struct audio_output **audio_outputs; +static unsigned int num_audio_outputs; + +/** + * The #music_buffer object where consumed chunks are returned. + */ +static struct music_buffer *g_music_buffer; + +/** + * The #music_pipe object which feeds all audio outputs. It is filled + * by audio_output_all_play(). + */ +static struct music_pipe *g_mp; + +/** + * The "elapsed_time" stamp of the most recently finished chunk. + */ +static float audio_output_all_elapsed_time = -1.0; + +unsigned int audio_output_count(void) +{ + return num_audio_outputs; +} + +struct audio_output * +audio_output_get(unsigned i) +{ + assert(i < num_audio_outputs); + + assert(audio_outputs[i] != NULL); + + return audio_outputs[i]; +} + +struct audio_output * +audio_output_find(const char *name) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = audio_output_get(i); + + if (strcmp(ao->name, name) == 0) + return ao; + } + + /* name not found */ + return NULL; +} + +static unsigned +audio_output_config_count(void) +{ + unsigned int nr = 0; + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param))) + nr++; + if (!nr) + nr = 1; /* we'll always have at least one device */ + return nr; +} + +void +audio_output_all_init(struct player_control *pc) +{ + const struct config_param *param = NULL; + unsigned int i; + GError *error = NULL; + + num_audio_outputs = audio_output_config_count(); + audio_outputs = g_new(struct audio_output *, num_audio_outputs); + + for (i = 0; i < num_audio_outputs; i++) + { + unsigned int j; + + param = config_get_next_param(CONF_AUDIO_OUTPUT, param); + + /* only allow param to be NULL if there just one audioOutput */ + assert(param || (num_audio_outputs == 1)); + + struct audio_output *output = audio_output_new(param, pc, &error); + if (output == NULL) { + if (param != NULL) + MPD_ERROR("line %i: %s", + param->line, error->message); + else + MPD_ERROR("%s", error->message); + } + + audio_outputs[i] = output; + + /* require output names to be unique: */ + for (j = 0; j < i; j++) { + if (!strcmp(output->name, audio_outputs[j]->name)) { + MPD_ERROR("output devices with identical " + "names: %s\n", output->name); + } + } + } +} + +void +audio_output_all_finish(void) +{ + unsigned int i; + + for (i = 0; i < num_audio_outputs; i++) { + audio_output_disable(audio_outputs[i]); + audio_output_finish(audio_outputs[i]); + } + + g_free(audio_outputs); + audio_outputs = NULL; + num_audio_outputs = 0; +} + +void +audio_output_all_enable_disable(void) +{ + for (unsigned i = 0; i < num_audio_outputs; i++) { + struct audio_output *ao = audio_outputs[i]; + bool enabled; + + g_mutex_lock(ao->mutex); + enabled = ao->really_enabled; + g_mutex_unlock(ao->mutex); + + if (ao->enabled != enabled) { + if (ao->enabled) + audio_output_enable(ao); + else + audio_output_disable(ao); + } + } +} + +/** + * Determine if all (active) outputs have finished the current + * command. + */ +static bool +audio_output_all_finished(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = audio_outputs[i]; + bool not_finished; + + g_mutex_lock(ao->mutex); + not_finished = audio_output_is_open(ao) && + !audio_output_command_is_finished(ao); + g_mutex_unlock(ao->mutex); + + if (not_finished) + return false; + } + + return true; +} + +static void audio_output_wait_all(void) +{ + while (!audio_output_all_finished()) + audio_output_client_notify.Wait(); +} + +/** + * Signals all audio outputs which are open. + */ +static void +audio_output_allow_play_all(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_allow_play(audio_outputs[i]); +} + +static void +audio_output_reset_reopen(struct audio_output *ao) +{ + g_mutex_lock(ao->mutex); + + if (!ao->open && ao->fail_timer != NULL) { + g_timer_destroy(ao->fail_timer); + ao->fail_timer = NULL; + } + + g_mutex_unlock(ao->mutex); +} + +/** + * Resets the "reopen" flag on all audio devices. MPD should + * immediately retry to open the device instead of waiting for the + * timeout when the user wants to start playback. + */ +static void +audio_output_all_reset_reopen(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = audio_outputs[i]; + + audio_output_reset_reopen(ao); + } +} + +/** + * Opens all output devices which are enabled, but closed. + * + * @return true if there is at least open output device which is open + */ +static bool +audio_output_all_update(void) +{ + unsigned int i; + bool ret = false; + + if (!audio_format_defined(&input_audio_format)) + return false; + + for (i = 0; i < num_audio_outputs; ++i) + ret = audio_output_update(audio_outputs[i], + &input_audio_format, g_mp) || ret; + + return ret; +} + +void +audio_output_all_set_replay_gain_mode(enum replay_gain_mode mode) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_set_replay_gain_mode(audio_outputs[i], mode); +} + +bool +audio_output_all_play(struct music_chunk *chunk, GError **error_r) +{ + bool ret; + unsigned int i; + + assert(g_music_buffer != NULL); + assert(g_mp != NULL); + assert(chunk != NULL); + assert(chunk->CheckFormat(input_audio_format)); + + ret = audio_output_all_update(); + if (!ret) { + /* TODO: obtain real error */ + g_set_error(error_r, output_quark(), 0, + "Failed to open audio output"); + return false; + } + + music_pipe_push(g_mp, chunk); + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_play(audio_outputs[i]); + + return true; +} + +bool +audio_output_all_open(const struct audio_format *audio_format, + struct music_buffer *buffer, + GError **error_r) +{ + bool ret = false, enabled = false; + unsigned int i; + + assert(audio_format != NULL); + assert(buffer != NULL); + assert(g_music_buffer == NULL || g_music_buffer == buffer); + assert((g_mp == NULL) == (g_music_buffer == NULL)); + + g_music_buffer = buffer; + + /* the audio format must be the same as existing chunks in the + pipe */ + assert(g_mp == NULL || music_pipe_check_format(g_mp, audio_format)); + + if (g_mp == NULL) + g_mp = music_pipe_new(); + else + /* if the pipe hasn't been cleared, the the audio + format must not have changed */ + assert(music_pipe_empty(g_mp) || + audio_format_equals(audio_format, + &input_audio_format)); + + input_audio_format = *audio_format; + + audio_output_all_reset_reopen(); + audio_output_all_enable_disable(); + audio_output_all_update(); + + for (i = 0; i < num_audio_outputs; ++i) { + if (audio_outputs[i]->enabled) + enabled = true; + + if (audio_outputs[i]->open) + ret = true; + } + + if (!enabled) + g_set_error(error_r, output_quark(), 0, + "All audio outputs are disabled"); + else if (!ret) + /* TODO: obtain real error */ + g_set_error(error_r, output_quark(), 0, + "Failed to open audio output"); + + if (!ret) + /* close all devices if there was an error */ + audio_output_all_close(); + + return ret; +} + +/** + * Has the specified audio output already consumed this chunk? + */ +static bool +chunk_is_consumed_in(const struct audio_output *ao, + const struct music_chunk *chunk) +{ + if (!ao->open) + return true; + + if (ao->chunk == NULL) + return false; + + assert(chunk == ao->chunk || music_pipe_contains(g_mp, ao->chunk)); + + if (chunk != ao->chunk) { + assert(chunk->next != NULL); + return true; + } + + return ao->chunk_finished && chunk->next == NULL; +} + +/** + * Has this chunk been consumed by all audio outputs? + */ +static bool +chunk_is_consumed(const struct music_chunk *chunk) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) { + const struct audio_output *ao = audio_outputs[i]; + bool consumed; + + g_mutex_lock(ao->mutex); + consumed = chunk_is_consumed_in(ao, chunk); + g_mutex_unlock(ao->mutex); + + if (!consumed) + return false; + } + + return true; +} + +/** + * There's only one chunk left in the pipe (#g_mp), and all audio + * outputs have consumed it already. Clear the reference. + */ +static void +clear_tail_chunk(G_GNUC_UNUSED const struct music_chunk *chunk, bool *locked) +{ + assert(chunk->next == NULL); + assert(music_pipe_contains(g_mp, chunk)); + + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = audio_outputs[i]; + + /* this mutex will be unlocked by the caller when it's + ready */ + g_mutex_lock(ao->mutex); + locked[i] = ao->open; + + if (!locked[i]) { + g_mutex_unlock(ao->mutex); + continue; + } + + assert(ao->chunk == chunk); + assert(ao->chunk_finished); + ao->chunk = NULL; + } +} + +unsigned +audio_output_all_check(void) +{ + const struct music_chunk *chunk; + bool is_tail; + struct music_chunk *shifted; + bool locked[num_audio_outputs]; + + assert(g_music_buffer != NULL); + assert(g_mp != NULL); + + while ((chunk = music_pipe_peek(g_mp)) != NULL) { + assert(!music_pipe_empty(g_mp)); + + if (!chunk_is_consumed(chunk)) + /* at least one output is not finished playing + this chunk */ + return music_pipe_size(g_mp); + + if (chunk->length > 0 && chunk->times >= 0.0) + /* only update elapsed_time if the chunk + provides a defined value */ + audio_output_all_elapsed_time = chunk->times; + + is_tail = chunk->next == NULL; + if (is_tail) + /* this is the tail of the pipe - clear the + chunk reference in all outputs */ + clear_tail_chunk(chunk, locked); + + /* remove the chunk from the pipe */ + shifted = music_pipe_shift(g_mp); + assert(shifted == chunk); + + if (is_tail) + /* unlock all audio outputs which were locked + by clear_tail_chunk() */ + for (unsigned i = 0; i < num_audio_outputs; ++i) + if (locked[i]) + g_mutex_unlock(audio_outputs[i]->mutex); + + /* return the chunk to the buffer */ + music_buffer_return(g_music_buffer, shifted); + } + + return 0; +} + +bool +audio_output_all_wait(struct player_control *pc, unsigned threshold) +{ + pc->Lock(); + + if (audio_output_all_check() < threshold) { + pc->Unlock(); + return true; + } + + pc->Wait(); + pc->Unlock(); + + return audio_output_all_check() < threshold; +} + +void +audio_output_all_pause(void) +{ + unsigned int i; + + audio_output_all_update(); + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_pause(audio_outputs[i]); + + audio_output_wait_all(); +} + +void +audio_output_all_drain(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_drain_async(audio_outputs[i]); + + audio_output_wait_all(); +} + +void +audio_output_all_cancel(void) +{ + unsigned int i; + + /* send the cancel() command to all audio outputs */ + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_cancel(audio_outputs[i]); + + audio_output_wait_all(); + + /* clear the music pipe and return all chunks to the buffer */ + + if (g_mp != NULL) + music_pipe_clear(g_mp, g_music_buffer); + + /* the audio outputs are now waiting for a signal, to + synchronize the cleared music pipe */ + + audio_output_allow_play_all(); + + /* invalidate elapsed_time */ + + audio_output_all_elapsed_time = -1.0; +} + +void +audio_output_all_close(void) +{ + unsigned int i; + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_close(audio_outputs[i]); + + if (g_mp != NULL) { + assert(g_music_buffer != NULL); + + music_pipe_clear(g_mp, g_music_buffer); + music_pipe_free(g_mp); + g_mp = NULL; + } + + g_music_buffer = NULL; + + audio_format_clear(&input_audio_format); + + audio_output_all_elapsed_time = -1.0; +} + +void +audio_output_all_release(void) +{ + unsigned int i; + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_release(audio_outputs[i]); + + if (g_mp != NULL) { + assert(g_music_buffer != NULL); + + music_pipe_clear(g_mp, g_music_buffer); + music_pipe_free(g_mp); + g_mp = NULL; + } + + g_music_buffer = NULL; + + audio_format_clear(&input_audio_format); + + audio_output_all_elapsed_time = -1.0; +} + +void +audio_output_all_song_border(void) +{ + /* clear the elapsed_time pointer at the beginning of a new + song */ + audio_output_all_elapsed_time = 0.0; +} + +float +audio_output_all_get_elapsed_time(void) +{ + return audio_output_all_elapsed_time; +} diff --git a/src/OutputAll.hxx b/src/OutputAll.hxx new file mode 100644 index 000000000..becf4b695 --- /dev/null +++ b/src/OutputAll.hxx @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Functions for dealing with all configured (enabled) audion outputs + * at once. + * + */ + +#ifndef OUTPUT_ALL_H +#define OUTPUT_ALL_H + +#include "replay_gain_info.h" +#include "gerror.h" + +#include <stdbool.h> +#include <stddef.h> + +struct audio_format; +struct music_buffer; +struct music_chunk; +struct player_control; + +/** + * Global initialization: load audio outputs from the configuration + * file and initialize them. + */ +void +audio_output_all_init(struct player_control *pc); + +/** + * Global finalization: free memory occupied by audio outputs. All + */ +void +audio_output_all_finish(void); + +/** + * Returns the total number of audio output devices, including those + * who are disabled right now. + */ +unsigned int audio_output_count(void); + +/** + * Returns the "i"th audio output device. + */ +struct audio_output * +audio_output_get(unsigned i); + +/** + * Returns the audio output device with the specified name. Returns + * NULL if the name does not exist. + */ +struct audio_output * +audio_output_find(const char *name); + +/** + * Checks the "enabled" flag of all audio outputs, and if one has + * changed, commit the change. + */ +void +audio_output_all_enable_disable(void); + +/** + * Opens all audio outputs which are not disabled. + * + * @param audio_format the preferred audio format, or NULL to reuse + * the previous format + * @param buffer the #music_buffer where consumed #music_chunk objects + * should be returned + * @return true on success, false on failure + */ +bool +audio_output_all_open(const struct audio_format *audio_format, + struct music_buffer *buffer, + GError **error_r); + +/** + * Closes all audio outputs. + */ +void +audio_output_all_close(void); + +/** + * Closes all audio outputs. Outputs with the "always_on" flag are + * put into pause mode. + */ +void +audio_output_all_release(void); + +void +audio_output_all_set_replay_gain_mode(enum replay_gain_mode mode); + +/** + * Enqueue a #music_chunk object for playing, i.e. pushes it to a + * #music_pipe. + * + * @param chunk the #music_chunk object to be played + * @return true on success, false if no audio output was able to play + * (all closed then) + */ +bool +audio_output_all_play(struct music_chunk *chunk, GError **error_r); + +/** + * Checks if the output devices have drained their music pipe, and + * returns the consumed music chunks to the #music_buffer. + * + * @return the number of chunks to play left in the #music_pipe + */ +unsigned +audio_output_all_check(void); + +/** + * Checks if the size of the #music_pipe is below the #threshold. If + * not, it attempts to synchronize with all output threads, and waits + * until another #music_chunk is finished. + * + * @param threshold the maximum number of chunks in the pipe + * @return true if there are less than #threshold chunks in the pipe + */ +bool +audio_output_all_wait(struct player_control *pc, unsigned threshold); + +/** + * Puts all audio outputs into pause mode. Most implementations will + * simply close it then. + */ +void +audio_output_all_pause(void); + +/** + * Drain all audio outputs. + */ +void +audio_output_all_drain(void); + +/** + * Try to cancel data which may still be in the device's buffers. + */ +void +audio_output_all_cancel(void); + +/** + * Indicate that a new song will begin now. + */ +void +audio_output_all_song_border(void); + +/** + * Returns the "elapsed_time" stamp of the most recently finished + * chunk. A negative value is returned when no chunk has been + * finished yet. + */ +float +audio_output_all_get_elapsed_time(void); + +#endif diff --git a/src/OutputCommand.cxx b/src/OutputCommand.cxx new file mode 100644 index 000000000..be52b49a8 --- /dev/null +++ b/src/OutputCommand.cxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Glue functions for controlling the audio outputs over the MPD + * protocol. These functions perform extra validation on all + * parameters, because they might be from an untrusted source. + * + */ + +#include "config.h" +#include "OutputCommand.hxx" +#include "OutputAll.hxx" +#include "PlayerControl.hxx" +#include "Idle.hxx" + +extern "C" { +#include "output_internal.h" +#include "output_plugin.h" +#include "mixer_control.h" +} + +extern unsigned audio_output_state_version; + +bool +audio_output_enable_index(unsigned idx) +{ + struct audio_output *ao; + + if (idx >= audio_output_count()) + return false; + + ao = audio_output_get(idx); + if (ao->enabled) + return true; + + ao->enabled = true; + idle_add(IDLE_OUTPUT); + + ao->player_control->UpdateAudio(); + + ++audio_output_state_version; + + return true; +} + +bool +audio_output_disable_index(unsigned idx) +{ + struct audio_output *ao; + struct mixer *mixer; + + if (idx >= audio_output_count()) + return false; + + ao = audio_output_get(idx); + if (!ao->enabled) + return true; + + ao->enabled = false; + idle_add(IDLE_OUTPUT); + + mixer = ao->mixer; + if (mixer != NULL) { + mixer_close(mixer); + idle_add(IDLE_MIXER); + } + + ao->player_control->UpdateAudio(); + + ++audio_output_state_version; + + return true; +} diff --git a/src/OutputCommand.hxx b/src/OutputCommand.hxx new file mode 100644 index 000000000..74eaf8f1c --- /dev/null +++ b/src/OutputCommand.hxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Glue functions for controlling the audio outputs over the MPD + * protocol. These functions perform extra validation on all + * parameters, because they might be from an untrusted source. + * + */ + +#ifndef MPD_OUTPUT_COMMAND_HXX +#define MPD_OUTPUT_COMMAND_HXX + +/** + * Enables an audio output. Returns false if the specified output + * does not exist. + */ +bool +audio_output_enable_index(unsigned idx); + +/** + * Disables an audio output. Returns false if the specified output + * does not exist. + */ +bool +audio_output_disable_index(unsigned idx); + +#endif diff --git a/src/OutputCommands.cxx b/src/OutputCommands.cxx new file mode 100644 index 000000000..7d626477a --- /dev/null +++ b/src/OutputCommands.cxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputCommands.hxx" +#include "OutputPrint.hxx" +#include "OutputCommand.hxx" +#include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" + +#include <string.h> + +enum command_return +handle_enableoutput(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned device; + bool ret; + + if (!check_unsigned(client, &device, argv[1])) + return COMMAND_RETURN_ERROR; + + ret = audio_output_enable_index(device); + if (!ret) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such audio output"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_disableoutput(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned device; + bool ret; + + if (!check_unsigned(client, &device, argv[1])) + return COMMAND_RETURN_ERROR; + + ret = audio_output_disable_index(device); + if (!ret) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such audio output"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_devices(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + printAudioDevices(client); + + return COMMAND_RETURN_OK; +} diff --git a/src/OutputCommands.hxx b/src/OutputCommands.hxx new file mode 100644 index 000000000..4f7082bfb --- /dev/null +++ b/src/OutputCommands.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_COMMANDS_HXX +#define MPD_OUTPUT_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_enableoutput(Client *client, int argc, char *argv[]); + +enum command_return +handle_disableoutput(Client *client, int argc, char *argv[]); + +enum command_return +handle_devices(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/OutputControl.cxx b/src/OutputControl.cxx new file mode 100644 index 000000000..37565082e --- /dev/null +++ b/src/OutputControl.cxx @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputControl.hxx" +#include "OutputThread.hxx" +#include "output_api.h" + +extern "C" { +#include "output_internal.h" +#include "mixer_control.h" +#include "mixer_plugin.h" +} + +#include "notify.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" +#include "FilterPlugin.hxx" + +#include <assert.h> +#include <stdlib.h> + +enum { + /** after a failure, wait this number of seconds before + automatically reopening the device */ + REOPEN_AFTER = 10, +}; + +struct notify audio_output_client_notify; + +/** + * Waits for command completion. + * + * @param ao the #audio_output instance; must be locked + */ +static void ao_command_wait(struct audio_output *ao) +{ + while (ao->command != AO_COMMAND_NONE) { + g_mutex_unlock(ao->mutex); + audio_output_client_notify.Wait(); + g_mutex_lock(ao->mutex); + } +} + +/** + * Sends a command to the #audio_output object, but does not wait for + * completion. + * + * @param ao the #audio_output instance; must be locked + */ +static void ao_command_async(struct audio_output *ao, + enum audio_output_command cmd) +{ + assert(ao->command == AO_COMMAND_NONE); + ao->command = cmd; + g_cond_signal(ao->cond); +} + +/** + * Sends a command to the #audio_output object and waits for + * completion. + * + * @param ao the #audio_output instance; must be locked + */ +static void +ao_command(struct audio_output *ao, enum audio_output_command cmd) +{ + ao_command_async(ao, cmd); + ao_command_wait(ao); +} + +/** + * Lock the #audio_output object and execute the command + * synchronously. + */ +static void +ao_lock_command(struct audio_output *ao, enum audio_output_command cmd) +{ + g_mutex_lock(ao->mutex); + ao_command(ao, cmd); + g_mutex_unlock(ao->mutex); +} + +void +audio_output_set_replay_gain_mode(struct audio_output *ao, + enum replay_gain_mode mode) +{ + if (ao->replay_gain_filter != NULL) + replay_gain_filter_set_mode(ao->replay_gain_filter, mode); +} + +void +audio_output_enable(struct audio_output *ao) +{ + if (ao->thread == NULL) { + if (ao->plugin->enable == NULL) { + /* don't bother to start the thread now if the + device doesn't even have a enable() method; + just assign the variable and we're done */ + ao->really_enabled = true; + return; + } + + audio_output_thread_start(ao); + } + + ao_lock_command(ao, AO_COMMAND_ENABLE); +} + +void +audio_output_disable(struct audio_output *ao) +{ + if (ao->thread == NULL) { + if (ao->plugin->disable == NULL) + ao->really_enabled = false; + else + /* if there's no thread yet, the device cannot + be enabled */ + assert(!ao->really_enabled); + + return; + } + + ao_lock_command(ao, AO_COMMAND_DISABLE); +} + +/** + * Object must be locked (and unlocked) by the caller. + */ +static bool +audio_output_open(struct audio_output *ao, + const struct audio_format *audio_format, + const struct music_pipe *mp) +{ + bool open; + + assert(ao != NULL); + assert(ao->allow_play); + assert(audio_format_valid(audio_format)); + assert(mp != NULL); + + if (ao->fail_timer != NULL) { + g_timer_destroy(ao->fail_timer); + ao->fail_timer = NULL; + } + + if (ao->open && + audio_format_equals(audio_format, &ao->in_audio_format)) { + assert(ao->pipe == mp || + (ao->always_on && ao->pause)); + + if (ao->pause) { + ao->chunk = NULL; + ao->pipe = mp; + + /* unpause with the CANCEL command; this is a + hack, but suits well for forcing the thread + to leave the ao_pause() thread, and we need + to flush the device buffer anyway */ + + /* we're not using audio_output_cancel() here, + because that function is asynchronous */ + ao_command(ao, AO_COMMAND_CANCEL); + } + + return true; + } + + ao->in_audio_format = *audio_format; + ao->chunk = NULL; + + ao->pipe = mp; + + if (ao->thread == NULL) + audio_output_thread_start(ao); + + ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN); + open = ao->open; + + if (open && ao->mixer != NULL) { + GError *error = NULL; + + if (!mixer_open(ao->mixer, &error)) { + g_warning("Failed to open mixer for '%s': %s", + ao->name, error->message); + g_error_free(error); + } + } + + return open; +} + +/** + * Same as audio_output_close(), but expects the lock to be held by + * the caller. + */ +static void +audio_output_close_locked(struct audio_output *ao) +{ + assert(ao != NULL); + assert(ao->allow_play); + + if (ao->mixer != NULL) + mixer_auto_close(ao->mixer); + + assert(!ao->open || ao->fail_timer == NULL); + + if (ao->open) + ao_command(ao, AO_COMMAND_CLOSE); + else if (ao->fail_timer != NULL) { + g_timer_destroy(ao->fail_timer); + ao->fail_timer = NULL; + } +} + +bool +audio_output_update(struct audio_output *ao, + const struct audio_format *audio_format, + const struct music_pipe *mp) +{ + assert(mp != NULL); + + g_mutex_lock(ao->mutex); + + if (ao->enabled && ao->really_enabled) { + if (ao->fail_timer == NULL || + g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) { + bool success = audio_output_open(ao, audio_format, mp); + g_mutex_unlock(ao->mutex); + return success; + } + } else if (audio_output_is_open(ao)) + audio_output_close_locked(ao); + + g_mutex_unlock(ao->mutex); + return false; +} + +void +audio_output_play(struct audio_output *ao) +{ + g_mutex_lock(ao->mutex); + + assert(ao->allow_play); + + if (audio_output_is_open(ao)) + g_cond_signal(ao->cond); + + g_mutex_unlock(ao->mutex); +} + +void audio_output_pause(struct audio_output *ao) +{ + if (ao->mixer != NULL && ao->plugin->pause == NULL) + /* the device has no pause mode: close the mixer, + unless its "global" flag is set (checked by + mixer_auto_close()) */ + mixer_auto_close(ao->mixer); + + g_mutex_lock(ao->mutex); + assert(ao->allow_play); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_PAUSE); + g_mutex_unlock(ao->mutex); +} + +void +audio_output_drain_async(struct audio_output *ao) +{ + g_mutex_lock(ao->mutex); + assert(ao->allow_play); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_DRAIN); + g_mutex_unlock(ao->mutex); +} + +void audio_output_cancel(struct audio_output *ao) +{ + g_mutex_lock(ao->mutex); + + if (audio_output_is_open(ao)) { + ao->allow_play = false; + ao_command_async(ao, AO_COMMAND_CANCEL); + } + + g_mutex_unlock(ao->mutex); +} + +void +audio_output_allow_play(struct audio_output *ao) +{ + g_mutex_lock(ao->mutex); + + ao->allow_play = true; + if (audio_output_is_open(ao)) + g_cond_signal(ao->cond); + + g_mutex_unlock(ao->mutex); +} + +void +audio_output_release(struct audio_output *ao) +{ + if (ao->always_on) + audio_output_pause(ao); + else + audio_output_close(ao); +} + +void audio_output_close(struct audio_output *ao) +{ + assert(ao != NULL); + assert(!ao->open || ao->fail_timer == NULL); + + g_mutex_lock(ao->mutex); + audio_output_close_locked(ao); + g_mutex_unlock(ao->mutex); +} + +void audio_output_finish(struct audio_output *ao) +{ + audio_output_close(ao); + + assert(ao->fail_timer == NULL); + + if (ao->thread != NULL) { + assert(ao->allow_play); + ao_lock_command(ao, AO_COMMAND_KILL); + g_thread_join(ao->thread); + ao->thread = NULL; + } + + audio_output_free(ao); +} diff --git a/src/OutputControl.hxx b/src/OutputControl.hxx new file mode 100644 index 000000000..2ff095398 --- /dev/null +++ b/src/OutputControl.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_CONTROL_HXX +#define MPD_OUTPUT_CONTROL_HXX + +#include "replay_gain_info.h" + +#include <stddef.h> + +struct audio_output; +struct audio_format; +struct config_param; +struct music_pipe; +struct player_control; + +void +audio_output_set_replay_gain_mode(struct audio_output *ao, + enum replay_gain_mode mode); + +/** + * Enables the device. + */ +void +audio_output_enable(struct audio_output *ao); + +/** + * Disables the device. + */ +void +audio_output_disable(struct audio_output *ao); + +/** + * Opens or closes the device, depending on the "enabled" flag. + * + * @return true if the device is open + */ +bool +audio_output_update(struct audio_output *ao, + const struct audio_format *audio_format, + const struct music_pipe *mp); + +void +audio_output_play(struct audio_output *ao); + +void audio_output_pause(struct audio_output *ao); + +void +audio_output_drain_async(struct audio_output *ao); + +/** + * Clear the "allow_play" flag and send the "CANCEL" command + * asynchronously. To finish the operation, the caller has to call + * audio_output_allow_play(). + */ +void audio_output_cancel(struct audio_output *ao); + +/** + * Set the "allow_play" and signal the thread. + */ +void +audio_output_allow_play(struct audio_output *ao); + +void audio_output_close(struct audio_output *ao); + +/** + * Closes the audio output, but if the "always_on" flag is set, put it + * into pause mode instead. + */ +void +audio_output_release(struct audio_output *ao); + +void audio_output_finish(struct audio_output *ao); + +#endif diff --git a/src/OutputError.hxx b/src/OutputError.hxx new file mode 100644 index 000000000..451df9857 --- /dev/null +++ b/src/OutputError.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_ERROR_HXX +#define MPD_OUTPUT_ERROR_HXX + +#include <glib.h> + +/** + * Quark for GError.domain. + */ +G_GNUC_CONST +static inline GQuark +output_quark(void) +{ + return g_quark_from_static_string("output"); +} + +#endif diff --git a/src/OutputFinish.cxx b/src/OutputFinish.cxx new file mode 100644 index 000000000..8b9480b88 --- /dev/null +++ b/src/OutputFinish.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. + */ + +#include "config.h" + +extern "C" { +#include "output_internal.h" +#include "output_plugin.h" +#include "mixer_control.h" +} + +#include "FilterInternal.hxx" + +#include <assert.h> + +void +ao_base_finish(struct audio_output *ao) +{ + assert(!ao->open); + assert(ao->fail_timer == NULL); + assert(ao->thread == NULL); + + if (ao->mixer != NULL) + mixer_free(ao->mixer); + + g_cond_free(ao->cond); + g_mutex_free(ao->mutex); + + delete ao->replay_gain_filter; + delete ao->other_replay_gain_filter; + delete ao->filter; + + pcm_buffer_deinit(&ao->cross_fade_buffer); +} + +void +audio_output_free(struct audio_output *ao) +{ + assert(!ao->open); + assert(ao->fail_timer == NULL); + assert(ao->thread == NULL); + + ao_plugin_finish(ao); +} diff --git a/src/OutputInit.cxx b/src/OutputInit.cxx new file mode 100644 index 000000000..8c60fe4f1 --- /dev/null +++ b/src/OutputInit.cxx @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputControl.hxx" +#include "OutputList.hxx" +#include "OutputError.hxx" +#include "FilterConfig.hxx" +#include "output_api.h" +#include "AudioParser.hxx" + +extern "C" { +#include "output_internal.h" +#include "mixer_control.h" +#include "mixer_type.h" +#include "mixer_list.h" +} + +#include "mixer/SoftwareMixerPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterRegistry.hxx" +#include "filter/AutoConvertFilterPlugin.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" +#include "filter/ChainFilterPlugin.hxx" + +#include <glib.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "output" + +#define AUDIO_OUTPUT_TYPE "type" +#define AUDIO_OUTPUT_NAME "name" +#define AUDIO_OUTPUT_FORMAT "format" +#define AUDIO_FILTERS "filters" + +static const struct audio_output_plugin * +audio_output_detect(GError **error) +{ + g_warning("Attempt to detect audio output device"); + + audio_output_plugins_for_each(plugin) { + if (plugin->test_default_device == NULL) + continue; + + g_warning("Attempting to detect a %s audio device", + plugin->name); + if (ao_plugin_test_default_device(plugin)) + return plugin; + } + + g_set_error(error, output_quark(), 0, + "Unable to detect an audio device"); + return NULL; +} + +/** + * Determines the mixer type which should be used for the specified + * configuration block. + * + * This handles the deprecated options mixer_type (global) and + * mixer_enabled, if the mixer_type setting is not configured. + */ +static enum mixer_type +audio_output_mixer_type(const struct config_param *param) +{ + /* read the local "mixer_type" setting */ + const char *p = config_get_block_string(param, "mixer_type", NULL); + if (p != NULL) + return mixer_type_parse(p); + + /* try the local "mixer_enabled" setting next (deprecated) */ + if (!config_get_block_bool(param, "mixer_enabled", true)) + return MIXER_TYPE_NONE; + + /* fall back to the global "mixer_type" setting (also + deprecated) */ + return mixer_type_parse(config_get_string(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, + Filter &filter_chain, + GError **error_r) +{ + struct mixer *mixer; + + switch (audio_output_mixer_type(param)) { + case MIXER_TYPE_NONE: + case MIXER_TYPE_UNKNOWN: + return NULL; + + case MIXER_TYPE_HARDWARE: + if (plugin == NULL) + return NULL; + + return mixer_new(plugin, ao, param, error_r); + + case MIXER_TYPE_SOFTWARE: + mixer = mixer_new(&software_mixer_plugin, NULL, NULL, NULL); + assert(mixer != NULL); + + filter_chain_append(filter_chain, "software_mixer", + software_mixer_get_filter(mixer)); + return mixer; + } + + assert(false); + return NULL; +} + +bool +ao_base_init(struct audio_output *ao, + const struct audio_output_plugin *plugin, + const struct config_param *param, GError **error_r) +{ + assert(ao != NULL); + assert(plugin != NULL); + assert(plugin->finish != NULL); + assert(plugin->open != NULL); + assert(plugin->close != NULL); + assert(plugin->play != NULL); + + GError *error = NULL; + + if (param) { + const char *p; + + ao->name = config_get_block_string(param, AUDIO_OUTPUT_NAME, + NULL); + if (ao->name == NULL) { + g_set_error(error_r, output_quark(), 0, + "Missing \"name\" configuration"); + return false; + } + + p = config_get_block_string(param, AUDIO_OUTPUT_FORMAT, + NULL); + if (p != NULL) { + bool success = + audio_format_parse(&ao->config_audio_format, + p, true, error_r); + if (!success) + return false; + } else + audio_format_clear(&ao->config_audio_format); + } else { + ao->name = "default detected output"; + + audio_format_clear(&ao->config_audio_format); + } + + ao->plugin = plugin; + ao->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; + ao->open = false; + ao->pause = false; + ao->allow_play = true; + ao->fail_timer = NULL; + + pcm_buffer_init(&ao->cross_fade_buffer); + + /* set up the filter chain */ + + ao->filter = filter_chain_new(); + assert(ao->filter != NULL); + + /* create the normalization filter (if configured) */ + + if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) { + Filter *normalize_filter = + filter_new(&normalize_filter_plugin, NULL, NULL); + assert(normalize_filter != NULL); + + filter_chain_append(*ao->filter, "normalize", + autoconvert_filter_new(normalize_filter)); + } + + filter_chain_parse(*ao->filter, + config_get_block_string(param, AUDIO_FILTERS, ""), + &error + ); + + // It's not really fatal - Part of the filter chain has been set up already + // and even an empty one will work (if only with unexpected behaviour) + if (error != NULL) { + g_warning("Failed to initialize filter chain for '%s': %s", + ao->name, error->message); + g_error_free(error); + } + + ao->thread = NULL; + ao->command = AO_COMMAND_NONE; + ao->mutex = g_mutex_new(); + ao->cond = g_cond_new(); + + ao->mixer = NULL; + ao->replay_gain_filter = NULL; + ao->other_replay_gain_filter = NULL; + + /* done */ + + return true; +} + +static bool +audio_output_setup(struct audio_output *ao, const struct config_param *param, + GError **error_r) +{ + + /* create the replay_gain filter */ + + const char *replay_gain_handler = + config_get_block_string(param, "replay_gain_handler", + "software"); + + if (strcmp(replay_gain_handler, "none") != 0) { + ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, NULL); + assert(ao->replay_gain_filter != NULL); + + ao->replay_gain_serial = 0; + + ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, NULL); + assert(ao->other_replay_gain_filter != NULL); + + ao->other_replay_gain_serial = 0; + } else { + ao->replay_gain_filter = NULL; + ao->other_replay_gain_filter = NULL; + } + + /* set up the mixer */ + + GError *error = NULL; + ao->mixer = audio_output_load_mixer(ao, param, + ao->plugin->mixer_plugin, + *ao->filter, &error); + if (ao->mixer == NULL && error != NULL) { + g_warning("Failed to initialize hardware mixer for '%s': %s", + ao->name, error->message); + g_error_free(error); + } + + /* use the hardware mixer for replay gain? */ + + if (strcmp(replay_gain_handler, "mixer") == 0) { + if (ao->mixer != NULL) + replay_gain_filter_set_mixer(ao->replay_gain_filter, + ao->mixer, 100); + else + g_warning("No such mixer for output '%s'", ao->name); + } else if (strcmp(replay_gain_handler, "software") != 0 && + ao->replay_gain_filter != NULL) { + g_set_error(error_r, output_quark(), 0, + "Invalid \"replay_gain_handler\" value"); + return false; + } + + /* the "convert" filter must be the last one in the chain */ + + ao->convert_filter = filter_new(&convert_filter_plugin, NULL, NULL); + assert(ao->convert_filter != NULL); + + filter_chain_append(*ao->filter, "convert", ao->convert_filter); + + return true; +} + +struct audio_output * +audio_output_new(const struct config_param *param, + struct player_control *pc, + GError **error_r) +{ + const struct audio_output_plugin *plugin; + + if (param) { + const char *p; + + p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL); + if (p == NULL) { + g_set_error(error_r, output_quark(), 0, + "Missing \"type\" configuration"); + return nullptr; + } + + plugin = audio_output_plugin_get(p); + if (plugin == NULL) { + g_set_error(error_r, output_quark(), 0, + "No such audio output plugin: %s", p); + return nullptr; + } + } else { + g_warning("No 'audio_output' defined in config file\n"); + + plugin = audio_output_detect(error_r); + if (plugin == NULL) + return nullptr; + + g_message("Successfully detected a %s audio device", + plugin->name); + } + + struct audio_output *ao = ao_plugin_init(plugin, param, error_r); + if (ao == NULL) + return NULL; + + if (!audio_output_setup(ao, param, error_r)) { + ao_plugin_finish(ao); + return NULL; + } + + ao->player_control = pc; + return ao; +} diff --git a/src/OutputList.cxx b/src/OutputList.cxx new file mode 100644 index 000000000..72f241bdd --- /dev/null +++ b/src/OutputList.cxx @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputList.hxx" +#include "output_api.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/HttpdOutputPlugin.hxx" +#include "output/jack_output_plugin.h" +#include "output/mvp_output_plugin.h" +#include "output/NullOutputPlugin.hxx" +#include "output/openal_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/RoarOutputPlugin.hxx" +#include "output/shout_output_plugin.h" +#include "output/solaris_output_plugin.h" +#include "output/winmm_output_plugin.h" + +const struct audio_output_plugin *const audio_output_plugins[] = { +#ifdef HAVE_SHOUT + &shout_output_plugin, +#endif + &null_output_plugin, +#ifdef HAVE_FIFO + &fifo_output_plugin, +#endif +#ifdef ENABLE_PIPE_OUTPUT + &pipe_output_plugin, +#endif +#ifdef HAVE_ALSA + &alsa_output_plugin, +#endif +#ifdef HAVE_ROAR + &roar_output_plugin, +#endif +#ifdef HAVE_AO + &ao_output_plugin, +#endif +#ifdef HAVE_OSS + &oss_output_plugin, +#endif +#ifdef HAVE_OPENAL + &openal_output_plugin, +#endif +#ifdef HAVE_OSX + &osx_output_plugin, +#endif +#ifdef ENABLE_SOLARIS_OUTPUT + &solaris_output_plugin, +#endif +#ifdef HAVE_PULSE + &pulse_output_plugin, +#endif +#ifdef HAVE_MVP + &mvp_output_plugin, +#endif +#ifdef HAVE_JACK + &jack_output_plugin, +#endif +#ifdef ENABLE_HTTPD_OUTPUT + &httpd_output_plugin, +#endif +#ifdef ENABLE_RECORDER_OUTPUT + &recorder_output_plugin, +#endif +#ifdef ENABLE_WINMM_OUTPUT + &winmm_output_plugin, +#endif +#ifdef ENABLE_FFADO_OUTPUT + &ffado_output_plugin, +#endif + NULL +}; + +const struct audio_output_plugin * +audio_output_plugin_get(const char *name) +{ + audio_output_plugins_for_each(plugin) + if (strcmp(plugin->name, name) == 0) + return plugin; + + return NULL; +} diff --git a/src/OutputList.hxx b/src/OutputList.hxx new file mode 100644 index 000000000..b7716c67e --- /dev/null +++ b/src/OutputList.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_LIST_HXX +#define MPD_OUTPUT_LIST_HXX + +extern const struct audio_output_plugin *const audio_output_plugins[]; + +const struct audio_output_plugin * +audio_output_plugin_get(const char *name); + +#define audio_output_plugins_for_each(plugin) \ + for (const struct audio_output_plugin *plugin, \ + *const*output_plugin_iterator = &audio_output_plugins[0]; \ + (plugin = *output_plugin_iterator) != NULL; ++output_plugin_iterator) + +#endif diff --git a/src/OutputPlugin.cxx b/src/OutputPlugin.cxx new file mode 100644 index 000000000..9aa0f7792 --- /dev/null +++ b/src/OutputPlugin.cxx @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +extern "C" { +#include "output_plugin.h" +} + +#include "output_internal.h" + +struct audio_output * +ao_plugin_init(const struct audio_output_plugin *plugin, + const struct config_param *param, + GError **error) +{ + assert(plugin != NULL); + assert(plugin->init != NULL); + + return plugin->init(param, error); +} + +void +ao_plugin_finish(struct audio_output *ao) +{ + ao->plugin->finish(ao); +} + +bool +ao_plugin_enable(struct audio_output *ao, GError **error_r) +{ + return ao->plugin->enable != NULL + ? ao->plugin->enable(ao, error_r) + : true; +} + +void +ao_plugin_disable(struct audio_output *ao) +{ + if (ao->plugin->disable != NULL) + ao->plugin->disable(ao); +} + +bool +ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error) +{ + return ao->plugin->open(ao, audio_format, error); +} + +void +ao_plugin_close(struct audio_output *ao) +{ + ao->plugin->close(ao); +} + +unsigned +ao_plugin_delay(struct audio_output *ao) +{ + return ao->plugin->delay != NULL + ? ao->plugin->delay(ao) + : 0; +} + +void +ao_plugin_send_tag(struct audio_output *ao, const struct tag *tag) +{ + if (ao->plugin->send_tag != NULL) + ao->plugin->send_tag(ao, tag); +} + +size_t +ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) +{ + return ao->plugin->play(ao, chunk, size, error); +} + +void +ao_plugin_drain(struct audio_output *ao) +{ + if (ao->plugin->drain != NULL) + ao->plugin->drain(ao); +} + +void +ao_plugin_cancel(struct audio_output *ao) +{ + if (ao->plugin->cancel != NULL) + ao->plugin->cancel(ao); +} + +bool +ao_plugin_pause(struct audio_output *ao) +{ + return ao->plugin->pause != NULL && ao->plugin->pause(ao); +} diff --git a/src/OutputPrint.cxx b/src/OutputPrint.cxx new file mode 100644 index 000000000..240ea967b --- /dev/null +++ b/src/OutputPrint.cxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Protocol specific code for the audio output library. + * + */ + +#include "config.h" +#include "OutputPrint.hxx" +#include "OutputAll.hxx" +#include "output_internal.h" +#include "Client.hxx" + +void +printAudioDevices(Client *client) +{ + const unsigned n = audio_output_count(); + + for (unsigned i = 0; i < n; ++i) { + const struct audio_output *ao = audio_output_get(i); + + client_printf(client, + "outputid: %i\n" + "outputname: %s\n" + "outputenabled: %i\n", + i, ao->name, ao->enabled); + } +} diff --git a/src/OutputPrint.hxx b/src/OutputPrint.hxx new file mode 100644 index 000000000..78717d0af --- /dev/null +++ b/src/OutputPrint.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Protocol specific code for the audio output library. + * + */ + +#ifndef MPD_OUTPUT_PRINT_HXX +#define MPD_OUTPUT_PRINT_HXX + +class Client; + +void +printAudioDevices(Client *client); + +#endif diff --git a/src/OutputState.cxx b/src/OutputState.cxx new file mode 100644 index 000000000..27fa34f8f --- /dev/null +++ b/src/OutputState.cxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Saving and loading the audio output states to/from the state file. + * + */ + +#include "config.h" +#include "OutputState.hxx" +#include "OutputAll.hxx" +#include "output_internal.h" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#define AUDIO_DEVICE_STATE "audio_device_state:" + +unsigned audio_output_state_version; + +void +audio_output_state_save(FILE *fp) +{ + unsigned n = audio_output_count(); + + assert(n > 0); + + for (unsigned i = 0; i < n; ++i) { + const struct audio_output *ao = audio_output_get(i); + + fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n", + ao->enabled, ao->name); + } +} + +bool +audio_output_state_read(const char *line) +{ + long value; + char *endptr; + const char *name; + struct audio_output *ao; + + if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE)) + return false; + + line += sizeof(AUDIO_DEVICE_STATE) - 1; + + value = strtol(line, &endptr, 10); + if (*endptr != ':' || (value != 0 && value != 1)) + return false; + + if (value != 0) + /* state is "enabled": no-op */ + return true; + + name = endptr + 1; + ao = audio_output_find(name); + if (ao == NULL) { + g_debug("Ignoring device state for '%s'", name); + return true; + } + + ao->enabled = false; + return true; +} + +unsigned +audio_output_state_get_version(void) +{ + return audio_output_state_version; +} diff --git a/src/OutputState.hxx b/src/OutputState.hxx new file mode 100644 index 000000000..5ab765ba8 --- /dev/null +++ b/src/OutputState.hxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Saving and loading the audio output states to/from the state file. + * + */ + +#ifndef MPD_OUTPUT_STATE_HXX +#define MPD_OUTPUT_STATE_HXX + +#include <stdio.h> + +bool +audio_output_state_read(const char *line); + +void +audio_output_state_save(FILE *fp); + +/** + * Generates a version number for the current state of the audio + * outputs. This is used by timer_save_state_file() to determine + * whether the state has changed and the state file should be saved. + */ +unsigned +audio_output_state_get_version(void); + +#endif diff --git a/src/OutputThread.cxx b/src/OutputThread.cxx new file mode 100644 index 000000000..f1ffe876f --- /dev/null +++ b/src/OutputThread.cxx @@ -0,0 +1,689 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputThread.hxx" +#include "output_api.h" +#include "PcmMix.hxx" + +extern "C" { +#include "output_internal.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 "gcc.h" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "output" + +static void ao_command_finished(struct audio_output *ao) +{ + assert(ao->command != AO_COMMAND_NONE); + ao->command = AO_COMMAND_NONE; + + g_mutex_unlock(ao->mutex); + audio_output_client_notify.Signal(); + g_mutex_lock(ao->mutex); +} + +static bool +ao_enable(struct audio_output *ao) +{ + GError *error = NULL; + bool success; + + if (ao->really_enabled) + return true; + + g_mutex_unlock(ao->mutex); + success = ao_plugin_enable(ao, &error); + g_mutex_lock(ao->mutex); + if (!success) { + g_warning("Failed to enable \"%s\" [%s]: %s\n", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + return false; + } + + ao->really_enabled = true; + return true; +} + +static void +ao_close(struct audio_output *ao, bool drain); + +static void +ao_disable(struct audio_output *ao) +{ + if (ao->open) + ao_close(ao, false); + + if (ao->really_enabled) { + ao->really_enabled = false; + + g_mutex_unlock(ao->mutex); + ao_plugin_disable(ao); + g_mutex_lock(ao->mutex); + } +} + +static const struct audio_format * +ao_filter_open(struct audio_output *ao, audio_format &format, + GError **error_r) +{ + assert(audio_format_valid(&format)); + + /* the replay_gain filter cannot fail here */ + if (ao->replay_gain_filter != NULL) + ao->replay_gain_filter->Open(format, error_r); + if (ao->other_replay_gain_filter != NULL) + ao->other_replay_gain_filter->Open(format, error_r); + + const struct audio_format *af + = ao->filter->Open(format, error_r); + if (af == NULL) { + if (ao->replay_gain_filter != NULL) + ao->replay_gain_filter->Close(); + if (ao->other_replay_gain_filter != NULL) + ao->other_replay_gain_filter->Close(); + } + + return af; +} + +static void +ao_filter_close(struct audio_output *ao) +{ + if (ao->replay_gain_filter != NULL) + ao->replay_gain_filter->Close(); + if (ao->other_replay_gain_filter != NULL) + ao->other_replay_gain_filter->Close(); + + ao->filter->Close(); +} + +static void +ao_open(struct audio_output *ao) +{ + bool success; + GError *error = NULL; + struct audio_format_string af_string; + + assert(!ao->open); + assert(ao->pipe != NULL); + assert(ao->chunk == NULL); + assert(audio_format_valid(&ao->in_audio_format)); + + if (ao->fail_timer != NULL) { + /* this can only happen when this + output thread fails while + audio_output_open() is run in the + player thread */ + g_timer_destroy(ao->fail_timer); + ao->fail_timer = NULL; + } + + /* enable the device (just in case the last enable has failed) */ + + if (!ao_enable(ao)) + /* still no luck */ + return; + + /* open the filter */ + + 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); + g_error_free(error); + + ao->fail_timer = g_timer_new(); + return; + } + + assert(audio_format_valid(filter_audio_format)); + + ao->out_audio_format = *filter_audio_format; + audio_format_mask_apply(&ao->out_audio_format, + &ao->config_audio_format); + + g_mutex_unlock(ao->mutex); + success = ao_plugin_open(ao, &ao->out_audio_format, &error); + g_mutex_lock(ao->mutex); + + assert(!ao->open); + + if (!success) { + g_warning("Failed to open \"%s\" [%s]: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + ao_filter_close(ao); + ao->fail_timer = g_timer_new(); + return; + } + + convert_filter_set(ao->convert_filter, ao->out_audio_format); + + ao->open = true; + + g_debug("opened plugin=%s name=\"%s\" " + "audio_format=%s", + ao->plugin->name, ao->name, + audio_format_to_string(&ao->out_audio_format, &af_string)); + + if (!audio_format_equals(&ao->in_audio_format, + &ao->out_audio_format)) + g_debug("converting from %s", + audio_format_to_string(&ao->in_audio_format, + &af_string)); +} + +static void +ao_close(struct audio_output *ao, bool drain) +{ + assert(ao->open); + + ao->pipe = NULL; + + ao->chunk = NULL; + ao->open = false; + + g_mutex_unlock(ao->mutex); + + if (drain) + ao_plugin_drain(ao); + else + ao_plugin_cancel(ao); + + ao_plugin_close(ao); + ao_filter_close(ao); + + g_mutex_lock(ao->mutex); + + g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name); +} + +static void +ao_reopen_filter(struct audio_output *ao) +{ + const struct audio_format *filter_audio_format; + GError *error = NULL; + + ao_filter_close(ao); + filter_audio_format = ao_filter_open(ao, ao->in_audio_format, &error); + if (filter_audio_format == NULL) { + g_warning("Failed to open filter for \"%s\" [%s]: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + /* this is a little code duplication fro ao_close(), + but we cannot call this function because we must + not call filter_close(ao->filter) again */ + + ao->pipe = NULL; + + ao->chunk = NULL; + ao->open = false; + ao->fail_timer = g_timer_new(); + + g_mutex_unlock(ao->mutex); + ao_plugin_close(ao); + g_mutex_lock(ao->mutex); + + return; + } + + convert_filter_set(ao->convert_filter, ao->out_audio_format); +} + +static void +ao_reopen(struct audio_output *ao) +{ + if (!audio_format_fully_defined(&ao->config_audio_format)) { + if (ao->open) { + const struct music_pipe *mp = ao->pipe; + ao_close(ao, true); + ao->pipe = mp; + } + + /* no audio format is configured: copy in->out, let + the output's open() method determine the effective + out_audio_format */ + ao->out_audio_format = ao->in_audio_format; + audio_format_mask_apply(&ao->out_audio_format, + &ao->config_audio_format); + } + + if (ao->open) + /* the audio format has changed, and all filters have + to be reconfigured */ + ao_reopen_filter(ao); + else + ao_open(ao); +} + +/** + * Wait until the output's delay reaches zero. + * + * @return true if playback should be continued, false if a command + * was issued + */ +static bool +ao_wait(struct audio_output *ao) +{ + while (true) { + unsigned delay = ao_plugin_delay(ao); + if (delay == 0) + return true; + + GTimeVal tv; + g_get_current_time(&tv); + g_time_val_add(&tv, delay * 1000); + (void)g_cond_timed_wait(ao->cond, ao->mutex, &tv); + + if (ao->command != AO_COMMAND_NONE) + return false; + } +} + +static const void * +ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, + Filter *replay_gain_filter, + unsigned *replay_gain_serial_p, + size_t *length_r) +{ + assert(chunk != NULL); + assert(!chunk->IsEmpty()); + assert(chunk->CheckFormat(ao->in_audio_format)); + + const void *data = chunk->data; + size_t length = chunk->length; + + (void)ao; + + assert(length % audio_format_frame_size(&ao->in_audio_format) == 0); + + if (length > 0 && replay_gain_filter != NULL) { + if (chunk->replay_gain_serial != *replay_gain_serial_p) { + replay_gain_filter_set_info(replay_gain_filter, + chunk->replay_gain_serial != 0 + ? &chunk->replay_gain_info + : NULL); + *replay_gain_serial_p = chunk->replay_gain_serial; + } + + GError *error = NULL; + data = replay_gain_filter->FilterPCM(data, length, + &length, &error); + if (data == NULL) { + g_warning("\"%s\" [%s] failed to filter: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + return NULL; + } + } + + *length_r = length; + return data; +} + +static const void * +ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, + size_t *length_r) +{ + GError *error = NULL; + + size_t length; + const void *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter, + &ao->replay_gain_serial, &length); + if (data == NULL) + return NULL; + + if (length == 0) { + /* empty chunk, nothing to do */ + *length_r = 0; + return data; + } + + /* cross-fade */ + + if (chunk->other != NULL) { + size_t other_length; + const void *other_data = + ao_chunk_data(ao, chunk->other, + ao->other_replay_gain_filter, + &ao->other_replay_gain_serial, + &other_length); + if (other_data == NULL) + return NULL; + + if (other_length == 0) { + *length_r = 0; + return data; + } + + /* if the "other" chunk is longer, then that trailer + is used as-is, without mixing; it is part of the + "next" song being faded in, and if there's a rest, + it means cross-fading ends here */ + + if (length > other_length) + length = other_length; + + void *dest = pcm_buffer_get(&ao->cross_fade_buffer, + other_length); + memcpy(dest, other_data, other_length); + 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(sample_format(ao->in_audio_format.format))); + return NULL; + } + + data = dest; + length = other_length; + } + + /* apply filter chain */ + + data = ao->filter->FilterPCM(data, length, &length, &error); + if (data == NULL) { + g_warning("\"%s\" [%s] failed to filter: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + return NULL; + } + + *length_r = length; + return data; +} + +static bool +ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) +{ + GError *error = NULL; + + assert(ao != NULL); + assert(ao->filter != NULL); + + if (ao->tags && gcc_unlikely(chunk->tag != NULL)) { + g_mutex_unlock(ao->mutex); + ao_plugin_send_tag(ao, chunk->tag); + g_mutex_lock(ao->mutex); + } + + size_t size; +#if GCC_CHECK_VERSION(4,7) + /* workaround -Wmaybe-uninitialized false positive */ + size = 0; +#endif + const char *data = (const char *)ao_filter_chunk(ao, chunk, &size); + if (data == NULL) { + ao_close(ao, false); + + /* don't automatically reopen this device for 10 + seconds */ + ao->fail_timer = g_timer_new(); + return false; + } + + while (size > 0 && ao->command == AO_COMMAND_NONE) { + size_t nbytes; + + if (!ao_wait(ao)) + break; + + g_mutex_unlock(ao->mutex); + nbytes = ao_plugin_play(ao, data, size, &error); + g_mutex_lock(ao->mutex); + if (nbytes == 0) { + /* play()==0 means failure */ + g_warning("\"%s\" [%s] failed to play: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + ao_close(ao, false); + + /* don't automatically reopen this device for + 10 seconds */ + assert(ao->fail_timer == NULL); + ao->fail_timer = g_timer_new(); + + return false; + } + + assert(nbytes <= size); + assert(nbytes % audio_format_frame_size(&ao->out_audio_format) == 0); + + data += nbytes; + size -= nbytes; + } + + return true; +} + +static const struct music_chunk * +ao_next_chunk(struct audio_output *ao) +{ + return ao->chunk != NULL + /* continue the previous play() call */ + ? ao->chunk->next + /* get the first chunk from the pipe */ + : music_pipe_peek(ao->pipe); +} + +/** + * Plays all remaining chunks, until the tail of the pipe has been + * reached (and no more chunks are queued), or until a command is + * received. + * + * @return true if at least one chunk has been available, false if the + * tail of the pipe was already reached + */ +static bool +ao_play(struct audio_output *ao) +{ + bool success; + const struct music_chunk *chunk; + + assert(ao->pipe != NULL); + + chunk = ao_next_chunk(ao); + if (chunk == NULL) + /* no chunk available */ + return false; + + ao->chunk_finished = false; + + while (chunk != NULL && ao->command == AO_COMMAND_NONE) { + assert(!ao->chunk_finished); + + ao->chunk = chunk; + + success = ao_play_chunk(ao, chunk); + if (!success) { + assert(ao->chunk == NULL); + break; + } + + assert(ao->chunk == chunk); + chunk = chunk->next; + } + + ao->chunk_finished = true; + + g_mutex_unlock(ao->mutex); + ao->player_control->LockSignal(); + g_mutex_lock(ao->mutex); + + return true; +} + +static void ao_pause(struct audio_output *ao) +{ + bool ret; + + g_mutex_unlock(ao->mutex); + ao_plugin_cancel(ao); + g_mutex_lock(ao->mutex); + + ao->pause = true; + ao_command_finished(ao); + + do { + if (!ao_wait(ao)) + break; + + g_mutex_unlock(ao->mutex); + ret = ao_plugin_pause(ao); + g_mutex_lock(ao->mutex); + + if (!ret) { + ao_close(ao, false); + break; + } + } while (ao->command == AO_COMMAND_NONE); + + ao->pause = false; +} + +static gpointer audio_output_task(gpointer arg) +{ + struct audio_output *ao = (struct audio_output *)arg; + + g_mutex_lock(ao->mutex); + + while (1) { + switch (ao->command) { + case AO_COMMAND_NONE: + break; + + case AO_COMMAND_ENABLE: + ao_enable(ao); + ao_command_finished(ao); + break; + + case AO_COMMAND_DISABLE: + ao_disable(ao); + ao_command_finished(ao); + break; + + case AO_COMMAND_OPEN: + ao_open(ao); + ao_command_finished(ao); + break; + + case AO_COMMAND_REOPEN: + ao_reopen(ao); + ao_command_finished(ao); + break; + + case AO_COMMAND_CLOSE: + assert(ao->open); + assert(ao->pipe != NULL); + + ao_close(ao, false); + ao_command_finished(ao); + break; + + case AO_COMMAND_PAUSE: + if (!ao->open) { + /* the output has failed after + audio_output_all_pause() has + submitted the PAUSE command; bail + out */ + ao_command_finished(ao); + break; + } + + ao_pause(ao); + /* don't "break" here: this might cause + ao_play() to be called when command==CLOSE + ends the paused state - "continue" checks + the new command first */ + continue; + + case AO_COMMAND_DRAIN: + if (ao->open) { + assert(ao->chunk == NULL); + assert(music_pipe_peek(ao->pipe) == NULL); + + g_mutex_unlock(ao->mutex); + ao_plugin_drain(ao); + g_mutex_lock(ao->mutex); + } + + ao_command_finished(ao); + continue; + + case AO_COMMAND_CANCEL: + ao->chunk = NULL; + + if (ao->open) { + g_mutex_unlock(ao->mutex); + ao_plugin_cancel(ao); + g_mutex_lock(ao->mutex); + } + + ao_command_finished(ao); + continue; + + case AO_COMMAND_KILL: + ao->chunk = NULL; + ao_command_finished(ao); + g_mutex_unlock(ao->mutex); + return NULL; + } + + if (ao->open && ao->allow_play && ao_play(ao)) + /* don't wait for an event if there are more + chunks in the pipe */ + continue; + + if (ao->command == AO_COMMAND_NONE) + g_cond_wait(ao->cond, ao->mutex); + } +} + +void audio_output_thread_start(struct audio_output *ao) +{ + GError *e = NULL; + + assert(ao->command == AO_COMMAND_NONE); + + if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e))) + MPD_ERROR("Failed to spawn output task: %s\n", e->message); +} diff --git a/src/OutputThread.hxx b/src/OutputThread.hxx new file mode 100644 index 000000000..1a7932162 --- /dev/null +++ b/src/OutputThread.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_THREAD_HXX +#define MPD_OUTPUT_THREAD_HXX + +struct audio_output; + +void audio_output_thread_start(struct audio_output *ao); + +#endif diff --git a/src/Page.cxx b/src/Page.cxx new file mode 100644 index 000000000..bf30376e4 --- /dev/null +++ b/src/Page.cxx @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Page.hxx" + +#include <glib.h> + +#include <new> + +#include <assert.h> +#include <string.h> + +Page * +Page::Create(size_t size) +{ + void *p = g_malloc(sizeof(Page) + size - + sizeof(Page::data)); + return ::new(p) Page(size); +} + +Page * +Page::Copy(const void *data, size_t size) +{ + assert(data != nullptr); + + Page *page = Create(size); + memcpy(page->data, data, size); + return page; +} + +Page * +Page::Concat(const Page &a, const Page &b) +{ + Page *page = Create(a.size + b.size); + + memcpy(page->data, a.data, a.size); + memcpy(page->data + a.size, b.data, b.size); + + return page; +} + +bool +Page::Unref() +{ + bool unused = ref.Decrement(); + + if (unused) { + this->Page::~Page(); + g_free(this); + } + + return unused; +} diff --git a/src/Page.hxx b/src/Page.hxx new file mode 100644 index 000000000..2bc9a6ac5 --- /dev/null +++ b/src/Page.hxx @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This is a library which manages reference counted buffers. + */ + +#ifndef MPD_PAGE_HXX +#define MPD_PAGE_HXX + +#include "util/RefCount.hxx" + +#include <algorithm> + +#include <stddef.h> + +/** + * A dynamically allocated buffer which keeps track of its reference + * count. This is useful for passing buffers around, when several + * instances hold references to one buffer. + */ +class Page { + /** + * The number of references to this buffer. This library uses + * atomic functions to access it, i.e. no locks are required. + * As soon as this attribute reaches zero, the buffer is + * freed. + */ + RefCount ref; + +public: + /** + * The size of this buffer in bytes. + */ + const size_t size; + + /** + * Dynamic array containing the buffer data. + */ + unsigned char data[sizeof(long)]; + +protected: + Page(size_t _size):size(_size) {} + ~Page() = default; + + /** + * Allocates a new #Page object, without filling the data + * element. + */ + static Page *Create(size_t size); + +public: + /** + * Creates a new #page object, and copies data from the + * specified buffer. It is initialized with a reference count + * of 1. + * + * @param data the source buffer + * @param size the size of the source buffer + */ + static Page *Copy(const void *data, size_t size); + + /** + * Concatenates two pages to a new page. + * + * @param a the first page + * @param b the second page, which is appended + */ + static Page *Concat(const Page &a, const Page &b); + + /** + * Increases the reference counter. + */ + void Ref() { + ref.Increment(); + } + + /** + * Decreases the reference counter. If it reaches zero, the #page is + * freed. + * + * @return true if the #page has been freed + */ + bool Unref(); +}; + +#endif diff --git a/src/Partition.hxx b/src/Partition.hxx new file mode 100644 index 000000000..776f74e2a --- /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/PcmChannels.cxx b/src/PcmChannels.cxx new file mode 100644 index 000000000..eca6b2506 --- /dev/null +++ b/src/PcmChannels.cxx @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PcmChannels.hxx" +#include "pcm_buffer.h" +#include "PcmUtils.hxx" + +#include <assert.h> + +template<typename D, typename S> +static void +MonoToStereo(D dest, S src, S end) +{ + while (src != end) { + const auto value = *src++; + + *dest++ = value; + *dest++ = value; + } + +} + +static void +pcm_convert_channels_16_2_to_1(int16_t *restrict dest, + const int16_t *restrict src, + const int16_t *restrict src_end) +{ + while (src < src_end) { + int32_t a = *src++, b = *src++; + + *dest++ = (a + b) / 2; + } +} + +static void +pcm_convert_channels_16_n_to_2(int16_t *restrict dest, + unsigned src_channels, + const int16_t *restrict src, + const int16_t *restrict src_end) +{ + unsigned c; + + assert(src_channels > 0); + + while (src < src_end) { + int32_t sum = 0; + int16_t value; + + for (c = 0; c < src_channels; ++c) + sum += *src++; + value = sum / (int)src_channels; + + /* XXX this is actually only mono ... */ + *dest++ = value; + *dest++ = value; + } +} + +const int16_t * +pcm_convert_channels_16(struct pcm_buffer *buffer, + unsigned dest_channels, + unsigned src_channels, const int16_t *src, + size_t src_size, size_t *dest_size_r) +{ + assert(src_size % (sizeof(*src) * src_channels) == 0); + + size_t dest_size = src_size / src_channels * dest_channels; + *dest_size_r = dest_size; + + int16_t *dest = (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) + MonoToStereo(dest, src, src_end); + else if (src_channels == 2 && dest_channels == 1) + pcm_convert_channels_16_2_to_1(dest, src, src_end); + else if (dest_channels == 2) + pcm_convert_channels_16_n_to_2(dest, src_channels, src, + src_end); + else + return NULL; + + return dest; +} + +static void +pcm_convert_channels_24_2_to_1(int32_t *restrict dest, + const int32_t *restrict src, + const int32_t *restrict src_end) +{ + while (src < src_end) { + int32_t a = *src++, b = *src++; + + *dest++ = (a + b) / 2; + } +} + +static void +pcm_convert_channels_24_n_to_2(int32_t *restrict dest, + unsigned src_channels, + const int32_t *restrict src, + const int32_t *restrict src_end) +{ + unsigned c; + + assert(src_channels > 0); + + while (src < src_end) { + int32_t sum = 0; + int32_t value; + + for (c = 0; c < src_channels; ++c) + sum += *src++; + value = sum / (int)src_channels; + + /* XXX this is actually only mono ... */ + *dest++ = value; + *dest++ = value; + } +} + +const int32_t * +pcm_convert_channels_24(struct pcm_buffer *buffer, + unsigned dest_channels, + unsigned src_channels, const int32_t *src, + size_t src_size, size_t *dest_size_r) +{ + assert(src_size % (sizeof(*src) * src_channels) == 0); + + size_t dest_size = src_size / src_channels * dest_channels; + *dest_size_r = dest_size; + + int32_t *dest = (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) + MonoToStereo(dest, src, src_end); + else if (src_channels == 2 && dest_channels == 1) + pcm_convert_channels_24_2_to_1(dest, src, src_end); + else if (dest_channels == 2) + pcm_convert_channels_24_n_to_2(dest, src_channels, src, + src_end); + else + return NULL; + + return dest; +} + +static void +pcm_convert_channels_32_2_to_1(int32_t *restrict dest, + const int32_t *restrict src, + const int32_t *restrict src_end) +{ + while (src < src_end) { + int64_t a = *src++, b = *src++; + + *dest++ = (a + b) / 2; + } +} + +static void +pcm_convert_channels_32_n_to_2(int32_t *dest, + unsigned src_channels, const int32_t *src, + const int32_t *src_end) +{ + unsigned c; + + assert(src_channels > 0); + + while (src < src_end) { + int64_t sum = 0; + int32_t value; + + for (c = 0; c < src_channels; ++c) + sum += *src++; + value = sum / (int64_t)src_channels; + + /* XXX this is actually only mono ... */ + *dest++ = value; + *dest++ = value; + } +} + +const int32_t * +pcm_convert_channels_32(struct pcm_buffer *buffer, + unsigned dest_channels, + unsigned src_channels, const int32_t *src, + size_t src_size, size_t *dest_size_r) +{ + assert(src_size % (sizeof(*src) * src_channels) == 0); + + size_t dest_size = src_size / src_channels * dest_channels; + *dest_size_r = dest_size; + + int32_t *dest = (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) + MonoToStereo(dest, src, src_end); + else if (src_channels == 2 && dest_channels == 1) + pcm_convert_channels_32_2_to_1(dest, src, src_end); + else if (dest_channels == 2) + pcm_convert_channels_32_n_to_2(dest, src_channels, src, + src_end); + else + return NULL; + + return dest; +} + +static void +pcm_convert_channels_float_2_to_1(float *restrict dest, + const float *restrict src, + const float *restrict src_end) +{ + while (src < src_end) { + double a = *src++, b = *src++; + + *dest++ = (a + b) / 2; + } +} + +static void +pcm_convert_channels_float_n_to_2(float *dest, + unsigned src_channels, const float *src, + const float *src_end) +{ + unsigned c; + + assert(src_channels > 0); + + while (src < src_end) { + double sum = 0; + float value; + + for (c = 0; c < src_channels; ++c) + sum += *src++; + value = sum / (double)src_channels; + + /* XXX this is actually only mono ... */ + *dest++ = value; + *dest++ = value; + } +} + +const float * +pcm_convert_channels_float(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/PcmChannels.hxx b/src/PcmChannels.hxx new file mode 100644 index 000000000..ede49cd81 --- /dev/null +++ b/src/PcmChannels.hxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_CHANNELS_HXX +#define MPD_PCM_CHANNELS_HXX + +#include <stdint.h> +#include <stddef.h> + +struct pcm_buffer; + +/** + * Changes the number of channels in 16 bit PCM data. + * + * @param buffer the destination pcm_buffer object + * @param dest_channels the number of channels requested + * @param src_channels the number of channels in the source buffer + * @param src the source PCM buffer + * @param src_size the number of bytes in #src + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const int16_t * +pcm_convert_channels_16(struct pcm_buffer *buffer, + unsigned dest_channels, + unsigned src_channels, const int16_t *src, + size_t src_size, size_t *dest_size_r); + +/** + * Changes the number of channels in 24 bit PCM data (aligned at 32 + * bit boundaries). + * + * @param buffer the destination pcm_buffer object + * @param dest_channels the number of channels requested + * @param src_channels the number of channels in the source buffer + * @param src the source PCM buffer + * @param src_size the number of bytes in #src + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const int32_t * +pcm_convert_channels_24(struct pcm_buffer *buffer, + unsigned dest_channels, + unsigned src_channels, const int32_t *src, + size_t src_size, size_t *dest_size_r); + +/** + * Changes the number of channels in 32 bit PCM data. + * + * @param buffer the destination pcm_buffer object + * @param dest_channels the number of channels requested + * @param src_channels the number of channels in the source buffer + * @param src the source PCM buffer + * @param src_size the number of bytes in #src + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const int32_t * +pcm_convert_channels_32(struct pcm_buffer *buffer, + unsigned dest_channels, + unsigned src_channels, const int32_t *src, + size_t src_size, size_t *dest_size_r); + +/** + * 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 000000000..9618b9642 --- /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 000000000..f08188a9c --- /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 000000000..98d0d443e --- /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/PcmDither.hxx b/src/PcmDither.hxx new file mode 100644 index 000000000..106382307 --- /dev/null +++ b/src/PcmDither.hxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_DITHER_HXX +#define MPD_PCM_DITHER_HXX + +#include <stdint.h> + +class PcmDither { + int32_t error[3]; + int32_t random; + +public: + constexpr PcmDither() + :error{0, 0, 0}, random(0) {} + + void Dither24To16(int16_t *dest, const int32_t *src, + const int32_t *src_end); + + void Dither32To16(int16_t *dest, const int32_t *src, + const int32_t *src_end); + +private: + int16_t Dither24To16(int_fast32_t sample); + int16_t Dither32To16(int_fast32_t sample); +}; + +#endif diff --git a/src/PcmFormat.cxx b/src/PcmFormat.cxx new file mode 100644 index 000000000..1385d161b --- /dev/null +++ b/src/PcmFormat.cxx @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PcmFormat.hxx" +#include "PcmDither.hxx" +#include "pcm_buffer.h" +#include "pcm_pack.h" +#include "PcmUtils.hxx" + +#include <type_traits> + +template<typename S> +struct DefaultSampleBits { + typedef decltype(*S()) T; + typedef typename std::remove_reference<T>::type U; + + static constexpr auto value = sizeof(U) * 8; +}; + +static void +pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end) +{ + while (in < in_end) { + *out++ = *in++ << 8; + } +} + +static void +pcm_convert_24_to_16(PcmDither &dither, + int16_t *out, const int32_t *in, const int32_t *in_end) +{ + dither.Dither24To16(out, in, in_end); +} + +static void +pcm_convert_32_to_16(PcmDither &dither, + int16_t *out, const int32_t *in, const int32_t *in_end) +{ + dither.Dither32To16(out, in, in_end); +} + +template<typename S, unsigned bits=DefaultSampleBits<S>::value> +static void +ConvertFromFloat(S dest, const float *src, const float *end) +{ + typedef decltype(*S()) T; + typedef typename std::remove_reference<T>::type U; + + const float factor = 1 << (bits - 1); + + while (src != end) { + int sample(*src++ * factor); + *dest++ = PcmClamp<U, int, bits>(sample); + } +} + +template<typename S, unsigned bits=DefaultSampleBits<S>::value> +static void +ConvertFromFloat(S dest, const float *src, size_t size) +{ + ConvertFromFloat<S, bits>(dest, src, pcm_end_pointer(src, size)); +} + +template<typename S, unsigned bits=sizeof(S)*8> +static S * +AllocateFromFloat(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 = (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, PcmDither &dither, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + int16_t *dest; + *dest_size_r = src_size / 2; + assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); + dest = (int16_t *)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, PcmDither &dither, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + int16_t *dest; + *dest_size_r = src_size / 2; + assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); + dest = (int16_t *)pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_32_to_16(dither, dest, src, + pcm_end_pointer(src, src_size)); + return dest; +} + +static int16_t * +pcm_allocate_float_to_16(struct pcm_buffer *buffer, + const float *src, size_t src_size, + size_t *dest_size_r) +{ + return AllocateFromFloat<int16_t>(*buffer, src, src_size, dest_size_r); +} + +const int16_t * +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) +{ + assert(src_size % sample_format_size(src_format) == 0); + + switch (src_format) { + case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: + break; + + case SAMPLE_FORMAT_S8: + return pcm_allocate_8_to_16(buffer, + (const int8_t *)src, src_size, + dest_size_r); + + case SAMPLE_FORMAT_S16: + *dest_size_r = src_size; + return (const int16_t *)src; + + case SAMPLE_FORMAT_S24_P32: + 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, + (const int32_t *)src, src_size, + dest_size_r); + + case SAMPLE_FORMAT_FLOAT: + return pcm_allocate_float_to_16(buffer, + (const float *)src, src_size, + dest_size_r); + } + + return NULL; +} + +static void +pcm_convert_8_to_24(int32_t *out, const int8_t *in, const int8_t *in_end) +{ + while (in < in_end) + *out++ = *in++ << 16; +} + +static void +pcm_convert_16_to_24(int32_t *out, const int16_t *in, const int16_t *in_end) +{ + while (in < in_end) + *out++ = *in++ << 8; +} + +static void +pcm_convert_32_to_24(int32_t *restrict out, + const int32_t *restrict in, + const int32_t *restrict in_end) +{ + while (in < in_end) + *out++ = *in++ >> 8; +} + +static int32_t * +pcm_allocate_8_to_24(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 = (int32_t *)pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_8_to_24(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_16_to_24(struct pcm_buffer *buffer, + const int16_t *src, size_t src_size, size_t *dest_size_r) +{ + int32_t *dest; + *dest_size_r = src_size * 2; + assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); + dest = (int32_t *)pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_16_to_24(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_32_to_24(struct pcm_buffer *buffer, + const int32_t *src, size_t src_size, size_t *dest_size_r) +{ + *dest_size_r = src_size; + int32_t *dest = (int32_t *)pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_32_to_24(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_float_to_24(struct pcm_buffer *buffer, + const float *src, size_t src_size, + size_t *dest_size_r) +{ + return AllocateFromFloat<int32_t, 24>(*buffer, src, src_size, + dest_size_r); +} + +const int32_t * +pcm_convert_to_24(struct pcm_buffer *buffer, + enum sample_format src_format, const void *src, + size_t src_size, size_t *dest_size_r) +{ + assert(src_size % sample_format_size(src_format) == 0); + + switch (src_format) { + case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: + break; + + case SAMPLE_FORMAT_S8: + return pcm_allocate_8_to_24(buffer, + (const int8_t *)src, src_size, + dest_size_r); + + case SAMPLE_FORMAT_S16: + return pcm_allocate_16_to_24(buffer, + (const int16_t *)src, src_size, + dest_size_r); + + case SAMPLE_FORMAT_S24_P32: + *dest_size_r = src_size; + return (const int32_t *)src; + + case SAMPLE_FORMAT_S32: + 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, + (const float *)src, src_size, + dest_size_r); + } + + return NULL; +} + +static void +pcm_convert_8_to_32(int32_t *out, const int8_t *in, const int8_t *in_end) +{ + while (in < in_end) + *out++ = *in++ << 24; +} + +static void +pcm_convert_16_to_32(int32_t *out, const int16_t *in, const int16_t *in_end) +{ + while (in < in_end) + *out++ = *in++ << 16; +} + +static void +pcm_convert_24_to_32(int32_t *restrict out, + const int32_t *restrict in, + const int32_t *restrict in_end) +{ + while (in < in_end) + *out++ = *in++ << 8; +} + +static int32_t * +pcm_allocate_8_to_32(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 = (int32_t *)pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_8_to_32(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_16_to_32(struct pcm_buffer *buffer, + const int16_t *src, size_t src_size, size_t *dest_size_r) +{ + int32_t *dest; + *dest_size_r = src_size * 2; + assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); + dest = (int32_t *)pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_16_to_32(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_24p32_to_32(struct pcm_buffer *buffer, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + *dest_size_r = src_size; + int32_t *dest = (int32_t *)pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_24_to_32(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_float_to_32(struct pcm_buffer *buffer, + const float *src, size_t src_size, + size_t *dest_size_r) +{ + /* convert to S24_P32 first */ + int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size, + dest_size_r); + + /* convert to 32 bit in-place */ + pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r)); + return dest; +} + +const int32_t * +pcm_convert_to_32(struct pcm_buffer *buffer, + enum sample_format src_format, const void *src, + size_t src_size, size_t *dest_size_r) +{ + assert(src_size % sample_format_size(src_format) == 0); + + switch (src_format) { + case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: + break; + + case SAMPLE_FORMAT_S8: + return pcm_allocate_8_to_32(buffer, + (const int8_t *)src, src_size, + dest_size_r); + + case SAMPLE_FORMAT_S16: + 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, + (const int32_t *)src, src_size, + dest_size_r); + + case SAMPLE_FORMAT_S32: + *dest_size_r = src_size; + return (const int32_t *)src; + + case SAMPLE_FORMAT_FLOAT: + return pcm_allocate_float_to_32(buffer, + (const float *)src, src_size, + dest_size_r); + } + + return NULL; +} + +template<typename S, unsigned bits=DefaultSampleBits<S>::value> +static void +ConvertToFloat(float *dest, S src, S end) +{ + constexpr float factor = 0.5 / (1 << (bits - 2)); + while (src != end) + *dest++ = float(*src++) * factor; + +} + +template<typename S, unsigned bits=DefaultSampleBits<S>::value> +static void +ConvertToFloat(float *dest, S src, size_t size) +{ + ConvertToFloat<S, bits>(dest, src, pcm_end_pointer(src, size)); +} + +template<typename S, unsigned bits=DefaultSampleBits<S>::value> +static float * +AllocateToFloat(pcm_buffer &buffer, S src, size_t src_size, + size_t *dest_size_r) +{ + constexpr size_t src_sample_size = sizeof(*S()); + assert(src_size % src_sample_size == 0); + + const size_t num_samples = src_size / src_sample_size; + *dest_size_r = num_samples * sizeof(float); + float *dest = (float *)pcm_buffer_get(&buffer, *dest_size_r); + ConvertToFloat<S, bits>(dest, src, src_size); + return dest; +} + +static float * +pcm_allocate_8_to_float(struct pcm_buffer *buffer, + const int8_t *src, size_t src_size, + size_t *dest_size_r) +{ + return AllocateToFloat(*buffer, src, src_size, dest_size_r); +} + +static float * +pcm_allocate_16_to_float(struct pcm_buffer *buffer, + const int16_t *src, size_t src_size, + size_t *dest_size_r) +{ + return AllocateToFloat(*buffer, src, src_size, dest_size_r); +} + +static float * +pcm_allocate_24p32_to_float(struct pcm_buffer *buffer, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + return AllocateToFloat<decltype(src), 24> + (*buffer, src, src_size, dest_size_r); +} + +static float * +pcm_allocate_32_to_float(struct pcm_buffer *buffer, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + return AllocateToFloat(*buffer, src, src_size, dest_size_r); +} + +const float * +pcm_convert_to_float(struct pcm_buffer *buffer, + enum sample_format src_format, const void *src, + size_t src_size, size_t *dest_size_r) +{ + switch (src_format) { + case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: + break; + + case SAMPLE_FORMAT_S8: + return pcm_allocate_8_to_float(buffer, + (const int8_t *)src, src_size, + dest_size_r); + + case SAMPLE_FORMAT_S16: + return pcm_allocate_16_to_float(buffer, + (const int16_t *)src, src_size, + dest_size_r); + + case SAMPLE_FORMAT_S24_P32: + return pcm_allocate_24p32_to_float(buffer, + (const int32_t *)src, src_size, + dest_size_r); + + case SAMPLE_FORMAT_S32: + return pcm_allocate_32_to_float(buffer, + (const int32_t *)src, src_size, + dest_size_r); + + case SAMPLE_FORMAT_FLOAT: + *dest_size_r = src_size; + return (const float *)src; + } + + return NULL; +} diff --git a/src/PcmFormat.hxx b/src/PcmFormat.hxx new file mode 100644 index 000000000..a5970b2d2 --- /dev/null +++ b/src/PcmFormat.hxx @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_FORMAT_HXX +#define MPD_PCM_FORMAT_HXX + +#include "audio_format.h" + +#include <stdint.h> +#include <stddef.h> + +struct pcm_buffer; +class PcmDither; + +/** + * Converts PCM samples to 16 bit. If the source format is 24 bit, + * then dithering is applied. + * + * @param buffer a pcm_buffer object + * @param dither a pcm_dither object for 24-to-16 conversion + * @param bits the number of in the source buffer + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const int16_t * +pcm_convert_to_16(struct pcm_buffer *buffer, PcmDither &dither, + enum sample_format src_format, const void *src, + size_t src_size, size_t *dest_size_r); + +/** + * Converts PCM samples to 24 bit (32 bit alignment). + * + * @param buffer a pcm_buffer object + * @param bits the number of in the source buffer + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const int32_t * +pcm_convert_to_24(struct pcm_buffer *buffer, + enum sample_format src_format, const void *src, + size_t src_size, size_t *dest_size_r); + +/** + * Converts PCM samples to 32 bit. + * + * @param buffer a pcm_buffer object + * @param bits the number of in the source buffer + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const int32_t * +pcm_convert_to_32(struct pcm_buffer *buffer, + enum sample_format src_format, const void *src, + size_t src_size, size_t *dest_size_r); + +/** + * Converts PCM samples to 32 bit floating point. + * + * @param buffer a pcm_buffer object + * @param bits the number of in the source buffer + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const float * +pcm_convert_to_float(struct pcm_buffer *buffer, + enum sample_format src_format, const void *src, + size_t src_size, size_t *dest_size_r); + +#endif diff --git a/src/PcmMix.cxx b/src/PcmMix.cxx new file mode 100644 index 000000000..8435c0c2a --- /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/PcmMix.hxx b/src/PcmMix.hxx new file mode 100644 index 000000000..bb7110d04 --- /dev/null +++ b/src/PcmMix.hxx @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_MIX_HXX +#define MPD_PCM_MIX_HXX + +#include "audio_format.h" +#include "gcc.h" + +#include <stddef.h> + +/* + * Linearly mixes two PCM buffers. Both must have the same length and + * the same audio format. The formula is: + * + * s1 := s1 * portion1 + s2 * (1 - portion1) + * + * @param buffer1 the first PCM buffer, and the destination buffer + * @param buffer2 the second PCM buffer + * @param size the size of both buffers in bytes + * @param format the sample format of both buffers + * @param portion1 a number between 0.0 and 1.0 specifying the portion + * of the first buffer in the mix; portion2 = (1.0 - portion1). The value + * NaN is used by the MixRamp code to specify that simple addition is required. + * + * @return true on success, false if the format is not supported + */ +gcc_warn_unused_result +bool +pcm_mix(void *buffer1, const void *buffer2, size_t size, + enum sample_format format, float portion1); + +#endif diff --git a/src/PcmPrng.hxx b/src/PcmPrng.hxx new file mode 100644 index 000000000..0c823250d --- /dev/null +++ b/src/PcmPrng.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_PRNG_HXX +#define MPD_PCM_PRNG_HXX + +/** + * A very simple linear congruential PRNG. It's good enough for PCM + * dithering. + */ +static unsigned long +pcm_prng(unsigned long state) +{ + return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; +} + +#endif diff --git a/src/PcmUtils.hxx b/src/PcmUtils.hxx new file mode 100644 index 000000000..d77c4194a --- /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/PcmVolume.cxx b/src/PcmVolume.cxx new file mode 100644 index 000000000..556ab9925 --- /dev/null +++ b/src/PcmVolume.cxx @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PcmVolume.hxx" +#include "PcmUtils.hxx" +#include "audio_format.h" + +#include <glib.h> + +#include <stdint.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "pcm_volume" + +static void +pcm_volume_change_8(int8_t *buffer, const int8_t *end, int volume) +{ + while (buffer < end) { + int32_t sample = *buffer; + + sample = (sample * volume + pcm_volume_dither() + + PCM_VOLUME_1 / 2) + / PCM_VOLUME_1; + + *buffer++ = PcmClamp<int8_t, int16_t, 8>(sample); + } +} + +static void +pcm_volume_change_16(int16_t *buffer, const int16_t *end, int volume) +{ + while (buffer < end) { + int32_t sample = *buffer; + + sample = (sample * volume + pcm_volume_dither() + + PCM_VOLUME_1 / 2) + / PCM_VOLUME_1; + + *buffer++ = PcmClamp<int16_t, int32_t, 16>(sample); + } +} + +#ifdef __i386__ +/** + * Optimized volume function for i386. Use the EDX:EAX 2*32 bit + * multiplication result instead of emulating 64 bit multiplication. + */ +static inline int32_t +pcm_volume_sample_24(int32_t sample, int32_t volume, G_GNUC_UNUSED int32_t dither) +{ + int32_t result; + + asm(/* edx:eax = sample * volume */ + "imul %2\n" + + /* "add %3, %1\n" dithering disabled for now, because we + have no overflow check - is dithering really important + here? */ + + /* eax = edx:eax / PCM_VOLUME_1 */ + "sal $22, %%edx\n" + "shr $10, %1\n" + "or %%edx, %1\n" + + : "=a"(result) + : "0"(sample), "r"(volume) /* , "r"(dither) */ + : "edx" + ); + + return result; +} +#endif + +static void +pcm_volume_change_24(int32_t *buffer, const int32_t *end, int volume) +{ + while (buffer < end) { +#ifdef __i386__ + /* assembly version for i386 */ + int32_t sample = *buffer; + + sample = pcm_volume_sample_24(sample, volume, + pcm_volume_dither()); +#else + /* portable version */ + int64_t sample = *buffer; + + sample = (sample * volume + pcm_volume_dither() + + PCM_VOLUME_1 / 2) + / PCM_VOLUME_1; +#endif + *buffer++ = PcmClamp<int32_t, int32_t, 24>(sample); + } +} + +static void +pcm_volume_change_32(int32_t *buffer, const int32_t *end, int volume) +{ + while (buffer < end) { +#ifdef __i386__ + /* assembly version for i386 */ + int32_t sample = *buffer; + + *buffer++ = pcm_volume_sample_24(sample, volume, 0); +#else + /* portable version */ + int64_t sample = *buffer; + + sample = (sample * volume + pcm_volume_dither() + + PCM_VOLUME_1 / 2) + / PCM_VOLUME_1; + *buffer++ = PcmClamp<int32_t, int64_t, 32>(sample); +#endif + } +} + +static void +pcm_volume_change_float(float *buffer, const float *end, float volume) +{ + while (buffer < end) { + float sample = *buffer; + sample *= volume; + *buffer++ = sample; + } +} + +bool +pcm_volume(void *buffer, size_t length, + enum sample_format format, + int volume) +{ + if (volume == PCM_VOLUME_1) + return true; + + if (volume <= 0) { + memset(buffer, 0, length); + return true; + } + + const void *end = pcm_end_pointer(buffer, length); + switch (format) { + case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: + /* not implemented */ + return false; + + case SAMPLE_FORMAT_S8: + pcm_volume_change_8((int8_t *)buffer, (const int8_t *)end, + volume); + return true; + + case SAMPLE_FORMAT_S16: + pcm_volume_change_16((int16_t *)buffer, (const int16_t *)end, + volume); + return true; + + case SAMPLE_FORMAT_S24_P32: + pcm_volume_change_24((int32_t *)buffer, (const int32_t *)end, + volume); + return true; + + case SAMPLE_FORMAT_S32: + pcm_volume_change_32((int32_t *)buffer, (const int32_t *)end, + volume); + return true; + + case SAMPLE_FORMAT_FLOAT: + pcm_volume_change_float((float *)buffer, (const float *)end, + pcm_volume_to_float(volume)); + return true; + } + + /* unreachable */ + assert(false); + return false; +} diff --git a/src/PcmVolume.hxx b/src/PcmVolume.hxx new file mode 100644 index 000000000..d3e6a5536 --- /dev/null +++ b/src/PcmVolume.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_PCM_VOLUME_HXX +#define MPD_PCM_VOLUME_HXX + +#include "PcmPrng.hxx" +#include "audio_format.h" + +#include <stdint.h> +#include <stdbool.h> +#include <stddef.h> + +enum { + /** this value means "100% volume" */ + PCM_VOLUME_1 = 1024, +}; + +struct audio_format; + +/** + * Converts a float value (0.0 = silence, 1.0 = 100% volume) to an + * integer volume value (1000 = 100%). + */ +static inline int +pcm_float_to_volume(float volume) +{ + return volume * PCM_VOLUME_1 + 0.5; +} + +static inline float +pcm_volume_to_float(int volume) +{ + return (float)volume / (float)PCM_VOLUME_1; +} + +/** + * Returns the next volume dithering number, between -511 and +511. + * This number is taken from a global PRNG, see pcm_prng(). + */ +static inline int +pcm_volume_dither(void) +{ + static unsigned long state; + uint32_t r; + + r = state = pcm_prng(state); + + return (r & 511) - ((r >> 9) & 511); +} + +/** + * Adjust the volume of the specified PCM buffer. + * + * @param buffer the PCM buffer + * @param length the length of the PCM buffer + * @param format the sample format of the PCM buffer + * @param volume the volume between 0 and #PCM_VOLUME_1 + * @return true on success, false if the audio format is not supported + */ +bool +pcm_volume(void *buffer, size_t length, + enum sample_format format, + int volume); + +#endif diff --git a/src/Permission.cxx b/src/Permission.cxx new file mode 100644 index 000000000..e6cf5cfb8 --- /dev/null +++ b/src/Permission.cxx @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Permission.hxx" +#include "mpd_error.h" +#include "conf.h" + +#include <map> +#include <string> + +#include <glib.h> + +#include <stdbool.h> +#include <string.h> + +#define PERMISSION_PASSWORD_CHAR '@' +#define PERMISSION_SEPERATOR "," + +#define PERMISSION_READ_STRING "read" +#define PERMISSION_ADD_STRING "add" +#define PERMISSION_CONTROL_STRING "control" +#define PERMISSION_ADMIN_STRING "admin" + +static std::map<std::string, unsigned> permission_passwords; + +static unsigned permission_default; + +static unsigned parsePermissions(const char *string) +{ + unsigned permission = 0; + gchar **tokens; + + if (!string) + return 0; + + tokens = g_strsplit(string, PERMISSION_SEPERATOR, 0); + for (unsigned i = 0; tokens[i] != NULL; ++i) { + char *temp = tokens[i]; + + if (strcmp(temp, PERMISSION_READ_STRING) == 0) { + permission |= PERMISSION_READ; + } else if (strcmp(temp, PERMISSION_ADD_STRING) == 0) { + permission |= PERMISSION_ADD; + } else if (strcmp(temp, PERMISSION_CONTROL_STRING) == 0) { + permission |= PERMISSION_CONTROL; + } else if (strcmp(temp, PERMISSION_ADMIN_STRING) == 0) { + permission |= PERMISSION_ADMIN; + } else { + MPD_ERROR("unknown permission \"%s\"", temp); + } + } + + g_strfreev(tokens); + + return permission; +} + +void initPermissions(void) +{ + char *password; + unsigned permission; + const struct config_param *param; + + permission_default = PERMISSION_READ | PERMISSION_ADD | + PERMISSION_CONTROL | PERMISSION_ADMIN; + + param = config_get_next_param(CONF_PASSWORD, NULL); + + if (param) { + permission_default = 0; + + do { + const char *separator = + strchr(param->value, PERMISSION_PASSWORD_CHAR); + + if (separator == NULL) + MPD_ERROR("\"%c\" not found in password string " + "\"%s\", line %i", + PERMISSION_PASSWORD_CHAR, + param->value, param->line); + + password = g_strndup(param->value, + separator - param->value); + + permission = parsePermissions(separator + 1); + + permission_passwords.insert(std::make_pair(password, + permission)); + } while ((param = config_get_next_param(CONF_PASSWORD, param))); + } + + param = config_get_param(CONF_DEFAULT_PERMS); + + if (param) + permission_default = parsePermissions(param->value); +} + +int getPermissionFromPassword(char const* password, unsigned* permission) +{ + auto i = permission_passwords.find(password); + if (i == permission_passwords.end()) + return -1; + + *permission = i->second; + return 0; +} + +unsigned getDefaultPermissions(void) +{ + return permission_default; +} diff --git a/src/Permission.hxx b/src/Permission.hxx new file mode 100644 index 000000000..4ff3850e0 --- /dev/null +++ b/src/Permission.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PERMISSION_HXX +#define MPD_PERMISSION_HXX + +#define PERMISSION_NONE 0 +#define PERMISSION_READ 1 +#define PERMISSION_ADD 2 +#define PERMISSION_CONTROL 4 +#define PERMISSION_ADMIN 8 + + +int getPermissionFromPassword(char const* password, unsigned* permission); + +unsigned getDefaultPermissions(void); + +void initPermissions(void); + +#endif diff --git a/src/PlayerCommands.cxx b/src/PlayerCommands.cxx new file mode 100644 index 000000000..32cd16d9d --- /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 000000000..a2fed5853 --- /dev/null +++ b/src/PlayerCommands.hxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYER_COMMANDS_HXX +#define MPD_PLAYER_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_play(Client *client, int argc, char *argv[]); + +enum command_return +handle_playid(Client *client, int argc, char *argv[]); + +enum command_return +handle_stop(Client *client, int argc, char *argv[]); + +enum command_return +handle_currentsong(Client *client, int argc, char *argv[]); + +enum command_return +handle_pause(Client *client, int argc, char *argv[]); + +enum command_return +handle_status(Client *client, int argc, char *argv[]); + +enum command_return +handle_next(Client *client, int argc, char *argv[]); + +enum command_return +handle_previous(Client *client, int argc, char *avg[]); + +enum command_return +handle_repeat(Client *client, int argc, char *argv[]); + +enum command_return +handle_single(Client *client, int argc, char *argv[]); + +enum command_return +handle_consume(Client *client, int argc, char *argv[]); + +enum command_return +handle_random(Client *client, int argc, char *argv[]); + +enum command_return +handle_clearerror(Client *client, int argc, char *argv[]); + +enum command_return +handle_seek(Client *client, int argc, char *argv[]); + +enum command_return +handle_seekid(Client *client, int argc, char *argv[]); + +enum command_return +handle_seekcur(Client *client, int argc, char *argv[]); + +enum command_return +handle_crossfade(Client *client, int argc, char *argv[]); + +enum command_return +handle_mixrampdb(Client *client, int argc, char *argv[]); + +enum command_return +handle_mixrampdelay(Client *client, int argc, char *argv[]); + +enum command_return +handle_replay_gain_mode(Client *client, int argc, char *argv[]); + +enum command_return +handle_replay_gain_status(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/PlayerControl.cxx b/src/PlayerControl.cxx new file mode 100644 index 000000000..d3e8c7d08 --- /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 000000000..de05e17ab --- /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/PlayerThread.cxx b/src/PlayerThread.cxx new file mode 100644 index 000000000..599df833d --- /dev/null +++ b/src/PlayerThread.cxx @@ -0,0 +1,1205 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlayerThread.hxx" +#include "DecoderThread.hxx" +#include "DecoderControl.hxx" +#include "MusicPipe.hxx" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "song.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> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "player_thread" + +enum xfade_state { + XFADE_DISABLED = -1, + XFADE_UNKNOWN = 0, + XFADE_ENABLED = 1 +}; + +struct player { + struct player_control *pc; + + struct decoder_control *dc; + + struct music_pipe *pipe; + + /** + * are we waiting for buffered_before_play? + */ + bool buffering; + + /** + * true if the decoder is starting and did not provide data + * yet + */ + bool decoder_starting; + + /** + * is the player paused? + */ + bool paused; + + /** + * is there a new song in pc.next_song? + */ + bool queued; + + /** + * Was any audio output opened successfully? It might have + * failed meanwhile, but was not explicitly closed by the + * player thread. When this flag is unset, some output + * methods must not be called. + */ + bool output_open; + + /** + * the song currently being played + */ + struct song *song; + + /** + * is cross fading enabled? + */ + enum xfade_state xfade; + + /** + * has cross-fading begun? + */ + bool cross_fading; + + /** + * The number of chunks used for crossfading. + */ + unsigned cross_fade_chunks; + + /** + * The tag of the "next" song during cross-fade. It is + * postponed, and sent to the output thread when the new song + * really begins. + */ + struct tag *cross_fade_tag; + + /** + * The current audio format for the audio outputs. + */ + struct audio_format play_audio_format; + + /** + * The time stamp of the chunk most recently sent to the + * output thread. This attribute is only used if + * audio_output_all_get_elapsed_time() didn't return a usable + * value; the output thread can estimate the elapsed time more + * precisely. + */ + float elapsed_time; + + player(player_control *_pc, decoder_control *_dc) + :pc(_pc), dc(_dc), + buffering(false), + decoder_starting(false), + paused(false), + queued(true), + output_open(false), + song(NULL), + xfade(XFADE_UNKNOWN), + cross_fading(false), + cross_fade_chunks(0), + cross_fade_tag(NULL), + elapsed_time(0.0) {} +}; + +static struct music_buffer *player_buffer; + +static void +player_command_finished_locked(struct player_control *pc) +{ + assert(pc->command != PLAYER_COMMAND_NONE); + + pc->command = PLAYER_COMMAND_NONE; + pc->ClientSignal(); +} + +static void +player_command_finished(struct player_control *pc) +{ + pc->Lock(); + player_command_finished_locked(pc); + pc->Unlock(); +} + +/** + * Start the decoder. + * + * Player lock is not held. + */ +static void +player_dc_start(struct player *player, struct music_pipe *pipe) +{ + struct player_control *pc = player->pc; + struct decoder_control *dc = player->dc; + + assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); + assert(pc->next_song != NULL); + + unsigned start_ms = pc->next_song->start_ms; + if (pc->command == PLAYER_COMMAND_SEEK) + start_ms += (unsigned)(pc->seek_where * 1000); + + dc->Start(song_dup_detached(pc->next_song), + start_ms, pc->next_song->end_ms, + player_buffer, pipe); +} + +/** + * Is the decoder still busy on the same song as the player? + * + * Note: this function does not check if the decoder is already + * finished. + */ +static bool +player_dc_at_current_song(const struct player *player) +{ + assert(player != NULL); + assert(player->pipe != NULL); + + return player->dc->pipe == player->pipe; +} + +/** + * Returns true if the decoder is decoding the next song (or has begun + * decoding it, or has finished doing it), and the player hasn't + * switched to that song yet. + */ +static bool +player_dc_at_next_song(const struct player *player) +{ + return player->dc->pipe != NULL && !player_dc_at_current_song(player); +} + +/** + * Stop the decoder and clears (and frees) its music pipe. + * + * Player lock is not held. + */ +static void +player_dc_stop(struct player *player) +{ + struct decoder_control *dc = player->dc; + + dc->Stop(); + + if (dc->pipe != NULL) { + /* clear and free the decoder pipe */ + + music_pipe_clear(dc->pipe, player_buffer); + + if (dc->pipe != player->pipe) + music_pipe_free(dc->pipe); + + dc->pipe = NULL; + } +} + +/** + * After the decoder has been started asynchronously, wait for the + * "START" command to finish. The decoder may not be initialized yet, + * i.e. there is no audio_format information yet. + * + * The player lock is not held. + */ +static bool +player_wait_for_decoder(struct player *player) +{ + struct player_control *pc = player->pc; + struct decoder_control *dc = player->dc; + + assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); + assert(pc->next_song != NULL); + + player->queued = false; + + GError *error = dc->LockGetError(); + if (error != NULL) { + pc->Lock(); + pc->SetError(PLAYER_ERROR_DECODER, error); + + song_free(pc->next_song); + pc->next_song = NULL; + + pc->Unlock(); + + return false; + } + + if (player->song != NULL) + song_free(player->song); + + player->song = pc->next_song; + player->elapsed_time = 0.0; + + /* set the "starting" flag, which will be cleared by + player_check_decoder_startup() */ + player->decoder_starting = true; + + pc->Lock(); + + /* update player_control's song information */ + pc->total_time = song_get_duration(pc->next_song); + pc->bit_rate = 0; + audio_format_clear(&pc->audio_format); + + /* clear the queued song */ + pc->next_song = NULL; + + pc->Unlock(); + + /* call syncPlaylistWithQueue() in the main thread */ + GlobalEvents::Emit(GlobalEvents::PLAYLIST); + + return true; +} + +/** + * Returns the real duration of the song, comprising the duration + * indicated by the decoder plugin. + */ +static double +real_song_duration(const struct song *song, double decoder_duration) +{ + assert(song != NULL); + + if (decoder_duration <= 0.0) + /* the decoder plugin didn't provide information; fall + back to song_get_duration() */ + return song_get_duration(song); + + if (song->end_ms > 0 && song->end_ms / 1000.0 < decoder_duration) + return (song->end_ms - song->start_ms) / 1000.0; + + return decoder_duration - song->start_ms / 1000.0; +} + +/** + * Wrapper for audio_output_all_open(). Upon failure, it pauses the + * player. + * + * @return true on success + */ +static bool +player_open_output(struct player *player) +{ + struct player_control *pc = player->pc; + + assert(audio_format_defined(&player->play_audio_format)); + assert(pc->state == PLAYER_STATE_PLAY || + pc->state == PLAYER_STATE_PAUSE); + + GError *error = NULL; + if (audio_output_all_open(&player->play_audio_format, player_buffer, + &error)) { + player->output_open = true; + player->paused = false; + + pc->Lock(); + pc->state = PLAYER_STATE_PLAY; + pc->Unlock(); + + return true; + } else { + g_warning("%s", error->message); + + player->output_open = false; + + /* pause: the user may resume playback as soon as an + audio output becomes available */ + player->paused = true; + + pc->Lock(); + pc->SetError(PLAYER_ERROR_OUTPUT, error); + pc->state = PLAYER_STATE_PAUSE; + pc->Unlock(); + + idle_add(IDLE_PLAYER); + + return false; + } +} + +/** + * The decoder has acknowledged the "START" command (see + * player_wait_for_decoder()). This function checks if the decoder + * initialization has completed yet. + * + * The player lock is not held. + */ +static bool +player_check_decoder_startup(struct player *player) +{ + struct player_control *pc = player->pc; + struct decoder_control *dc = player->dc; + + assert(player->decoder_starting); + + dc->Lock(); + + GError *error = dc->GetError(); + if (error != NULL) { + /* the decoder failed */ + dc->Unlock(); + + pc->Lock(); + pc->SetError(PLAYER_ERROR_DECODER, error); + pc->Unlock(); + + return false; + } else if (!dc->IsStarting()) { + /* the decoder is ready and ok */ + + dc->Unlock(); + + if (player->output_open && + !audio_output_all_wait(pc, 1)) + /* the output devices havn't finished playing + all chunks yet - wait for that */ + return true; + + pc->Lock(); + pc->total_time = real_song_duration(dc->song, dc->total_time); + pc->audio_format = dc->in_audio_format; + pc->Unlock(); + + player->play_audio_format = dc->out_audio_format; + player->decoder_starting = false; + + if (!player->paused && !player_open_output(player)) { + char *uri = song_get_uri(dc->song); + g_warning("problems opening audio device " + "while playing \"%s\"", uri); + g_free(uri); + + return true; + } + + return true; + } else { + /* the decoder is not yet ready; wait + some more */ + dc->WaitForDecoder(); + dc->Unlock(); + + return true; + } +} + +/** + * Sends a chunk of silence to the audio outputs. This is called when + * there is not enough decoded data in the pipe yet, to prevent + * underruns in the hardware buffers. + * + * The player lock is not held. + */ +static bool +player_send_silence(struct player *player) +{ + assert(player->output_open); + assert(audio_format_defined(&player->play_audio_format)); + + struct music_chunk *chunk = music_buffer_allocate(player_buffer); + if (chunk == NULL) { + g_warning("Failed to allocate silence buffer"); + return false; + } + +#ifndef NDEBUG + chunk->audio_format = player->play_audio_format; +#endif + + size_t frame_size = + audio_format_frame_size(&player->play_audio_format); + /* this formula ensures that we don't send + partial frames */ + unsigned num_frames = sizeof(chunk->data) / frame_size; + + chunk->times = -1.0; /* undefined time stamp */ + chunk->length = num_frames * frame_size; + memset(chunk->data, 0, chunk->length); + + GError *error = NULL; + if (!audio_output_all_play(chunk, &error)) { + g_warning("%s", error->message); + g_error_free(error); + + music_buffer_return(player_buffer, chunk); + return false; + } + + return true; +} + +/** + * This is the handler for the #PLAYER_COMMAND_SEEK command. + * + * The player lock is not held. + */ +static bool player_seek_decoder(struct player *player) +{ + struct player_control *pc = player->pc; + struct song *song = pc->next_song; + struct decoder_control *dc = player->dc; + + assert(pc->next_song != NULL); + + const unsigned start_ms = song->start_ms; + + if (!dc->LockIsCurrentSong(song)) { + /* the decoder is already decoding the "next" song - + stop it and start the previous song again */ + + player_dc_stop(player); + + /* clear music chunks which might still reside in the + pipe */ + music_pipe_clear(player->pipe, player_buffer); + + /* re-start the decoder */ + player_dc_start(player, player->pipe); + if (!player_wait_for_decoder(player)) { + /* decoder failure */ + player_command_finished(pc); + return false; + } + } else { + if (!player_dc_at_current_song(player)) { + /* the decoder is already decoding the "next" song, + but it is the same song file; exchange the pipe */ + music_pipe_clear(player->pipe, player_buffer); + music_pipe_free(player->pipe); + player->pipe = dc->pipe; + } + + song_free(pc->next_song); + pc->next_song = NULL; + player->queued = false; + } + + /* wait for the decoder to complete initialization */ + + while (player->decoder_starting) { + if (!player_check_decoder_startup(player)) { + /* decoder failure */ + player_command_finished(pc); + return false; + } + } + + /* send the SEEK command */ + + double where = pc->seek_where; + if (where > pc->total_time) + where = pc->total_time - 0.1; + if (where < 0.0) + where = 0.0; + + if (!dc->Seek(where + start_ms / 1000.0)) { + /* decoder failure */ + player_command_finished(pc); + return false; + } + + player->elapsed_time = where; + + player_command_finished(pc); + + player->xfade = XFADE_UNKNOWN; + + /* re-fill the buffer after seeking */ + player->buffering = true; + + audio_output_all_cancel(); + + return true; +} + +/** + * Player lock must be held before calling. + */ +static void player_process_command(struct player *player) +{ + struct player_control *pc = player->pc; + G_GNUC_UNUSED struct decoder_control *dc = player->dc; + + switch (pc->command) { + case PLAYER_COMMAND_NONE: + case PLAYER_COMMAND_STOP: + case PLAYER_COMMAND_EXIT: + case PLAYER_COMMAND_CLOSE_AUDIO: + break; + + case PLAYER_COMMAND_UPDATE_AUDIO: + pc->Unlock(); + audio_output_all_enable_disable(); + pc->Lock(); + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_QUEUE: + assert(pc->next_song != NULL); + assert(!player->queued); + assert(!player_dc_at_next_song(player)); + + player->queued = true; + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_PAUSE: + pc->Unlock(); + + player->paused = !player->paused; + if (player->paused) { + audio_output_all_pause(); + pc->Lock(); + + pc->state = PLAYER_STATE_PAUSE; + } else if (!audio_format_defined(&player->play_audio_format)) { + /* the decoder hasn't provided an audio format + yet - don't open the audio device yet */ + pc->Lock(); + + pc->state = PLAYER_STATE_PLAY; + } else { + player_open_output(player); + + pc->Lock(); + } + + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_SEEK: + pc->Unlock(); + player_seek_decoder(player); + pc->Lock(); + break; + + case PLAYER_COMMAND_CANCEL: + if (pc->next_song == NULL) { + /* the cancel request arrived too late, we're + already playing the queued song... stop + everything now */ + pc->command = PLAYER_COMMAND_STOP; + return; + } + + if (player_dc_at_next_song(player)) { + /* the decoder is already decoding the song - + stop it and reset the position */ + pc->Unlock(); + player_dc_stop(player); + pc->Lock(); + } + + song_free(pc->next_song); + pc->next_song = NULL; + player->queued = false; + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_REFRESH: + if (player->output_open && !player->paused) { + pc->Unlock(); + audio_output_all_check(); + pc->Lock(); + } + + pc->elapsed_time = audio_output_all_get_elapsed_time(); + if (pc->elapsed_time < 0.0) + pc->elapsed_time = player->elapsed_time; + + player_command_finished_locked(pc); + break; + } +} + +static void +update_song_tag(struct song *song, const struct tag *new_tag) +{ + if (song_is_file(song)) + /* don't update tags of local files, only remote + streams may change tags dynamically */ + return; + + struct tag *old_tag = song->tag; + song->tag = tag_dup(new_tag); + + if (old_tag != NULL) + tag_free(old_tag); + + /* the main thread will update the playlist version when he + receives this event */ + GlobalEvents::Emit(GlobalEvents::TAG); + + /* notify all clients that the tag of the current song has + changed */ + idle_add(IDLE_PLAYER); +} + +/** + * Plays a #music_chunk object (after applying software volume). If + * it contains a (stream) tag, copy it to the current song, so MPD's + * playlist reflects the new stream tag. + * + * Player lock is not held. + */ +static bool +play_chunk(struct player_control *pc, + struct song *song, struct music_chunk *chunk, + const struct audio_format *format, + GError **error_r) +{ + assert(chunk->CheckFormat(*format)); + + if (chunk->tag != NULL) + update_song_tag(song, chunk->tag); + + if (chunk->length == 0) { + music_buffer_return(player_buffer, chunk); + return true; + } + + pc->Lock(); + pc->bit_rate = chunk->bit_rate; + pc->Unlock(); + + /* send the chunk to the audio outputs */ + + if (!audio_output_all_play(chunk, error_r)) + return false; + + pc->total_play_time += (double)chunk->length / + audio_format_time_to_size(format); + return true; +} + +/** + * Obtains the next chunk from the music pipe, optionally applies + * cross-fading, and sends it to all audio outputs. + * + * @return true on success, false on error (playback will be stopped) + */ +static bool +play_next_chunk(struct player *player) +{ + struct player_control *pc = player->pc; + struct decoder_control *dc = player->dc; + + if (!audio_output_all_wait(pc, 64)) + /* the output pipe is still large enough, don't send + another chunk */ + return true; + + unsigned cross_fade_position; + struct music_chunk *chunk = NULL; + if (player->xfade == XFADE_ENABLED && + player_dc_at_next_song(player) && + (cross_fade_position = music_pipe_size(player->pipe)) + <= player->cross_fade_chunks) { + /* perform cross fade */ + struct music_chunk *other_chunk = + music_pipe_shift(dc->pipe); + + if (!player->cross_fading) { + /* beginning of the cross fade - adjust + crossFadeChunks which might be bigger than + the remaining number of chunks in the old + song */ + player->cross_fade_chunks = cross_fade_position; + player->cross_fading = true; + } + + if (other_chunk != NULL) { + chunk = music_pipe_shift(player->pipe); + assert(chunk != NULL); + assert(chunk->other == NULL); + + /* don't send the tags of the new song (which + is being faded in) yet; postpone it until + the current song is faded out */ + player->cross_fade_tag = + tag_merge_replace(player->cross_fade_tag, + other_chunk->tag); + other_chunk->tag = NULL; + + if (std::isnan(pc->mixramp_delay_seconds)) { + chunk->mix_ratio = ((float)cross_fade_position) + / player->cross_fade_chunks; + } else { + chunk->mix_ratio = nan(""); + } + + if (other_chunk->IsEmpty()) { + /* the "other" chunk was a music_chunk + which had only a tag, but no music + data - we cannot cross-fade that; + but since this happens only at the + beginning of the new song, we can + easily recover by throwing it away + now */ + music_buffer_return(player_buffer, + other_chunk); + other_chunk = NULL; + } + + chunk->other = other_chunk; + } else { + /* there are not enough decoded chunks yet */ + + dc->Lock(); + + if (dc->IsIdle()) { + /* the decoder isn't running, abort + cross fading */ + dc->Unlock(); + + player->xfade = XFADE_DISABLED; + } else { + /* wait for the decoder */ + dc->Signal(); + dc->WaitForDecoder(); + dc->Unlock(); + + return true; + } + } + } + + if (chunk == NULL) + chunk = music_pipe_shift(player->pipe); + + assert(chunk != NULL); + + /* insert the postponed tag if cross-fading is finished */ + + if (player->xfade != XFADE_ENABLED && player->cross_fade_tag != NULL) { + chunk->tag = tag_merge_replace(chunk->tag, + player->cross_fade_tag); + player->cross_fade_tag = NULL; + } + + /* play the current chunk */ + + GError *error = NULL; + if (!play_chunk(player->pc, player->song, chunk, + &player->play_audio_format, &error)) { + g_warning("%s", error->message); + + music_buffer_return(player_buffer, chunk); + + pc->Lock(); + + pc->SetError(PLAYER_ERROR_OUTPUT, error); + + /* pause: the user may resume playback as soon as an + audio output becomes available */ + pc->state = PLAYER_STATE_PAUSE; + player->paused = true; + + pc->Unlock(); + + idle_add(IDLE_PLAYER); + + return false; + } + + /* this formula should prevent that the decoder gets woken up + with each chunk; it is more efficient to make it decode a + larger block at a time */ + dc->Lock(); + if (!dc->IsIdle() && + music_pipe_size(dc->pipe) <= (pc->buffered_before_play + + music_buffer_size(player_buffer) * 3) / 4) + dc->Signal(); + dc->Unlock(); + + return true; +} + +/** + * This is called at the border between two songs: the audio output + * has consumed all chunks of the current song, and we should start + * sending chunks from the next one. + * + * The player lock is not held. + * + * @return true on success, false on error (playback will be stopped) + */ +static bool +player_song_border(struct player *player) +{ + player->xfade = XFADE_UNKNOWN; + + char *uri = song_get_uri(player->song); + g_message("played \"%s\"", uri); + g_free(uri); + + music_pipe_free(player->pipe); + player->pipe = player->dc->pipe; + + audio_output_all_song_border(); + + if (!player_wait_for_decoder(player)) + return false; + + struct player_control *const pc = player->pc; + pc->Lock(); + + const bool border_pause = pc->border_pause; + if (border_pause) { + player->paused = true; + pc->state = PLAYER_STATE_PAUSE; + } + + pc->Unlock(); + + if (border_pause) + idle_add(IDLE_PLAYER); + + return true; +} + +/* + * The main loop of the player thread, during playback. This is + * basically a state machine, which multiplexes data between the + * decoder thread and the output threads. + */ +static void do_play(struct player_control *pc, struct decoder_control *dc) +{ + player player(pc, dc); + + pc->Unlock(); + + player.pipe = music_pipe_new(); + + player_dc_start(&player, player.pipe); + if (!player_wait_for_decoder(&player)) { + assert(player.song == NULL); + + player_dc_stop(&player); + player_command_finished(pc); + music_pipe_free(player.pipe); + GlobalEvents::Emit(GlobalEvents::PLAYLIST); + pc->Lock(); + return; + } + + pc->Lock(); + pc->state = PLAYER_STATE_PLAY; + + if (pc->command == PLAYER_COMMAND_SEEK) + player.elapsed_time = pc->seek_where; + + player_command_finished_locked(pc); + + while (true) { + player_process_command(&player); + if (pc->command == PLAYER_COMMAND_STOP || + pc->command == PLAYER_COMMAND_EXIT || + pc->command == PLAYER_COMMAND_CLOSE_AUDIO) { + pc->Unlock(); + audio_output_all_cancel(); + break; + } + + pc->Unlock(); + + if (player.buffering) { + /* buffering at the start of the song - wait + until the buffer is large enough, to + prevent stuttering on slow machines */ + + if (music_pipe_size(player.pipe) < pc->buffered_before_play && + !dc->LockIsIdle()) { + /* not enough decoded buffer space yet */ + + if (!player.paused && + player.output_open && + audio_output_all_check() < 4 && + !player_send_silence(&player)) + break; + + dc->Lock(); + /* XXX race condition: check decoder again */ + dc->WaitForDecoder(); + dc->Unlock(); + pc->Lock(); + continue; + } else { + /* buffering is complete */ + player.buffering = false; + } + } + + if (player.decoder_starting) { + /* wait until the decoder is initialized completely */ + + if (!player_check_decoder_startup(&player)) + break; + + pc->Lock(); + continue; + } + +#ifndef NDEBUG + /* + music_pipe_check_format(&play_audio_format, + player.next_song_chunk, + &dc->out_audio_format); + */ +#endif + + if (dc->LockIsIdle() && player.queued && + dc->pipe == player.pipe) { + /* the decoder has finished the current song; + make it decode the next song */ + + assert(dc->pipe == NULL || dc->pipe == player.pipe); + + player_dc_start(&player, music_pipe_new()); + } + + if (/* no cross-fading if MPD is going to pause at the + end of the current song */ + !pc->border_pause && + player_dc_at_next_song(&player) && + player.xfade == XFADE_UNKNOWN && + !dc->LockIsStarting()) { + /* enable cross fading in this song? if yes, + calculate how many chunks will be required + for it */ + player.cross_fade_chunks = + cross_fade_calc(pc->cross_fade_seconds, dc->total_time, + pc->mixramp_db, + pc->mixramp_delay_seconds, + dc->replay_gain_db, + dc->replay_gain_prev_db, + dc->mixramp_start, + dc->mixramp_prev_end, + &dc->out_audio_format, + &player.play_audio_format, + music_buffer_size(player_buffer) - + pc->buffered_before_play); + if (player.cross_fade_chunks > 0) { + player.xfade = XFADE_ENABLED; + player.cross_fading = false; + } else + /* cross fading is disabled or the + next song is too short */ + player.xfade = XFADE_DISABLED; + } + + if (player.paused) { + pc->Lock(); + + if (pc->command == PLAYER_COMMAND_NONE) + pc->Wait(); + continue; + } else if (!music_pipe_empty(player.pipe)) { + /* at least one music chunk is ready - send it + to the audio output */ + + play_next_chunk(&player); + } else if (audio_output_all_check() > 0) { + /* not enough data from decoder, but the + output thread is still busy, so it's + okay */ + + /* XXX synchronize in a better way */ + g_usleep(10000); + } else if (player_dc_at_next_song(&player)) { + /* at the beginning of a new song */ + + if (!player_song_border(&player)) + break; + } else if (dc->LockIsIdle()) { + /* check the size of the pipe again, because + the decoder thread may have added something + since we last checked */ + if (music_pipe_empty(player.pipe)) { + /* wait for the hardware to finish + playback */ + audio_output_all_drain(); + break; + } + } else if (player.output_open) { + /* the decoder is too busy and hasn't provided + new PCM data in time: send silence (if the + output pipe is empty) */ + if (!player_send_silence(&player)) + break; + } + + pc->Lock(); + } + + player_dc_stop(&player); + + music_pipe_clear(player.pipe, player_buffer); + music_pipe_free(player.pipe); + + if (player.cross_fade_tag != NULL) + tag_free(player.cross_fade_tag); + + 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; + + pc->Unlock(); + + GlobalEvents::Emit(GlobalEvents::PLAYLIST); + + pc->Lock(); +} + +static gpointer +player_task(gpointer arg) +{ + struct player_control *pc = (struct player_control *)arg; + + struct decoder_control *dc = new decoder_control(); + decoder_thread_start(dc); + + player_buffer = music_buffer_new(pc->buffer_chunks); + + pc->Lock(); + + while (1) { + switch (pc->command) { + case PLAYER_COMMAND_SEEK: + case PLAYER_COMMAND_QUEUE: + assert(pc->next_song != NULL); + + do_play(pc, dc); + break; + + case PLAYER_COMMAND_STOP: + pc->Unlock(); + audio_output_all_cancel(); + pc->Lock(); + + /* fall through */ + + case PLAYER_COMMAND_PAUSE: + if (pc->next_song != NULL) { + song_free(pc->next_song); + pc->next_song = NULL; + } + + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_CLOSE_AUDIO: + pc->Unlock(); + + audio_output_all_release(); + + pc->Lock(); + player_command_finished_locked(pc); + +#ifndef NDEBUG + /* in the DEBUG build, check for leaked + music_chunk objects by freeing the + music_buffer */ + music_buffer_free(player_buffer); + player_buffer = music_buffer_new(pc->buffer_chunks); +#endif + + break; + + case PLAYER_COMMAND_UPDATE_AUDIO: + pc->Unlock(); + audio_output_all_enable_disable(); + pc->Lock(); + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_EXIT: + pc->Unlock(); + + dc->Quit(); + delete dc; + audio_output_all_close(); + music_buffer_free(player_buffer); + + player_command_finished(pc); + return NULL; + + case PLAYER_COMMAND_CANCEL: + if (pc->next_song != NULL) { + song_free(pc->next_song); + pc->next_song = NULL; + } + + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_REFRESH: + /* no-op when not playing */ + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_NONE: + pc->Wait(); + break; + } + } +} + +void +player_create(struct player_control *pc) +{ + assert(pc->thread == NULL); + + GError *e = NULL; + pc->thread = g_thread_create(player_task, pc, true, &e); + if (pc->thread == NULL) + MPD_ERROR("Failed to spawn player task: %s", e->message); +} diff --git a/src/PlayerThread.hxx b/src/PlayerThread.hxx new file mode 100644 index 000000000..197669cd6 --- /dev/null +++ b/src/PlayerThread.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* \file + * + * The player thread controls the playback. It acts as a bridge + * between the decoder thread and the output thread(s): it receives + * #music_chunk objects from the decoder, optionally mixes them + * (cross-fading), applies software volume, and sends them to the + * audio outputs via audio_output_all_play(). + * + * It is controlled by the main thread (the playlist code), see + * player_control.h. The playlist enqueues new songs into the player + * thread and sends it commands. + * + * The player thread itself does not do any I/O. It synchronizes with + * other threads via #GMutex and #GCond objects, and passes + * #music_chunk instances around in #music_pipe objects. + */ + +#ifndef MPD_PLAYER_THREAD_HXX +#define MPD_PLAYER_THREAD_HXX + +struct player_control; + +void +player_create(struct player_control *pc); + +#endif diff --git a/src/Playlist.cxx b/src/Playlist.cxx new file mode 100644 index 000000000..89bdac637 --- /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 000000000..c01813322 --- /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/PlaylistAny.cxx b/src/PlaylistAny.cxx new file mode 100644 index 000000000..3f6733f0b --- /dev/null +++ b/src/PlaylistAny.cxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "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, Mutex &mutex, Cond &cond, + struct input_stream **is_r) +{ + assert(uri_has_scheme(uri)); + + struct playlist_provider *playlist = + playlist_list_open_uri(uri, mutex, cond); + if (playlist != NULL) { + *is_r = NULL; + return playlist; + } + + GError *error = NULL; + struct input_stream *is = input_stream_open(uri, mutex, cond, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("Failed to open %s: %s", + uri, error->message); + g_error_free(error); + } + + return NULL; + } + + playlist = playlist_list_open_stream(is, uri); + if (playlist == NULL) { + input_stream_close(is); + return NULL; + } + + *is_r = is; + return playlist; +} + +struct playlist_provider * +playlist_open_any(const char *uri, Mutex &mutex, Cond &cond, + struct input_stream **is_r) +{ + return uri_has_scheme(uri) + ? playlist_open_remote(uri, mutex, cond, is_r) + : playlist_mapper_open(uri, mutex, cond, is_r); +} diff --git a/src/PlaylistAny.hxx b/src/PlaylistAny.hxx new file mode 100644 index 000000000..d69087b3f --- /dev/null +++ b/src/PlaylistAny.hxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_ANY_HXX +#define MPD_PLAYLIST_ANY_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +struct playlist_provider; +struct input_stream; + +/** + * Opens a playlist from the specified URI, which can be either an + * absolute remote URI (with a scheme) or a relative path to the + * music orplaylist directory. + * + * @param is_r on success, an input_stream object may be returned + * here, which must be closed after the playlist_provider object is + * freed + */ +struct playlist_provider * +playlist_open_any(const char *uri, Mutex &mutex, Cond &cond, + struct input_stream **is_r); + +#endif diff --git a/src/PlaylistCommands.cxx b/src/PlaylistCommands.cxx new file mode 100644 index 000000000..dc3b3e0de --- /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 000000000..067f428b6 --- /dev/null +++ b/src/PlaylistCommands.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_COMMANDS_HXX +#define MPD_PLAYLIST_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_save(Client *client, int argc, char *argv[]); + +enum command_return +handle_load(Client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylist(Client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylistinfo(Client *client, int argc, char *argv[]); + +enum command_return +handle_rm(Client *client, int argc, char *argv[]); + +enum command_return +handle_rename(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistdelete(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistmove(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistclear(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistadd(Client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylists(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/PlaylistControl.cxx b/src/PlaylistControl.cxx new file mode 100644 index 000000000..3db61cc7c --- /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/PlaylistDatabase.cxx b/src/PlaylistDatabase.cxx new file mode 100644 index 000000000..edc6a2815 --- /dev/null +++ b/src/PlaylistDatabase.cxx @@ -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. + */ + +#include "config.h" +#include "PlaylistDatabase.hxx" +#include "PlaylistVector.hxx" +#include "TextFile.hxx" + +extern "C" { +#include "string_util.h" +} + +#include <string.h> +#include <stdlib.h> + +static GQuark +playlist_database_quark(void) +{ + return g_quark_from_static_string("playlist_database"); +} + +void +playlist_vector_save(FILE *fp, const PlaylistVector &pv) +{ + for (const PlaylistInfo &pi : pv) + fprintf(fp, PLAYLIST_META_BEGIN "%s\n" + "mtime: %li\n" + "playlist_end\n", + pi.name.c_str(), (long)pi.mtime); +} + +bool +playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name, + GError **error_r) +{ + PlaylistInfo pm(name, 0); + + char *line, *colon; + const char *value; + + while ((line = file.ReadLine()) != NULL && + strcmp(line, "playlist_end") != 0) { + colon = strchr(line, ':'); + if (colon == NULL || colon == line) { + g_set_error(error_r, playlist_database_quark(), 0, + "unknown line in db: %s", line); + return false; + } + + *colon++ = 0; + value = strchug_fast_c(colon); + + if (strcmp(line, "mtime") == 0) + pm.mtime = strtol(value, NULL, 10); + else { + g_set_error(error_r, playlist_database_quark(), 0, + "unknown line in db: %s", line); + return false; + } + } + + pv.UpdateOrInsert(std::move(pm)); + return true; +} diff --git a/src/PlaylistDatabase.hxx b/src/PlaylistDatabase.hxx new file mode 100644 index 000000000..a08d623fb --- /dev/null +++ b/src/PlaylistDatabase.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_DATABASE_HXX +#define MPD_PLAYLIST_DATABASE_HXX + +#include "check.h" +#include "gerror.h" + +#include <stdio.h> + +#define PLAYLIST_META_BEGIN "playlist_begin: " + +class PlaylistVector; +class TextFile; + +void +playlist_vector_save(FILE *fp, const PlaylistVector &pv); + +bool +playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name, + GError **error_r); + +#endif diff --git a/src/PlaylistEdit.cxx b/src/PlaylistEdit.cxx new file mode 100644 index 000000000..df38c3da1 --- /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/PlaylistFile.cxx b/src/PlaylistFile.cxx new file mode 100644 index 000000000..a879f70a0 --- /dev/null +++ b/src/PlaylistFile.cxx @@ -0,0 +1,472 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlaylistFile.hxx" +#include "PlaylistSave.hxx" +#include "PlaylistInfo.hxx" +#include "PlaylistVector.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" +#include "song.h" +#include "io_error.h" +#include "Mapper.hxx" +#include "TextFile.hxx" +#include "conf.h" +#include "Idle.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" + +extern "C" { +#include "uri.h" +} + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> +#include <string.h> +#include <errno.h> + +static const char PLAYLIST_COMMENT = '#'; + +static unsigned playlist_max_length; +bool playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS; + +void +spl_global_init(void) +{ + playlist_max_length = config_get_positive(CONF_MAX_PLAYLIST_LENGTH, + DEFAULT_PLAYLIST_MAX_LENGTH); + + playlist_saveAbsolutePaths = + config_get_bool(CONF_SAVE_ABSOLUTE_PATHS, + DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS); +} + +bool +spl_valid_name(const char *name_utf8) +{ + /* + * Not supporting '/' was done out of laziness, and we should + * really strive to support it in the future. + * + * Not supporting '\r' and '\n' is done out of protocol + * limitations (and arguably laziness), but bending over head + * over heels to modify the protocol (and compatibility with + * all clients) to support idiots who put '\r' and '\n' in + * filenames isn't going to happen, either. + */ + + return strchr(name_utf8, '/') == NULL && + strchr(name_utf8, '\n') == NULL && + strchr(name_utf8, '\r') == NULL; +} + +static const char * +spl_map(GError **error_r) +{ + const 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.c_str(); +} + +static bool +spl_check_name(const char *name_utf8, GError **error_r) +{ + if (!spl_valid_name(name_utf8)) { + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_BAD_NAME, + "Bad playlist name"); + return false; + } + + return true; +} + +static Path +spl_map_to_fs(const char *name_utf8, GError **error_r) +{ + if (spl_map(error_r) == NULL || + !spl_check_name(name_utf8, error_r)) + return Path::Null(); + + Path path_fs = map_spl_utf8_to_fs(name_utf8); + if (path_fs.IsNull()) + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_BAD_NAME, + "Bad playlist name"); + + return path_fs; +} + +/** + * Create a GError for the current errno. + */ +static void +playlist_errno(GError **error_r) +{ + switch (errno) { + case ENOENT: + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_NO_SUCH_LIST, + "No such playlist"); + break; + + default: + set_error_errno(error_r); + break; + } +} + +static bool +LoadPlaylistFileInfo(PlaylistInfo &info, + const char *parent_path_fs, const char *name_fs) +{ + size_t name_length = strlen(name_fs); + + if (name_length < sizeof(PLAYLIST_FILE_SUFFIX) || + memchr(name_fs, '\n', name_length) != NULL) + return false; + + if (!g_str_has_suffix(name_fs, PLAYLIST_FILE_SUFFIX)) + return false; + + 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 false; + + 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.empty()) + return false; + + info.name = std::move(name_utf8); + info.mtime = st.st_mtime; + return true; +} + +PlaylistVector +ListPlaylistFiles(GError **error_r) +{ + PlaylistVector list; + + const char *parent_path_fs = spl_map(error_r); + if (parent_path_fs == NULL) + return list; + + DIR *dir = opendir(parent_path_fs); + if (dir == NULL) { + set_error_errno(error_r); + return list; + } + + PlaylistInfo info; + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + if (LoadPlaylistFileInfo(info, parent_path_fs, ent->d_name)) + list.push_back(std::move(info)); + } + + closedir(dir); + return list; +} + +static bool +SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path, + GError **error_r) +{ + assert(utf8path != NULL); + + if (spl_map(error_r) == NULL) + return false; + + const Path path_fs = spl_map_to_fs(utf8path, error_r); + if (path_fs.IsNull()) + return false; + + FILE *file = FOpen(path_fs, FOpenMode::WriteText); + if (file == NULL) { + playlist_errno(error_r); + return false; + } + + for (const auto &uri_utf8 : contents) + playlist_print_uri(file, uri_utf8.c_str()); + + fclose(file); + return true; +} + +PlaylistFileContents +LoadPlaylistFile(const char *utf8path, GError **error_r) +{ + PlaylistFileContents contents; + + if (spl_map(error_r) == NULL) + return contents; + + const Path path_fs = spl_map_to_fs(utf8path, error_r); + if (path_fs.IsNull()) + return contents; + + TextFile file(path_fs); + if (file.HasFailed()) { + playlist_errno(error_r); + return contents; + } + + char *s; + while ((s = file.ReadLine()) != NULL) { + if (*s == 0 || *s == PLAYLIST_COMMENT) + continue; + + if (!uri_has_scheme(s)) { + char *path_utf8; + + path_utf8 = map_fs_to_utf8(s); + if (path_utf8 == NULL) + continue; + + s = path_utf8; + } else + s = g_strdup(s); + + contents.emplace_back(s); + if (contents.size() >= playlist_max_length) + break; + } + + return contents; +} + +bool +spl_move_index(const char *utf8path, unsigned src, unsigned dest, + GError **error_r) +{ + if (src == dest) + /* this doesn't check whether the playlist exists, but + what the hell.. */ + return true; + + GError *error = nullptr; + auto contents = LoadPlaylistFile(utf8path, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); + return false; + } + + if (src >= contents.size() || dest >= contents.size()) { + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_BAD_RANGE, + "Bad range"); + return false; + } + + const auto src_i = std::next(contents.begin(), src); + auto value = std::move(*src_i); + contents.erase(src_i); + + const auto dest_i = std::next(contents.begin(), dest); + contents.insert(dest_i, std::move(value)); + + bool result = SavePlaylistFile(contents, utf8path, error_r); + + idle_add(IDLE_STORED_PLAYLIST); + return result; +} + +bool +spl_clear(const char *utf8path, GError **error_r) +{ + if (spl_map(error_r) == NULL) + return false; + + const Path path_fs = spl_map_to_fs(utf8path, error_r); + if (path_fs.IsNull()) + return false; + + FILE *file = FOpen(path_fs, FOpenMode::WriteText); + if (file == NULL) { + playlist_errno(error_r); + return false; + } + + fclose(file); + + idle_add(IDLE_STORED_PLAYLIST); + return true; +} + +bool +spl_delete(const char *name_utf8, GError **error_r) +{ + const Path path_fs = spl_map_to_fs(name_utf8, error_r); + if (path_fs.IsNull()) + return false; + + if (!RemoveFile(path_fs)) { + playlist_errno(error_r); + return false; + } + + idle_add(IDLE_STORED_PLAYLIST); + return true; +} + +bool +spl_remove_index(const char *utf8path, unsigned pos, GError **error_r) +{ + GError *error = nullptr; + auto contents = LoadPlaylistFile(utf8path, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); + return false; + } + + if (pos >= contents.size()) { + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_BAD_RANGE, + "Bad range"); + return false; + } + + contents.erase(std::next(contents.begin(), pos)); + + bool result = SavePlaylistFile(contents, utf8path, error_r); + + idle_add(IDLE_STORED_PLAYLIST); + return result; +} + +bool +spl_append_song(const char *utf8path, struct song *song, GError **error_r) +{ + if (spl_map(error_r) == NULL) + return false; + + const Path path_fs = spl_map_to_fs(utf8path, error_r); + if (path_fs.IsNull()) + return false; + + FILE *file = FOpen(path_fs, FOpenMode::AppendText); + if (file == NULL) { + playlist_errno(error_r); + return false; + } + + struct stat st; + if (fstat(fileno(file), &st) < 0) { + playlist_errno(error_r); + fclose(file); + return false; + } + + if (st.st_size / (MPD_PATH_MAX + 1) >= (off_t)playlist_max_length) { + fclose(file); + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_TOO_LARGE, + "Stored playlist is too large"); + return false; + } + + playlist_print_song(file, song); + + fclose(file); + + idle_add(IDLE_STORED_PLAYLIST); + return true; +} + +bool +spl_append_uri(const char *url, const char *utf8file, GError **error_r) +{ + if (uri_has_scheme(url)) { + struct song *song = song_remote_new(url); + bool success = spl_append_song(utf8file, song, error_r); + song_free(song); + return success; + } else { + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + song *song = db->GetSong(url, error_r); + if (song == nullptr) + return false; + + bool success = spl_append_song(utf8file, song, error_r); + db->ReturnSong(song); + return success; + } +} + +static bool +spl_rename_internal(const Path &from_path_fs, const Path &to_path_fs, + GError **error_r) +{ + if (!FileExists(from_path_fs)) { + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_NO_SUCH_LIST, + "No such playlist"); + return false; + } + + if (FileExists(to_path_fs)) { + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_LIST_EXISTS, + "Playlist exists already"); + return false; + } + + if (!RenameFile(from_path_fs, to_path_fs)) { + playlist_errno(error_r); + return false; + } + + idle_add(IDLE_STORED_PLAYLIST); + return true; +} + +bool +spl_rename(const char *utf8from, const char *utf8to, GError **error_r) +{ + if (spl_map(error_r) == NULL) + return false; + + Path from_path_fs = spl_map_to_fs(utf8from, error_r); + if (from_path_fs.IsNull()) + return false; + + Path to_path_fs = spl_map_to_fs(utf8to, error_r); + if (to_path_fs.IsNull()) + return false; + + return spl_rename_internal(from_path_fs, to_path_fs, error_r); +} diff --git a/src/PlaylistFile.hxx b/src/PlaylistFile.hxx new file mode 100644 index 000000000..a9aeaa237 --- /dev/null +++ b/src/PlaylistFile.hxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_FILE_HXX +#define MPD_PLAYLIST_FILE_HXX + +#include "gerror.h" + +#include <vector> +#include <string> + +struct song; +struct PlaylistInfo; +class PlaylistVector; + +typedef std::vector<std::string> PlaylistFileContents; + +extern bool playlist_saveAbsolutePaths; + +/** + * Perform some global initialization, e.g. load configuration values. + */ +void +spl_global_init(void); + +#ifdef __cplusplus + +/** + * Determines whether the specified string is a valid name for a + * stored playlist. + */ +bool +spl_valid_name(const char *name_utf8); + +/** + * Returns a list of stored_playlist_info struct pointers. Returns + * NULL if an error occurred. + */ +PlaylistVector +ListPlaylistFiles(GError **error_r); + +PlaylistFileContents +LoadPlaylistFile(const char *utf8path, GError **error_r); + +bool +spl_move_index(const char *utf8path, unsigned src, unsigned dest, + GError **error_r); + +bool +spl_clear(const char *utf8path, GError **error_r); + +bool +spl_delete(const char *name_utf8, GError **error_r); + +bool +spl_remove_index(const char *utf8path, unsigned pos, GError **error_r); + +bool +spl_append_song(const char *utf8path, struct song *song, GError **error_r); + +bool +spl_append_uri(const char *file, const char *utf8file, GError **error_r); + +bool +spl_rename(const char *utf8from, const char *utf8to, GError **error_r); + +#endif + +#endif diff --git a/src/PlaylistGlobal.cxx b/src/PlaylistGlobal.cxx new file mode 100644 index 000000000..9dd971d3f --- /dev/null +++ b/src/PlaylistGlobal.cxx @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * The manager of the global "struct playlist" instance (g_playlist). + * + */ + +#include "config.h" +#include "PlaylistGlobal.hxx" +#include "Playlist.hxx" +#include "Main.hxx" +#include "Partition.hxx" +#include "GlobalEvents.hxx" + +static void +playlist_tag_event(void) +{ + global_partition->playlist.TagChanged(); +} + +static void +playlist_event(void) +{ + global_partition->playlist.SyncWithPlayer(global_partition->pc); +} + +void +playlist_global_init() +{ + GlobalEvents::Register(GlobalEvents::TAG, playlist_tag_event); + GlobalEvents::Register(GlobalEvents::PLAYLIST, playlist_event); +} diff --git a/src/PlaylistGlobal.hxx b/src/PlaylistGlobal.hxx new file mode 100644 index 000000000..4397292db --- /dev/null +++ b/src/PlaylistGlobal.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_GLOBAL_HXX +#define MPD_PLAYLIST_GLOBAL_HXX + +void +playlist_global_init(); + +#endif diff --git a/src/PlaylistInfo.hxx b/src/PlaylistInfo.hxx new file mode 100644 index 000000000..96e4f6db9 --- /dev/null +++ b/src/PlaylistInfo.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_INFO_HXX +#define MPD_PLAYLIST_INFO_HXX + +#include "check.h" +#include "gcc.h" + +#include <string> + +#include <sys/time.h> + +/** + * A directory entry pointing to a playlist file. + */ +struct PlaylistInfo { + /** + * The UTF-8 encoded name of the playlist file. + */ + std::string name; + + time_t mtime; + + class CompareName { + const char *const name; + + public: + constexpr CompareName(const char *_name):name(_name) {} + + gcc_pure + bool operator()(const PlaylistInfo &pi) const { + return pi.name.compare(name) == 0; + } + }; + + PlaylistInfo() = default; + + template<typename N> + PlaylistInfo(N &&_name, time_t _mtime) + :name(std::forward<N>(_name)), mtime(_mtime) {} + + PlaylistInfo(const PlaylistInfo &other) = delete; + PlaylistInfo(PlaylistInfo &&) = default; +}; + +#endif diff --git a/src/PlaylistMapper.cxx b/src/PlaylistMapper.cxx new file mode 100644 index 000000000..85f47e44b --- /dev/null +++ b/src/PlaylistMapper.cxx @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlaylistMapper.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistRegistry.hxx" +#include "Mapper.hxx" +#include "fs/Path.hxx" + +extern "C" { +#include "uri.h" +} + +#include <assert.h> + +static struct playlist_provider * +playlist_open_path(const char *path_fs, Mutex &mutex, Cond &cond, + struct input_stream **is_r) +{ + struct playlist_provider *playlist; + + playlist = playlist_list_open_uri(path_fs, mutex, cond); + if (playlist != NULL) + *is_r = NULL; + else + playlist = playlist_list_open_path(path_fs, mutex, cond, is_r); + + return playlist; +} + +/** + * Load a playlist from the configured playlist directory. + */ +static struct playlist_provider * +playlist_open_in_playlist_dir(const char *uri, Mutex &mutex, Cond &cond, + struct input_stream **is_r) +{ + char *path_fs; + + assert(spl_valid_name(uri)); + + const Path &playlist_directory_fs = map_spl_path(); + if (playlist_directory_fs.IsNull()) + return NULL; + + path_fs = g_build_filename(playlist_directory_fs.c_str(), uri, NULL); + + struct playlist_provider *playlist = + playlist_open_path(path_fs, mutex, cond, is_r); + g_free(path_fs); + + return playlist; +} + +/** + * Load a playlist from the configured music directory. + */ +static struct playlist_provider * +playlist_open_in_music_dir(const char *uri, Mutex &mutex, Cond &cond, + struct input_stream **is_r) +{ + assert(uri_safe_local(uri)); + + Path path = map_uri_fs(uri); + if (path.IsNull()) + return NULL; + + return playlist_open_path(path.c_str(), mutex, cond, is_r); +} + +struct playlist_provider * +playlist_mapper_open(const char *uri, Mutex &mutex, Cond &cond, + struct input_stream **is_r) +{ + struct playlist_provider *playlist; + + if (spl_valid_name(uri)) { + playlist = playlist_open_in_playlist_dir(uri, mutex, cond, + is_r); + if (playlist != NULL) + return playlist; + } + + if (uri_safe_local(uri)) { + playlist = playlist_open_in_music_dir(uri, mutex, cond, is_r); + if (playlist != NULL) + return playlist; + } + + return NULL; +} diff --git a/src/PlaylistMapper.hxx b/src/PlaylistMapper.hxx new file mode 100644 index 000000000..abfdb5481 --- /dev/null +++ b/src/PlaylistMapper.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_MAPPER_HXX +#define MPD_PLAYLIST_MAPPER_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +struct input_stream; + +/** + * Opens a playlist from an URI relative to the playlist or music + * directory. + * + * @param is_r on success, an input_stream object may be returned + * here, which must be closed after the playlist_provider object is + * freed + */ +struct playlist_provider * +playlist_mapper_open(const char *uri, Mutex &mutex, Cond &cond, + struct input_stream **is_r); + +#endif diff --git a/src/PlaylistPlugin.hxx b/src/PlaylistPlugin.hxx new file mode 100644 index 000000000..d422106bb --- /dev/null +++ b/src/PlaylistPlugin.hxx @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_PLUGIN_HXX +#define MPD_PLAYLIST_PLUGIN_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +#include <stddef.h> + +struct config_param; +struct input_stream; +struct tag; + +/** + * An object which provides the contents of a playlist. + */ +struct playlist_provider { + const struct playlist_plugin *plugin; +}; + +static inline void +playlist_provider_init(struct playlist_provider *playlist, + const struct playlist_plugin *plugin) +{ + playlist->plugin = plugin; +} + +struct playlist_plugin { + const char *name; + + /** + * Initialize the plugin. Optional method. + * + * @param param a configuration block for this plugin, or NULL + * if none is configured + * @return true if the plugin was initialized successfully, + * false if the plugin is not available + */ + bool (*init)(const struct config_param *param); + + /** + * Deinitialize a plugin which was initialized successfully. + * Optional method. + */ + void (*finish)(void); + + /** + * Opens the playlist on the specified URI. This URI has + * either matched one of the schemes or one of the suffixes. + */ + struct playlist_provider *(*open_uri)(const char *uri, + Mutex &mutex, Cond &cond); + + /** + * Opens the playlist in the specified input stream. It has + * either matched one of the suffixes or one of the MIME + * types. + */ + struct playlist_provider *(*open_stream)(struct input_stream *is); + + void (*close)(struct playlist_provider *playlist); + + struct song *(*read)(struct playlist_provider *playlist); + + const char *const*schemes; + const char *const*suffixes; + const char *const*mime_types; +}; + +/** + * Initialize a plugin. + * + * @param param a configuration block for this plugin, or NULL if none + * is configured + * @return true if the plugin was initialized successfully, false if + * the plugin is not available + */ +static inline bool +playlist_plugin_init(const struct playlist_plugin *plugin, + const struct config_param *param) +{ + return plugin->init != NULL + ? plugin->init(param) + : true; +} + +/** + * Deinitialize a plugin which was initialized successfully. + */ +static inline void +playlist_plugin_finish(const struct playlist_plugin *plugin) +{ + if (plugin->finish != NULL) + plugin->finish(); +} + +static inline struct playlist_provider * +playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri, + Mutex &mutex, Cond &cond) +{ + return plugin->open_uri(uri, mutex, cond); +} + +static inline struct playlist_provider * +playlist_plugin_open_stream(const struct playlist_plugin *plugin, + struct input_stream *is) +{ + return plugin->open_stream(is); +} + +static inline void +playlist_plugin_close(struct playlist_provider *playlist) +{ + playlist->plugin->close(playlist); +} + +static inline struct song * +playlist_plugin_read(struct playlist_provider *playlist) +{ + return playlist->plugin->read(playlist); +} + +#endif diff --git a/src/PlaylistPrint.cxx b/src/PlaylistPrint.cxx new file mode 100644 index 000000000..e79e87732 --- /dev/null +++ b/src/PlaylistPrint.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 "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(Client *client, const struct playlist *playlist) +{ + const struct queue *queue = &playlist->queue; + + queue_print_uris(client, queue, 0, queue->GetLength()); +} + +bool +playlist_print_info(Client *client, const struct playlist *playlist, + unsigned start, unsigned end) +{ + const struct queue *queue = &playlist->queue; + + if (end > queue->GetLength()) + /* correct the "end" offset */ + end = queue->GetLength(); + + if (start > end) + /* an invalid "start" offset is fatal */ + return false; + + queue_print_info(client, queue, start, end); + return true; +} + +bool +playlist_print_id(Client *client, const struct playlist *playlist, + unsigned id) +{ + const struct queue *queue = &playlist->queue; + int position; + + position = queue->IdToPosition(id); + if (position < 0) + /* no such song */ + return false; + + return playlist_print_info(client, playlist, position, position + 1); +} + +bool +playlist_print_current(Client *client, const struct playlist *playlist) +{ + int current_position = playlist->GetCurrentPosition(); + if (current_position < 0) + return false; + + queue_print_info(client, &playlist->queue, + current_position, current_position + 1); + return true; +} + +void +playlist_print_find(Client *client, const struct playlist *playlist, + const SongFilter &filter) +{ + queue_find(client, &playlist->queue, filter); +} + +void +playlist_print_changes_info(Client *client, + const struct playlist *playlist, + uint32_t version) +{ + queue_print_changes_info(client, &playlist->queue, version); +} + +void +playlist_print_changes_position(Client *client, + const struct playlist *playlist, + uint32_t version) +{ + queue_print_changes_position(client, &playlist->queue, version); +} + +static bool +PrintSongDetails(Client *client, const char *uri_utf8) +{ + const Database *db = GetDatabase(nullptr); + if (db == nullptr) + return false; + + song *song = db->GetSong(uri_utf8, nullptr); + if (song == nullptr) + return false; + + song_print_info(client, song); + db->ReturnSong(song); + return true; +} + +bool +spl_print(Client *client, const char *name_utf8, bool detail, + GError **error_r) +{ + GError *error = NULL; + PlaylistFileContents contents = LoadPlaylistFile(name_utf8, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); + return false; + } + + for (const auto &uri_utf8 : contents) { + if (!detail || !PrintSongDetails(client, uri_utf8.c_str())) + client_printf(client, SONG_FILE "%s\n", + uri_utf8.c_str()); + } + + return true; +} + +static void +playlist_provider_print(Client *client, const char *uri, + struct playlist_provider *playlist, bool detail) +{ + struct song *song; + char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; + + while ((song = playlist_plugin_read(playlist)) != NULL) { + song = playlist_check_translate_song(song, base_uri, false); + if (song == NULL) + continue; + + if (detail) + song_print_info(client, song); + else + song_print_uri(client, song); + + song_free(song); + } + + g_free(base_uri); +} + +bool +playlist_file_print(Client *client, const char *uri, bool detail) +{ + Mutex mutex; + Cond cond; + + struct input_stream *is; + struct playlist_provider *playlist = + playlist_open_any(uri, mutex, cond, &is); + if (playlist == NULL) + return false; + + playlist_provider_print(client, uri, playlist, detail); + playlist_plugin_close(playlist); + + if (is != NULL) + input_stream_close(is); + + return true; +} diff --git a/src/PlaylistPrint.hxx b/src/PlaylistPrint.hxx new file mode 100644 index 000000000..16bee9b85 --- /dev/null +++ b/src/PlaylistPrint.hxx @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_PRINT_HXX +#define MPD_PLAYLIST_PRINT_HXX + +#include "gerror.h" + +#include <stdint.h> + +struct playlist; +class SongFilter; +class Client; + +/** + * Sends the whole playlist to the client, song URIs only. + */ +void +playlist_print_uris(Client *client, const struct playlist *playlist); + +/** + * Sends a range of the playlist to the client, including all known + * information about the songs. The "end" offset is decreased + * automatically if it is too large; passing UINT_MAX is allowed. + * This function however fails when the start offset is invalid. + */ +bool +playlist_print_info(Client *client, const struct playlist *playlist, + unsigned start, unsigned end); + +/** + * Sends the song with the specified id to the client. + * + * @return true on suite, false if there is no such song + */ +bool +playlist_print_id(Client *client, const struct playlist *playlist, + unsigned id); + +/** + * Sends the current song to the client. + * + * @return true on success, false if there is no current song + */ +bool +playlist_print_current(Client *client, const struct playlist *playlist); + +/** + * Find songs in the playlist. + */ +void +playlist_print_find(Client *client, const struct playlist *playlist, + const SongFilter &filter); + +/** + * Print detailed changes since the specified playlist version. + */ +void +playlist_print_changes_info(Client *client, + const struct playlist *playlist, + uint32_t version); + +/** + * Print changes since the specified playlist version, position only. + */ +void +playlist_print_changes_position(Client *client, + const struct playlist *playlist, + uint32_t version); + +/** + * Send the stored playlist to the client. + * + * @param client the client which requested the playlist + * @param name_utf8 the name of the stored playlist in UTF-8 encoding + * @param detail true if all details should be printed + * @return true on success, false if the playlist does not exist + */ +bool +spl_print(Client *client, const char *name_utf8, bool detail, + GError **error_r); + +/** + * Send the playlist file to the client. + * + * @param client the client which requested the playlist + * @param uri the URI of the playlist file in UTF-8 encoding + * @param detail true if all details should be printed + * @return true on success, false if the playlist does not exist + */ +bool +playlist_file_print(Client *client, const char *uri, bool detail); + +#endif diff --git a/src/PlaylistQueue.cxx b/src/PlaylistQueue.cxx new file mode 100644 index 000000000..c52f49a91 --- /dev/null +++ b/src/PlaylistQueue.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 "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, + struct playlist *dest, struct player_control *pc, + bool secure) +{ + enum playlist_result result; + struct song *song; + char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; + + for (unsigned i = 0; + i < end_index && (song = playlist_plugin_read(source)) != NULL; + ++i) { + if (i < start_index) { + /* skip songs before the start index */ + song_free(song); + continue; + } + + song = playlist_check_translate_song(song, base_uri, secure); + if (song == NULL) + continue; + + result = dest->AppendSong(*pc, song); + song_free(song); + if (result != PLAYLIST_RESULT_SUCCESS) { + g_free(base_uri); + return result; + } + } + + g_free(base_uri); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist_open_into_queue(const char *uri, + unsigned start_index, unsigned end_index, + struct playlist *dest, struct player_control *pc, + bool secure) +{ + Mutex mutex; + Cond cond; + + struct input_stream *is; + struct playlist_provider *playlist = + playlist_open_any(uri, mutex, cond, &is); + if (playlist == NULL) + return PLAYLIST_RESULT_NO_SUCH_LIST; + + enum playlist_result result = + playlist_load_into_queue(uri, playlist, start_index, end_index, + dest, pc, secure); + playlist_plugin_close(playlist); + + if (is != NULL) + input_stream_close(is); + + return result; +} diff --git a/src/PlaylistQueue.hxx b/src/PlaylistQueue.hxx new file mode 100644 index 000000000..cda77c818 --- /dev/null +++ b/src/PlaylistQueue.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! \file + * \brief Glue between playlist plugin and the play queue + */ + +#ifndef MPD_PLAYLIST_QUEUE_HXX +#define MPD_PLAYLIST_QUEUE_HXX + +#include "playlist_error.h" + +struct playlist_provider; +struct playlist; +struct player_control; + +/** + * Loads the contents of a playlist and append it to the specified + * play queue. + * + * @param uri the URI of the playlist, used to resolve relative song + * URIs + * @param start_index the index of the first song + * @param end_index the index of the last song (excluding) + */ +enum playlist_result +playlist_load_into_queue(const char *uri, struct playlist_provider *source, + unsigned start_index, unsigned end_index, + struct playlist *dest, struct player_control *pc, + bool secure); + +/** + * Opens a playlist with a playlist plugin and append to the specified + * play queue. + */ +enum playlist_result +playlist_open_into_queue(const char *uri, + unsigned start_index, unsigned end_index, + struct playlist *dest, struct player_control *pc, + bool secure); + +#endif + diff --git a/src/PlaylistRegistry.cxx b/src/PlaylistRegistry.cxx new file mode 100644 index 000000000..6dc94034e --- /dev/null +++ b/src/PlaylistRegistry.cxx @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlaylistRegistry.hxx" +#include "PlaylistPlugin.hxx" +#include "playlist/ExtM3uPlaylistPlugin.hxx" +#include "playlist/M3uPlaylistPlugin.hxx" +#include "playlist/XspfPlaylistPlugin.hxx" +#include "playlist/LastFMPlaylistPlugin.hxx" +#include "playlist/DespotifyPlaylistPlugin.hxx" +#include "playlist/SoundCloudPlaylistPlugin.hxx" +#include "playlist/PlsPlaylistPlugin.hxx" +#include "playlist/AsxPlaylistPlugin.hxx" +#include "playlist/RssPlaylistPlugin.hxx" +#include "playlist/CuePlaylistPlugin.hxx" +#include "playlist/EmbeddedCuePlaylistPlugin.hxx" +#include "input_stream.h" + +extern "C" { +#include "uri.h" +} + +#include "string_util.h" +#include "conf.h" +#include "mpd_error.h" + +#include <assert.h> +#include <string.h> +#include <stdio.h> + +const struct playlist_plugin *const playlist_plugins[] = { + &extm3u_playlist_plugin, + &m3u_playlist_plugin, + &xspf_playlist_plugin, + &pls_playlist_plugin, + &asx_playlist_plugin, + &rss_playlist_plugin, +#ifdef ENABLE_DESPOTIFY + &despotify_playlist_plugin, +#endif +#ifdef ENABLE_LASTFM + &lastfm_playlist_plugin, +#endif +#ifdef ENABLE_SOUNDCLOUD + &soundcloud_playlist_plugin, +#endif + &cue_playlist_plugin, + &embcue_playlist_plugin, + NULL +}; + +/** which plugins have been initialized successfully? */ +static bool playlist_plugins_enabled[G_N_ELEMENTS(playlist_plugins)]; + +#define playlist_plugins_for_each_enabled(plugin) \ + playlist_plugins_for_each(plugin) \ + if (playlist_plugins_enabled[playlist_plugin_iterator - playlist_plugins]) + +/** + * Find the "playlist" configuration block for the specified plugin. + * + * @param plugin_name the name of the playlist plugin + * @return the configuration block, or NULL if none was configured + */ +static const struct config_param * +playlist_plugin_config(const char *plugin_name) +{ + const struct config_param *param = NULL; + + assert(plugin_name != NULL); + + while ((param = config_get_next_param(CONF_PLAYLIST_PLUGIN, param)) != NULL) { + const char *name = + config_get_block_string(param, "name", NULL); + if (name == NULL) + MPD_ERROR("playlist configuration without 'plugin' name in line %d", + param->line); + + if (strcmp(name, plugin_name) == 0) + return param; + } + + return NULL; +} + +void +playlist_list_global_init(void) +{ + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + const struct config_param *param = + playlist_plugin_config(plugin->name); + + if (!config_get_block_bool(param, "enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + playlist_plugins_enabled[i] = + playlist_plugin_init(playlist_plugins[i], param); + } +} + +void +playlist_list_global_finish(void) +{ + playlist_plugins_for_each_enabled(plugin) + playlist_plugin_finish(plugin); +} + +static struct playlist_provider * +playlist_list_open_uri_scheme(const char *uri, Mutex &mutex, Cond &cond, + bool *tried) +{ + char *scheme; + struct playlist_provider *playlist = NULL; + + assert(uri != NULL); + + scheme = g_uri_parse_scheme(uri); + if (scheme == NULL) + return NULL; + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + assert(!tried[i]); + + if (playlist_plugins_enabled[i] && plugin->open_uri != NULL && + plugin->schemes != NULL && + string_array_contains(plugin->schemes, scheme)) { + playlist = playlist_plugin_open_uri(plugin, uri, + mutex, cond); + if (playlist != NULL) + break; + + tried[i] = true; + } + } + + g_free(scheme); + return playlist; +} + +static struct playlist_provider * +playlist_list_open_uri_suffix(const char *uri, Mutex &mutex, Cond &cond, + const bool *tried) +{ + const char *suffix; + struct playlist_provider *playlist = NULL; + + assert(uri != NULL); + + suffix = uri_get_suffix(uri); + if (suffix == NULL) + return NULL; + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && !tried[i] && + plugin->open_uri != NULL && plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) { + playlist = playlist_plugin_open_uri(plugin, uri, + mutex, cond); + if (playlist != NULL) + break; + } + } + + return playlist; +} + +struct playlist_provider * +playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond) +{ + struct playlist_provider *playlist; + /** this array tracks which plugins have already been tried by + playlist_list_open_uri_scheme() */ + bool tried[G_N_ELEMENTS(playlist_plugins) - 1]; + + assert(uri != NULL); + + memset(tried, false, sizeof(tried)); + + playlist = playlist_list_open_uri_scheme(uri, mutex, cond, tried); + if (playlist == NULL) + playlist = playlist_list_open_uri_suffix(uri, mutex, cond, + tried); + + return playlist; +} + +static struct playlist_provider * +playlist_list_open_stream_mime2(struct input_stream *is, const char *mime) +{ + struct playlist_provider *playlist; + + assert(is != NULL); + assert(mime != NULL); + + playlist_plugins_for_each_enabled(plugin) { + if (plugin->open_stream != NULL && + plugin->mime_types != NULL && + string_array_contains(plugin->mime_types, mime)) { + /* rewind the stream, so each plugin gets a + fresh start */ + input_stream_seek(is, 0, SEEK_SET, NULL); + + playlist = playlist_plugin_open_stream(plugin, is); + if (playlist != NULL) + return playlist; + } + } + + return NULL; +} + +static struct playlist_provider * +playlist_list_open_stream_mime(struct input_stream *is, const char *full_mime) +{ + assert(full_mime != NULL); + + const char *semicolon = strchr(full_mime, ';'); + if (semicolon == NULL) + return playlist_list_open_stream_mime2(is, full_mime); + + if (semicolon == full_mime) + return NULL; + + /* probe only the portion before the semicolon*/ + char *mime = g_strndup(full_mime, semicolon - full_mime); + struct playlist_provider *playlist = + playlist_list_open_stream_mime2(is, mime); + g_free(mime); + return playlist; +} + +static struct playlist_provider * +playlist_list_open_stream_suffix(struct input_stream *is, const char *suffix) +{ + struct playlist_provider *playlist; + + assert(is != NULL); + assert(suffix != NULL); + + playlist_plugins_for_each_enabled(plugin) { + if (plugin->open_stream != NULL && + plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) { + /* rewind the stream, so each plugin gets a + fresh start */ + input_stream_seek(is, 0, SEEK_SET, NULL); + + playlist = playlist_plugin_open_stream(plugin, is); + if (playlist != NULL) + return playlist; + } + } + + return NULL; +} + +struct playlist_provider * +playlist_list_open_stream(struct input_stream *is, const char *uri) +{ + const char *suffix; + struct playlist_provider *playlist; + + input_stream_lock_wait_ready(is); + + const char *const mime = input_stream_get_mime_type(is); + if (mime != NULL) { + playlist = playlist_list_open_stream_mime(is, mime); + if (playlist != NULL) + return playlist; + } + + suffix = uri != NULL ? uri_get_suffix(uri) : NULL; + if (suffix != NULL) { + playlist = playlist_list_open_stream_suffix(is, suffix); + if (playlist != NULL) + return playlist; + } + + return NULL; +} + +bool +playlist_suffix_supported(const char *suffix) +{ + assert(suffix != NULL); + + playlist_plugins_for_each_enabled(plugin) { + if (plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) + return true; + } + + return false; +} + +struct playlist_provider * +playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond, + struct input_stream **is_r) +{ + GError *error = NULL; + const char *suffix; + struct input_stream *is; + struct playlist_provider *playlist; + + assert(path_fs != NULL); + + suffix = uri_get_suffix(path_fs); + if (suffix == NULL || !playlist_suffix_supported(suffix)) + return NULL; + + is = input_stream_open(path_fs, mutex, cond, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + return NULL; + } + + input_stream_lock_wait_ready(is); + + playlist = playlist_list_open_stream_suffix(is, suffix); + if (playlist != NULL) + *is_r = is; + else + input_stream_close(is); + + return playlist; +} diff --git a/src/PlaylistRegistry.hxx b/src/PlaylistRegistry.hxx new file mode 100644 index 000000000..7c34c1565 --- /dev/null +++ b/src/PlaylistRegistry.hxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_REGISTRY_HXX +#define MPD_PLAYLIST_REGISTRY_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +struct playlist_provider; +struct input_stream; + +extern const struct playlist_plugin *const playlist_plugins[]; + +#define playlist_plugins_for_each(plugin) \ + for (const struct playlist_plugin *plugin, \ + *const*playlist_plugin_iterator = &playlist_plugins[0]; \ + (plugin = *playlist_plugin_iterator) != NULL; \ + ++playlist_plugin_iterator) + +/** + * Initializes all playlist plugins. + */ +void +playlist_list_global_init(void); + +/** + * Deinitializes all playlist plugins. + */ +void +playlist_list_global_finish(void); + +/** + * Opens a playlist by its URI. + */ +struct playlist_provider * +playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond); + +/** + * Opens a playlist from an input stream. + * + * @param is an #input_stream object which is open and ready + * @param uri optional URI which was used to open the stream; may be + * used to select the appropriate playlist plugin + */ +struct playlist_provider * +playlist_list_open_stream(struct input_stream *is, const char *uri); + +/** + * Determines if there is a playlist plugin which can handle the + * specified file name suffix. + */ +bool +playlist_suffix_supported(const char *suffix); + +/** + * Opens a playlist from a local file. + * + * @param path_fs the path of the playlist file + * @param is_r on success, an input_stream object is returned here, + * which must be closed after the playlist_provider object is freed + * @return a playlist, or NULL on error + */ +struct playlist_provider * +playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond, + struct input_stream **is_r); + +#endif diff --git a/src/PlaylistSave.cxx b/src/PlaylistSave.cxx new file mode 100644 index 000000000..6ca6740fc --- /dev/null +++ b/src/PlaylistSave.cxx @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlaylistSave.hxx" +#include "PlaylistFile.hxx" +#include "Playlist.hxx" +#include "song.h" +#include "Mapper.hxx" +#include "Idle.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" + +extern "C" { +#include "uri.h" +} + +#include <glib.h> + +void +playlist_print_song(FILE *file, const struct song *song) +{ + if (playlist_saveAbsolutePaths && song_in_database(song)) { + const Path path = map_song_fs(song); + if (!path.IsNull()) + fprintf(file, "%s\n", path.c_str()); + } else { + char *uri = song_get_uri(song); + const Path uri_fs = Path::FromUTF8(uri); + g_free(uri); + + if (!uri_fs.IsNull()) + fprintf(file, "%s\n", uri_fs.c_str()); + } +} + +void +playlist_print_uri(FILE *file, const char *uri) +{ + Path path = playlist_saveAbsolutePaths && !uri_has_scheme(uri) && + !g_path_is_absolute(uri) + ? map_uri_fs(uri) + : Path::FromUTF8(uri); + + if (!path.IsNull()) + fprintf(file, "%s\n", path.c_str()); +} + +enum playlist_result +spl_save_queue(const char *name_utf8, const struct queue *queue) +{ + if (map_spl_path().IsNull()) + return PLAYLIST_RESULT_DISABLED; + + if (!spl_valid_name(name_utf8)) + return PLAYLIST_RESULT_BAD_NAME; + + const Path path_fs = map_spl_utf8_to_fs(name_utf8); + if (path_fs.IsNull()) + return PLAYLIST_RESULT_BAD_NAME; + + if (FileExists(path_fs)) + return PLAYLIST_RESULT_LIST_EXISTS; + + FILE *file = FOpen(path_fs, FOpenMode::WriteText); + + if (file == NULL) + return PLAYLIST_RESULT_ERRNO; + + for (unsigned i = 0; i < queue->GetLength(); i++) + playlist_print_song(file, queue->Get(i)); + + fclose(file); + + idle_add(IDLE_STORED_PLAYLIST); + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +spl_save_playlist(const char *name_utf8, const struct playlist *playlist) +{ + return spl_save_queue(name_utf8, &playlist->queue); +} + +bool +playlist_load_spl(struct playlist *playlist, struct player_control *pc, + const char *name_utf8, + unsigned start_index, unsigned end_index, + GError **error_r) +{ + GError *error = NULL; + PlaylistFileContents contents = LoadPlaylistFile(name_utf8, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); + return false; + } + + if (end_index > contents.size()) + end_index = contents.size(); + + for (unsigned i = start_index; i < end_index; ++i) { + const auto &uri_utf8 = contents[i]; + + if ((playlist->AppendURI(*pc, uri_utf8.c_str())) != PLAYLIST_RESULT_SUCCESS) { + /* for windows compatibility, convert slashes */ + char *temp2 = g_strdup(uri_utf8.c_str()); + char *p = temp2; + while (*p) { + if (*p == '\\') + *p = '/'; + p++; + } + + if (playlist->AppendURI(*pc, temp2) != PLAYLIST_RESULT_SUCCESS) + g_warning("can't add file \"%s\"", temp2); + + g_free(temp2); + } + } + + return true; +} diff --git a/src/PlaylistSave.hxx b/src/PlaylistSave.hxx new file mode 100644 index 000000000..ff5f0c494 --- /dev/null +++ b/src/PlaylistSave.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_SAVE_H +#define MPD_PLAYLIST_SAVE_H + +#include "playlist_error.h" + +#include <stdio.h> + +struct song; +struct queue; +struct playlist; +struct player_control; + +void +playlist_print_song(FILE *fp, const struct song *song); + +void +playlist_print_uri(FILE *fp, const char *uri); + +/** + * Saves a queue object into a stored playlist file. + */ +enum playlist_result +spl_save_queue(const char *name_utf8, const struct queue *queue); + +/** + * Saves a playlist object into a stored playlist file. + */ +enum playlist_result +spl_save_playlist(const char *name_utf8, const struct playlist *playlist); + +/** + * Loads a stored playlist file, and append all songs to the global + * playlist. + */ +bool +playlist_load_spl(struct playlist *playlist, struct player_control *pc, + const char *name_utf8, + unsigned start_index, unsigned end_index, + GError **error_r); + +#endif diff --git a/src/PlaylistSong.cxx b/src/PlaylistSong.cxx new file mode 100644 index 000000000..4df4d22d4 --- /dev/null +++ b/src/PlaylistSong.cxx @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "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 <glib.h> + +#include <assert.h> +#include <string.h> + +static void +merge_song_metadata(struct song *dest, const struct song *base, + const struct song *add) +{ + dest->tag = base->tag != NULL + ? (add->tag != NULL + ? tag_merge(base->tag, add->tag) + : tag_dup(base->tag)) + : (add->tag != NULL + ? tag_dup(add->tag) + : NULL); + + dest->mtime = base->mtime; + dest->start_ms = add->start_ms; + dest->end_ms = add->end_ms; +} + +static struct song * +apply_song_metadata(struct song *dest, const struct song *src) +{ + struct song *tmp; + + assert(dest != NULL); + assert(src != NULL); + + if (src->tag == NULL && src->start_ms == 0 && src->end_ms == 0) + return dest; + + if (song_in_database(dest)) { + const Path &path_fs = map_song_fs(dest); + if (path_fs.IsNull()) + return dest; + + std::string path_utf8 = path_fs.ToUTF8(); + if (path_utf8.empty()) + path_utf8 = path_fs.c_str(); + + tmp = song_file_new(path_utf8.c_str(), NULL); + + merge_song_metadata(tmp, dest, src); + } else { + tmp = song_file_new(dest->uri, NULL); + merge_song_metadata(tmp, dest, src); + } + + if (dest->tag != NULL && dest->tag->time > 0 && + src->start_ms > 0 && src->end_ms == 0 && + src->start_ms / 1000 < (unsigned)dest->tag->time) + /* the range is open-ended, and the playlist plugin + did not know the total length of the song file + (e.g. last track on a CUE file); fix it up here */ + tmp->tag->time = dest->tag->time - src->start_ms / 1000; + + song_free(dest); + return tmp; +} + +static struct song * +playlist_check_load_song(const struct song *song, const char *uri, bool secure) +{ + struct song *dest; + + if (uri_has_scheme(uri)) { + dest = song_remote_new(uri); + } else if (g_path_is_absolute(uri) && secure) { + dest = song_file_load(uri, NULL); + if (dest == NULL) + return NULL; + } else { + 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); +} + +struct song * +playlist_check_translate_song(struct song *song, const char *base_uri, + bool secure) +{ + if (song_in_database(song)) + /* already ok */ + return song; + + const char *uri = song->uri; + + if (uri_has_scheme(uri)) { + if (uri_supported_scheme(uri)) + /* valid remote song */ + return song; + else { + /* unsupported remote song */ + song_free(song); + return NULL; + } + } + + if (base_uri != NULL && strcmp(base_uri, ".") == 0) + /* g_path_get_dirname() returns "." when there is no + directory name in the given path; clear that now, + because it would break the database lookup + functions */ + base_uri = NULL; + + if (g_path_is_absolute(uri)) { + /* XXX fs_charset vs utf8? */ + const char *suffix = map_to_relative_path(uri); + assert(suffix != NULL); + + if (suffix != uri) + uri = suffix; + else if (!secure) { + /* local files must be relative to the music + directory when "secure" is enabled */ + song_free(song); + return NULL; + } + + base_uri = NULL; + } + + char *allocated = NULL; + if (base_uri != NULL) + uri = allocated = g_build_filename(base_uri, uri, NULL); + + struct song *dest = playlist_check_load_song(song, uri, secure); + song_free(song); + g_free(allocated); + return dest; +} diff --git a/src/PlaylistSong.hxx b/src/PlaylistSong.hxx new file mode 100644 index 000000000..117ee1338 --- /dev/null +++ b/src/PlaylistSong.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_PLAYLIST_SONG_HXX +#define MPD_PLAYLIST_SONG_HXX + +/** + * Verifies the song, returns NULL if it is unsafe. Translate the + * song to a new song object within the database, if it is a local + * file. The old song object is freed. + * + * @param secure if true, then local files are only allowed if they + * are relative to base_uri + */ +struct song * +playlist_check_translate_song(struct song *song, const char *base_uri, + bool secure); + +#endif diff --git a/src/PlaylistState.cxx b/src/PlaylistState.cxx new file mode 100644 index 000000000..d03de0a16 --- /dev/null +++ b/src/PlaylistState.cxx @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Saving and loading the playlist to/from the state file. + * + */ + +#include "config.h" +#include "PlaylistState.hxx" +#include "Playlist.hxx" +#include "QueueSave.hxx" +#include "TextFile.hxx" +#include "PlayerControl.hxx" +#include "conf.h" + +#include <string.h> +#include <stdlib.h> + +#define PLAYLIST_STATE_FILE_STATE "state: " +#define PLAYLIST_STATE_FILE_RANDOM "random: " +#define PLAYLIST_STATE_FILE_REPEAT "repeat: " +#define PLAYLIST_STATE_FILE_SINGLE "single: " +#define PLAYLIST_STATE_FILE_CONSUME "consume: " +#define PLAYLIST_STATE_FILE_CURRENT "current: " +#define PLAYLIST_STATE_FILE_TIME "time: " +#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: " +#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: " +#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: " +#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin" +#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end" + +#define PLAYLIST_STATE_FILE_STATE_PLAY "play" +#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause" +#define PLAYLIST_STATE_FILE_STATE_STOP "stop" + +#define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX + +void +playlist_state_save(FILE *fp, const struct playlist *playlist, + struct player_control *pc) +{ + const auto player_status = pc->GetStatus(); + + fputs(PLAYLIST_STATE_FILE_STATE, fp); + + if (playlist->playing) { + switch (player_status.state) { + case PLAYER_STATE_PAUSE: + fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp); + break; + default: + fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp); + } + fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", + playlist->queue.OrderToPosition(playlist->current)); + fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n", + (int)player_status.elapsed_time); + } else { + fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp); + + if (playlist->current >= 0) + fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", + playlist->queue.OrderToPosition(playlist->current)); + } + + fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist->queue.random); + fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist->queue.repeat); + fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist->queue.single); + fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n", + playlist->queue.consume); + fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n", + (int)pc->GetCrossFade()); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", + pc->GetMixRampDb()); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", + pc->GetMixRampDelay()); + fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp); + queue_save(fp, &playlist->queue); + fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp); +} + +static void +playlist_state_load(TextFile &file, struct playlist *playlist) +{ + const char *line = file.ReadLine(); + if (line == NULL) { + g_warning("No playlist in state file"); + return; + } + + while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { + queue_load_song(file, line, &playlist->queue); + + line = file.ReadLine(); + if (line == NULL) { + g_warning("'" PLAYLIST_STATE_FILE_PLAYLIST_END + "' not found in state file"); + break; + } + } + + playlist->queue.IncrementVersion(); +} + +bool +playlist_state_restore(const char *line, TextFile &file, + struct playlist *playlist, struct player_control *pc) +{ + int current = -1; + int seek_time = 0; + enum player_state state = PLAYER_STATE_STOP; + bool random_mode = false; + + if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE)) + return false; + + line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1; + + if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0) + state = PLAYER_STATE_PLAY; + else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0) + state = PLAYER_STATE_PAUSE; + + while ((line = file.ReadLine()) != NULL) { + if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_TIME)) { + seek_time = + atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_REPEAT)) { + playlist->SetRepeat(*pc, + strcmp(&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), + "1") == 0); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) { + playlist->SetSingle(*pc, + strcmp(&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), + "1") == 0); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) { + playlist->SetConsume(strcmp(&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), + "1") == 0); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CROSSFADE)) { + pc->SetCrossFade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) { + pc->SetMixRampDb(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { + pc->SetMixRampDelay(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) { + random_mode = + strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), + "1") == 0; + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CURRENT)) { + current = atoi(&(line + [strlen + (PLAYLIST_STATE_FILE_CURRENT)])); + } else if (g_str_has_prefix(line, + PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { + playlist_state_load(file, playlist); + } + } + + playlist->SetRandom(*pc, random_mode); + + if (!playlist->queue.IsEmpty()) { + if (!playlist->queue.IsValidPosition(current)) + current = 0; + + if (state == PLAYER_STATE_PLAY && + config_get_bool(CONF_RESTORE_PAUSED, false)) + /* the user doesn't want MPD to auto-start + playback after startup; fall back to + "pause" */ + state = PLAYER_STATE_PAUSE; + + /* enable all devices for the first time; this must be + called here, after the audio output states were + restored, before playback begins */ + if (state != PLAYER_STATE_STOP) + pc->UpdateAudio(); + + if (state == PLAYER_STATE_STOP /* && config_option */) + playlist->current = current; + else if (seek_time == 0) + playlist->PlayPosition(*pc, current); + else + playlist->SeekSongPosition(*pc, current, seek_time); + + if (state == PLAYER_STATE_PAUSE) + pc->Pause(); + } + + return true; +} + +unsigned +playlist_state_get_hash(const struct playlist *playlist, + struct player_control *pc) +{ + const auto player_status = pc->GetStatus(); + + return playlist->queue.version ^ + (player_status.state != PLAYER_STATE_STOP + ? ((int)player_status.elapsed_time << 8) + : 0) ^ + (playlist->current >= 0 + ? (playlist->queue.OrderToPosition(playlist->current) << 16) + : 0) ^ + ((int)pc->GetCrossFade() << 20) ^ + (player_status.state << 24) ^ + (playlist->queue.random << 27) ^ + (playlist->queue.repeat << 28) ^ + (playlist->queue.single << 29) ^ + (playlist->queue.consume << 30) ^ + (playlist->queue.random << 31); +} diff --git a/src/PlaylistState.hxx b/src/PlaylistState.hxx new file mode 100644 index 000000000..7e9944789 --- /dev/null +++ b/src/PlaylistState.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Saving and loading the playlist to/from the state file. + * + */ + +#ifndef MPD_PLAYLIST_STATE_HXX +#define MPD_PLAYLIST_STATE_HXX + +#include <stdio.h> + +struct playlist; +struct player_control; +class TextFile; + +void +playlist_state_save(FILE *fp, const struct playlist *playlist, + struct player_control *pc); + +bool +playlist_state_restore(const char *line, TextFile &file, + struct playlist *playlist, struct player_control *pc); + +/** + * Generates a hash number for the current state of the playlist and + * the playback options. This is used by timer_save_state_file() to + * determine whether the state has changed and the state file should + * be saved. + */ +unsigned +playlist_state_get_hash(const struct playlist *playlist, + struct player_control *pc); + +#endif diff --git a/src/PlaylistVector.cxx b/src/PlaylistVector.cxx new file mode 100644 index 000000000..06c7b9ff0 --- /dev/null +++ b/src/PlaylistVector.cxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlaylistVector.hxx" +#include "DatabaseLock.hxx" + +#include <algorithm> + +#include <assert.h> +#include <string.h> +#include <glib.h> + +PlaylistVector::iterator +PlaylistVector::find(const char *name) +{ + assert(holding_db_lock()); + assert(name != NULL); + + return std::find_if(begin(), end(), + PlaylistInfo::CompareName(name)); +} + +bool +PlaylistVector::UpdateOrInsert(PlaylistInfo &&pi) +{ + assert(holding_db_lock()); + + auto i = find(pi.name.c_str()); + if (i != end()) { + if (pi.mtime == i->mtime) + return false; + + i->mtime = pi.mtime; + } else + push_back(std::move(pi)); + + return true; +} + +bool +PlaylistVector::erase(const char *name) +{ + assert(holding_db_lock()); + + auto i = find(name); + if (i == end()) + return false; + + erase(i); + return true; +} diff --git a/src/PlaylistVector.hxx b/src/PlaylistVector.hxx new file mode 100644 index 000000000..d10c90fda --- /dev/null +++ b/src/PlaylistVector.hxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_VECTOR_HXX +#define MPD_PLAYLIST_VECTOR_HXX + +#include "PlaylistInfo.hxx" +#include "gcc.h" + +#include <list> + +class PlaylistVector : protected std::list<PlaylistInfo> { +protected: + /** + * Caller must lock the #db_mutex. + */ + gcc_pure + iterator find(const char *name); + +public: + using std::list<PlaylistInfo>::empty; + using std::list<PlaylistInfo>::begin; + using std::list<PlaylistInfo>::end; + using std::list<PlaylistInfo>::push_back; + using std::list<PlaylistInfo>::erase; + + /** + * Caller must lock the #db_mutex. + * + * @return true if the vector or one of its items was modified + */ + bool UpdateOrInsert(PlaylistInfo &&pi); + + /** + * Caller must lock the #db_mutex. + */ + bool erase(const char *name); +}; + +#endif /* SONGVEC_H */ diff --git a/src/Queue.cxx b/src/Queue.cxx new file mode 100644 index 000000000..3fdb9ed1e --- /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 000000000..6e7786bcd --- /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 000000000..4a3e1312d --- /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 000000000..97b61e212 --- /dev/null +++ b/src/QueueCommands.hxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_QUEUE_COMMANDS_HXX +#define MPD_QUEUE_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_add(Client *client, int argc, char *argv[]); + +enum command_return +handle_addid(Client *client, int argc, char *argv[]); + +enum command_return +handle_delete(Client *client, int argc, char *argv[]); + +enum command_return +handle_deleteid(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlist(Client *client, int argc, char *argv[]); + +enum command_return +handle_shuffle(Client *client, int argc, char *argv[]); + +enum command_return +handle_clear(Client *client, int argc, char *argv[]); + +enum command_return +handle_plchanges(Client *client, int argc, char *argv[]); + +enum command_return +handle_plchangesposid(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistinfo(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistid(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistfind(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistsearch(Client *client, int argc, char *argv[]); + +enum command_return +handle_prio(Client *client, int argc, char *argv[]); + +enum command_return +handle_prioid(Client *client, int argc, char *argv[]); + +enum command_return +handle_move(Client *client, int argc, char *argv[]); + +enum command_return +handle_moveid(Client *client, int argc, char *argv[]); + +enum command_return +handle_swap(Client *client, int argc, char *argv[]); + +enum command_return +handle_swapid(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/QueuePrint.cxx b/src/QueuePrint.cxx new file mode 100644 index 000000000..28abc5a8c --- /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/QueuePrint.hxx b/src/QueuePrint.hxx new file mode 100644 index 000000000..6b3a29fb6 --- /dev/null +++ b/src/QueuePrint.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This library sends information about songs in the queue to the + * client. + */ + +#ifndef MPD_QUEUE_PRINT_HXX +#define MPD_QUEUE_PRINT_HXX + +#include <stdint.h> + +struct queue; +class SongFilter; +class Client; + +void +queue_print_info(Client *client, const struct queue *queue, + unsigned start, unsigned end); + +void +queue_print_uris(Client *client, const struct queue *queue, + unsigned start, unsigned end); + +void +queue_print_changes_info(Client *client, const struct queue *queue, + uint32_t version); + +void +queue_print_changes_position(Client *client, const struct queue *queue, + uint32_t version); + +void +queue_find(Client *client, const struct queue *queue, + const SongFilter &filter); + +#endif diff --git a/src/QueueSave.cxx b/src/QueueSave.cxx new file mode 100644 index 000000000..09b0645f8 --- /dev/null +++ b/src/QueueSave.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 "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 <stdlib.h> + +#define PRIO_LABEL "Prio: " + +static void +queue_save_database_song(FILE *fp, int idx, const struct song *song) +{ + char *uri = song_get_uri(song); + + fprintf(fp, "%i:%s\n", idx, uri); + g_free(uri); +} + +static void +queue_save_full_song(FILE *fp, const struct song *song) +{ + song_save(fp, song); +} + +static void +queue_save_song(FILE *fp, int idx, const struct song *song) +{ + if (song_in_database(song)) + queue_save_database_song(fp, idx, song); + else + queue_save_full_song(fp, song); +} + +void +queue_save(FILE *fp, const struct queue *queue) +{ + for (unsigned i = 0; i < queue->GetLength(); i++) { + uint8_t prio = queue->GetPriorityAtPosition(i); + if (prio != 0) + fprintf(fp, PRIO_LABEL "%u\n", prio); + + queue_save_song(fp, i, queue->Get(i)); + } +} + +void +queue_load_song(TextFile &file, const char *line, queue *queue) +{ + if (queue->IsFull()) + return; + + uint8_t priority = 0; + if (g_str_has_prefix(line, PRIO_LABEL)) { + priority = strtoul(line + sizeof(PRIO_LABEL) - 1, NULL, 10); + + line = file.ReadLine(); + if (line == NULL) + return; + } + + const Database *db = nullptr; + 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(file, NULL, uri, &error); + if (song == NULL) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + } else { + char *endptr; + long ret = strtol(line, &endptr, 10); + if (ret < 0 || *endptr != ':' || endptr[1] == 0) { + g_warning("Malformed playlist line in state file"); + return; + } + + const char *uri = endptr + 1; + + if (uri_has_scheme(uri)) { + song = song_remote_new(uri); + } else { + db = GetDatabase(nullptr); + if (db == nullptr) + return; + + song = db->GetSong(uri, nullptr); + if (song == nullptr) + return; + } + } + + queue->Append(song, priority); + + if (db != nullptr) + db->ReturnSong(song); +} diff --git a/src/QueueSave.hxx b/src/QueueSave.hxx new file mode 100644 index 000000000..25d7804fb --- /dev/null +++ b/src/QueueSave.hxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This library saves the queue into the state file, and also loads it + * back into memory. + */ + +#ifndef MPD_QUEUE_SAVE_HXX +#define MPD_QUEUE_SAVE_HXX + +#include <stdio.h> + +struct queue; +class TextFile; + +void +queue_save(FILE *fp, const struct queue *queue); + +/** + * Loads one song from the state file and appends it to the queue. + */ +void +queue_load_song(TextFile &file, const char *line, queue *queue); + +#endif diff --git a/src/ReplayGainConfig.cxx b/src/ReplayGainConfig.cxx new file mode 100644 index 000000000..d86c70053 --- /dev/null +++ b/src/ReplayGainConfig.cxx @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "replay_gain_config.h" +#include "Idle.hxx" +#include "conf.h" +#include "Playlist.hxx" +#include "mpd_error.h" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF; + +static constexpr bool DEFAULT_REPLAYGAIN_LIMIT = true; + +float replay_gain_preamp = 1.0; +float replay_gain_missing_preamp = 1.0; +bool replay_gain_limit = DEFAULT_REPLAYGAIN_LIMIT; + +const char * +replay_gain_get_mode_string(void) +{ + switch (replay_gain_mode) { + case REPLAY_GAIN_AUTO: + return "auto"; + + case REPLAY_GAIN_OFF: + return "off"; + + case REPLAY_GAIN_TRACK: + return "track"; + + case REPLAY_GAIN_ALBUM: + return "album"; + } + + /* unreachable */ + assert(false); + return "off"; +} + +bool +replay_gain_set_mode_string(const char *p) +{ + assert(p != NULL); + + if (strcmp(p, "off") == 0) + replay_gain_mode = REPLAY_GAIN_OFF; + else if (strcmp(p, "track") == 0) + replay_gain_mode = REPLAY_GAIN_TRACK; + else if (strcmp(p, "album") == 0) + replay_gain_mode = REPLAY_GAIN_ALBUM; + else if (strcmp(p, "auto") == 0) + replay_gain_mode = REPLAY_GAIN_AUTO; + else + return false; + + idle_add(IDLE_OPTIONS); + + return true; +} + +void replay_gain_global_init(void) +{ + const struct config_param *param = config_get_param(CONF_REPLAYGAIN); + + if (param != NULL && !replay_gain_set_mode_string(param->value)) { + MPD_ERROR("replaygain value \"%s\" at line %i is invalid\n", + param->value, param->line); + } + + param = config_get_param(CONF_REPLAYGAIN_PREAMP); + + if (param) { + char *test; + float f = strtod(param->value, &test); + + if (*test != '\0') { + MPD_ERROR("Replaygain preamp \"%s\" is not a number at " + "line %i\n", param->value, param->line); + } + + if (f < -15 || f > 15) { + MPD_ERROR("Replaygain preamp \"%s\" is not between -15 and" + "15 at line %i\n", param->value, param->line); + } + + replay_gain_preamp = pow(10, f / 20.0); + } + + param = config_get_param(CONF_REPLAYGAIN_MISSING_PREAMP); + + if (param) { + char *test; + float f = strtod(param->value, &test); + + if (*test != '\0') { + MPD_ERROR("Replaygain missing preamp \"%s\" is not a number at " + "line %i\n", param->value, param->line); + } + + if (f < -15 || f > 15) { + MPD_ERROR("Replaygain missing preamp \"%s\" is not between -15 and" + "15 at line %i\n", param->value, param->line); + } + + replay_gain_missing_preamp = pow(10, f / 20.0); + } + + replay_gain_limit = config_get_bool(CONF_REPLAYGAIN_LIMIT, DEFAULT_REPLAYGAIN_LIMIT); +} + +enum replay_gain_mode +replay_gain_get_real_mode(bool random_mode) +{ + enum replay_gain_mode rgm; + + rgm = replay_gain_mode; + + if (rgm == REPLAY_GAIN_AUTO) + rgm = random_mode ? REPLAY_GAIN_TRACK : REPLAY_GAIN_ALBUM; + + return rgm; +} diff --git a/src/ReplayGainInfo.cxx b/src/ReplayGainInfo.cxx new file mode 100644 index 000000000..b9d1b82c6 --- /dev/null +++ b/src/ReplayGainInfo.cxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "replay_gain_info.h" + +float +replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, float missing_preamp, bool peak_limit) +{ + float scale; + + if (replay_gain_tuple_defined(tuple)) { + scale = pow(10.0, tuple->gain / 20.0); + scale *= preamp; + if (scale > 15.0) + scale = 15.0; + + if (peak_limit && scale * tuple->peak > 1.0) + scale = 1.0 / tuple->peak; + } else + scale = missing_preamp; + + return scale; +} + +void +replay_gain_info_complete(struct replay_gain_info *info) +{ + if (!replay_gain_tuple_defined(&info->tuples[REPLAY_GAIN_ALBUM])) + info->tuples[REPLAY_GAIN_ALBUM] = + info->tuples[REPLAY_GAIN_TRACK]; +} diff --git a/src/SignalHandlers.cxx b/src/SignalHandlers.cxx new file mode 100644 index 000000000..d438eb703 --- /dev/null +++ b/src/SignalHandlers.cxx @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SignalHandlers.hxx" + +#ifndef WIN32 + +#include "Log.hxx" +#include "Main.hxx" +#include "event/Loop.hxx" +#include "GlobalEvents.hxx" +#include "mpd_error.h" + +#include <glib.h> + +#include <signal.h> +#include <errno.h> +#include <string.h> + +static void exit_signal_handler(G_GNUC_UNUSED int signum) +{ + GlobalEvents::Emit(GlobalEvents::SHUTDOWN); +} + +static void reload_signal_handler(G_GNUC_UNUSED int signum) +{ + GlobalEvents::Emit(GlobalEvents::RELOAD); +} + +static void +x_sigaction(int signum, const struct sigaction *act) +{ + if (sigaction(signum, act, NULL) < 0) + MPD_ERROR("sigaction() failed: %s", strerror(errno)); +} + +static void +handle_reload_event(void) +{ + g_debug("got SIGHUP, reopening log files"); + cycle_log_files(); +} + +#endif + +void initSigHandlers(void) +{ +#ifndef WIN32 + struct sigaction sa; + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + x_sigaction(SIGPIPE, &sa); + + sa.sa_handler = exit_signal_handler; + x_sigaction(SIGINT, &sa); + x_sigaction(SIGTERM, &sa); + + GlobalEvents::Register(GlobalEvents::RELOAD, handle_reload_event); + sa.sa_handler = reload_signal_handler; + x_sigaction(SIGHUP, &sa); +#endif +} diff --git a/src/SignalHandlers.hxx b/src/SignalHandlers.hxx new file mode 100644 index 000000000..99f347fb0 --- /dev/null +++ b/src/SignalHandlers.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SIGNAL_HANDLERS_HXX +#define MPD_SIGNAL_HANDLERS_HXX + +void initSigHandlers(void); + +#endif diff --git a/src/SocketError.hxx b/src/SocketError.hxx new file mode 100644 index 000000000..53a0c1d8f --- /dev/null +++ b/src/SocketError.hxx @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SOCKET_ERROR_HXX +#define MPD_SOCKET_ERROR_HXX + +#include "gcc.h" + +#include <glib.h> + +#ifdef WIN32 +#include <winsock2.h> +typedef DWORD socket_error_t; +#else +#include <errno.h> +typedef int socket_error_t; +#endif + +/** + * A GQuark for GError for socket I/O errors. The code is an errno + * value (or WSAGetLastError() on Windows). + */ +gcc_const +static inline GQuark +SocketErrorQuark(void) +{ + return g_quark_from_static_string("socket"); +} + +gcc_pure +static inline socket_error_t +GetSocketError() +{ +#ifdef WIN32 + return WSAGetLastError(); +#else + return errno; +#endif +} + +gcc_const +static inline bool +IsSocketErrorAgain(socket_error_t code) +{ +#ifdef WIN32 + return code == WSAEINPROGRESS; +#else + return code == EAGAIN; +#endif +} + +gcc_const +static inline bool +IsSocketErrorInterruped(socket_error_t code) +{ +#ifdef WIN32 + return code == WSAEINTR; +#else + return code == EINTR; +#endif +} + +gcc_const +static inline bool +IsSocketErrorClosed(socket_error_t code) +{ +#ifdef WIN32 + return code == WSAECONNRESET; +#else + return code == EPIPE || code == ECONNRESET; +#endif +} + +/** + * Helper class that formats a socket error message into a + * human-readable string. On Windows, a buffer is necessary for this, + * and this class hosts the buffer. + */ +class SocketErrorMessage { +#ifdef WIN32 + char msg[256]; +#else + const char *const msg; +#endif + +public: +#ifdef WIN32 + explicit SocketErrorMessage(socket_error_t code=GetSocketError()) { + DWORD nbytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, code, 0, + (LPSTR)msg, sizeof(msg), NULL); + if (nbytes == 0) + strcpy(msg, "Unknown error"); + } +#else + explicit SocketErrorMessage(socket_error_t code=GetSocketError()) + :msg(g_strerror(code)) {} +#endif + + operator const char *() const { + return msg; + } +}; + +static inline void +SetSocketError(GError **error_r, socket_error_t code) +{ +#ifdef WIN32 + if (error_r == NULL) + return; +#endif + + const SocketErrorMessage msg(code); + g_set_error_literal(error_r, SocketErrorQuark(), code, msg); +} + +static inline void +SetSocketError(GError **error_r) +{ + SetSocketError(error_r, GetSocketError()); +} + +gcc_malloc +static inline GError * +NewSocketError(socket_error_t code) +{ + const SocketErrorMessage msg(code); + return g_error_new_literal(SocketErrorQuark(), code, msg); +} + +gcc_malloc +static inline GError * +NewSocketError() +{ + return NewSocketError(GetSocketError()); +} + +#endif diff --git a/src/SocketUtil.cxx b/src/SocketUtil.cxx new file mode 100644 index 000000000..dd7eb7dd2 --- /dev/null +++ b/src/SocketUtil.cxx @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SocketUtil.hxx" +#include "SocketError.hxx" +#include "fd_util.h" + +#include <glib.h> + +#include <unistd.h> + +#ifndef G_OS_WIN32 +#include <sys/socket.h> +#else /* G_OS_WIN32 */ +#include <ws2tcpip.h> +#include <winsock.h> +#endif /* G_OS_WIN32 */ + +#ifdef HAVE_IPV6 +#include <string.h> +#endif + +int +socket_bind_listen(int domain, int type, int protocol, + const struct sockaddr *address, size_t address_length, + int backlog, + GError **error_r) +{ + int fd, ret; + const int reuse = 1; + + fd = socket_cloexec_nonblock(domain, type, protocol); + if (fd < 0) { + SetSocketError(error_r); + g_prefix_error(error_r, "Failed to create socket: "); + return -1; + } + + ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, + (const char *) &reuse, sizeof(reuse)); + if (ret < 0) { + SetSocketError(error_r); + g_prefix_error(error_r, "setsockopt() failed: "); + close_socket(fd); + return -1; + } + + ret = bind(fd, address, address_length); + if (ret < 0) { + SetSocketError(error_r); + close_socket(fd); + return -1; + } + + ret = listen(fd, backlog); + if (ret < 0) { + SetSocketError(error_r); + g_prefix_error(error_r, "listen() failed: "); + close_socket(fd); + return -1; + } + +#ifdef HAVE_STRUCT_UCRED + setsockopt(fd, SOL_SOCKET, SO_PASSCRED, + (const char *) &reuse, sizeof(reuse)); +#endif + + return fd; +} + +int +socket_keepalive(int fd) +{ + const int reuse = 1; + + return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, + (const char *)&reuse, sizeof(reuse)); +} diff --git a/src/SocketUtil.hxx b/src/SocketUtil.hxx new file mode 100644 index 000000000..3d0be78c4 --- /dev/null +++ b/src/SocketUtil.hxx @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This library provides easy helper functions for working with + * sockets. + * + */ + +#ifndef MPD_SOCKET_UTIL_HXX +#define MPD_SOCKET_UTIL_HXX + +#include "gerror.h" + +#include <stddef.h> + +struct sockaddr; + +/** + * Creates a socket listening on the specified address. This is a + * shortcut for socket(), bind() and listen(). + * + * @param domain the socket domain, e.g. PF_INET6 + * @param type the socket type, e.g. SOCK_STREAM + * @param protocol the protocol, usually 0 to let the kernel choose + * @param address the address to listen on + * @param address_length the size of #address + * @param backlog the backlog parameter for the listen() system call + * @param error location to store the error occurring, or NULL to + * ignore errors + * @return the socket file descriptor or -1 on error + */ +int +socket_bind_listen(int domain, int type, int protocol, + const struct sockaddr *address, size_t address_length, + int backlog, + GError **error); + +int +socket_keepalive(int fd); + +#endif diff --git a/src/Song.cxx b/src/Song.cxx new file mode 100644 index 000000000..4c820c3f8 --- /dev/null +++ b/src/Song.cxx @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "song.h" +#include "Directory.hxx" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> + +Directory detached_root; + +static struct song * +song_alloc(const char *uri, Directory *parent) +{ + size_t uri_length; + + assert(uri); + uri_length = strlen(uri); + assert(uri_length); + + 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; + song->start_ms = song->end_ms = 0; + + return song; +} + +struct song * +song_remote_new(const char *uri) +{ + return song_alloc(uri, nullptr); +} + +struct song * +song_file_new(const char *path, Directory *parent) +{ + assert((parent == nullptr) == (*path == '/')); + + return song_alloc(path, parent); +} + +struct song * +song_replace_uri(struct song *old_song, const char *uri) +{ + struct song *new_song = song_alloc(uri, old_song->parent); + new_song->tag = old_song->tag; + new_song->mtime = old_song->mtime; + new_song->start_ms = old_song->start_ms; + new_song->end_ms = old_song->end_ms; + g_free(old_song); + return new_song; +} + +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) +{ + if (song->tag) + tag_free(song->tag); + 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 != nullptr); + assert(*song->uri); + + if (!song_in_database(song) || song->parent->IsRoot()) + return g_strdup(song->uri); + else + return g_strconcat(song->parent->GetPath(), + "/", song->uri, nullptr); +} + +double +song_get_duration(const struct song *song) +{ + if (song->end_ms > 0) + return (song->end_ms - song->start_ms) / 1000.0; + + if (song->tag == 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 000000000..0e138386b --- /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 000000000..afec81300 --- /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/SongPointer.hxx b/src/SongPointer.hxx new file mode 100644 index 000000000..c80f96f4d --- /dev/null +++ b/src/SongPointer.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SONG_POINTER_HXX +#define MPD_SONG_POINTER_HXX + +#include "song.h" + +#include <utility> + +class SongPointer { + struct song *song; + +public: + explicit SongPointer(struct song *_song) + :song(_song) {} + + SongPointer(const SongPointer &) = delete; + + SongPointer(SongPointer &&other):song(other.song) { + other.song = nullptr; + } + + ~SongPointer() { + if (song != nullptr) + song_free(song); + } + + 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/SongPrint.cxx b/src/SongPrint.cxx new file mode 100644 index 000000000..126e5b9f4 --- /dev/null +++ b/src/SongPrint.cxx @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SongPrint.hxx" +#include "song.h" +#include "Directory.hxx" +#include "TimePrint.hxx" +#include "TagPrint.hxx" +#include "Mapper.hxx" +#include "Client.hxx" + +extern "C" { +#include "uri.h" +} + +#include <glib.h> + +void +song_print_uri(Client *client, struct song *song) +{ + if (song_in_database(song) && !song->parent->IsRoot()) { + client_printf(client, "%s%s/%s\n", SONG_FILE, + song->parent->GetPath(), song->uri); + } else { + char *allocated; + const char *uri; + + uri = allocated = uri_remove_auth(song->uri); + if (uri == NULL) + uri = song->uri; + + client_printf(client, "%s%s\n", SONG_FILE, + map_to_relative_path(uri)); + + g_free(allocated); + } +} + +void +song_print_info(Client *client, struct song *song) +{ + song_print_uri(client, song); + + if (song->end_ms > 0) + client_printf(client, "Range: %u.%03u-%u.%03u\n", + song->start_ms / 1000, + song->start_ms % 1000, + song->end_ms / 1000, + song->end_ms % 1000); + else if (song->start_ms > 0) + client_printf(client, "Range: %u.%03u-\n", + song->start_ms / 1000, + song->start_ms % 1000); + + if (song->mtime > 0) + 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 000000000..49f9478be --- /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/SongSave.cxx b/src/SongSave.cxx new file mode 100644 index 000000000..2b74d9354 --- /dev/null +++ b/src/SongSave.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 "SongSave.hxx" +#include "song.h" +#include "TagSave.hxx" +#include "Directory.hxx" +#include "TextFile.hxx" +#include "tag.h" + +extern "C" { +#include "string_util.h" +} + +#include <glib.h> + +#include <stdlib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "song" + +#define SONG_MTIME "mtime" +#define SONG_END "song_end" + +static GQuark +song_save_quark(void) +{ + return g_quark_from_static_string("song_save"); +} + +void +song_save(FILE *fp, const struct song *song) +{ + fprintf(fp, SONG_BEGIN "%s\n", song->uri); + + if (song->end_ms > 0) + fprintf(fp, "Range: %u-%u\n", song->start_ms, song->end_ms); + else if (song->start_ms > 0) + fprintf(fp, "Range: %u-\n", song->start_ms); + + if (song->tag != NULL) + tag_save(fp, song->tag); + + fprintf(fp, SONG_MTIME ": %li\n", (long)song->mtime); + fprintf(fp, SONG_END "\n"); +} + +struct song * +song_load(TextFile &file, Directory *parent, const char *uri, + GError **error_r) +{ + struct song *song = parent != NULL + ? song_file_new(uri, parent) + : song_remote_new(uri); + char *line, *colon; + enum tag_type type; + const char *value; + + while ((line = file.ReadLine()) != NULL && + strcmp(line, SONG_END) != 0) { + colon = strchr(line, ':'); + if (colon == NULL || colon == line) { + if (song->tag != NULL) + tag_end_add(song->tag); + song_free(song); + + g_set_error(error_r, song_save_quark(), 0, + "unknown line in db: %s", line); + return NULL; + } + + *colon++ = 0; + value = strchug_fast_c(colon); + + if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) { + if (!song->tag) { + song->tag = tag_new(); + tag_begin_add(song->tag); + } + + tag_add_item(song->tag, type, value); + } else if (strcmp(line, "Time") == 0) { + if (!song->tag) { + song->tag = tag_new(); + tag_begin_add(song->tag); + } + + song->tag->time = atoi(value); + } else if (strcmp(line, "Playlist") == 0) { + if (!song->tag) { + song->tag = tag_new(); + tag_begin_add(song->tag); + } + + song->tag->has_playlist = strcmp(value, "yes") == 0; + } else if (strcmp(line, SONG_MTIME) == 0) { + song->mtime = atoi(value); + } else if (strcmp(line, "Range") == 0) { + char *endptr; + + song->start_ms = strtoul(value, &endptr, 10); + if (*endptr == '-') + song->end_ms = strtoul(endptr + 1, NULL, 10); + } else { + if (song->tag != NULL) + tag_end_add(song->tag); + song_free(song); + + g_set_error(error_r, song_save_quark(), 0, + "unknown line in db: %s", line); + return NULL; + } + } + + if (song->tag != NULL) + tag_end_add(song->tag); + + return song; +} diff --git a/src/SongSave.hxx b/src/SongSave.hxx new file mode 100644 index 000000000..3b0c3319c --- /dev/null +++ b/src/SongSave.hxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SONG_SAVE_HXX +#define MPD_SONG_SAVE_HXX + +#include "gerror.h" + +#include <stdio.h> + +#define SONG_BEGIN "song_begin: " + +struct song; +struct Directory; +class TextFile; + +void +song_save(FILE *fp, const struct song *song); + +/** + * Loads a song from the input file. Reading stops after the + * "song_end" line. + * + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return true on success, false on error + */ +struct song * +song_load(TextFile &file, Directory *parent, const char *uri, + GError **error_r); + +#endif diff --git a/src/SongSticker.cxx b/src/SongSticker.cxx new file mode 100644 index 000000000..86385fc33 --- /dev/null +++ b/src/SongSticker.cxx @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SongSticker.hxx" +#include "StickerDatabase.hxx" +#include "song.h" +#include "Directory.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +char * +sticker_song_get_value(const struct song *song, const char *name) +{ + char *uri, *value; + + assert(song != NULL); + assert(song_in_database(song)); + + uri = song_get_uri(song); + value = sticker_load_value("song", uri, name); + g_free(uri); + + return value; +} + +bool +sticker_song_set_value(const struct song *song, + const char *name, const char *value) +{ + char *uri; + bool ret; + + assert(song != NULL); + assert(song_in_database(song)); + + uri = song_get_uri(song); + ret = sticker_store_value("song", uri, name, value); + g_free(uri); + + return ret; +} + +bool +sticker_song_delete(const struct song *song) +{ + char *uri; + bool ret; + + assert(song != NULL); + assert(song_in_database(song)); + + uri = song_get_uri(song); + ret = sticker_delete("song", uri); + g_free(uri); + + return ret; +} + +bool +sticker_song_delete_value(const struct song *song, const char *name) +{ + char *uri; + bool success; + + assert(song != NULL); + assert(song_in_database(song)); + + uri = song_get_uri(song); + success = sticker_delete_value("song", uri, name); + g_free(uri); + + return success; +} + +struct sticker * +sticker_song_get(const struct song *song) +{ + char *uri; + struct sticker *sticker; + + assert(song != NULL); + assert(song_in_database(song)); + + uri = song_get_uri(song); + sticker = sticker_load("song", uri); + g_free(uri); + + return sticker; +} + +struct sticker_song_find_data { + Directory *directory; + const char *base_uri; + size_t base_uri_length; + + void (*func)(struct song *song, const char *value, + void *user_data); + void *user_data; +}; + +static void +sticker_song_find_cb(const char *uri, const char *value, void *user_data) +{ + struct sticker_song_find_data *data = + (struct sticker_song_find_data *)user_data; + + if (memcmp(uri, data->base_uri, data->base_uri_length) != 0) + /* should not happen, ignore silently */ + return; + + song *song = data->directory->LookupSong(uri + data->base_uri_length); + if (song != NULL) + data->func(song, value, data->user_data); +} + +bool +sticker_song_find(Directory *directory, const char *name, + void (*func)(struct song *song, const char *value, + void *user_data), + void *user_data) +{ + struct sticker_song_find_data data; + data.directory = directory; + data.func = func; + data.user_data = user_data; + + char *allocated; + data.base_uri = directory->GetPath(); + if (*data.base_uri != 0) + /* append slash to base_uri */ + data.base_uri = allocated = + g_strconcat(data.base_uri, "/", NULL); + else + /* searching in root directory - no trailing slash */ + allocated = NULL; + + data.base_uri_length = strlen(data.base_uri); + + bool success = sticker_find("song", data.base_uri, name, + sticker_song_find_cb, &data); + g_free(allocated); + + return success; +} diff --git a/src/SongSticker.hxx b/src/SongSticker.hxx new file mode 100644 index 000000000..07539b400 --- /dev/null +++ b/src/SongSticker.hxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SONG_STICKER_HXX +#define MPD_SONG_STICKER_HXX + +#include "gerror.h" + +struct song; +struct Directory; +struct sticker; + +/** + * Returns one value from a song's sticker record. The caller must + * free the return value with g_free(). + */ +char * +sticker_song_get_value(const struct song *song, const char *name); + +/** + * Sets a sticker value in the specified song. Overwrites existing + * values. + */ +bool +sticker_song_set_value(const struct song *song, + const char *name, const char *value); + +/** + * Deletes a sticker from the database. All values are deleted. + */ +bool +sticker_song_delete(const struct song *song); + +/** + * Deletes a sticker value. Does nothing if the sticker did not + * exist. + */ +bool +sticker_song_delete_value(const struct song *song, const char *name); + +/** + * Loads the sticker for the specified song. + * + * @param song the song object + * @return a sticker object, or NULL on error or if there is no sticker + */ +struct sticker * +sticker_song_get(const struct song *song); + +/** + * Finds stickers with the specified name below the specified + * directory. + * + * Caller must lock the #db_mutex. + * + * @param directory the base directory to search in + * @param name the name of the sticker + * @return true on success (even if no sticker was found), false on + * failure + */ +bool +sticker_song_find(Directory *directory, const char *name, + void (*func)(struct song *song, const char *value, + void *user_data), + void *user_data); + +#endif diff --git a/src/SongUpdate.cxx b/src/SongUpdate.cxx new file mode 100644 index 000000000..186512ffe --- /dev/null +++ b/src/SongUpdate.cxx @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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 */ + +extern "C" { +#include "song.h" +#include "uri.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_handler.h" +} + +#include <glib.h> + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> + +struct song * +song_file_load(const char *path_utf8, Directory *parent) +{ + struct song *song; + bool ret; + + assert((parent == NULL) == g_path_is_absolute(path_utf8)); + assert(!uri_has_scheme(path_utf8)); + assert(strchr(path_utf8, '\n') == NULL); + + song = song_file_new(path_utf8, parent); + + //in archive ? + if (parent != NULL && parent->device == DEVICE_INARCHIVE) { + ret = song_file_update_inarchive(song); + } else { + ret = song_file_update(song); + } + if (!ret) { + song_free(song); + return NULL; + } + + return song; +} + +/** + * Attempts to load APE or ID3 tags from the specified file. + */ +static bool +tag_scan_fallback(const char *path, + const struct tag_handler *handler, void *handler_ctx) +{ + return tag_ape_scan2(path, handler, handler_ctx) || + tag_id3_scan(path, handler, handler_ctx); +} + +bool +song_file_update(struct song *song) +{ + const char *suffix; + const struct decoder_plugin *plugin; + struct stat st; + struct input_stream *is = NULL; + + assert(song_is_file(song)); + + /* check if there's a suffix and a plugin */ + + suffix = uri_get_suffix(song->uri); + if (suffix == NULL) + return false; + + plugin = decoder_plugin_from_suffix(suffix, NULL); + if (plugin == NULL) + return false; + + const Path path_fs = map_song_fs(song); + if (path_fs.IsNull()) + return false; + + if (song->tag != NULL) { + tag_free(song->tag); + song->tag = NULL; + } + + if (!StatFile(path_fs, st) || !S_ISREG(st.st_mode)) { + return false; + } + + song->mtime = st.st_mtime; + + Mutex mutex; + Cond cond; + + do { + /* load file tag */ + song->tag = tag_new(); + if (decoder_plugin_scan_file(plugin, path_fs.c_str(), + &full_tag_handler, song->tag)) + break; + + tag_free(song->tag); + song->tag = NULL; + + /* fall back to stream tag */ + if (plugin->scan_stream != NULL) { + /* open the input_stream (if not already + open) */ + if (is == NULL) { + is = input_stream_open(path_fs.c_str(), + mutex, cond, + NULL); + } + + /* now try the stream_tag() method */ + if (is != NULL) { + song->tag = tag_new(); + if (decoder_plugin_scan_stream(plugin, is, + &full_tag_handler, + song->tag)) + break; + + tag_free(song->tag); + song->tag = NULL; + + input_stream_lock_seek(is, 0, SEEK_SET, NULL); + } + } + + plugin = decoder_plugin_from_suffix(suffix, plugin); + } while (plugin != NULL); + + if (is != NULL) + input_stream_close(is); + + if (song->tag != NULL && tag_is_empty(song->tag)) + tag_scan_fallback(path_fs.c_str(), &full_tag_handler, + song->tag); + + return song->tag != NULL; +} + +bool +song_file_update_inarchive(struct song *song) +{ + const char *suffix; + const struct decoder_plugin *plugin; + + assert(song_is_file(song)); + + /* check if there's a suffix and a plugin */ + + suffix = uri_get_suffix(song->uri); + if (suffix == NULL) + return false; + + plugin = decoder_plugin_from_suffix(suffix, nullptr); + if (plugin == NULL) + return false; + + if (song->tag != NULL) + tag_free(song->tag); + + //accept every file that has music suffix + //because we don't support tag reading through + //input streams + song->tag = tag_new(); + + return true; +} diff --git a/src/StateFile.cxx b/src/StateFile.cxx new file mode 100644 index 000000000..a15eb7220 --- /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/StateFile.hxx b/src/StateFile.hxx new file mode 100644 index 000000000..9ec97d518 --- /dev/null +++ b/src/StateFile.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_STATE_FILE_HXX +#define MPD_STATE_FILE_HXX + +#include "event/TimeoutMonitor.hxx" +#include "fs/Path.hxx" +#include "gcc.h" + +#include <string> + +struct Partition; + +class StateFile final : private TimeoutMonitor { + Path path; + std::string path_utf8; + + Partition &partition; + + /** + * These version numbers determine whether we need to save the state + * file. If nothing has changed, we won't let the hard drive spin up. + */ + unsigned prev_volume_version, prev_output_version, + prev_playlist_version; + +public: + StateFile(Path &&path, const char *path_utf8, + Partition &partition, EventLoop &loop); + + void Read(); + void Write(); + 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 000000000..354d26c59 --- /dev/null +++ b/src/Stats.cxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +extern "C" { +#include "stats.h" +} + +#include "PlayerControl.hxx" +#include "ClientInternal.hxx" +#include "DatabaseSelection.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseSimple.hxx" + +struct stats stats; + +void stats_global_init(void) +{ + stats.timer = g_timer_new(); +} + +void stats_global_finish(void) +{ + g_timer_destroy(stats.timer); +} + +void stats_update(void) +{ + GError *error = nullptr; + + DatabaseStats stats2; + + const DatabaseSelection selection("", true); + if (GetDatabase()->GetStats(selection, stats2, &error)) { + stats.song_count = stats2.song_count; + stats.song_duration = stats2.total_duration; + stats.artist_count = stats2.artist_count; + stats.album_count = stats2.album_count; + } else { + g_warning("%s", error->message); + g_error_free(error); + + stats.song_count = 0; + stats.song_duration = 0; + stats.artist_count = 0; + stats.album_count = 0; + } +} + +void +stats_print(Client *client) +{ + client_printf(client, + "artists: %u\n" + "albums: %u\n" + "songs: %i\n" + "uptime: %li\n" + "playtime: %li\n" + "db_playtime: %li\n", + stats.artist_count, + stats.album_count, + stats.song_count, + (long)g_timer_elapsed(stats.timer, NULL), + (long)(client->player_control->GetTotalPlayTime() + 0.5), + stats.song_duration); + + if (db_is_simple()) + client_printf(client, + "db_update: %li\n", + (long)db_get_mtime()); +} diff --git a/src/StickerCommands.cxx b/src/StickerCommands.cxx new file mode 100644 index 000000000..d13647c33 --- /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 000000000..840bd33d5 --- /dev/null +++ b/src/StickerCommands.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_STICKER_COMMANDS_HXX +#define MPD_STICKER_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_sticker(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/StickerDatabase.cxx b/src/StickerDatabase.cxx new file mode 100644 index 000000000..2d77e4b63 --- /dev/null +++ b/src/StickerDatabase.cxx @@ -0,0 +1,631 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "StickerDatabase.hxx" +#include "Idle.hxx" + +#include <string> +#include <map> + +#include <glib.h> +#include <sqlite3.h> +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "sticker" + +#if SQLITE_VERSION_NUMBER < 3003009 +#define sqlite3_prepare_v2 sqlite3_prepare +#endif + +struct sticker { + std::map<std::string, std::string> table; +}; + +enum sticker_sql { + STICKER_SQL_GET, + STICKER_SQL_LIST, + STICKER_SQL_UPDATE, + STICKER_SQL_INSERT, + STICKER_SQL_DELETE, + STICKER_SQL_DELETE_VALUE, + STICKER_SQL_FIND, +}; + +static const char *const sticker_sql[] = { + //[STICKER_SQL_GET] = + "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?", + //[STICKER_SQL_LIST] = + "SELECT name,value FROM sticker WHERE type=? AND uri=?", + //[STICKER_SQL_UPDATE] = + "UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?", + //[STICKER_SQL_INSERT] = + "INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)", + //[STICKER_SQL_DELETE] = + "DELETE FROM sticker WHERE type=? AND uri=?", + //[STICKER_SQL_DELETE_VALUE] = + "DELETE FROM sticker WHERE type=? AND uri=? AND name=?", + //[STICKER_SQL_FIND] = + "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?", +}; + +static const char sticker_sql_create[] = + "CREATE TABLE IF NOT EXISTS sticker(" + " type VARCHAR NOT NULL, " + " uri VARCHAR NOT NULL, " + " name VARCHAR NOT NULL, " + " value VARCHAR NOT NULL" + ");" + "CREATE UNIQUE INDEX IF NOT EXISTS" + " sticker_value ON sticker(type, uri, name);" + ""; + +static sqlite3 *sticker_db; +static sqlite3_stmt *sticker_stmt[G_N_ELEMENTS(sticker_sql)]; + +static GQuark +sticker_quark(void) +{ + return g_quark_from_static_string("sticker"); +} + +static sqlite3_stmt * +sticker_prepare(const char *sql, GError **error_r) +{ + int ret; + sqlite3_stmt *stmt; + + ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, NULL); + if (ret != SQLITE_OK) { + g_set_error(error_r, sticker_quark(), ret, + "sqlite3_prepare_v2() failed: %s", + sqlite3_errmsg(sticker_db)); + return NULL; + } + + return stmt; +} + +bool +sticker_global_init(const char *path, GError **error_r) +{ + int ret; + + if (path == NULL) + /* not configured */ + return true; + + /* open/create the sqlite database */ + + ret = sqlite3_open(path, &sticker_db); + if (ret != SQLITE_OK) { + g_set_error(error_r, sticker_quark(), ret, + "Failed to open sqlite database '%s': %s", + path, sqlite3_errmsg(sticker_db)); + return false; + } + + /* create the table and index */ + + ret = sqlite3_exec(sticker_db, sticker_sql_create, NULL, NULL, NULL); + if (ret != SQLITE_OK) { + g_set_error(error_r, sticker_quark(), ret, + "Failed to create sticker table: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + /* prepare the statements we're going to use */ + + for (unsigned i = 0; i < G_N_ELEMENTS(sticker_sql); ++i) { + assert(sticker_sql[i] != NULL); + + sticker_stmt[i] = sticker_prepare(sticker_sql[i], error_r); + if (sticker_stmt[i] == NULL) + return false; + } + + return true; +} + +void +sticker_global_finish(void) +{ + if (sticker_db == NULL) + /* not configured */ + return; + + for (unsigned i = 0; i < G_N_ELEMENTS(sticker_stmt); ++i) { + assert(sticker_stmt[i] != NULL); + + sqlite3_finalize(sticker_stmt[i]); + } + + sqlite3_close(sticker_db); +} + +bool +sticker_enabled(void) +{ + return sticker_db != NULL; +} + +char * +sticker_load_value(const char *type, const char *uri, const char *name) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET]; + int ret; + char *value; + + assert(sticker_enabled()); + assert(type != NULL); + assert(uri != NULL); + assert(name != NULL); + + if (*name == 0) + return NULL; + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return NULL; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return NULL; + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return NULL; + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + if (ret == SQLITE_ROW) { + /* record found */ + value = g_strdup((const char*)sqlite3_column_text(stmt, 0)); + } else if (ret == SQLITE_DONE) { + /* no record found */ + value = NULL; + } else { + /* error */ + g_warning("sqlite3_step() failed: %s", + sqlite3_errmsg(sticker_db)); + return NULL; + } + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + return value; +} + +static bool +sticker_list_values(std::map<std::string, std::string> &table, + const char *type, const char *uri) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_LIST]; + int ret; + + assert(type != NULL); + assert(uri != NULL); + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + do { + ret = sqlite3_step(stmt); + switch (ret) { + const char *name, *value; + + case SQLITE_ROW: + name = (const char*)sqlite3_column_text(stmt, 0); + value = (const char*)sqlite3_column_text(stmt, 1); + + table.insert(std::make_pair(name, value)); + break; + case SQLITE_DONE: + break; + case SQLITE_BUSY: + /* no op */ + break; + default: + g_warning("sqlite3_step() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + } while (ret != SQLITE_DONE); + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + return true; +} + +static bool +sticker_update_value(const char *type, const char *uri, + const char *name, const char *value) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_UPDATE]; + int ret; + + assert(type != NULL); + assert(uri != NULL); + assert(name != NULL); + assert(*name != 0); + assert(value != NULL); + + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, value, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, type, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, uri, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + ret = sqlite3_bind_text(stmt, 4, name, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + if (ret != SQLITE_DONE) { + g_warning("sqlite3_step() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + ret = sqlite3_changes(sticker_db); + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + idle_add(IDLE_STICKER); + return ret > 0; +} + +static bool +sticker_insert_value(const char *type, const char *uri, + const char *name, const char *value) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_INSERT]; + int ret; + + assert(type != NULL); + assert(uri != NULL); + assert(name != NULL); + assert(*name != 0); + assert(value != NULL); + + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + ret = sqlite3_bind_text(stmt, 4, value, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + if (ret != SQLITE_DONE) { + g_warning("sqlite3_step() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + + idle_add(IDLE_STICKER); + return true; +} + +bool +sticker_store_value(const char *type, const char *uri, + const char *name, const char *value) +{ + assert(sticker_enabled()); + assert(type != NULL); + assert(uri != NULL); + assert(name != NULL); + assert(value != NULL); + + if (*name == 0) + return false; + + return sticker_update_value(type, uri, name, value) || + sticker_insert_value(type, uri, name, value); +} + +bool +sticker_delete(const char *type, const char *uri) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE]; + int ret; + + assert(sticker_enabled()); + assert(type != NULL); + assert(uri != NULL); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + if (ret != SQLITE_DONE) { + g_warning("sqlite3_step() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + idle_add(IDLE_STICKER); + return true; +} + +bool +sticker_delete_value(const char *type, const char *uri, const char *name) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE_VALUE]; + int ret; + + assert(sticker_enabled()); + assert(type != NULL); + assert(uri != NULL); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + if (ret != SQLITE_DONE) { + g_warning("sqlite3_step() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + ret = sqlite3_changes(sticker_db); + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + idle_add(IDLE_STICKER); + return ret > 0; +} + +void +sticker_free(struct sticker *sticker) +{ + delete sticker; +} + +const char * +sticker_get_value(const struct sticker *sticker, const char *name) +{ + auto i = sticker->table.find(name); + if (i == sticker->table.end()) + return nullptr; + + return i->second.c_str(); +} + +void +sticker_foreach(const struct sticker *sticker, + void (*func)(const char *name, const char *value, + gpointer user_data), + gpointer user_data) +{ + for (const auto &i : sticker->table) + func(i.first.c_str(), i.second.c_str(), user_data); +} + +struct sticker * +sticker_load(const char *type, const char *uri) +{ + sticker s; + + if (!sticker_list_values(s.table, type, uri)) + return NULL; + + if (s.table.empty()) + /* don't return empty sticker objects */ + return NULL; + + return new sticker(std::move(s)); +} + +bool +sticker_find(const char *type, const char *base_uri, const char *name, + void (*func)(const char *uri, const char *value, + gpointer user_data), + gpointer user_data) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND]; + int ret; + + assert(type != NULL); + assert(name != NULL); + assert(func != NULL); + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + if (base_uri == NULL) + base_uri = ""; + + ret = sqlite3_bind_text(stmt, 2, base_uri, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); + if (ret != SQLITE_OK) { + g_warning("sqlite3_bind_text() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + do { + ret = sqlite3_step(stmt); + switch (ret) { + case SQLITE_ROW: + func((const char*)sqlite3_column_text(stmt, 0), + (const char*)sqlite3_column_text(stmt, 1), + user_data); + break; + case SQLITE_DONE: + break; + case SQLITE_BUSY: + /* no op */ + break; + default: + g_warning("sqlite3_step() failed: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + } while (ret != SQLITE_DONE); + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + return true; +} diff --git a/src/StickerDatabase.hxx b/src/StickerDatabase.hxx new file mode 100644 index 000000000..90ff9b066 --- /dev/null +++ b/src/StickerDatabase.hxx @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This is the sticker database library. It is the backend of all the + * sticker code in MPD. + * + * "Stickers" are pieces of information attached to existing MPD + * objects (e.g. song files, directories, albums). Clients can create + * arbitrary name/value pairs. MPD itself does not assume any special + * meaning in them. + * + * The goal is to allow clients to share additional (possibly dynamic) + * information about songs, which is neither stored on the client (not + * available to other clients), nor stored in the song files (MPD has + * no write access). + * + * Client developers should create a standard for common sticker + * names, to ensure interoperability. + * + * Examples: song ratings; statistics; deferred tag writes; lyrics; + * ... + * + */ + +#ifndef MPD_STICKER_DATABASE_HXX +#define MPD_STICKER_DATABASE_HXX + +#include "gerror.h" + +struct sticker; + +/** + * Opens the sticker database (if path is not NULL). + * + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return true on success, false on error + */ +bool +sticker_global_init(const char *path, GError **error_r); + +/** + * Close the sticker database. + */ +void +sticker_global_finish(void); + +/** + * Returns true if the sticker database is configured and available. + */ +bool +sticker_enabled(void); + +/** + * Returns one value from an object's sticker record. The caller must + * free the return value with g_free(). + */ +char * +sticker_load_value(const char *type, const char *uri, const char *name); + +/** + * Sets a sticker value in the specified object. Overwrites existing + * values. + */ +bool +sticker_store_value(const char *type, const char *uri, + const char *name, const char *value); + +/** + * Deletes a sticker from the database. All sticker values of the + * specified object are deleted. + */ +bool +sticker_delete(const char *type, const char *uri); + +/** + * Deletes a sticker value. Fails if no sticker with this name + * exists. + */ +bool +sticker_delete_value(const char *type, const char *uri, const char *name); + +/** + * Frees resources held by the sticker object. + * + * @param sticker the sticker object to be freed + */ +void +sticker_free(struct sticker *sticker); + +/** + * Determines a single value in a sticker. + * + * @param sticker the sticker object + * @param name the name of the sticker + * @return the sticker value, or NULL if none was found + */ +const char * +sticker_get_value(const struct sticker *sticker, const char *name); + +/** + * Iterates over all sticker items in a sticker. + * + * @param sticker the sticker object + * @param func a callback function + * @param user_data an opaque pointer for the callback function + */ +void +sticker_foreach(const struct sticker *sticker, + void (*func)(const char *name, const char *value, + void *user_data), + void *user_data); + +/** + * Loads the sticker for the specified resource. + * + * @param type the resource type, e.g. "song" + * @param uri the URI of the resource, e.g. the song path + * @return a sticker object, or NULL on error or if there is no sticker + */ +struct sticker * +sticker_load(const char *type, const char *uri); + +/** + * Finds stickers with the specified name below the specified URI. + * + * @param type the resource type, e.g. "song" + * @param base_uri the URI prefix of the resources, or NULL if all + * resources should be searched + * @param name the name of the sticker + * @return true on success (even if no sticker was found), false on + * failure + */ +bool +sticker_find(const char *type, const char *base_uri, const char *name, + void (*func)(const char *uri, const char *value, + void *user_data), + void *user_data); + +#endif diff --git a/src/StickerPrint.cxx b/src/StickerPrint.cxx new file mode 100644 index 000000000..1708ee4f0 --- /dev/null +++ b/src/StickerPrint.cxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "StickerPrint.hxx" +#include "StickerDatabase.hxx" +#include "Client.hxx" + +void +sticker_print_value(Client *client, + const char *name, const char *value) +{ + client_printf(client, "sticker: %s=%s\n", name, value); +} + +static void +print_sticker_cb(const char *name, const char *value, void *data) +{ + Client *client = (Client *)data; + + sticker_print_value(client, name, value); +} + +void +sticker_print(Client *client, const struct sticker *sticker) +{ + sticker_foreach(sticker, print_sticker_cb, client); +} diff --git a/src/StickerPrint.hxx b/src/StickerPrint.hxx new file mode 100644 index 000000000..27225a494 --- /dev/null +++ b/src/StickerPrint.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_STICKER_PRINT_HXX +#define MPD_STICKER_PRINT_HXX + +struct sticker; +class Client; + +/** + * Sends one sticker value to the client. + */ +void +sticker_print_value(Client *client, const char *name, const char *value); + +/** + * Sends all sticker values to the client. + */ +void +sticker_print(Client *client, const struct sticker *sticker); + +#endif diff --git a/src/Tag.cxx b/src/Tag.cxx new file mode 100644 index 000000000..afdeb0558 --- /dev/null +++ b/src/Tag.cxx @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "tag.h" +#include "TagInternal.hxx" +#include "TagPool.hxx" +#include "conf.h" +#include "song.h" +#include "mpd_error.h" + +#include <glib.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> + +/** + * Maximum number of items managed in the bulk list; if it is + * exceeded, we switch back to "normal" reallocation. + */ +#define BULK_MAX 64 + +static struct { +#ifndef NDEBUG + bool busy; +#endif + struct tag_item *items[BULK_MAX]; +} bulk; + +bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; + +enum tag_type +tag_name_parse(const char *name) +{ + assert(name != nullptr); + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + assert(tag_item_names[i] != nullptr); + + if (strcmp(name, tag_item_names[i]) == 0) + return (enum tag_type)i; + } + + return TAG_NUM_OF_ITEM_TYPES; +} + +enum tag_type +tag_name_parse_i(const char *name) +{ + assert(name != nullptr); + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + assert(tag_item_names[i] != nullptr); + + if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0) + return (enum tag_type)i; + } + + return TAG_NUM_OF_ITEM_TYPES; +} + +static size_t items_size(const struct tag *tag) +{ + return tag->num_items * sizeof(struct tag_item *); +} + +void tag_lib_init(void) +{ + const char *value; + int quit = 0; + char *temp; + char *s; + char *c; + enum tag_type type; + + /* parse the "metadata_to_use" config parameter below */ + + /* ignore comments by default */ + ignore_tag_items[TAG_COMMENT] = true; + + value = config_get_string(CONF_METADATA_TO_USE, nullptr); + if (value == nullptr) + return; + + memset(ignore_tag_items, true, TAG_NUM_OF_ITEM_TYPES); + + if (0 == g_ascii_strcasecmp(value, "none")) + return; + + temp = c = s = g_strdup(value); + while (!quit) { + if (*s == ',' || *s == '\0') { + if (*s == '\0') + quit = 1; + *s = '\0'; + + c = g_strstrip(c); + if (*c == 0) + continue; + + type = tag_name_parse_i(c); + if (type == TAG_NUM_OF_ITEM_TYPES) + MPD_ERROR("error parsing metadata item \"%s\"", + c); + + ignore_tag_items[type] = false; + + s++; + c = s; + } + s++; + } + + g_free(temp); +} + +struct tag *tag_new(void) +{ + struct tag *ret = g_new(struct tag, 1); + ret->items = nullptr; + ret->time = -1; + ret->has_playlist = false; + ret->num_items = 0; + return ret; +} + +static void tag_delete_item(struct tag *tag, unsigned idx) +{ + assert(idx < tag->num_items); + tag->num_items--; + + tag_pool_lock.lock(); + tag_pool_put_item(tag->items[idx]); + tag_pool_lock.unlock(); + + if (tag->num_items - idx > 0) { + memmove(tag->items + idx, tag->items + idx + 1, + (tag->num_items - idx) * sizeof(tag->items[0])); + } + + if (tag->num_items > 0) { + tag->items = (struct tag_item **) + g_realloc(tag->items, items_size(tag)); + } else { + g_free(tag->items); + tag->items = nullptr; + } +} + +void tag_clear_items_by_type(struct tag *tag, enum tag_type type) +{ + for (unsigned i = 0; i < tag->num_items; i++) { + if (tag->items[i]->type == type) { + tag_delete_item(tag, i); + /* decrement since when just deleted this node */ + i--; + } + } +} + +void tag_free(struct tag *tag) +{ + int i; + + assert(tag != nullptr); + + tag_pool_lock.lock(); + for (i = tag->num_items; --i >= 0; ) + tag_pool_put_item(tag->items[i]); + tag_pool_lock.unlock(); + + if (tag->items == bulk.items) { +#ifndef NDEBUG + assert(bulk.busy); + bulk.busy = false; +#endif + } else + g_free(tag->items); + + g_free(tag); +} + +struct tag *tag_dup(const struct tag *tag) +{ + struct tag *ret; + + if (!tag) + return 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 + ? (struct tag_item **)g_malloc(items_size(tag)) + : nullptr; + + tag_pool_lock.lock(); + for (unsigned i = 0; i < tag->num_items; i++) + ret->items[i] = tag_pool_dup_item(tag->items[i]); + tag_pool_lock.unlock(); + + return ret; +} + +struct tag * +tag_merge(const struct tag *base, const struct tag *add) +{ + struct tag *ret; + unsigned n; + + 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 + ? (struct tag_item **)g_malloc(items_size(ret)) + : nullptr; + + tag_pool_lock.lock(); + + /* copy all items from "add" */ + + for (unsigned i = 0; i < add->num_items; ++i) + ret->items[i] = tag_pool_dup_item(add->items[i]); + + n = add->num_items; + + /* copy additional items from "base" */ + + for (unsigned i = 0; i < base->num_items; ++i) + if (!tag_has_type(add, base->items[i]->type)) + ret->items[n++] = tag_pool_dup_item(base->items[i]); + + tag_pool_lock.unlock(); + + assert(n <= ret->num_items); + + if (n < ret->num_items) { + /* some tags were not copied - shrink ret->items */ + assert(n > 0); + + ret->num_items = n; + ret->items = (struct tag_item **) + g_realloc(ret->items, items_size(ret)); + } + + return ret; +} + +struct tag * +tag_merge_replace(struct tag *base, struct tag *add) +{ + if (add == nullptr) + return base; + + if (base == nullptr) + return add; + + struct tag *tag = tag_merge(base, add); + tag_free(base); + tag_free(add); + + return tag; +} + +const char * +tag_get_value(const struct tag *tag, enum tag_type type) +{ + assert(tag != 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 nullptr; +} + +bool tag_has_type(const struct tag *tag, enum tag_type type) +{ + return tag_get_value(tag, type) != nullptr; +} + +bool tag_equal(const struct tag *tag1, const struct tag *tag2) +{ + if (tag1 == nullptr && tag2 == nullptr) + return true; + else if (!tag1 || !tag2) + return false; + + if (tag1->time != tag2->time) + return false; + + if (tag1->num_items != tag2->num_items) + return false; + + for (unsigned i = 0; i < tag1->num_items; i++) { + if (tag1->items[i]->type != tag2->items[i]->type) + return false; + if (strcmp(tag1->items[i]->value, tag2->items[i]->value)) { + return false; + } + } + + return true; +} + +/** + * Replace invalid sequences with the question mark. + */ +static char * +patch_utf8(const char *src, size_t length, const gchar *end) +{ + /* duplicate the string, and replace invalid bytes in that + buffer */ + char *dest = g_strdup(src); + + do { + dest[end - src] = '?'; + } while (!g_utf8_validate(end + 1, (src + length) - (end + 1), &end)); + + return dest; +} + +static char * +fix_utf8(const char *str, size_t length) +{ + const gchar *end; + char *temp; + gsize written; + + assert(str != nullptr); + + /* check if the string is already valid UTF-8 */ + if (g_utf8_validate(str, length, &end)) + return nullptr; + + /* no, it's not - try to import it from ISO-Latin-1 */ + temp = g_convert(str, length, "utf-8", "iso-8859-1", + nullptr, &written, nullptr); + if (temp != nullptr) + /* success! */ + return temp; + + /* no, still broken - there's no medication, just patch + invalid sequences */ + return patch_utf8(str, length, end); +} + +void tag_begin_add(struct tag *tag) +{ + assert(!bulk.busy); + assert(tag != nullptr); + assert(tag->items == nullptr); + assert(tag->num_items == 0); + +#ifndef NDEBUG + bulk.busy = true; +#endif + tag->items = bulk.items; +} + +void tag_end_add(struct tag *tag) +{ + if (tag->items == bulk.items) { + assert(tag->num_items <= BULK_MAX); + + if (tag->num_items > 0) { + /* copy the tag items from the bulk list over + to a new list (which fits exactly) */ + tag->items = (struct tag_item **) + g_malloc(items_size(tag)); + memcpy(tag->items, bulk.items, items_size(tag)); + } else + tag->items = nullptr; + } + +#ifndef NDEBUG + bulk.busy = false; +#endif +} + +static bool +char_is_non_printable(unsigned char ch) +{ + return ch < 0x20; +} + +static const char * +find_non_printable(const char *p, size_t length) +{ + for (size_t i = 0; i < length; ++i) + if (char_is_non_printable(p[i])) + return p + i; + + return nullptr; +} + +/** + * Clears all non-printable characters, convert them to space. + * Returns nullptr if nothing needs to be cleared. + */ +static char * +clear_non_printable(const char *p, size_t length) +{ + const char *first = find_non_printable(p, length); + char *dest; + + if (first == nullptr) + return nullptr; + + dest = g_strndup(p, length); + + for (size_t i = first - p; i < length; ++i) + if (char_is_non_printable(dest[i])) + dest[i] = ' '; + + return dest; +} + +static char * +fix_tag_value(const char *p, size_t length) +{ + char *utf8, *cleared; + + utf8 = fix_utf8(p, length); + if (utf8 != nullptr) { + p = utf8; + length = strlen(p); + } + + cleared = clear_non_printable(p, length); + if (cleared == nullptr) + cleared = utf8; + else + g_free(utf8); + + return cleared; +} + +static void +tag_add_item_internal(struct tag *tag, enum tag_type type, + const char *value, size_t len) +{ + unsigned int i = tag->num_items; + char *p; + + p = fix_tag_value(value, len); + if (p != nullptr) { + value = p; + len = strlen(value); + } + + tag->num_items++; + + if (tag->items != bulk.items) + /* bulk mode disabled */ + 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 = (struct tag_item **)g_malloc(items_size(tag)); + memcpy(tag->items, bulk.items, + items_size(tag) - sizeof(struct tag_item *)); + } + + tag_pool_lock.lock(); + tag->items[i] = tag_pool_get_item(type, value, len); + tag_pool_lock.unlock(); + + g_free(p); +} + +void tag_add_item_n(struct tag *tag, enum tag_type type, + const char *value, size_t len) +{ + if (ignore_tag_items[type]) + { + return; + } + if (!value || !len) + return; + + tag_add_item_internal(tag, type, value, len); +} diff --git a/src/TagFile.cxx b/src/TagFile.cxx new file mode 100644 index 000000000..046778bd2 --- /dev/null +++ b/src/TagFile.cxx @@ -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. + */ + +#include "config.h" +#include "TagFile.hxx" + +extern "C" { +#include "uri.h" +} + +#include "DecoderList.hxx" +#include "decoder_plugin.h" +#include "input_stream.h" + +#include <assert.h> +#include <unistd.h> /* for SEEK_SET */ + +bool +tag_file_scan(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + assert(path_fs != NULL); + assert(handler != NULL); + + /* check if there's a suffix and a plugin */ + + const char *suffix = uri_get_suffix(path_fs); + if (suffix == NULL) + return false; + + const struct decoder_plugin *plugin = + decoder_plugin_from_suffix(suffix, NULL); + if (plugin == NULL) + return false; + + struct input_stream *is = NULL; + Mutex mutex; + Cond cond; + + do { + /* load file tag */ + if (decoder_plugin_scan_file(plugin, path_fs, + handler, handler_ctx)) + break; + + /* fall back to stream tag */ + if (plugin->scan_stream != NULL) { + /* open the input_stream (if not already + open) */ + if (is == nullptr) + is = input_stream_open(path_fs, mutex, cond, + NULL); + + /* now try the stream_tag() method */ + if (is != NULL) { + if (decoder_plugin_scan_stream(plugin, is, + handler, + handler_ctx)) + break; + + input_stream_lock_seek(is, 0, SEEK_SET, NULL); + } + } + + plugin = decoder_plugin_from_suffix(suffix, plugin); + } while (plugin != NULL); + + if (is != NULL) + input_stream_close(is); + + return plugin != NULL; +} diff --git a/src/TagFile.hxx b/src/TagFile.hxx new file mode 100644 index 000000000..61f491854 --- /dev/null +++ b/src/TagFile.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TAG_FILE_HXX +#define MPD_TAG_FILE_HXX + +#include "check.h" + +#include <stdbool.h> + +struct tag_handler; + +/** + * Scan the tags of a song file. Invokes matching decoder plugins, + * but does not invoke the special "APE" and "ID3" scanners. + */ +bool +tag_file_scan(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx); + +#endif diff --git a/src/TagInternal.hxx b/src/TagInternal.hxx new file mode 100644 index 000000000..afce0427e --- /dev/null +++ b/src/TagInternal.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TAG_INTERNAL_HXX +#define MPD_TAG_INTERNAL_HXX + +#include "tag.h" + +extern bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; + +#endif diff --git a/src/TagNames.c b/src/TagNames.c new file mode 100644 index 000000000..eca320afb --- /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/TagPool.cxx b/src/TagPool.cxx new file mode 100644 index 000000000..a9b892845 --- /dev/null +++ b/src/TagPool.cxx @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TagPool.hxx" + +#include <glib.h> + +#include <assert.h> + +Mutex tag_pool_lock; + +#define NUM_SLOTS 4096 + +struct slot { + struct slot *next; + unsigned char ref; + struct tag_item item; +} mpd_packed; + +static struct slot *slots[NUM_SLOTS]; + +static inline unsigned +calc_hash_n(enum tag_type type, const char *p, size_t length) +{ + unsigned hash = 5381; + + assert(p != nullptr); + + while (length-- > 0) + hash = (hash << 5) + hash + *p++; + + return hash ^ type; +} + +static inline unsigned +calc_hash(enum tag_type type, const char *p) +{ + unsigned hash = 5381; + + assert(p != nullptr); + + while (*p != 0) + hash = (hash << 5) + hash + *p++; + + return hash ^ type; +} + +static inline struct slot * +tag_item_to_slot(struct tag_item *item) +{ + return (struct slot*)(((char*)item) - offsetof(struct slot, item)); +} + +static struct slot *slot_alloc(struct slot *next, + enum tag_type type, + const char *value, int length) +{ + struct slot *slot; + + slot = (struct slot *) + g_malloc(sizeof(*slot) - sizeof(slot->item.value) + length + 1); + slot->next = next; + slot->ref = 1; + slot->item.type = type; + memcpy(slot->item.value, value, length); + slot->item.value[length] = 0; + return slot; +} + +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 != nullptr; slot = slot->next) { + if (slot->item.type == type && + length == strlen(slot->item.value) && + memcmp(value, slot->item.value, length) == 0 && + slot->ref < 0xff) { + assert(slot->ref > 0); + ++slot->ref; + return &slot->item; + } + } + + slot = slot_alloc(*slot_p, type, value, length); + *slot_p = slot; + return &slot->item; +} + +struct tag_item *tag_pool_dup_item(struct tag_item *item) +{ + struct slot *slot = tag_item_to_slot(item); + + assert(slot->ref > 0); + + if (slot->ref < 0xff) { + ++slot->ref; + return item; + } else { + /* the reference counter overflows above 0xff; + duplicate the item, and start with 1 */ + size_t length = strlen(item->value); + struct slot **slot_p = + &slots[calc_hash_n(item->type, item->value, + length) % NUM_SLOTS]; + slot = slot_alloc(*slot_p, item->type, + item->value, strlen(item->value)); + *slot_p = slot; + return &slot->item; + } +} + +void tag_pool_put_item(struct tag_item *item) +{ + struct slot **slot_p, *slot; + + slot = tag_item_to_slot(item); + assert(slot->ref > 0); + --slot->ref; + + if (slot->ref > 0) + return; + + for (slot_p = &slots[calc_hash(item->type, item->value) % NUM_SLOTS]; + *slot_p != slot; + slot_p = &(*slot_p)->next) { + assert(*slot_p != nullptr); + } + + *slot_p = slot->next; + g_free(slot); +} diff --git a/src/TagPool.hxx b/src/TagPool.hxx new file mode 100644 index 000000000..649cef6e5 --- /dev/null +++ b/src/TagPool.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TAG_POOL_HXX +#define MPD_TAG_POOL_HXX + +#include "tag.h" +#include "thread/Mutex.hxx" + +extern Mutex tag_pool_lock; + +struct tag_item; + +struct tag_item * +tag_pool_get_item(enum tag_type type, const char *value, size_t length); + +struct tag_item *tag_pool_dup_item(struct tag_item *item); + +void tag_pool_put_item(struct tag_item *item); + +#endif diff --git a/src/TagPrint.cxx b/src/TagPrint.cxx new file mode 100644 index 000000000..b3ad07df4 --- /dev/null +++ b/src/TagPrint.cxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TagPrint.hxx" +#include "tag.h" +#include "TagInternal.hxx" +#include "song.h" +#include "Client.hxx" + +void tag_print_types(Client *client) +{ + int i; + + for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { + if (!ignore_tag_items[i]) + client_printf(client, "tagtype: %s\n", + tag_item_names[i]); + } +} + +void tag_print(Client *client, const struct tag *tag) +{ + if (tag->time >= 0) + client_printf(client, SONG_TIME "%i\n", tag->time); + + for (unsigned i = 0; i < tag->num_items; i++) { + client_printf(client, "%s: %s\n", + tag_item_names[tag->items[i]->type], + tag->items[i]->value); + } +} diff --git a/src/TagPrint.hxx b/src/TagPrint.hxx new file mode 100644 index 000000000..99e1d0850 --- /dev/null +++ b/src/TagPrint.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TAG_PRINT_HXX +#define MPD_TAG_PRINT_HXX + +struct tag; +class Client; + +void tag_print_types(Client *client); + +void tag_print(Client *client, const struct tag *tag); + +#endif diff --git a/src/TagSave.cxx b/src/TagSave.cxx new file mode 100644 index 000000000..15da9fc4b --- /dev/null +++ b/src/TagSave.cxx @@ -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. + */ + +#include "config.h" +#include "TagSave.hxx" +#include "tag.h" +#include "TagInternal.hxx" +#include "song.h" + +void tag_save(FILE *file, const struct tag *tag) +{ + if (tag->time >= 0) + fprintf(file, SONG_TIME "%i\n", tag->time); + + if (tag->has_playlist) + fprintf(file, "Playlist: yes\n"); + + for (unsigned i = 0; i < tag->num_items; i++) + fprintf(file, "%s: %s\n", + tag_item_names[tag->items[i]->type], + tag->items[i]->value); +} diff --git a/src/TagSave.hxx b/src/TagSave.hxx new file mode 100644 index 000000000..a7ccfa613 --- /dev/null +++ b/src/TagSave.hxx @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TAG_SAVE_HXX +#define MPD_TAG_SAVE_HXX + +#include <stdio.h> + +struct tag; + +void tag_save(FILE *file, const struct tag *tag); + +#endif diff --git a/src/TextFile.cxx b/src/TextFile.cxx new file mode 100644 index 000000000..4ad59ee4a --- /dev/null +++ b/src/TextFile.cxx @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TextFile.hxx" + +#include <assert.h> +#include <string.h> + +char * +TextFile::ReadLine() +{ + gsize length = 0, i; + char *p; + + assert(file != NULL); + assert(buffer != NULL); + assert(buffer->allocated_len >= step); + + while (buffer->len < max_length) { + p = fgets(buffer->str + length, + buffer->allocated_len - length, file); + if (p == NULL) { + if (length == 0 || ferror(file)) + return NULL; + break; + } + + i = strlen(buffer->str + length); + length += i; + if (i < step - 1 || buffer->str[length - 1] == '\n') + break; + + g_string_set_size(buffer, length + step); + } + + /* remove the newline characters */ + if (buffer->str[length - 1] == '\n') + --length; + if (buffer->str[length - 1] == '\r') + --length; + + g_string_set_size(buffer, length); + return buffer->str; +} diff --git a/src/TextFile.hxx b/src/TextFile.hxx new file mode 100644 index 000000000..d593e0961 --- /dev/null +++ b/src/TextFile.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TEXT_FILE_HXX +#define MPD_TEXT_FILE_HXX + +#include "gcc.h" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" + +#include <glib.h> + +class TextFile { + static constexpr size_t max_length = 512 * 1024; + static constexpr size_t step = 1024; + + FILE *const file; + + GString *const buffer; + +public: + TextFile(const Path &path_fs) + :file(FOpen(path_fs, FOpenMode::ReadText)), + buffer(g_string_sized_new(step)) {} + + TextFile(const TextFile &other) = delete; + + ~TextFile() { + if (file != nullptr) + fclose(file); + + g_string_free(buffer, true); + } + + bool HasFailed() const { + return gcc_unlikely(file == nullptr); + } + + /** + * Reads a line from the input file, and strips trailing + * space. There is a reasonable maximum line length, only to + * prevent denial of service. + * + * @param file the source file, opened in text mode + * @param buffer an allocator for the buffer + * @return a pointer to the line, or NULL on end-of-file or error + */ + char *ReadLine(); +}; + +#endif diff --git a/src/TimePrint.cxx b/src/TimePrint.cxx new file mode 100644 index 000000000..c5247dd9d --- /dev/null +++ b/src/TimePrint.cxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TimePrint.hxx" +#include "Client.hxx" + +#include <glib.h> + +void +time_print(Client *client, const char *name, time_t t) +{ +#ifdef G_OS_WIN32 + const struct tm *tm2 = gmtime(&t); +#else + struct tm tm; + const struct tm *tm2 = gmtime_r(&t, &tm); +#endif + if (tm2 == NULL) + return; + + char buffer[32]; + strftime(buffer, sizeof(buffer), +#ifdef G_OS_WIN32 + "%Y-%m-%dT%H:%M:%SZ", +#else + "%FT%TZ", +#endif + tm2); + client_printf(client, "%s: %s\n", name, buffer); +} diff --git a/src/TimePrint.hxx b/src/TimePrint.hxx new file mode 100644 index 000000000..f45101256 --- /dev/null +++ b/src/TimePrint.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TIME_PRINT_HXX +#define MPD_TIME_PRINT_HXX + +#include <time.h> + +class Client; + +/** + * Write a line with a time stamp to the client. + */ +void +time_print(Client *client, const char *name, time_t t); + +#endif diff --git a/src/UpdateArchive.cxx b/src/UpdateArchive.cxx new file mode 100644 index 000000000..133dfd476 --- /dev/null +++ b/src/UpdateArchive.cxx @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "UpdateArchive.hxx" +#include "UpdateInternal.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" +#include "song.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(Directory *directory, const char *name) +{ + const char *tmp = strchr(name, '/'); + if (tmp) { + char *child_name = g_strndup(name, tmp - name); + //add dir is not there already + db_lock(); + Directory *subdir = + directory->MakeChild(child_name); + subdir->device = DEVICE_INARCHIVE; + db_unlock(); + g_free(child_name); + + //create directories first + update_archive_tree(subdir, tmp+1); + } else { + if (strlen(name) == 0) { + g_warning("archive returned directory only"); + return; + } + + //add file + db_lock(); + struct song *song = directory->FindSong(name); + db_unlock(); + if (song == NULL) { + song = song_file_load(name, directory); + if (song != NULL) { + db_lock(); + directory->AddSong(song); + db_unlock(); + + modified = true; + g_message("added %s/%s", + directory->GetPath(), name); + } + } + } +} + +/** + * Updates the file listing from an archive file. + * + * @param parent the parent directory the archive file resides in + * @param name the UTF-8 encoded base name of the archive file + * @param st stat() information on the archive file + * @param plugin the archive plugin which fits this archive type + */ +static void +update_archive_file2(Directory *parent, const char *name, + const struct stat *st, + const struct archive_plugin *plugin) +{ + db_lock(); + Directory *directory = parent->FindChild(name); + db_unlock(); + + if (directory != NULL && directory->mtime == st->st_mtime && + !walk_discard) + /* MPD has already scanned the archive, and it hasn't + changed since - don't consider updating it */ + return; + + const Path path_fs = map_directory_child_fs(parent, name); + + /* open archive */ + GError *error = NULL; + ArchiveFile *file = archive_file_open(plugin, path_fs.c_str(), &error); + if (file == NULL) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + + g_debug("archive %s opened", path_fs.c_str()); + + if (directory == NULL) { + g_debug("creating archive directory: %s", name); + db_lock(); + directory = parent->CreateChild(name); + /* mark this directory as archive (we use device for + this) */ + directory->device = DEVICE_INARCHIVE; + db_unlock(); + } + + directory->mtime = st->st_mtime; + + class UpdateArchiveVisitor final : public ArchiveVisitor { + Directory *directory; + + public: + UpdateArchiveVisitor(Directory *_directory) + :directory(_directory) {} + + virtual void VisitArchiveEntry(const char *path_utf8) override { + g_debug("adding archive file: %s", path_utf8); + update_archive_tree(directory, path_utf8); + } + }; + + UpdateArchiveVisitor visitor(directory); + file->Visit(visitor); + file->Close(); +} + +bool +update_archive_file(Directory *directory, + const char *name, const char *suffix, + const struct stat *st) +{ +#ifdef ENABLE_ARCHIVE + const struct archive_plugin *plugin = + archive_plugin_from_suffix(suffix); + if (plugin == NULL) + return false; + + update_archive_file2(directory, name, st, plugin); + return true; +#else + (void)directory; + (void)name; + (void)suffix; + (void)st; + + return false; +#endif +} diff --git a/src/UpdateArchive.hxx b/src/UpdateArchive.hxx new file mode 100644 index 000000000..73b363d27 --- /dev/null +++ b/src/UpdateArchive.hxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_ARCHIVE_HXX +#define MPD_UPDATE_ARCHIVE_HXX + +#include "check.h" +#include "gcc.h" + +#include <sys/stat.h> + +struct Directory; +struct archive_plugin; + +#ifdef ENABLE_ARCHIVE + +bool +update_archive_file(Directory *directory, + const char *name, const char *suffix, + const struct stat *st); + +#else + +static inline bool +update_archive_file(gcc_unused Directory *directory, + gcc_unused const char *name, + gcc_unused const char *suffix, + gcc_unused const struct stat *st) +{ + return false; +} + +#endif + +#endif diff --git a/src/UpdateContainer.cxx b/src/UpdateContainer.cxx new file mode 100644 index 000000000..27ee89bae --- /dev/null +++ b/src/UpdateContainer.cxx @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "UpdateContainer.hxx" +#include "UpdateInternal.hxx" +#include "UpdateDatabase.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" +#include "song.h" +#include "decoder_plugin.h" +#include "Mapper.hxx" +#include "fs/Path.hxx" + +extern "C" { +#include "tag_handler.h" +} + +#include <glib.h> + +/** + * Create the specified directory object if it does not exist already + * or if the #stat object indicates that it has been modified since + * the last update. Returns NULL when it exists already and is + * unmodified. + * + * The caller must lock the database. + */ +static Directory * +make_directory_if_modified(Directory *parent, const char *name, + const struct stat *st) +{ + Directory *directory = parent->FindChild(name); + + // directory exists already + if (directory != NULL) { + if (directory->mtime == st->st_mtime && !walk_discard) { + /* not modified */ + return NULL; + } + + delete_directory(directory); + modified = true; + } + + directory = parent->MakeChild(name); + directory->mtime = st->st_mtime; + return directory; +} + +bool +update_container_file(Directory *directory, + const char *name, + const struct stat *st, + const struct decoder_plugin *plugin) +{ + if (plugin->container_scan == NULL) + return false; + + db_lock(); + Directory *contdir = make_directory_if_modified(directory, name, st); + if (contdir == NULL) { + /* not modified */ + db_unlock(); + return true; + } + + contdir->device = DEVICE_CONTAINER; + db_unlock(); + + const Path pathname = map_directory_child_fs(directory, name); + + char *vtrack; + unsigned int tnum = 0; + while ((vtrack = plugin->container_scan(pathname.c_str(), ++tnum)) != NULL) { + struct song *song = song_file_new(vtrack, contdir); + + // shouldn't be necessary but it's there.. + song->mtime = st->st_mtime; + + const Path child_path_fs = + map_directory_child_fs(contdir, vtrack); + + song->tag = tag_new(); + decoder_plugin_scan_file(plugin, child_path_fs.c_str(), + &add_tag_handler, song->tag); + + db_lock(); + contdir->AddSong(song); + db_unlock(); + + modified = true; + + g_message("added %s/%s", directory->GetPath(), vtrack); + g_free(vtrack); + } + + if (tnum == 1) { + db_lock(); + delete_directory(contdir); + db_unlock(); + return false; + } else + return true; +} diff --git a/src/UpdateContainer.hxx b/src/UpdateContainer.hxx new file mode 100644 index 000000000..0078730d6 --- /dev/null +++ b/src/UpdateContainer.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_CONTAINER_HXX +#define MPD_UPDATE_CONTAINER_HXX + +#include "check.h" + +#include <sys/stat.h> + +struct Directory; +struct decoder_plugin; + +bool +update_container_file(Directory *directory, + const char *name, + const struct stat *st, + const struct decoder_plugin *plugin); + +#endif diff --git a/src/UpdateDatabase.cxx b/src/UpdateDatabase.cxx new file mode 100644 index 000000000..984fb1be8 --- /dev/null +++ b/src/UpdateDatabase.cxx @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "UpdateDatabase.hxx" +#include "UpdateRemove.hxx" +#include "PlaylistVector.hxx" +#include "Directory.hxx" +#include "song.h" +#include "DatabaseLock.hxx" + +#include <glib.h> +#include <assert.h> + +void +delete_song(Directory *dir, struct song *del) +{ + assert(del->parent == dir); + + /* first, prevent traversers in main task from getting this */ + dir->RemoveSong(del); + + db_unlock(); /* temporary unlock, because update_remove_song() blocks */ + + /* now take it out of the playlist (in the main_task) */ + update_remove_song(del); + + /* finally, all possible references gone, free it */ + song_free(del); + + db_lock(); +} + +/** + * Recursively remove all sub directories and songs from a directory, + * leaving an empty directory. + * + * Caller must lock the #db_mutex. + */ +static void +clear_directory(Directory *directory) +{ + Directory *child, *n; + directory_for_each_child_safe(child, n, directory) + delete_directory(child); + + struct song *song, *ns; + directory_for_each_song_safe(song, ns, directory) { + assert(song->parent == directory); + delete_song(directory, song); + } +} + +void +delete_directory(Directory *directory) +{ + assert(directory->parent != NULL); + + clear_directory(directory); + + directory->Delete(); +} + +bool +delete_name_in(Directory *parent, const char *name) +{ + bool modified = false; + + db_lock(); + Directory *directory = parent->FindChild(name); + + if (directory != NULL) { + delete_directory(directory); + modified = true; + } + + struct song *song = parent->FindSong(name); + if (song != NULL) { + delete_song(parent, song); + modified = true; + } + + parent->playlists.erase(name); + + db_unlock(); + + return modified; +} diff --git a/src/UpdateDatabase.hxx b/src/UpdateDatabase.hxx new file mode 100644 index 000000000..7b55ce95d --- /dev/null +++ b/src/UpdateDatabase.hxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_DATABASE_HXX +#define MPD_UPDATE_DATABASE_HXX + +#include "check.h" + +struct Directory; +struct song; + +/** + * Caller must lock the #db_mutex. + */ +void +delete_song(Directory *parent, struct song *song); + +/** + * Recursively free a directory and all its contents. + * + * Caller must lock the #db_mutex. + */ +void +delete_directory(Directory *directory); + +/** + * Caller must NOT lock the #db_mutex. + * + * @return true if the database was modified + */ +bool +delete_name_in(Directory *parent, const char *name); + +#endif diff --git a/src/UpdateGlue.cxx b/src/UpdateGlue.cxx new file mode 100644 index 000000000..664638782 --- /dev/null +++ b/src/UpdateGlue.cxx @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "UpdateGlue.hxx" +#include "UpdateQueue.hxx" +#include "UpdateWalk.hxx" +#include "UpdateRemove.hxx" +#include "Mapper.hxx" +#include "DatabaseSimple.hxx" +#include "Idle.hxx" +#include "GlobalEvents.hxx" + +extern "C" { +#include "stats.h" +} + +#include "Main.hxx" +#include "Partition.hxx" +#include "mpd_error.h" + +#include <glib.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "update" + +static enum update_progress { + UPDATE_PROGRESS_IDLE = 0, + UPDATE_PROGRESS_RUNNING = 1, + UPDATE_PROGRESS_DONE = 2 +} progress; + +static bool modified; + +static GThread *update_thr; + +static const unsigned update_task_id_max = 1 << 15; + +static unsigned update_task_id; + +/* XXX this flag is passed to update_task() */ +static bool discard; + +unsigned +isUpdatingDB(void) +{ + return (progress != UPDATE_PROGRESS_IDLE) ? update_task_id : 0; +} + +static void * update_task(void *_path) +{ + const char *path = (const char *)_path; + + if (path != NULL && *path != 0) + g_debug("starting: %s", path); + else + g_debug("starting"); + + modified = update_walk(path, discard); + + if (modified || !db_exists()) { + GError *error = NULL; + if (!db_save(&error)) { + g_warning("Failed to save database: %s", + error->message); + g_error_free(error); + } + } + + if (path != NULL && *path != 0) + g_debug("finished: %s", path); + else + g_debug("finished"); + g_free(_path); + + progress = UPDATE_PROGRESS_DONE; + GlobalEvents::Emit(GlobalEvents::UPDATE); + return NULL; +} + +static void +spawn_update_task(const char *path) +{ + GError *e = NULL; + + assert(g_thread_self() == main_task); + + progress = UPDATE_PROGRESS_RUNNING; + modified = false; + + update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e); + if (update_thr == NULL) + MPD_ERROR("Failed to spawn update task: %s", e->message); + + if (++update_task_id > update_task_id_max) + update_task_id = 1; + g_debug("spawned thread for update job id %i", update_task_id); +} + +unsigned +update_enqueue(const char *path, bool _discard) +{ + assert(g_thread_self() == main_task); + + if (!db_is_simple() || !mapper_has_music_directory()) + return 0; + + if (progress != UPDATE_PROGRESS_IDLE) { + unsigned next_task_id = + update_queue_push(path, discard, update_task_id); + if (next_task_id == 0) + return 0; + + return next_task_id > update_task_id_max ? 1 : next_task_id; + } + + discard = _discard; + spawn_update_task(path); + + idle_add(IDLE_UPDATE); + + return update_task_id; +} + +/** + * Called in the main thread after the database update is finished. + */ +static void update_finished_event(void) +{ + char *path; + + assert(progress == UPDATE_PROGRESS_DONE); + + g_thread_join(update_thr); + + idle_add(IDLE_UPDATE); + + if (modified) { + /* send "idle" events */ + global_partition->playlist.FullIncrementVersions(); + idle_add(IDLE_DATABASE); + } + + path = update_queue_shift(&discard); + if (path != NULL) { + /* schedule the next path */ + spawn_update_task(path); + g_free(path); + } else { + progress = UPDATE_PROGRESS_IDLE; + + stats_update(); + } +} + +void update_global_init(void) +{ + GlobalEvents::Register(GlobalEvents::UPDATE, update_finished_event); + + update_remove_global_init(); + update_walk_global_init(); +} + +void update_global_finish(void) +{ + update_walk_global_finish(); +} diff --git a/src/UpdateGlue.hxx b/src/UpdateGlue.hxx new file mode 100644 index 000000000..9d546a2a3 --- /dev/null +++ b/src/UpdateGlue.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_GLUE_HXX +#define MPD_UPDATE_GLUE_HXX + +void update_global_init(void); + +void update_global_finish(void); + +unsigned +isUpdatingDB(void); + +/** + * Add this path to the database update queue. + * + * @param path a path to update; if NULL or an empty string, + * the whole music directory is updated + * @return the job id, or 0 on error + */ +unsigned +update_enqueue(const char *path, bool discard); + +#endif diff --git a/src/UpdateIO.cxx b/src/UpdateIO.cxx new file mode 100644 index 000000000..7e262555c --- /dev/null +++ b/src/UpdateIO.cxx @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "UpdateIO.hxx" +#include "Directory.hxx" +#include "Mapper.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" + +#include <glib.h> + +#include <errno.h> +#include <unistd.h> + +int +stat_directory(const Directory *directory, struct stat *st) +{ + const Path path_fs = map_directory_fs(directory); + if (path_fs.IsNull()) + return -1; + + if (!StatFile(path_fs, *st)) { + int error = errno; + const std::string path_utf8 = path_fs.ToUTF8(); + g_warning("Failed to stat %s: %s", + path_utf8.c_str(), g_strerror(error)); + return -1; + } + + return 0; +} + +int +stat_directory_child(const Directory *parent, const char *name, + struct stat *st) +{ + const Path path_fs = map_directory_child_fs(parent, name); + if (path_fs.IsNull()) + return -1; + + if (!StatFile(path_fs, *st)) { + int error = errno; + const std::string path_utf8 = path_fs.ToUTF8(); + g_warning("Failed to stat %s: %s", + path_utf8.c_str(), g_strerror(error)); + return -1; + } + + return 0; +} + +bool +directory_exists(const Directory *directory) +{ + const Path path_fs = map_directory_fs(directory); + if (path_fs.IsNull()) + /* invalid path: cannot exist */ + return false; + + return directory->device == DEVICE_INARCHIVE || + directory->device == DEVICE_CONTAINER + ? FileExists(path_fs) + : DirectoryExists(path_fs); +} + +bool +directory_child_is_regular(const Directory *directory, + const char *name_utf8) +{ + const Path path_fs = map_directory_child_fs(directory, name_utf8); + if (path_fs.IsNull()) + return false; + + return FileExists(path_fs); +} + +bool +directory_child_access(const Directory *directory, + const char *name, int mode) +{ +#ifdef WIN32 + /* CheckAccess() is useless on WIN32 */ + (void)directory; + (void)name; + (void)mode; + return true; +#else + const Path path = map_directory_child_fs(directory, name); + if (path.IsNull()) + /* something went wrong, but that isn't a permission + problem */ + return true; + + return CheckAccess(path, mode) || errno != EACCES; +#endif +} diff --git a/src/UpdateIO.hxx b/src/UpdateIO.hxx new file mode 100644 index 000000000..ee47b2682 --- /dev/null +++ b/src/UpdateIO.hxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_IO_HXX +#define MPD_UPDATE_IO_HXX + +#include "check.h" + +#include <sys/stat.h> + +struct Directory; + +int +stat_directory(const Directory *directory, struct stat *st); + +int +stat_directory_child(const Directory *parent, const char *name, + struct stat *st); + +bool +directory_exists(const Directory *directory); + +bool +directory_child_is_regular(const Directory *directory, + const char *name_utf8); + +/** + * Checks if the given permissions on the mapped file are given. + */ +bool +directory_child_access(const Directory *directory, + const char *name, int mode); + +#endif diff --git a/src/UpdateInternal.hxx b/src/UpdateInternal.hxx new file mode 100644 index 000000000..50443c086 --- /dev/null +++ b/src/UpdateInternal.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_INTERNAL_H +#define MPD_UPDATE_INTERNAL_H + +#include "check.h" + +extern bool walk_discard; +extern bool modified; + +#endif diff --git a/src/UpdateQueue.cxx b/src/UpdateQueue.cxx new file mode 100644 index 000000000..85eb22358 --- /dev/null +++ b/src/UpdateQueue.cxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "UpdateQueue.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +/* make this dynamic?, or maybe this is big enough... */ +static struct { + char *path; + bool discard; +} update_queue[32]; + +static size_t update_queue_length; + +unsigned +update_queue_push(const char *path, bool discard, unsigned base) +{ + assert(update_queue_length <= G_N_ELEMENTS(update_queue)); + + if (update_queue_length == G_N_ELEMENTS(update_queue)) + return 0; + + update_queue[update_queue_length].path = g_strdup(path); + update_queue[update_queue_length].discard = discard; + + ++update_queue_length; + + return base + update_queue_length; +} + +char * +update_queue_shift(bool *discard_r) +{ + char *path; + + if (update_queue_length == 0) + return NULL; + + path = update_queue[0].path; + *discard_r = update_queue[0].discard; + + memmove(&update_queue[0], &update_queue[1], + --update_queue_length * sizeof(update_queue[0])); + return path; +} diff --git a/src/UpdateQueue.hxx b/src/UpdateQueue.hxx new file mode 100644 index 000000000..7de06964f --- /dev/null +++ b/src/UpdateQueue.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_QUEUE_HXX +#define MPD_UPDATE_QUEUE_HXX + +#include "check.h" + +unsigned +update_queue_push(const char *path, bool discard, unsigned base); + +char * +update_queue_shift(bool *discard_r); + +#endif diff --git a/src/UpdateRemove.cxx b/src/UpdateRemove.cxx new file mode 100644 index 000000000..acf8bf46c --- /dev/null +++ b/src/UpdateRemove.cxx @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "UpdateRemove.hxx" +#include "Playlist.hxx" +#include "Partition.hxx" +#include "GlobalEvents.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +#include "song.h" +#include "Main.hxx" + +#ifdef ENABLE_SQLITE +#include "StickerDatabase.hxx" +#include "SongSticker.hxx" +#endif + +#include <glib.h> + +#include <assert.h> + +static const struct song *removed_song; + +static Mutex remove_mutex; +static Cond remove_cond; + +/** + * Safely remove a song from the database. This must be done in the + * main task, to be sure that there is no pointer left to it. + */ +static void +song_remove_event(void) +{ + char *uri; + + assert(removed_song != NULL); + + uri = song_get_uri(removed_song); + g_message("removing %s", uri); + g_free(uri); + +#ifdef ENABLE_SQLITE + /* if the song has a sticker, remove it */ + if (sticker_enabled()) + sticker_song_delete(removed_song); +#endif + + global_partition->DeleteSong(*removed_song); + + /* clear "removed_song" and send signal to update thread */ + remove_mutex.lock(); + removed_song = NULL; + remove_cond.signal(); + remove_mutex.unlock(); +} + +void +update_remove_global_init(void) +{ + GlobalEvents::Register(GlobalEvents::DELETE, song_remove_event); +} + +void +update_remove_song(const struct song *song) +{ + assert(removed_song == NULL); + + removed_song = song; + + GlobalEvents::Emit(GlobalEvents::DELETE); + + remove_mutex.lock(); + + while (removed_song != NULL) + remove_cond.wait(remove_mutex); + + remove_mutex.unlock(); +} diff --git a/src/UpdateRemove.hxx b/src/UpdateRemove.hxx new file mode 100644 index 000000000..a83e14ae1 --- /dev/null +++ b/src/UpdateRemove.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_REMOVE_HXX +#define MPD_UPDATE_REMOVE_HXX + +#include "check.h" + +struct song; + +void +update_remove_global_init(void); + +/** + * Sends a signal to the main thread which will in turn remove the + * song: from the sticker database and from the playlist. This + * serialized access is implemented to avoid excessive locking. + */ +void +update_remove_song(const struct song *song); + +#endif diff --git a/src/UpdateSong.cxx b/src/UpdateSong.cxx new file mode 100644 index 000000000..676ba48e2 --- /dev/null +++ b/src/UpdateSong.cxx @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "UpdateSong.hxx" +#include "UpdateInternal.hxx" +#include "UpdateIO.hxx" +#include "UpdateDatabase.hxx" +#include "UpdateContainer.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" +#include "song.h" +#include "decoder_plugin.h" +#include "DecoderList.hxx" + +#include <glib.h> + +#include <unistd.h> + +static void +update_song_file2(Directory *directory, + const char *name, const struct stat *st, + const struct decoder_plugin *plugin) +{ + db_lock(); + 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->GetPath(), name); + if (song != NULL) { + db_lock(); + delete_song(directory, song); + db_unlock(); + } + + return; + } + + if (!(song != NULL && st->st_mtime == song->mtime && + !walk_discard) && + update_container_file(directory, name, st, plugin)) { + if (song != NULL) { + db_lock(); + delete_song(directory, song); + db_unlock(); + } + + return; + } + + if (song == NULL) { + g_debug("reading %s/%s", directory->GetPath(), name); + song = song_file_load(name, directory); + if (song == NULL) { + g_debug("ignoring unrecognized file %s/%s", + directory->GetPath(), name); + return; + } + + db_lock(); + directory->AddSong(song); + db_unlock(); + + modified = true; + g_message("added %s/%s", + directory->GetPath(), name); + } else if (st->st_mtime != song->mtime || walk_discard) { + g_message("updating %s/%s", + directory->GetPath(), name); + if (!song_file_update(song)) { + g_debug("deleting unrecognized file %s/%s", + directory->GetPath(), name); + db_lock(); + delete_song(directory, song); + db_unlock(); + } + + modified = true; + } +} + +bool +update_song_file(Directory *directory, + const char *name, const char *suffix, + const struct stat *st) +{ + const struct decoder_plugin *plugin = + decoder_plugin_from_suffix(suffix, nullptr); + if (plugin == NULL) + return false; + + update_song_file2(directory, name, st, plugin); + return true; +} diff --git a/src/UpdateSong.hxx b/src/UpdateSong.hxx new file mode 100644 index 000000000..60a532e3a --- /dev/null +++ b/src/UpdateSong.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_SONG_HXX +#define MPD_UPDATE_SONG_HXX + +#include "check.h" + +#include <sys/stat.h> + +struct Directory; + +bool +update_song_file(Directory *directory, + const char *name, const char *suffix, + const struct stat *st); + +#endif diff --git a/src/UpdateWalk.cxx b/src/UpdateWalk.cxx new file mode 100644 index 000000000..9a2e87d56 --- /dev/null +++ b/src/UpdateWalk.cxx @@ -0,0 +1,495 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "UpdateWalk.hxx" +#include "UpdateIO.hxx" +#include "UpdateDatabase.hxx" +#include "UpdateSong.hxx" +#include "UpdateArchive.hxx" +#include "DatabaseLock.hxx" +#include "DatabaseSimple.hxx" +#include "Directory.hxx" +#include "song.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> + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "update" + +bool walk_discard; +bool modified; + +#ifndef WIN32 + +enum { + DEFAULT_FOLLOW_INSIDE_SYMLINKS = true, + DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true, +}; + +static bool follow_inside_symlinks; +static bool follow_outside_symlinks; + +#endif + +void +update_walk_global_init(void) +{ +#ifndef WIN32 + follow_inside_symlinks = + config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS, + DEFAULT_FOLLOW_INSIDE_SYMLINKS); + + follow_outside_symlinks = + config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS, + DEFAULT_FOLLOW_OUTSIDE_SYMLINKS); +#endif +} + +void +update_walk_global_finish(void) +{ +} + +static void +directory_set_stat(Directory *dir, const struct stat *st) +{ + dir->inode = st->st_ino; + dir->device = st->st_dev; + dir->have_stat = true; +} + +static void +remove_excluded_from_directory(Directory *directory, + const ExcludeList &exclude_list) +{ + db_lock(); + + Directory *child, *n; + directory_for_each_child_safe(child, n, directory) { + const Path name_fs = Path::FromUTF8(child->GetName()); + + if (name_fs.IsNull() || exclude_list.Check(name_fs.c_str())) { + delete_directory(child); + modified = true; + } + } + + struct song *song, *ns; + directory_for_each_song_safe(song, ns, directory) { + assert(song->parent == directory); + + const Path name_fs = Path::FromUTF8(song->uri); + if (name_fs.IsNull() || exclude_list.Check(name_fs.c_str())) { + delete_song(directory, song); + modified = true; + } + } + + db_unlock(); +} + +static void +purge_deleted_from_directory(Directory *directory) +{ + Directory *child, *n; + directory_for_each_child_safe(child, n, directory) { + if (directory_exists(child)) + continue; + + db_lock(); + delete_directory(child); + db_unlock(); + + modified = true; + } + + struct song *song, *ns; + directory_for_each_song_safe(song, ns, directory) { + const Path path = map_song_fs(song); + if (path.IsNull() || !FileExists(path)) { + db_lock(); + delete_song(directory, song); + db_unlock(); + + modified = true; + } + } + + for (auto i = directory->playlists.begin(), + end = directory->playlists.end(); + i != end;) { + if (!directory_child_is_regular(directory, i->name.c_str())) { + db_lock(); + i = directory->playlists.erase(i); + db_unlock(); + } else + ++i; + } +} + +#ifndef G_OS_WIN32 +static int +update_directory_stat(Directory *directory) +{ + struct stat st; + if (stat_directory(directory, &st) < 0) + return -1; + + directory_set_stat(directory, &st); + return 0; +} +#endif + +static int +find_inode_ancestor(Directory *parent, ino_t inode, dev_t device) +{ +#ifndef G_OS_WIN32 + while (parent) { + if (!parent->have_stat && update_directory_stat(parent) < 0) + return -1; + + if (parent->inode == inode && parent->device == device) { + g_debug("recursive directory found"); + return 1; + } + + parent = parent->parent; + } +#else + (void)parent; + (void)inode; + (void)device; +#endif + + return 0; +} + +static bool +update_playlist_file2(Directory *directory, + const char *name, const char *suffix, + const struct stat *st) +{ + if (!playlist_suffix_supported(suffix)) + return false; + + PlaylistInfo pi(name, st->st_mtime); + + db_lock(); + if (directory->playlists.UpdateOrInsert(std::move(pi))) + modified = true; + db_unlock(); + return true; +} + +static bool +update_regular_file(Directory *directory, + const char *name, const struct stat *st) +{ + const char *suffix = uri_get_suffix(name); + if (suffix == NULL) + return false; + + return update_song_file(directory, name, suffix, st) || + update_archive_file(directory, name, suffix, st) || + update_playlist_file2(directory, name, suffix, st); +} + +static bool +update_directory(Directory *directory, const struct stat *st); + +static void +update_directory_child(Directory *directory, + const char *name, const struct stat *st) +{ + assert(strchr(name, '/') == NULL); + + if (S_ISREG(st->st_mode)) { + update_regular_file(directory, name, st); + } else if (S_ISDIR(st->st_mode)) { + if (find_inode_ancestor(directory, st->st_ino, st->st_dev)) + return; + + db_lock(); + Directory *subdir = directory->MakeChild(name); + db_unlock(); + + assert(directory == subdir->parent); + + if (!update_directory(subdir, st)) { + db_lock(); + delete_directory(subdir); + db_unlock(); + } + } else { + g_debug("update: %s is not a directory, archive or music", name); + } +} + +/* we don't look at "." / ".." nor files with newlines in their name */ +G_GNUC_PURE +static bool skip_path(const char *path) +{ + return (path[0] == '.' && path[1] == 0) || + (path[0] == '.' && path[1] == '.' && path[2] == 0) || + strchr(path, '\n') != NULL; +} + +G_GNUC_PURE +static bool +skip_symlink(const Directory *directory, const char *utf8_name) +{ +#ifndef WIN32 + const Path path_fs = map_directory_child_fs(directory, utf8_name); + if (path_fs.IsNull()) + return true; + + char buffer[MPD_PATH_MAX]; + 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; + + if ((size_t)length >= sizeof(buffer)) + /* skip symlinks when the buffer is too small for the + link target */ + return true; + + /* null-terminate the buffer, because readlink() will not */ + buffer[length] = 0; + + if (!follow_inside_symlinks && !follow_outside_symlinks) { + /* ignore all symlinks */ + return true; + } else if (follow_inside_symlinks && follow_outside_symlinks) { + /* consider all symlinks */ + return false; + } + + if (g_path_is_absolute(buffer)) { + /* if the symlink points to an absolute path, see if + that path is inside the music directory */ + const char *relative = map_to_relative_path(buffer); + return relative > buffer + ? !follow_inside_symlinks + : !follow_outside_symlinks; + } + + const char *p = buffer; + while (*p == '.') { + if (p[1] == '.' && G_IS_DIR_SEPARATOR(p[2])) { + /* "../" moves to parent directory */ + directory = directory->parent; + if (directory == NULL) { + /* we have moved outside the music + directory - skip this symlink + if such symlinks are not allowed */ + return !follow_outside_symlinks; + } + p += 3; + } else if (G_IS_DIR_SEPARATOR(p[1])) + /* eliminate "./" */ + p += 2; + else + break; + } + + /* we are still in the music directory, so this symlink points + to a song which is already in the database - skip according + to the follow_inside_symlinks param*/ + return !follow_inside_symlinks; +#else + /* no symlink checking on WIN32 */ + + (void)directory; + (void)utf8_name; + + return false; +#endif +} + +static bool +update_directory(Directory *directory, const struct stat *st) +{ + assert(S_ISDIR(st->st_mode)); + + directory_set_stat(directory, st); + + const Path path_fs = map_directory_fs(directory); + if (path_fs.IsNull()) + return false; + + DIR *dir = opendir(path_fs.c_str()); + if (!dir) { + g_warning("Failed to open directory %s: %s", + path_fs.c_str(), g_strerror(errno)); + return false; + } + + ExcludeList exclude_list; + exclude_list.LoadFile(Path::Build(path_fs, ".mpdignore")); + + if (!exclude_list.IsEmpty()) + remove_excluded_from_directory(directory, exclude_list); + + purge_deleted_from_directory(directory); + + struct dirent *ent; + while ((ent = readdir(dir))) { + std::string utf8; + struct stat st2; + + if (skip_path(ent->d_name) || exclude_list.Check(ent->d_name)) + continue; + + utf8 = Path::ToUTF8(ent->d_name); + if (utf8.empty()) + continue; + + if (skip_symlink(directory, utf8.c_str())) { + modified |= delete_name_in(directory, utf8.c_str()); + continue; + } + + if (stat_directory_child(directory, utf8.c_str(), &st2) == 0) + update_directory_child(directory, utf8.c_str(), &st2); + else + modified |= delete_name_in(directory, utf8.c_str()); + } + + closedir(dir); + + directory->mtime = st->st_mtime; + + return true; +} + +static Directory * +directory_make_child_checked(Directory *parent, const char *name_utf8) +{ + db_lock(); + Directory *directory = parent->FindChild(name_utf8); + db_unlock(); + + if (directory != NULL) + return directory; + + struct stat st; + if (stat_directory_child(parent, name_utf8, &st) < 0 || + find_inode_ancestor(parent, st.st_ino, st.st_dev)) + return NULL; + + if (skip_symlink(parent, name_utf8)) + return NULL; + + /* if we're adding directory paths, make sure to delete filenames + with potentially the same name */ + db_lock(); + struct song *conflicting = parent->FindSong(name_utf8); + if (conflicting) + delete_song(parent, conflicting); + + directory = parent->CreateChild(name_utf8); + db_unlock(); + + directory_set_stat(directory, &st); + return directory; +} + +static Directory * +directory_make_uri_parent_checked(const char *uri) +{ + Directory *directory = db_get_root(); + char *duplicated = g_strdup(uri); + char *name_utf8 = duplicated, *slash; + + while ((slash = strchr(name_utf8, '/')) != NULL) { + *slash = 0; + + if (*name_utf8 == 0) + continue; + + directory = directory_make_child_checked(directory, name_utf8); + if (directory == NULL) + break; + + name_utf8 = slash + 1; + } + + g_free(duplicated); + return directory; +} + +static void +update_uri(const char *uri) +{ + Directory *parent = directory_make_uri_parent_checked(uri); + if (parent == NULL) + return; + + char *name = g_path_get_basename(uri); + + struct stat st; + if (!skip_symlink(parent, name) && + stat_directory_child(parent, name, &st) == 0) + update_directory_child(parent, name, &st); + else + modified |= delete_name_in(parent, name); + + g_free(name); +} + +bool +update_walk(const char *path, bool discard) +{ + walk_discard = discard; + modified = false; + + if (path != NULL && !isRootDirectory(path)) { + update_uri(path); + } else { + Directory *directory = db_get_root(); + struct stat st; + + if (stat_directory(directory, &st) == 0) + update_directory(directory, &st); + } + + return modified; +} diff --git a/src/UpdateWalk.hxx b/src/UpdateWalk.hxx new file mode 100644 index 000000000..62c0d0a8e --- /dev/null +++ b/src/UpdateWalk.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_WALK_HXX +#define MPD_UPDATE_WALK_HXX + +#include "check.h" + +void +update_walk_global_init(void); + +void +update_walk_global_finish(void); + +/** + * Returns true if the database was modified. + */ +bool +update_walk(const char *path, bool discard); + +#endif diff --git a/src/Volume.cxx b/src/Volume.cxx new file mode 100644 index 000000000..116f4aa18 --- /dev/null +++ b/src/Volume.cxx @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Volume.hxx" +#include "MixerAll.hxx" +#include "Idle.hxx" +#include "GlobalEvents.hxx" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "volume" + +#define SW_VOLUME_STATE "sw_volume: " + +static unsigned volume_software_set = 100; + +/** the cached hardware mixer value; invalid if negative */ +static int last_hardware_volume = -1; +/** the age of #last_hardware_volume */ +static GTimer *hardware_volume_timer; + +/** + * Handler for #GlobalEvents::MIXER. + */ +static void +mixer_event_callback(void) +{ + /* flush the hardware volume cache */ + last_hardware_volume = -1; + + /* notify clients */ + idle_add(IDLE_MIXER); +} + +void volume_finish(void) +{ + g_timer_destroy(hardware_volume_timer); +} + +void volume_init(void) +{ + hardware_volume_timer = g_timer_new(); + + GlobalEvents::Register(GlobalEvents::MIXER, mixer_event_callback); +} + +int volume_level_get(void) +{ + assert(hardware_volume_timer != NULL); + + if (last_hardware_volume >= 0 && + g_timer_elapsed(hardware_volume_timer, NULL) < 1.0) + /* throttle access to hardware mixers */ + return last_hardware_volume; + + last_hardware_volume = mixer_all_get_volume(); + g_timer_start(hardware_volume_timer); + return last_hardware_volume; +} + +static bool software_volume_change(unsigned volume) +{ + assert(volume <= 100); + + volume_software_set = volume; + mixer_all_set_software_volume(volume); + + return true; +} + +static bool hardware_volume_change(unsigned volume) +{ + /* reset the cache */ + last_hardware_volume = -1; + + return mixer_all_set_volume(volume); +} + +bool volume_level_change(unsigned volume) +{ + assert(volume <= 100); + + volume_software_set = volume; + + idle_add(IDLE_MIXER); + + return hardware_volume_change(volume); +} + +bool +read_sw_volume_state(const char *line) +{ + char *end = NULL; + long int sv; + + if (!g_str_has_prefix(line, SW_VOLUME_STATE)) + return false; + + line += sizeof(SW_VOLUME_STATE) - 1; + sv = strtol(line, &end, 10); + if (*end == 0 && sv >= 0 && sv <= 100) + software_volume_change(sv); + else + g_warning("Can't parse software volume: %s\n", line); + return true; +} + +void save_sw_volume_state(FILE *fp) +{ + fprintf(fp, SW_VOLUME_STATE "%u\n", volume_software_set); +} + +unsigned +sw_volume_state_get_hash(void) +{ + return volume_software_set; +} diff --git a/src/Volume.hxx b/src/Volume.hxx new file mode 100644 index 000000000..024b2840a --- /dev/null +++ b/src/Volume.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_VOLUME_HXX +#define MPD_VOLUME_HXX + +#include <stdio.h> + +void volume_init(void); + +void volume_finish(void); + +int volume_level_get(void); + +bool volume_level_change(unsigned volume); + +bool +read_sw_volume_state(const char *line); + +void save_sw_volume_state(FILE *fp); + +/** + * Generates a hash number for the current state of the software + * volume control. This is used by timer_save_state_file() to + * determine whether the state has changed and the state file should + * be saved. + */ +unsigned +sw_volume_state_get_hash(void); + +#endif diff --git a/src/Win32Main.cxx b/src/Win32Main.cxx new file mode 100644 index 000000000..0cd5e445b --- /dev/null +++ b/src/Win32Main.cxx @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Main.hxx" + +#ifdef WIN32 + +#include "mpd_error.h" +#include "GlobalEvents.hxx" + +#include <cstdlib> +#include <atomic> + +#include <glib.h> + +#include <windows.h> + +static int service_argc; +static char **service_argv; +static char service_name[] = ""; +static std::atomic_bool running; +static SERVICE_STATUS_HANDLE service_handle; + +static void WINAPI +service_main(DWORD argc, CHAR *argv[]); + +static SERVICE_TABLE_ENTRY service_registry[] = { + {service_name, service_main}, + {NULL, NULL} +}; + +static void +service_notify_status(DWORD status_code) +{ + SERVICE_STATUS current_status; + + current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING + ? 0 + : SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP; + + current_status.dwCurrentState = status_code; + current_status.dwWin32ExitCode = NO_ERROR; + current_status.dwCheckPoint = 0; + current_status.dwWaitHint = 1000; + + SetServiceStatus(service_handle, ¤t_status); +} + +static DWORD WINAPI +service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type, + G_GNUC_UNUSED void *event_data, G_GNUC_UNUSED void *context) +{ + switch (control) { + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + GlobalEvents::Emit(GlobalEvents::SHUTDOWN); + return NO_ERROR; + default: + return NO_ERROR; + } +} + +static void WINAPI +service_main(G_GNUC_UNUSED DWORD argc, G_GNUC_UNUSED CHAR *argv[]) +{ + DWORD error_code; + gchar* error_message; + + service_handle = + RegisterServiceCtrlHandlerEx(service_name, + service_dispatcher, NULL); + + if (service_handle == 0) { + error_code = GetLastError(); + error_message = g_win32_error_message(error_code); + MPD_ERROR("RegisterServiceCtrlHandlerEx() failed: %s", + error_message); + } + + service_notify_status(SERVICE_START_PENDING); + mpd_main(service_argc, service_argv); + service_notify_status(SERVICE_STOPPED); +} + +static BOOL WINAPI +console_handler(DWORD event) +{ + switch (event) { + case CTRL_C_EVENT: + case CTRL_CLOSE_EVENT: + if (running.load()) { + // Recent msdn docs that process is terminated + // if this function returns TRUE. + // We initiate correct shutdown sequence (if possible). + // Once main() returns CRT will terminate our process + // regardless our thread is still active. + // If this did not happen within 3 seconds + // let's shutdown anyway. + GlobalEvents::Emit(GlobalEvents::SHUTDOWN); + // Under debugger it's better to wait indefinitely + // to allow debugging of shutdown code. + Sleep(IsDebuggerPresent() ? INFINITE : 3000); + } + // If we're not running main loop there is no chance for + // clean shutdown. + std::exit(EXIT_FAILURE); + return TRUE; + default: + return FALSE; + } +} + +int win32_main(int argc, char *argv[]) +{ + DWORD error_code; + gchar* error_message; + + service_argc = argc; + service_argv = argv; + + if (StartServiceCtrlDispatcher(service_registry)) + return 0; /* run as service successefully */ + + error_code = GetLastError(); + if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) { + /* running as console app */ + running.store(false); + SetConsoleTitle("Music Player Daemon"); + SetConsoleCtrlHandler(console_handler, TRUE); + return mpd_main(argc, argv); + } + + error_message = g_win32_error_message(error_code); + MPD_ERROR("StartServiceCtrlDispatcher() failed: %s", error_message); +} + +void win32_app_started() +{ + if (service_handle != 0) + service_notify_status(SERVICE_RUNNING); + else + running.store(true); +} + +void win32_app_stopping() +{ + if (service_handle != 0) + service_notify_status(SERVICE_STOP_PENDING); + else + running.store(false); +} + +#endif diff --git a/src/ZeroconfAvahi.cxx b/src/ZeroconfAvahi.cxx new file mode 100644 index 000000000..4764ad755 --- /dev/null +++ b/src/ZeroconfAvahi.cxx @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ZeroconfAvahi.hxx" +#include "ZeroconfInternal.hxx" +#include "Listen.hxx" +#include "event/Loop.hxx" +#include "mpd_error.h" + +#include <glib.h> + +#include <avahi-client/client.h> +#include <avahi-client/publish.h> + +#include <avahi-common/alternative.h> +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> + +#include <avahi-glib/glib-watch.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "avahi" + +static char *avahiName; +static int avahiRunning; +static AvahiGLibPoll *avahi_glib_poll; +static const AvahiPoll *avahi_poll; +static AvahiClient *avahiClient; +static AvahiEntryGroup *avahiGroup; + +static void avahiRegisterService(AvahiClient * c); + +/* Callback when the EntryGroup changes state */ +static void avahiGroupCallback(AvahiEntryGroup * g, + AvahiEntryGroupState state, + G_GNUC_UNUSED void *userdata) +{ + char *n; + assert(g); + + g_debug("Service group changed to state %d", state); + + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + /* The entry group has been established successfully */ + g_message("Service '%s' successfully established.", + avahiName); + break; + + case AVAHI_ENTRY_GROUP_COLLISION: + /* A service name collision happened. Let's pick a new name */ + n = avahi_alternative_service_name(avahiName); + avahi_free(avahiName); + avahiName = n; + + g_message("Service name collision, renaming service to '%s'", + avahiName); + + /* And recreate the services */ + avahiRegisterService(avahi_entry_group_get_client(g)); + break; + + case AVAHI_ENTRY_GROUP_FAILURE: + g_warning("Entry group failure: %s", + avahi_strerror(avahi_client_errno + (avahi_entry_group_get_client(g)))); + /* Some kind of failure happened while we were registering our services */ + avahiRunning = 0; + break; + + case AVAHI_ENTRY_GROUP_UNCOMMITED: + g_debug("Service group is UNCOMMITED"); + break; + case AVAHI_ENTRY_GROUP_REGISTERING: + g_debug("Service group is REGISTERING"); + } +} + +/* Registers a new service with avahi */ +static void avahiRegisterService(AvahiClient * c) +{ + int ret; + assert(c); + g_debug("Registering service %s/%s", SERVICE_TYPE, avahiName); + + /* If this is the first time we're called, + * let's create a new entry group */ + if (!avahiGroup) { + avahiGroup = avahi_entry_group_new(c, avahiGroupCallback, NULL); + if (!avahiGroup) { + g_warning("Failed to create avahi EntryGroup: %s", + avahi_strerror(avahi_client_errno(c))); + goto fail; + } + } + + /* Add the service */ + /* TODO: This currently binds to ALL interfaces. + * We could maybe add a service per actual bound interface, + * if that's better. */ + ret = avahi_entry_group_add_service(avahiGroup, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + AvahiPublishFlags(0), + avahiName, SERVICE_TYPE, NULL, + NULL, listen_port, NULL); + if (ret < 0) { + g_warning("Failed to add service %s: %s", SERVICE_TYPE, + avahi_strerror(ret)); + goto fail; + } + + /* Tell the server to register the service group */ + ret = avahi_entry_group_commit(avahiGroup); + if (ret < 0) { + g_warning("Failed to commit service group: %s", + avahi_strerror(ret)); + goto fail; + } + return; + +fail: + avahiRunning = 0; +} + +/* Callback when avahi changes state */ +static void avahiClientCallback(AvahiClient * c, AvahiClientState state, + G_GNUC_UNUSED void *userdata) +{ + int reason; + assert(c); + + /* Called whenever the client or server state changes */ + g_debug("Client changed to state %d", state); + + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + g_debug("Client is RUNNING"); + + /* The server has startup successfully and registered its host + * name on the network, so it's time to create our services */ + if (!avahiGroup) + avahiRegisterService(c); + break; + + case AVAHI_CLIENT_FAILURE: + reason = avahi_client_errno(c); + if (reason == AVAHI_ERR_DISCONNECTED) { + g_message("Client Disconnected, will reconnect shortly"); + if (avahiGroup) { + avahi_entry_group_free(avahiGroup); + avahiGroup = NULL; + } + if (avahiClient) + avahi_client_free(avahiClient); + avahiClient = + avahi_client_new(avahi_poll, + AVAHI_CLIENT_NO_FAIL, + avahiClientCallback, NULL, + &reason); + if (!avahiClient) { + g_warning("Could not reconnect: %s", + avahi_strerror(reason)); + avahiRunning = 0; + } + } else { + g_warning("Client failure: %s (terminal)", + avahi_strerror(reason)); + avahiRunning = 0; + } + break; + + case AVAHI_CLIENT_S_COLLISION: + g_debug("Client is COLLISION"); + /* Let's drop our registered services. When the server is back + * in AVAHI_SERVER_RUNNING state we will register them + * again with the new host name. */ + if (avahiGroup) { + g_debug("Resetting group"); + avahi_entry_group_reset(avahiGroup); + } + + case AVAHI_CLIENT_S_REGISTERING: + g_debug("Client is REGISTERING"); + /* The server records are now being established. This + * might be caused by a host name change. We need to wait + * for our own records to register until the host name is + * properly esatblished. */ + + if (avahiGroup) { + g_debug("Resetting group"); + avahi_entry_group_reset(avahiGroup); + } + + break; + + case AVAHI_CLIENT_CONNECTING: + g_debug("Client is CONNECTING"); + } +} + +void +AvahiInit(EventLoop &loop, const char *serviceName) +{ + int error; + g_debug("Initializing interface"); + + if (!avahi_is_valid_service_name(serviceName)) + MPD_ERROR("Invalid zeroconf_name \"%s\"", serviceName); + + avahiName = avahi_strdup(serviceName); + + avahiRunning = 1; + + avahi_glib_poll = avahi_glib_poll_new(loop.GetContext(), + G_PRIORITY_DEFAULT); + avahi_poll = avahi_glib_poll_get(avahi_glib_poll); + + avahiClient = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL, + avahiClientCallback, NULL, &error); + + if (!avahiClient) { + g_warning("Failed to create client: %s", + avahi_strerror(error)); + AvahiDeinit(); + } +} + +void +AvahiDeinit(void) +{ + g_debug("Shutting down interface"); + + if (avahiGroup) { + avahi_entry_group_free(avahiGroup); + avahiGroup = NULL; + } + + if (avahiClient) { + avahi_client_free(avahiClient); + avahiClient = NULL; + } + + if (avahi_glib_poll != NULL) { + avahi_glib_poll_free(avahi_glib_poll); + avahi_glib_poll = NULL; + } + + avahi_free(avahiName); + avahiName = NULL; +} diff --git a/src/ZeroconfAvahi.hxx b/src/ZeroconfAvahi.hxx new file mode 100644 index 000000000..bb046350a --- /dev/null +++ b/src/ZeroconfAvahi.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ZEROCONF_AVAHI_HXX +#define MPD_ZEROCONF_AVAHI_HXX + +class EventLoop; + +void +AvahiInit(EventLoop &loop, const char *service_name); + +void +AvahiDeinit(); + +#endif diff --git a/src/ZeroconfBonjour.cxx b/src/ZeroconfBonjour.cxx new file mode 100644 index 000000000..959c90242 --- /dev/null +++ b/src/ZeroconfBonjour.cxx @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ZeroconfBonjour.hxx" +#include "ZeroconfInternal.hxx" +#include "Listen.hxx" +#include "event/SocketMonitor.hxx" +#include "gcc.h" + +#include <glib.h> + +#include <dns_sd.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "bonjour" + +class BonjourMonitor final : public SocketMonitor { + DNSServiceRef service_ref; + +public: + BonjourMonitor(EventLoop &_loop, DNSServiceRef _service_ref) + :SocketMonitor(DNSServiceRefSockFD(_service_ref), _loop), + service_ref(_service_ref) { + ScheduleRead(); + } + + ~BonjourMonitor() { + Steal(); + DNSServiceRefDeallocate(service_ref); + } + +protected: + virtual bool OnSocketReady(gcc_unused unsigned flags) override { + DNSServiceProcessResult(service_ref); + return false; + } +}; + +static BonjourMonitor *bonjour_monitor; + +static void +dnsRegisterCallback(G_GNUC_UNUSED DNSServiceRef sdRef, + G_GNUC_UNUSED DNSServiceFlags flags, + DNSServiceErrorType errorCode, const char *name, + G_GNUC_UNUSED const char *regtype, + G_GNUC_UNUSED const char *domain, + G_GNUC_UNUSED void *context) +{ + if (errorCode != kDNSServiceErr_NoError) { + g_warning("Failed to register zeroconf service."); + + bonjour_monitor->Cancel(); + } else { + g_debug("Registered zeroconf service with name '%s'", name); + } +} + +void +BonjourInit(EventLoop &loop, const char *service_name) +{ + DNSServiceRef dnsReference; + DNSServiceErrorType error = DNSServiceRegister(&dnsReference, + 0, 0, service_name, + SERVICE_TYPE, NULL, NULL, + g_htons(listen_port), 0, + NULL, + dnsRegisterCallback, + NULL); + + if (error != kDNSServiceErr_NoError) { + g_warning("Failed to register zeroconf service."); + + if (dnsReference) { + DNSServiceRefDeallocate(dnsReference); + dnsReference = NULL; + } + return; + } + + bonjour_monitor = new BonjourMonitor(loop, dnsReference); +} + +void +BonjourDeinit() +{ + delete bonjour_monitor; +} diff --git a/src/ZeroconfBonjour.hxx b/src/ZeroconfBonjour.hxx new file mode 100644 index 000000000..d91fe9a0d --- /dev/null +++ b/src/ZeroconfBonjour.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ZEROCONF_BONJOUR_HXX +#define MPD_ZEROCONF_BONJOUR_HXX + +class EventLoop; + +void +BonjourInit(EventLoop &loop, const char *service_name); + +void +BonjourDeinit(); + +#endif diff --git a/src/ZeroconfGlue.cxx b/src/ZeroconfGlue.cxx new file mode 100644 index 000000000..14e1d0866 --- /dev/null +++ b/src/ZeroconfGlue.cxx @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ZeroconfGlue.hxx" +#include "ZeroconfAvahi.hxx" +#include "ZeroconfBonjour.hxx" +#include "conf.h" +#include "Listen.hxx" +#include "gcc.h" + +#include <glib.h> + +/* The default service name to publish + * (overridden by 'zeroconf_name' config parameter) + */ +#define SERVICE_NAME "Music Player" + +#define DEFAULT_ZEROCONF_ENABLED 1 + +static int zeroconfEnabled; + +void +ZeroconfInit(gcc_unused EventLoop &loop) +{ + const char *serviceName; + + zeroconfEnabled = config_get_bool(CONF_ZEROCONF_ENABLED, + DEFAULT_ZEROCONF_ENABLED); + if (!zeroconfEnabled) + return; + + if (listen_port <= 0) { + g_warning("No global port, disabling zeroconf"); + zeroconfEnabled = false; + return; + } + + serviceName = config_get_string(CONF_ZEROCONF_NAME, SERVICE_NAME); + +#ifdef HAVE_AVAHI + AvahiInit(loop, serviceName); +#endif + +#ifdef HAVE_BONJOUR + BonjourInit(loop, serviceName); +#endif +} + +void +ZeroconfDeinit() +{ + if (!zeroconfEnabled) + return; + +#ifdef HAVE_AVAHI + AvahiDeinit(); +#endif /* HAVE_AVAHI */ + +#ifdef HAVE_BONJOUR + BonjourDeinit(); +#endif +} diff --git a/src/ZeroconfGlue.hxx b/src/ZeroconfGlue.hxx new file mode 100644 index 000000000..2a291ce29 --- /dev/null +++ b/src/ZeroconfGlue.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ZEROCONF_GLUE_HXX +#define MPD_ZEROCONF_GLUE_HXX + +#include "check.h" + +class EventLoop; + +#ifdef HAVE_ZEROCONF + +void +ZeroconfInit(EventLoop &loop); + +void +ZeroconfDeinit(); + +#else /* ! HAVE_ZEROCONF */ + +static inline void +ZeroconfInit(EventLoop &) +{} + +static inline void +ZeroconfDeinit() +{} + +#endif /* ! HAVE_ZEROCONF */ + +#endif diff --git a/src/ZeroconfInternal.hxx b/src/ZeroconfInternal.hxx new file mode 100644 index 000000000..2eadcff6e --- /dev/null +++ b/src/ZeroconfInternal.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef ZEROCONF_INTERNAL_H +#define ZEROCONF_INTERNAL_H + +/* The dns-sd service type qualifier to publish */ +#define SERVICE_TYPE "_mpd._tcp" + +#endif diff --git a/src/archive/Bzip2ArchivePlugin.cxx b/src/archive/Bzip2ArchivePlugin.cxx new file mode 100644 index 000000000..182b9ccd1 --- /dev/null +++ b/src/archive/Bzip2ArchivePlugin.cxx @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * single bz2 archive handling (requires libbz2) + */ + +#include "config.h" +#include "Bzip2ArchivePlugin.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "ArchiveVisitor.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "util/RefCount.hxx" + +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include <glib.h> +#include <bzlib.h> + +#ifdef HAVE_OLDER_BZIP2 +#define BZ2_bzDecompressInit bzDecompressInit +#define BZ2_bzDecompress bzDecompress +#endif + +class Bzip2ArchiveFile final : public ArchiveFile { +public: + RefCount ref; + + char *const name; + struct input_stream *const istream; + + Bzip2ArchiveFile(const char *path, input_stream *_is) + :ArchiveFile(bz2_archive_plugin), + name(g_path_get_basename(path)), + istream(_is) { + // remove .bz2 suffix + size_t len = strlen(name); + if (len > 4) + name[len - 4] = 0; + } + + ~Bzip2ArchiveFile() { + input_stream_close(istream); + } + + void Ref() { + ref.Increment(); + } + + void Unref() { + if (!ref.Decrement()) + return; + + g_free(name); + delete this; + } + + virtual void Close() override { + Unref(); + } + + virtual void Visit(ArchiveVisitor &visitor) override { + visitor.VisitArchiveEntry(name); + } + + virtual input_stream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + GError **error_r) override; +}; + +struct Bzip2InputStream { + struct input_stream base; + + Bzip2ArchiveFile *archive; + + bool eof; + + bz_stream bzstream; + + char buffer[5000]; + + Bzip2InputStream(Bzip2ArchiveFile &context, const char *uri, + Mutex &mutex, Cond &cond); + ~Bzip2InputStream(); + + bool Open(GError **error_r); + void Close(); +}; + +extern const struct input_plugin bz2_inputplugin; + +static inline GQuark +bz2_quark(void) +{ + return g_quark_from_static_string("bz2"); +} + +/* single archive handling allocation helpers */ + +inline bool +Bzip2InputStream::Open(GError **error_r) +{ + bzstream.bzalloc = nullptr; + bzstream.bzfree = nullptr; + bzstream.opaque = nullptr; + + bzstream.next_in = (char *)buffer; + bzstream.avail_in = 0; + + int ret = BZ2_bzDecompressInit(&bzstream, 0, 0); + if (ret != BZ_OK) { + g_set_error(error_r, bz2_quark(), ret, + "BZ2_bzDecompressInit() has failed"); + return false; + } + + base.ready = true; + return true; +} + +inline void +Bzip2InputStream::Close() +{ + BZ2_bzDecompressEnd(&bzstream); +} + +/* archive open && listing routine */ + +static ArchiveFile * +bz2_open(const char *pathname, GError **error_r) +{ + static Mutex mutex; + static Cond cond; + input_stream *is = input_stream_open(pathname, mutex, cond, error_r); + if (is == nullptr) + return nullptr; + + return new Bzip2ArchiveFile(pathname, is); +} + +/* single archive handling */ + +Bzip2InputStream::Bzip2InputStream(Bzip2ArchiveFile &_context, const char *uri, + Mutex &mutex, Cond &cond) + :base(bz2_inputplugin, uri, mutex, cond), + archive(&_context), eof(false) +{ + archive->Ref(); +} + +Bzip2InputStream::~Bzip2InputStream() +{ + archive->Unref(); +} + +input_stream * +Bzip2ArchiveFile::OpenStream(const char *path, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + Bzip2InputStream *bis = new Bzip2InputStream(*this, path, mutex, cond); + if (!bis->Open(error_r)) { + delete bis; + return NULL; + } + + return &bis->base; +} + +static void +bz2_is_close(struct input_stream *is) +{ + Bzip2InputStream *bis = (Bzip2InputStream *)is; + + bis->Close(); + delete bis; +} + +static bool +bz2_fillbuffer(Bzip2InputStream *bis, GError **error_r) +{ + size_t count; + bz_stream *bzstream; + + bzstream = &bis->bzstream; + + if (bzstream->avail_in > 0) + return true; + + count = input_stream_read(bis->archive->istream, + bis->buffer, sizeof(bis->buffer), + error_r); + if (count == 0) + return false; + + bzstream->next_in = bis->buffer; + bzstream->avail_in = count; + return true; +} + +static size_t +bz2_is_read(struct input_stream *is, void *ptr, size_t length, + GError **error_r) +{ + Bzip2InputStream *bis = (Bzip2InputStream *)is; + bz_stream *bzstream; + int bz_result; + size_t nbytes = 0; + + if (bis->eof) + return 0; + + bzstream = &bis->bzstream; + bzstream->next_out = (char *)ptr; + bzstream->avail_out = length; + + do { + if (!bz2_fillbuffer(bis, error_r)) + return 0; + + bz_result = BZ2_bzDecompress(bzstream); + + if (bz_result == BZ_STREAM_END) { + bis->eof = true; + break; + } + + if (bz_result != BZ_OK) { + g_set_error(error_r, bz2_quark(), bz_result, + "BZ2_bzDecompress() has failed"); + return 0; + } + } while (bzstream->avail_out == length); + + nbytes = length - bzstream->avail_out; + is->offset += nbytes; + + return nbytes; +} + +static bool +bz2_is_eof(struct input_stream *is) +{ + Bzip2InputStream *bis = (Bzip2InputStream *)is; + + return bis->eof; +} + +/* exported structures */ + +static const char *const bz2_extensions[] = { + "bz2", + NULL +}; + +const struct input_plugin bz2_inputplugin = { + nullptr, + nullptr, + nullptr, + nullptr, + bz2_is_close, + nullptr, + nullptr, + nullptr, + nullptr, + bz2_is_read, + bz2_is_eof, + nullptr, +}; + +const struct archive_plugin bz2_archive_plugin = { + "bz2", + nullptr, + nullptr, + bz2_open, + bz2_extensions, +}; + diff --git a/src/archive/Bzip2ArchivePlugin.hxx b/src/archive/Bzip2ArchivePlugin.hxx new file mode 100644 index 000000000..a7933a7a7 --- /dev/null +++ b/src/archive/Bzip2ArchivePlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_BZ2_HXX +#define MPD_ARCHIVE_BZ2_HXX + +extern const struct archive_plugin bz2_archive_plugin; + +#endif diff --git a/src/archive/Iso9660ArchivePlugin.cxx b/src/archive/Iso9660ArchivePlugin.cxx new file mode 100644 index 000000000..97fd8bd52 --- /dev/null +++ b/src/archive/Iso9660ArchivePlugin.cxx @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * iso archive handling (requires cdio, and iso9660) + */ + +#include "config.h" +#include "Iso9660ArchivePlugin.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "ArchiveVisitor.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "util/RefCount.hxx" + +#include <cdio/cdio.h> +#include <cdio/iso9660.h> + +#include <glib.h> + +#include <stdlib.h> +#include <string.h> + +#define CEILING(x, y) ((x+(y-1))/y) + +class Iso9660ArchiveFile final : public ArchiveFile { +public: + RefCount ref; + + iso9660_t *iso; + + Iso9660ArchiveFile(iso9660_t *_iso) + :ArchiveFile(iso9660_archive_plugin), iso(_iso) {} + + ~Iso9660ArchiveFile() { + iso9660_close(iso); + } + + void Unref() { + if (ref.Decrement()) + delete this; + } + + void Visit(const char *path, ArchiveVisitor &visitor); + + virtual void Close() override { + Unref(); + } + + virtual void Visit(ArchiveVisitor &visitor) override; + + virtual input_stream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + GError **error_r) override; +}; + +extern const struct input_plugin iso9660_input_plugin; + +static inline GQuark +iso9660_quark(void) +{ + return g_quark_from_static_string("iso9660"); +} + +/* archive open && listing routine */ + +inline void +Iso9660ArchiveFile::Visit(const char *psz_path, ArchiveVisitor &visitor) +{ + CdioList_t *entlist; + CdioListNode_t *entnode; + iso9660_stat_t *statbuf; + char pathname[4096]; + + entlist = iso9660_ifs_readdir (iso, psz_path); + if (!entlist) { + return; + } + /* Iterate over the list of nodes that iso9660_ifs_readdir gives */ + _CDIO_LIST_FOREACH (entnode, entlist) { + statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode); + + strcpy(pathname, psz_path); + strcat(pathname, statbuf->filename); + + if (iso9660_stat_s::_STAT_DIR == statbuf->type ) { + if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) { + strcat(pathname, "/"); + Visit(pathname, visitor); + } + } else { + //remove leading / + visitor.VisitArchiveEntry(pathname + 1); + } + } + _cdio_list_free (entlist, true); +} + +static ArchiveFile * +iso9660_archive_open(const char *pathname, GError **error_r) +{ + /* open archive */ + auto iso = iso9660_open(pathname); + if (iso == nullptr) { + g_set_error(error_r, iso9660_quark(), 0, + "Failed to open ISO9660 file %s", pathname); + return NULL; + } + + return new Iso9660ArchiveFile(iso); +} + +void +Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor) +{ + Visit("/", visitor); +} + +/* single archive handling */ + +struct Iso9660InputStream { + struct input_stream base; + + Iso9660ArchiveFile *archive; + + iso9660_stat_t *statbuf; + size_t max_blocks; + + Iso9660InputStream(Iso9660ArchiveFile &_archive, const char *uri, + Mutex &mutex, Cond &cond, + iso9660_stat_t *_statbuf) + :base(iso9660_input_plugin, uri, mutex, cond), + archive(&_archive), statbuf(_statbuf), + max_blocks(CEILING(statbuf->size, ISO_BLOCKSIZE)) { + + base.ready = true; + base.size = statbuf->size; + + archive->ref.Increment(); + } + + ~Iso9660InputStream() { + free(statbuf); + archive->Unref(); + } +}; + +input_stream * +Iso9660ArchiveFile::OpenStream(const char *pathname, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + auto statbuf = iso9660_ifs_stat_translate(iso, pathname); + if (statbuf == nullptr) { + g_set_error(error_r, iso9660_quark(), 0, + "not found in the ISO file: %s", pathname); + return NULL; + } + + Iso9660InputStream *iis = + new Iso9660InputStream(*this, pathname, mutex, cond, + statbuf); + return &iis->base; +} + +static void +iso9660_input_close(struct input_stream *is) +{ + Iso9660InputStream *iis = (Iso9660InputStream *)is; + + delete iis; +} + + +static size_t +iso9660_input_read(struct input_stream *is, void *ptr, size_t size, GError **error_r) +{ + Iso9660InputStream *iis = (Iso9660InputStream *)is; + int toread, readed = 0; + int no_blocks, cur_block; + size_t left_bytes = iis->statbuf->size - is->offset; + + size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE; + + if (left_bytes < size) { + toread = left_bytes; + no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE); + } else { + toread = size; + no_blocks = toread / ISO_BLOCKSIZE; + } + if (no_blocks > 0) { + + cur_block = is->offset / ISO_BLOCKSIZE; + + readed = iso9660_iso_seek_read (iis->archive->iso, ptr, + iis->statbuf->lsn + cur_block, no_blocks); + + if (readed != no_blocks * ISO_BLOCKSIZE) { + g_set_error(error_r, iso9660_quark(), 0, + "error reading ISO file at lsn %lu", + (long unsigned int) cur_block); + return 0; + } + if (left_bytes < size) { + readed = left_bytes; + } + + is->offset += readed; + } + return readed; +} + +static bool +iso9660_input_eof(struct input_stream *is) +{ + return is->offset == is->size; +} + +/* exported structures */ + +static const char *const iso9660_archive_extensions[] = { + "iso", + NULL +}; + +const struct input_plugin iso9660_input_plugin = { + nullptr, + nullptr, + nullptr, + nullptr, + iso9660_input_close, + nullptr, + nullptr, + nullptr, + nullptr, + iso9660_input_read, + iso9660_input_eof, + nullptr, +}; + +const struct archive_plugin iso9660_archive_plugin = { + "iso", + nullptr, + nullptr, + iso9660_archive_open, + iso9660_archive_extensions, +}; diff --git a/src/archive/Iso9660ArchivePlugin.hxx b/src/archive/Iso9660ArchivePlugin.hxx new file mode 100644 index 000000000..6fbab6159 --- /dev/null +++ b/src/archive/Iso9660ArchivePlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_ISO9660_HXX +#define MPD_ARCHIVE_ISO9660_HXX + +extern const struct archive_plugin iso9660_archive_plugin; + +#endif diff --git a/src/archive/ZzipArchivePlugin.cxx b/src/archive/ZzipArchivePlugin.cxx new file mode 100644 index 000000000..d0db7aa37 --- /dev/null +++ b/src/archive/ZzipArchivePlugin.cxx @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * zip archive handling (requires zziplib) + */ + +#include "config.h" +#include "ZzipArchivePlugin.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "ArchiveVisitor.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "util/RefCount.hxx" + +#include <zzip/zzip.h> +#include <glib.h> +#include <string.h> + +class ZzipArchiveFile final : public ArchiveFile { +public: + RefCount ref; + + ZZIP_DIR *const dir; + + ZzipArchiveFile(ZZIP_DIR *_dir) + :ArchiveFile(zzip_archive_plugin), dir(_dir) {} + + ~ZzipArchiveFile() { + zzip_dir_close(dir); + } + + void Unref() { + if (ref.Decrement()) + delete this; + } + + virtual void Close() override { + Unref(); + } + + virtual void Visit(ArchiveVisitor &visitor) override; + + virtual input_stream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + GError **error_r) override; +}; + +extern const struct input_plugin zzip_input_plugin; + +static inline GQuark +zzip_quark(void) +{ + return g_quark_from_static_string("zzip"); +} + +/* archive open && listing routine */ + +static ArchiveFile * +zzip_archive_open(const char *pathname, GError **error_r) +{ + ZZIP_DIR *dir = zzip_dir_open(pathname, NULL); + if (dir == nullptr) { + g_set_error(error_r, zzip_quark(), 0, + "Failed to open ZIP file %s", pathname); + return NULL; + } + + return new ZzipArchiveFile(dir); +} + +inline void +ZzipArchiveFile::Visit(ArchiveVisitor &visitor) +{ + zzip_rewinddir(dir); + + ZZIP_DIRENT dirent; + while (zzip_dir_read(dir, &dirent)) + //add only files + if (dirent.st_size > 0) + visitor.VisitArchiveEntry(dirent.d_name); +} + +/* single archive handling */ + +struct ZzipInputStream { + struct input_stream base; + + ZzipArchiveFile *archive; + + ZZIP_FILE *file; + + ZzipInputStream(ZzipArchiveFile &_archive, const char *uri, + Mutex &mutex, Cond &cond, + ZZIP_FILE *_file) + :base(zzip_input_plugin, uri, mutex, cond), + archive(&_archive), file(_file) { + base.ready = true; + //we are seekable (but its not recommendent to do so) + base.seekable = true; + + ZZIP_STAT z_stat; + zzip_file_stat(file, &z_stat); + base.size = z_stat.st_size; + + archive->ref.Increment(); + } + + ~ZzipInputStream() { + zzip_file_close(file); + archive->Unref(); + } +}; + +input_stream * +ZzipArchiveFile::OpenStream(const char *pathname, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + ZZIP_FILE *_file = zzip_file_open(dir, pathname, 0); + if (_file == nullptr) { + g_set_error(error_r, zzip_quark(), 0, + "not found in the ZIP file: %s", pathname); + return NULL; + } + + ZzipInputStream *zis = + new ZzipInputStream(*this, pathname, + mutex, cond, + _file); + return &zis->base; +} + +static void +zzip_input_close(struct input_stream *is) +{ + ZzipInputStream *zis = (ZzipInputStream *)is; + + delete zis; +} + +static size_t +zzip_input_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + ZzipInputStream *zis = (ZzipInputStream *)is; + int ret; + + ret = zzip_file_read(zis->file, ptr, size); + if (ret < 0) { + g_set_error(error_r, zzip_quark(), ret, + "zzip_file_read() has failed"); + return 0; + } + + is->offset = zzip_tell(zis->file); + + return ret; +} + +static bool +zzip_input_eof(struct input_stream *is) +{ + ZzipInputStream *zis = (ZzipInputStream *)is; + + return (goffset)zzip_tell(zis->file) == is->size; +} + +static bool +zzip_input_seek(struct input_stream *is, + goffset offset, int whence, GError **error_r) +{ + ZzipInputStream *zis = (ZzipInputStream *)is; + zzip_off_t ofs = zzip_seek(zis->file, offset, whence); + if (ofs != -1) { + g_set_error(error_r, zzip_quark(), ofs, + "zzip_seek() has failed"); + is->offset = ofs; + return true; + } + return false; +} + +/* exported structures */ + +static const char *const zzip_archive_extensions[] = { + "zip", + NULL +}; + +const struct input_plugin zzip_input_plugin = { + nullptr, + nullptr, + nullptr, + nullptr, + zzip_input_close, + nullptr, + nullptr, + nullptr, + nullptr, + zzip_input_read, + zzip_input_eof, + zzip_input_seek, +}; + +const struct archive_plugin zzip_archive_plugin = { + "zzip", + nullptr, + nullptr, + zzip_archive_open, + zzip_archive_extensions, +}; diff --git a/src/archive/ZzipArchivePlugin.hxx b/src/archive/ZzipArchivePlugin.hxx new file mode 100644 index 000000000..4ba16849b --- /dev/null +++ b/src/archive/ZzipArchivePlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_ZZIP_HXX +#define MPD_ARCHIVE_ZZIP_HXX + +extern const struct archive_plugin zzip_archive_plugin; + +#endif diff --git a/src/archive/bz2_archive_plugin.c b/src/archive/bz2_archive_plugin.c deleted file mode 100644 index e2420048b..000000000 --- a/src/archive/bz2_archive_plugin.c +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * single bz2 archive handling (requires libbz2) - */ - -#include "config.h" -#include "archive/bz2_archive_plugin.h" -#include "archive_api.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "refcount.h" - -#include <stdint.h> -#include <stddef.h> -#include <string.h> -#include <glib.h> -#include <bzlib.h> - -#ifdef HAVE_OLDER_BZIP2 -#define BZ2_bzDecompressInit bzDecompressInit -#define BZ2_bzDecompress bzDecompress -#endif - -struct bz2_archive_file { - struct archive_file base; - - struct refcount ref; - - char *name; - bool reset; - struct input_stream *istream; -}; - -struct bz2_input_stream { - struct input_stream base; - - struct bz2_archive_file *archive; - - bool eof; - - bz_stream bzstream; - - char buffer[5000]; -}; - -static const struct input_plugin bz2_inputplugin; - -static inline GQuark -bz2_quark(void) -{ - return g_quark_from_static_string("bz2"); -} - -/* single archive handling allocation helpers */ - -static bool -bz2_alloc(struct bz2_input_stream *data, GError **error_r) -{ - int ret; - - data->bzstream.bzalloc = NULL; - data->bzstream.bzfree = NULL; - data->bzstream.opaque = NULL; - - data->bzstream.next_in = (void *) data->buffer; - data->bzstream.avail_in = 0; - - ret = BZ2_bzDecompressInit(&data->bzstream, 0, 0); - if (ret != BZ_OK) { - g_free(data); - - g_set_error(error_r, bz2_quark(), ret, - "BZ2_bzDecompressInit() has failed"); - return false; - } - - return true; -} - -static void -bz2_destroy(struct bz2_input_stream *data) -{ - BZ2_bzDecompressEnd(&data->bzstream); -} - -/* archive open && listing routine */ - -#if GCC_CHECK_VERSION(4, 2) -/* workaround for a warning caused by G_STATIC_MUTEX_INIT */ -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif - -static struct archive_file * -bz2_open(const char *pathname, GError **error_r) -{ - struct bz2_archive_file *context; - int len; - - context = g_malloc(sizeof(*context)); - archive_file_init(&context->base, &bz2_archive_plugin); - refcount_init(&context->ref); - - //open archive - static GStaticMutex mutex = G_STATIC_MUTEX_INIT; - context->istream = input_stream_open(pathname, - g_static_mutex_get_mutex(&mutex), - NULL, - error_r); - if (context->istream == NULL) { - g_free(context); - return NULL; - } - - context->name = g_path_get_basename(pathname); - - //remove suffix - len = strlen(context->name); - if (len > 4) { - context->name[len - 4] = 0; //remove .bz2 suffix - } - - return &context->base; -} - -static void -bz2_scan_reset(struct archive_file *file) -{ - struct bz2_archive_file *context = (struct bz2_archive_file *) file; - context->reset = true; -} - -static char * -bz2_scan_next(struct archive_file *file) -{ - struct bz2_archive_file *context = (struct bz2_archive_file *) file; - char *name = NULL; - - if (context->reset) { - name = context->name; - context->reset = false; - } - - return name; -} - -static void -bz2_close(struct archive_file *file) -{ - struct bz2_archive_file *context = (struct bz2_archive_file *) file; - - if (!refcount_dec(&context->ref)) - return; - - g_free(context->name); - - input_stream_close(context->istream); - g_free(context); -} - -/* single archive handling */ - -static struct input_stream * -bz2_open_stream(struct archive_file *file, const char *path, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct bz2_archive_file *context = (struct bz2_archive_file *) file; - struct bz2_input_stream *bis = g_new(struct bz2_input_stream, 1); - - input_stream_init(&bis->base, &bz2_inputplugin, path, - mutex, cond); - - bis->archive = context; - - bis->base.ready = true; - bis->base.seekable = false; - - if (!bz2_alloc(bis, error_r)) { - input_stream_deinit(&bis->base); - g_free(bis); - return NULL; - } - - bis->eof = false; - - refcount_inc(&context->ref); - - return &bis->base; -} - -static void -bz2_is_close(struct input_stream *is) -{ - struct bz2_input_stream *bis = (struct bz2_input_stream *)is; - - bz2_destroy(bis); - - bz2_close(&bis->archive->base); - - input_stream_deinit(&bis->base); - g_free(bis); -} - -static bool -bz2_fillbuffer(struct bz2_input_stream *bis, GError **error_r) -{ - size_t count; - bz_stream *bzstream; - - bzstream = &bis->bzstream; - - if (bzstream->avail_in > 0) - return true; - - count = input_stream_read(bis->archive->istream, - bis->buffer, sizeof(bis->buffer), - error_r); - if (count == 0) - return false; - - bzstream->next_in = bis->buffer; - bzstream->avail_in = count; - return true; -} - -static size_t -bz2_is_read(struct input_stream *is, void *ptr, size_t length, - GError **error_r) -{ - struct bz2_input_stream *bis = (struct bz2_input_stream *)is; - bz_stream *bzstream; - int bz_result; - size_t nbytes = 0; - - if (bis->eof) - return 0; - - bzstream = &bis->bzstream; - bzstream->next_out = ptr; - bzstream->avail_out = length; - - do { - if (!bz2_fillbuffer(bis, error_r)) - return 0; - - bz_result = BZ2_bzDecompress(bzstream); - - if (bz_result == BZ_STREAM_END) { - bis->eof = true; - break; - } - - if (bz_result != BZ_OK) { - g_set_error(error_r, bz2_quark(), bz_result, - "BZ2_bzDecompress() has failed"); - return 0; - } - } while (bzstream->avail_out == length); - - nbytes = length - bzstream->avail_out; - is->offset += nbytes; - - return nbytes; -} - -static bool -bz2_is_eof(struct input_stream *is) -{ - struct bz2_input_stream *bis = (struct bz2_input_stream *)is; - - return bis->eof; -} - -/* exported structures */ - -static const char *const bz2_extensions[] = { - "bz2", - NULL -}; - -static const struct input_plugin bz2_inputplugin = { - .close = bz2_is_close, - .read = bz2_is_read, - .eof = bz2_is_eof, -}; - -const struct archive_plugin bz2_archive_plugin = { - .name = "bz2", - .open = bz2_open, - .scan_reset = bz2_scan_reset, - .scan_next = bz2_scan_next, - .open_stream = bz2_open_stream, - .close = bz2_close, - .suffixes = bz2_extensions -}; - diff --git a/src/archive/bz2_archive_plugin.h b/src/archive/bz2_archive_plugin.h deleted file mode 100644 index 46c69a66c..000000000 --- a/src/archive/bz2_archive_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_BZ2_H -#define MPD_ARCHIVE_BZ2_H - -extern const struct archive_plugin bz2_archive_plugin; - -#endif diff --git a/src/archive/iso9660_archive_plugin.c b/src/archive/iso9660_archive_plugin.c deleted file mode 100644 index bb6cb9588..000000000 --- a/src/archive/iso9660_archive_plugin.c +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * iso archive handling (requires cdio, and iso9660) - */ - -#include "config.h" -#include "archive/iso9660_archive_plugin.h" -#include "archive_api.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "refcount.h" - -#include <cdio/cdio.h> -#include <cdio/iso9660.h> - -#include <glib.h> -#include <string.h> - -#define CEILING(x, y) ((x+(y-1))/y) - -struct iso9660_archive_file { - struct archive_file base; - - struct refcount ref; - - iso9660_t *iso; - GSList *list; - GSList *iter; -}; - -static const struct input_plugin iso9660_input_plugin; - -static inline GQuark -iso9660_quark(void) -{ - return g_quark_from_static_string("iso9660"); -} - -/* archive open && listing routine */ - -static void -listdir_recur(const char *psz_path, struct iso9660_archive_file *context) -{ - iso9660_t *iso = context->iso; - CdioList_t *entlist; - CdioListNode_t *entnode; - iso9660_stat_t *statbuf; - char pathname[4096]; - - entlist = iso9660_ifs_readdir (iso, psz_path); - if (!entlist) { - return; - } - /* Iterate over the list of nodes that iso9660_ifs_readdir gives */ - _CDIO_LIST_FOREACH (entnode, entlist) { - statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode); - - strcpy(pathname, psz_path); - strcat(pathname, statbuf->filename); - - if (_STAT_DIR == statbuf->type ) { - if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) { - strcat(pathname, "/"); - listdir_recur(pathname, context); - } - } else { - //remove leading / - context->list = g_slist_prepend( context->list, - g_strdup(pathname + 1)); - } - } - _cdio_list_free (entlist, true); -} - -static struct archive_file * -iso9660_archive_open(const char *pathname, GError **error_r) -{ - struct iso9660_archive_file *context = - g_new(struct iso9660_archive_file, 1); - - archive_file_init(&context->base, &iso9660_archive_plugin); - refcount_init(&context->ref); - - context->list = NULL; - - /* open archive */ - context->iso = iso9660_open (pathname); - if (context->iso == NULL) { - g_set_error(error_r, iso9660_quark(), 0, - "Failed to open ISO9660 file %s", pathname); - return NULL; - } - - listdir_recur("/", context); - - return &context->base; -} - -static void -iso9660_archive_scan_reset(struct archive_file *file) -{ - struct iso9660_archive_file *context = - (struct iso9660_archive_file *)file; - - //reset iterator - context->iter = context->list; -} - -static char * -iso9660_archive_scan_next(struct archive_file *file) -{ - struct iso9660_archive_file *context = - (struct iso9660_archive_file *)file; - - char *data = NULL; - if (context->iter != NULL) { - ///fetch data and goto next - data = context->iter->data; - context->iter = g_slist_next(context->iter); - } - return data; -} - -static void -iso9660_archive_close(struct archive_file *file) -{ - struct iso9660_archive_file *context = - (struct iso9660_archive_file *)file; - GSList *tmp; - - if (!refcount_dec(&context->ref)) - return; - - if (context->list) { - //free list - for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp)) - g_free(tmp->data); - g_slist_free(context->list); - } - //close archive - iso9660_close(context->iso); - - g_free(context); -} - -/* single archive handling */ - -struct iso9660_input_stream { - struct input_stream base; - - struct iso9660_archive_file *archive; - - iso9660_stat_t *statbuf; - size_t max_blocks; -}; - -static struct input_stream * -iso9660_archive_open_stream(struct archive_file *file, const char *pathname, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct iso9660_archive_file *context = - (struct iso9660_archive_file *)file; - struct iso9660_input_stream *iis; - - iis = g_new(struct iso9660_input_stream, 1); - input_stream_init(&iis->base, &iso9660_input_plugin, pathname, - mutex, cond); - - iis->archive = context; - iis->statbuf = iso9660_ifs_stat_translate(context->iso, pathname); - if (iis->statbuf == NULL) { - g_free(iis); - g_set_error(error_r, iso9660_quark(), 0, - "not found in the ISO file: %s", pathname); - return NULL; - } - - iis->base.ready = true; - //we are not seekable - iis->base.seekable = false; - - iis->base.size = iis->statbuf->size; - - iis->max_blocks = CEILING(iis->statbuf->size, ISO_BLOCKSIZE); - - refcount_inc(&context->ref); - - return &iis->base; -} - -static void -iso9660_input_close(struct input_stream *is) -{ - struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is; - - g_free(iis->statbuf); - - iso9660_archive_close(&iis->archive->base); - - input_stream_deinit(&iis->base); - g_free(iis); -} - - -static size_t -iso9660_input_read(struct input_stream *is, void *ptr, size_t size, GError **error_r) -{ - struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is; - int toread, readed = 0; - int no_blocks, cur_block; - size_t left_bytes = iis->statbuf->size - is->offset; - - size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE; - - if (left_bytes < size) { - toread = left_bytes; - no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE); - } else { - toread = size; - no_blocks = toread / ISO_BLOCKSIZE; - } - if (no_blocks > 0) { - - cur_block = is->offset / ISO_BLOCKSIZE; - - readed = iso9660_iso_seek_read (iis->archive->iso, ptr, - iis->statbuf->lsn + cur_block, no_blocks); - - if (readed != no_blocks * ISO_BLOCKSIZE) { - g_set_error(error_r, iso9660_quark(), 0, - "error reading ISO file at lsn %lu", - (long unsigned int) cur_block); - return 0; - } - if (left_bytes < size) { - readed = left_bytes; - } - - is->offset += readed; - } - return readed; -} - -static bool -iso9660_input_eof(struct input_stream *is) -{ - return is->offset == is->size; -} - -/* exported structures */ - -static const char *const iso9660_archive_extensions[] = { - "iso", - NULL -}; - -static const struct input_plugin iso9660_input_plugin = { - .close = iso9660_input_close, - .read = iso9660_input_read, - .eof = iso9660_input_eof, -}; - -const struct archive_plugin iso9660_archive_plugin = { - .name = "iso", - .open = iso9660_archive_open, - .scan_reset = iso9660_archive_scan_reset, - .scan_next = iso9660_archive_scan_next, - .open_stream = iso9660_archive_open_stream, - .close = iso9660_archive_close, - .suffixes = iso9660_archive_extensions -}; diff --git a/src/archive/iso9660_archive_plugin.h b/src/archive/iso9660_archive_plugin.h deleted file mode 100644 index 47dc6e474..000000000 --- a/src/archive/iso9660_archive_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_ISO9660_H -#define MPD_ARCHIVE_ISO9660_H - -extern const struct archive_plugin iso9660_archive_plugin; - -#endif diff --git a/src/archive/zzip_archive_plugin.c b/src/archive/zzip_archive_plugin.c deleted file mode 100644 index ad96b5f89..000000000 --- a/src/archive/zzip_archive_plugin.c +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * zip archive handling (requires zziplib) - */ - -#include "config.h" -#include "archive/zzip_archive_plugin.h" -#include "archive_api.h" -#include "archive_api.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "refcount.h" - -#include <zzip/zzip.h> -#include <glib.h> -#include <string.h> - -struct zzip_archive { - struct archive_file base; - - struct refcount ref; - - ZZIP_DIR *dir; - GSList *list; - GSList *iter; -}; - -static const struct input_plugin zzip_input_plugin; - -static inline GQuark -zzip_quark(void) -{ - return g_quark_from_static_string("zzip"); -} - -/* archive open && listing routine */ - -static struct archive_file * -zzip_archive_open(const char *pathname, GError **error_r) -{ - struct zzip_archive *context = g_malloc(sizeof(*context)); - ZZIP_DIRENT dirent; - - archive_file_init(&context->base, &zzip_archive_plugin); - refcount_init(&context->ref); - - // open archive - context->list = NULL; - context->dir = zzip_dir_open(pathname, NULL); - if (context->dir == NULL) { - g_set_error(error_r, zzip_quark(), 0, - "Failed to open ZIP file %s", pathname); - return NULL; - } - - while (zzip_dir_read(context->dir, &dirent)) { - //add only files - if (dirent.st_size > 0) { - context->list = g_slist_prepend(context->list, - g_strdup(dirent.d_name)); - } - } - - return &context->base; -} - -static void -zzip_archive_scan_reset(struct archive_file *file) -{ - struct zzip_archive *context = (struct zzip_archive *) file; - //reset iterator - context->iter = context->list; -} - -static char * -zzip_archive_scan_next(struct archive_file *file) -{ - struct zzip_archive *context = (struct zzip_archive *) file; - char *data = NULL; - if (context->iter != NULL) { - ///fetch data and goto next - data = context->iter->data; - context->iter = g_slist_next(context->iter); - } - return data; -} - -static void -zzip_archive_close(struct archive_file *file) -{ - struct zzip_archive *context = (struct zzip_archive *) file; - - if (!refcount_dec(&context->ref)) - return; - - if (context->list) { - //free list - for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp)) - g_free(tmp->data); - g_slist_free(context->list); - } - //close archive - zzip_dir_close (context->dir); - - g_free(context); -} - -/* single archive handling */ - -struct zzip_input_stream { - struct input_stream base; - - struct zzip_archive *archive; - - ZZIP_FILE *file; -}; - -static struct input_stream * -zzip_archive_open_stream(struct archive_file *file, - const char *pathname, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct zzip_archive *context = (struct zzip_archive *) file; - struct zzip_input_stream *zis; - ZZIP_STAT z_stat; - - zis = g_new(struct zzip_input_stream, 1); - input_stream_init(&zis->base, &zzip_input_plugin, pathname, - mutex, cond); - - zis->archive = context; - zis->file = zzip_file_open(context->dir, pathname, 0); - if (zis->file == NULL) { - g_free(zis); - g_set_error(error_r, zzip_quark(), 0, - "not found in the ZIP file: %s", pathname); - return NULL; - } - - zis->base.ready = true; - //we are seekable (but its not recommendent to do so) - zis->base.seekable = true; - - zzip_file_stat(zis->file, &z_stat); - zis->base.size = z_stat.st_size; - - refcount_inc(&context->ref); - - return &zis->base; -} - -static void -zzip_input_close(struct input_stream *is) -{ - struct zzip_input_stream *zis = (struct zzip_input_stream *)is; - - zzip_file_close(zis->file); - zzip_archive_close(&zis->archive->base); - input_stream_deinit(&zis->base); - g_free(zis); -} - -static size_t -zzip_input_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct zzip_input_stream *zis = (struct zzip_input_stream *)is; - int ret; - - ret = zzip_file_read(zis->file, ptr, size); - if (ret < 0) { - g_set_error(error_r, zzip_quark(), ret, - "zzip_file_read() has failed"); - return 0; - } - - is->offset = zzip_tell(zis->file); - - return ret; -} - -static bool -zzip_input_eof(struct input_stream *is) -{ - struct zzip_input_stream *zis = (struct zzip_input_stream *)is; - - return (goffset)zzip_tell(zis->file) == is->size; -} - -static bool -zzip_input_seek(struct input_stream *is, - goffset offset, int whence, GError **error_r) -{ - struct zzip_input_stream *zis = (struct zzip_input_stream *)is; - zzip_off_t ofs = zzip_seek(zis->file, offset, whence); - if (ofs != -1) { - g_set_error(error_r, zzip_quark(), ofs, - "zzip_seek() has failed"); - is->offset = ofs; - return true; - } - return false; -} - -/* exported structures */ - -static const char *const zzip_archive_extensions[] = { - "zip", - NULL -}; - -static const struct input_plugin zzip_input_plugin = { - .close = zzip_input_close, - .read = zzip_input_read, - .eof = zzip_input_eof, - .seek = zzip_input_seek, -}; - -const struct archive_plugin zzip_archive_plugin = { - .name = "zzip", - .open = zzip_archive_open, - .scan_reset = zzip_archive_scan_reset, - .scan_next = zzip_archive_scan_next, - .open_stream = zzip_archive_open_stream, - .close = zzip_archive_close, - .suffixes = zzip_archive_extensions -}; diff --git a/src/archive/zzip_archive_plugin.h b/src/archive/zzip_archive_plugin.h deleted file mode 100644 index 2b2c01e5a..000000000 --- a/src/archive/zzip_archive_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_ZZIP_H -#define MPD_ARCHIVE_ZZIP_H - -extern const struct archive_plugin zzip_archive_plugin; - -#endif diff --git a/src/archive_api.c b/src/archive_api.c deleted file mode 100644 index be3c35f7e..000000000 --- a/src/archive_api.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "archive_api.h" - -#include <stdio.h> - -#include <string.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <glib.h> - -/** - * - * archive_lookup is used to determine if part of pathname refers to an regular - * file (archive). If so then its also used to split pathname into archive file - * and path used to locate file in archive. It also returns suffix of the file. - * How it works: - * We do stat of the parent of input pathname as long as we find an regular file - * Normally this should never happen. When routine returns true pathname modified - * and split into archive, inpath and suffix. Otherwise nothing happens - * - * For example: - * - * /music/path/Talco.zip/Talco - Combat Circus/12 - A la pachenka.mp3 - * is split into archive: /music/path/Talco.zip - * inarchive pathname: Talco - Combat Circus/12 - A la pachenka.mp3 - * and suffix: zip - */ - -bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix) -{ - char *pathdupe; - int len, idx; - struct stat st_info; - bool ret = false; - - *archive = NULL; - *inpath = NULL; - *suffix = NULL; - - pathdupe = g_strdup(pathname); - len = idx = strlen(pathname); - - while (idx > 0) { - //try to stat if its real directory - if (stat(pathdupe, &st_info) == -1) { - if (errno != ENOTDIR) { - g_warning("stat %s failed (errno=%d)\n", pathdupe, errno); - break; - } - } else { - //is something found ins original path (is not an archive) - if (idx == len) { - break; - } - //its a file ? - if (S_ISREG(st_info.st_mode)) { - //so the upper should be file - pathname[idx] = 0; - ret = true; - *archive = pathname; - *inpath = pathname + idx+1; - - //try to get suffix - *suffix = NULL; - while (idx > 0) { - if (pathname[idx] == '.') { - *suffix = pathname + idx + 1; - break; - } - idx--; - } - break; - } else { - g_warning("not a regular file %s\n", pathdupe); - break; - } - } - //find one dir up - while (idx > 0) { - if (pathdupe[idx] == '/') { - pathdupe[idx] = 0; - break; - } - idx--; - } - } - g_free(pathdupe); - return ret; -} - diff --git a/src/archive_api.h b/src/archive_api.h deleted file mode 100644 index 4e0f603f5..000000000 --- a/src/archive_api.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_API_H -#define MPD_ARCHIVE_API_H - -/* - * This is the public API which is used by archive plugins to - * provide transparent archive decompression layer for mpd - * - */ - -#include "archive_internal.h" -#include "archive_plugin.h" -#include "input_stream.h" - -#include <stdbool.h> - -bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix); - -#endif - diff --git a/src/archive_internal.h b/src/archive_internal.h deleted file mode 100644 index 0d885e91c..000000000 --- a/src/archive_internal.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_INTERNAL_H -#define MPD_ARCHIVE_INTERNAL_H - -struct archive_file { - const struct archive_plugin *plugin; -}; - -static inline void -archive_file_init(struct archive_file *archive_file, - const struct archive_plugin *plugin) -{ - archive_file->plugin = plugin; -} - -#endif diff --git a/src/archive_list.c b/src/archive_list.c deleted file mode 100644 index e23567bdb..000000000 --- a/src/archive_list.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "archive_list.h" -#include "archive_plugin.h" -#include "string_util.h" -#include "archive/bz2_archive_plugin.h" -#include "archive/iso9660_archive_plugin.h" -#include "archive/zzip_archive_plugin.h" - -#include <string.h> -#include <glib.h> - -const struct archive_plugin *const archive_plugins[] = { -#ifdef HAVE_BZ2 - &bz2_archive_plugin, -#endif -#ifdef HAVE_ZZIP - &zzip_archive_plugin, -#endif -#ifdef HAVE_ISO9660 - &iso9660_archive_plugin, -#endif - NULL -}; - -/** which plugins have been initialized successfully? */ -static bool archive_plugins_enabled[G_N_ELEMENTS(archive_plugins) - 1]; - -#define archive_plugins_for_each_enabled(plugin) \ - archive_plugins_for_each(plugin) \ - if (archive_plugins_enabled[archive_plugin_iterator - archive_plugins]) - -const struct archive_plugin * -archive_plugin_from_suffix(const char *suffix) -{ - if (suffix == NULL) - return NULL; - - archive_plugins_for_each_enabled(plugin) - if (plugin->suffixes != NULL && - string_array_contains(plugin->suffixes, suffix)) - return plugin; - - return NULL; -} - -const struct archive_plugin * -archive_plugin_from_name(const char *name) -{ - archive_plugins_for_each_enabled(plugin) - if (strcmp(plugin->name, name) == 0) - return plugin; - - return NULL; -} - -void archive_plugin_init_all(void) -{ - for (unsigned i = 0; archive_plugins[i] != NULL; ++i) { - const struct archive_plugin *plugin = archive_plugins[i]; - if (plugin->init == NULL || archive_plugins[i]->init()) - archive_plugins_enabled[i] = true; - } -} - -void archive_plugin_deinit_all(void) -{ - archive_plugins_for_each_enabled(plugin) - if (plugin->finish != NULL) - plugin->finish(); -} - diff --git a/src/archive_list.h b/src/archive_list.h deleted file mode 100644 index f944583ed..000000000 --- a/src/archive_list.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_LIST_H -#define MPD_ARCHIVE_LIST_H - -struct archive_plugin; - -extern const struct archive_plugin *const archive_plugins[]; - -#define archive_plugins_for_each(plugin) \ - for (const struct archive_plugin *plugin, \ - *const*archive_plugin_iterator = &archive_plugins[0]; \ - (plugin = *archive_plugin_iterator) != NULL; \ - ++archive_plugin_iterator) - -/* interface for using plugins */ - -const struct archive_plugin * -archive_plugin_from_suffix(const char *suffix); - -const struct archive_plugin * -archive_plugin_from_name(const char *name); - -/* this is where we "load" all the "plugins" ;-) */ -void archive_plugin_init_all(void); - -/* this is where we "unload" all the "plugins" */ -void archive_plugin_deinit_all(void); - -#endif diff --git a/src/archive_plugin.c b/src/archive_plugin.c deleted file mode 100644 index cf23e6393..000000000 --- a/src/archive_plugin.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "archive_plugin.h" -#include "archive_internal.h" - -#include <assert.h> - -struct archive_file * -archive_file_open(const struct archive_plugin *plugin, const char *path, - GError **error_r) -{ - struct archive_file *file; - - assert(plugin != NULL); - assert(plugin->open != NULL); - assert(path != NULL); - assert(error_r == NULL || *error_r == NULL); - - file = plugin->open(path, error_r); - - if (file != NULL) { - assert(file->plugin != NULL); - assert(file->plugin->close != NULL); - assert(file->plugin->scan_reset != NULL); - assert(file->plugin->scan_next != NULL); - assert(file->plugin->open_stream != NULL); - assert(error_r == NULL || *error_r == NULL); - } else { - assert(error_r == NULL || *error_r != NULL); - } - - return file; -} - -void -archive_file_close(struct archive_file *file) -{ - assert(file != NULL); - assert(file->plugin != NULL); - assert(file->plugin->close != NULL); - - file->plugin->close(file); -} - -void -archive_file_scan_reset(struct archive_file *file) -{ - assert(file != NULL); - assert(file->plugin != NULL); - assert(file->plugin->scan_reset != NULL); - assert(file->plugin->scan_next != NULL); - - file->plugin->scan_reset(file); -} - -char * -archive_file_scan_next(struct archive_file *file) -{ - assert(file != NULL); - assert(file->plugin != NULL); - assert(file->plugin->scan_next != NULL); - - return file->plugin->scan_next(file); -} - -struct input_stream * -archive_file_open_stream(struct archive_file *file, const char *path, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - assert(file != NULL); - assert(file->plugin != NULL); - assert(file->plugin->open_stream != NULL); - - return file->plugin->open_stream(file, path, mutex, cond, - error_r); -} diff --git a/src/archive_plugin.h b/src/archive_plugin.h deleted file mode 100644 index b7b92446d..000000000 --- a/src/archive_plugin.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_PLUGIN_H -#define MPD_ARCHIVE_PLUGIN_H - -#include <glib.h> - -#include <stdbool.h> - -struct input_stream; -struct archive_file; - -struct archive_plugin { - const char *name; - - /** - * optional, set this to NULL if the archive plugin doesn't - * have/need one this must false if there is an error and - * true otherwise - */ - bool (*init)(void); - - /** - * optional, set this to NULL if the archive plugin doesn't - * have/need one - */ - void (*finish)(void); - - /** - * tryes to open archive file and associates handle with archive - * returns pointer to handle used is all operations with this archive - * or NULL when opening fails - */ - struct archive_file *(*open)(const char *path_fs, GError **error_r); - - /** - * reset routine will move current read index in archive to default - * position and then the filenames from archives can be read - * via scan_next routine - */ - void (*scan_reset)(struct archive_file *); - - /** - * the read method will return corresponding files from archive - * (as pathnames) and move read index to next file. When there is no - * next file it return NULL. - */ - char *(*scan_next)(struct archive_file *); - - /** - * Opens an input_stream of a file within the archive. - * - * @param path the path within the archive - * @param error_r location to store the error occurring, or - * NULL to ignore errors - */ - struct input_stream *(*open_stream)(struct archive_file *af, - const char *path, - GMutex *mutex, GCond *cond, - GError **error_r); - - /** - * closes archive file. - */ - void (*close)(struct archive_file *); - - /** - * suffixes handled by this plugin. - * last element in these arrays must always be a NULL - */ - const char *const*suffixes; -}; - -struct archive_file * -archive_file_open(const struct archive_plugin *plugin, const char *path, - GError **error_r); - -void -archive_file_close(struct archive_file *file); - -void -archive_file_scan_reset(struct archive_file *file); - -char * -archive_file_scan_next(struct archive_file *file); - -struct input_stream * -archive_file_open_stream(struct archive_file *file, const char *path, - GMutex *mutex, GCond *cond, - GError **error_r); - -#endif diff --git a/src/audio_check.h b/src/audio_check.h index 9f71cf9c0..d4d3f13fd 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_config.c b/src/audio_config.c deleted file mode 100644 index 72869c384..000000000 --- a/src/audio_config.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "audio_config.h" -#include "audio_format.h" -#include "audio_parser.h" -#include "output_internal.h" -#include "output_plugin.h" -#include "output_all.h" -#include "conf.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> - -static struct audio_format configured_audio_format; - -void getOutputAudioFormat(const struct audio_format *inAudioFormat, - struct audio_format *outAudioFormat) -{ - *outAudioFormat = *inAudioFormat; - audio_format_mask_apply(outAudioFormat, &configured_audio_format); -} - -void initAudioConfig(void) -{ - const struct config_param *param = config_get_param(CONF_AUDIO_OUTPUT_FORMAT); - GError *error = NULL; - bool ret; - - if (param == NULL) - return; - - ret = audio_format_parse(&configured_audio_format, param->value, - true, &error); - if (!ret) - MPD_ERROR("error parsing \"%s\" at line %i: %s", - CONF_AUDIO_OUTPUT_FORMAT, param->line, - error->message); -} diff --git a/src/audio_config.h b/src/audio_config.h deleted file mode 100644 index 85143247f..000000000 --- a/src/audio_config.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_AUDIO_CONFIG_H -#define MPD_AUDIO_CONFIG_H - -#include <stdbool.h> - -struct audio_format; - -void getOutputAudioFormat(const struct audio_format *inFormat, - struct audio_format *outFormat); - -/* make sure initPlayerData is called before this function!! */ -void initAudioConfig(void); - -#endif diff --git a/src/audio_format.h b/src/audio_format.h index bf77add3b..f9b176bc1 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/audio_parser.c b/src/audio_parser.c deleted file mode 100644 index 152eab5d4..000000000 --- a/src/audio_parser.c +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Parser functions for audio related objects. - * - */ - -#include "config.h" -#include "audio_parser.h" -#include "audio_format.h" -#include "audio_check.h" -#include "gcc.h" - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -/** - * The GLib quark used for errors reported by this library. - */ -static inline GQuark -audio_parser_quark(void) -{ - return g_quark_from_static_string("audio_parser"); -} - -static bool -parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r, - const char **endptr_r, GError **error_r) -{ - unsigned long value; - char *endptr; - - if (mask && *src == '*') { - *sample_rate_r = 0; - *endptr_r = src + 1; - return true; - } - - value = strtoul(src, &endptr, 10); - if (endptr == src) { - g_set_error(error_r, audio_parser_quark(), 0, - "Failed to parse the sample rate"); - return false; - } else if (!audio_check_sample_rate(value, error_r)) - return false; - - *sample_rate_r = value; - *endptr_r = endptr; - return true; -} - -static bool -parse_sample_format(const char *src, bool mask, - enum sample_format *sample_format_r, - const char **endptr_r, GError **error_r) -{ - unsigned long value; - char *endptr; - enum sample_format sample_format; - - if (mask && *src == '*') { - *sample_format_r = SAMPLE_FORMAT_UNDEFINED; - *endptr_r = src + 1; - return true; - } - - if (*src == 'f') { - *sample_format_r = SAMPLE_FORMAT_FLOAT; - *endptr_r = src + 1; - return true; - } - - if (memcmp(src, "dsd", 3) == 0) { - *sample_format_r = SAMPLE_FORMAT_DSD; - *endptr_r = src + 3; - return true; - } - - value = strtoul(src, &endptr, 10); - if (endptr == src) { - g_set_error(error_r, audio_parser_quark(), 0, - "Failed to parse the sample format"); - return false; - } - - switch (value) { - case 8: - sample_format = SAMPLE_FORMAT_S8; - break; - - case 16: - sample_format = SAMPLE_FORMAT_S16; - break; - - case 24: - if (memcmp(endptr, "_3", 2) == 0) - /* for backwards compatibility */ - endptr += 2; - - sample_format = SAMPLE_FORMAT_S24_P32; - break; - - case 32: - sample_format = SAMPLE_FORMAT_S32; - break; - - default: - g_set_error(error_r, audio_parser_quark(), 0, - "Invalid sample format: %lu", value); - return false; - } - - assert(audio_valid_sample_format(sample_format)); - - *sample_format_r = sample_format; - *endptr_r = endptr; - return true; -} - -static bool -parse_channel_count(const char *src, bool mask, uint8_t *channels_r, - const char **endptr_r, GError **error_r) -{ - unsigned long value; - char *endptr; - - if (mask && *src == '*') { - *channels_r = 0; - *endptr_r = src + 1; - return true; - } - - value = strtoul(src, &endptr, 10); - if (endptr == src) { - g_set_error(error_r, audio_parser_quark(), 0, - "Failed to parse the channel count"); - return false; - } else if (!audio_check_channel_count(value, error_r)) - return false; - - *channels_r = value; - *endptr_r = endptr; - return true; -} - -bool -audio_format_parse(struct audio_format *dest, const char *src, - bool mask, GError **error_r) -{ - uint32_t rate; - enum sample_format sample_format; - uint8_t channels; - - audio_format_clear(dest); - - /* parse sample rate */ - -#if GCC_CHECK_VERSION(4,7) - /* workaround -Wmaybe-uninitialized false positive */ - rate = 0; -#endif - - if (!parse_sample_rate(src, mask, &rate, &src, error_r)) - return false; - - if (*src++ != ':') { - g_set_error(error_r, audio_parser_quark(), 0, - "Sample format missing"); - return false; - } - - /* parse sample format */ - -#if GCC_CHECK_VERSION(4,7) - /* workaround -Wmaybe-uninitialized false positive */ - sample_format = SAMPLE_FORMAT_UNDEFINED; -#endif - - if (!parse_sample_format(src, mask, &sample_format, &src, error_r)) - return false; - - if (*src++ != ':') { - g_set_error(error_r, audio_parser_quark(), 0, - "Channel count missing"); - return false; - } - - /* parse channel count */ - - if (!parse_channel_count(src, mask, &channels, &src, error_r)) - return false; - - if (*src != 0) { - g_set_error(error_r, audio_parser_quark(), 0, - "Extra data after channel count: %s", src); - return false; - } - - audio_format_init(dest, rate, sample_format, channels); - assert(mask ? audio_format_mask_valid(dest) - : audio_format_valid(dest)); - - return true; -} diff --git a/src/audio_parser.h b/src/audio_parser.h deleted file mode 100644 index a963eb467..000000000 --- a/src/audio_parser.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Parser functions for audio related objects. - */ - -#ifndef AUDIO_PARSER_H -#define AUDIO_PARSER_H - -#include <glib.h> - -#include <stdbool.h> - -struct audio_format; - -/** - * Parses a string in the form "SAMPLE_RATE:BITS:CHANNELS" into an - * #audio_format. - * - * @param dest the destination #audio_format struct - * @param src the input string - * @param mask if true, then "*" is allowed for any number of items - * @param error_r location to store the error occurring, or NULL to - * ignore errors - * @return true on success - */ -bool -audio_format_parse(struct audio_format *dest, const char *src, - bool mask, GError **error_r); - -#endif diff --git a/src/buffer.c b/src/buffer.c deleted file mode 100644 index 559f39a9a..000000000 --- a/src/buffer.c +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "buffer.h" -#include "chunk.h" -#include "poison.h" - -#include <glib.h> - -#include <assert.h> - -struct music_buffer { - struct music_chunk *chunks; - unsigned num_chunks; - - struct music_chunk *available; - - /** a mutex which protects #available */ - GMutex *mutex; - -#ifndef NDEBUG - unsigned num_allocated; -#endif -}; - -struct music_buffer * -music_buffer_new(unsigned num_chunks) -{ - struct music_buffer *buffer; - struct music_chunk *chunk; - - assert(num_chunks > 0); - - buffer = g_new(struct music_buffer, 1); - - buffer->chunks = g_new(struct music_chunk, num_chunks); - buffer->num_chunks = num_chunks; - - chunk = buffer->available = buffer->chunks; - poison_undefined(chunk, sizeof(*chunk)); - - for (unsigned i = 1; i < num_chunks; ++i) { - chunk->next = &buffer->chunks[i]; - chunk = chunk->next; - poison_undefined(chunk, sizeof(*chunk)); - } - - chunk->next = NULL; - - buffer->mutex = g_mutex_new(); - -#ifndef NDEBUG - buffer->num_allocated = 0; -#endif - - return buffer; -} - -void -music_buffer_free(struct music_buffer *buffer) -{ - assert(buffer->chunks != NULL); - assert(buffer->num_chunks > 0); - assert(buffer->num_allocated == 0); - - g_mutex_free(buffer->mutex); - g_free(buffer->chunks); - g_free(buffer); -} - -unsigned -music_buffer_size(const struct music_buffer *buffer) -{ - return buffer->num_chunks; -} - -struct music_chunk * -music_buffer_allocate(struct music_buffer *buffer) -{ - struct music_chunk *chunk; - - g_mutex_lock(buffer->mutex); - - chunk = buffer->available; - if (chunk != NULL) { - buffer->available = chunk->next; - music_chunk_init(chunk); - -#ifndef NDEBUG - ++buffer->num_allocated; -#endif - } - - g_mutex_unlock(buffer->mutex); - return chunk; -} - -void -music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk) -{ - assert(buffer != NULL); - assert(chunk != NULL); - - if (chunk->other != NULL) - music_buffer_return(buffer, chunk->other); - - g_mutex_lock(buffer->mutex); - - music_chunk_free(chunk); - poison_undefined(chunk, sizeof(*chunk)); - - chunk->next = buffer->available; - buffer->available = chunk; - -#ifndef NDEBUG - --buffer->num_allocated; -#endif - - g_mutex_unlock(buffer->mutex); -} diff --git a/src/buffer.h b/src/buffer.h deleted file mode 100644 index f860231e7..000000000 --- a/src/buffer.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_MUSIC_BUFFER_H -#define MPD_MUSIC_BUFFER_H - -/** - * An allocator for #music_chunk objects. - */ -struct music_buffer; - -/** - * Creates a new #music_buffer object. - * - * @param num_chunks the number of #music_chunk reserved in this - * buffer - */ -struct music_buffer * -music_buffer_new(unsigned num_chunks); - -/** - * Frees the #music_buffer object - */ -void -music_buffer_free(struct music_buffer *buffer); - -/** - * Returns the total number of reserved chunks in this buffer. This - * is the same value which was passed to the constructor - * music_buffer_new(). - */ -unsigned -music_buffer_size(const struct music_buffer *buffer); - -/** - * Allocates a chunk from the buffer. When it is not used anymore, - * call music_buffer_return(). - * - * @return an empty chunk or NULL if there are no chunks available - */ -struct music_chunk * -music_buffer_allocate(struct music_buffer *buffer); - -/** - * Returns a chunk to the buffer. It can be reused by - * music_buffer_allocate() then. - */ -void -music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk); - -#endif diff --git a/src/chunk.c b/src/chunk.c deleted file mode 100644 index 1eb96f4b9..000000000 --- a/src/chunk.c +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "chunk.h" -#include "audio_format.h" -#include "tag.h" - -#include <assert.h> - -void -music_chunk_init(struct music_chunk *chunk) -{ - chunk->other = NULL; - chunk->length = 0; - chunk->tag = NULL; - chunk->replay_gain_serial = 0; -} - -void -music_chunk_free(struct music_chunk *chunk) -{ - if (chunk->tag != NULL) - tag_free(chunk->tag); -} - -#ifndef NDEBUG -bool -music_chunk_check_format(const struct music_chunk *chunk, - const struct audio_format *audio_format) -{ - assert(chunk != NULL); - assert(audio_format != NULL); - assert(audio_format_valid(audio_format)); - - return chunk->length == 0 || - audio_format_equals(&chunk->audio_format, audio_format); -} -#endif - -void * -music_chunk_write(struct music_chunk *chunk, - const struct audio_format *audio_format, - float data_time, uint16_t bit_rate, - size_t *max_length_r) -{ - const size_t frame_size = audio_format_frame_size(audio_format); - size_t num_frames; - - assert(music_chunk_check_format(chunk, audio_format)); - assert(chunk->length == 0 || audio_format_valid(&chunk->audio_format)); - - if (chunk->length == 0) { - /* if the chunk is empty, nobody has set bitRate and - times yet */ - - chunk->bit_rate = bit_rate; - chunk->times = data_time; - } - - num_frames = (sizeof(chunk->data) - chunk->length) / frame_size; - if (num_frames == 0) - return NULL; - -#ifndef NDEBUG - chunk->audio_format = *audio_format; -#endif - - *max_length_r = num_frames * frame_size; - return chunk->data + chunk->length; -} - -bool -music_chunk_expand(struct music_chunk *chunk, - const struct audio_format *audio_format, size_t length) -{ - const size_t frame_size = audio_format_frame_size(audio_format); - - assert(chunk != NULL); - assert(chunk->length + length <= sizeof(chunk->data)); - assert(audio_format_equals(&chunk->audio_format, audio_format)); - - chunk->length += length; - - return chunk->length + frame_size > sizeof(chunk->data); -} diff --git a/src/chunk.h b/src/chunk.h deleted file mode 100644 index a06a203eb..000000000 --- a/src/chunk.h +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CHUNK_H -#define MPD_CHUNK_H - -#include "replay_gain_info.h" - -#ifndef NDEBUG -#include "audio_format.h" -#endif - -#include <stdbool.h> -#include <stdint.h> -#include <stddef.h> - -enum { - CHUNK_SIZE = 4096, -}; - -struct audio_format; - -/** - * A chunk of music data. Its format is defined by the - * music_pipe_append() caller. - */ -struct music_chunk { - /** the next chunk in a linked list */ - struct music_chunk *next; - - /** - * An optional chunk which should be mixed into this chunk. - * This is used for cross-fading. - */ - struct music_chunk *other; - - /** - * The current mix ratio for cross-fading: 1.0 means play 100% - * of this chunk, 0.0 means play 100% of the "other" chunk. - */ - float mix_ratio; - - /** number of bytes stored in this chunk */ - uint16_t length; - - /** current bit rate of the source file */ - uint16_t bit_rate; - - /** the time stamp within the song */ - float times; - - /** - * An optional tag associated with this chunk (and the - * following chunks); appears at song boundaries. The tag - * object is owned by this chunk, and must be freed when this - * chunk is deinitialized in music_chunk_free() - */ - struct tag *tag; - - /** - * Replay gain information associated with this chunk. - * Only valid if the serial is not 0. - */ - struct replay_gain_info replay_gain_info; - - /** - * A serial number for checking if replay gain info has - * changed since the last chunk. The magic value 0 indicates - * that there is no replay gain info available. - */ - unsigned replay_gain_serial; - - /** the data (probably PCM) */ - char data[CHUNK_SIZE]; - -#ifndef NDEBUG - struct audio_format audio_format; -#endif -}; - -void -music_chunk_init(struct music_chunk *chunk); - -void -music_chunk_free(struct music_chunk *chunk); - -static inline bool -music_chunk_is_empty(const struct music_chunk *chunk) -{ - return chunk->length == 0 && chunk->tag == NULL; -} - -#ifndef NDEBUG -/** - * Checks if the audio format if the chunk is equal to the specified - * audio_format. - */ -bool -music_chunk_check_format(const struct music_chunk *chunk, - const struct audio_format *audio_format); -#endif - -/** - * Prepares appending to the music chunk. Returns a buffer where you - * may write into. After you are finished, call music_chunk_expand(). - * - * @param chunk the music_chunk object - * @param audio_format the audio format for the appended data; must - * stay the same for the life cycle of this chunk - * @param data_time the time within the song - * @param bit_rate the current bit rate of the source file - * @param max_length_r the maximum write length is returned here - * @return a writable buffer, or NULL if the chunk is full - */ -void * -music_chunk_write(struct music_chunk *chunk, - const struct audio_format *audio_format, - float data_time, uint16_t bit_rate, - size_t *max_length_r); - -/** - * Increases the length of the chunk after the caller has written to - * the buffer returned by music_chunk_write(). - * - * @param chunk the music_chunk object - * @param audio_format the audio format for the appended data; must - * stay the same for the life cycle of this chunk - * @param length the number of bytes which were appended - * @return true if the chunk is full - */ -bool -music_chunk_expand(struct music_chunk *chunk, - const struct audio_format *audio_format, size_t length); - -#endif diff --git a/src/client.c b/src/client.c deleted file mode 100644 index 3fa2c9be4..000000000 --- a/src/client.c +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" - -bool client_is_expired(const struct client *client) -{ - return client->channel == NULL; -} - -int client_get_uid(const struct client *client) -{ - return client->uid; -} - -unsigned client_get_permission(const struct client *client) -{ - return client->permission; -} - -void client_set_permission(struct client *client, unsigned permission) -{ - client->permission = permission; -} diff --git a/src/client.h b/src/client.h deleted file mode 100644 index 0302a2e0a..000000000 --- a/src/client.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_H -#define MPD_CLIENT_H - -#include <glib.h> -#include <stdbool.h> -#include <stddef.h> -#include <stdarg.h> - -struct client; -struct sockaddr; -struct player_control; - -void client_manager_init(void); -void client_manager_deinit(void); - -void client_new(struct player_control *player_control, - int fd, const struct sockaddr *sa, size_t sa_length, int uid); - -G_GNUC_PURE -bool client_is_expired(const struct client *client); - -/** - * returns the uid of the client process, or a negative value if the - * uid is unknown - */ -G_GNUC_PURE -int client_get_uid(const struct client *client); - -/** - * Is this client running on the same machine, connected with a local - * (UNIX domain) socket? - */ -G_GNUC_PURE -static inline bool -client_is_local(const struct client *client) -{ - return client_get_uid(client) > 0; -} - -G_GNUC_PURE -unsigned client_get_permission(const struct client *client); - -void client_set_permission(struct client *client, unsigned permission); - -/** - * Write a C string to the client. - */ -void client_puts(struct client *client, const char *s); - -/** - * Write a printf-like formatted string to the client. - */ -void client_vprintf(struct client *client, const char *fmt, va_list args); - -/** - * Write a printf-like formatted string to the client. - */ -G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...); - -#endif diff --git a/src/client_event.c b/src/client_event.c deleted file mode 100644 index 4f54ae0a7..000000000 --- a/src/client_event.c +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" -#include "main.h" - -#include <assert.h> - -static gboolean -client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct client *client = data; - - assert(!client_is_expired(client)); - - if (condition != G_IO_OUT) { - client_set_expired(client); - return false; - } - - client_write_deferred(client); - - if (client_is_expired(client)) { - client_close(client); - return false; - } - - g_timer_start(client->last_activity); - - if (g_queue_is_empty(client->deferred_send)) { - /* done sending deferred buffers exist: schedule - read */ - client->source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - client_in_event, client); - return false; - } - - /* write more */ - return true; -} - -gboolean -client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct client *client = data; - enum command_return ret; - - assert(!client_is_expired(client)); - - if (condition != G_IO_IN) { - client_set_expired(client); - return false; - } - - g_timer_start(client->last_activity); - - ret = client_read(client); - switch (ret) { - case COMMAND_RETURN_OK: - case COMMAND_RETURN_ERROR: - break; - - case COMMAND_RETURN_KILL: - client_close(client); - g_main_loop_quit(main_loop); - return false; - - case COMMAND_RETURN_CLOSE: - client_close(client); - return false; - } - - if (client_is_expired(client)) { - client_close(client); - return false; - } - - if (!g_queue_is_empty(client->deferred_send)) { - /* deferred buffers exist: schedule write */ - client->source_id = g_io_add_watch(client->channel, - G_IO_OUT|G_IO_ERR|G_IO_HUP, - client_out_event, client); - return false; - } - - /* read more */ - return true; -} diff --git a/src/client_expire.c b/src/client_expire.c deleted file mode 100644 index 1ca32ebcc..000000000 --- a/src/client_expire.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" - -static guint expire_source_id; - -void -client_set_expired(struct client *client) -{ - if (!client_is_expired(client)) - client_schedule_expire(); - - if (client->source_id != 0) { - g_source_remove(client->source_id); - client->source_id = 0; - } - - if (client->channel != NULL) { - g_io_channel_unref(client->channel); - client->channel = NULL; - } -} - -static void -client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct client *client = data; - - if (client_is_expired(client)) { - g_debug("[%u] expired", client->num); - client_close(client); - } else if (!client->idle_waiting && /* idle clients - never expire */ - (int)g_timer_elapsed(client->last_activity, NULL) > - client_timeout) { - g_debug("[%u] timeout", client->num); - client_close(client); - } -} - -static void -client_manager_expire(void) -{ - client_list_foreach(client_check_expired_callback, NULL); -} - -/** - * An idle event which calls client_manager_expire(). - */ -static gboolean -client_manager_expire_event(G_GNUC_UNUSED gpointer data) -{ - expire_source_id = 0; - client_manager_expire(); - return false; -} - -void -client_schedule_expire(void) -{ - if (expire_source_id == 0) - /* delayed deletion */ - expire_source_id = g_idle_add(client_manager_expire_event, - NULL); -} - -void -client_deinit_expire(void) -{ - if (expire_source_id != 0) - g_source_remove(expire_source_id); -} diff --git a/src/client_file.c b/src/client_file.c deleted file mode 100644 index 2ee433308..000000000 --- a/src/client_file.c +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "client_file.h" -#include "client.h" -#include "ack.h" - -#include <sys/stat.h> -#include <sys/types.h> -#include <errno.h> -#include <unistd.h> - -bool -client_allow_file(const struct client *client, const char *path_fs, - GError **error_r) -{ -#ifdef WIN32 - (void)client; - (void)path_fs; - - g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION, - "Access denied"); - return false; -#else - const int uid = client_get_uid(client); - if (uid >= 0 && (uid_t)uid == geteuid()) - /* always allow access if user runs his own MPD - instance */ - return true; - - if (uid <= 0) { - /* unauthenticated client */ - g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION, - "Access denied"); - return false; - } - - struct stat st; - if (stat(path_fs, &st) < 0) { - g_set_error(error_r, g_file_error_quark(), errno, - "%s", g_strerror(errno)); - return false; - } - - if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) { - /* client is not owner */ - g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION, - "Access denied"); - return false; - } - - return true; -#endif -} diff --git a/src/client_file.h b/src/client_file.h deleted file mode 100644 index bc64bd041..000000000 --- a/src/client_file.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_FILE_H -#define MPD_CLIENT_FILE_H - -#include <glib.h> -#include <stdbool.h> - -struct client; - -/** - * Is this client allowed to use the specified local file? - * - * Note that this function is vulnerable to timing/symlink attacks. - * We cannot fix this as long as there are plugins that open a file by - * its name, and not by file descriptor / callbacks. - * - * @param path_fs the absolute path name in filesystem encoding - * @return true if access is allowed - */ -bool -client_allow_file(const struct client *client, const char *path_fs, - GError **error_r); - -#endif diff --git a/src/client_global.c b/src/client_global.c deleted file mode 100644 index adf3b2f9e..000000000 --- a/src/client_global.c +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" -#include "conf.h" - -#include <assert.h> - -#define CLIENT_TIMEOUT_DEFAULT (60) -#define CLIENT_MAX_CONNECTIONS_DEFAULT (10) -#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024) -#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024) - -/* set this to zero to indicate we have no possible clients */ -unsigned int client_max_connections; -int client_timeout; -size_t client_max_command_list_size; -size_t client_max_output_buffer_size; - -void client_manager_init(void) -{ - client_timeout = config_get_positive(CONF_CONN_TIMEOUT, - CLIENT_TIMEOUT_DEFAULT); - client_max_connections = - config_get_positive(CONF_MAX_CONN, - CLIENT_MAX_CONNECTIONS_DEFAULT); - client_max_command_list_size = - config_get_positive(CONF_MAX_COMMAND_LIST_SIZE, - CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024) - * 1024; - - client_max_output_buffer_size = - config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE, - CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024) - * 1024; -} - -static void client_close_all(void) -{ - while (!client_list_is_empty()) { - struct client *client = client_list_get_first(); - - client_close(client); - } - - assert(client_list_is_empty()); -} - -void client_manager_deinit(void) -{ - client_close_all(); - - client_max_connections = 0; - - client_deinit_expire(); -} diff --git a/src/client_idle.c b/src/client_idle.c deleted file mode 100644 index 930911d6e..000000000 --- a/src/client_idle.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_idle.h" -#include "client_internal.h" -#include "idle.h" - -#include <assert.h> - -/** - * Send "idle" response to this client. - */ -static void -client_idle_notify(struct client *client) -{ - unsigned flags, i; - const char *const* idle_names; - - assert(client->idle_waiting); - assert(client->idle_flags != 0); - - flags = client->idle_flags; - client->idle_flags = 0; - client->idle_waiting = false; - - idle_names = idle_get_names(); - for (i = 0; idle_names[i]; ++i) { - if (flags & (1 << i) & client->idle_subscriptions) - client_printf(client, "changed: %s\n", - idle_names[i]); - } - - client_puts(client, "OK\n"); - g_timer_start(client->last_activity); -} - -void -client_idle_add(struct client *client, unsigned flags) -{ - if (client_is_expired(client)) - return; - - client->idle_flags |= flags; - if (client->idle_waiting - && (client->idle_flags & client->idle_subscriptions)) { - client_idle_notify(client); - client_write_output(client); - } -} - -static void -client_idle_callback(gpointer data, gpointer user_data) -{ - struct client *client = data; - unsigned flags = GPOINTER_TO_UINT(user_data); - - client_idle_add(client, flags); -} - -void client_manager_idle_add(unsigned flags) -{ - assert(flags != 0); - - client_list_foreach(client_idle_callback, GUINT_TO_POINTER(flags)); -} - -bool client_idle_wait(struct client *client, unsigned flags) -{ - assert(!client->idle_waiting); - - client->idle_waiting = true; - client->idle_subscriptions = flags; - - if (client->idle_flags & client->idle_subscriptions) { - client_idle_notify(client); - return true; - } else - return false; -} diff --git a/src/client_idle.h b/src/client_idle.h deleted file mode 100644 index c56fd014c..000000000 --- a/src/client_idle.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_IDLE_H -#define MPD_CLIENT_IDLE_H - -#include <stdbool.h> - -struct client; - -void -client_idle_add(struct client *client, unsigned flags); - -/** - * Adds the specified idle flags to all clients and immediately sends - * notifications to all waiting clients. - */ -void -client_manager_idle_add(unsigned flags); - -/** - * Checks whether the client has pending idle flags. If yes, they are - * sent immediately and "true" is returned". If no, it puts the - * client into waiting mode and returns false. - */ -bool -client_idle_wait(struct client *client, unsigned flags); - -#endif diff --git a/src/client_internal.h b/src/client_internal.h deleted file mode 100644 index ba97e4b8f..000000000 --- a/src/client_internal.h +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_INTERNAL_H -#define MPD_CLIENT_INTERNAL_H - -#include "client.h" -#include "client_message.h" -#include "command.h" - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "client" - -enum { - CLIENT_MAX_SUBSCRIPTIONS = 16, - CLIENT_MAX_MESSAGES = 64, -}; - -struct deferred_buffer { - size_t size; - char data[sizeof(long)]; -}; - -struct client { - struct player_control *player_control; - - GIOChannel *channel; - guint source_id; - - /** the buffer for reading lines from the #channel */ - struct fifo_buffer *input; - - unsigned permission; - - /** the uid of the client process, or -1 if unknown */ - int uid; - - /** - * How long since the last activity from this client? - */ - GTimer *last_activity; - - GSList *cmd_list; /* for when in list mode */ - int cmd_list_OK; /* print OK after each command execution */ - size_t cmd_list_size; /* mem cmd_list consumes */ - GQueue *deferred_send; /* for output if client is slow */ - size_t deferred_bytes; /* mem deferred_send consumes */ - unsigned int num; /* client number */ - - char send_buf[16384]; - size_t send_buf_used; /* bytes used this instance */ - - /** is this client waiting for an "idle" response? */ - bool idle_waiting; - - /** idle flags pending on this client, to be sent as soon as - the client enters "idle" */ - unsigned idle_flags; - - /** idle flags that the client wants to receive */ - unsigned idle_subscriptions; - - /** - * A list of channel names this client is subscribed to. - */ - GSList *subscriptions; - - /** - * The number of subscriptions in #subscriptions. Used to - * limit the number of subscriptions. - */ - unsigned num_subscriptions; - - /** - * A list of messages this client has received in reverse - * order (latest first). - */ - GSList *messages; - - /** - * The number of messages in #messages. - */ - unsigned num_messages; -}; - -extern unsigned int client_max_connections; -extern int client_timeout; -extern size_t client_max_command_list_size; -extern size_t client_max_output_buffer_size; - -bool -client_list_is_empty(void); - -bool -client_list_is_full(void); - -struct client * -client_list_get_first(void); - -void -client_list_add(struct client *client); - -void -client_list_foreach(GFunc func, gpointer user_data); - -void -client_list_remove(struct client *client); - -void -client_close(struct client *client); - -static inline void -new_cmd_list_ptr(struct client *client, const char *s) -{ - client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s)); -} - -static inline void -free_cmd_list(GSList *list) -{ - for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp)) - g_free(tmp->data); - - g_slist_free(list); -} - -void -client_set_expired(struct client *client); - -/** - * Schedule an "expired" check for all clients: permanently delete - * clients which have been set "expired" with client_set_expired(). - */ -void -client_schedule_expire(void); - -/** - * Removes a scheduled "expired" check. - */ -void -client_deinit_expire(void); - -enum command_return -client_read(struct client *client); - -enum command_return -client_process_line(struct client *client, char *line); - -void -client_write_deferred(struct client *client); - -void -client_write_output(struct client *client); - -gboolean -client_in_event(GIOChannel *source, GIOCondition condition, - gpointer data); - -#endif diff --git a/src/client_list.c b/src/client_list.c deleted file mode 100644 index 2c7f37aff..000000000 --- a/src/client_list.c +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" - -#include <assert.h> - -static GList *clients; -static unsigned num_clients; - -bool -client_list_is_empty(void) -{ - return num_clients == 0; -} - -bool -client_list_is_full(void) -{ - return num_clients >= client_max_connections; -} - -struct client * -client_list_get_first(void) -{ - assert(clients != NULL); - - return clients->data; -} - -void -client_list_add(struct client *client) -{ - clients = g_list_prepend(clients, client); - ++num_clients; -} - -void -client_list_foreach(GFunc func, gpointer user_data) -{ - g_list_foreach(clients, func, user_data); -} - -void -client_list_remove(struct client *client) -{ - assert(num_clients > 0); - assert(clients != NULL); - - clients = g_list_remove(clients, client); - --num_clients; -} diff --git a/src/client_message.c b/src/client_message.c deleted file mode 100644 index b681b4e7f..000000000 --- a/src/client_message.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "client_message.h" - -#include <assert.h> -#include <glib.h> - -G_GNUC_PURE -static bool -valid_channel_char(const char ch) -{ - return g_ascii_isalnum(ch) || - ch == '_' || ch == '-' || ch == '.' || ch == ':'; -} - -bool -client_message_valid_channel_name(const char *name) -{ - do { - if (!valid_channel_char(*name)) - return false; - } while (*++name != 0); - - return true; -} - -void -client_message_init_null(struct client_message *msg) -{ - assert(msg != NULL); - - msg->channel = NULL; - msg->message = NULL; -} - -void -client_message_init(struct client_message *msg, - const char *channel, const char *message) -{ - assert(msg != NULL); - - msg->channel = g_strdup(channel); - msg->message = g_strdup(message); -} - -void -client_message_copy(struct client_message *dest, - const struct client_message *src) -{ - assert(dest != NULL); - assert(src != NULL); - assert(client_message_defined(src)); - - client_message_init(dest, src->channel, src->message); -} - -struct client_message * -client_message_dup(const struct client_message *src) -{ - struct client_message *dest = g_slice_new(struct client_message); - client_message_copy(dest, src); - return dest; -} - -void -client_message_deinit(struct client_message *msg) -{ - assert(msg != NULL); - - g_free(msg->channel); - g_free(msg->message); -} - -void -client_message_free(struct client_message *msg) -{ - client_message_deinit(msg); - g_slice_free(struct client_message, msg); -} diff --git a/src/client_message.h b/src/client_message.h deleted file mode 100644 index 38c2e7615..000000000 --- a/src/client_message.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_MESSAGE_H -#define MPD_CLIENT_MESSAGE_H - -#include <assert.h> -#include <stdbool.h> -#include <stddef.h> -#include <glib.h> - -/** - * A client-to-client message. - */ -struct client_message { - char *channel; - - char *message; -}; - -G_GNUC_PURE -bool -client_message_valid_channel_name(const char *name); - -G_GNUC_PURE -static inline bool -client_message_defined(const struct client_message *msg) -{ - assert(msg != NULL); - assert((msg->channel == NULL) == (msg->message == NULL)); - - return msg->channel != NULL; -} - -void -client_message_init_null(struct client_message *msg); - -void -client_message_init(struct client_message *msg, - const char *channel, const char *message); - -void -client_message_copy(struct client_message *dest, - const struct client_message *src); - -G_GNUC_MALLOC -struct client_message * -client_message_dup(const struct client_message *src); - -void -client_message_deinit(struct client_message *msg); - -void -client_message_free(struct client_message *msg); - -#endif diff --git a/src/client_new.c b/src/client_new.c deleted file mode 100644 index cf28c43c5..000000000 --- a/src/client_new.c +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" -#include "fd_util.h" -#include "fifo_buffer.h" -#include "resolver.h" -#include "permission.h" -#include "glib_socket.h" - -#include <assert.h> -#include <sys/types.h> -#ifdef WIN32 -#include <winsock2.h> -#else -#include <sys/socket.h> -#endif -#include <unistd.h> - -#ifdef HAVE_LIBWRAP -#include <tcpd.h> -#endif - - -#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO - -static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n"; - -void -client_new(struct player_control *player_control, - int fd, const struct sockaddr *sa, size_t sa_length, int uid) -{ - static unsigned int next_client_num; - struct client *client; - char *remote; - - assert(player_control != NULL); - assert(fd >= 0); - -#ifdef HAVE_LIBWRAP - if (sa->sa_family != AF_UNIX) { - char *hostaddr = sockaddr_to_string(sa, sa_length, NULL); - const char *progname = g_get_prgname(); - - struct request_info req; - request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); - - fromhost(&req); - - if (!hosts_access(&req)) { - /* tcp wrappers says no */ - g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, - "libwrap refused connection (libwrap=%s) from %s", - progname, hostaddr); - - g_free(hostaddr); - close_socket(fd); - return; - } - - g_free(hostaddr); - } -#endif /* HAVE_WRAP */ - - if (client_list_is_full()) { - g_warning("Max Connections Reached!"); - close_socket(fd); - return; - } - - client = g_new0(struct client, 1); - client->player_control = player_control; - - client->channel = g_io_channel_new_socket(fd); - /* GLib is responsible for closing the file descriptor */ - g_io_channel_set_close_on_unref(client->channel, true); - /* NULL encoding means the stream is binary safe; the MPD - protocol is UTF-8 only, but we are doing this call anyway - to prevent GLib from messing around with the stream */ - g_io_channel_set_encoding(client->channel, NULL, NULL); - /* we prefer to do buffering */ - g_io_channel_set_buffered(client->channel, false); - - client->source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - client_in_event, client); - - client->input = fifo_buffer_new(4096); - - client->permission = getDefaultPermissions(); - client->uid = uid; - - client->last_activity = g_timer_new(); - - client->cmd_list = NULL; - client->cmd_list_OK = -1; - client->cmd_list_size = 0; - - client->deferred_send = g_queue_new(); - client->deferred_bytes = 0; - client->num = next_client_num++; - - client->send_buf_used = 0; - - client->subscriptions = NULL; - client->messages = NULL; - client->num_messages = 0; - - (void)send(fd, GREETING, sizeof(GREETING) - 1, 0); - - client_list_add(client); - - remote = sockaddr_to_string(sa, sa_length, NULL); - g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, - "[%u] opened from %s", client->num, remote); - g_free(remote); -} - -static void -deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct deferred_buffer *buffer = data; - g_free(buffer); -} - -void -client_close(struct client *client) -{ - client_list_remove(client); - - client_set_expired(client); - - g_timer_destroy(client->last_activity); - - if (client->cmd_list) { - free_cmd_list(client->cmd_list); - client->cmd_list = NULL; - } - - g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL); - g_queue_free(client->deferred_send); - - fifo_buffer_free(client->input); - - g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, - "[%u] closed", client->num); - g_free(client); -} diff --git a/src/client_process.c b/src/client_process.c deleted file mode 100644 index 57a8a7824..000000000 --- a/src/client_process.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" - -#include <string.h> - -#define CLIENT_LIST_MODE_BEGIN "command_list_begin" -#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin" -#define CLIENT_LIST_MODE_END "command_list_end" - -static enum command_return -client_process_command_list(struct client *client, bool list_ok, GSList *list) -{ - enum command_return ret = COMMAND_RETURN_OK; - unsigned num = 0; - - for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) { - char *cmd = cur->data; - - g_debug("command_process_list: process command \"%s\"", - cmd); - ret = command_process(client, num++, cmd); - g_debug("command_process_list: command returned %i", ret); - if (ret != COMMAND_RETURN_OK || client_is_expired(client)) - break; - else if (list_ok) - client_puts(client, "list_OK\n"); - } - - return ret; -} - -enum command_return -client_process_line(struct client *client, char *line) -{ - enum command_return ret; - - if (strcmp(line, "noidle") == 0) { - if (client->idle_waiting) { - /* send empty idle response and leave idle mode */ - client->idle_waiting = false; - command_success(client); - client_write_output(client); - } - - /* do nothing if the client wasn't idling: the client - has already received the full idle response from - client_idle_notify(), which he can now evaluate */ - - return COMMAND_RETURN_OK; - } else if (client->idle_waiting) { - /* during idle mode, clients must not send anything - except "noidle" */ - g_warning("[%u] command \"%s\" during idle", - client->num, line); - return COMMAND_RETURN_CLOSE; - } - - if (client->cmd_list_OK >= 0) { - if (strcmp(line, CLIENT_LIST_MODE_END) == 0) { - g_debug("[%u] process command list", - client->num); - - /* for scalability reasons, we have prepended - each new command; now we have to reverse it - to restore the correct order */ - client->cmd_list = g_slist_reverse(client->cmd_list); - - ret = client_process_command_list(client, - client->cmd_list_OK, - client->cmd_list); - g_debug("[%u] process command " - "list returned %i", client->num, ret); - - if (ret == COMMAND_RETURN_CLOSE || - client_is_expired(client)) - return COMMAND_RETURN_CLOSE; - - if (ret == COMMAND_RETURN_OK) - command_success(client); - - client_write_output(client); - free_cmd_list(client->cmd_list); - client->cmd_list = NULL; - client->cmd_list_OK = -1; - } else { - size_t len = strlen(line) + 1; - client->cmd_list_size += len; - if (client->cmd_list_size > - client_max_command_list_size) { - g_warning("[%u] command list size (%lu) " - "is larger than the max (%lu)", - client->num, - (unsigned long)client->cmd_list_size, - (unsigned long)client_max_command_list_size); - return COMMAND_RETURN_CLOSE; - } - - new_cmd_list_ptr(client, line); - ret = COMMAND_RETURN_OK; - } - } else { - if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) { - client->cmd_list_OK = 0; - ret = COMMAND_RETURN_OK; - } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) { - client->cmd_list_OK = 1; - ret = COMMAND_RETURN_OK; - } else { - g_debug("[%u] process command \"%s\"", - client->num, line); - ret = command_process(client, 0, line); - g_debug("[%u] command returned %i", - client->num, ret); - - if (ret == COMMAND_RETURN_CLOSE || - client_is_expired(client)) - return COMMAND_RETURN_CLOSE; - - if (ret == COMMAND_RETURN_OK) - command_success(client); - - client_write_output(client); - } - } - - return ret; -} diff --git a/src/client_read.c b/src/client_read.c deleted file mode 100644 index 26ade264e..000000000 --- a/src/client_read.c +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" -#include "fifo_buffer.h" - -#include <assert.h> -#include <string.h> - -static char * -client_read_line(struct client *client) -{ - const char *p, *newline; - size_t length; - char *line; - - p = fifo_buffer_read(client->input, &length); - if (p == NULL) - return NULL; - - newline = memchr(p, '\n', length); - if (newline == NULL) - return NULL; - - line = g_strndup(p, newline - p); - fifo_buffer_consume(client->input, newline - p + 1); - - return g_strchomp(line); -} - -static enum command_return -client_input_received(struct client *client, size_t bytesRead) -{ - char *line; - - fifo_buffer_append(client->input, bytesRead); - - /* process all lines */ - - while ((line = client_read_line(client)) != NULL) { - enum command_return ret = client_process_line(client, line); - g_free(line); - - if (ret == COMMAND_RETURN_KILL || - ret == COMMAND_RETURN_CLOSE) - return ret; - if (client_is_expired(client)) - return COMMAND_RETURN_CLOSE; - } - - return COMMAND_RETURN_OK; -} - -enum command_return -client_read(struct client *client) -{ - char *p; - size_t max_length; - GError *error = NULL; - GIOStatus status; - gsize bytes_read; - - assert(client != NULL); - assert(client->channel != NULL); - - p = fifo_buffer_write(client->input, &max_length); - if (p == NULL) { - g_warning("[%u] buffer overflow", client->num); - return COMMAND_RETURN_CLOSE; - } - - status = g_io_channel_read_chars(client->channel, p, max_length, - &bytes_read, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - return client_input_received(client, bytes_read); - - case G_IO_STATUS_AGAIN: - /* try again later, after select() */ - return COMMAND_RETURN_OK; - - case G_IO_STATUS_EOF: - /* peer disconnected */ - return COMMAND_RETURN_CLOSE; - - case G_IO_STATUS_ERROR: - /* I/O error */ - g_warning("failed to read from client %d: %s", - client->num, error->message); - g_error_free(error); - return COMMAND_RETURN_CLOSE; - } - - /* unreachable */ - return COMMAND_RETURN_CLOSE; -} diff --git a/src/client_subscribe.c b/src/client_subscribe.c deleted file mode 100644 index c65a7ed31..000000000 --- a/src/client_subscribe.c +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "client_subscribe.h" -#include "client_internal.h" -#include "client_idle.h" -#include "idle.h" - -#include <string.h> - -G_GNUC_PURE -static GSList * -client_find_subscription(const struct client *client, const char *channel) -{ - for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i)) - if (strcmp((const char *)i->data, channel) == 0) - return i; - - return NULL; -} - -enum client_subscribe_result -client_subscribe(struct client *client, const char *channel) -{ - assert(client != NULL); - assert(channel != NULL); - - if (!client_message_valid_channel_name(channel)) - return CLIENT_SUBSCRIBE_INVALID; - - if (client_find_subscription(client, channel) != NULL) - return CLIENT_SUBSCRIBE_ALREADY; - - if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) - return CLIENT_SUBSCRIBE_FULL; - - client->subscriptions = g_slist_prepend(client->subscriptions, - g_strdup(channel)); - ++client->num_subscriptions; - - idle_add(IDLE_SUBSCRIPTION); - - return CLIENT_SUBSCRIBE_OK; -} - -bool -client_unsubscribe(struct client *client, const char *channel) -{ - GSList *i = client_find_subscription(client, channel); - if (i == NULL) - return false; - - assert(client->num_subscriptions > 0); - - client->subscriptions = g_slist_remove(client->subscriptions, i->data); - --client->num_subscriptions; - - idle_add(IDLE_SUBSCRIPTION); - - assert((client->num_subscriptions == 0) == - (client->subscriptions == NULL)); - - return true; -} - -void -client_unsubscribe_all(struct client *client) -{ - for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i)) - g_free(i->data); - - g_slist_free(client->subscriptions); - client->subscriptions = NULL; - client->num_subscriptions = 0; -} - -bool -client_push_message(struct client *client, const struct client_message *msg) -{ - assert(client != NULL); - assert(msg != NULL); - assert(client_message_defined(msg)); - - if (client->num_messages >= CLIENT_MAX_MESSAGES || - client_find_subscription(client, msg->channel) == NULL) - return false; - - if (client->messages == NULL) - client_idle_add(client, IDLE_MESSAGE); - - client->messages = g_slist_prepend(client->messages, - client_message_dup(msg)); - ++client->num_messages; - - return true; -} - -GSList * -client_read_messages(struct client *client) -{ - GSList *messages = g_slist_reverse(client->messages); - - client->messages = NULL; - client->num_messages = 0; - - return messages; -} diff --git a/src/client_subscribe.h b/src/client_subscribe.h deleted file mode 100644 index 09f864417..000000000 --- a/src/client_subscribe.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_SUBSCRIBE_H -#define MPD_CLIENT_SUBSCRIBE_H - -#include <stdbool.h> -#include <glib.h> - -struct client; -struct client_message; - -enum client_subscribe_result { - /** success */ - CLIENT_SUBSCRIBE_OK, - - /** invalid channel name */ - CLIENT_SUBSCRIBE_INVALID, - - /** already subscribed to this channel */ - CLIENT_SUBSCRIBE_ALREADY, - - /** too many subscriptions */ - CLIENT_SUBSCRIBE_FULL, -}; - -enum client_subscribe_result -client_subscribe(struct client *client, const char *channel); - -bool -client_unsubscribe(struct client *client, const char *channel); - -void -client_unsubscribe_all(struct client *client); - -bool -client_push_message(struct client *client, const struct client_message *msg); - -G_GNUC_MALLOC -GSList * -client_read_messages(struct client *client); - -#endif diff --git a/src/client_write.c b/src/client_write.c deleted file mode 100644 index 78cfca8a1..000000000 --- a/src/client_write.c +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" - -#include <assert.h> -#include <string.h> -#include <stdio.h> - -static size_t -client_write_deferred_buffer(struct client *client, - const struct deferred_buffer *buffer) -{ - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->channel != NULL); - assert(buffer != NULL); - - status = g_io_channel_write_chars - (client->channel, buffer->data, buffer->size, - &bytes_written, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - return bytes_written; - - case G_IO_STATUS_AGAIN: - return 0; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - client_set_expired(client); - return 0; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - client_set_expired(client); - g_warning("failed to flush buffer for %i: %s", - client->num, error->message); - g_error_free(error); - return 0; - } - - /* unreachable */ - return 0; -} - -void -client_write_deferred(struct client *client) -{ - size_t ret; - - while (!g_queue_is_empty(client->deferred_send)) { - struct deferred_buffer *buf = - g_queue_peek_head(client->deferred_send); - - assert(buf->size > 0); - assert(buf->size <= client->deferred_bytes); - - ret = client_write_deferred_buffer(client, buf); - if (ret == 0) - break; - - if (ret < buf->size) { - assert(client->deferred_bytes >= (size_t)ret); - client->deferred_bytes -= ret; - buf->size -= ret; - memmove(buf->data, buf->data + ret, buf->size); - break; - } else { - size_t decr = sizeof(*buf) - - sizeof(buf->data) + buf->size; - - assert(client->deferred_bytes >= decr); - client->deferred_bytes -= decr; - g_free(buf); - g_queue_pop_head(client->deferred_send); - } - - g_timer_start(client->last_activity); - } - - if (g_queue_is_empty(client->deferred_send)) { - g_debug("[%u] buffer empty %lu", client->num, - (unsigned long)client->deferred_bytes); - assert(client->deferred_bytes == 0); - } -} - -static void client_defer_output(struct client *client, - const void *data, size_t length) -{ - size_t alloc; - struct deferred_buffer *buf; - - assert(length > 0); - - alloc = sizeof(*buf) - sizeof(buf->data) + length; - client->deferred_bytes += alloc; - if (client->deferred_bytes > client_max_output_buffer_size) { - g_warning("[%u] output buffer size (%lu) is " - "larger than the max (%lu)", - client->num, - (unsigned long)client->deferred_bytes, - (unsigned long)client_max_output_buffer_size); - /* cause client to close */ - client_set_expired(client); - return; - } - - buf = g_malloc(alloc); - buf->size = length; - memcpy(buf->data, data, length); - - g_queue_push_tail(client->deferred_send, buf); -} - -static void client_write_direct(struct client *client, - const char *data, size_t length) -{ - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->channel != NULL); - assert(data != NULL); - assert(length > 0); - assert(g_queue_is_empty(client->deferred_send)); - - status = g_io_channel_write_chars(client->channel, data, length, - &bytes_written, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - case G_IO_STATUS_AGAIN: - break; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - client_set_expired(client); - return; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - client_set_expired(client); - g_warning("failed to write to %i: %s", - client->num, error->message); - g_error_free(error); - return; - } - - if (bytes_written < length) - client_defer_output(client, data + bytes_written, - length - bytes_written); - - if (!g_queue_is_empty(client->deferred_send)) - g_debug("[%u] buffer created", client->num); -} - -void -client_write_output(struct client *client) -{ - if (client_is_expired(client) || !client->send_buf_used) - return; - - if (!g_queue_is_empty(client->deferred_send)) { - client_defer_output(client, client->send_buf, - client->send_buf_used); - - if (client_is_expired(client)) - return; - - /* try to flush the deferred buffers now; the current - server command may take too long to finish, and - meanwhile try to feed output to the client, - otherwise it will time out. One reason why - deferring is slow might be that currently each - client_write() allocates a new deferred buffer. - This should be optimized after MPD 0.14. */ - client_write_deferred(client); - } else - client_write_direct(client, client->send_buf, - client->send_buf_used); - - client->send_buf_used = 0; -} - -/** - * Write a block of data to the client. - */ -static void client_write(struct client *client, const char *buffer, size_t buflen) -{ - /* if the client is going to be closed, do nothing */ - if (client_is_expired(client)) - return; - - while (buflen > 0 && !client_is_expired(client)) { - size_t copylen; - - assert(client->send_buf_used < sizeof(client->send_buf)); - - copylen = sizeof(client->send_buf) - client->send_buf_used; - if (copylen > buflen) - copylen = buflen; - - memcpy(client->send_buf + client->send_buf_used, buffer, - copylen); - buflen -= copylen; - client->send_buf_used += copylen; - buffer += copylen; - if (client->send_buf_used >= sizeof(client->send_buf)) - client_write_output(client); - } -} - -void client_puts(struct client *client, const char *s) -{ - client_write(client, s, strlen(s)); -} - -void client_vprintf(struct client *client, const char *fmt, va_list args) -{ -#ifndef G_OS_WIN32 - va_list tmp; - int length; - char *buffer; - - va_copy(tmp, args); - length = vsnprintf(NULL, 0, fmt, tmp); - va_end(tmp); - - if (length <= 0) - /* wtf.. */ - return; - - buffer = g_malloc(length + 1); - vsnprintf(buffer, length + 1, fmt, args); - client_write(client, buffer, length); - g_free(buffer); -#else - /* On mingw32, snprintf() expects a 64 bit integer instead of - a "long int" for "%li". This is not consistent with our - expectation, so we're using plain sprintf() here, hoping - the static buffer is large enough. Sorry for this hack, - but WIN32 development is so painful, I'm not in the mood to - do it properly now. */ - - static char buffer[4096]; - vsprintf(buffer, fmt, args); - client_write(client, buffer, strlen(buffer)); -#endif -} - -G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...) -{ - va_list args; - - va_start(args, fmt); - client_vprintf(client, fmt, args); - va_end(args); -} diff --git a/src/clock.h b/src/clock.h index 4ece35ab1..f1338938f 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/cmdline.c b/src/cmdline.c deleted file mode 100644 index cb7eff36a..000000000 --- a/src/cmdline.c +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "cmdline.h" -#include "path.h" -#include "log.h" -#include "conf.h" -#include "decoder_list.h" -#include "decoder_plugin.h" -#include "output_list.h" -#include "output_plugin.h" -#include "input_registry.h" -#include "input_plugin.h" -#include "playlist_list.h" -#include "playlist_plugin.h" -#include "ls.h" -#include "mpd_error.h" -#include "glib_compat.h" - -#ifdef ENABLE_ENCODER -#include "encoder_list.h" -#include "encoder_plugin.h" -#endif - -#ifdef ENABLE_ARCHIVE -#include "archive_list.h" -#include "archive_plugin.h" -#endif - -#include <glib.h> - -#include <stdio.h> -#include <stdlib.h> - -#ifdef G_OS_WIN32 -#define CONFIG_FILE_LOCATION "\\mpd\\mpd.conf" -#else /* G_OS_WIN32 */ -#define USER_CONFIG_FILE_LOCATION1 ".mpdconf" -#define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf" -#endif - -static GQuark -cmdline_quark(void) -{ - return g_quark_from_static_string("cmdline"); -} - -G_GNUC_NORETURN -static void version(void) -{ - puts(PACKAGE " (MPD: Music Player Daemon) " VERSION " \n" - "\n" - "Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n" - "Copyright (C) 2008-2012 Max Kellermann <max@duempel.org>\n" - "This is free software; see the source for copying conditions. There is NO\n" - "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" - "\n" - "Decoders plugins:"); - - decoder_plugins_for_each(plugin) { - printf(" [%s]", plugin->name); - - const char *const*suffixes = plugin->suffixes; - if (suffixes != NULL) - for (; *suffixes != NULL; ++suffixes) - printf(" %s", *suffixes); - - puts(""); - } - - puts("\n" - "Output plugins:"); - audio_output_plugins_for_each(plugin) - printf(" %s", plugin->name); - puts(""); - -#ifdef ENABLE_ENCODER - puts("\n" - "Encoder plugins:"); - encoder_plugins_for_each(plugin) - printf(" %s", plugin->name); - puts(""); -#endif - -#ifdef ENABLE_ARCHIVE - puts("\n" - "Archive plugins:"); - archive_plugins_for_each(plugin) { - printf(" [%s]", plugin->name); - - const char *const*suffixes = plugin->suffixes; - if (suffixes != NULL) - for (; *suffixes != NULL; ++suffixes) - printf(" %s", *suffixes); - - puts(""); - } -#endif - - puts("\n" - "Input plugins:"); - input_plugins_for_each(plugin) - printf(" %s", plugin->name); - - puts("\n\n" - "Playlist plugins:"); - playlist_plugins_for_each(plugin) - printf(" %s", plugin->name); - - puts("\n\n" - "Protocols:"); - print_supported_uri_schemes_to_fp(stdout); - - exit(EXIT_SUCCESS); -} - -static const char *summary = - "Music Player Daemon - a daemon for playing music."; - -bool -parse_cmdline(int argc, char **argv, struct options *options, - GError **error_r) -{ - GError *error = NULL; - GOptionContext *context; - bool ret; - static gboolean option_version, - option_no_daemon, - option_no_config; - const GOptionEntry entries[] = { - { "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill, - "kill the currently running mpd session", NULL }, - { "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config, - "don't read from config", NULL }, - { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon, - "don't detach from console", NULL }, - { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr, - NULL, NULL }, - { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr, - "print messages to stderr", NULL }, - { "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose, - "verbose logging", NULL }, - { "version", 'V', 0, G_OPTION_ARG_NONE, &option_version, - "print version number", NULL }, - { .long_name = NULL } - }; - - options->kill = false; - options->daemon = true; - options->log_stderr = false; - options->verbose = false; - - context = g_option_context_new("[path/to/mpd.conf]"); - g_option_context_add_main_entries(context, entries, NULL); - - g_option_context_set_summary(context, summary); - - ret = g_option_context_parse(context, &argc, &argv, &error); - g_option_context_free(context); - - if (!ret) - MPD_ERROR("option parsing failed: %s\n", error->message); - - if (option_version) - version(); - - /* initialize the logging library, so the configuration file - parser can use it already */ - log_early_init(options->verbose); - - options->daemon = !option_no_daemon; - - if (option_no_config) { - g_debug("Ignoring config, using daemon defaults\n"); - return true; - } else if (argc <= 1) { - /* default configuration file path */ - char *path1; - -#ifdef G_OS_WIN32 - path1 = g_build_filename(g_get_user_config_dir(), - CONFIG_FILE_LOCATION, NULL); - if (g_file_test(path1, G_FILE_TEST_IS_REGULAR)) - ret = config_read_file(path1, error_r); - else { - int i = 0; - char *system_path = NULL; - const char * const *system_config_dirs; - - system_config_dirs = g_get_system_config_dirs(); - - while(system_config_dirs[i] != NULL) { - system_path = g_build_filename(system_config_dirs[i], - CONFIG_FILE_LOCATION, - NULL); - if(g_file_test(system_path, - G_FILE_TEST_IS_REGULAR)) { - ret = config_read_file(system_path,error_r); - g_free(system_path); - break; - } else - g_free(system_path); - ++i; - } - } -#else /* G_OS_WIN32 */ - char *path2; - path1 = g_build_filename(g_get_home_dir(), - USER_CONFIG_FILE_LOCATION1, NULL); - path2 = g_build_filename(g_get_home_dir(), - USER_CONFIG_FILE_LOCATION2, NULL); - if (g_file_test(path1, G_FILE_TEST_IS_REGULAR)) - ret = config_read_file(path1, error_r); - else if (g_file_test(path2, G_FILE_TEST_IS_REGULAR)) - ret = config_read_file(path2, error_r); - else if (g_file_test(SYSTEM_CONFIG_FILE_LOCATION, - G_FILE_TEST_IS_REGULAR)) - ret = config_read_file(SYSTEM_CONFIG_FILE_LOCATION, - error_r); -#endif - - g_free(path1); -#ifndef G_OS_WIN32 - g_free(path2); -#endif - - return ret; - } else if (argc == 2) { - /* specified configuration file */ - return config_read_file(argv[1], error_r); - } else { - g_set_error(error_r, cmdline_quark(), 0, - "too many arguments"); - return false; - } -} diff --git a/src/cmdline.h b/src/cmdline.h deleted file mode 100644 index 68f625a6c..000000000 --- a/src/cmdline.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef CMDLINE_H -#define CMDLINE_H - -#include <glib.h> - -#include <stdbool.h> - -struct options { - gboolean kill; - gboolean daemon; - gboolean log_stderr; - gboolean verbose; -}; - -bool -parse_cmdline(int argc, char **argv, struct options *options, - GError **error_r); - -#endif diff --git a/src/command.c b/src/command.c deleted file mode 100644 index c405925f2..000000000 --- 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 68d1f95e4..9ea5bb52f 100644 --- a/src/command.h +++ b/src/command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,27 +20,34 @@ #ifndef MPD_COMMAND_H #define MPD_COMMAND_H -#include "ack.h" - -#include <glib.h> -#include <stdbool.h> - enum command_return { - COMMAND_RETURN_ERROR = -1, - COMMAND_RETURN_OK = 0, - COMMAND_RETURN_KILL = 10, - COMMAND_RETURN_CLOSE = 20, + /** + * The command has succeeded, but the "OK" response was not + * yet sent to the client. + */ + COMMAND_RETURN_OK, + + /** + * The connection is now in "idle" mode, and no response shall + * be generated. + */ + COMMAND_RETURN_IDLE, + + /** + * There was an error. The "ACK" response was sent to the + * client. + */ + COMMAND_RETURN_ERROR, + + /** + * The connection to this client shall be closed. + */ + COMMAND_RETURN_CLOSE, + + /** + * The MPD process shall be shut down. + */ + COMMAND_RETURN_KILL, }; -struct client; - -void command_init(void); - -void command_finish(void); - -enum command_return -command_process(struct client *client, unsigned num, char *line); - -void command_success(struct client *client); - #endif diff --git a/src/conf.c b/src/conf.c deleted file mode 100644 index 167f2da92..000000000 --- a/src/conf.c +++ /dev/null @@ -1,666 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "conf.h" -#include "utils.h" -#include "string_util.h" -#include "tokenizer.h" -#include "path.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <stdio.h> -#include <stdlib.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "config" - -#define MAX_STRING_SIZE MPD_PATH_MAX+80 - -#define CONF_COMMENT '#' - -struct config_entry { - const char *const name; - const bool repeatable; - const bool block; - - GSList *params; -}; - -static struct config_entry config_entries[] = { - { .name = CONF_MUSIC_DIR, false, false }, - { .name = CONF_PLAYLIST_DIR, false, false }, - { .name = CONF_FOLLOW_INSIDE_SYMLINKS, false, false }, - { .name = CONF_FOLLOW_OUTSIDE_SYMLINKS, false, false }, - { .name = CONF_DB_FILE, false, false }, - { .name = CONF_STICKER_FILE, false, false }, - { .name = CONF_LOG_FILE, false, false }, - { .name = CONF_PID_FILE, false, false }, - { .name = CONF_STATE_FILE, false, false }, - { .name = "restore_paused", false, false }, - { .name = CONF_USER, false, false }, - { .name = CONF_GROUP, false, false }, - { .name = CONF_BIND_TO_ADDRESS, true, false }, - { .name = CONF_PORT, false, false }, - { .name = CONF_LOG_LEVEL, false, false }, - { .name = CONF_ZEROCONF_NAME, false, false }, - { .name = CONF_ZEROCONF_ENABLED, false, false }, - { .name = CONF_PASSWORD, true, false }, - { .name = CONF_DEFAULT_PERMS, false, false }, - { .name = CONF_AUDIO_OUTPUT, true, true }, - { .name = CONF_AUDIO_OUTPUT_FORMAT, false, false }, - { .name = CONF_MIXER_TYPE, false, false }, - { .name = CONF_REPLAYGAIN, false, false }, - { .name = CONF_REPLAYGAIN_PREAMP, false, false }, - { .name = CONF_REPLAYGAIN_MISSING_PREAMP, false, false }, - { .name = CONF_REPLAYGAIN_LIMIT, false, false }, - { .name = CONF_VOLUME_NORMALIZATION, false, false }, - { .name = CONF_SAMPLERATE_CONVERTER, false, false }, - { .name = CONF_AUDIO_BUFFER_SIZE, false, false }, - { .name = CONF_BUFFER_BEFORE_PLAY, false, false }, - { .name = CONF_HTTP_PROXY_HOST, false, false }, - { .name = CONF_HTTP_PROXY_PORT, false, false }, - { .name = CONF_HTTP_PROXY_USER, false, false }, - { .name = CONF_HTTP_PROXY_PASSWORD, false, false }, - { .name = CONF_CONN_TIMEOUT, false, false }, - { .name = CONF_MAX_CONN, false, false }, - { .name = CONF_MAX_PLAYLIST_LENGTH, false, false }, - { .name = CONF_MAX_COMMAND_LIST_SIZE, false, false }, - { .name = CONF_MAX_OUTPUT_BUFFER_SIZE, false, false }, - { .name = CONF_FS_CHARSET, false, false }, - { .name = CONF_ID3V1_ENCODING, false, false }, - { .name = CONF_METADATA_TO_USE, false, false }, - { .name = CONF_SAVE_ABSOLUTE_PATHS, false, false }, - { .name = CONF_DECODER, true, true }, - { .name = CONF_INPUT, true, true }, - { .name = CONF_GAPLESS_MP3_PLAYBACK, false, false }, - { .name = CONF_PLAYLIST_PLUGIN, true, true }, - { .name = CONF_AUTO_UPDATE, false, false }, - { .name = CONF_AUTO_UPDATE_DEPTH, false, false }, - { .name = CONF_DESPOTIFY_USER, false, false }, - { .name = CONF_DESPOTIFY_PASSWORD, false, false}, - { .name = CONF_DESPOTIFY_HIGH_BITRATE, false, false }, - { .name = "filter", true, true }, -}; - -static bool -get_bool(const char *value, bool *value_r) -{ - static const char *t[] = { "yes", "true", "1", NULL }; - static const char *f[] = { "no", "false", "0", NULL }; - - if (string_array_contains(t, value)) { - *value_r = true; - return true; - } - - if (string_array_contains(f, value)) { - *value_r = false; - return true; - } - - return false; -} - -struct config_param * -config_new_param(const char *value, int line) -{ - struct config_param *ret = g_new(struct config_param, 1); - - if (!value) - ret->value = NULL; - else - ret->value = g_strdup(value); - - ret->line = line; - - ret->num_block_params = 0; - ret->block_params = NULL; - ret->used = false; - - return ret; -} - -void -config_param_free(struct config_param *param) -{ - g_free(param->value); - - for (unsigned i = 0; i < param->num_block_params; i++) { - g_free(param->block_params[i].name); - g_free(param->block_params[i].value); - } - - if (param->num_block_params) - g_free(param->block_params); - - g_free(param); -} - -static void -config_param_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct config_param *param = data; - - config_param_free(param); -} - -static struct config_entry * -config_entry_get(const char *name) -{ - for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) { - struct config_entry *entry = &config_entries[i]; - if (strcmp(entry->name, name) == 0) - return entry; - } - - return NULL; -} - -void config_global_finish(void) -{ - for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) { - struct config_entry *entry = &config_entries[i]; - - g_slist_foreach(entry->params, - config_param_free_callback, NULL); - g_slist_free(entry->params); - } -} - -void config_global_init(void) -{ -} - -static void -config_param_check(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct config_param *param = data; - - if (!param->used) - /* this whole config_param was not queried at all - - the feature might be disabled at compile time? - Silently ignore it here. */ - return; - - for (unsigned i = 0; i < param->num_block_params; i++) { - struct block_param *bp = ¶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; -} diff --git a/src/conf.h b/src/conf.h index 815c739b1..8eb185fa7 100644 --- a/src/conf.h +++ b/src/conf.h @@ -20,208 +20,14 @@ #ifndef MPD_CONF_H #define MPD_CONF_H -#include <stdbool.h> -#include <glib.h> - -#define CONF_MUSIC_DIR "music_directory" -#define CONF_PLAYLIST_DIR "playlist_directory" -#define CONF_FOLLOW_INSIDE_SYMLINKS "follow_inside_symlinks" -#define CONF_FOLLOW_OUTSIDE_SYMLINKS "follow_outside_symlinks" -#define CONF_DB_FILE "db_file" -#define CONF_STICKER_FILE "sticker_file" -#define CONF_LOG_FILE "log_file" -#define CONF_PID_FILE "pid_file" -#define CONF_STATE_FILE "state_file" -#define CONF_USER "user" -#define CONF_GROUP "group" -#define CONF_BIND_TO_ADDRESS "bind_to_address" -#define CONF_PORT "port" -#define CONF_LOG_LEVEL "log_level" -#define CONF_ZEROCONF_NAME "zeroconf_name" -#define CONF_ZEROCONF_ENABLED "zeroconf_enabled" -#define CONF_PASSWORD "password" -#define CONF_DEFAULT_PERMS "default_permissions" -#define CONF_AUDIO_OUTPUT "audio_output" -#define CONF_AUDIO_FILTER "filter" -#define CONF_AUDIO_OUTPUT_FORMAT "audio_output_format" -#define CONF_MIXER_TYPE "mixer_type" -#define CONF_REPLAYGAIN "replaygain" -#define CONF_REPLAYGAIN_PREAMP "replaygain_preamp" -#define CONF_REPLAYGAIN_MISSING_PREAMP "replaygain_missing_preamp" -#define CONF_REPLAYGAIN_LIMIT "replaygain_limit" -#define CONF_VOLUME_NORMALIZATION "volume_normalization" -#define CONF_SAMPLERATE_CONVERTER "samplerate_converter" -#define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size" -#define CONF_BUFFER_BEFORE_PLAY "buffer_before_play" -#define CONF_HTTP_PROXY_HOST "http_proxy_host" -#define CONF_HTTP_PROXY_PORT "http_proxy_port" -#define CONF_HTTP_PROXY_USER "http_proxy_user" -#define CONF_HTTP_PROXY_PASSWORD "http_proxy_password" -#define CONF_CONN_TIMEOUT "connection_timeout" -#define CONF_MAX_CONN "max_connections" -#define CONF_MAX_PLAYLIST_LENGTH "max_playlist_length" -#define CONF_MAX_COMMAND_LIST_SIZE "max_command_list_size" -#define CONF_MAX_OUTPUT_BUFFER_SIZE "max_output_buffer_size" -#define CONF_FS_CHARSET "filesystem_charset" -#define CONF_ID3V1_ENCODING "id3v1_encoding" -#define CONF_METADATA_TO_USE "metadata_to_use" -#define CONF_SAVE_ABSOLUTE_PATHS "save_absolute_paths_in_playlists" -#define CONF_DECODER "decoder" -#define CONF_INPUT "input" -#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback" -#define CONF_PLAYLIST_PLUGIN "playlist_plugin" -#define CONF_AUTO_UPDATE "auto_update" -#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth" -#define CONF_DESPOTIFY_USER "despotify_user" -#define CONF_DESPOTIFY_PASSWORD "despotify_password" -#define CONF_DESPOTIFY_HIGH_BITRATE "despotify_high_bitrate" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "ConfigData.hxx" +#include "gcc.h" #define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16) #define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false #define MAX_FILTER_CHAIN_LENGTH 255 -struct block_param { - char *name; - char *value; - int line; - - /** - * This flag is false when nobody has queried the value of - * this option yet. - */ - bool used; -}; - -struct config_param { - char *value; - unsigned int line; - - struct block_param *block_params; - unsigned num_block_params; - - /** - * This flag is false when nobody has queried the value of - * this option yet. - */ - bool used; -}; - -/** - * A GQuark for GError instances, resulting from malformed - * configuration. - */ -G_GNUC_CONST -static inline GQuark -config_quark(void) -{ - return g_quark_from_static_string("config"); -} - -void config_global_init(void); -void config_global_finish(void); - -/** - * Call this function after all configuration has been evaluated. It - * checks for unused parameters, and logs warnings. - */ -void config_global_check(void); - -bool -config_read_file(const char *file, GError **error_r); - -/* don't free the returned value - set _last_ to NULL to get first entry */ -G_GNUC_PURE -const struct config_param * -config_get_next_param(const char *name, const struct config_param *last); - -G_GNUC_PURE -static inline const struct config_param * -config_get_param(const char *name) -{ - return config_get_next_param(name, NULL); -} - -/* Note on G_GNUC_PURE: Some of the functions declared pure are not - really pure in strict sense. They have side effect such that they - validate parameter's value and signal an error if it's invalid. - However, if the argument was already validated or we don't care - about the argument at all, this may be ignored so in the end, we - should be fine with calling those functions pure. */ - -G_GNUC_PURE -const char * -config_get_string(const char *name, const char *default_value); - -/** - * Returns an optional configuration variable which contains an - * absolute path. If there is a tilde prefix, it is expanded. - * Returns NULL if the value is not present. If the path could not be - * parsed, returns NULL and sets the error. - * - * The return value must be freed with g_free(). - */ -G_GNUC_MALLOC -char * -config_dup_path(const char *name, GError **error_r); - -G_GNUC_PURE -unsigned -config_get_unsigned(const char *name, unsigned default_value); - -G_GNUC_PURE -unsigned -config_get_positive(const char *name, unsigned default_value); - -G_GNUC_PURE -const struct block_param * -config_get_block_param(const struct config_param *param, const char *name); - -G_GNUC_PURE -bool config_get_bool(const char *name, bool default_value); - -G_GNUC_PURE -const char * -config_get_block_string(const struct config_param *param, const char *name, - const char *default_value); - -G_GNUC_MALLOC -static inline char * -config_dup_block_string(const struct config_param *param, const char *name, - const char *default_value) -{ - return g_strdup(config_get_block_string(param, name, default_value)); -} - -/** - * Same as config_dup_path(), but looks up the setting in the - * specified block. - */ -G_GNUC_MALLOC -char * -config_dup_block_path(const struct config_param *param, const char *name, - GError **error_r); - -G_GNUC_PURE -unsigned -config_get_block_unsigned(const struct config_param *param, const char *name, - unsigned default_value); - -G_GNUC_PURE -bool -config_get_block_bool(const struct config_param *param, const char *name, - bool default_value); - -G_GNUC_MALLOC -struct config_param * -config_new_param(const char *value, int line); - -void -config_param_free(struct config_param *param); - -void -config_add_block_param(struct config_param * param, const char *name, - const char *value, int line); - #endif diff --git a/src/crossfade.c b/src/crossfade.c deleted file mode 100644 index 46a0dff30..000000000 --- a/src/crossfade.c +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "crossfade.h" -#include "chunk.h" -#include "audio_format.h" -#include "tag.h" - -#include <assert.h> -#include <string.h> -#include <stdlib.h> -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "crossfade" - -#ifdef G_OS_WIN32 -static char * -strtok_r(char *str, const char *delim, G_GNUC_UNUSED char **saveptr) -{ - return strtok(str, delim); -} -#endif - -static float mixramp_interpolate(char *ramp_list, float required_db) -{ - float db, secs, last_db = nan(""), last_secs = 0; - char *ramp_str, *save_str = NULL; - - /* ramp_list is a string of pairs of dBs and seconds that describe the - * volume profile. Delimiters are semi-colons between pairs and spaces - * between the dB and seconds of a pair. - * The dB values must be monotonically increasing for this to work. */ - - while (1) { - /* Parse the dB tokens out of the input string. */ - ramp_str = strtok_r(ramp_list, " ", &save_str); - - /* Tell strtok to continue next time round. */ - ramp_list = NULL; - - /* Parse the dB value. */ - if (NULL == ramp_str) { - return nan(""); - } - db = (float)atof(ramp_str); - - /* Parse the time. */ - ramp_str = strtok_r(NULL, ";", &save_str); - if (NULL == ramp_str) { - return nan(""); - } - secs = (float)atof(ramp_str); - - /* Check for exact match. */ - if (db == required_db) { - return secs; - } - - /* Save if too quiet. */ - if (db < required_db) { - last_db = db; - last_secs = secs; - continue; - } - - /* If required db < any stored value, use the least. */ - if (isnan(last_db)) { - return secs; - } - - /* Finally, interpolate linearly. */ - secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db); - return secs; - } -} - -unsigned cross_fade_calc(float duration, float total_time, - float mixramp_db, float mixramp_delay, - float replay_gain_db, float replay_gain_prev_db, - char *mixramp_start, char *mixramp_prev_end, - const struct audio_format *af, - const struct audio_format *old_format, - unsigned max_chunks) -{ - unsigned int chunks = 0; - float chunks_f; - float mixramp_overlap; - - if (duration < 0 || duration >= total_time || - /* we can't crossfade when the audio formats are different */ - !audio_format_equals(af, old_format)) - return 0; - - assert(duration >= 0); - assert(audio_format_valid(af)); - - chunks_f = (float)audio_format_time_to_size(af) / (float)CHUNK_SIZE; - - if (isnan(mixramp_delay) || !(mixramp_start) || !(mixramp_prev_end)) { - chunks = (chunks_f * duration + 0.5); - } else { - /* Calculate mixramp overlap. */ - mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db - replay_gain_db) - + mixramp_interpolate(mixramp_prev_end, mixramp_db - replay_gain_prev_db); - if (!isnan(mixramp_overlap) && (mixramp_delay <= mixramp_overlap)) { - chunks = (chunks_f * (mixramp_overlap - mixramp_delay)); - g_debug("will overlap %d chunks, %fs", chunks, - mixramp_overlap - mixramp_delay); - } - } - - if (chunks > max_chunks) { - chunks = max_chunks; - g_warning("audio_buffer_size too small for computed MixRamp overlap"); - } - - return chunks; -} diff --git a/src/crossfade.h b/src/crossfade.h deleted file mode 100644 index d581dbfe0..000000000 --- a/src/crossfade.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CROSSFADE_H -#define MPD_CROSSFADE_H - -struct audio_format; -struct music_chunk; - -/** - * Calculate how many music pipe chunks should be used for crossfading. - * - * @param duration the requested crossfade duration - * @param total_time total_time the duration of the new song - * @param mixramp_db the current mixramp_db setting - * @param mixramp_delay the current mixramp_delay setting - * @param replay_gain_db the ReplayGain adjustment used for this song - * @param replay_gain_prev_db the ReplayGain adjustment used on the last song - * @param mixramp_start the next songs mixramp_start tag - * @param mixramp_prev_end the last songs mixramp_end setting - * @param af the audio format of the new song - * @param old_format the audio format of the current song - * @param max_chunks the maximum number of chunks - * @return the number of chunks for crossfading, or 0 if cross fading - * should be disabled for this song change - */ -unsigned cross_fade_calc(float duration, float total_time, - float mixramp_db, float mixramp_delay, - float replay_gain_db, float replay_gain_prev_db, - char *mixramp_start, char *mixramp_prev_end, - const struct audio_format *af, - const struct audio_format *old_format, - unsigned max_chunks); - -#endif diff --git a/src/cue/cue_parser.c b/src/cue/cue_parser.c index 9ccc3bcdd..bee757c9c 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/database.c b/src/database.c deleted file mode 100644 index 8c903bb45..000000000 --- a/src/database.c +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "database.h" -#include "db_error.h" -#include "db_save.h" -#include "db_selection.h" -#include "db_visitor.h" -#include "db_plugin.h" -#include "db/simple_db_plugin.h" -#include "directory.h" -#include "stats.h" -#include "conf.h" -#include "glib_compat.h" - -#include <glib.h> - -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <assert.h> -#include <string.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "database" - -static struct db *db; -static bool db_is_open; - -bool -db_init(const struct config_param *path, GError **error_r) -{ - assert(db == NULL); - assert(!db_is_open); - - if (path == NULL) - return true; - - struct config_param *param = config_new_param("database", path->line); - config_add_block_param(param, "path", path->value, path->line); - - db = db_plugin_new(&simple_db_plugin, param, error_r); - - config_param_free(param); - - return db != NULL; -} - -void -db_finish(void) -{ - if (db_is_open) - db_plugin_close(db); - - if (db != NULL) - db_plugin_free(db); -} - -struct directory * -db_get_root(void) -{ - assert(db != NULL); - - return simple_db_get_root(db); -} - -struct directory * -db_get_directory(const char *name) -{ - if (db == NULL) - return NULL; - - struct directory *music_root = db_get_root(); - if (name == NULL) - return music_root; - - struct directory *directory = - directory_lookup_directory(music_root, name); - return directory; -} - -struct song * -db_get_song(const char *file) -{ - assert(file != NULL); - - g_debug("get song: %s", file); - - if (db == NULL) - return NULL; - - return db_plugin_get_song(db, file, NULL); -} - -bool -db_visit(const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - if (db == NULL) { - g_set_error_literal(error_r, db_quark(), DB_DISABLED, - "No database"); - return false; - } - - return db_plugin_visit(db, selection, visitor, ctx, error_r); -} - -bool -db_walk(const char *uri, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - struct db_selection selection; - db_selection_init(&selection, uri, true); - - return db_visit(&selection, visitor, ctx, error_r); -} - -bool -db_save(GError **error_r) -{ - assert(db != NULL); - assert(db_is_open); - - return simple_db_save(db, error_r); -} - -bool -db_load(GError **error) -{ - assert(db != NULL); - assert(!db_is_open); - - if (!db_plugin_open(db, error)) - return false; - - db_is_open = true; - - stats_update(); - - return true; -} - -time_t -db_get_mtime(void) -{ - assert(db != NULL); - assert(db_is_open); - - return simple_db_get_mtime(db); -} diff --git a/src/database.h b/src/database.h deleted file mode 100644 index f877b74d3..000000000 --- a/src/database.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DATABASE_H -#define MPD_DATABASE_H - -#include "gcc.h" - -#include <glib.h> - -#include <sys/time.h> -#include <stdbool.h> - -struct config_param; -struct directory; -struct db_selection; -struct db_visitor; - -/** - * Initialize the database library. - * - * @param path the absolute path of the database file - */ -bool -db_init(const struct config_param *path, GError **error_r); - -void -db_finish(void); - -/** - * Returns the root directory object. Returns NULL if there is no - * configured music directory. - */ -G_GNUC_PURE -struct directory * -db_get_root(void); - -/** - * Caller must lock the #db_mutex. - */ -gcc_nonnull(1) -G_GNUC_PURE -struct directory * -db_get_directory(const char *name); - -gcc_nonnull(1) -G_GNUC_PURE -struct song * -db_get_song(const char *file); - -gcc_nonnull(1,2) -bool -db_visit(const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r); - -gcc_nonnull(1,2) -bool -db_walk(const char *uri, - const struct db_visitor *visitor, void *ctx, - GError **error_r); - -bool -db_save(GError **error_r); - -bool -db_load(GError **error); - -G_GNUC_PURE -time_t -db_get_mtime(void); - -/** - * Returns true if there is a valid database file on the disk. - */ -G_GNUC_PURE -static inline bool -db_exists(void) -{ - /* mtime is set only if the database file was loaded or saved - successfully */ - return db_get_mtime() > 0; -} - -#endif diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx new file mode 100644 index 000000000..efaaffeba --- /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 000000000..8e878baca --- /dev/null +++ b/src/db/ProxyDatabasePlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PROXY_DATABASE_PLUGIN_HXX +#define MPD_PROXY_DATABASE_PLUGIN_HXX + +struct DatabasePlugin; + +extern const DatabasePlugin proxy_db_plugin; + +#endif diff --git a/src/db/SimpleDatabasePlugin.cxx b/src/db/SimpleDatabasePlugin.cxx new file mode 100644 index 000000000..2b720c41f --- /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 000000000..525e854db --- /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 697e8da5f..000000000 --- a/src/db/simple_db_plugin.c +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "simple_db_plugin.h" -#include "db_internal.h" -#include "db_error.h" -#include "db_selection.h" -#include "db_visitor.h" -#include "db_save.h" -#include "db_lock.h" -#include "conf.h" -#include "directory.h" - -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> - -struct simple_db { - struct db base; - - char *path; - - struct directory *root; - - time_t mtime; -}; - -G_GNUC_CONST -static inline GQuark -simple_db_quark(void) -{ - return g_quark_from_static_string("simple_db"); -} - -G_GNUC_PURE -static const struct directory * -simple_db_lookup_directory(const struct simple_db *db, const char *uri) -{ - assert(db != NULL); - assert(db->root != NULL); - assert(uri != NULL); - - db_lock(); - struct directory *directory = - directory_lookup_directory(db->root, uri); - db_unlock(); - return directory; -} - -static struct db * -simple_db_init(const struct config_param *param, GError **error_r) -{ - struct simple_db *db = g_malloc(sizeof(*db)); - db_base_init(&db->base, &simple_db_plugin); - - GError *error = NULL; - db->path = config_dup_block_path(param, "path", &error); - if (db->path == NULL) { - g_free(db); - if (error != NULL) - g_propagate_error(error_r, error); - else - g_set_error(error_r, simple_db_quark(), 0, - "No \"path\" parameter specified"); - return NULL; - } - - return &db->base; -} - -static void -simple_db_finish(struct db *_db) -{ - struct simple_db *db = (struct simple_db *)_db; - - g_free(db->path); - g_free(db); -} - -static bool -simple_db_check(struct simple_db *db, GError **error_r) -{ - assert(db != NULL); - assert(db->path != NULL); - - /* Check if the file exists */ - if (access(db->path, F_OK)) { - /* If the file doesn't exist, we can't check if we can write - * it, so we are going to try to get the directory path, and - * see if we can write a file in that */ - char *dirPath = g_path_get_dirname(db->path); - - /* Check that the parent part of the path is a directory */ - struct stat st; - if (stat(dirPath, &st) < 0) { - g_free(dirPath); - g_set_error(error_r, simple_db_quark(), errno, - "Couldn't stat parent directory of db file " - "\"%s\": %s", - db->path, g_strerror(errno)); - return false; - } - - if (!S_ISDIR(st.st_mode)) { - g_free(dirPath); - g_set_error(error_r, simple_db_quark(), 0, - "Couldn't create db file \"%s\" because the " - "parent path is not a directory", - db->path); - return false; - } - - /* Check if we can write to the directory */ - if (access(dirPath, X_OK | W_OK)) { - g_set_error(error_r, simple_db_quark(), errno, - "Can't create db file in \"%s\": %s", - dirPath, g_strerror(errno)); - g_free(dirPath); - return false; - } - - g_free(dirPath); - - return true; - } - - /* Path exists, now check if it's a regular file */ - struct stat st; - if (stat(db->path, &st) < 0) { - g_set_error(error_r, simple_db_quark(), errno, - "Couldn't stat db file \"%s\": %s", - db->path, g_strerror(errno)); - return false; - } - - if (!S_ISREG(st.st_mode)) { - g_set_error(error_r, simple_db_quark(), 0, - "db file \"%s\" is not a regular file", - db->path); - return false; - } - - /* And check that we can write to it */ - if (access(db->path, R_OK | W_OK)) { - g_set_error(error_r, simple_db_quark(), errno, - "Can't open db file \"%s\" for reading/writing: %s", - db->path, g_strerror(errno)); - return false; - } - - return true; -} - -static bool -simple_db_load(struct simple_db *db, GError **error_r) -{ - assert(db != NULL); - assert(db->path != NULL); - assert(db->root != NULL); - - FILE *fp = fopen(db->path, "r"); - if (fp == NULL) { - g_set_error(error_r, simple_db_quark(), errno, - "Failed to open database file \"%s\": %s", - db->path, g_strerror(errno)); - return false; - } - - if (!db_load_internal(fp, db->root, error_r)) { - fclose(fp); - return false; - } - - fclose(fp); - - struct stat st; - if (stat(db->path, &st) == 0) - db->mtime = st.st_mtime; - - return true; -} - -static bool -simple_db_open(struct db *_db, G_GNUC_UNUSED GError **error_r) -{ - struct simple_db *db = (struct simple_db *)_db; - - db->root = directory_new_root(); - db->mtime = 0; - - GError *error = NULL; - if (!simple_db_load(db, &error)) { - directory_free(db->root); - - g_warning("Failed to load database: %s", error->message); - g_error_free(error); - - if (!simple_db_check(db, error_r)) - return false; - - db->root = directory_new_root(); - } - - return true; -} - -static void -simple_db_close(struct db *_db) -{ - struct simple_db *db = (struct simple_db *)_db; - - assert(db->root != NULL); - - directory_free(db->root); -} - -static struct song * -simple_db_get_song(struct db *_db, const char *uri, GError **error_r) -{ - struct simple_db *db = (struct simple_db *)_db; - - assert(db->root != NULL); - - db_lock(); - struct song *song = directory_lookup_song(db->root, uri); - db_unlock(); - if (song == NULL) - g_set_error(error_r, db_quark(), DB_NOT_FOUND, - "No such song: %s", uri); - - return song; -} - -static bool -simple_db_visit(struct db *_db, const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - const struct simple_db *db = (const struct simple_db *)_db; - const struct directory *directory = - simple_db_lookup_directory(db, selection->uri); - if (directory == NULL) { - struct song *song; - if (visitor->song != NULL && - (song = simple_db_get_song(_db, selection->uri, NULL)) != NULL) - return visitor->song(song, ctx, error_r); - - g_set_error(error_r, db_quark(), DB_NOT_FOUND, - "No such directory"); - return false; - } - - if (selection->recursive && visitor->directory != NULL && - !visitor->directory(directory, ctx, error_r)) - return false; - - db_lock(); - bool ret = directory_walk(directory, selection->recursive, - visitor, ctx, error_r); - db_unlock(); - return ret; -} - -const struct db_plugin simple_db_plugin = { - .name = "simple", - .init = simple_db_init, - .finish = simple_db_finish, - .open = simple_db_open, - .close = simple_db_close, - .get_song = simple_db_get_song, - .visit = simple_db_visit, -}; - -struct directory * -simple_db_get_root(struct db *_db) -{ - struct simple_db *db = (struct simple_db *)_db; - - assert(db != NULL); - assert(db->root != NULL); - - return db->root; -} - -bool -simple_db_save(struct db *_db, GError **error_r) -{ - struct simple_db *db = (struct simple_db *)_db; - struct directory *music_root = db->root; - - db_lock(); - - g_debug("removing empty directories from DB"); - directory_prune_empty(music_root); - - g_debug("sorting DB"); - directory_sort(music_root); - - db_unlock(); - - g_debug("writing DB"); - - FILE *fp = fopen(db->path, "w"); - if (!fp) { - g_set_error(error_r, simple_db_quark(), errno, - "unable to write to db file \"%s\": %s", - db->path, g_strerror(errno)); - return false; - } - - db_save_internal(fp, music_root); - - if (ferror(fp)) { - g_set_error(error_r, simple_db_quark(), errno, - "Failed to write to database file: %s", - g_strerror(errno)); - fclose(fp); - return false; - } - - fclose(fp); - - struct stat st; - if (stat(db->path, &st) == 0) - db->mtime = st.st_mtime; - - return true; -} - -time_t -simple_db_get_mtime(const struct db *_db) -{ - const struct simple_db *db = (const struct simple_db *)_db; - - assert(db != NULL); - assert(db->root != NULL); - - return db->mtime; -} diff --git a/src/db/simple_db_plugin.h b/src/db/simple_db_plugin.h deleted file mode 100644 index 511505846..000000000 --- a/src/db/simple_db_plugin.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SIMPLE_DB_PLUGIN_H -#define MPD_SIMPLE_DB_PLUGIN_H - -#include <glib.h> -#include <stdbool.h> -#include <time.h> - -extern const struct db_plugin simple_db_plugin; - -struct db; - -G_GNUC_PURE -struct directory * -simple_db_get_root(struct db *db); - -bool -simple_db_save(struct db *db, GError **error_r); - -G_GNUC_PURE -time_t -simple_db_get_mtime(const struct db *db); - -#endif diff --git a/src/dbUtils.c b/src/dbUtils.c deleted file mode 100644 index c212d9f9c..000000000 --- a/src/dbUtils.c +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "dbUtils.h" -#include "locate.h" -#include "database.h" -#include "db_visitor.h" -#include "playlist.h" -#include "stored_playlist.h" - -#include <glib.h> - -static bool -add_to_queue_song(struct song *song, void *ctx, GError **error_r) -{ - struct player_control *pc = ctx; - - enum playlist_result result = - playlist_append_song(&g_playlist, pc, song, NULL); - if (result != PLAYLIST_RESULT_SUCCESS) { - g_set_error(error_r, playlist_quark(), result, - "Playlist error"); - return false; - } - - return true; -} - -static const struct db_visitor add_to_queue_visitor = { - .song = add_to_queue_song, -}; - -bool -addAllIn(struct player_control *pc, const char *uri, GError **error_r) -{ - return db_walk(uri, &add_to_queue_visitor, pc, error_r); -} - -struct add_data { - const char *path; -}; - -static bool -add_to_spl_song(struct song *song, void *ctx, GError **error_r) -{ - struct add_data *data = ctx; - - if (!spl_append_song(data->path, song, error_r)) - return false; - - return true; -} - -static const struct db_visitor add_to_spl_visitor = { - .song = add_to_spl_song, -}; - -bool -addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8, - GError **error_r) -{ - struct add_data data = { - .path = path_utf8, - }; - - return db_walk(uri_utf8, &add_to_spl_visitor, &data, error_r); -} - -struct find_add_data { - struct player_control *pc; - const struct locate_item_list *criteria; -}; - -static bool -find_add_song(struct song *song, void *ctx, GError **error_r) -{ - struct find_add_data *data = ctx; - - if (!locate_song_match(song, data->criteria)) - return true; - - enum playlist_result result = - playlist_append_song(&g_playlist, data->pc, - song, NULL); - if (result != PLAYLIST_RESULT_SUCCESS) { - g_set_error(error_r, playlist_quark(), result, - "Playlist error"); - return false; - } - - return true; -} - -static const struct db_visitor find_add_visitor = { - .song = find_add_song, -}; - -bool -findAddIn(struct player_control *pc, const char *name, - const struct locate_item_list *criteria, GError **error_r) -{ - struct find_add_data data; - data.pc = pc; - data.criteria = criteria; - - return db_walk(name, &find_add_visitor, &data, error_r); -} - -static bool -searchadd_visitor_song(struct song *song, void *_data, GError **error_r) -{ - struct find_add_data *data = _data; - - if (!locate_song_search(song, data->criteria)) - return true; - - enum playlist_result result = - playlist_append_song(&g_playlist, data->pc, song, NULL); - if (result != PLAYLIST_RESULT_SUCCESS) { - g_set_error(error_r, playlist_quark(), result, - "Playlist error"); - return false; - } - - return true; -} - -static const struct db_visitor searchadd_visitor = { - .song = searchadd_visitor_song, -}; - -bool -search_add_songs(struct player_control *pc, const char *uri, - const struct locate_item_list *criteria, - GError **error_r) -{ - struct locate_item_list *new_list = - locate_item_list_casefold(criteria); - struct find_add_data data = { - .pc = pc, - .criteria = new_list, - }; - - bool success = db_walk(uri, &searchadd_visitor, &data, error_r); - - locate_item_list_free(new_list); - - return success; -} - -struct search_add_playlist_data { - const char *playlist; - const struct locate_item_list *criteria; -}; - -static bool -searchaddpl_visitor_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct search_add_playlist_data *data = _data; - - if (!locate_song_search(song, data->criteria)) - return true; - - if (!spl_append_song(data->playlist, song, error_r)) - return false; - - return true; -} - -static const struct db_visitor searchaddpl_visitor = { - .song = searchaddpl_visitor_song, -}; - -bool -search_add_to_playlist(const char *uri, const char *path_utf8, - const struct locate_item_list *criteria, - GError **error_r) -{ - struct locate_item_list *new_list - = locate_item_list_casefold(criteria); - struct search_add_playlist_data data = { - .playlist = path_utf8, - .criteria = new_list, - }; - - bool success = db_walk(uri, &searchaddpl_visitor, &data, error_r); - - locate_item_list_free(new_list); - - return success; -} diff --git a/src/dbUtils.h b/src/dbUtils.h deleted file mode 100644 index 706c807fd..000000000 --- a/src/dbUtils.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_UTILS_H -#define MPD_DB_UTILS_H - -#include "gcc.h" - -#include <glib.h> -#include <stdbool.h> - -struct locate_item_list; -struct player_control; - -gcc_nonnull(1,2) -bool -addAllIn(struct player_control *pc, const char *uri, GError **error_r); - -gcc_nonnull(1,2) -bool -addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8, - GError **error_r); - -gcc_nonnull(1,2,3) -bool -findAddIn(struct player_control *pc, const char *name, - const struct locate_item_list *criteria, GError **error_r); - -gcc_nonnull(1,2,3) -bool -search_add_songs(struct player_control *pc, const char *uri, - const struct locate_item_list *criteria, GError **error_r); - -gcc_nonnull(1,2,3) -bool -search_add_to_playlist(const char *uri, const char *path_utf8, - const struct locate_item_list *criteria, - GError **error_r); - -#endif diff --git a/src/db_internal.h b/src/db_internal.h deleted file mode 100644 index a33351524..000000000 --- a/src/db_internal.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_INTERNAL_H -#define MPD_DB_INTERNAL_H - -#include "db_plugin.h" - -#include <assert.h> - -static inline void -db_base_init(struct db *db, const struct db_plugin *plugin) -{ - assert(plugin != NULL); - - db->plugin = plugin; -} - -#endif diff --git a/src/db_lock.c b/src/db_lock.c deleted file mode 100644 index 53759673d..000000000 --- a/src/db_lock.c +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "db_lock.h" -#include "gcc.h" - -#if GCC_CHECK_VERSION(4, 2) -/* workaround for a warning caused by G_STATIC_MUTEX_INIT */ -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif - -GStaticMutex db_mutex = G_STATIC_MUTEX_INIT; - -#ifndef NDEBUG -GThread *db_mutex_holder; -#endif diff --git a/src/db_lock.h b/src/db_lock.h deleted file mode 100644 index 4640502f3..000000000 --- a/src/db_lock.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Support for locking data structures from the database, for safe - * multi-threading. - */ - -#ifndef MPD_DB_LOCK_H -#define MPD_DB_LOCK_H - -#include "check.h" - -#include <glib.h> -#include <assert.h> -#include <stdbool.h> - -extern GStaticMutex db_mutex; - -#ifndef NDEBUG - -extern GThread *db_mutex_holder; - -/** - * Does the current thread hold the database lock? - */ -G_GNUC_PURE -static inline bool -holding_db_lock(void) -{ - return db_mutex_holder == g_thread_self(); -} - -#endif - -/** - * Obtain the global database lock. This is needed before - * dereferencing a #song or #directory. It is not recursive. - */ -static inline void -db_lock(void) -{ - assert(!holding_db_lock()); - - g_static_mutex_lock(&db_mutex); - - assert(db_mutex_holder == NULL); -#ifndef NDEBUG - db_mutex_holder = g_thread_self(); -#endif -} - -/** - * Release the global database lock. - */ -static inline void -db_unlock(void) -{ - assert(holding_db_lock()); -#ifndef NDEBUG - db_mutex_holder = NULL; -#endif - - g_static_mutex_unlock(&db_mutex); -} - -#endif diff --git a/src/db_plugin.h b/src/db_plugin.h deleted file mode 100644 index 1c7e14ede..000000000 --- a/src/db_plugin.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This header declares the db_plugin class. It describes a - * plugin API for databases of song metadata. - */ - -#ifndef MPD_DB_PLUGIN_H -#define MPD_DB_PLUGIN_H - -#include <glib.h> -#include <assert.h> -#include <stdbool.h> - -struct config_param; -struct db_selection; -struct db_visitor; - -struct db { - const struct db_plugin *plugin; -}; - -struct db_plugin { - const char *name; - - /** - * Allocates and configures a database. - */ - struct db *(*init)(const struct config_param *param, GError **error_r); - - /** - * Free instance data. - */ - void (*finish)(struct db *db); - - /** - * Open the database. Read it into memory if applicable. - */ - bool (*open)(struct db *db, GError **error_r); - - /** - * Close the database, free allocated memory. - */ - void (*close)(struct db *db); - - /** - * Look up a song (including tag data) in the database. - * - * @param the URI of the song within the music directory - * (UTF-8) - */ - struct song *(*get_song)(struct db *db, const char *uri, - GError **error_r); - - /** - * Visit the selected entities. - */ - bool (*visit)(struct db *db, const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r); -}; - -G_GNUC_MALLOC -static inline struct db * -db_plugin_new(const struct db_plugin *plugin, const struct config_param *param, - GError **error_r) -{ - assert(plugin != NULL); - assert(plugin->init != NULL); - assert(plugin->finish != NULL); - assert(plugin->get_song != NULL); - assert(plugin->visit != NULL); - assert(error_r == NULL || *error_r == NULL); - - struct db *db = plugin->init(param, error_r); - assert(db == NULL || db->plugin == plugin); - assert(db != NULL || error_r == NULL || *error_r != NULL); - - return db; -} - -static inline void -db_plugin_free(struct db *db) -{ - assert(db != NULL); - assert(db->plugin != NULL); - assert(db->plugin->finish != NULL); - - db->plugin->finish(db); -} - -static inline bool -db_plugin_open(struct db *db, GError **error_r) -{ - assert(db != NULL); - assert(db->plugin != NULL); - - return db->plugin->open != NULL - ? db->plugin->open(db, error_r) - : true; -} - -static inline void -db_plugin_close(struct db *db) -{ - assert(db != NULL); - assert(db->plugin != NULL); - - if (db->plugin->close != NULL) - db->plugin->close(db); -} - -static inline struct song * -db_plugin_get_song(struct db *db, const char *uri, GError **error_r) -{ - assert(db != NULL); - assert(db->plugin != NULL); - assert(db->plugin->get_song != NULL); - assert(uri != NULL); - - return db->plugin->get_song(db, uri, error_r); -} - -static inline bool -db_plugin_visit(struct db *db, const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - assert(db != NULL); - assert(db->plugin != NULL); - assert(selection != NULL); - assert(visitor != NULL); - assert(error_r == NULL || *error_r == NULL); - - return db->plugin->visit(db, selection, visitor, ctx, error_r); -} - -#endif diff --git a/src/db_print.c b/src/db_print.c deleted file mode 100644 index 4d7e3f5ef..000000000 --- a/src/db_print.c +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "db_print.h" -#include "db_selection.h" -#include "db_visitor.h" -#include "locate.h" -#include "directory.h" -#include "database.h" -#include "client.h" -#include "song.h" -#include "song_print.h" -#include "playlist_vector.h" -#include "tag.h" -#include "strset.h" - -#include <glib.h> - -typedef struct _ListCommandItem { - int8_t tagType; - const struct locate_item_list *criteria; -} ListCommandItem; - -typedef struct _SearchStats { - const struct locate_item_list *criteria; - int numberOfSongs; - unsigned long playTime; -} SearchStats; - -static bool -print_visitor_directory(const struct directory *directory, void *data, - G_GNUC_UNUSED GError **error_r) -{ - struct client *client = data; - - if (!directory_is_root(directory)) - client_printf(client, "directory: %s\n", directory_get_path(directory)); - - return true; -} - -static void -print_playlist_in_directory(struct client *client, - const struct directory *directory, - const char *name_utf8) -{ - if (directory_is_root(directory)) - client_printf(client, "playlist: %s\n", name_utf8); - else - client_printf(client, "playlist: %s/%s\n", - directory_get_path(directory), name_utf8); -} - -static bool -print_visitor_song(struct song *song, void *data, - G_GNUC_UNUSED GError **error_r) -{ - assert(song != NULL); - assert(song->parent != NULL); - - struct client *client = data; - song_print_uri(client, song); - - if (song->tag != NULL && song->tag->has_playlist) - /* this song file has an embedded CUE sheet */ - print_playlist_in_directory(client, song->parent, - song->uri); - - return true; -} - -static bool -print_visitor_song_info(struct song *song, void *data, - G_GNUC_UNUSED GError **error_r) -{ - assert(song != NULL); - assert(song->parent != NULL); - - struct client *client = data; - song_print_info(client, song); - - if (song->tag != NULL && song->tag->has_playlist) - /* this song file has an embedded CUE sheet */ - print_playlist_in_directory(client, song->parent, - song->uri); - - return true; -} - -static bool -print_visitor_playlist(const struct playlist_metadata *playlist, - const struct directory *directory, void *ctx, - G_GNUC_UNUSED GError **error_r) -{ - struct client *client = ctx; - print_playlist_in_directory(client, directory, playlist->name); - return true; -} - -static bool -print_visitor_playlist_info(const struct playlist_metadata *playlist, - const struct directory *directory, - void *ctx, G_GNUC_UNUSED GError **error_r) -{ - struct client *client = ctx; - print_playlist_in_directory(client, directory, playlist->name); - -#ifndef G_OS_WIN32 - struct tm tm; -#endif - char timestamp[32]; - time_t t = playlist->mtime; - strftime(timestamp, sizeof(timestamp), -#ifdef G_OS_WIN32 - "%Y-%m-%dT%H:%M:%SZ", - gmtime(&t) -#else - "%FT%TZ", - gmtime_r(&t, &tm) -#endif - ); - client_printf(client, "Last-Modified: %s\n", timestamp); - - return true; -} - -static const struct db_visitor print_visitor = { - .directory = print_visitor_directory, - .song = print_visitor_song, - .playlist = print_visitor_playlist, -}; - -static const struct db_visitor print_info_visitor = { - .directory = print_visitor_directory, - .song = print_visitor_song_info, - .playlist = print_visitor_playlist_info, -}; - -bool -db_selection_print(struct client *client, const struct db_selection *selection, - bool full, GError **error_r) -{ - return db_visit(selection, full ? &print_info_visitor : &print_visitor, - client, error_r); -} - -struct search_data { - struct client *client; - const struct locate_item_list *criteria; -}; - -static bool -search_visitor_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct search_data *data = _data; - - if (locate_song_search(song, data->criteria)) - song_print_info(data->client, song); - - return true; -} - -static const struct db_visitor search_visitor = { - .song = search_visitor_song, -}; - -bool -searchForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r) -{ - struct locate_item_list *new_list - = locate_item_list_casefold(criteria); - struct search_data data; - - data.client = client; - data.criteria = new_list; - - bool success = db_walk(name, &search_visitor, &data, error_r); - - locate_item_list_free(new_list); - - return success; -} - -static bool -find_visitor_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct search_data *data = _data; - - if (locate_song_match(song, data->criteria)) - song_print_info(data->client, song); - - return true; -} - -static const struct db_visitor find_visitor = { - .song = find_visitor_song, -}; - -bool -findSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r) -{ - struct search_data data; - - data.client = client; - data.criteria = criteria; - - return db_walk(name, &find_visitor, &data, error_r); -} - -static void printSearchStats(struct client *client, SearchStats *stats) -{ - client_printf(client, "songs: %i\n", stats->numberOfSongs); - client_printf(client, "playtime: %li\n", stats->playTime); -} - -static bool -stats_visitor_song(struct song *song, void *data, - G_GNUC_UNUSED GError **error_r) -{ - SearchStats *stats = data; - - if (locate_song_match(song, stats->criteria)) { - stats->numberOfSongs++; - stats->playTime += song_get_duration(song); - } - - return true; -} - -static const struct db_visitor stats_visitor = { - .song = stats_visitor_song, -}; - -bool -searchStatsForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r) -{ - SearchStats stats; - - stats.criteria = criteria; - stats.numberOfSongs = 0; - stats.playTime = 0; - - if (!db_walk(name, &stats_visitor, &stats, error_r)) - return false; - - printSearchStats(client, &stats); - return true; -} - -bool -printAllIn(struct client *client, const char *uri_utf8, GError **error_r) -{ - struct db_selection selection; - db_selection_init(&selection, uri_utf8, true); - return db_selection_print(client, &selection, false, error_r); -} - -bool -printInfoForAllIn(struct client *client, const char *uri_utf8, - GError **error_r) -{ - struct db_selection selection; - db_selection_init(&selection, uri_utf8, true); - return db_selection_print(client, &selection, true, error_r); -} - -static ListCommandItem * -newListCommandItem(int tagType, const struct locate_item_list *criteria) -{ - ListCommandItem *item = g_new(ListCommandItem, 1); - - item->tagType = tagType; - item->criteria = criteria; - - return item; -} - -static void freeListCommandItem(ListCommandItem * item) -{ - g_free(item); -} - -static void -visitTag(struct client *client, struct strset *set, - struct song *song, enum tag_type tagType) -{ - struct tag *tag = song->tag; - bool found = false; - - if (tagType == LOCATE_TAG_FILE_TYPE) { - song_print_uri(client, song); - return; - } - - if (!tag) - return; - - for (unsigned i = 0; i < tag->num_items; i++) { - if (tag->items[i]->type == tagType) { - strset_add(set, tag->items[i]->value); - found = true; - } - } - - if (!found) - strset_add(set, ""); -} - -struct list_tags_data { - struct client *client; - ListCommandItem *item; - struct strset *set; -}; - -static bool -unique_tags_visitor_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct list_tags_data *data = _data; - ListCommandItem *item = data->item; - - if (locate_song_match(song, item->criteria)) - visitTag(data->client, data->set, song, item->tagType); - - return true; -} - -static const struct db_visitor unique_tags_visitor = { - .song = unique_tags_visitor_song, -}; - -bool -listAllUniqueTags(struct client *client, int type, - const struct locate_item_list *criteria, - GError **error_r) -{ - ListCommandItem *item = newListCommandItem(type, criteria); - struct list_tags_data data = { - .client = client, - .item = item, - }; - - if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { - data.set = strset_new(); - } - - if (!db_walk("", &unique_tags_visitor, &data, error_r)) { - freeListCommandItem(item); - return false; - } - - if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { - const char *value; - - strset_rewind(data.set); - - while ((value = strset_next(data.set)) != NULL) - client_printf(client, "%s: %s\n", - tag_item_names[type], - value); - - strset_free(data.set); - } - - freeListCommandItem(item); - - return true; -} diff --git a/src/db_print.h b/src/db_print.h deleted file mode 100644 index 1b957da18..000000000 --- a/src/db_print.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_PRINT_H -#define MPD_DB_PRINT_H - -#include "gcc.h" - -#include <glib.h> -#include <stdbool.h> - -struct client; -struct locate_item_list; -struct db_selection; -struct db_visitor; - -gcc_nonnull(1,2) -bool -db_selection_print(struct client *client, const struct db_selection *selection, - bool full, GError **error_r); - -gcc_nonnull(1,2) -bool -printAllIn(struct client *client, const char *uri_utf8, GError **error_r); - -gcc_nonnull(1,2) -bool -printInfoForAllIn(struct client *client, const char *uri_utf8, - GError **error_r); - -gcc_nonnull(1,2,3) -bool -searchForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r); - -gcc_nonnull(1,2,3) -bool -findSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r); - -gcc_nonnull(1,2,3) -bool -searchStatsForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r); - -gcc_nonnull(1,3) -bool -listAllUniqueTags(struct client *client, int type, - const struct locate_item_list *criteria, - GError **error_r); - -#endif diff --git a/src/db_save.c b/src/db_save.c deleted file mode 100644 index 4af9d58b8..000000000 --- a/src/db_save.c +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "db_save.h" -#include "db_lock.h" -#include "directory.h" -#include "directory_save.h" -#include "song.h" -#include "path.h" -#include "text_file.h" -#include "tag.h" -#include "tag_internal.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "database" - -#define DIRECTORY_INFO_BEGIN "info_begin" -#define DIRECTORY_INFO_END "info_end" -#define DB_FORMAT_PREFIX "format: " -#define DIRECTORY_MPD_VERSION "mpd_version: " -#define DIRECTORY_FS_CHARSET "fs_charset: " -#define DB_TAG_PREFIX "tag: " - -enum { - DB_FORMAT = 1, -}; - -G_GNUC_CONST -static GQuark -db_quark(void) -{ - return g_quark_from_static_string("database"); -} - -void -db_save_internal(FILE *fp, const struct directory *music_root) -{ - assert(music_root != NULL); - - fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN); - fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT); - fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION); - fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, path_get_fs_charset()); - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (!ignore_tag_items[i]) - fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]); - - fprintf(fp, "%s\n", DIRECTORY_INFO_END); - - directory_save(fp, music_root); -} - -bool -db_load_internal(FILE *fp, struct directory *music_root, GError **error) -{ - GString *buffer = g_string_sized_new(1024); - char *line; - int format = 0; - bool found_charset = false, found_version = false; - bool success; - bool tags[TAG_NUM_OF_ITEM_TYPES]; - - assert(music_root != NULL); - - /* get initial info */ - line = read_text_line(fp, buffer); - if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) { - g_set_error(error, db_quark(), 0, "Database corrupted"); - g_string_free(buffer, true); - return false; - } - - memset(tags, false, sizeof(tags)); - - while ((line = read_text_line(fp, buffer)) != NULL && - strcmp(line, DIRECTORY_INFO_END) != 0) { - if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) { - format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1); - } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) { - if (found_version) { - g_set_error(error, db_quark(), 0, - "Duplicate version line"); - g_string_free(buffer, true); - return false; - } - - found_version = true; - } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) { - const char *new_charset, *old_charset; - - if (found_charset) { - g_set_error(error, db_quark(), 0, - "Duplicate charset line"); - g_string_free(buffer, true); - return false; - } - - found_charset = true; - - new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1; - old_charset = path_get_fs_charset(); - if (old_charset != NULL - && strcmp(new_charset, old_charset)) { - g_set_error(error, db_quark(), 0, - "Existing database has charset " - "\"%s\" instead of \"%s\"; " - "discarding database file", - new_charset, old_charset); - g_string_free(buffer, true); - return false; - } - } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) { - const char *name = line + sizeof(DB_TAG_PREFIX) - 1; - enum tag_type tag = tag_name_parse(name); - if (tag == TAG_NUM_OF_ITEM_TYPES) { - g_set_error(error, db_quark(), 0, - "Unrecognized tag '%s', " - "discarding database file", - name); - return false; - } - - tags[tag] = true; - } else { - g_set_error(error, db_quark(), 0, - "Malformed line: %s", line); - g_string_free(buffer, true); - return false; - } - } - - if (format != DB_FORMAT) { - g_set_error(error, db_quark(), 0, - "Database format mismatch, " - "discarding database file"); - return false; - } - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - if (!ignore_tag_items[i] && !tags[i]) { - g_set_error(error, db_quark(), 0, - "Tag list mismatch, " - "discarding database file"); - return false; - } - } - - g_debug("reading DB"); - - db_lock(); - success = directory_load(fp, music_root, buffer, error); - db_unlock(); - g_string_free(buffer, true); - - return success; -} diff --git a/src/db_save.h b/src/db_save.h deleted file mode 100644 index e760ec881..000000000 --- a/src/db_save.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_SAVE_H -#define MPD_DB_SAVE_H - -#include <glib.h> -#include <stdbool.h> -#include <stdio.h> - -struct directory; - -void -db_save_internal(FILE *file, const struct directory *root); - -bool -db_load_internal(FILE *file, struct directory *root, GError **error); - -#endif diff --git a/src/db_selection.h b/src/db_selection.h deleted file mode 100644 index 2cebb4907..000000000 --- a/src/db_selection.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_SELECTION_H -#define MPD_DB_SELECTION_H - -#include "gcc.h" - -#include <assert.h> - -struct directory; -struct song; - -struct db_selection { - /** - * The base URI of the search (UTF-8). Must not begin or end - * with a slash. NULL or an empty string searches the whole - * database. - */ - const char *uri; - - /** - * Recursively search all sub directories? - */ - bool recursive; -}; - -gcc_nonnull(1,2) -static inline void -db_selection_init(struct db_selection *selection, - const char *uri, bool recursive) -{ - assert(selection != NULL); - assert(uri != NULL); - - selection->uri = uri; - selection->recursive = recursive; -} - -#endif diff --git a/src/db_visitor.h b/src/db_visitor.h deleted file mode 100644 index 6b90c1868..000000000 --- a/src/db_visitor.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_VISITOR_H -#define MPD_DB_VISITOR_H - -struct directory; -struct song; -struct playlist_metadata; - -struct db_visitor { - /** - * Visit a directory. Optional method. - * - * @return true to continue the operation, false on error (set error_r) - */ - bool (*directory)(const struct directory *directory, void *ctx, - GError **error_r); - - /** - * Visit a song. Optional method. - * - * @return true to continue the operation, false on error (set error_r) - */ - bool (*song)(struct song *song, void *ctx, GError **error_r); - - /** - * Visit a playlist. Optional method. - * - * @param directory the directory the playlist resides in - * @return true to continue the operation, false on error (set error_r) - */ - bool (*playlist)(const struct playlist_metadata *playlist, - const struct directory *directory, void *ctx, - GError **error_r); -}; - -#endif diff --git a/src/decoder/AdPlugDecoderPlugin.cxx b/src/decoder/AdPlugDecoderPlugin.cxx new file mode 100644 index 000000000..6d08fab56 --- /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 000000000..9fdf438aa --- /dev/null +++ b/src/decoder/AdPlugDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_ADPLUG_H +#define MPD_DECODER_ADPLUG_H + +extern const struct decoder_plugin adplug_decoder_plugin; + +#endif diff --git a/src/decoder/FLACCommon.cxx b/src/decoder/FLACCommon.cxx new file mode 100644 index 000000000..25fd1f964 --- /dev/null +++ b/src/decoder/FLACCommon.cxx @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Common data structures and functions used by FLAC and OggFLAC + */ + +#include "config.h" +#include "FLACCommon.hxx" +#include "FLACMetaData.hxx" +#include "FLAC_PCM.hxx" + +extern "C" { +#include "audio_check.h" +} + +#include <glib.h> + +#include <assert.h> + +flac_data::flac_data(struct decoder *_decoder, + struct input_stream *_input_stream) + :FLACInput(_input_stream, _decoder), + initialized(false), unsupported(false), + total_frames(0), first_frame(0), next_frame(0), position(0), + decoder(_decoder), input_stream(_input_stream), + tag(nullptr) +{ + pcm_buffer_init(&buffer); +} + +flac_data::~flac_data() +{ + pcm_buffer_deinit(&buffer); + + if (tag != nullptr) + tag_free(tag); +} + +static enum sample_format +flac_sample_format(unsigned bits_per_sample) +{ + switch (bits_per_sample) { + case 8: + return SAMPLE_FORMAT_S8; + + case 16: + return SAMPLE_FORMAT_S16; + + case 24: + return SAMPLE_FORMAT_S24_P32; + + case 32: + return SAMPLE_FORMAT_S32; + + default: + return SAMPLE_FORMAT_UNDEFINED; + } +} + +static void +flac_got_stream_info(struct flac_data *data, + const FLAC__StreamMetadata_StreamInfo *stream_info) +{ + if (data->initialized || data->unsupported) + return; + + GError *error = nullptr; + if (!audio_format_init_checked(&data->audio_format, + stream_info->sample_rate, + flac_sample_format(stream_info->bits_per_sample), + stream_info->channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); + data->unsupported = true; + return; + } + + data->frame_size = audio_format_frame_size(&data->audio_format); + + if (data->total_frames == 0) + data->total_frames = stream_info->total_samples; + + data->initialized = true; +} + +void flac_metadata_common_cb(const FLAC__StreamMetadata * block, + struct flac_data *data) +{ + if (data->unsupported) + return; + + struct replay_gain_info rgi; + char *mixramp_start; + char *mixramp_end; + + switch (block->type) { + case FLAC__METADATA_TYPE_STREAMINFO: + flac_got_stream_info(data, &block->data.stream_info); + break; + + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + if (flac_parse_replay_gain(&rgi, block)) + decoder_replay_gain(data->decoder, &rgi); + + if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block)) + decoder_mixramp(data->decoder, + mixramp_start, mixramp_end); + + if (data->tag != nullptr) + flac_vorbis_comments_to_tag(data->tag, + &block->data.vorbis_comment); + + default: + break; + } +} + +/** + * This function attempts to call decoder_initialized() in case there + * was no STREAMINFO block. This is allowed for nonseekable streams, + * where the server sends us only a part of the file, without + * providing the STREAMINFO block from the beginning of the file + * (e.g. when seeking with SqueezeBox Server). + */ +static bool +flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) +{ + if (data->unsupported) + return false; + + GError *error = nullptr; + if (!audio_format_init_checked(&data->audio_format, + header->sample_rate, + flac_sample_format(header->bits_per_sample), + header->channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); + data->unsupported = true; + return false; + } + + data->frame_size = audio_format_frame_size(&data->audio_format); + + decoder_initialized(data->decoder, &data->audio_format, + data->input_stream->seekable, + (float)data->total_frames / + (float)data->audio_format.sample_rate); + + data->initialized = true; + + return true; +} + +FLAC__StreamDecoderWriteStatus +flac_common_write(struct flac_data *data, const FLAC__Frame * frame, + const FLAC__int32 *const buf[], + FLAC__uint64 nbytes) +{ + enum decoder_command cmd; + void *buffer; + unsigned bit_rate; + + if (!data->initialized && !flac_got_first_frame(data, &frame->header)) + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + + size_t buffer_size = frame->header.blocksize * data->frame_size; + buffer = pcm_buffer_get(&data->buffer, buffer_size); + + flac_convert(buffer, frame->header.channels, + (enum sample_format)data->audio_format.format, buf, + 0, frame->header.blocksize); + + if (nbytes > 0) + bit_rate = nbytes * 8 * frame->header.sample_rate / + (1000 * frame->header.blocksize); + else + bit_rate = 0; + + cmd = decoder_data(data->decoder, data->input_stream, + buffer, buffer_size, + bit_rate); + data->next_frame += frame->header.blocksize; + switch (cmd) { + case DECODE_COMMAND_NONE: + case DECODE_COMMAND_START: + break; + + case DECODE_COMMAND_STOP: + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + + case DECODE_COMMAND_SEEK: + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} diff --git a/src/decoder/FLACCommon.hxx b/src/decoder/FLACCommon.hxx new file mode 100644 index 000000000..b80372bbf --- /dev/null +++ b/src/decoder/FLACCommon.hxx @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Common data structures and functions used by FLAC and OggFLAC + */ + +#ifndef MPD_FLAC_COMMON_HXX +#define MPD_FLAC_COMMON_HXX + +#include "FLACInput.hxx" +#include "decoder_api.h" + +extern "C" { +#include "pcm_buffer.h" +} + +#include <FLAC/stream_decoder.h> +#include <FLAC/metadata.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "flac" + +struct flac_data : public FLACInput { + struct pcm_buffer buffer; + + /** + * The size of one frame in the output buffer. + */ + unsigned frame_size; + + /** + * Has decoder_initialized() been called yet? + */ + bool initialized; + + /** + * Does the FLAC file contain an unsupported audio format? + */ + bool unsupported; + + /** + * The validated audio format of the FLAC file. This + * attribute is defined if "initialized" is true. + */ + struct audio_format audio_format; + + /** + * The total number of frames in this song. The decoder + * plugin may initialize this attribute to override the value + * provided by libFLAC (e.g. for sub songs from a CUE sheet). + */ + FLAC__uint64 total_frames; + + /** + * The number of the first frame in this song. This is only + * non-zero if playing sub songs from a CUE sheet. + */ + FLAC__uint64 first_frame; + + /** + * The number of the next frame which is going to be decoded. + */ + FLAC__uint64 next_frame; + + FLAC__uint64 position; + + struct decoder *decoder; + struct input_stream *input_stream; + + struct tag *tag; + + 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); + +FLAC__StreamDecoderWriteStatus +flac_common_write(struct flac_data *data, const FLAC__Frame * frame, + const FLAC__int32 *const buf[], + FLAC__uint64 nbytes); + +#endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/FLACDecoderPlugin.cxx b/src/decoder/FLACDecoderPlugin.cxx new file mode 100644 index 000000000..43b604097 --- /dev/null +++ b/src/decoder/FLACDecoderPlugin.cxx @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "FLACDecoderPlugin.h" +#include "FLACCommon.hxx" +#include "FLACMetaData.hxx" +#include "OggCodec.hxx" + +#include <glib.h> + +#include <assert.h> +#include <unistd.h> + +#include <sys/stat.h> +#include <sys/types.h> + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +#error libFLAC is too old +#endif + +static void flacPrintErroredState(FLAC__StreamDecoderState state) +{ + switch (state) { + case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA: + case FLAC__STREAM_DECODER_READ_METADATA: + case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: + case FLAC__STREAM_DECODER_READ_FRAME: + case FLAC__STREAM_DECODER_END_OF_STREAM: + return; + + case FLAC__STREAM_DECODER_OGG_ERROR: + case FLAC__STREAM_DECODER_SEEK_ERROR: + case FLAC__STREAM_DECODER_ABORTED: + case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + case FLAC__STREAM_DECODER_UNINITIALIZED: + break; + } + + g_warning("%s\n", FLAC__StreamDecoderStateString[state]); +} + +static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec, + const FLAC__StreamMetadata * block, void *vdata) +{ + flac_metadata_common_cb(block, (struct flac_data *) vdata); +} + +static FLAC__StreamDecoderWriteStatus +flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame, + const FLAC__int32 *const buf[], void *vdata) +{ + struct flac_data *data = (struct flac_data *) vdata; + FLAC__uint64 nbytes = 0; + + if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) { + if (data->position > 0 && nbytes > data->position) { + nbytes -= data->position; + data->position += nbytes; + } else { + data->position = nbytes; + nbytes = 0; + } + } else + nbytes = 0; + + return flac_common_write(data, frame, buf, nbytes); +} + +static bool +flac_scan_file(const char *file, + const struct tag_handler *handler, void *handler_ctx) +{ + FLACMetadataChain chain; + if (!chain.Read(file)) { + g_debug("Failed to read FLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static bool +flac_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + FLACMetadataChain chain; + if (!chain.Read(is)) { + g_debug("Failed to read FLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +/** + * Some glue code around FLAC__stream_decoder_new(). + */ +static FLAC__StreamDecoder * +flac_decoder_new(void) +{ + FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); + if (sd == nullptr) { + g_warning("FLAC__stream_decoder_new() failed"); + return nullptr; + } + + if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) + g_debug("FLAC__stream_decoder_set_metadata_respond() has failed"); + + return sd; +} + +static bool +flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd, + FLAC__uint64 duration) +{ + data->total_frames = duration; + + if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) { + g_warning("problem reading metadata"); + return false; + } + + if (data->initialized) { + /* done */ + decoder_initialized(data->decoder, &data->audio_format, + data->input_stream->seekable, + (float)data->total_frames / + (float)data->audio_format.sample_rate); + return true; + } + + if (data->input_stream->seekable) + /* allow the workaround below only for nonseekable + streams*/ + return false; + + /* no stream_info packet found; try to initialize the decoder + from the first frame header */ + FLAC__stream_decoder_process_single(sd); + return data->initialized; +} + +static void +flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, + FLAC__uint64 t_start, FLAC__uint64 t_end) +{ + struct decoder *decoder = data->decoder; + enum decoder_command cmd; + + data->first_frame = t_start; + + while (true) { + if (data->tag != nullptr && !tag_is_empty(data->tag)) { + cmd = decoder_tag(data->decoder, data->input_stream, + data->tag); + tag_free(data->tag); + data->tag = tag_new(); + } else + cmd = decoder_get_command(decoder); + + if (cmd == DECODE_COMMAND_SEEK) { + FLAC__uint64 seek_sample = t_start + + decoder_seek_where(decoder) * + data->audio_format.sample_rate; + if (seek_sample >= t_start && + (t_end == 0 || seek_sample <= t_end) && + FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) { + data->next_frame = seek_sample; + data->position = 0; + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } else if (cmd == DECODE_COMMAND_STOP || + FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM) + break; + + if (t_end != 0 && data->next_frame >= t_end) + /* end of this sub track */ + break; + + if (!FLAC__stream_decoder_process_single(flac_dec) && + decoder_get_command(decoder) == DECODE_COMMAND_NONE) { + /* a failure that was not triggered by a + decoder command */ + flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec)); + break; + } + } +} + +static FLAC__StreamDecoderInitStatus +stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) +{ + return FLAC__stream_decoder_init_ogg_stream(flac_dec, + FLACInput::Read, + FLACInput::Seek, + FLACInput::Tell, + FLACInput::Length, + FLACInput::Eof, + flac_write_cb, + flacMetadata, + FLACInput::Error, + data); +} + +static FLAC__StreamDecoderInitStatus +stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) +{ + return FLAC__stream_decoder_init_stream(flac_dec, + FLACInput::Read, + FLACInput::Seek, + FLACInput::Tell, + FLACInput::Length, + FLACInput::Eof, + flac_write_cb, + flacMetadata, + FLACInput::Error, + data); +} + +static FLAC__StreamDecoderInitStatus +stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg) +{ + return is_ogg + ? stream_init_oggflac(flac_dec, data) + : stream_init_flac(flac_dec, data); +} + +static void +flac_decode_internal(struct decoder * decoder, + struct input_stream *input_stream, + bool is_ogg) +{ + FLAC__StreamDecoder *flac_dec; + + flac_dec = flac_decoder_new(); + if (flac_dec == nullptr) + return; + + struct flac_data data(decoder, input_stream); + data.tag = tag_new(); + + FLAC__StreamDecoderInitStatus status = + stream_init(flac_dec, &data, is_ogg); + if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + FLAC__stream_decoder_delete(flac_dec); + g_warning("%s", FLAC__StreamDecoderInitStatusString[status]); + return; + } + + if (!flac_decoder_initialize(&data, flac_dec, 0)) { + FLAC__stream_decoder_finish(flac_dec); + FLAC__stream_decoder_delete(flac_dec); + return; + } + + flac_decoder_loop(&data, flac_dec, 0, 0); + + FLAC__stream_decoder_finish(flac_dec); + FLAC__stream_decoder_delete(flac_dec); +} + +static void +flac_decode(struct decoder * decoder, struct input_stream *input_stream) +{ + flac_decode_internal(decoder, input_stream, false); +} + +static bool +oggflac_init(G_GNUC_UNUSED const struct config_param *param) +{ + return !!FLAC_API_SUPPORTS_OGG_FLAC; +} + +static bool +oggflac_scan_file(const char *file, + const struct tag_handler *handler, void *handler_ctx) +{ + FLACMetadataChain chain; + if (!chain.ReadOgg(file)) { + g_debug("Failed to read OggFLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static bool +oggflac_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + FLACMetadataChain chain; + if (!chain.ReadOgg(is)) { + g_debug("Failed to read OggFLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static void +oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) +{ + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_FLAC) + return; + + /* rewind the stream, because ogg_codec_detect() has + moved it */ + input_stream_lock_seek(input_stream, 0, SEEK_SET, nullptr); + + flac_decode_internal(decoder, input_stream, true); +} + +static const char *const oggflac_suffixes[] = { "ogg", "oga", nullptr }; +static const char *const oggflac_mime_types[] = { + "application/ogg", + "application/x-ogg", + "audio/ogg", + "audio/x-flac+ogg", + "audio/x-ogg", + nullptr +}; + +const struct decoder_plugin oggflac_decoder_plugin = { + "oggflac", + oggflac_init, + nullptr, + oggflac_decode, + nullptr, + oggflac_scan_file, + oggflac_scan_stream, + nullptr, + oggflac_suffixes, + oggflac_mime_types, +}; + +static const char *const flac_suffixes[] = { "flac", nullptr }; +static const char *const flac_mime_types[] = { + "application/flac", + "application/x-flac", + "audio/flac", + "audio/x-flac", + nullptr +}; + +const struct decoder_plugin flac_decoder_plugin = { + "flac", + nullptr, + nullptr, + flac_decode, + nullptr, + flac_scan_file, + flac_scan_stream, + nullptr, + flac_suffixes, + flac_mime_types, +}; diff --git a/src/decoder/FLACDecoderPlugin.h b/src/decoder/FLACDecoderPlugin.h new file mode 100644 index 000000000..c99deeef7 --- /dev/null +++ b/src/decoder/FLACDecoderPlugin.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_FLAC_H +#define MPD_DECODER_FLAC_H + +extern const struct decoder_plugin flac_decoder_plugin; +extern const struct decoder_plugin oggflac_decoder_plugin; + +#endif diff --git a/src/decoder/FLACIOHandle.cxx b/src/decoder/FLACIOHandle.cxx new file mode 100644 index 000000000..08ec36e48 --- /dev/null +++ b/src/decoder/FLACIOHandle.cxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FLACIOHandle.hxx" +#include "io_error.h" +#include "gcc.h" + +#include <errno.h> + +static size_t +FLACIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) +{ + input_stream *is = (input_stream *)handle; + + uint8_t *const p0 = (uint8_t *)ptr, *p = p0, + *const end = p0 + size * nmemb; + + /* libFLAC is very picky about short reads, and expects the IO + callback to fill the whole buffer (undocumented!) */ + + GError *error = nullptr; + while (p < end) { + size_t nbytes = input_stream_lock_read(is, p, end - p, &error); + if (nbytes == 0) { + if (error == nullptr) + /* end of file */ + break; + + if (error->domain == errno_quark()) + errno = error->code; + else + /* just some random non-zero + errno value */ + errno = EINVAL; + g_error_free(error); + return 0; + } + + p += nbytes; + } + + /* libFLAC expects a clean errno after returning from the IO + callbacks (undocumented!) */ + errno = 0; + return (p - p0) / size; +} + +static int +FLACIOSeek(FLAC__IOHandle handle, FLAC__int64 offset, int whence) +{ + input_stream *is = (input_stream *)handle; + + return input_stream_lock_seek(is, offset, whence, nullptr) ? 0 : -1; +} + +static FLAC__int64 +FLACIOTell(FLAC__IOHandle handle) +{ + input_stream *is = (input_stream *)handle; + + return is->offset; +} + +static int +FLACIOEof(FLAC__IOHandle handle) +{ + input_stream *is = (input_stream *)handle; + + return input_stream_lock_eof(is); +} + +static int +FLACIOClose(gcc_unused FLAC__IOHandle handle) +{ + /* no-op because the libFLAC caller is repsonsible for closing + the #input_stream */ + + return 0; +} + +const FLAC__IOCallbacks flac_io_callbacks = { + FLACIORead, + nullptr, + nullptr, + nullptr, + FLACIOEof, + FLACIOClose, +}; + +const FLAC__IOCallbacks flac_io_callbacks_seekable = { + FLACIORead, + nullptr, + FLACIOSeek, + FLACIOTell, + FLACIOEof, + FLACIOClose, +}; diff --git a/src/decoder/FLACIOHandle.hxx b/src/decoder/FLACIOHandle.hxx new file mode 100644 index 000000000..505d2db1a --- /dev/null +++ b/src/decoder/FLACIOHandle.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_IO_HANDLE_HXX +#define MPD_FLAC_IO_HANDLE_HXX + +#include "gcc.h" +#include "InputStream.hxx" + +#include <FLAC/callback.h> + +extern const FLAC__IOCallbacks flac_io_callbacks; +extern const FLAC__IOCallbacks flac_io_callbacks_seekable; + +static inline FLAC__IOHandle +ToFLACIOHandle(input_stream *is) +{ + return (FLAC__IOHandle)is; +} + +static inline const FLAC__IOCallbacks & +GetFLACIOCallbacks(const input_stream *is) +{ + return is->seekable + ? flac_io_callbacks_seekable + : flac_io_callbacks; +} + +#endif diff --git a/src/decoder/FLACInput.cxx b/src/decoder/FLACInput.cxx new file mode 100644 index 000000000..ba0a86ce8 --- /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 000000000..7661567d1 --- /dev/null +++ b/src/decoder/FLACInput.hxx @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_INPUT_HXX +#define MPD_FLAC_INPUT_HXX + +#include <FLAC/stream_decoder.h> + +/** + * This class wraps an #input_stream in libFLAC stream decoder + * callbacks. + */ +class FLACInput { + struct decoder *decoder; + + struct input_stream *input_stream; + +public: + FLACInput(struct input_stream *_input_stream, + struct decoder *_decoder=nullptr) + :decoder(_decoder), input_stream(_input_stream) {} + +protected: + FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes); + FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset); + FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset); + FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length); + FLAC__bool Eof(); + void Error(FLAC__StreamDecoderErrorStatus status); + +public: + static FLAC__StreamDecoderReadStatus + Read(const FLAC__StreamDecoder *flac_decoder, + FLAC__byte buffer[], size_t *bytes, void *client_data); + + static FLAC__StreamDecoderSeekStatus + Seek(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 absolute_byte_offset, void *client_data); + + static FLAC__StreamDecoderTellStatus + Tell(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *absolute_byte_offset, void *client_data); + + static FLAC__StreamDecoderLengthStatus + Length(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *stream_length, void *client_data); + + static FLAC__bool + Eof(const FLAC__StreamDecoder *flac_decoder, void *client_data); + + static void + Error(const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, void *client_data); +}; + +#endif diff --git a/src/decoder/FLACMetaData.cxx b/src/decoder/FLACMetaData.cxx new file mode 100644 index 000000000..8273a230b --- /dev/null +++ b/src/decoder/FLACMetaData.cxx @@ -0,0 +1,253 @@ +/* + * 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 "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 <stdlib.h> + +static bool +flac_find_float_comment(const FLAC__StreamMetadata *block, + const char *cmnt, float *fl) +{ + int offset; + size_t pos; + int len; + unsigned char tmp, *p; + + offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, + cmnt); + if (offset < 0) + return false; + + pos = strlen(cmnt) + 1; /* 1 is for '=' */ + len = block->data.vorbis_comment.comments[offset].length - pos; + if (len <= 0) + return false; + + p = &block->data.vorbis_comment.comments[offset].entry[pos]; + tmp = p[len]; + p[len] = '\0'; + *fl = (float)atof((char *)p); + p[len] = tmp; + + return true; +} + +bool +flac_parse_replay_gain(struct replay_gain_info *rgi, + const FLAC__StreamMetadata *block) +{ + bool found = false; + + replay_gain_info_init(rgi); + + if (flac_find_float_comment(block, "replaygain_album_gain", + &rgi->tuples[REPLAY_GAIN_ALBUM].gain)) + found = true; + if (flac_find_float_comment(block, "replaygain_album_peak", + &rgi->tuples[REPLAY_GAIN_ALBUM].peak)) + found = true; + if (flac_find_float_comment(block, "replaygain_track_gain", + &rgi->tuples[REPLAY_GAIN_TRACK].gain)) + found = true; + if (flac_find_float_comment(block, "replaygain_track_peak", + &rgi->tuples[REPLAY_GAIN_TRACK].peak)) + found = true; + + return found; +} + +static bool +flac_find_string_comment(const FLAC__StreamMetadata *block, + const char *cmnt, char **str) +{ + int offset; + size_t pos; + int len; + const unsigned char *p; + + *str = nullptr; + offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, + cmnt); + if (offset < 0) + return false; + + pos = strlen(cmnt) + 1; /* 1 is for '=' */ + len = block->data.vorbis_comment.comments[offset].length - pos; + if (len <= 0) + return false; + + p = &block->data.vorbis_comment.comments[offset].entry[pos]; + *str = g_strndup((const char *)p, len); + + return true; +} + +bool +flac_parse_mixramp(char **mixramp_start, char **mixramp_end, + const FLAC__StreamMetadata *block) +{ + bool found = false; + + if (flac_find_string_comment(block, "mixramp_start", mixramp_start)) + found = true; + if (flac_find_string_comment(block, "mixramp_end", mixramp_end)) + found = true; + + return found; +} + +/** + * Checks if the specified name matches the entry's name, and if yes, + * returns the comment value (not null-temrinated). + */ +static const char * +flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const char *name, size_t *length_r) +{ + size_t name_length = strlen(name); + const char *comment = (const char*)entry->entry; + + if (entry->length <= name_length || + g_ascii_strncasecmp(comment, name, name_length) != 0) + return nullptr; + + if (comment[name_length] == '=') { + *length_r = entry->length - name_length - 1; + return comment + name_length + 1; + } + + return nullptr; +} + +/** + * Check if the comment's name equals the passed name, and if so, copy + * the comment value into the tag. + */ +static bool +flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const char *name, enum tag_type tag_type, + const struct tag_handler *handler, void *handler_ctx) +{ + const char *value; + size_t value_length; + + value = flac_comment_value(entry, name, &value_length); + if (value != nullptr) { + char *p = g_strndup(value, value_length); + tag_handler_invoke_tag(handler, handler_ctx, tag_type, p); + g_free(p); + return true; + } + + return false; +} + +static void +flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const struct tag_handler *handler, void *handler_ctx) +{ + if (handler->pair != nullptr) { + char *name = g_strdup((const char*)entry->entry); + char *value = strchr(name, '='); + + if (value != nullptr && value > name) { + *value++ = 0; + tag_handler_invoke_pair(handler, handler_ctx, + name, value); + } + + g_free(name); + } + + for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) + if (flac_copy_comment(entry, i->name, i->type, + handler, handler_ctx)) + return; + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (flac_copy_comment(entry, + tag_item_names[i], (enum tag_type)i, + handler, handler_ctx)) + return; +} + +static void +flac_scan_comments(const FLAC__StreamMetadata_VorbisComment *comment, + const struct tag_handler *handler, void *handler_ctx) +{ + for (unsigned i = 0; i < comment->num_comments; ++i) + flac_scan_comment(&comment->comments[i], + handler, handler_ctx); +} + +void +flac_scan_metadata(const FLAC__StreamMetadata *block, + const struct tag_handler *handler, void *handler_ctx) +{ + switch (block->type) { + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + flac_scan_comments(&block->data.vorbis_comment, + handler, handler_ctx); + break; + + case FLAC__METADATA_TYPE_STREAMINFO: + if (block->data.stream_info.sample_rate > 0) + tag_handler_invoke_duration(handler, handler_ctx, + flac_duration(&block->data.stream_info)); + break; + + default: + break; + } +} + +void +flac_vorbis_comments_to_tag(struct tag *tag, + const FLAC__StreamMetadata_VorbisComment *comment) +{ + flac_scan_comments(comment, &add_tag_handler, tag); +} + +void +FLACMetadataChain::Scan(const struct tag_handler *handler, void *handler_ctx) +{ + FLACMetadataIterator iterator(*this); + + do { + FLAC__StreamMetadata *block = iterator.GetBlock(); + if (block == nullptr) + break; + + flac_scan_metadata(block, handler, handler_ctx); + } while (iterator.Next()); +} diff --git a/src/decoder/FLACMetaData.hxx b/src/decoder/FLACMetaData.hxx new file mode 100644 index 000000000..0eceec23c --- /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.cxx b/src/decoder/FLAC_PCM.cxx new file mode 100644 index 000000000..303530aa7 --- /dev/null +++ b/src/decoder/FLAC_PCM.cxx @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FLAC_PCM.hxx" + +#include <assert.h> + +static void flac_convert_stereo16(int16_t *dest, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + for (; position < end; ++position) { + *dest++ = buf[0][position]; + *dest++ = buf[1][position]; + } +} + +static void +flac_convert_16(int16_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +/** + * Note: this function also handles 24 bit files! + */ +static void +flac_convert_32(int32_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +static void +flac_convert_8(int8_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +void +flac_convert(void *dest, + unsigned int num_channels, enum sample_format sample_format, + const FLAC__int32 *const buf[], + unsigned int position, unsigned int end) +{ + switch (sample_format) { + case SAMPLE_FORMAT_S16: + if (num_channels == 2) + flac_convert_stereo16((int16_t*)dest, buf, + position, end); + else + flac_convert_16((int16_t*)dest, num_channels, buf, + position, end); + break; + + case SAMPLE_FORMAT_S24_P32: + case SAMPLE_FORMAT_S32: + flac_convert_32((int32_t*)dest, num_channels, buf, + position, end); + break; + + case SAMPLE_FORMAT_S8: + flac_convert_8((int8_t*)dest, num_channels, buf, + position, end); + break; + + case SAMPLE_FORMAT_FLOAT: + case SAMPLE_FORMAT_DSD: + case SAMPLE_FORMAT_UNDEFINED: + /* unreachable */ + assert(false); + } +} diff --git a/src/decoder/FLAC_PCM.hxx b/src/decoder/FLAC_PCM.hxx new file mode 100644 index 000000000..97d214c17 --- /dev/null +++ b/src/decoder/FLAC_PCM.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_PCM_HXX +#define MPD_FLAC_PCM_HXX + +#include "audio_format.h" + +#include <FLAC/ordinals.h> + +void +flac_convert(void *dest, + unsigned int num_channels, enum sample_format sample_format, + const FLAC__int32 *const buf[], + unsigned int position, unsigned int end); + +#endif diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/FfmpegDecoderPlugin.cxx new file mode 100644 index 000000000..dd98b9686 --- /dev/null +++ b/src/decoder/FfmpegDecoderPlugin.cxx @@ -0,0 +1,752 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "FfmpegDecoderPlugin.hxx" +#include "decoder_api.h" +#include "FfmpegMetaData.hxx" +#include "tag_handler.h" +#include "InputStream.hxx" + +extern "C" { +#include "audio_check.h" +} + +#include <glib.h> + +#include <assert.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +extern "C" { +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libavformat/avio.h> +#include <libavutil/avutil.h> +#include <libavutil/log.h> +#include <libavutil/mathematics.h> +#include <libavutil/dict.h> +} + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "ffmpeg" + +static GLogLevelFlags +level_ffmpeg_to_glib(int level) +{ + if (level <= AV_LOG_FATAL) + return G_LOG_LEVEL_CRITICAL; + + if (level <= AV_LOG_ERROR) + return G_LOG_LEVEL_WARNING; + + if (level <= AV_LOG_INFO) + return G_LOG_LEVEL_MESSAGE; + + return G_LOG_LEVEL_DEBUG; +} + +static void +mpd_ffmpeg_log_callback(G_GNUC_UNUSED void *ptr, int level, + const char *fmt, va_list vl) +{ + const AVClass * cls = NULL; + + if (ptr != NULL) + cls = *(const AVClass *const*)ptr; + + if (cls != NULL) { + char *domain = g_strconcat(G_LOG_DOMAIN, "/", cls->item_name(ptr), NULL); + g_logv(domain, level_ffmpeg_to_glib(level), fmt, vl); + g_free(domain); + } +} + +struct mpd_ffmpeg_stream { + struct decoder *decoder; + struct input_stream *input; + + AVIOContext *io; + + unsigned char buffer[8192]; +}; + +static int +mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size) +{ + struct mpd_ffmpeg_stream *stream = (struct mpd_ffmpeg_stream *)opaque; + + return decoder_read(stream->decoder, stream->input, + (void *)buf, size); +} + +static int64_t +mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) +{ + struct mpd_ffmpeg_stream *stream = (struct mpd_ffmpeg_stream *)opaque; + + if (whence == AVSEEK_SIZE) + return stream->input->size; + + if (!input_stream_lock_seek(stream->input, pos, whence, NULL)) + return -1; + + return stream->input->offset; +} + +static struct mpd_ffmpeg_stream * +mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input) +{ + struct mpd_ffmpeg_stream *stream = g_new(struct mpd_ffmpeg_stream, 1); + stream->decoder = decoder; + stream->input = input; + stream->io = avio_alloc_context(stream->buffer, sizeof(stream->buffer), + false, stream, + mpd_ffmpeg_stream_read, NULL, + input->seekable + ? mpd_ffmpeg_stream_seek : NULL); + if (stream->io == NULL) { + g_free(stream); + return NULL; + } + + return stream; +} + +/** + * API compatibility wrapper for av_open_input_stream() and + * avformat_open_input(). + */ +static int +mpd_ffmpeg_open_input(AVFormatContext **ic_ptr, + AVIOContext *pb, + const char *filename, + AVInputFormat *fmt) +{ + AVFormatContext *context = avformat_alloc_context(); + if (context == NULL) + return AVERROR(ENOMEM); + + context->pb = pb; + *ic_ptr = context; + return avformat_open_input(ic_ptr, filename, fmt, NULL); +} + +static void +mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream) +{ + av_free(stream->io); + g_free(stream); +} + +static bool +ffmpeg_init(G_GNUC_UNUSED const struct config_param *param) +{ + av_log_set_callback(mpd_ffmpeg_log_callback); + + av_register_all(); + return true; +} + +static int +ffmpeg_find_audio_stream(const AVFormatContext *format_context) +{ + for (unsigned i = 0; i < format_context->nb_streams; ++i) + if (format_context->streams[i]->codec->codec_type == + AVMEDIA_TYPE_AUDIO) + return i; + + return -1; +} + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53,25,0) +/** + * On some platforms, libavcodec wants the output buffer aligned to 16 + * bytes (because it uses SSE/Altivec internally). This function + * returns the aligned version of the specified buffer, and corrects + * the buffer size. + */ +static void * +align16(void *p, size_t *length_p) +{ + unsigned add = 16 - (size_t)p % 16; + + *length_p -= add; + return (char *)p + add; +} +#endif + +G_GNUC_CONST +static double +time_from_ffmpeg(int64_t t, const AVRational time_base) +{ + assert(t != (int64_t)AV_NOPTS_VALUE); + + return (double)av_rescale_q(t, time_base, (AVRational){1, 1024}) + / (double)1024; +} + +G_GNUC_CONST +static int64_t +time_to_ffmpeg(double t, const AVRational time_base) +{ + return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024}, + time_base); +} + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0) + +static void +copy_interleave_frame2(uint8_t *dest, uint8_t **src, + unsigned nframes, unsigned nchannels, + unsigned sample_size) +{ + for (unsigned frame = 0; frame < nframes; ++frame) { + for (unsigned channel = 0; channel < nchannels; ++channel) { + memcpy(dest, src[channel] + frame * sample_size, + sample_size); + dest += sample_size; + } + } +} + +/** + * Copy PCM data from a AVFrame to an interleaved buffer. + */ +static int +copy_interleave_frame(const AVCodecContext *codec_context, + const AVFrame *frame, + uint8_t *buffer, size_t buffer_size) +{ + int plane_size; + const int data_size = + av_samples_get_buffer_size(&plane_size, + codec_context->channels, + frame->nb_samples, + codec_context->sample_fmt, 1); + if (buffer_size < (size_t)data_size) + /* buffer is too small - shouldn't happen */ + return AVERROR(EINVAL); + + if (av_sample_fmt_is_planar(codec_context->sample_fmt) && + codec_context->channels > 1) { + copy_interleave_frame2(buffer, frame->extended_data, + frame->nb_samples, + codec_context->channels, + av_get_bytes_per_sample(codec_context->sample_fmt)); + } else { + memcpy(buffer, frame->extended_data[0], data_size); + } + + return data_size; +} +#endif + +static enum decoder_command +ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, + const AVPacket *packet, + AVCodecContext *codec_context, + const AVRational *time_base) +{ + if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) + decoder_timestamp(decoder, + time_from_ffmpeg(packet->pts, *time_base)); + + AVPacket packet2 = *packet; + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0) + uint8_t aligned_buffer[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16]; + const size_t buffer_size = sizeof(aligned_buffer); +#else + /* libavcodec < 0.8 needs an aligned buffer */ + uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16]; + size_t buffer_size = sizeof(audio_buf); + int16_t *aligned_buffer = (int16_t *)align16(audio_buf, &buffer_size); +#endif + + enum decoder_command cmd = DECODE_COMMAND_NONE; + while (packet2.size > 0 && + cmd == DECODE_COMMAND_NONE) { + int audio_size = buffer_size; +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0) + AVFrame frame; + int got_frame = 0; + int len = avcodec_decode_audio4(codec_context, + &frame, &got_frame, + &packet2); + if (len >= 0 && got_frame) { + audio_size = copy_interleave_frame(codec_context, + &frame, + aligned_buffer, + buffer_size); + if (audio_size < 0) + len = audio_size; + } else if (len >= 0) + len = -1; +#else + int len = avcodec_decode_audio3(codec_context, + aligned_buffer, &audio_size, + &packet2); +#endif + + if (len < 0) { + /* if error, we skip the frame */ + g_message("decoding failed, frame skipped\n"); + break; + } + + packet2.data += len; + packet2.size -= len; + + if (audio_size <= 0) + continue; + + cmd = decoder_data(decoder, is, + aligned_buffer, audio_size, + codec_context->bit_rate / 1000); + } + return cmd; +} + +G_GNUC_CONST +static enum sample_format +ffmpeg_sample_format(enum AVSampleFormat sample_fmt) +{ + switch (sample_fmt) { + case AV_SAMPLE_FMT_S16: +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0) + case AV_SAMPLE_FMT_S16P: +#endif + return SAMPLE_FORMAT_S16; + + case AV_SAMPLE_FMT_S32: +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0) + case AV_SAMPLE_FMT_S32P: +#endif + return SAMPLE_FORMAT_S32; + +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0) + case AV_SAMPLE_FMT_FLTP: + return SAMPLE_FORMAT_FLOAT; +#endif + + default: + break; + } + + char buffer[64]; + const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer), + sample_fmt); + if (name != NULL) + g_warning("Unsupported libavcodec SampleFormat value: %s (%d)", + name, sample_fmt); + else + g_warning("Unsupported libavcodec SampleFormat value: %d", + sample_fmt); + return SAMPLE_FORMAT_UNDEFINED; +} + +static AVInputFormat * +ffmpeg_probe(struct decoder *decoder, struct input_stream *is) +{ + enum { + BUFFER_SIZE = 16384, + PADDING = 16, + }; + + unsigned char *buffer = (unsigned char *)g_malloc(BUFFER_SIZE); + size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); + if (nbytes <= PADDING || + !input_stream_lock_seek(is, 0, SEEK_SET, NULL)) { + g_free(buffer); + return NULL; + } + + /* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes + beyond the declared buffer limit, which makes valgrind + angry; this workaround removes some padding from the buffer + size */ + nbytes -= PADDING; + + AVProbeData avpd; + avpd.buf = buffer; + avpd.buf_size = nbytes; + avpd.filename = is->uri.c_str(); + + AVInputFormat *format = av_probe_input_format(&avpd, true); + g_free(buffer); + + return format; +} + +static void +ffmpeg_decode(struct decoder *decoder, struct input_stream *input) +{ + AVInputFormat *input_format = ffmpeg_probe(decoder, input); + if (input_format == NULL) + return; + + g_debug("detected input format '%s' (%s)", + input_format->name, input_format->long_name); + + struct mpd_ffmpeg_stream *stream = + mpd_ffmpeg_stream_open(decoder, input); + if (stream == NULL) { + g_warning("Failed to open stream"); + return; + } + + //ffmpeg works with ours "fileops" helper + AVFormatContext *format_context = NULL; + if (mpd_ffmpeg_open_input(&format_context, stream->io, + input->uri.c_str(), + input_format) != 0) { + g_warning("Open failed\n"); + mpd_ffmpeg_stream_close(stream); + return; + } + +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,6,0) + const int find_result = + avformat_find_stream_info(format_context, NULL); +#else + const int find_result = av_find_stream_info(format_context); +#endif + if (find_result < 0) { + g_warning("Couldn't find stream info\n"); +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) + avformat_close_input(&format_context); +#else + av_close_input_stream(format_context); +#endif + mpd_ffmpeg_stream_close(stream); + return; + } + + int audio_stream = ffmpeg_find_audio_stream(format_context); + if (audio_stream == -1) { + g_warning("No audio stream inside\n"); +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) + avformat_close_input(&format_context); +#else + av_close_input_stream(format_context); +#endif + mpd_ffmpeg_stream_close(stream); + return; + } + + AVStream *av_stream = format_context->streams[audio_stream]; + + AVCodecContext *codec_context = av_stream->codec; + if (codec_context->codec_name[0] != 0) + g_debug("codec '%s'", codec_context->codec_name); + + AVCodec *codec = avcodec_find_decoder(codec_context->codec_id); + + if (!codec) { + g_warning("Unsupported audio codec\n"); +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) + avformat_close_input(&format_context); +#else + av_close_input_stream(format_context); +#endif + mpd_ffmpeg_stream_close(stream); + return; + } + + const enum sample_format sample_format = + ffmpeg_sample_format(codec_context->sample_fmt); + if (sample_format == SAMPLE_FORMAT_UNDEFINED) + return; + + GError *error = NULL; + struct audio_format audio_format; + if (!audio_format_init_checked(&audio_format, + codec_context->sample_rate, + sample_format, + codec_context->channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) + avformat_close_input(&format_context); +#else + av_close_input_stream(format_context); +#endif + mpd_ffmpeg_stream_close(stream); + return; + } + + /* the audio format must be read from AVCodecContext by now, + because avcodec_open() has been demonstrated to fill bogus + values into AVCodecContext.channels - a change that will be + reverted later by avcodec_decode_audio3() */ + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,6,0) + const int open_result = avcodec_open2(codec_context, codec, NULL); +#else + const int open_result = avcodec_open(codec_context, codec); +#endif + if (open_result < 0) { + g_warning("Could not open codec\n"); +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) + avformat_close_input(&format_context); +#else + av_close_input_stream(format_context); +#endif + mpd_ffmpeg_stream_close(stream); + return; + } + + int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE + ? format_context->duration / AV_TIME_BASE + : 0; + + decoder_initialized(decoder, &audio_format, + input->seekable, total_time); + + enum decoder_command cmd; + do { + AVPacket packet; + if (av_read_frame(format_context, &packet) < 0) + /* end of file */ + break; + + if (packet.stream_index == audio_stream) + cmd = ffmpeg_send_packet(decoder, input, + &packet, codec_context, + &av_stream->time_base); + else + cmd = decoder_get_command(decoder); + + av_free_packet(&packet); + + if (cmd == DECODE_COMMAND_SEEK) { + int64_t where = + time_to_ffmpeg(decoder_seek_where(decoder), + av_stream->time_base); + + if (av_seek_frame(format_context, audio_stream, where, + AV_TIME_BASE) < 0) + decoder_seek_error(decoder); + else { + avcodec_flush_buffers(codec_context); + decoder_command_finished(decoder); + } + } + } while (cmd != DECODE_COMMAND_STOP); + + avcodec_close(codec_context); +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) + avformat_close_input(&format_context); +#else + av_close_input_stream(format_context); +#endif + mpd_ffmpeg_stream_close(stream); +} + +//no tag reading in ffmpeg, check if playable +static bool +ffmpeg_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + AVInputFormat *input_format = ffmpeg_probe(NULL, is); + if (input_format == NULL) + return false; + + struct mpd_ffmpeg_stream *stream = mpd_ffmpeg_stream_open(NULL, is); + if (stream == NULL) + return false; + + AVFormatContext *f = NULL; + if (mpd_ffmpeg_open_input(&f, stream->io, is->uri.c_str(), + input_format) != 0) { + mpd_ffmpeg_stream_close(stream); + return false; + } + +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,6,0) + const int find_result = + avformat_find_stream_info(f, NULL); +#else + const int find_result = av_find_stream_info(f); +#endif + if (find_result < 0) { +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) + avformat_close_input(&f); +#else + av_close_input_stream(f); +#endif + mpd_ffmpeg_stream_close(stream); + return false; + } + + if (f->duration != (int64_t)AV_NOPTS_VALUE) + tag_handler_invoke_duration(handler, handler_ctx, + f->duration / AV_TIME_BASE); + + ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx); + int idx = ffmpeg_find_audio_stream(f); + if (idx >= 0) + ffmpeg_scan_dictionary(f->streams[idx]->metadata, + handler, handler_ctx); + +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) + avformat_close_input(&f); +#else + av_close_input_stream(f); +#endif + mpd_ffmpeg_stream_close(stream); + + return true; +} + +/** + * A list of extensions found for the formats supported by ffmpeg. + * This list is current as of 02-23-09; To find out if there are more + * supported formats, check the ffmpeg changelog since this date for + * more formats. + */ +static const char *const ffmpeg_suffixes[] = { + "16sv", "3g2", "3gp", "4xm", "8svx", "aa3", "aac", "ac3", "afc", "aif", + "aifc", "aiff", "al", "alaw", "amr", "anim", "apc", "ape", "asf", + "atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak", + "cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa", + "eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726", + "gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts", + "m4a", "m4b", "m4v", + "mad", + "mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+", + "mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu", + "mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv", + "ogx", "oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra", + "ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd", + "sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts", + "tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc", + "vp6", "vmd", "wav", "webm", "wma", "wmv", "wsaud", "wsvga", "wv", + "wve", + NULL +}; + +static const char *const ffmpeg_mime_types[] = { + "application/m4a", + "application/mp4", + "application/octet-stream", + "application/ogg", + "application/x-ms-wmz", + "application/x-ms-wmd", + "application/x-ogg", + "application/x-shockwave-flash", + "application/x-shorten", + "audio/8svx", + "audio/16sv", + "audio/aac", + "audio/ac3", + "audio/aiff" + "audio/amr", + "audio/basic", + "audio/flac", + "audio/m4a", + "audio/mp4", + "audio/mpeg", + "audio/musepack", + "audio/ogg", + "audio/qcelp", + "audio/vorbis", + "audio/vorbis+ogg", + "audio/x-8svx", + "audio/x-16sv", + "audio/x-aac", + "audio/x-ac3", + "audio/x-aiff" + "audio/x-alaw", + "audio/x-au", + "audio/x-dca", + "audio/x-eac3", + "audio/x-flac", + "audio/x-gsm", + "audio/x-mace", + "audio/x-matroska", + "audio/x-monkeys-audio", + "audio/x-mpeg", + "audio/x-ms-wma", + "audio/x-ms-wax", + "audio/x-musepack", + "audio/x-ogg", + "audio/x-vorbis", + "audio/x-vorbis+ogg", + "audio/x-pn-realaudio", + "audio/x-pn-multirate-realaudio", + "audio/x-speex", + "audio/x-tta" + "audio/x-voc", + "audio/x-wav", + "audio/x-wma", + "audio/x-wv", + "video/anim", + "video/quicktime", + "video/msvideo", + "video/ogg", + "video/theora", + "video/webm", + "video/x-dv", + "video/x-flv", + "video/x-matroska", + "video/x-mjpeg", + "video/x-mpeg", + "video/x-ms-asf", + "video/x-msvideo", + "video/x-ms-wmv", + "video/x-ms-wvx", + "video/x-ms-wm", + "video/x-ms-wmx", + "video/x-nut", + "video/x-pva", + "video/x-theora", + "video/x-vid", + "video/x-wmv", + "video/x-xvid", + + /* special value for the "ffmpeg" input plugin: all streams by + the "ffmpeg" input plugin shall be decoded by this + plugin */ + "audio/x-mpd-ffmpeg", + + NULL +}; + +const struct decoder_plugin ffmpeg_decoder_plugin = { + "ffmpeg", + ffmpeg_init, + nullptr, + ffmpeg_decode, + nullptr, + nullptr, + ffmpeg_scan_stream, + nullptr, + ffmpeg_suffixes, + ffmpeg_mime_types +}; diff --git a/src/decoder/FfmpegDecoderPlugin.hxx b/src/decoder/FfmpegDecoderPlugin.hxx new file mode 100644 index 000000000..9a637fff0 --- /dev/null +++ b/src/decoder/FfmpegDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_FFMPEG_HXX +#define MPD_DECODER_FFMPEG_HXX + +extern const struct decoder_plugin ffmpeg_decoder_plugin; + +#endif diff --git a/src/decoder/FfmpegMetaData.cxx b/src/decoder/FfmpegMetaData.cxx new file mode 100644 index 000000000..2d7ebbca3 --- /dev/null +++ b/src/decoder/FfmpegMetaData.cxx @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "FfmpegMetaData.hxx" +#include "tag_table.h" +#include "tag_handler.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "ffmpeg" + +static const struct tag_table ffmpeg_tags[] = { + { "year", TAG_DATE }, + { "author-sort", TAG_ARTIST_SORT }, + { "album_artist", TAG_ALBUM_ARTIST }, + { "album_artist-sort", TAG_ALBUM_ARTIST_SORT }, + + /* sentinel */ + { NULL, TAG_NUM_OF_ITEM_TYPES } +}; + +static void +ffmpeg_copy_metadata(enum tag_type type, + AVDictionary *m, const char *name, + const struct tag_handler *handler, void *handler_ctx) +{ + AVDictionaryEntry *mt = NULL; + + while ((mt = av_dict_get(m, name, mt, 0)) != NULL) + tag_handler_invoke_tag(handler, handler_ctx, + type, mt->value); +} + +static void +ffmpeg_scan_pairs(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx) +{ + AVDictionaryEntry *i = NULL; + + while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != NULL) + tag_handler_invoke_pair(handler, handler_ctx, + i->key, i->value); +} + +void +ffmpeg_scan_dictionary(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx) +{ + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + ffmpeg_copy_metadata(tag_type(i), dict, tag_item_names[i], + handler, handler_ctx); + + for (const struct tag_table *i = ffmpeg_tags; + i->name != NULL; ++i) + ffmpeg_copy_metadata(i->type, dict, i->name, + handler, handler_ctx); + + if (handler->pair != NULL) + ffmpeg_scan_pairs(dict, handler, handler_ctx); +} diff --git a/src/decoder/FfmpegMetaData.hxx b/src/decoder/FfmpegMetaData.hxx new file mode 100644 index 000000000..466d2cb1d --- /dev/null +++ b/src/decoder/FfmpegMetaData.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_FFMPEG_METADATA_HXX +#define MPD_FFMPEG_METADATA_HXX + +extern "C" { +#include <libavformat/avformat.h> +#include <libavutil/avutil.h> +#include <libavutil/dict.h> +} + +struct tag_handler; + +void +ffmpeg_scan_dictionary(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx); + +#endif diff --git a/src/decoder/OggCodec.cxx b/src/decoder/OggCodec.cxx new file mode 100644 index 000000000..5ad9c69d6 --- /dev/null +++ b/src/decoder/OggCodec.cxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) + */ + +#include "config.h" +#include "OggCodec.hxx" + +enum ogg_codec +ogg_codec_detect(struct decoder *decoder, struct input_stream *is) +{ + /* oggflac detection based on code in ogg123 and this post + * http://lists.xiph.org/pipermail/flac/2004-December/000393.html + * ogg123 trunk still doesn't have this patch as of June 2005 */ + unsigned char buf[41]; + size_t r = decoder_read(decoder, is, buf, sizeof(buf)); + if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0) + return OGG_CODEC_UNKNOWN; + + if ((memcmp(buf + 29, "FLAC", 4) == 0 && + memcmp(buf + 37, "fLaC", 4) == 0) || + memcmp(buf + 28, "FLAC", 4) == 0 || + memcmp(buf + 28, "fLaC", 4) == 0) + return OGG_CODEC_FLAC; + + if (memcmp(buf + 28, "Opus", 4) == 0) + return OGG_CODEC_OPUS; + + return OGG_CODEC_VORBIS; +} diff --git a/src/decoder/OggCodec.hxx b/src/decoder/OggCodec.hxx new file mode 100644 index 000000000..e241560fb --- /dev/null +++ b/src/decoder/OggCodec.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) + */ + +#ifndef MPD_OGG_CODEC_HXX +#define MPD_OGG_CODEC_HXX + +#include "decoder_api.h" + +enum ogg_codec { + OGG_CODEC_UNKNOWN, + OGG_CODEC_VORBIS, + OGG_CODEC_FLAC, + OGG_CODEC_OPUS, +}; + +enum ogg_codec +ogg_codec_detect(struct decoder *decoder, struct input_stream *is); + +#endif /* _OGG_COMMON_H */ diff --git a/src/decoder/OggFind.cxx b/src/decoder/OggFind.cxx new file mode 100644 index 000000000..9df4c6455 --- /dev/null +++ b/src/decoder/OggFind.cxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OggFind.hxx" +#include "OggSyncState.hxx" + +bool +OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet) +{ + while (true) { + int r = ogg_stream_packetout(&os, &packet); + if (r == 0) { + if (!oy.ExpectPageIn(os)) + return false; + + continue; + } else if (r > 0 && packet.e_o_s) + return true; + } +} diff --git a/src/decoder/OggFind.hxx b/src/decoder/OggFind.hxx new file mode 100644 index 000000000..7d18d2067 --- /dev/null +++ b/src/decoder/OggFind.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OGG_FIND_HXX +#define MPD_OGG_FIND_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +class OggSyncState; + +/** + * Skip all pages/packets until an end-of-stream (EOS) packet for the + * specified stream is found. + * + * @return true if the EOS packet was found + */ +bool +OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet); + +#endif diff --git a/src/decoder/OggSyncState.hxx b/src/decoder/OggSyncState.hxx new file mode 100644 index 000000000..eaeb9bd8c --- /dev/null +++ b/src/decoder/OggSyncState.hxx @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OGG_SYNC_STATE_HXX +#define MPD_OGG_SYNC_STATE_HXX + +#include "check.h" +#include "OggUtil.hxx" + +#include <ogg/ogg.h> + +#include <stddef.h> + +/** + * Wrapper for an ogg_sync_state. + */ +class OggSyncState { + ogg_sync_state oy; + + input_stream &is; + struct decoder *const decoder; + +public: + OggSyncState(input_stream &_is, struct decoder *const _decoder=nullptr) + :is(_is), decoder(_decoder) { + ogg_sync_init(&oy); + } + + ~OggSyncState() { + ogg_sync_clear(&oy); + } + + void Reset() { + ogg_sync_reset(&oy); + } + + bool Feed(size_t size) { + return OggFeed(oy, decoder, &is, size); + } + + bool ExpectPage(ogg_page &page) { + return OggExpectPage(oy, page, decoder, &is); + } + + bool ExpectFirstPage(ogg_stream_state &os) { + return OggExpectFirstPage(oy, os, decoder, &is); + } + + bool ExpectPageIn(ogg_stream_state &os) { + return OggExpectPageIn(oy, os, decoder, &is); + } + + bool ExpectPageSeek(ogg_page &page) { + return OggExpectPageSeek(oy, page, decoder, &is); + } + + bool ExpectPageSeekIn(ogg_stream_state &os) { + return OggExpectPageSeekIn(oy, os, decoder, &is); + } +}; + +#endif diff --git a/src/decoder/OggUtil.cxx b/src/decoder/OggUtil.cxx new file mode 100644 index 000000000..a1125a2c6 --- /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 000000000..324797815 --- /dev/null +++ b/src/decoder/OggUtil.hxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OGG_UTIL_HXX +#define MPD_OGG_UTIL_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +#include <stddef.h> + +struct input_stream; +struct decoder; + +/** + * Feed data from the #input_stream into the #ogg_sync_state. + * + * @return false on error or end-of-file + */ +bool +OggFeed(ogg_sync_state &oy, struct decoder *decoder, input_stream *is, + size_t size); + +/** + * Feed into the #ogg_sync_state until a page gets available. Garbage + * data at the beginning is considered a fatal error. + * + * @return true if a page is available + */ +bool +OggExpectPage(ogg_sync_state &oy, ogg_page &page, + decoder *decoder, input_stream *input_stream); + +/** + * Combines OggExpectPage(), ogg_stream_init() and + * ogg_stream_pagein(). + * + * @return true if the stream was initialized and the first page was + * delivered to it + */ +bool +OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is); + +/** + * Combines OggExpectPage() and ogg_stream_pagein(). + * + * @return true if a page was delivered to the stream + */ +bool +OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is); + +/** + * Like OggExpectPage(), but allow skipping garbage (after seeking). + */ +bool +OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, + decoder *decoder, input_stream *input_stream); + +/** + * Combines OggExpectPageSeek() and ogg_stream_pagein(). + * + * @return true if a page was delivered to the stream + */ +bool +OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is); + +#endif diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx new file mode 100644 index 000000000..3e3a1e4e7 --- /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 000000000..c95d6ded3 --- /dev/null +++ b/src/decoder/OpusDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_OPUS_H +#define MPD_DECODER_OPUS_H + +extern const struct decoder_plugin opus_decoder_plugin; + +#endif diff --git a/src/decoder/OpusHead.cxx b/src/decoder/OpusHead.cxx new file mode 100644 index 000000000..c57e08e10 --- /dev/null +++ b/src/decoder/OpusHead.cxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OpusHead.hxx" + +#include <stdint.h> +#include <string.h> + +struct OpusHead { + char signature[8]; + uint8_t version, channels; + uint16_t pre_skip; + uint32_t sample_rate; + uint16_t output_gain; + uint8_t channel_mapping; +}; + +bool +ScanOpusHeader(const void *data, size_t size, unsigned &channels_r) +{ + const OpusHead *h = (const OpusHead *)data; + if (size < 19 || (h->version & 0xf0) != 0) + return false; + + channels_r = h->channels; + return true; +} diff --git a/src/decoder/OpusHead.hxx b/src/decoder/OpusHead.hxx new file mode 100644 index 000000000..9f75c4f70 --- /dev/null +++ b/src/decoder/OpusHead.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_HEAD_HXX +#define MPD_OPUS_HEAD_HXX + +#include "check.h" + +#include <stddef.h> + +bool +ScanOpusHeader(const void *data, size_t size, unsigned &channels_r); + +#endif diff --git a/src/decoder/OpusReader.hxx b/src/decoder/OpusReader.hxx new file mode 100644 index 000000000..1fd07b55c --- /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 000000000..cb35a6247 --- /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 000000000..2f3eec844 --- /dev/null +++ b/src/decoder/OpusTags.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_TAGS_HXX +#define MPD_OPUS_TAGS_HXX + +#include "check.h" + +#include <stddef.h> + +bool +ScanOpusTags(const void *data, size_t size, + const struct tag_handler *handler, void *ctx); + +#endif diff --git a/src/decoder/VorbisComments.cxx b/src/decoder/VorbisComments.cxx new file mode 100644 index 000000000..10fe22369 --- /dev/null +++ b/src/decoder/VorbisComments.cxx @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "VorbisComments.hxx" +#include "XiphTags.h" +#include "tag.h" +#include "tag_table.h" +#include "tag_handler.h" +#include "replay_gain_info.h" + +#include <glib.h> +#include <assert.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> + +static const char * +vorbis_comment_value(const char *comment, const char *needle) +{ + size_t len = strlen(needle); + + if (g_ascii_strncasecmp(comment, needle, len) == 0 && + comment[len] == '=') + return comment + len + 1; + + return NULL; +} + +bool +vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments) +{ + const char *temp; + bool found = false; + + replay_gain_info_init(rgi); + + while (*comments) { + if ((temp = + vorbis_comment_value(*comments, "replaygain_track_gain"))) { + rgi->tuples[REPLAY_GAIN_TRACK].gain = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_album_gain"))) { + rgi->tuples[REPLAY_GAIN_ALBUM].gain = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_track_peak"))) { + rgi->tuples[REPLAY_GAIN_TRACK].peak = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_album_peak"))) { + rgi->tuples[REPLAY_GAIN_ALBUM].peak = atof(temp); + found = true; + } + + comments++; + } + + return found; +} + +/** + * Check if the comment's name equals the passed name, and if so, copy + * the comment value into the tag. + */ +static bool +vorbis_copy_comment(const char *comment, + const char *name, enum tag_type tag_type, + const struct tag_handler *handler, void *handler_ctx) +{ + const char *value; + + value = vorbis_comment_value(comment, name); + if (value != NULL) { + tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); + return true; + } + + return false; +} + +static void +vorbis_scan_comment(const char *comment, + const struct tag_handler *handler, void *handler_ctx) +{ + if (handler->pair != NULL) { + char *name = g_strdup((const char*)comment); + char *value = strchr(name, '='); + + if (value != NULL && value > name) { + *value++ = 0; + tag_handler_invoke_pair(handler, handler_ctx, + name, value); + } + + g_free(name); + } + + for (const struct tag_table *i = xiph_tags; i->name != NULL; ++i) + if (vorbis_copy_comment(comment, i->name, i->type, + handler, handler_ctx)) + return; + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (vorbis_copy_comment(comment, + tag_item_names[i], tag_type(i), + handler, handler_ctx)) + return; +} + +void +vorbis_comments_scan(char **comments, + const struct tag_handler *handler, void *handler_ctx) +{ + while (*comments) + vorbis_scan_comment(*comments++, + handler, handler_ctx); + +} + +struct tag * +vorbis_comments_to_tag(char **comments) +{ + struct tag *tag = tag_new(); + vorbis_comments_scan(comments, &add_tag_handler, tag); + + if (tag_is_empty(tag)) { + tag_free(tag); + tag = NULL; + } + + return tag; +} diff --git a/src/decoder/VorbisComments.hxx b/src/decoder/VorbisComments.hxx new file mode 100644 index 000000000..8212cac47 --- /dev/null +++ b/src/decoder/VorbisComments.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_VORBIS_COMMENTS_HXX +#define MPD_VORBIS_COMMENTS_HXX + +#include "check.h" + +#include <stdbool.h> + +struct replay_gain_info; +struct tag_handler; + +bool +vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments); + +void +vorbis_comments_scan(char **comments, + const struct tag_handler *handler, void *handler_ctx); + +struct tag * +vorbis_comments_to_tag(char **comments); + +#endif diff --git a/src/decoder/VorbisDecoderPlugin.cxx b/src/decoder/VorbisDecoderPlugin.cxx new file mode 100644 index 000000000..488786ed8 --- /dev/null +++ b/src/decoder/VorbisDecoderPlugin.cxx @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "VorbisDecoderPlugin.h" +#include "VorbisComments.hxx" +#include "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 +#define OV_EXCLUDE_STATIC_CALLBACKS +#include <vorbis/vorbisfile.h> +#else +#include <tremor/ivorbisfile.h> +/* Macros to make Tremor's API look like libogg. Tremor always + returns host-byte-order 16-bit signed data, and uses integer + milliseconds where libogg uses double seconds. +*/ +#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ + ov_read(VF, BUFFER, LENGTH, BITSTREAM) +#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000) +#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000) +#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000)) +#endif /* HAVE_TREMOR */ + +#include <glib.h> + +#include <assert.h> +#include <errno.h> +#include <unistd.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "vorbis" + +#if G_BYTE_ORDER == G_BIG_ENDIAN +#define VORBIS_BIG_ENDIAN true +#else +#define VORBIS_BIG_ENDIAN false +#endif + +struct vorbis_input_stream { + struct decoder *decoder; + + struct input_stream *input_stream; + bool seekable; +}; + +static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) +{ + struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; + size_t ret = decoder_read(vis->decoder, vis->input_stream, + ptr, size * nmemb); + + errno = 0; + + return ret / size; +} + +static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence) +{ + struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; + + return vis->seekable && + (!vis->decoder || decoder_get_command(vis->decoder) != DECODE_COMMAND_STOP) && + input_stream_lock_seek(vis->input_stream, offset, whence, NULL) + ? 0 : -1; +} + +/* TODO: check Ogg libraries API and see if we can just not have this func */ +static int ogg_close_cb(G_GNUC_UNUSED void *data) +{ + return 0; +} + +static long ogg_tell_cb(void *data) +{ + struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; + + return (long)vis->input_stream->offset; +} + +static const ov_callbacks vorbis_is_callbacks = { + ogg_read_cb, + ogg_seek_cb, + ogg_close_cb, + ogg_tell_cb, +}; + +static const char * +vorbis_strerror(int code) +{ + switch (code) { + case OV_EREAD: + return "read error"; + + case OV_ENOTVORBIS: + return "not vorbis stream"; + + case OV_EVERSION: + return "vorbis version mismatch"; + + case OV_EBADHEADER: + return "invalid vorbis header"; + + case OV_EFAULT: + return "internal logic error"; + + default: + return "unknown error"; + } +} + +static bool +vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf, + struct decoder *decoder, struct input_stream *input_stream) +{ + vis->decoder = decoder; + vis->input_stream = input_stream; + vis->seekable = input_stream_cheap_seeking(input_stream); + + int ret = ov_open_callbacks(vis, vf, NULL, 0, vorbis_is_callbacks); + if (ret < 0) { + if (decoder == NULL || + decoder_get_command(decoder) == DECODE_COMMAND_NONE) + g_warning("Failed to open Ogg Vorbis stream: %s", + vorbis_strerror(ret)); + return false; + } + + return true; +} + +static void +vorbis_send_comments(struct decoder *decoder, struct input_stream *is, + char **comments) +{ + struct tag *tag = vorbis_comments_to_tag(comments); + if (!tag) + return; + + decoder_tag(decoder, is, tag); + 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; + + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_VORBIS) + return; + + /* rewind the stream, because ogg_codec_detect() has + moved it */ + input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); + + struct vorbis_input_stream vis; + OggVorbis_File vf; + if (!vorbis_is_open(&vis, &vf, decoder, input_stream)) + return; + + const vorbis_info *vi = ov_info(&vf, -1); + if (vi == NULL) { + g_warning("ov_info() has failed"); + return; + } + + 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; + } + + float total_time = ov_time_total(&vf, -1); + if (total_time < 0) + total_time = 0; + + decoder_initialized(decoder, &audio_format, vis.seekable, total_time); + + enum decoder_command cmd = decoder_get_command(decoder); + +#ifdef HAVE_TREMOR + char buffer[4096]; +#else + float buffer[2048]; + const int frames_per_buffer = + G_N_ELEMENTS(buffer) / audio_format.channels; + const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels; +#endif + + int prev_section = -1; + unsigned kbit_rate = 0; + + do { + if (cmd == DECODE_COMMAND_SEEK) { + double seek_where = decoder_seek_where(decoder); + if (0 == ov_time_seek_page(&vf, seek_where)) { + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } + + int current_section; + +#ifdef HAVE_TREMOR + long nbytes = ov_read(&vf, buffer, sizeof(buffer), + VORBIS_BIG_ENDIAN, 2, 1, + ¤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) { + vi = ov_info(&vf, -1); + if (vi == NULL) { + g_warning("ov_info() has failed"); + break; + } + + if (vi->rate != (long)audio_format.sample_rate || + vi->channels != (int)audio_format.channels) { + /* we don't support audio format + change yet */ + g_warning("audio format change, stopping here"); + break; + } + + char **comments = ov_comment(&vf, -1)->user_comments; + vorbis_send_comments(decoder, input_stream, comments); + + struct replay_gain_info rgi; + if (vorbis_comments_to_replay_gain(&rgi, comments)) + decoder_replay_gain(decoder, &rgi); + + prev_section = current_section; + } + + long test = ov_bitrate_instant(&vf); + if (test > 0) + kbit_rate = test / 1000; + + cmd = decoder_data(decoder, input_stream, + buffer, nbytes, + kbit_rate); + } while (cmd != DECODE_COMMAND_STOP); + + ov_clear(&vf); +} + +static bool +vorbis_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + struct vorbis_input_stream vis; + OggVorbis_File vf; + + if (!vorbis_is_open(&vis, &vf, NULL, is)) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, + (int)(ov_time_total(&vf, -1) + 0.5)); + + vorbis_comments_scan(ov_comment(&vf, -1)->user_comments, + handler, handler_ctx); + + ov_clear(&vf); + return true; +} + +static const char *const vorbis_suffixes[] = { + "ogg", "oga", NULL +}; + +static const char *const vorbis_mime_types[] = { + "application/ogg", + "application/x-ogg", + "audio/ogg", + "audio/vorbis", + "audio/vorbis+ogg", + "audio/x-ogg", + "audio/x-vorbis", + "audio/x-vorbis+ogg", + NULL +}; + +const struct decoder_plugin vorbis_decoder_plugin = { + "vorbis", + nullptr, + nullptr, + vorbis_stream_decode, + nullptr, + nullptr, + vorbis_scan_stream, + nullptr, + vorbis_suffixes, + vorbis_mime_types +}; diff --git a/src/decoder/VorbisDecoderPlugin.h b/src/decoder/VorbisDecoderPlugin.h new file mode 100644 index 000000000..618c9ffde --- /dev/null +++ b/src/decoder/VorbisDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_VORBIS_H +#define MPD_DECODER_VORBIS_H + +extern const struct decoder_plugin vorbis_decoder_plugin; + +#endif diff --git a/src/decoder/WavpackDecoderPlugin.cxx b/src/decoder/WavpackDecoderPlugin.cxx new file mode 100644 index 000000000..bac62d429 --- /dev/null +++ b/src/decoder/WavpackDecoderPlugin.cxx @@ -0,0 +1,603 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "WavpackDecoderPlugin.hxx" +#include "decoder_api.h" +#include "InputStream.hxx" + +extern "C" { +#include "audio_check.h" +} + +#include "tag_handler.h" +#include "tag_ape.h" + +#include <wavpack/wavpack.h> +#include <glib.h> + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "wavpack" + +#define ERRORLEN 80 + +/** A pointer type for format converter function. */ +typedef void (*format_samples_t)( + int bytes_per_sample, + void *buffer, uint32_t count +); + +/* + * This function has been borrowed from the tiny player found on + * wavpack.com. Modifications were required because mpd only handles + * max 24-bit samples. + */ +static void +format_samples_int(int bytes_per_sample, void *buffer, uint32_t count) +{ + int32_t *src = (int32_t *)buffer; + + switch (bytes_per_sample) { + case 1: { + int8_t *dst = (int8_t *)buffer; + /* + * The asserts like the following one are because we do the + * formatting of samples within a single buffer. The size + * of the output samples never can be greater than the size + * of the input ones. Otherwise we would have an overflow. + */ + static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); + + /* pass through and align 8-bit samples */ + while (count--) { + *dst++ = *src++; + } + break; + } + case 2: { + uint16_t *dst = (uint16_t *)buffer; + static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); + + /* pass through and align 16-bit samples */ + while (count--) { + *dst++ = *src++; + } + break; + } + + case 3: + case 4: + /* do nothing */ + break; + } +} + +/* + * This function converts floating point sample data to 24-bit integer. + */ +static void +format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer, + uint32_t count) +{ + float *p = (float *)buffer; + + while (count--) { + *p /= (1 << 23); + ++p; + } +} + +/** + * Choose a MPD sample format from libwavpacks' number of bits. + */ +static enum sample_format +wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample) +{ + if (is_float) + return SAMPLE_FORMAT_FLOAT; + + switch (bytes_per_sample) { + case 1: + return SAMPLE_FORMAT_S8; + + case 2: + return SAMPLE_FORMAT_S16; + + case 3: + return SAMPLE_FORMAT_S24_P32; + + case 4: + return SAMPLE_FORMAT_S32; + + default: + return SAMPLE_FORMAT_UNDEFINED; + } +} + +/* + * This does the main decoding thing. + * Requires an already opened WavpackContext. + */ +static void +wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek) +{ + GError *error = NULL; + bool is_float; + enum sample_format sample_format; + struct audio_format audio_format; + format_samples_t format_samples; + float total_time; + int bytes_per_sample, output_sample_size; + + is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0; + sample_format = + wavpack_bits_to_sample_format(is_float, + WavpackGetBytesPerSample(wpc)); + + if (!audio_format_init_checked(&audio_format, + WavpackGetSampleRate(wpc), + sample_format, + WavpackGetNumChannels(wpc), &error)) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + + if (is_float) { + format_samples = format_samples_float; + } else { + format_samples = format_samples_int; + } + + total_time = WavpackGetNumSamples(wpc); + total_time /= audio_format.sample_rate; + bytes_per_sample = WavpackGetBytesPerSample(wpc); + output_sample_size = audio_format_frame_size(&audio_format); + + /* wavpack gives us all kind of samples in a 32-bit space */ + int32_t chunk[1024]; + const uint32_t samples_requested = G_N_ELEMENTS(chunk) / + audio_format.channels; + + decoder_initialized(decoder, &audio_format, can_seek, total_time); + + enum decoder_command cmd = decoder_get_command(decoder); + while (cmd != DECODE_COMMAND_STOP) { + if (cmd == DECODE_COMMAND_SEEK) { + if (can_seek) { + unsigned where = decoder_seek_where(decoder) * + audio_format.sample_rate; + + if (WavpackSeekSample(wpc, where)) { + decoder_command_finished(decoder); + } else { + decoder_seek_error(decoder); + } + } else { + decoder_seek_error(decoder); + } + } + + uint32_t samples_got = WavpackUnpackSamples(wpc, chunk, + samples_requested); + if (samples_got == 0) + break; + + int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 + + 0.5); + format_samples(bytes_per_sample, chunk, + samples_got * audio_format.channels); + + cmd = decoder_data(decoder, NULL, chunk, + samples_got * output_sample_size, + bitrate); + } +} + +/** + * Locate and parse a floating point tag. Returns true if it was + * found. + */ +static bool +wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r) +{ + char buffer[64]; + int ret; + + ret = WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)); + if (ret <= 0) + return false; + + *value_r = atof(buffer); + return true; +} + +static bool +wavpack_replaygain(struct replay_gain_info *replay_gain_info, + WavpackContext *wpc) +{ + bool found = false; + + replay_gain_info_init(replay_gain_info); + + found |= wavpack_tag_float( + wpc, "replaygain_track_gain", + &replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain + ); + found |= wavpack_tag_float( + wpc, "replaygain_track_peak", + &replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak + ); + found |= wavpack_tag_float( + wpc, "replaygain_album_gain", + &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain + ); + found |= wavpack_tag_float( + wpc, "replaygain_album_peak", + &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak + ); + + return found; +} + +static void +wavpack_scan_tag_item(WavpackContext *wpc, const char *name, + enum tag_type type, + const struct tag_handler *handler, void *handler_ctx) +{ + char buffer[1024]; + int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); + if (len <= 0 || (unsigned)len >= sizeof(buffer)) + return; + + tag_handler_invoke_tag(handler, handler_ctx, type, buffer); + +} + +static void +wavpack_scan_pair(WavpackContext *wpc, const char *name, + const struct tag_handler *handler, void *handler_ctx) +{ + char buffer[8192]; + int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); + if (len <= 0 || (unsigned)len >= sizeof(buffer)) + return; + + tag_handler_invoke_pair(handler, handler_ctx, name, buffer); +} + +/* + * Reads metainfo from the specified file. + */ +static bool +wavpack_scan_file(const char *fname, + const struct tag_handler *handler, void *handler_ctx) +{ + WavpackContext *wpc; + char error[ERRORLEN]; + + wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0); + if (wpc == NULL) { + g_warning( + "failed to open WavPack file \"%s\": %s\n", + fname, error + ); + return false; + } + + tag_handler_invoke_duration(handler, handler_ctx, + WavpackGetNumSamples(wpc) / + WavpackGetSampleRate(wpc)); + + /* the WavPack format implies APEv2 tags, which means we can + reuse the mapping from tag_ape.c */ + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + const char *name = tag_item_names[i]; + if (name != NULL) + wavpack_scan_tag_item(wpc, name, (enum tag_type)i, + handler, handler_ctx); + } + + for (const struct tag_table *i = ape_tags; i->name != NULL; ++i) + wavpack_scan_tag_item(wpc, i->name, i->type, + handler, handler_ctx); + + if (handler->pair != NULL) { + char name[64]; + + for (int i = 0, n = WavpackGetNumTagItems(wpc); + i < n; ++i) { + int len = WavpackGetTagItemIndexed(wpc, i, name, + sizeof(name)); + if (len <= 0 || (unsigned)len >= sizeof(name)) + continue; + + wavpack_scan_pair(wpc, name, handler, handler_ctx); + } + } + + WavpackCloseFile(wpc); + + return true; +} + +/* + * mpd input_stream <=> WavpackStreamReader wrapper callbacks + */ + +/* This struct is needed for per-stream last_byte storage. */ +struct wavpack_input { + struct decoder *decoder; + struct input_stream *is; + /* Needed for push_back_byte() */ + int last_byte; +}; + +/** + * Little wrapper for struct wavpack_input to cast from void *. + */ +static struct wavpack_input * +wpin(void *id) +{ + assert(id); + return (struct wavpack_input *)id; +} + +static int32_t +wavpack_input_read_bytes(void *id, void *data, int32_t bcount) +{ + uint8_t *buf = (uint8_t *)data; + int32_t i = 0; + + if (wpin(id)->last_byte != EOF) { + *buf++ = wpin(id)->last_byte; + wpin(id)->last_byte = EOF; + --bcount; + ++i; + } + + /* wavpack fails if we return a partial read, so we just wait + until the buffer is full */ + while (bcount > 0) { + size_t nbytes = decoder_read( + wpin(id)->decoder, wpin(id)->is, buf, bcount + ); + if (nbytes == 0) { + /* EOF, error or a decoder command */ + break; + } + + i += nbytes; + bcount -= nbytes; + buf += nbytes; + } + + return i; +} + +static uint32_t +wavpack_input_get_pos(void *id) +{ + return wpin(id)->is->offset; +} + +static int +wavpack_input_set_pos_abs(void *id, uint32_t pos) +{ + return input_stream_lock_seek(wpin(id)->is, pos, SEEK_SET, NULL) + ? 0 : -1; +} + +static int +wavpack_input_set_pos_rel(void *id, int32_t delta, int mode) +{ + return input_stream_lock_seek(wpin(id)->is, delta, mode, NULL) + ? 0 : -1; +} + +static int +wavpack_input_push_back_byte(void *id, int c) +{ + if (wpin(id)->last_byte == EOF) { + wpin(id)->last_byte = c; + return c; + } else { + return EOF; + } +} + +static uint32_t +wavpack_input_get_length(void *id) +{ + if (wpin(id)->is->size < 0) + return 0; + + return wpin(id)->is->size; +} + +static int +wavpack_input_can_seek(void *id) +{ + return wpin(id)->is->seekable; +} + +static WavpackStreamReader mpd_is_reader = { + wavpack_input_read_bytes, + wavpack_input_get_pos, + wavpack_input_set_pos_abs, + wavpack_input_set_pos_rel, + wavpack_input_push_back_byte, + wavpack_input_get_length, + wavpack_input_can_seek, + nullptr /* no need to write edited tags */ +}; + +static void +wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder, + struct input_stream *is) +{ + isp->decoder = decoder; + isp->is = is; + isp->last_byte = EOF; +} + +static struct input_stream * +wavpack_open_wvc(struct decoder *decoder, const char *uri, + Mutex &mutex, Cond &cond, + struct wavpack_input *wpi) +{ + struct input_stream *is_wvc; + char *wvc_url = NULL; + char first_byte; + size_t nbytes; + + /* + * As we use dc->utf8url, this function will be bad for + * single files. utf8url is not absolute file path :/ + */ + if (uri == NULL) + return nullptr; + + wvc_url = g_strconcat(uri, "c", NULL); + is_wvc = input_stream_open(wvc_url, mutex, cond, NULL); + g_free(wvc_url); + + if (is_wvc == NULL) + return NULL; + + /* + * And we try to buffer in order to get know + * about a possible 404 error. + */ + nbytes = decoder_read( + decoder, is_wvc, &first_byte, sizeof(first_byte) + ); + if (nbytes == 0) { + input_stream_close(is_wvc); + return NULL; + } + + /* push it back */ + wavpack_input_init(wpi, decoder, is_wvc); + wpi->last_byte = first_byte; + return is_wvc; +} + +/* + * Decodes a stream. + */ +static void +wavpack_streamdecode(struct decoder * decoder, struct input_stream *is) +{ + char error[ERRORLEN]; + WavpackContext *wpc; + struct input_stream *is_wvc; + int open_flags = OPEN_NORMALIZE; + struct wavpack_input isp, isp_wvc; + bool can_seek = is->seekable; + + is_wvc = wavpack_open_wvc(decoder, is->uri.c_str(), + is->mutex, is->cond, + &isp_wvc); + if (is_wvc != NULL) { + open_flags |= OPEN_WVC; + can_seek &= is_wvc->seekable; + } + + if (!can_seek) { + open_flags |= OPEN_STREAMING; + } + + wavpack_input_init(&isp, decoder, is); + wpc = WavpackOpenFileInputEx( + &mpd_is_reader, &isp, + open_flags & OPEN_WVC ? &isp_wvc : NULL, + error, open_flags, 23 + ); + + if (wpc == NULL) { + g_warning("failed to open WavPack stream: %s\n", error); + return; + } + + wavpack_decode(decoder, wpc, can_seek); + + WavpackCloseFile(wpc); + if (open_flags & OPEN_WVC) { + input_stream_close(is_wvc); + } +} + +/* + * Decodes a file. + */ +static void +wavpack_filedecode(struct decoder *decoder, const char *fname) +{ + char error[ERRORLEN]; + WavpackContext *wpc; + + wpc = WavpackOpenFileInput( + fname, error, + OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, 23 + ); + if (wpc == NULL) { + g_warning( + "failed to open WavPack file \"%s\": %s\n", + fname, error + ); + return; + } + + struct replay_gain_info replay_gain_info; + if (wavpack_replaygain(&replay_gain_info, wpc)) + decoder_replay_gain(decoder, &replay_gain_info); + + wavpack_decode(decoder, wpc, true); + + WavpackCloseFile(wpc); +} + +static char const *const wavpack_suffixes[] = { + "wv", + NULL +}; + +static char const *const wavpack_mime_types[] = { + "audio/x-wavpack", + NULL +}; + +const struct decoder_plugin wavpack_decoder_plugin = { + "wavpack", + nullptr, + nullptr, + wavpack_streamdecode, + wavpack_filedecode, + wavpack_scan_file, + nullptr, + nullptr, + wavpack_suffixes, + wavpack_mime_types +}; diff --git a/src/decoder/WavpackDecoderPlugin.hxx b/src/decoder/WavpackDecoderPlugin.hxx new file mode 100644 index 000000000..9ebe6354f --- /dev/null +++ b/src/decoder/WavpackDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_WAVPACK_HXX +#define MPD_DECODER_WAVPACK_HXX + +extern const struct decoder_plugin wavpack_decoder_plugin; + +#endif diff --git a/src/decoder/XiphTags.c b/src/decoder/XiphTags.c new file mode 100644 index 000000000..d55787b94 --- /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 000000000..22a4e2204 --- /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/_flac_common.c b/src/decoder/_flac_common.c deleted file mode 100644 index d7f0c4a8a..000000000 --- a/src/decoder/_flac_common.c +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Common data structures and functions used by FLAC and OggFLAC - */ - -#include "config.h" -#include "_flac_common.h" -#include "flac_metadata.h" -#include "flac_pcm.h" -#include "audio_check.h" - -#include <glib.h> - -#include <assert.h> - -void -flac_data_init(struct flac_data *data, struct decoder * decoder, - struct input_stream *input_stream) -{ - pcm_buffer_init(&data->buffer); - - data->unsupported = false; - data->initialized = false; - data->total_frames = 0; - data->first_frame = 0; - data->next_frame = 0; - - data->position = 0; - data->decoder = decoder; - data->input_stream = input_stream; - data->tag = NULL; -} - -void -flac_data_deinit(struct flac_data *data) -{ - pcm_buffer_deinit(&data->buffer); - - if (data->tag != NULL) - tag_free(data->tag); -} - -static enum sample_format -flac_sample_format(unsigned bits_per_sample) -{ - switch (bits_per_sample) { - case 8: - return SAMPLE_FORMAT_S8; - - case 16: - return SAMPLE_FORMAT_S16; - - case 24: - return SAMPLE_FORMAT_S24_P32; - - case 32: - return SAMPLE_FORMAT_S32; - - default: - return SAMPLE_FORMAT_UNDEFINED; - } -} - -static void -flac_got_stream_info(struct flac_data *data, - const FLAC__StreamMetadata_StreamInfo *stream_info) -{ - if (data->initialized || data->unsupported) - return; - - GError *error = NULL; - if (!audio_format_init_checked(&data->audio_format, - stream_info->sample_rate, - flac_sample_format(stream_info->bits_per_sample), - stream_info->channels, &error)) { - g_warning("%s", error->message); - g_error_free(error); - data->unsupported = true; - return; - } - - data->frame_size = audio_format_frame_size(&data->audio_format); - - if (data->total_frames == 0) - data->total_frames = stream_info->total_samples; - - data->initialized = true; -} - -void flac_metadata_common_cb(const FLAC__StreamMetadata * block, - struct flac_data *data) -{ - if (data->unsupported) - return; - - struct replay_gain_info rgi; - char *mixramp_start; - char *mixramp_end; - float replay_gain_db = 0; - - switch (block->type) { - case FLAC__METADATA_TYPE_STREAMINFO: - flac_got_stream_info(data, &block->data.stream_info); - break; - - case FLAC__METADATA_TYPE_VORBIS_COMMENT: - if (flac_parse_replay_gain(&rgi, block)) - replay_gain_db = decoder_replay_gain(data->decoder, &rgi); - - if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block)) - decoder_mixramp(data->decoder, replay_gain_db, - mixramp_start, mixramp_end); - - if (data->tag != NULL) - flac_vorbis_comments_to_tag(data->tag, NULL, - &block->data.vorbis_comment); - - default: - break; - } -} - -void flac_error_common_cb(const FLAC__StreamDecoderErrorStatus status, - struct flac_data *data) -{ - if (decoder_get_command(data->decoder) == DECODE_COMMAND_STOP) - return; - - g_warning("%s", FLAC__StreamDecoderErrorStatusString[status]); -} - -/** - * This function attempts to call decoder_initialized() in case there - * was no STREAMINFO block. This is allowed for nonseekable streams, - * where the server sends us only a part of the file, without - * providing the STREAMINFO block from the beginning of the file - * (e.g. when seeking with SqueezeBox Server). - */ -static bool -flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) -{ - if (data->unsupported) - return false; - - GError *error = NULL; - if (!audio_format_init_checked(&data->audio_format, - header->sample_rate, - flac_sample_format(header->bits_per_sample), - header->channels, &error)) { - g_warning("%s", error->message); - g_error_free(error); - data->unsupported = true; - return false; - } - - data->frame_size = audio_format_frame_size(&data->audio_format); - - decoder_initialized(data->decoder, &data->audio_format, - data->input_stream->seekable, - (float)data->total_frames / - (float)data->audio_format.sample_rate); - - data->initialized = true; - - return true; -} - -FLAC__StreamDecoderWriteStatus -flac_common_write(struct flac_data *data, const FLAC__Frame * frame, - const FLAC__int32 *const buf[], - FLAC__uint64 nbytes) -{ - enum decoder_command cmd; - void *buffer; - unsigned bit_rate; - - if (!data->initialized && !flac_got_first_frame(data, &frame->header)) - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - - size_t buffer_size = frame->header.blocksize * data->frame_size; - buffer = pcm_buffer_get(&data->buffer, buffer_size); - - flac_convert(buffer, frame->header.channels, - data->audio_format.format, buf, - 0, frame->header.blocksize); - - if (nbytes > 0) - bit_rate = nbytes * 8 * frame->header.sample_rate / - (1000 * frame->header.blocksize); - else - bit_rate = 0; - - cmd = decoder_data(data->decoder, data->input_stream, - buffer, buffer_size, - bit_rate); - data->next_frame += frame->header.blocksize; - switch (cmd) { - case DECODE_COMMAND_NONE: - case DECODE_COMMAND_START: - break; - - case DECODE_COMMAND_STOP: - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - - case DECODE_COMMAND_SEEK: - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; - } - - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; -} diff --git a/src/decoder/_flac_common.h b/src/decoder/_flac_common.h deleted file mode 100644 index 0d90ba656..000000000 --- a/src/decoder/_flac_common.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Common data structures and functions used by FLAC and OggFLAC - */ - -#ifndef MPD_FLAC_COMMON_H -#define MPD_FLAC_COMMON_H - -#include "decoder_api.h" -#include "pcm_buffer.h" - -#include <glib.h> - -#include <FLAC/stream_decoder.h> -#include <FLAC/metadata.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "flac" - -struct flac_data { - struct pcm_buffer buffer; - - /** - * The size of one frame in the output buffer. - */ - unsigned frame_size; - - /** - * Has decoder_initialized() been called yet? - */ - bool initialized; - - /** - * Does the FLAC file contain an unsupported audio format? - */ - bool unsupported; - - /** - * The validated audio format of the FLAC file. This - * attribute is defined if "initialized" is true. - */ - struct audio_format audio_format; - - /** - * The total number of frames in this song. The decoder - * plugin may initialize this attribute to override the value - * provided by libFLAC (e.g. for sub songs from a CUE sheet). - */ - FLAC__uint64 total_frames; - - /** - * The number of the first frame in this song. This is only - * non-zero if playing sub songs from a CUE sheet. - */ - FLAC__uint64 first_frame; - - /** - * The number of the next frame which is going to be decoded. - */ - FLAC__uint64 next_frame; - - FLAC__uint64 position; - struct decoder *decoder; - struct input_stream *input_stream; - struct tag *tag; -}; - -/* initializes a given FlacData struct */ -void -flac_data_init(struct flac_data *data, struct decoder * decoder, - struct input_stream *input_stream); - -void -flac_data_deinit(struct flac_data *data); - -void flac_metadata_common_cb(const FLAC__StreamMetadata * block, - struct flac_data *data); - -void flac_error_common_cb(FLAC__StreamDecoderErrorStatus status, - struct flac_data *data); - -FLAC__StreamDecoderWriteStatus -flac_common_write(struct flac_data *data, const FLAC__Frame * frame, - const FLAC__int32 *const buf[], - FLAC__uint64 nbytes); - -#endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/_ogg_common.c b/src/decoder/_ogg_common.c deleted file mode 100644 index 09d2712da..000000000 --- a/src/decoder/_ogg_common.c +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) - */ - -#include "config.h" -#include "_ogg_common.h" - -ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream) -{ - /* oggflac detection based on code in ogg123 and this post - * http://lists.xiph.org/pipermail/flac/2004-December/000393.html - * ogg123 trunk still doesn't have this patch as of June 2005 */ - unsigned char buf[41]; - size_t r; - - r = decoder_read(NULL, inStream, buf, sizeof(buf)); - if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0) - return VORBIS; - - if ((memcmp(buf + 29, "FLAC", 4) == 0 && - memcmp(buf + 37, "fLaC", 4) == 0) || - memcmp(buf + 28, "FLAC", 4) == 0 || - memcmp(buf + 28, "fLaC", 4) == 0) - return FLAC; - - return VORBIS; -} diff --git a/src/decoder/_ogg_common.h b/src/decoder/_ogg_common.h deleted file mode 100644 index 85e4ebba6..000000000 --- a/src/decoder/_ogg_common.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) - */ - -#ifndef MPD_OGG_COMMON_H -#define MPD_OGG_COMMON_H - -#include "decoder_api.h" - -typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type; - -ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream); - -#endif /* _OGG_COMMON_H */ diff --git a/src/decoder/audiofile_decoder_plugin.c b/src/decoder/audiofile_decoder_plugin.c index b344795e7..d2ceee8ae 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 84471fb3a..44d12d899 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 3df9497c4..d3043fb05 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 d9675f5fe..0912740c3 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 c0107eb30..23576a629 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 911f033b8..a7ece93fe 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/ffmpeg_decoder_plugin.c b/src/decoder/ffmpeg_decoder_plugin.c deleted file mode 100644 index 4c4cb2b81..000000000 --- a/src/decoder/ffmpeg_decoder_plugin.c +++ /dev/null @@ -1,800 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" -#include "ffmpeg_metadata.h" -#include "tag_handler.h" - -#include <glib.h> - -#include <assert.h> -#include <stdio.h> -#include <unistd.h> -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> - -#include <libavcodec/avcodec.h> -#include <libavformat/avformat.h> -#include <libavformat/avio.h> -#include <libavutil/avutil.h> -#include <libavutil/log.h> -#include <libavutil/mathematics.h> -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) -#include <libavutil/dict.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "ffmpeg" - -static GLogLevelFlags -level_ffmpeg_to_glib(int level) -{ - if (level <= AV_LOG_FATAL) - return G_LOG_LEVEL_CRITICAL; - - if (level <= AV_LOG_ERROR) - return G_LOG_LEVEL_WARNING; - - if (level <= AV_LOG_INFO) - return G_LOG_LEVEL_MESSAGE; - - return G_LOG_LEVEL_DEBUG; -} - -static void -mpd_ffmpeg_log_callback(G_GNUC_UNUSED void *ptr, int level, - const char *fmt, va_list vl) -{ - const AVClass * cls = NULL; - - if (ptr != NULL) - cls = *(const AVClass *const*)ptr; - - if (cls != NULL) { - char *domain = g_strconcat(G_LOG_DOMAIN, "/", cls->item_name(ptr), NULL); - g_logv(domain, level_ffmpeg_to_glib(level), fmt, vl); - g_free(domain); - } -} - -struct mpd_ffmpeg_stream { - struct decoder *decoder; - struct input_stream *input; - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0) - AVIOContext *io; -#else - ByteIOContext *io; -#endif - unsigned char buffer[8192]; -}; - -static int -mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size) -{ - struct mpd_ffmpeg_stream *stream = opaque; - - return decoder_read(stream->decoder, stream->input, - (void *)buf, size); -} - -static int64_t -mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) -{ - struct mpd_ffmpeg_stream *stream = opaque; - - if (whence == AVSEEK_SIZE) - return stream->input->size; - - if (!input_stream_lock_seek(stream->input, pos, whence, NULL)) - return -1; - - return stream->input->offset; -} - -static struct mpd_ffmpeg_stream * -mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input) -{ - struct mpd_ffmpeg_stream *stream = g_new(struct mpd_ffmpeg_stream, 1); - stream->decoder = decoder; - stream->input = input; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0) - stream->io = avio_alloc_context(stream->buffer, sizeof(stream->buffer), - false, stream, - mpd_ffmpeg_stream_read, NULL, - input->seekable - ? mpd_ffmpeg_stream_seek : NULL); -#else - stream->io = av_alloc_put_byte(stream->buffer, sizeof(stream->buffer), - false, stream, - mpd_ffmpeg_stream_read, NULL, - input->seekable - ? mpd_ffmpeg_stream_seek : NULL); -#endif - if (stream->io == NULL) { - g_free(stream); - return NULL; - } - - return stream; -} - -/** - * API compatibility wrapper for av_open_input_stream() and - * avformat_open_input(). - */ -static int -mpd_ffmpeg_open_input(AVFormatContext **ic_ptr, -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0) - AVIOContext *pb, -#else - ByteIOContext *pb, -#endif - const char *filename, - AVInputFormat *fmt) -{ -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,1,3) - AVFormatContext *context = avformat_alloc_context(); - if (context == NULL) - return AVERROR(ENOMEM); - - context->pb = pb; - *ic_ptr = context; - return avformat_open_input(ic_ptr, filename, fmt, NULL); -#else - return av_open_input_stream(ic_ptr, pb, filename, fmt, NULL); -#endif -} - -static void -mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream) -{ - av_free(stream->io); - g_free(stream); -} - -static bool -ffmpeg_init(G_GNUC_UNUSED const struct config_param *param) -{ - av_log_set_callback(mpd_ffmpeg_log_callback); - - av_register_all(); - return true; -} - -static int -ffmpeg_find_audio_stream(const AVFormatContext *format_context) -{ - for (unsigned i = 0; i < format_context->nb_streams; ++i) - if (format_context->streams[i]->codec->codec_type == -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 64, 0) - AVMEDIA_TYPE_AUDIO) -#else - CODEC_TYPE_AUDIO) -#endif - return i; - - return -1; -} - -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53,25,0) -/** - * On some platforms, libavcodec wants the output buffer aligned to 16 - * bytes (because it uses SSE/Altivec internally). This function - * returns the aligned version of the specified buffer, and corrects - * the buffer size. - */ -static void * -align16(void *p, size_t *length_p) -{ - unsigned add = 16 - (size_t)p % 16; - - *length_p -= add; - return (char *)p + add; -} -#endif - -G_GNUC_CONST -static double -time_from_ffmpeg(int64_t t, const AVRational time_base) -{ - assert(t != (int64_t)AV_NOPTS_VALUE); - - return (double)av_rescale_q(t, time_base, (AVRational){1, 1024}) - / (double)1024; -} - -G_GNUC_CONST -static int64_t -time_to_ffmpeg(double t, const AVRational time_base) -{ - return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024}, - time_base); -} - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0) - -static void -copy_interleave_frame2(uint8_t *dest, uint8_t **src, - unsigned nframes, unsigned nchannels, - unsigned sample_size) -{ - for (unsigned frame = 0; frame < nframes; ++frame) { - for (unsigned channel = 0; channel < nchannels; ++channel) { - memcpy(dest, src[channel] + frame * sample_size, - sample_size); - dest += sample_size; - } - } -} - -/** - * Copy PCM data from a AVFrame to an interleaved buffer. - */ -static int -copy_interleave_frame(const AVCodecContext *codec_context, - const AVFrame *frame, - uint8_t *buffer, size_t buffer_size) -{ - int plane_size; - const int data_size = - av_samples_get_buffer_size(&plane_size, - codec_context->channels, - frame->nb_samples, - codec_context->sample_fmt, 1); - if (buffer_size < (size_t)data_size) - /* buffer is too small - shouldn't happen */ - return AVERROR(EINVAL); - - if (av_sample_fmt_is_planar(codec_context->sample_fmt) && - codec_context->channels > 1) { - copy_interleave_frame2(buffer, frame->extended_data, - frame->nb_samples, - codec_context->channels, - av_get_bytes_per_sample(codec_context->sample_fmt)); - } else { - memcpy(buffer, frame->extended_data[0], data_size); - } - - return data_size; -} -#endif - -static enum decoder_command -ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, - const AVPacket *packet, - AVCodecContext *codec_context, - const AVRational *time_base) -{ - if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) - decoder_timestamp(decoder, - time_from_ffmpeg(packet->pts, *time_base)); - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0) - AVPacket packet2 = *packet; -#else - const uint8_t *packet_data = packet->data; - int packet_size = packet->size; -#endif - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0) - uint8_t aligned_buffer[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16]; - const size_t buffer_size = sizeof(aligned_buffer); -#else - /* libavcodec < 0.8 needs an aligned buffer */ - uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16]; - size_t buffer_size = sizeof(audio_buf); - int16_t *aligned_buffer = align16(audio_buf, &buffer_size); -#endif - - enum decoder_command cmd = DECODE_COMMAND_NONE; - while ( -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0) - packet2.size > 0 && -#else - packet_size > 0 && -#endif - cmd == DECODE_COMMAND_NONE) { - int audio_size = buffer_size; -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0) - AVFrame frame; - int got_frame = 0; - int len = avcodec_decode_audio4(codec_context, - &frame, &got_frame, - &packet2); - if (len >= 0 && got_frame) { - audio_size = copy_interleave_frame(codec_context, - &frame, - aligned_buffer, - buffer_size); - if (audio_size < 0) - len = audio_size; - } else if (len >= 0) - len = -1; -#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0) - int len = avcodec_decode_audio3(codec_context, - aligned_buffer, &audio_size, - &packet2); -#else - int len = avcodec_decode_audio2(codec_context, - aligned_buffer, &audio_size, - packet_data, packet_size); -#endif - - if (len < 0) { - /* if error, we skip the frame */ - g_message("decoding failed, frame skipped\n"); - break; - } - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0) - packet2.data += len; - packet2.size -= len; -#else - packet_data += len; - packet_size -= len; -#endif - - if (audio_size <= 0) - continue; - - cmd = decoder_data(decoder, is, - aligned_buffer, audio_size, - codec_context->bit_rate / 1000); - } - return cmd; -} - -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(52, 94, 1) -#define AVSampleFormat SampleFormat -#endif - -G_GNUC_CONST -static enum sample_format -ffmpeg_sample_format(enum AVSampleFormat sample_fmt) -{ - switch (sample_fmt) { -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1) - case AV_SAMPLE_FMT_S16: -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0) - case AV_SAMPLE_FMT_S16P: -#endif -#else - case SAMPLE_FMT_S16: -#endif - return SAMPLE_FORMAT_S16; - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1) - case AV_SAMPLE_FMT_S32: -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0) - case AV_SAMPLE_FMT_S32P: -#endif -#else - case SAMPLE_FMT_S32: -#endif - return SAMPLE_FORMAT_S32; - -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0) - case AV_SAMPLE_FMT_FLTP: - return SAMPLE_FORMAT_FLOAT; -#endif - - default: - break; - } - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1) - char buffer[64]; - const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer), - sample_fmt); - if (name != NULL) - g_warning("Unsupported libavcodec SampleFormat value: %s (%d)", - name, sample_fmt); - else -#endif - g_warning("Unsupported libavcodec SampleFormat value: %d", - sample_fmt); - return SAMPLE_FORMAT_UNDEFINED; -} - -static AVInputFormat * -ffmpeg_probe(struct decoder *decoder, struct input_stream *is) -{ - enum { - BUFFER_SIZE = 16384, - PADDING = 16, - }; - - unsigned char *buffer = g_malloc(BUFFER_SIZE); - size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); - if (nbytes <= PADDING || - !input_stream_lock_seek(is, 0, SEEK_SET, NULL)) { - g_free(buffer); - return NULL; - } - - /* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes - beyond the declared buffer limit, which makes valgrind - angry; this workaround removes some padding from the buffer - size */ - nbytes -= PADDING; - - AVProbeData avpd = { - .buf = buffer, - .buf_size = nbytes, - .filename = is->uri, - }; - - AVInputFormat *format = av_probe_input_format(&avpd, true); - g_free(buffer); - - return format; -} - -static void -ffmpeg_decode(struct decoder *decoder, struct input_stream *input) -{ - AVInputFormat *input_format = ffmpeg_probe(decoder, input); - if (input_format == NULL) - return; - - g_debug("detected input format '%s' (%s)", - input_format->name, input_format->long_name); - - struct mpd_ffmpeg_stream *stream = - mpd_ffmpeg_stream_open(decoder, input); - if (stream == NULL) { - g_warning("Failed to open stream"); - return; - } - - //ffmpeg works with ours "fileops" helper - AVFormatContext *format_context = NULL; - if (mpd_ffmpeg_open_input(&format_context, stream->io, input->uri, - input_format) != 0) { - g_warning("Open failed\n"); - mpd_ffmpeg_stream_close(stream); - return; - } - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,6,0) - const int find_result = - avformat_find_stream_info(format_context, NULL); -#else - const int find_result = av_find_stream_info(format_context); -#endif - if (find_result < 0) { - g_warning("Couldn't find stream info\n"); -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&format_context); -#else - av_close_input_stream(format_context); -#endif - mpd_ffmpeg_stream_close(stream); - return; - } - - int audio_stream = ffmpeg_find_audio_stream(format_context); - if (audio_stream == -1) { - g_warning("No audio stream inside\n"); -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&format_context); -#else - av_close_input_stream(format_context); -#endif - mpd_ffmpeg_stream_close(stream); - return; - } - - AVStream *av_stream = format_context->streams[audio_stream]; - - AVCodecContext *codec_context = av_stream->codec; - if (codec_context->codec_name[0] != 0) - g_debug("codec '%s'", codec_context->codec_name); - - AVCodec *codec = avcodec_find_decoder(codec_context->codec_id); - - if (!codec) { - g_warning("Unsupported audio codec\n"); -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&format_context); -#else - av_close_input_stream(format_context); -#endif - mpd_ffmpeg_stream_close(stream); - return; - } - - const enum sample_format sample_format = - ffmpeg_sample_format(codec_context->sample_fmt); - if (sample_format == SAMPLE_FORMAT_UNDEFINED) - return; - - GError *error = NULL; - struct audio_format audio_format; - if (!audio_format_init_checked(&audio_format, - codec_context->sample_rate, - sample_format, - codec_context->channels, &error)) { - g_warning("%s", error->message); - g_error_free(error); -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&format_context); -#else - av_close_input_stream(format_context); -#endif - mpd_ffmpeg_stream_close(stream); - return; - } - - /* the audio format must be read from AVCodecContext by now, - because avcodec_open() has been demonstrated to fill bogus - values into AVCodecContext.channels - a change that will be - reverted later by avcodec_decode_audio3() */ - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,6,0) - const int open_result = avcodec_open2(codec_context, codec, NULL); -#else - const int open_result = avcodec_open(codec_context, codec); -#endif - if (open_result < 0) { - g_warning("Could not open codec\n"); -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&format_context); -#else - av_close_input_stream(format_context); -#endif - mpd_ffmpeg_stream_close(stream); - return; - } - - int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE - ? format_context->duration / AV_TIME_BASE - : 0; - - decoder_initialized(decoder, &audio_format, - input->seekable, total_time); - - enum decoder_command cmd; - do { - AVPacket packet; - if (av_read_frame(format_context, &packet) < 0) - /* end of file */ - break; - - if (packet.stream_index == audio_stream) - cmd = ffmpeg_send_packet(decoder, input, - &packet, codec_context, - &av_stream->time_base); - else - cmd = decoder_get_command(decoder); - - av_free_packet(&packet); - - if (cmd == DECODE_COMMAND_SEEK) { - int64_t where = - time_to_ffmpeg(decoder_seek_where(decoder), - av_stream->time_base); - - if (av_seek_frame(format_context, audio_stream, where, - AV_TIME_BASE) < 0) - decoder_seek_error(decoder); - else { - avcodec_flush_buffers(codec_context); - decoder_command_finished(decoder); - } - } - } while (cmd != DECODE_COMMAND_STOP); - - avcodec_close(codec_context); -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&format_context); -#else - av_close_input_stream(format_context); -#endif - mpd_ffmpeg_stream_close(stream); -} - -//no tag reading in ffmpeg, check if playable -static bool -ffmpeg_scan_stream(struct input_stream *is, - const struct tag_handler *handler, void *handler_ctx) -{ - AVInputFormat *input_format = ffmpeg_probe(NULL, is); - if (input_format == NULL) - return false; - - struct mpd_ffmpeg_stream *stream = mpd_ffmpeg_stream_open(NULL, is); - if (stream == NULL) - return false; - - AVFormatContext *f = NULL; - if (mpd_ffmpeg_open_input(&f, stream->io, is->uri, - input_format) != 0) { - mpd_ffmpeg_stream_close(stream); - return false; - } - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,6,0) - const int find_result = - avformat_find_stream_info(f, NULL); -#else - const int find_result = av_find_stream_info(f); -#endif - if (find_result < 0) { -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&f); -#else - av_close_input_stream(f); -#endif - mpd_ffmpeg_stream_close(stream); - return false; - } - - if (f->duration != (int64_t)AV_NOPTS_VALUE) - tag_handler_invoke_duration(handler, handler_ctx, - f->duration / AV_TIME_BASE); - -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,101,0) - av_metadata_conv(f, NULL, f->iformat->metadata_conv); -#endif - - ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx); - int idx = ffmpeg_find_audio_stream(f); - if (idx >= 0) - ffmpeg_scan_dictionary(f->streams[idx]->metadata, - handler, handler_ctx); - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&f); -#else - av_close_input_stream(f); -#endif - mpd_ffmpeg_stream_close(stream); - - return true; -} - -/** - * A list of extensions found for the formats supported by ffmpeg. - * This list is current as of 02-23-09; To find out if there are more - * supported formats, check the ffmpeg changelog since this date for - * more formats. - */ -static const char *const ffmpeg_suffixes[] = { - "16sv", "3g2", "3gp", "4xm", "8svx", "aa3", "aac", "ac3", "afc", "aif", - "aifc", "aiff", "al", "alaw", "amr", "anim", "apc", "ape", "asf", - "atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak", - "cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa", - "eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726", - "gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts", - "m4a", "m4b", "m4v", - "mad", - "mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+", - "mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu", - "mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv", - "ogx", "oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra", - "ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd", - "sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts", - "tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc", - "vp6", "vmd", "wav", "webm", "wma", "wmv", "wsaud", "wsvga", "wv", - "wve", - NULL -}; - -static const char *const ffmpeg_mime_types[] = { - "application/m4a", - "application/mp4", - "application/octet-stream", - "application/ogg", - "application/x-ms-wmz", - "application/x-ms-wmd", - "application/x-ogg", - "application/x-shockwave-flash", - "application/x-shorten", - "audio/8svx", - "audio/16sv", - "audio/aac", - "audio/ac3", - "audio/aiff" - "audio/amr", - "audio/basic", - "audio/flac", - "audio/m4a", - "audio/mp4", - "audio/mpeg", - "audio/musepack", - "audio/ogg", - "audio/qcelp", - "audio/vorbis", - "audio/vorbis+ogg", - "audio/x-8svx", - "audio/x-16sv", - "audio/x-aac", - "audio/x-ac3", - "audio/x-aiff" - "audio/x-alaw", - "audio/x-au", - "audio/x-dca", - "audio/x-eac3", - "audio/x-flac", - "audio/x-gsm", - "audio/x-mace", - "audio/x-matroska", - "audio/x-monkeys-audio", - "audio/x-mpeg", - "audio/x-ms-wma", - "audio/x-ms-wax", - "audio/x-musepack", - "audio/x-ogg", - "audio/x-vorbis", - "audio/x-vorbis+ogg", - "audio/x-pn-realaudio", - "audio/x-pn-multirate-realaudio", - "audio/x-speex", - "audio/x-tta" - "audio/x-voc", - "audio/x-wav", - "audio/x-wma", - "audio/x-wv", - "video/anim", - "video/quicktime", - "video/msvideo", - "video/ogg", - "video/theora", - "video/webm", - "video/x-dv", - "video/x-flv", - "video/x-matroska", - "video/x-mjpeg", - "video/x-mpeg", - "video/x-ms-asf", - "video/x-msvideo", - "video/x-ms-wmv", - "video/x-ms-wvx", - "video/x-ms-wm", - "video/x-ms-wmx", - "video/x-nut", - "video/x-pva", - "video/x-theora", - "video/x-vid", - "video/x-wmv", - "video/x-xvid", - - /* special value for the "ffmpeg" input plugin: all streams by - the "ffmpeg" input plugin shall be decoded by this - plugin */ - "audio/x-mpd-ffmpeg", - - NULL -}; - -const struct decoder_plugin ffmpeg_decoder_plugin = { - .name = "ffmpeg", - .init = ffmpeg_init, - .stream_decode = ffmpeg_decode, - .scan_stream = ffmpeg_scan_stream, - .suffixes = ffmpeg_suffixes, - .mime_types = ffmpeg_mime_types -}; diff --git a/src/decoder/ffmpeg_metadata.c b/src/decoder/ffmpeg_metadata.c deleted file mode 100644 index 3ef774f63..000000000 --- a/src/decoder/ffmpeg_metadata.c +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "ffmpeg_metadata.h" -#include "tag_table.h" -#include "tag_handler.h" - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "ffmpeg" - -static const struct tag_table ffmpeg_tags[] = { -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,50,0) - { "author", TAG_ARTIST }, -#endif - { "year", TAG_DATE }, - { "author-sort", TAG_ARTIST_SORT }, - { "album_artist", TAG_ALBUM_ARTIST }, - { "album_artist-sort", TAG_ALBUM_ARTIST_SORT }, - - /* sentinel */ - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - -static void -ffmpeg_copy_metadata(enum tag_type type, - AVDictionary *m, const char *name, - const struct tag_handler *handler, void *handler_ctx) -{ - AVDictionaryEntry *mt = NULL; - - while ((mt = av_dict_get(m, name, mt, 0)) != NULL) - tag_handler_invoke_tag(handler, handler_ctx, - type, mt->value); -} - -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) - -static void -ffmpeg_scan_pairs(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx) -{ - AVDictionaryEntry *i = NULL; - - while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != NULL) - tag_handler_invoke_pair(handler, handler_ctx, - i->key, i->value); -} - -#endif - -void -ffmpeg_scan_dictionary(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx) -{ - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - ffmpeg_copy_metadata(i, dict, tag_item_names[i], - handler, handler_ctx); - - for (const struct tag_table *i = ffmpeg_tags; - i->name != NULL; ++i) - ffmpeg_copy_metadata(i->type, dict, i->name, - handler, handler_ctx); - -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) - if (handler->pair != NULL) - ffmpeg_scan_pairs(dict, handler, handler_ctx); -#endif -} diff --git a/src/decoder/ffmpeg_metadata.h b/src/decoder/ffmpeg_metadata.h deleted file mode 100644 index 60658f479..000000000 --- a/src/decoder/ffmpeg_metadata.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FFMPEG_METADATA_H -#define MPD_FFMPEG_METADATA_H - -#include <libavformat/avformat.h> -#include <libavutil/avutil.h> -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) -#include <libavutil/dict.h> -#endif - -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53,1,0) -#define AVDictionary AVMetadata -#define AVDictionaryEntry AVMetadataTag -#define av_dict_get av_metadata_get -#endif - -struct tag_handler; - -void -ffmpeg_scan_dictionary(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx); - -#endif diff --git a/src/decoder/flac_compat.h b/src/decoder/flac_compat.h deleted file mode 100644 index 9a30acc26..000000000 --- a/src/decoder/flac_compat.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Common data structures and functions used by FLAC and OggFLAC - */ - -#ifndef MPD_FLAC_COMPAT_H -#define MPD_FLAC_COMPAT_H - -#include <FLAC/export.h> -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -# include <FLAC/seekable_stream_decoder.h> - -/* starting with libFLAC 1.1.3, the SeekableStreamDecoder has been - merged into the StreamDecoder. The following macros try to emulate - the new API for libFLAC 1.1.2 by mapping MPD's StreamDecoder calls - to the old SeekableStreamDecoder API. */ - -#define FLAC__StreamDecoder FLAC__SeekableStreamDecoder -#define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new -#define FLAC__stream_decoder_get_decode_position FLAC__seekable_stream_decoder_get_decode_position -#define FLAC__stream_decoder_get_state FLAC__seekable_stream_decoder_get_state -#define FLAC__stream_decoder_process_single FLAC__seekable_stream_decoder_process_single -#define FLAC__stream_decoder_process_until_end_of_metadata FLAC__seekable_stream_decoder_process_until_end_of_metadata -#define FLAC__stream_decoder_seek_absolute FLAC__seekable_stream_decoder_seek_absolute -#define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish -#define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete - -#define FLAC__STREAM_DECODER_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM - -typedef unsigned flac_read_status_size_t; - -#define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus -#define FLAC__STREAM_DECODER_READ_STATUS_CONTINUE FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK -#define FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK -#define FLAC__STREAM_DECODER_READ_STATUS_ABORT FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR - -#define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus -#define FLAC__STREAM_DECODER_SEEK_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK -#define FLAC__STREAM_DECODER_SEEK_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR -#define FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR - -#define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus -#define FLAC__STREAM_DECODER_TELL_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK -#define FLAC__STREAM_DECODER_TELL_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR -#define FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR - -#define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus -#define FLAC__STREAM_DECODER_LENGTH_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK -#define FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR -#define FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR - -typedef enum { - FLAC__STREAM_DECODER_INIT_STATUS_OK, - FLAC__STREAM_DECODER_INIT_STATUS_ERROR, -} FLAC__StreamDecoderInitStatus; - -static inline FLAC__StreamDecoderInitStatus -FLAC__stream_decoder_init_stream(FLAC__SeekableStreamDecoder *decoder, - FLAC__SeekableStreamDecoderReadCallback read_cb, - FLAC__SeekableStreamDecoderSeekCallback seek_cb, - FLAC__SeekableStreamDecoderTellCallback tell_cb, - FLAC__SeekableStreamDecoderLengthCallback length_cb, - FLAC__SeekableStreamDecoderEofCallback eof_cb, - FLAC__SeekableStreamDecoderWriteCallback write_cb, - FLAC__SeekableStreamDecoderMetadataCallback metadata_cb, - FLAC__SeekableStreamDecoderErrorCallback error_cb, - void *data) -{ - return FLAC__seekable_stream_decoder_set_read_callback(decoder, read_cb) && - FLAC__seekable_stream_decoder_set_seek_callback(decoder, seek_cb) && - FLAC__seekable_stream_decoder_set_tell_callback(decoder, tell_cb) && - FLAC__seekable_stream_decoder_set_length_callback(decoder, length_cb) && - FLAC__seekable_stream_decoder_set_eof_callback(decoder, eof_cb) && - FLAC__seekable_stream_decoder_set_write_callback(decoder, write_cb) && - FLAC__seekable_stream_decoder_set_metadata_callback(decoder, metadata_cb) && - FLAC__seekable_stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT) && - FLAC__seekable_stream_decoder_set_error_callback(decoder, error_cb) && - FLAC__seekable_stream_decoder_set_client_data(decoder, data) && - FLAC__seekable_stream_decoder_init(decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK - ? FLAC__STREAM_DECODER_INIT_STATUS_OK - : FLAC__STREAM_DECODER_INIT_STATUS_ERROR; -} - -#else /* FLAC_API_VERSION_CURRENT > 7 */ - -# include <FLAC/stream_decoder.h> - -# define flac_init(a,b,c,d,e,f,g,h,i,j) \ - (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \ - == FLAC__STREAM_DECODER_INIT_STATUS_OK) - -typedef size_t flac_read_status_size_t; - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -#endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/flac_decoder_plugin.c b/src/decoder/flac_decoder_plugin.c deleted file mode 100644 index fb0b3502d..000000000 --- a/src/decoder/flac_decoder_plugin.c +++ /dev/null @@ -1,486 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "_flac_common.h" -#include "flac_compat.h" -#include "flac_metadata.h" - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 -#include "_ogg_common.h" -#endif - -#include <glib.h> - -#include <assert.h> -#include <unistd.h> - -#include <sys/stat.h> -#include <sys/types.h> - -/* this code was based on flac123, from flac-tools */ - -static FLAC__StreamDecoderReadStatus -flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__byte buf[], flac_read_status_size_t *bytes, - void *fdata) -{ - struct flac_data *data = fdata; - size_t r; - - r = decoder_read(data->decoder, data->input_stream, - (void *)buf, *bytes); - *bytes = r; - - if (r == 0) { - if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || - input_stream_lock_eof(data->input_stream)) - return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; - else - return FLAC__STREAM_DECODER_READ_STATUS_ABORT; - } - - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; -} - -static FLAC__StreamDecoderSeekStatus -flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (!data->input_stream->seekable) - return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; - - if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET, - NULL)) - return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; - - return FLAC__STREAM_DECODER_SEEK_STATUS_OK; -} - -static FLAC__StreamDecoderTellStatus -flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 * offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (!data->input_stream->seekable) - return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; - - *offset = (long)(data->input_stream->offset); - - return FLAC__STREAM_DECODER_TELL_STATUS_OK; -} - -static FLAC__StreamDecoderLengthStatus -flac_length_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 * length, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (data->input_stream->size < 0) - return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; - - *length = (size_t) (data->input_stream->size); - - return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; -} - -static FLAC__bool -flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && - decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || - input_stream_lock_eof(data->input_stream); -} - -static void -flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__StreamDecoderErrorStatus status, void *fdata) -{ - flac_error_common_cb(status, (struct flac_data *) fdata); -} - -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) -{ - switch (state) { - case FLAC__SEEKABLE_STREAM_DECODER_OK: - case FLAC__SEEKABLE_STREAM_DECODER_SEEKING: - case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: - return; - - case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: - case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: - case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: - break; - } - - g_warning("%s\n", FLAC__SeekableStreamDecoderStateString[state]); -} -#else /* FLAC_API_VERSION_CURRENT >= 7 */ -static void flacPrintErroredState(FLAC__StreamDecoderState state) -{ - switch (state) { - case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA: - case FLAC__STREAM_DECODER_READ_METADATA: - case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: - case FLAC__STREAM_DECODER_READ_FRAME: - case FLAC__STREAM_DECODER_END_OF_STREAM: - return; - - case FLAC__STREAM_DECODER_OGG_ERROR: - case FLAC__STREAM_DECODER_SEEK_ERROR: - case FLAC__STREAM_DECODER_ABORTED: - case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - case FLAC__STREAM_DECODER_UNINITIALIZED: - break; - } - - g_warning("%s\n", FLAC__StreamDecoderStateString[state]); -} -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec, - const FLAC__StreamMetadata * block, void *vdata) -{ - flac_metadata_common_cb(block, (struct flac_data *) vdata); -} - -static FLAC__StreamDecoderWriteStatus -flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame, - const FLAC__int32 *const buf[], void *vdata) -{ - struct flac_data *data = (struct flac_data *) vdata; - FLAC__uint64 nbytes = 0; - - if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) { - if (data->position > 0 && nbytes > data->position) { - nbytes -= data->position; - data->position += nbytes; - } else { - data->position = nbytes; - nbytes = 0; - } - } else - nbytes = 0; - - return flac_common_write(data, frame, buf, nbytes); -} - -static bool -flac_scan_file(const char *file, - const struct tag_handler *handler, void *handler_ctx) -{ - return flac_scan_file2(file, NULL, handler, handler_ctx); -} - -/** - * Some glue code around FLAC__stream_decoder_new(). - */ -static FLAC__StreamDecoder * -flac_decoder_new(void) -{ - FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); - if (sd == NULL) { - g_warning("FLAC__stream_decoder_new() failed"); - return NULL; - } - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) - g_debug("FLAC__stream_decoder_set_metadata_respond() has failed"); -#endif - - return sd; -} - -static bool -flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd, - FLAC__uint64 duration) -{ - data->total_frames = duration; - - if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) { - g_warning("problem reading metadata"); - return false; - } - - if (data->initialized) { - /* done */ - decoder_initialized(data->decoder, &data->audio_format, - data->input_stream->seekable, - (float)data->total_frames / - (float)data->audio_format.sample_rate); - return true; - } - - if (data->input_stream->seekable) - /* allow the workaround below only for nonseekable - streams*/ - return false; - - /* no stream_info packet found; try to initialize the decoder - from the first frame header */ - FLAC__stream_decoder_process_single(sd); - return data->initialized; -} - -static void -flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, - FLAC__uint64 t_start, FLAC__uint64 t_end) -{ - struct decoder *decoder = data->decoder; - enum decoder_command cmd; - - data->first_frame = t_start; - - while (true) { - if (data->tag != NULL && !tag_is_empty(data->tag)) { - cmd = decoder_tag(data->decoder, data->input_stream, - data->tag); - tag_free(data->tag); - data->tag = tag_new(); - } else - cmd = decoder_get_command(decoder); - - if (cmd == DECODE_COMMAND_SEEK) { - FLAC__uint64 seek_sample = t_start + - decoder_seek_where(decoder) * - data->audio_format.sample_rate; - if (seek_sample >= t_start && - (t_end == 0 || seek_sample <= t_end) && - FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) { - data->next_frame = seek_sample; - data->position = 0; - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } else if (cmd == DECODE_COMMAND_STOP || - FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM) - break; - - if (t_end != 0 && data->next_frame >= t_end) - /* end of this sub track */ - break; - - if (!FLAC__stream_decoder_process_single(flac_dec) && - decoder_get_command(decoder) == DECODE_COMMAND_NONE) { - /* a failure that was not triggered by a - decoder command */ - flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec)); - break; - } - } -} - -static FLAC__StreamDecoderInitStatus -stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) -{ -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - return FLAC__stream_decoder_init_ogg_stream(flac_dec, - flac_read_cb, - flac_seek_cb, - flac_tell_cb, - flac_length_cb, - flac_eof_cb, - flac_write_cb, - flacMetadata, - flac_error_cb, - data); -#else - (void)flac_dec; - (void)data; - - return FLAC__STREAM_DECODER_INIT_STATUS_ERROR; -#endif -} - -static FLAC__StreamDecoderInitStatus -stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) -{ - return FLAC__stream_decoder_init_stream(flac_dec, - flac_read_cb, flac_seek_cb, - flac_tell_cb, flac_length_cb, - flac_eof_cb, flac_write_cb, - flacMetadata, - flac_error_cb, - data); -} - -static FLAC__StreamDecoderInitStatus -stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg) -{ - return is_ogg - ? stream_init_oggflac(flac_dec, data) - : stream_init_flac(flac_dec, data); -} - -static void -flac_decode_internal(struct decoder * decoder, - struct input_stream *input_stream, - bool is_ogg) -{ - FLAC__StreamDecoder *flac_dec; - struct flac_data data; - - flac_dec = flac_decoder_new(); - if (flac_dec == NULL) - return; - - flac_data_init(&data, decoder, input_stream); - data.tag = tag_new(); - - FLAC__StreamDecoderInitStatus status = - stream_init(flac_dec, &data, is_ogg); - if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { - flac_data_deinit(&data); - FLAC__stream_decoder_delete(flac_dec); -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - g_warning("%s", FLAC__StreamDecoderInitStatusString[status]); -#endif - return; - } - - if (!flac_decoder_initialize(&data, flac_dec, 0)) { - flac_data_deinit(&data); - FLAC__stream_decoder_finish(flac_dec); - FLAC__stream_decoder_delete(flac_dec); - return; - } - - flac_decoder_loop(&data, flac_dec, 0, 0); - - flac_data_deinit(&data); - - FLAC__stream_decoder_finish(flac_dec); - FLAC__stream_decoder_delete(flac_dec); -} - -static void -flac_decode(struct decoder * decoder, struct input_stream *input_stream) -{ - flac_decode_internal(decoder, input_stream, false); -} - -#ifndef HAVE_OGGFLAC - -static bool -oggflac_init(G_GNUC_UNUSED const struct config_param *param) -{ -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - return !!FLAC_API_SUPPORTS_OGG_FLAC; -#else - /* disable oggflac when libflac is too old */ - return false; -#endif -} - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - -static bool -oggflac_scan_file(const char *file, - const struct tag_handler *handler, void *handler_ctx) -{ - FLAC__Metadata_Iterator *it; - FLAC__StreamMetadata *block; - FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); - - if (!(FLAC__metadata_chain_read_ogg(chain, file))) { - FLAC__metadata_chain_delete(chain); - return false; - } - - it = FLAC__metadata_iterator_new(); - FLAC__metadata_iterator_init(it, chain); - - do { - if (!(block = FLAC__metadata_iterator_get_block(it))) - break; - - flac_scan_metadata(NULL, block, - handler, handler_ctx); - } while (FLAC__metadata_iterator_next(it)); - FLAC__metadata_iterator_delete(it); - - FLAC__metadata_chain_delete(chain); - return true; -} - -static void -oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) -{ - if (ogg_stream_type_detect(input_stream) != FLAC) - return; - - /* rewind the stream, because ogg_stream_type_detect() has - moved it */ - input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); - - flac_decode_internal(decoder, input_stream, true); -} - -static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; -static const char *const oggflac_mime_types[] = { - "application/ogg", - "application/x-ogg", - "audio/ogg", - "audio/x-flac+ogg", - "audio/x-ogg", - NULL -}; - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -const struct decoder_plugin oggflac_decoder_plugin = { - .name = "oggflac", - .init = oggflac_init, -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - .stream_decode = oggflac_decode, - .scan_file = oggflac_scan_file, - .suffixes = oggflac_suffixes, - .mime_types = oggflac_mime_types -#endif -}; - -#endif /* HAVE_OGGFLAC */ - -static const char *const flac_suffixes[] = { "flac", NULL }; -static const char *const flac_mime_types[] = { - "application/flac", - "application/x-flac", - "audio/flac", - "audio/x-flac", - NULL -}; - -const struct decoder_plugin flac_decoder_plugin = { - .name = "flac", - .stream_decode = flac_decode, - .scan_file = flac_scan_file, - .suffixes = flac_suffixes, - .mime_types = flac_mime_types, -}; diff --git a/src/decoder/flac_metadata.c b/src/decoder/flac_metadata.c deleted file mode 100644 index bd1eaf323..000000000 --- a/src/decoder/flac_metadata.c +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "flac_metadata.h" -#include "replay_gain_info.h" -#include "tag.h" -#include "tag_handler.h" -#include "tag_table.h" - -#include <glib.h> - -#include <assert.h> -#include <stdbool.h> -#include <stdlib.h> - -static bool -flac_find_float_comment(const FLAC__StreamMetadata *block, - const char *cmnt, float *fl) -{ - int offset; - size_t pos; - int len; - unsigned char tmp, *p; - - offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, - cmnt); - if (offset < 0) - return false; - - pos = strlen(cmnt) + 1; /* 1 is for '=' */ - len = block->data.vorbis_comment.comments[offset].length - pos; - if (len <= 0) - return false; - - p = &block->data.vorbis_comment.comments[offset].entry[pos]; - tmp = p[len]; - p[len] = '\0'; - *fl = (float)atof((char *)p); - p[len] = tmp; - - return true; -} - -bool -flac_parse_replay_gain(struct replay_gain_info *rgi, - const FLAC__StreamMetadata *block) -{ - bool found = false; - - replay_gain_info_init(rgi); - - if (flac_find_float_comment(block, "replaygain_album_gain", - &rgi->tuples[REPLAY_GAIN_ALBUM].gain)) - found = true; - if (flac_find_float_comment(block, "replaygain_album_peak", - &rgi->tuples[REPLAY_GAIN_ALBUM].peak)) - found = true; - if (flac_find_float_comment(block, "replaygain_track_gain", - &rgi->tuples[REPLAY_GAIN_TRACK].gain)) - found = true; - if (flac_find_float_comment(block, "replaygain_track_peak", - &rgi->tuples[REPLAY_GAIN_TRACK].peak)) - found = true; - - return found; -} - -static bool -flac_find_string_comment(const FLAC__StreamMetadata *block, - const char *cmnt, char **str) -{ - int offset; - size_t pos; - int len; - const unsigned char *p; - - *str = NULL; - offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, - cmnt); - if (offset < 0) - return false; - - pos = strlen(cmnt) + 1; /* 1 is for '=' */ - len = block->data.vorbis_comment.comments[offset].length - pos; - if (len <= 0) - return false; - - p = &block->data.vorbis_comment.comments[offset].entry[pos]; - *str = g_strndup((const char *)p, len); - - return true; -} - -bool -flac_parse_mixramp(char **mixramp_start, char **mixramp_end, - const FLAC__StreamMetadata *block) -{ - bool found = false; - - if (flac_find_string_comment(block, "mixramp_start", mixramp_start)) - found = true; - if (flac_find_string_comment(block, "mixramp_end", mixramp_end)) - found = true; - - return found; -} - -/** - * Checks if the specified name matches the entry's name, and if yes, - * returns the comment value (not null-temrinated). - */ -static const char * -flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, const char *char_tnum, size_t *length_r) -{ - size_t name_length = strlen(name); - size_t char_tnum_length = 0; - const char *comment = (const char*)entry->entry; - - if (entry->length <= name_length || - g_ascii_strncasecmp(comment, name, name_length) != 0) - return NULL; - - if (char_tnum != NULL) { - char_tnum_length = strlen(char_tnum); - if (entry->length > name_length + char_tnum_length + 2 && - comment[name_length] == '[' && - g_ascii_strncasecmp(comment + name_length + 1, - char_tnum, char_tnum_length) == 0 && - comment[name_length + char_tnum_length + 1] == ']') - name_length = name_length + char_tnum_length + 2; - else if (entry->length > name_length + char_tnum_length && - g_ascii_strncasecmp(comment + name_length, - char_tnum, char_tnum_length) == 0) - name_length = name_length + char_tnum_length; - } - - if (comment[name_length] == '=') { - *length_r = entry->length - name_length - 1; - return comment + name_length + 1; - } - - return NULL; -} - -/** - * Check if the comment's name equals the passed name, and if so, copy - * the comment value into the tag. - */ -static bool -flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, enum tag_type tag_type, - const char *char_tnum, - const struct tag_handler *handler, void *handler_ctx) -{ - const char *value; - size_t value_length; - - value = flac_comment_value(entry, name, char_tnum, &value_length); - if (value != NULL) { - char *p = g_strndup(value, value_length); - tag_handler_invoke_tag(handler, handler_ctx, tag_type, p); - g_free(p); - return true; - } - - return false; -} - -static const struct tag_table flac_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - -static void -flac_scan_comment(const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const struct tag_handler *handler, void *handler_ctx) -{ - if (handler->pair != NULL) { - char *name = g_strdup((const char*)entry->entry); - char *value = strchr(name, '='); - - if (value != NULL && value > name) { - *value++ = 0; - tag_handler_invoke_pair(handler, handler_ctx, - name, value); - } - - g_free(name); - } - - for (const struct tag_table *i = flac_tags; i->name != NULL; ++i) - if (flac_copy_comment(entry, i->name, i->type, char_tnum, - handler, handler_ctx)) - return; - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (flac_copy_comment(entry, - tag_item_names[i], i, char_tnum, - handler, handler_ctx)) - return; -} - -static void -flac_scan_comments(const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment *comment, - const struct tag_handler *handler, void *handler_ctx) -{ - for (unsigned i = 0; i < comment->num_comments; ++i) - flac_scan_comment(char_tnum, &comment->comments[i], - handler, handler_ctx); -} - -void -flac_scan_metadata(const char *track, - const FLAC__StreamMetadata *block, - const struct tag_handler *handler, void *handler_ctx) -{ - switch (block->type) { - case FLAC__METADATA_TYPE_VORBIS_COMMENT: - flac_scan_comments(track, &block->data.vorbis_comment, - handler, handler_ctx); - break; - - case FLAC__METADATA_TYPE_STREAMINFO: - if (block->data.stream_info.sample_rate > 0) - tag_handler_invoke_duration(handler, handler_ctx, - flac_duration(&block->data.stream_info)); - break; - - default: - break; - } -} - -void -flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment *comment) -{ - flac_scan_comments(char_tnum, comment, - &add_tag_handler, tag); -} - -bool -flac_scan_file2(const char *file, const char *char_tnum, - const struct tag_handler *handler, void *handler_ctx) -{ - FLAC__Metadata_SimpleIterator *it; - FLAC__StreamMetadata *block = NULL; - - it = FLAC__metadata_simple_iterator_new(); - if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) { - const char *err; - FLAC_API FLAC__Metadata_SimpleIteratorStatus s; - - s = FLAC__metadata_simple_iterator_status(it); - - switch (s) { /* slightly more human-friendly messages: */ - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT: - err = "illegal input"; - break; - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE: - err = "error opening file"; - break; - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE: - err = "not a FLAC file"; - break; - default: - err = FLAC__Metadata_SimpleIteratorStatusString[s]; - } - g_debug("Reading '%s' metadata gave the following error: %s\n", - file, err); - FLAC__metadata_simple_iterator_delete(it); - return false; - } - - do { - block = FLAC__metadata_simple_iterator_get_block(it); - if (!block) - break; - - flac_scan_metadata(char_tnum, block, handler, handler_ctx); - FLAC__metadata_object_delete(block); - } while (FLAC__metadata_simple_iterator_next(it)); - - FLAC__metadata_simple_iterator_delete(it); - - return true; -} - -struct tag * -flac_tag_load(const char *file, const char *char_tnum) -{ - struct tag *tag = tag_new(); - - if (!flac_scan_file2(file, char_tnum, &add_tag_handler, tag) || - tag_is_empty(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; -} diff --git a/src/decoder/flac_metadata.h b/src/decoder/flac_metadata.h deleted file mode 100644 index 3c463d5d6..000000000 --- a/src/decoder/flac_metadata.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FLAC_METADATA_H -#define MPD_FLAC_METADATA_H - -#include <assert.h> -#include <stdbool.h> -#include <FLAC/metadata.h> - -struct tag_handler; -struct tag; -struct replay_gain_info; - -static inline unsigned -flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info) -{ - assert(stream_info->sample_rate > 0); - - return (stream_info->total_samples + stream_info->sample_rate - 1) / - stream_info->sample_rate; -} - -bool -flac_parse_replay_gain(struct replay_gain_info *rgi, - const FLAC__StreamMetadata *block); - -bool -flac_parse_mixramp(char **mixramp_start, char **mixramp_end, - const FLAC__StreamMetadata *block); - -void -flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment *comment); - -void -flac_scan_metadata(const char *track, - const FLAC__StreamMetadata *block, - const struct tag_handler *handler, void *handler_ctx); - -bool -flac_scan_file2(const char *file, const char *char_tnum, - const struct tag_handler *handler, void *handler_ctx); - -struct tag * -flac_tag_load(const char *file, const char *char_tnum); - -#endif diff --git a/src/decoder/flac_pcm.c b/src/decoder/flac_pcm.c deleted file mode 100644 index 6964d8ac6..000000000 --- a/src/decoder/flac_pcm.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "flac_pcm.h" - -#include <assert.h> - -static void flac_convert_stereo16(int16_t *dest, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - for (; position < end; ++position) { - *dest++ = buf[0][position]; - *dest++ = buf[1][position]; - } -} - -static void -flac_convert_16(int16_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -/** - * Note: this function also handles 24 bit files! - */ -static void -flac_convert_32(int32_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -static void -flac_convert_8(int8_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -void -flac_convert(void *dest, - unsigned int num_channels, enum sample_format sample_format, - const FLAC__int32 *const buf[], - unsigned int position, unsigned int end) -{ - switch (sample_format) { - case SAMPLE_FORMAT_S16: - if (num_channels == 2) - flac_convert_stereo16((int16_t*)dest, buf, - position, end); - else - flac_convert_16((int16_t*)dest, num_channels, buf, - position, end); - break; - - case SAMPLE_FORMAT_S24_P32: - case SAMPLE_FORMAT_S32: - flac_convert_32((int32_t*)dest, num_channels, buf, - position, end); - break; - - case SAMPLE_FORMAT_S8: - flac_convert_8((int8_t*)dest, num_channels, buf, - position, end); - break; - - case SAMPLE_FORMAT_FLOAT: - case SAMPLE_FORMAT_DSD: - case SAMPLE_FORMAT_UNDEFINED: - /* unreachable */ - assert(false); - } -} diff --git a/src/decoder/flac_pcm.h b/src/decoder/flac_pcm.h deleted file mode 100644 index a931998c1..000000000 --- a/src/decoder/flac_pcm.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FLAC_PCM_H -#define MPD_FLAC_PCM_H - -#include "audio_format.h" - -#include <FLAC/ordinals.h> - -void -flac_convert(void *dest, - unsigned int num_channels, enum sample_format sample_format, - const FLAC__int32 *const buf[], - unsigned int position, unsigned int end); - -#endif diff --git a/src/decoder/mad_decoder_plugin.c b/src/decoder/mad_decoder_plugin.c index 62c371642..4e2e03311 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 21ee79e7e..bc3d81767 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 ca78a22d0..000000000 --- a/src/decoder/mp4ff_decoder_plugin.c +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" -#include "tag_table.h" -#include "tag_handler.h" - -#include <glib.h> - -#include <mp4ff.h> -#include <faad.h> - -#include <assert.h> -#include <stdlib.h> -#include <unistd.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mp4ff" - -/* all code here is either based on or copied from FAAD2's frontend code */ - -struct mp4ff_input_stream { - mp4ff_callback_t callback; - - struct decoder *decoder; - struct input_stream *input_stream; -}; - -static int -mp4_get_aac_track(mp4ff_t * infile, faacDecHandle decoder, - uint32_t *sample_rate, unsigned char *channels_r) -{ -#ifdef HAVE_FAAD_LONG - /* neaacdec.h declares all arguments as "unsigned long", but - internally expects uint32_t pointers. To avoid gcc - warnings, use this workaround. */ - unsigned long *sample_rate_r = (unsigned long*)sample_rate; -#else - uint32_t *sample_rate_r = sample_rate; -#endif - int i, rc; - int num_tracks = mp4ff_total_tracks(infile); - - for (i = 0; i < num_tracks; i++) { - unsigned char *buff = NULL; - unsigned int buff_size = 0; - - if (mp4ff_get_track_type(infile, i) != 1) - /* not an audio track */ - continue; - - if (decoder == NULL) - /* have don't have a decoder to initialize - - we're done now, because we found an audio - track */ - return i; - - mp4ff_get_decoder_config(infile, i, &buff, &buff_size); - if (buff == NULL) - continue; - - rc = faacDecInit2(decoder, buff, buff_size, - sample_rate_r, channels_r); - free(buff); - - if (rc >= 0) - /* found a valid AAC track */ - return i; - } - - /* can't decode this */ - return -1; -} - -static uint32_t -mp4_read(void *user_data, void *buffer, uint32_t length) -{ - struct mp4ff_input_stream *mis = user_data; - - if (length == 0) - /* libmp4ff is known to attempt to read 0 bytes - make - this a special case, because the input_stream API - would not allow this */ - return 0; - - return decoder_read(mis->decoder, mis->input_stream, buffer, length); -} - -static uint32_t -mp4_seek(void *user_data, uint64_t position) -{ - struct mp4ff_input_stream *mis = user_data; - - return input_stream_lock_seek(mis->input_stream, position, SEEK_SET, - NULL) - ? 0 : -1; -} - -static const mp4ff_callback_t mpd_mp4ff_callback = { - .read = mp4_read, - .seek = mp4_seek, -}; - -static mp4ff_t * -mp4ff_input_stream_open(struct mp4ff_input_stream *mis, - struct decoder *decoder, - struct input_stream *input_stream) -{ - mis->callback = mpd_mp4ff_callback; - mis->callback.user_data = mis; - mis->decoder = decoder; - mis->input_stream = input_stream; - - return mp4ff_open_read(&mis->callback); -} - -static faacDecHandle -mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format) -{ - faacDecHandle decoder; - faacDecConfigurationPtr config; - int track; - uint32_t sample_rate; - unsigned char channels; - GError *error = NULL; - - decoder = faacDecOpen(); - - config = faacDecGetCurrentConfiguration(decoder); - config->outputFormat = FAAD_FMT_16BIT; -#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX - config->downMatrix = 1; -#endif -#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR - config->dontUpSampleImplicitSBR = 0; -#endif - faacDecSetConfiguration(decoder, config); - - track = mp4_get_aac_track(mp4fh, decoder, &sample_rate, &channels); - if (track < 0) { - g_warning("No AAC track found"); - faacDecClose(decoder); - return NULL; - } - - if (!audio_format_init_checked(audio_format, sample_rate, - SAMPLE_FORMAT_S16, channels, - &error)) { - g_warning("%s", error->message); - g_error_free(error); - faacDecClose(decoder); - return NULL; - } - - *track_r = track; - - return decoder; -} - -static void -mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) -{ - struct mp4ff_input_stream mis; - mp4ff_t *mp4fh; - int32_t track; - float file_time, total_time; - int32_t scale; - faacDecHandle decoder; - struct audio_format audio_format; - faacDecFrameInfo frame_info; - unsigned char *mp4_buffer; - unsigned int mp4_buffer_size; - long sample_id; - long num_samples; - long dur; - unsigned int sample_count; - char *sample_buffer; - size_t sample_buffer_length; - unsigned int initial = 1; - float *seek_table; - long seek_table_end = -1; - bool seek_position_found = false; - long offset; - uint16_t bit_rate = 0; - bool seeking = false; - double seek_where = 0; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - mp4fh = mp4ff_input_stream_open(&mis, mpd_decoder, input_stream); - if (!mp4fh) { - g_warning("Input does not appear to be a mp4 stream.\n"); - return; - } - - decoder = mp4_faad_new(mp4fh, &track, &audio_format); - if (decoder == NULL) { - mp4ff_close(mp4fh); - return; - } - - file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); - scale = mp4ff_time_scale(mp4fh, track); - - if (scale < 0) { - g_warning("Error getting audio format of mp4 AAC track.\n"); - faacDecClose(decoder); - mp4ff_close(mp4fh); - return; - } - total_time = ((float)file_time) / scale; - - num_samples = mp4ff_num_samples(mp4fh, track); - if (num_samples > (long)(G_MAXINT / sizeof(float))) { - g_warning("Integer overflow.\n"); - faacDecClose(decoder); - mp4ff_close(mp4fh); - return; - } - - file_time = 0.0; - - seek_table = input_stream->seekable - ? g_malloc(sizeof(float) * num_samples) - : NULL; - - decoder_initialized(mpd_decoder, &audio_format, - input_stream->seekable, - total_time); - - for (sample_id = 0; - sample_id < num_samples && cmd != DECODE_COMMAND_STOP; - sample_id++) { - if (cmd == DECODE_COMMAND_SEEK) { - assert(seek_table != NULL); - - seeking = true; - seek_where = decoder_seek_where(mpd_decoder); - } - - if (seeking && seek_table_end > 1 && - seek_table[seek_table_end] >= seek_where) { - int i = 2; - - assert(seek_table != NULL); - - while (seek_table[i] < seek_where) - i++; - sample_id = i - 1; - file_time = seek_table[sample_id]; - } - - dur = mp4ff_get_sample_duration(mp4fh, track, sample_id); - offset = mp4ff_get_sample_offset(mp4fh, track, sample_id); - - if (seek_table != NULL && sample_id > seek_table_end) { - seek_table[sample_id] = file_time; - seek_table_end = sample_id; - } - - if (sample_id == 0) - dur = 0; - if (offset > dur) - dur = 0; - else - dur -= offset; - file_time += ((float)dur) / scale; - - if (seeking && file_time >= seek_where) - seek_position_found = true; - - if (seeking && seek_position_found) { - seek_position_found = false; - seeking = 0; - decoder_command_finished(mpd_decoder); - } - - if (seeking) - continue; - - if (mp4ff_read_sample(mp4fh, track, sample_id, &mp4_buffer, - &mp4_buffer_size) == 0) - break; - -#ifdef HAVE_FAAD_BUFLEN_FUNCS - sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer, - mp4_buffer_size); -#else - sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer); -#endif - - free(mp4_buffer); - - if (frame_info.error > 0) { - g_warning("faad2 error: %s\n", - faacDecGetErrorMessage(frame_info.error)); - break; - } - - if (frame_info.channels != audio_format.channels) { - g_warning("channel count changed from %u to %u", - audio_format.channels, frame_info.channels); - break; - } - -#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE - if (frame_info.samplerate != audio_format.sample_rate) { - g_warning("sample rate changed from %u to %lu", - audio_format.sample_rate, - (unsigned long)frame_info.samplerate); - break; - } -#endif - - if (audio_format.channels * (unsigned long)(dur + offset) > frame_info.samples) { - dur = frame_info.samples / audio_format.channels; - offset = 0; - } - - sample_count = (unsigned long)(dur * audio_format.channels); - - if (sample_count > 0) { - initial = 0; - bit_rate = frame_info.bytesconsumed * 8.0 * - frame_info.channels * scale / - frame_info.samples / 1000 + 0.5; - } - - sample_buffer_length = sample_count * 2; - - sample_buffer += offset * audio_format.channels * 2; - - cmd = decoder_data(mpd_decoder, input_stream, - sample_buffer, sample_buffer_length, - bit_rate); - } - - g_free(seek_table); - faacDecClose(decoder); - mp4ff_close(mp4fh); -} - -static const struct tag_table mp4ff_tags[] = { - { "album artist", TAG_ALBUM_ARTIST }, - { "writer", TAG_COMPOSER }, - { "band", TAG_PERFORMER }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - -static enum tag_type -mp4ff_tag_name_parse(const char *name) -{ - enum tag_type type = tag_table_lookup_i(mp4ff_tags, name); - if (type == TAG_NUM_OF_ITEM_TYPES) - type = tag_name_parse_i(name); - - if (g_ascii_strcasecmp(name, "albumartist") == 0 || - g_ascii_strcasecmp(name, "album_artist") == 0) - return TAG_ALBUM_ARTIST; - - return type; -} - -static bool -mp4ff_scan_stream(struct input_stream *is, - const struct tag_handler *handler, void *handler_ctx) -{ - struct mp4ff_input_stream mis; - int32_t track; - int32_t file_time; - int32_t scale; - int i; - - mp4ff_t *mp4fh = mp4ff_input_stream_open(&mis, NULL, is); - if (mp4fh == NULL) - return false; - - track = mp4_get_aac_track(mp4fh, NULL, NULL, NULL); - if (track < 0) { - mp4ff_close(mp4fh); - return false; - } - - file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); - scale = mp4ff_time_scale(mp4fh, track); - if (scale < 0) { - mp4ff_close(mp4fh); - return false; - } - - tag_handler_invoke_duration(handler, handler_ctx, - ((float)file_time) / scale + 0.5); - - for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) { - char *item; - char *value; - - mp4ff_meta_get_by_index(mp4fh, i, &item, &value); - - tag_handler_invoke_pair(handler, handler_ctx, item, value); - - enum tag_type type = mp4ff_tag_name_parse(item); - if (type != TAG_NUM_OF_ITEM_TYPES) - tag_handler_invoke_tag(handler, handler_ctx, - type, value); - - free(item); - free(value); - } - - mp4ff_close(mp4fh); - - return true; -} - -static const char *const mp4_suffixes[] = { - "m4a", - "m4b", - "mp4", - NULL -}; - -static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL }; - -const struct decoder_plugin mp4ff_decoder_plugin = { - .name = "mp4ff", - .stream_decode = mp4_decode, - .scan_stream = mp4ff_scan_stream, - .suffixes = mp4_suffixes, - .mime_types = mp4_mime_types, -}; diff --git a/src/decoder/mpcdec_decoder_plugin.c b/src/decoder/mpcdec_decoder_plugin.c index d4768b35b..77db2416b 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 fc7dffc05..d529cef5c 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 5d162f179..175d2ee7c 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 8dd98236f..e70a2dc2e 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/vorbis_comments.c b/src/decoder/vorbis_comments.c deleted file mode 100644 index 6c2d57b72..000000000 --- a/src/decoder/vorbis_comments.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "vorbis_comments.h" -#include "tag.h" -#include "tag_table.h" -#include "tag_handler.h" -#include "replay_gain_info.h" - -#include <glib.h> -#include <assert.h> -#include <stddef.h> -#include <string.h> -#include <stdlib.h> - -static const char * -vorbis_comment_value(const char *comment, const char *needle) -{ - size_t len = strlen(needle); - - if (g_ascii_strncasecmp(comment, needle, len) == 0 && - comment[len] == '=') - return comment + len + 1; - - return NULL; -} - -bool -vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments) -{ - const char *temp; - bool found = false; - - replay_gain_info_init(rgi); - - while (*comments) { - if ((temp = - vorbis_comment_value(*comments, "replaygain_track_gain"))) { - rgi->tuples[REPLAY_GAIN_TRACK].gain = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_album_gain"))) { - rgi->tuples[REPLAY_GAIN_ALBUM].gain = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_track_peak"))) { - rgi->tuples[REPLAY_GAIN_TRACK].peak = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_album_peak"))) { - rgi->tuples[REPLAY_GAIN_ALBUM].peak = atof(temp); - found = true; - } - - comments++; - } - - return found; -} - -/** - * Check if the comment's name equals the passed name, and if so, copy - * the comment value into the tag. - */ -static bool -vorbis_copy_comment(const char *comment, - const char *name, enum tag_type tag_type, - const struct tag_handler *handler, void *handler_ctx) -{ - const char *value; - - value = vorbis_comment_value(comment, name); - if (value != NULL) { - tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); - return true; - } - - return false; -} - -static const struct tag_table vorbis_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - -static void -vorbis_scan_comment(const char *comment, - const struct tag_handler *handler, void *handler_ctx) -{ - if (handler->pair != NULL) { - char *name = g_strdup((const char*)comment); - char *value = strchr(name, '='); - - if (value != NULL && value > name) { - *value++ = 0; - tag_handler_invoke_pair(handler, handler_ctx, - name, value); - } - - g_free(name); - } - - for (const struct tag_table *i = vorbis_tags; i->name != NULL; ++i) - if (vorbis_copy_comment(comment, i->name, i->type, - handler, handler_ctx)) - return; - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (vorbis_copy_comment(comment, - tag_item_names[i], i, - handler, handler_ctx)) - return; -} - -void -vorbis_comments_scan(char **comments, - const struct tag_handler *handler, void *handler_ctx) -{ - while (*comments) - vorbis_scan_comment(*comments++, - handler, handler_ctx); - -} - -struct tag * -vorbis_comments_to_tag(char **comments) -{ - struct tag *tag = tag_new(); - vorbis_comments_scan(comments, &add_tag_handler, tag); - - if (tag_is_empty(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; -} diff --git a/src/decoder/vorbis_comments.h b/src/decoder/vorbis_comments.h deleted file mode 100644 index c15096930..000000000 --- a/src/decoder/vorbis_comments.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_VORBIS_COMMENTS_H -#define MPD_VORBIS_COMMENTS_H - -#include "check.h" - -#include <stdbool.h> - -struct replay_gain_info; -struct tag_handler; - -bool -vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments); - -void -vorbis_comments_scan(char **comments, - const struct tag_handler *handler, void *handler_ctx); - -struct tag * -vorbis_comments_to_tag(char **comments); - -#endif diff --git a/src/decoder/vorbis_decoder_plugin.c b/src/decoder/vorbis_decoder_plugin.c deleted file mode 100644 index 15cdc0ca9..000000000 --- a/src/decoder/vorbis_decoder_plugin.c +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "vorbis_comments.h" -#include "_ogg_common.h" -#include "audio_check.h" -#include "uri.h" -#include "tag_handler.h" - -#ifndef HAVE_TREMOR -#define OV_EXCLUDE_STATIC_CALLBACKS -#include <vorbis/vorbisfile.h> -#else -#include <tremor/ivorbisfile.h> -/* Macros to make Tremor's API look like libogg. Tremor always - returns host-byte-order 16-bit signed data, and uses integer - milliseconds where libogg uses double seconds. -*/ -#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ - ov_read(VF, BUFFER, LENGTH, BITSTREAM) -#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000) -#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000) -#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000)) -#endif /* HAVE_TREMOR */ - -#include <glib.h> - -#include <assert.h> -#include <errno.h> -#include <unistd.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "vorbis" -#define OGG_CHUNK_SIZE 4096 - -#if G_BYTE_ORDER == G_BIG_ENDIAN -#define OGG_DECODE_USE_BIGENDIAN 1 -#else -#define OGG_DECODE_USE_BIGENDIAN 0 -#endif - -struct vorbis_input_stream { - struct decoder *decoder; - - struct input_stream *input_stream; - bool seekable; -}; - -static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) -{ - struct vorbis_input_stream *vis = data; - size_t ret; - - ret = decoder_read(vis->decoder, vis->input_stream, ptr, size * nmemb); - - errno = 0; - - return ret / size; -} - -static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence) -{ - struct vorbis_input_stream *vis = data; - - return vis->seekable && - (!vis->decoder || decoder_get_command(vis->decoder) != DECODE_COMMAND_STOP) && - input_stream_lock_seek(vis->input_stream, offset, whence, NULL) - ? 0 : -1; -} - -/* TODO: check Ogg libraries API and see if we can just not have this func */ -static int ogg_close_cb(G_GNUC_UNUSED void *data) -{ - return 0; -} - -static long ogg_tell_cb(void *data) -{ - const struct vorbis_input_stream *vis = data; - - return (long)vis->input_stream->offset; -} - -static const ov_callbacks vorbis_is_callbacks = { - .read_func = ogg_read_cb, - .seek_func = ogg_seek_cb, - .close_func = ogg_close_cb, - .tell_func = ogg_tell_cb, -}; - -static const char * -vorbis_strerror(int code) -{ - switch (code) { - case OV_EREAD: - return "read error"; - - case OV_ENOTVORBIS: - return "not vorbis stream"; - - case OV_EVERSION: - return "vorbis version mismatch"; - - case OV_EBADHEADER: - return "invalid vorbis header"; - - case OV_EFAULT: - return "internal logic error"; - - default: - return "unknown error"; - } -} - -static bool -vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf, - struct decoder *decoder, struct input_stream *input_stream) -{ - vis->decoder = decoder; - vis->input_stream = input_stream; - vis->seekable = input_stream->seekable && - (input_stream->uri == NULL || - !uri_has_scheme(input_stream->uri)); - - int ret = ov_open_callbacks(vis, vf, NULL, 0, vorbis_is_callbacks); - if (ret < 0) { - if (decoder == NULL || - decoder_get_command(decoder) == DECODE_COMMAND_NONE) - g_warning("Failed to open Ogg Vorbis stream: %s", - vorbis_strerror(ret)); - return false; - } - - return true; -} - -static void -vorbis_send_comments(struct decoder *decoder, struct input_stream *is, - char **comments) -{ - struct tag *tag; - - tag = vorbis_comments_to_tag(comments); - if (!tag) - return; - - decoder_tag(decoder, is, tag); - tag_free(tag); -} - -/* public */ -static void -vorbis_stream_decode(struct decoder *decoder, - struct input_stream *input_stream) -{ - GError *error = NULL; - OggVorbis_File vf; - struct vorbis_input_stream vis; - struct audio_format audio_format; - float total_time; - int current_section; - int prev_section = -1; - long ret; - char chunk[OGG_CHUNK_SIZE]; - long bitRate = 0; - long test; - const vorbis_info *vi; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - if (ogg_stream_type_detect(input_stream) != VORBIS) - return; - - /* rewind the stream, because ogg_stream_type_detect() has - moved it */ - input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); - - if (!vorbis_is_open(&vis, &vf, decoder, input_stream)) - return; - - vi = ov_info(&vf, -1); - if (vi == NULL) { - g_warning("ov_info() has failed"); - return; - } - - if (!audio_format_init_checked(&audio_format, vi->rate, - SAMPLE_FORMAT_S16, - vi->channels, &error)) { - g_warning("%s", error->message); - g_error_free(error); - return; - } - - total_time = ov_time_total(&vf, -1); - if (total_time < 0) - total_time = 0; - - decoder_initialized(decoder, &audio_format, vis.seekable, total_time); - - do { - if (cmd == DECODE_COMMAND_SEEK) { - double seek_where = decoder_seek_where(decoder); - if (0 == ov_time_seek_page(&vf, seek_where)) { - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } - - ret = ov_read(&vf, chunk, sizeof(chunk), - OGG_DECODE_USE_BIGENDIAN, 2, 1, ¤t_section); - if (ret == OV_HOLE) /* bad packet */ - ret = 0; - else if (ret <= 0) - /* break on EOF or other error */ - break; - - if (current_section != prev_section) { - char **comments; - - vi = ov_info(&vf, -1); - if (vi == NULL) { - g_warning("ov_info() has failed"); - break; - } - - if (vi->rate != (long)audio_format.sample_rate || - vi->channels != (int)audio_format.channels) { - /* we don't support audio format - change yet */ - g_warning("audio format change, stopping here"); - break; - } - - comments = ov_comment(&vf, -1)->user_comments; - vorbis_send_comments(decoder, input_stream, comments); - - struct replay_gain_info rgi; - if (vorbis_comments_to_replay_gain(&rgi, comments)) - decoder_replay_gain(decoder, &rgi); - - prev_section = current_section; - } - - if ((test = ov_bitrate_instant(&vf)) > 0) - bitRate = test / 1000; - - cmd = decoder_data(decoder, input_stream, - chunk, ret, - bitRate); - } while (cmd != DECODE_COMMAND_STOP); - - ov_clear(&vf); -} - -static bool -vorbis_scan_stream(struct input_stream *is, - const struct tag_handler *handler, void *handler_ctx) -{ - struct vorbis_input_stream vis; - OggVorbis_File vf; - - if (!vorbis_is_open(&vis, &vf, NULL, is)) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, - (int)(ov_time_total(&vf, -1) + 0.5)); - - vorbis_comments_scan(ov_comment(&vf, -1)->user_comments, - handler, handler_ctx); - - ov_clear(&vf); - return true; -} - -static const char *const vorbis_suffixes[] = { - "ogg", "oga", NULL -}; - -static const char *const vorbis_mime_types[] = { - "application/ogg", - "application/x-ogg", - "audio/ogg", - "audio/vorbis", - "audio/vorbis+ogg", - "audio/x-ogg", - "audio/x-vorbis", - "audio/x-vorbis+ogg", - NULL -}; - -const struct decoder_plugin vorbis_decoder_plugin = { - .name = "vorbis", - .stream_decode = vorbis_stream_decode, - .scan_stream = vorbis_scan_stream, - .suffixes = vorbis_suffixes, - .mime_types = vorbis_mime_types -}; diff --git a/src/decoder/wavpack_decoder_plugin.c b/src/decoder/wavpack_decoder_plugin.c deleted file mode 100644 index 9ebd0fccc..000000000 --- a/src/decoder/wavpack_decoder_plugin.c +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" -#include "path.h" -#include "utils.h" -#include "tag_table.h" -#include "tag_handler.h" -#include "tag_ape.h" - -#include <wavpack/wavpack.h> -#include <glib.h> - -#include <assert.h> -#include <unistd.h> -#include <stdio.h> -#include <stdlib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "wavpack" - -#define ERRORLEN 80 - -/** A pointer type for format converter function. */ -typedef void (*format_samples_t)( - int bytes_per_sample, - void *buffer, uint32_t count -); - -/* - * This function has been borrowed from the tiny player found on - * wavpack.com. Modifications were required because mpd only handles - * max 24-bit samples. - */ -static void -format_samples_int(int bytes_per_sample, void *buffer, uint32_t count) -{ - int32_t *src = buffer; - - switch (bytes_per_sample) { - case 1: { - int8_t *dst = buffer; - /* - * The asserts like the following one are because we do the - * formatting of samples within a single buffer. The size - * of the output samples never can be greater than the size - * of the input ones. Otherwise we would have an overflow. - */ - assert_static(sizeof(*dst) <= sizeof(*src)); - - /* pass through and align 8-bit samples */ - while (count--) { - *dst++ = *src++; - } - break; - } - case 2: { - uint16_t *dst = buffer; - assert_static(sizeof(*dst) <= sizeof(*src)); - - /* pass through and align 16-bit samples */ - while (count--) { - *dst++ = *src++; - } - break; - } - - case 3: - case 4: - /* do nothing */ - break; - } -} - -/* - * This function converts floating point sample data to 24-bit integer. - */ -static void -format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer, - uint32_t count) -{ - float *p = buffer; - - while (count--) { - *p /= (1 << 23); - ++p; - } -} - -/** - * Choose a MPD sample format from libwavpacks' number of bits. - */ -static enum sample_format -wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample) -{ - if (is_float) - return SAMPLE_FORMAT_FLOAT; - - switch (bytes_per_sample) { - case 1: - return SAMPLE_FORMAT_S8; - - case 2: - return SAMPLE_FORMAT_S16; - - case 3: - return SAMPLE_FORMAT_S24_P32; - - case 4: - return SAMPLE_FORMAT_S32; - - default: - return SAMPLE_FORMAT_UNDEFINED; - } -} - -/* - * This does the main decoding thing. - * Requires an already opened WavpackContext. - */ -static void -wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek) -{ - GError *error = NULL; - bool is_float; - enum sample_format sample_format; - struct audio_format audio_format; - format_samples_t format_samples; - float total_time; - int bytes_per_sample, output_sample_size; - - is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0; - sample_format = - wavpack_bits_to_sample_format(is_float, - WavpackGetBytesPerSample(wpc)); - - if (!audio_format_init_checked(&audio_format, - WavpackGetSampleRate(wpc), - sample_format, - WavpackGetNumChannels(wpc), &error)) { - g_warning("%s", error->message); - g_error_free(error); - return; - } - - if (is_float) { - format_samples = format_samples_float; - } else { - format_samples = format_samples_int; - } - - total_time = WavpackGetNumSamples(wpc); - total_time /= audio_format.sample_rate; - bytes_per_sample = WavpackGetBytesPerSample(wpc); - output_sample_size = audio_format_frame_size(&audio_format); - - /* wavpack gives us all kind of samples in a 32-bit space */ - int32_t chunk[1024]; - const uint32_t samples_requested = G_N_ELEMENTS(chunk) / - audio_format.channels; - - decoder_initialized(decoder, &audio_format, can_seek, total_time); - - enum decoder_command cmd = decoder_get_command(decoder); - while (cmd != DECODE_COMMAND_STOP) { - if (cmd == DECODE_COMMAND_SEEK) { - if (can_seek) { - unsigned where = decoder_seek_where(decoder) * - audio_format.sample_rate; - - if (WavpackSeekSample(wpc, where)) { - decoder_command_finished(decoder); - } else { - decoder_seek_error(decoder); - } - } else { - decoder_seek_error(decoder); - } - } - - uint32_t samples_got = WavpackUnpackSamples(wpc, chunk, - samples_requested); - if (samples_got == 0) - break; - - int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 + - 0.5); - format_samples(bytes_per_sample, chunk, - samples_got * audio_format.channels); - - cmd = decoder_data(decoder, NULL, chunk, - samples_got * output_sample_size, - bitrate); - } -} - -/** - * Locate and parse a floating point tag. Returns true if it was - * found. - */ -static bool -wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r) -{ - char buffer[64]; - int ret; - - ret = WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)); - if (ret <= 0) - return false; - - *value_r = atof(buffer); - return true; -} - -static bool -wavpack_replaygain(struct replay_gain_info *replay_gain_info, - WavpackContext *wpc) -{ - bool found = false; - - replay_gain_info_init(replay_gain_info); - - found |= wavpack_tag_float( - wpc, "replaygain_track_gain", - &replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain - ); - found |= wavpack_tag_float( - wpc, "replaygain_track_peak", - &replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak - ); - found |= wavpack_tag_float( - wpc, "replaygain_album_gain", - &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain - ); - found |= wavpack_tag_float( - wpc, "replaygain_album_peak", - &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak - ); - - return found; -} - -static void -wavpack_scan_tag_item(WavpackContext *wpc, const char *name, - enum tag_type type, - const struct tag_handler *handler, void *handler_ctx) -{ - char buffer[1024]; - int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); - if (len <= 0 || (unsigned)len >= sizeof(buffer)) - return; - - tag_handler_invoke_tag(handler, handler_ctx, type, buffer); - -} - -static void -wavpack_scan_pair(WavpackContext *wpc, const char *name, - const struct tag_handler *handler, void *handler_ctx) -{ - char buffer[8192]; - int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); - if (len <= 0 || (unsigned)len >= sizeof(buffer)) - return; - - tag_handler_invoke_pair(handler, handler_ctx, name, buffer); -} - -/* - * Reads metainfo from the specified file. - */ -static bool -wavpack_scan_file(const char *fname, - const struct tag_handler *handler, void *handler_ctx) -{ - WavpackContext *wpc; - char error[ERRORLEN]; - - wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0); - if (wpc == NULL) { - g_warning( - "failed to open WavPack file \"%s\": %s\n", - fname, error - ); - return false; - } - - tag_handler_invoke_duration(handler, handler_ctx, - WavpackGetNumSamples(wpc) / - WavpackGetSampleRate(wpc)); - - /* the WavPack format implies APEv2 tags, which means we can - reuse the mapping from tag_ape.c */ - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - const char *name = tag_item_names[i]; - if (name != NULL) - wavpack_scan_tag_item(wpc, name, (enum tag_type)i, - handler, handler_ctx); - } - - for (const struct tag_table *i = ape_tags; i->name != NULL; ++i) - wavpack_scan_tag_item(wpc, i->name, i->type, - handler, handler_ctx); - - if (handler->pair != NULL) { - char name[64]; - - for (int i = 0, n = WavpackGetNumTagItems(wpc); - i < n; ++i) { - int len = WavpackGetTagItemIndexed(wpc, i, name, - sizeof(name)); - if (len <= 0 || (unsigned)len >= sizeof(name)) - continue; - - wavpack_scan_pair(wpc, name, handler, handler_ctx); - } - } - - WavpackCloseFile(wpc); - - return true; -} - -/* - * mpd input_stream <=> WavpackStreamReader wrapper callbacks - */ - -/* This struct is needed for per-stream last_byte storage. */ -struct wavpack_input { - struct decoder *decoder; - struct input_stream *is; - /* Needed for push_back_byte() */ - int last_byte; -}; - -/** - * Little wrapper for struct wavpack_input to cast from void *. - */ -static struct wavpack_input * -wpin(void *id) -{ - assert(id); - return id; -} - -static int32_t -wavpack_input_read_bytes(void *id, void *data, int32_t bcount) -{ - uint8_t *buf = (uint8_t *)data; - int32_t i = 0; - - if (wpin(id)->last_byte != EOF) { - *buf++ = wpin(id)->last_byte; - wpin(id)->last_byte = EOF; - --bcount; - ++i; - } - - /* wavpack fails if we return a partial read, so we just wait - until the buffer is full */ - while (bcount > 0) { - size_t nbytes = decoder_read( - wpin(id)->decoder, wpin(id)->is, buf, bcount - ); - if (nbytes == 0) { - /* EOF, error or a decoder command */ - break; - } - - i += nbytes; - bcount -= nbytes; - buf += nbytes; - } - - return i; -} - -static uint32_t -wavpack_input_get_pos(void *id) -{ - return wpin(id)->is->offset; -} - -static int -wavpack_input_set_pos_abs(void *id, uint32_t pos) -{ - return input_stream_lock_seek(wpin(id)->is, pos, SEEK_SET, NULL) - ? 0 : -1; -} - -static int -wavpack_input_set_pos_rel(void *id, int32_t delta, int mode) -{ - return input_stream_lock_seek(wpin(id)->is, delta, mode, NULL) - ? 0 : -1; -} - -static int -wavpack_input_push_back_byte(void *id, int c) -{ - if (wpin(id)->last_byte == EOF) { - wpin(id)->last_byte = c; - return c; - } else { - return EOF; - } -} - -static uint32_t -wavpack_input_get_length(void *id) -{ - if (wpin(id)->is->size < 0) - return 0; - - return wpin(id)->is->size; -} - -static int -wavpack_input_can_seek(void *id) -{ - return wpin(id)->is->seekable; -} - -static WavpackStreamReader mpd_is_reader = { - .read_bytes = wavpack_input_read_bytes, - .get_pos = wavpack_input_get_pos, - .set_pos_abs = wavpack_input_set_pos_abs, - .set_pos_rel = wavpack_input_set_pos_rel, - .push_back_byte = wavpack_input_push_back_byte, - .get_length = wavpack_input_get_length, - .can_seek = wavpack_input_can_seek, - .write_bytes = NULL /* no need to write edited tags */ -}; - -static void -wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder, - struct input_stream *is) -{ - isp->decoder = decoder; - isp->is = is; - isp->last_byte = EOF; -} - -static struct input_stream * -wavpack_open_wvc(struct decoder *decoder, const char *uri, - GMutex *mutex, GCond *cond, - struct wavpack_input *wpi) -{ - struct input_stream *is_wvc; - char *wvc_url = NULL; - char first_byte; - size_t nbytes; - - /* - * As we use dc->utf8url, this function will be bad for - * single files. utf8url is not absolute file path :/ - */ - if (uri == NULL) - return false; - - wvc_url = g_strconcat(uri, "c", NULL); - is_wvc = input_stream_open(wvc_url, mutex, cond, NULL); - g_free(wvc_url); - - if (is_wvc == NULL) - return NULL; - - /* - * And we try to buffer in order to get know - * about a possible 404 error. - */ - nbytes = decoder_read( - decoder, is_wvc, &first_byte, sizeof(first_byte) - ); - if (nbytes == 0) { - input_stream_close(is_wvc); - return NULL; - } - - /* push it back */ - wavpack_input_init(wpi, decoder, is_wvc); - wpi->last_byte = first_byte; - return is_wvc; -} - -/* - * Decodes a stream. - */ -static void -wavpack_streamdecode(struct decoder * decoder, struct input_stream *is) -{ - char error[ERRORLEN]; - WavpackContext *wpc; - struct input_stream *is_wvc; - int open_flags = OPEN_NORMALIZE; - struct wavpack_input isp, isp_wvc; - bool can_seek = is->seekable; - - is_wvc = wavpack_open_wvc(decoder, is->uri, is->mutex, is->cond, - &isp_wvc); - if (is_wvc != NULL) { - open_flags |= OPEN_WVC; - can_seek &= is_wvc->seekable; - } - - if (!can_seek) { - open_flags |= OPEN_STREAMING; - } - - wavpack_input_init(&isp, decoder, is); - wpc = WavpackOpenFileInputEx( - &mpd_is_reader, &isp, - open_flags & OPEN_WVC ? &isp_wvc : NULL, - error, open_flags, 23 - ); - - if (wpc == NULL) { - g_warning("failed to open WavPack stream: %s\n", error); - return; - } - - wavpack_decode(decoder, wpc, can_seek); - - WavpackCloseFile(wpc); - if (open_flags & OPEN_WVC) { - input_stream_close(is_wvc); - } -} - -/* - * Decodes a file. - */ -static void -wavpack_filedecode(struct decoder *decoder, const char *fname) -{ - char error[ERRORLEN]; - WavpackContext *wpc; - - wpc = WavpackOpenFileInput( - fname, error, - OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, 23 - ); - if (wpc == NULL) { - g_warning( - "failed to open WavPack file \"%s\": %s\n", - fname, error - ); - return; - } - - struct replay_gain_info replay_gain_info; - if (wavpack_replaygain(&replay_gain_info, wpc)) - decoder_replay_gain(decoder, &replay_gain_info); - - wavpack_decode(decoder, wpc, true); - - WavpackCloseFile(wpc); -} - -static char const *const wavpack_suffixes[] = { - "wv", - NULL -}; - -static char const *const wavpack_mime_types[] = { - "audio/x-wavpack", - NULL -}; - -const struct decoder_plugin wavpack_decoder_plugin = { - .name = "wavpack", - .stream_decode = wavpack_streamdecode, - .file_decode = wavpack_filedecode, - .scan_file = wavpack_scan_file, - .suffixes = wavpack_suffixes, - .mime_types = wavpack_mime_types -}; diff --git a/src/decoder_api.c b/src/decoder_api.c deleted file mode 100644 index a45d0f1e6..000000000 --- a/src/decoder_api.c +++ /dev/null @@ -1,567 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "decoder_internal.h" -#include "decoder_control.h" -#include "audio_config.h" -#include "song.h" -#include "buffer.h" -#include "pipe.h" -#include "chunk.h" -#include "replay_gain_config.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "decoder" - -void -decoder_initialized(struct decoder *decoder, - const struct audio_format *audio_format, - bool seekable, float total_time) -{ - struct decoder_control *dc = decoder->dc; - struct audio_format_string af_string; - - assert(dc->state == DECODE_STATE_START); - assert(dc->pipe != NULL); - assert(decoder != NULL); - assert(decoder->stream_tag == NULL); - assert(decoder->decoder_tag == NULL); - assert(!decoder->seeking); - assert(audio_format != NULL); - assert(audio_format_defined(audio_format)); - assert(audio_format_valid(audio_format)); - - dc->in_audio_format = *audio_format; - getOutputAudioFormat(audio_format, &dc->out_audio_format); - - dc->seekable = seekable; - dc->total_time = total_time; - - decoder_lock(dc); - dc->state = DECODE_STATE_DECODE; - g_cond_signal(dc->client_cond); - decoder_unlock(dc); - - g_debug("audio_format=%s, seekable=%s", - audio_format_to_string(&dc->in_audio_format, &af_string), - seekable ? "true" : "false"); - - if (!audio_format_equals(&dc->in_audio_format, - &dc->out_audio_format)) - g_debug("converting to %s", - audio_format_to_string(&dc->out_audio_format, - &af_string)); -} - -/** - * Checks if we need an "initial seek". If so, then the initial seek - * is prepared, and the function returns true. - */ -G_GNUC_PURE -static bool -decoder_prepare_initial_seek(struct decoder *decoder) -{ - const struct decoder_control *dc = decoder->dc; - assert(dc->pipe != NULL); - - if (dc->state != DECODE_STATE_DECODE) - /* wait until the decoder has finished initialisation - (reading file headers etc.) before emitting the - virtual "SEEK" command */ - return false; - - if (decoder->initial_seek_running) - /* initial seek has already begun - override any other - command */ - return true; - - if (decoder->initial_seek_pending) { - if (!dc->seekable) { - /* seeking is not possible */ - decoder->initial_seek_pending = false; - return false; - } - - if (dc->command == DECODE_COMMAND_NONE) { - /* begin initial seek */ - - decoder->initial_seek_pending = false; - decoder->initial_seek_running = true; - return true; - } - - /* skip initial seek when there's another command - (e.g. STOP) */ - - decoder->initial_seek_pending = false; - } - - return false; -} - -/** - * Returns the current decoder command. May return a "virtual" - * synthesized command, e.g. to seek to the beginning of the CUE - * track. - */ -G_GNUC_PURE -static enum decoder_command -decoder_get_virtual_command(struct decoder *decoder) -{ - const struct decoder_control *dc = decoder->dc; - assert(dc->pipe != NULL); - - if (decoder_prepare_initial_seek(decoder)) - return DECODE_COMMAND_SEEK; - - return dc->command; -} - -enum decoder_command -decoder_get_command(struct decoder *decoder) -{ - return decoder_get_virtual_command(decoder); -} - -void -decoder_command_finished(struct decoder *decoder) -{ - struct decoder_control *dc = decoder->dc; - - decoder_lock(dc); - - assert(dc->command != DECODE_COMMAND_NONE || - decoder->initial_seek_running); - assert(dc->command != DECODE_COMMAND_SEEK || - decoder->initial_seek_running || - dc->seek_error || decoder->seeking); - assert(dc->pipe != NULL); - - if (decoder->initial_seek_running) { - assert(!decoder->seeking); - assert(decoder->chunk == NULL); - assert(music_pipe_empty(dc->pipe)); - - decoder->initial_seek_running = false; - decoder->timestamp = dc->start_ms / 1000.; - decoder_unlock(dc); - return; - } - - if (decoder->seeking) { - decoder->seeking = false; - - /* delete frames from the old song position */ - - if (decoder->chunk != NULL) { - music_buffer_return(dc->buffer, decoder->chunk); - decoder->chunk = NULL; - } - - music_pipe_clear(dc->pipe, dc->buffer); - - decoder->timestamp = dc->seek_where; - } - - dc->command = DECODE_COMMAND_NONE; - g_cond_signal(dc->client_cond); - decoder_unlock(dc); -} - -double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder) -{ - const struct decoder_control *dc = decoder->dc; - - assert(dc->pipe != NULL); - - if (decoder->initial_seek_running) - return dc->start_ms / 1000.; - - assert(dc->command == DECODE_COMMAND_SEEK); - - decoder->seeking = true; - - return dc->seek_where; -} - -void decoder_seek_error(struct decoder * decoder) -{ - struct decoder_control *dc = decoder->dc; - - assert(dc->pipe != NULL); - - if (decoder->initial_seek_running) { - /* d'oh, we can't seek to the sub-song start position, - what now? - no idea, ignoring the problem for now. */ - decoder->initial_seek_running = false; - return; - } - - assert(dc->command == DECODE_COMMAND_SEEK); - - dc->seek_error = true; - decoder->seeking = false; - - decoder_command_finished(decoder); -} - -/** - * Should be read operation be cancelled? That is the case when the - * player thread has sent a command such as "STOP". - */ -G_GNUC_PURE -static inline bool -decoder_check_cancel_read(const struct decoder *decoder) -{ - if (decoder == NULL) - return false; - - const struct decoder_control *dc = decoder->dc; - if (dc->command == DECODE_COMMAND_NONE) - return false; - - /* ignore the SEEK command during initialization, the plugin - should handle that after it has initialized successfully */ - if (dc->command == DECODE_COMMAND_SEEK && - (dc->state == DECODE_STATE_START || decoder->seeking)) - return false; - - return true; -} - -size_t decoder_read(struct decoder *decoder, - struct input_stream *is, - void *buffer, size_t length) -{ - /* XXX don't allow decoder==NULL */ - GError *error = NULL; - size_t nbytes; - - assert(decoder == NULL || - decoder->dc->state == DECODE_STATE_START || - decoder->dc->state == DECODE_STATE_DECODE); - assert(is != NULL); - assert(buffer != NULL); - - if (length == 0) - return 0; - - input_stream_lock(is); - - while (true) { - if (decoder_check_cancel_read(decoder)) { - input_stream_unlock(is); - return 0; - } - - if (input_stream_available(is)) - break; - - g_cond_wait(is->cond, is->mutex); - } - - nbytes = input_stream_read(is, buffer, length, &error); - assert(nbytes == 0 || error == NULL); - assert(nbytes > 0 || error != NULL || input_stream_eof(is)); - - if (G_UNLIKELY(nbytes == 0 && error != NULL)) { - g_warning("%s", error->message); - g_error_free(error); - } - - input_stream_unlock(is); - - return nbytes; -} - -void -decoder_timestamp(struct decoder *decoder, double t) -{ - assert(decoder != NULL); - assert(t >= 0); - - decoder->timestamp = t; -} - -/** - * Sends a #tag as-is to the music pipe. Flushes the current chunk - * (decoder.chunk) if there is one. - */ -static enum decoder_command -do_send_tag(struct decoder *decoder, const struct tag *tag) -{ - struct music_chunk *chunk; - - if (decoder->chunk != NULL) { - /* there is a partial chunk - flush it, we want the - tag in a new chunk */ - decoder_flush_chunk(decoder); - g_cond_signal(decoder->dc->client_cond); - } - - assert(decoder->chunk == NULL); - - chunk = decoder_get_chunk(decoder); - if (chunk == NULL) { - assert(decoder->dc->command != DECODE_COMMAND_NONE); - return decoder->dc->command; - } - - chunk->tag = tag_dup(tag); - return DECODE_COMMAND_NONE; -} - -static bool -update_stream_tag(struct decoder *decoder, struct input_stream *is) -{ - struct tag *tag; - - tag = is != NULL - ? input_stream_lock_tag(is) - : NULL; - if (tag == NULL) { - tag = decoder->song_tag; - if (tag == NULL) - return false; - - /* no stream tag present - submit the song tag - instead */ - decoder->song_tag = NULL; - } - - if (decoder->stream_tag != NULL) - tag_free(decoder->stream_tag); - - decoder->stream_tag = tag; - return true; -} - -enum decoder_command -decoder_data(struct decoder *decoder, - struct input_stream *is, - const void *_data, size_t length, - uint16_t kbit_rate) -{ - struct decoder_control *dc = decoder->dc; - const char *data = _data; - GError *error = NULL; - enum decoder_command cmd; - - assert(dc->state == DECODE_STATE_DECODE); - assert(dc->pipe != NULL); - assert(length % audio_format_frame_size(&dc->in_audio_format) == 0); - - decoder_lock(dc); - cmd = decoder_get_virtual_command(decoder); - decoder_unlock(dc); - - if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK || - length == 0) - return cmd; - - /* send stream tags */ - - if (update_stream_tag(decoder, is)) { - if (decoder->decoder_tag != NULL) { - /* merge with tag from decoder plugin */ - struct tag *tag; - - tag = tag_merge(decoder->decoder_tag, - decoder->stream_tag); - cmd = do_send_tag(decoder, tag); - tag_free(tag); - } else - /* send only the stream tag */ - cmd = do_send_tag(decoder, decoder->stream_tag); - - if (cmd != DECODE_COMMAND_NONE) - return cmd; - } - - if (!audio_format_equals(&dc->in_audio_format, &dc->out_audio_format)) { - data = pcm_convert(&decoder->conv_state, - &dc->in_audio_format, data, length, - &dc->out_audio_format, &length, - &error); - if (data == NULL) { - /* the PCM conversion has failed - stop - playback, since we have no better way to - bail out */ - g_warning("%s", error->message); - return DECODE_COMMAND_STOP; - } - } - - while (length > 0) { - struct music_chunk *chunk; - char *dest; - size_t nbytes; - bool full; - - chunk = decoder_get_chunk(decoder); - if (chunk == NULL) { - assert(dc->command != DECODE_COMMAND_NONE); - return dc->command; - } - - dest = music_chunk_write(chunk, &dc->out_audio_format, - decoder->timestamp - - dc->song->start_ms / 1000.0, - kbit_rate, &nbytes); - if (dest == NULL) { - /* the chunk is full, flush it */ - decoder_flush_chunk(decoder); - g_cond_signal(dc->client_cond); - continue; - } - - assert(nbytes > 0); - - if (nbytes > length) - nbytes = length; - - /* copy the buffer */ - - memcpy(dest, data, nbytes); - - /* expand the music pipe chunk */ - - full = music_chunk_expand(chunk, &dc->out_audio_format, nbytes); - if (full) { - /* the chunk is full, flush it */ - decoder_flush_chunk(decoder); - g_cond_signal(dc->client_cond); - } - - data += nbytes; - length -= nbytes; - - decoder->timestamp += (double)nbytes / - audio_format_time_to_size(&dc->out_audio_format); - - if (dc->end_ms > 0 && - decoder->timestamp >= dc->end_ms / 1000.0) - /* the end of this range has been reached: - stop decoding */ - return DECODE_COMMAND_STOP; - } - - return DECODE_COMMAND_NONE; -} - -enum decoder_command -decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, - const struct tag *tag) -{ - G_GNUC_UNUSED const struct decoder_control *dc = decoder->dc; - enum decoder_command cmd; - - assert(dc->state == DECODE_STATE_DECODE); - assert(dc->pipe != NULL); - assert(tag != NULL); - - /* save the tag */ - - if (decoder->decoder_tag != NULL) - tag_free(decoder->decoder_tag); - decoder->decoder_tag = tag_dup(tag); - - /* check for a new stream tag */ - - update_stream_tag(decoder, is); - - /* check if we're seeking */ - - if (decoder_prepare_initial_seek(decoder)) - /* during initial seek, no music chunk must be created - until seeking is finished; skip the rest of the - function here */ - return DECODE_COMMAND_SEEK; - - /* send tag to music pipe */ - - if (decoder->stream_tag != NULL) { - /* merge with tag from input stream */ - struct tag *merged; - - merged = tag_merge(decoder->stream_tag, decoder->decoder_tag); - cmd = do_send_tag(decoder, merged); - tag_free(merged); - } else - /* send only the decoder tag */ - cmd = do_send_tag(decoder, tag); - - return cmd; -} - -float -decoder_replay_gain(struct decoder *decoder, - const struct replay_gain_info *replay_gain_info) -{ - float return_db = 0; - assert(decoder != NULL); - - if (replay_gain_info != NULL) { - static unsigned serial; - if (++serial == 0) - serial = 1; - - if (REPLAY_GAIN_OFF != replay_gain_mode) { - return_db = 20.0 * log10f( - replay_gain_tuple_scale( - &replay_gain_info->tuples[replay_gain_get_real_mode()], - replay_gain_preamp, replay_gain_missing_preamp, - replay_gain_limit)); - } - - decoder->replay_gain_info = *replay_gain_info; - decoder->replay_gain_serial = serial; - - if (decoder->chunk != NULL) { - /* flush the current chunk because the new - replay gain values affect the following - samples */ - decoder_flush_chunk(decoder); - g_cond_signal(decoder->dc->client_cond); - } - } else - decoder->replay_gain_serial = 0; - - return return_db; -} - -void -decoder_mixramp(struct decoder *decoder, float replay_gain_db, - char *mixramp_start, char *mixramp_end) -{ - assert(decoder != NULL); - struct decoder_control *dc = decoder->dc; - assert(dc != NULL); - - dc->replay_gain_db = replay_gain_db; - dc_mixramp_start(dc, mixramp_start); - dc_mixramp_end(dc, mixramp_end); -} diff --git a/src/decoder_api.h b/src/decoder_api.h index 6e011c395..3f84ca8bc 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 2ce03b666..000000000 --- a/src/decoder_control.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_control.h" -#include "pipe.h" - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "decoder_control" - -struct decoder_control * -dc_new(GCond *client_cond) -{ - struct decoder_control *dc = g_new(struct decoder_control, 1); - - dc->thread = NULL; - - dc->mutex = g_mutex_new(); - dc->cond = g_cond_new(); - dc->client_cond = client_cond; - - dc->state = DECODE_STATE_STOP; - dc->command = DECODE_COMMAND_NONE; - - dc->replay_gain_db = 0; - dc->replay_gain_prev_db = 0; - dc->mixramp_start = NULL; - dc->mixramp_end = NULL; - dc->mixramp_prev_end = NULL; - - return dc; -} - -void -dc_free(struct decoder_control *dc) -{ - g_cond_free(dc->cond); - g_mutex_free(dc->mutex); - g_free(dc->mixramp_start); - g_free(dc->mixramp_end); - g_free(dc->mixramp_prev_end); - g_free(dc); -} - -static void -dc_command_wait_locked(struct decoder_control *dc) -{ - while (dc->command != DECODE_COMMAND_NONE) - g_cond_wait(dc->client_cond, dc->mutex); -} - -static void -dc_command_locked(struct decoder_control *dc, enum decoder_command cmd) -{ - dc->command = cmd; - decoder_signal(dc); - dc_command_wait_locked(dc); -} - -static void -dc_command(struct decoder_control *dc, enum decoder_command cmd) -{ - decoder_lock(dc); - dc_command_locked(dc, cmd); - decoder_unlock(dc); -} - -static void -dc_command_async(struct decoder_control *dc, enum decoder_command cmd) -{ - decoder_lock(dc); - - dc->command = cmd; - decoder_signal(dc); - - decoder_unlock(dc); -} - -void -dc_start(struct decoder_control *dc, struct song *song, - unsigned start_ms, unsigned end_ms, - struct music_buffer *buffer, struct music_pipe *pipe) -{ - assert(song != NULL); - assert(buffer != NULL); - assert(pipe != NULL); - assert(music_pipe_empty(pipe)); - - dc->song = song; - dc->start_ms = start_ms; - dc->end_ms = end_ms; - dc->buffer = buffer; - dc->pipe = pipe; - dc_command(dc, DECODE_COMMAND_START); -} - -void -dc_stop(struct decoder_control *dc) -{ - decoder_lock(dc); - - if (dc->command != DECODE_COMMAND_NONE) - /* Attempt to cancel the current command. If it's too - late and the decoder thread is already executing - the old command, we'll call STOP again in this - function (see below). */ - dc_command_locked(dc, DECODE_COMMAND_STOP); - - if (dc->state != DECODE_STATE_STOP && dc->state != DECODE_STATE_ERROR) - dc_command_locked(dc, DECODE_COMMAND_STOP); - - decoder_unlock(dc); -} - -bool -dc_seek(struct decoder_control *dc, double where) -{ - assert(dc->state != DECODE_STATE_START); - assert(where >= 0.0); - - if (dc->state == DECODE_STATE_STOP || - dc->state == DECODE_STATE_ERROR || !dc->seekable) - return false; - - dc->seek_where = where; - dc->seek_error = false; - dc_command(dc, DECODE_COMMAND_SEEK); - - if (dc->seek_error) - return false; - - return true; -} - -void -dc_quit(struct decoder_control *dc) -{ - assert(dc->thread != NULL); - - dc->quit = true; - dc_command_async(dc, DECODE_COMMAND_STOP); - - g_thread_join(dc->thread); - dc->thread = NULL; -} - -void -dc_mixramp_start(struct decoder_control *dc, char *mixramp_start) -{ - assert(dc != NULL); - - g_free(dc->mixramp_start); - dc->mixramp_start = mixramp_start; -} - -void -dc_mixramp_end(struct decoder_control *dc, char *mixramp_end) -{ - assert(dc != NULL); - - g_free(dc->mixramp_end); - dc->mixramp_end = mixramp_end; -} - -void -dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end) -{ - assert(dc != NULL); - - g_free(dc->mixramp_prev_end); - dc->mixramp_prev_end = mixramp_prev_end; -} diff --git a/src/decoder_control.h b/src/decoder_control.h deleted file mode 100644 index 566b153ee..000000000 --- a/src/decoder_control.h +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_CONTROL_H -#define MPD_DECODER_CONTROL_H - -#include "decoder_command.h" -#include "audio_format.h" - -#include <glib.h> - -#include <assert.h> - -enum decoder_state { - DECODE_STATE_STOP = 0, - DECODE_STATE_START, - DECODE_STATE_DECODE, - - /** - * The last "START" command failed, because there was an I/O - * error or because no decoder was able to decode the file. - * This state will only come after START; once the state has - * turned to DECODE, by definition no such error can occur. - */ - DECODE_STATE_ERROR, -}; - -struct decoder_control { - /** the handle of the decoder thread, or NULL if the decoder - thread isn't running */ - GThread *thread; - - /** - * This lock protects #state and #command. - */ - GMutex *mutex; - - /** - * Trigger this object after you have modified #command. This - * is also used by the decoder thread to notify the caller - * when it has finished a command. - */ - GCond *cond; - - /** - * The trigger of this object's client. It is signalled - * whenever an event occurs. - */ - GCond *client_cond; - - enum decoder_state state; - enum decoder_command command; - - bool quit; - bool seek_error; - bool seekable; - double seek_where; - - /** the format of the song file */ - struct audio_format in_audio_format; - - /** the format being sent to the music pipe */ - struct audio_format out_audio_format; - - /** - * The song currently being decoded. This attribute is set by - * the player thread, when it sends the #DECODE_COMMAND_START - * command. - */ - const struct song *song; - - /** - * The initial seek position (in milliseconds), e.g. to the - * start of a sub-track described by a CUE file. - * - * This attribute is set by dc_start(). - */ - unsigned start_ms; - - /** - * The decoder will stop when it reaches this position (in - * milliseconds). 0 means don't stop before the end of the - * file. - * - * This attribute is set by dc_start(). - */ - unsigned end_ms; - - float total_time; - - /** the #music_chunk allocator */ - struct music_buffer *buffer; - - /** - * The destination pipe for decoded chunks. The caller thread - * owns this object, and is responsible for freeing it. - */ - struct music_pipe *pipe; - - float replay_gain_db; - float replay_gain_prev_db; - char *mixramp_start; - char *mixramp_end; - char *mixramp_prev_end; -}; - -G_GNUC_MALLOC -struct decoder_control * -dc_new(GCond *client_cond); - -void -dc_free(struct decoder_control *dc); - -/** - * Locks the #decoder_control object. - */ -static inline void -decoder_lock(struct decoder_control *dc) -{ - g_mutex_lock(dc->mutex); -} - -/** - * Unlocks the #decoder_control object. - */ -static inline void -decoder_unlock(struct decoder_control *dc) -{ - g_mutex_unlock(dc->mutex); -} - -/** - * Waits for a signal on the #decoder_control object. This function - * is only valid in the decoder thread. The object must be locked - * prior to calling this function. - */ -static inline void -decoder_wait(struct decoder_control *dc) -{ - g_cond_wait(dc->cond, dc->mutex); -} - -/** - * Signals the #decoder_control object. This function is only valid - * in the player thread. The object should be locked prior to calling - * this function. - */ -static inline void -decoder_signal(struct decoder_control *dc) -{ - g_cond_signal(dc->cond); -} - -static inline bool -decoder_is_idle(const struct decoder_control *dc) -{ - return dc->state == DECODE_STATE_STOP || - dc->state == DECODE_STATE_ERROR; -} - -static inline bool -decoder_is_starting(const struct decoder_control *dc) -{ - return dc->state == DECODE_STATE_START; -} - -static inline bool -decoder_has_failed(const struct decoder_control *dc) -{ - assert(dc->command == DECODE_COMMAND_NONE); - - return dc->state == DECODE_STATE_ERROR; -} - -static inline bool -decoder_lock_is_idle(struct decoder_control *dc) -{ - bool ret; - - decoder_lock(dc); - ret = decoder_is_idle(dc); - decoder_unlock(dc); - - return ret; -} - -static inline bool -decoder_lock_is_starting(struct decoder_control *dc) -{ - bool ret; - - decoder_lock(dc); - ret = decoder_is_starting(dc); - decoder_unlock(dc); - - return ret; -} - -static inline bool -decoder_lock_has_failed(struct decoder_control *dc) -{ - bool ret; - - decoder_lock(dc); - ret = decoder_has_failed(dc); - decoder_unlock(dc); - - return ret; -} - -static inline const struct song * -decoder_current_song(const struct decoder_control *dc) -{ - switch (dc->state) { - case DECODE_STATE_STOP: - case DECODE_STATE_ERROR: - return NULL; - - case DECODE_STATE_START: - case DECODE_STATE_DECODE: - return dc->song; - } - - assert(false); - return NULL; -} - -/** - * Start the decoder. - * - * @param the decoder - * @param song the song to be decoded - * @param start_ms see #decoder_control - * @param end_ms see #decoder_control - * @param pipe the pipe which receives the decoded chunks (owned by - * the caller) - */ -void -dc_start(struct decoder_control *dc, struct song *song, - unsigned start_ms, unsigned end_ms, - struct music_buffer *buffer, struct music_pipe *pipe); - -void -dc_stop(struct decoder_control *dc); - -bool -dc_seek(struct decoder_control *dc, double where); - -void -dc_quit(struct decoder_control *dc); - -void -dc_mixramp_start(struct decoder_control *dc, char *mixramp_start); - -void -dc_mixramp_end(struct decoder_control *dc, char *mixramp_end); - -void -dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end); - -#endif diff --git a/src/decoder_error.h b/src/decoder_error.h new file mode 100644 index 000000000..a12a31937 --- /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_internal.c b/src/decoder_internal.c deleted file mode 100644 index bc349f2ff..000000000 --- a/src/decoder_internal.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_internal.h" -#include "decoder_control.h" -#include "pipe.h" -#include "input_stream.h" -#include "buffer.h" -#include "chunk.h" - -#include <assert.h> - -/** - * All chunks are full of decoded data; wait for the player to free - * one. - */ -static enum decoder_command -need_chunks(struct decoder_control *dc, bool do_wait) -{ - if (dc->command == DECODE_COMMAND_STOP || - dc->command == DECODE_COMMAND_SEEK) - return dc->command; - - if (do_wait) { - decoder_wait(dc); - g_cond_signal(dc->client_cond); - - return dc->command; - } - - return DECODE_COMMAND_NONE; -} - -struct music_chunk * -decoder_get_chunk(struct decoder *decoder) -{ - struct decoder_control *dc = decoder->dc; - enum decoder_command cmd; - - assert(decoder != NULL); - - if (decoder->chunk != NULL) - return decoder->chunk; - - do { - decoder->chunk = music_buffer_allocate(dc->buffer); - if (decoder->chunk != NULL) { - decoder->chunk->replay_gain_serial = - decoder->replay_gain_serial; - if (decoder->replay_gain_serial != 0) - decoder->chunk->replay_gain_info = - decoder->replay_gain_info; - - return decoder->chunk; - } - - decoder_lock(dc); - cmd = need_chunks(dc, true); - decoder_unlock(dc); - } while (cmd == DECODE_COMMAND_NONE); - - return NULL; -} - -void -decoder_flush_chunk(struct decoder *decoder) -{ - struct decoder_control *dc = decoder->dc; - - assert(decoder != NULL); - assert(decoder->chunk != NULL); - - if (music_chunk_is_empty(decoder->chunk)) - music_buffer_return(dc->buffer, decoder->chunk); - else - music_pipe_push(dc->pipe, decoder->chunk); - - decoder->chunk = NULL; -} diff --git a/src/decoder_internal.h b/src/decoder_internal.h deleted file mode 100644 index d89e68cfc..000000000 --- a/src/decoder_internal.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_INTERNAL_H -#define MPD_DECODER_INTERNAL_H - -#include "decoder_command.h" -#include "pcm_convert.h" -#include "replay_gain_info.h" - -struct input_stream; - -struct decoder { - struct decoder_control *dc; - - struct pcm_convert_state conv_state; - - /** - * The time stamp of the next data chunk, in seconds. - */ - double timestamp; - - /** - * Is the initial seek (to the start position of the sub-song) - * pending, or has it been performed already? - */ - bool initial_seek_pending; - - /** - * Is the initial seek currently running? During this time, - * the decoder command is SEEK. This flag is set by - * decoder_get_virtual_command(), when the virtual SEEK - * command is generated for the first time. - */ - bool initial_seek_running; - - /** - * This flag is set by decoder_seek_where(), and checked by - * decoder_command_finished(). It is used to clean up after - * seeking. - */ - bool seeking; - - /** - * The tag from the song object. This is only used for local - * files, because we expect the stream server to send us a new - * tag each time we play it. - */ - struct tag *song_tag; - - /** the last tag received from the stream */ - struct tag *stream_tag; - - /** the last tag received from the decoder plugin */ - struct tag *decoder_tag; - - /** the chunk currently being written to */ - struct music_chunk *chunk; - - struct replay_gain_info replay_gain_info; - - /** - * A positive serial number for checking if replay gain info - * has changed since the last check. - */ - unsigned replay_gain_serial; -}; - -/** - * Returns the current chunk the decoder writes to, or allocates a new - * chunk if there is none. - * - * @return the chunk, or NULL if we have received a decoder command - */ -struct music_chunk * -decoder_get_chunk(struct decoder *decoder); - -/** - * Flushes the current chunk. - */ -void -decoder_flush_chunk(struct decoder *decoder); - -#endif diff --git a/src/decoder_list.c b/src/decoder_list.c deleted file mode 100644 index 177b632ad..000000000 --- a/src/decoder_list.c +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_list.h" -#include "decoder_plugin.h" -#include "utils.h" -#include "conf.h" -#include "mpd_error.h" -#include "decoder/pcm_decoder_plugin.h" -#include "decoder/dsdiff_decoder_plugin.h" -#include "decoder/dsf_decoder_plugin.h" - -#include <glib.h> - -#include <string.h> - -extern const struct decoder_plugin mad_decoder_plugin; -extern const struct decoder_plugin mpg123_decoder_plugin; -extern const struct decoder_plugin vorbis_decoder_plugin; -extern const struct decoder_plugin flac_decoder_plugin; -extern const struct decoder_plugin oggflac_decoder_plugin; -extern const struct decoder_plugin sndfile_decoder_plugin; -extern const struct decoder_plugin audiofile_decoder_plugin; -extern const struct decoder_plugin mp4ff_decoder_plugin; -extern const struct decoder_plugin faad_decoder_plugin; -extern const struct decoder_plugin mpcdec_decoder_plugin; -extern const struct decoder_plugin wavpack_decoder_plugin; -extern const struct decoder_plugin modplug_decoder_plugin; -extern const struct decoder_plugin mikmod_decoder_plugin; -extern const struct decoder_plugin sidplay_decoder_plugin; -extern const struct decoder_plugin wildmidi_decoder_plugin; -extern const struct decoder_plugin fluidsynth_decoder_plugin; -extern const struct decoder_plugin ffmpeg_decoder_plugin; -extern const struct decoder_plugin gme_decoder_plugin; - -const struct decoder_plugin *const decoder_plugins[] = { -#ifdef HAVE_MAD - &mad_decoder_plugin, -#endif -#ifdef HAVE_MPG123 - &mpg123_decoder_plugin, -#endif -#ifdef ENABLE_VORBIS_DECODER - &vorbis_decoder_plugin, -#endif -#if defined(HAVE_FLAC) - &oggflac_decoder_plugin, -#endif -#ifdef HAVE_FLAC - &flac_decoder_plugin, -#endif -#ifdef ENABLE_SNDFILE - &sndfile_decoder_plugin, -#endif -#ifdef HAVE_AUDIOFILE - &audiofile_decoder_plugin, -#endif - &dsdiff_decoder_plugin, - &dsf_decoder_plugin, -#ifdef HAVE_FAAD - &faad_decoder_plugin, -#endif -#ifdef HAVE_MP4 - &mp4ff_decoder_plugin, -#endif -#ifdef HAVE_MPCDEC - &mpcdec_decoder_plugin, -#endif -#ifdef HAVE_WAVPACK - &wavpack_decoder_plugin, -#endif -#ifdef HAVE_MODPLUG - &modplug_decoder_plugin, -#endif -#ifdef ENABLE_MIKMOD_DECODER - &mikmod_decoder_plugin, -#endif -#ifdef ENABLE_SIDPLAY - &sidplay_decoder_plugin, -#endif -#ifdef ENABLE_WILDMIDI - &wildmidi_decoder_plugin, -#endif -#ifdef ENABLE_FLUIDSYNTH - &fluidsynth_decoder_plugin, -#endif -#ifdef HAVE_FFMPEG - &ffmpeg_decoder_plugin, -#endif -#ifdef HAVE_GME - &gme_decoder_plugin, -#endif - &pcm_decoder_plugin, - NULL -}; - -enum { - num_decoder_plugins = G_N_ELEMENTS(decoder_plugins) - 1, -}; - -/** which plugins have been initialized successfully? */ -bool decoder_plugins_enabled[num_decoder_plugins]; - -static unsigned -decoder_plugin_index(const struct decoder_plugin *plugin) -{ - unsigned i = 0; - - while (decoder_plugins[i] != plugin) - ++i; - - return i; -} - -static unsigned -decoder_plugin_next_index(const struct decoder_plugin *plugin) -{ - return plugin == 0 - ? 0 /* start with first plugin */ - : decoder_plugin_index(plugin) + 1; -} - -const struct decoder_plugin * -decoder_plugin_from_suffix(const char *suffix, - const struct decoder_plugin *plugin) -{ - if (suffix == NULL) - return NULL; - - for (unsigned i = decoder_plugin_next_index(plugin); - decoder_plugins[i] != NULL; ++i) { - plugin = decoder_plugins[i]; - if (decoder_plugins_enabled[i] && - decoder_plugin_supports_suffix(plugin, suffix)) - return plugin; - } - - return NULL; -} - -const struct decoder_plugin * -decoder_plugin_from_mime_type(const char *mimeType, unsigned int next) -{ - static unsigned i = num_decoder_plugins; - - if (mimeType == NULL) - return NULL; - - if (!next) - i = 0; - for (; decoder_plugins[i] != NULL; ++i) { - const struct decoder_plugin *plugin = decoder_plugins[i]; - if (decoder_plugins_enabled[i] && - decoder_plugin_supports_mime_type(plugin, mimeType)) { - ++i; - return plugin; - } - } - - return NULL; -} - -const struct decoder_plugin * -decoder_plugin_from_name(const char *name) -{ - decoder_plugins_for_each_enabled(plugin) - if (strcmp(plugin->name, name) == 0) - return plugin; - - return NULL; -} - -/** - * Find the "decoder" configuration block for the specified plugin. - * - * @param plugin_name the name of the decoder plugin - * @return the configuration block, or NULL if none was configured - */ -static const struct config_param * -decoder_plugin_config(const char *plugin_name) -{ - const struct config_param *param = NULL; - - while ((param = config_get_next_param(CONF_DECODER, param)) != NULL) { - const char *name = - config_get_block_string(param, "plugin", NULL); - if (name == NULL) - MPD_ERROR("decoder configuration without 'plugin' name in line %d", - param->line); - - if (strcmp(name, plugin_name) == 0) - return param; - } - - return NULL; -} - -void decoder_plugin_init_all(void) -{ - for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) { - const struct decoder_plugin *plugin = decoder_plugins[i]; - const struct config_param *param = - decoder_plugin_config(plugin->name); - - if (!config_get_block_bool(param, "enabled", true)) - /* the plugin is disabled in mpd.conf */ - continue; - - if (decoder_plugin_init(plugin, param)) - decoder_plugins_enabled[i] = true; - } -} - -void decoder_plugin_deinit_all(void) -{ - decoder_plugins_for_each_enabled(plugin) - decoder_plugin_finish(plugin); -} diff --git a/src/decoder_list.h b/src/decoder_list.h deleted file mode 100644 index d0a6ade7e..000000000 --- a/src/decoder_list.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_LIST_H -#define MPD_DECODER_LIST_H - -#include <stdbool.h> - -struct decoder_plugin; - -extern const struct decoder_plugin *const decoder_plugins[]; -extern bool decoder_plugins_enabled[]; - -#define decoder_plugins_for_each(plugin) \ - for (const struct decoder_plugin *plugin, \ - *const*decoder_plugin_iterator = &decoder_plugins[0]; \ - (plugin = *decoder_plugin_iterator) != NULL; \ - ++decoder_plugin_iterator) - -#define decoder_plugins_for_each_enabled(plugin) \ - decoder_plugins_for_each(plugin) \ - if (decoder_plugins_enabled[decoder_plugin_iterator - decoder_plugins]) - -/* interface for using plugins */ - -/** - * Find the next enabled decoder plugin which supports the specified suffix. - * - * @param suffix the file name suffix - * @param plugin the previous plugin, or NULL to find the first plugin - * @return a plugin, or NULL if none matches - */ -const struct decoder_plugin * -decoder_plugin_from_suffix(const char *suffix, - const struct decoder_plugin *plugin); - -const struct decoder_plugin * -decoder_plugin_from_mime_type(const char *mimeType, unsigned int next); - -const struct decoder_plugin * -decoder_plugin_from_name(const char *name); - -/* this is where we "load" all the "plugins" ;-) */ -void decoder_plugin_init_all(void); - -/* this is where we "unload" all the "plugins" */ -void decoder_plugin_deinit_all(void); - -#endif diff --git a/src/decoder_plugin.h b/src/decoder_plugin.h index 933ba6751..b7ab738b5 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/decoder_print.c b/src/decoder_print.c deleted file mode 100644 index e14477ed8..000000000 --- a/src/decoder_print.c +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_print.h" -#include "decoder_list.h" -#include "decoder_plugin.h" -#include "client.h" - -#include <assert.h> - -static void -decoder_plugin_print(struct client *client, - const struct decoder_plugin *plugin) -{ - const char *const*p; - - assert(plugin != NULL); - assert(plugin->name != NULL); - - client_printf(client, "plugin: %s\n", plugin->name); - - if (plugin->suffixes != NULL) - for (p = plugin->suffixes; *p != NULL; ++p) - client_printf(client, "suffix: %s\n", *p); - - if (plugin->mime_types != NULL) - for (p = plugin->mime_types; *p != NULL; ++p) - client_printf(client, "mime_type: %s\n", *p); -} - -void -decoder_list_print(struct client *client) -{ - decoder_plugins_for_each_enabled(plugin) - decoder_plugin_print(client, plugin); -} diff --git a/src/decoder_print.h b/src/decoder_print.h deleted file mode 100644 index 31713d5d8..000000000 --- a/src/decoder_print.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_PRINT_H -#define MPD_DECODER_PRINT_H - -struct client; - -void -decoder_list_print(struct client *client); - -#endif diff --git a/src/decoder_thread.c b/src/decoder_thread.c deleted file mode 100644 index af80ed45b..000000000 --- a/src/decoder_thread.c +++ /dev/null @@ -1,510 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_thread.h" -#include "decoder_control.h" -#include "decoder_internal.h" -#include "decoder_list.h" -#include "decoder_plugin.h" -#include "decoder_api.h" -#include "replay_gain_ape.h" -#include "input_stream.h" -#include "pipe.h" -#include "song.h" -#include "tag.h" -#include "mapper.h" -#include "path.h" -#include "uri.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <unistd.h> -#include <stdio.h> /* for SEEK_SET */ - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "decoder_thread" - -/** - * Marks the current decoder command as "finished" and notifies the - * player thread. - * - * @param dc the #decoder_control object; must be locked - */ -static void -decoder_command_finished_locked(struct decoder_control *dc) -{ - assert(dc->command != DECODE_COMMAND_NONE); - - dc->command = DECODE_COMMAND_NONE; - - g_cond_signal(dc->client_cond); -} - -/** - * Opens the input stream with input_stream_open(), and waits until - * the stream gets ready. If a decoder STOP command is received - * during that, it cancels the operation (but does not close the - * stream). - * - * Unlock the decoder before calling this function. - * - * @return an input_stream on success or if #DECODE_COMMAND_STOP is - * received, NULL on error - */ -static struct input_stream * -decoder_input_stream_open(struct decoder_control *dc, const char *uri) -{ - GError *error = NULL; - struct input_stream *is; - - is = input_stream_open(uri, dc->mutex, dc->cond, &error); - if (is == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - - return NULL; - } - - /* wait for the input stream to become ready; its metadata - will be available then */ - - decoder_lock(dc); - - input_stream_update(is); - while (!is->ready && - dc->command != DECODE_COMMAND_STOP) { - decoder_wait(dc); - - input_stream_update(is); - } - - if (!input_stream_check(is, &error)) { - decoder_unlock(dc); - - g_warning("%s", error->message); - g_error_free(error); - - return NULL; - } - - decoder_unlock(dc); - - return is; -} - -static bool -decoder_stream_decode(const struct decoder_plugin *plugin, - struct decoder *decoder, - struct input_stream *input_stream) -{ - assert(plugin != NULL); - assert(plugin->stream_decode != NULL); - assert(decoder != NULL); - assert(decoder->stream_tag == NULL); - assert(decoder->decoder_tag == NULL); - assert(input_stream != NULL); - assert(input_stream->ready); - assert(decoder->dc->state == DECODE_STATE_START); - - g_debug("probing plugin %s", plugin->name); - - if (decoder->dc->command == DECODE_COMMAND_STOP) - return true; - - /* rewind the stream, so each plugin gets a fresh start */ - input_stream_seek(input_stream, 0, SEEK_SET, NULL); - - decoder_unlock(decoder->dc); - - decoder_plugin_stream_decode(plugin, decoder, input_stream); - - decoder_lock(decoder->dc); - - assert(decoder->dc->state == DECODE_STATE_START || - decoder->dc->state == DECODE_STATE_DECODE); - - return decoder->dc->state != DECODE_STATE_START; -} - -static bool -decoder_file_decode(const struct decoder_plugin *plugin, - struct decoder *decoder, const char *path) -{ - assert(plugin != NULL); - assert(plugin->file_decode != NULL); - assert(decoder != NULL); - assert(decoder->stream_tag == NULL); - assert(decoder->decoder_tag == NULL); - assert(path != NULL); - assert(g_path_is_absolute(path)); - assert(decoder->dc->state == DECODE_STATE_START); - - g_debug("probing plugin %s", plugin->name); - - if (decoder->dc->command == DECODE_COMMAND_STOP) - return true; - - decoder_unlock(decoder->dc); - - decoder_plugin_file_decode(plugin, decoder, path); - - decoder_lock(decoder->dc); - - assert(decoder->dc->state == DECODE_STATE_START || - decoder->dc->state == DECODE_STATE_DECODE); - - return decoder->dc->state != DECODE_STATE_START; -} - -/** - * Hack to allow tracking const decoder plugins in a GSList. - */ -static inline gpointer -deconst_plugin(const struct decoder_plugin *plugin) -{ - union { - const struct decoder_plugin *in; - gpointer out; - } u = { .in = plugin }; - - return u.out; -} - -/** - * Try decoding a stream, using plugins matching the stream's MIME type. - * - * @param tried_r a list of plugins which were tried - */ -static bool -decoder_run_stream_mime_type(struct decoder *decoder, struct input_stream *is, - GSList **tried_r) -{ - assert(tried_r != NULL); - - const struct decoder_plugin *plugin; - unsigned int next = 0; - - if (is->mime == NULL) - return false; - - while ((plugin = decoder_plugin_from_mime_type(is->mime, next++))) { - if (plugin->stream_decode == NULL) - continue; - - if (g_slist_find(*tried_r, plugin) != NULL) - /* don't try a plugin twice */ - continue; - - if (decoder_stream_decode(plugin, decoder, is)) - return true; - - *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin)); - } - - return false; -} - -/** - * Try decoding a stream, using plugins matching the stream's URI - * suffix. - * - * @param tried_r a list of plugins which were tried - */ -static bool -decoder_run_stream_suffix(struct decoder *decoder, struct input_stream *is, - const char *uri, GSList **tried_r) -{ - assert(tried_r != NULL); - - const char *suffix = uri_get_suffix(uri); - const struct decoder_plugin *plugin = NULL; - - if (suffix == NULL) - return false; - - while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) { - if (plugin->stream_decode == NULL) - continue; - - if (g_slist_find(*tried_r, plugin) != NULL) - /* don't try a plugin twice */ - continue; - - if (decoder_stream_decode(plugin, decoder, is)) - return true; - - *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin)); - } - - return false; -} - -/** - * Try decoding a stream, using the fallback plugin. - */ -static bool -decoder_run_stream_fallback(struct decoder *decoder, struct input_stream *is) -{ - const struct decoder_plugin *plugin; - - plugin = decoder_plugin_from_name("mad"); - return plugin != NULL && plugin->stream_decode != NULL && - decoder_stream_decode(plugin, decoder, is); -} - -/** - * Try decoding a stream. - */ -static bool -decoder_run_stream(struct decoder *decoder, const char *uri) -{ - struct decoder_control *dc = decoder->dc; - struct input_stream *input_stream; - bool success; - - decoder_unlock(dc); - - input_stream = decoder_input_stream_open(dc, uri); - if (input_stream == NULL) { - decoder_lock(dc); - return false; - } - - decoder_lock(dc); - - GSList *tried = NULL; - - success = dc->command == DECODE_COMMAND_STOP || - /* first we try mime types: */ - decoder_run_stream_mime_type(decoder, input_stream, &tried) || - /* if that fails, try suffix matching the URL: */ - decoder_run_stream_suffix(decoder, input_stream, uri, - &tried) || - /* fallback to mp3: this is needed for bastard streams - that don't have a suffix or set the mimeType */ - (tried == NULL && - decoder_run_stream_fallback(decoder, input_stream)); - - g_slist_free(tried); - - decoder_unlock(dc); - input_stream_close(input_stream); - decoder_lock(dc); - - return success; -} - -/** - * Attempt to load replay gain data, and pass it to - * decoder_replay_gain(). - */ -static void -decoder_load_replay_gain(struct decoder *decoder, const char *path_fs) -{ - struct replay_gain_info info; - if (replay_gain_ape_read(path_fs, &info)) - decoder_replay_gain(decoder, &info); -} - -/** - * Try decoding a file. - */ -static bool -decoder_run_file(struct decoder *decoder, const char *path_fs) -{ - struct decoder_control *dc = decoder->dc; - const char *suffix = uri_get_suffix(path_fs); - const struct decoder_plugin *plugin = NULL; - - if (suffix == NULL) - return false; - - decoder_unlock(dc); - - decoder_load_replay_gain(decoder, path_fs); - - while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) { - if (plugin->file_decode != NULL) { - decoder_lock(dc); - - if (decoder_file_decode(plugin, decoder, path_fs)) - return true; - - decoder_unlock(dc); - } else if (plugin->stream_decode != NULL) { - struct input_stream *input_stream; - bool success; - - input_stream = decoder_input_stream_open(dc, path_fs); - if (input_stream == NULL) - continue; - - decoder_lock(dc); - - success = decoder_stream_decode(plugin, decoder, - input_stream); - - decoder_unlock(dc); - - input_stream_close(input_stream); - - if (success) { - decoder_lock(dc); - return true; - } - } - } - - decoder_lock(dc); - return false; -} - -static void -decoder_run_song(struct decoder_control *dc, - const struct song *song, const char *uri) -{ - struct decoder decoder = { - .dc = dc, - .initial_seek_pending = dc->start_ms > 0, - .initial_seek_running = false, - }; - int ret; - - decoder.timestamp = 0.0; - decoder.seeking = false; - decoder.song_tag = song->tag != NULL && song_is_file(song) - ? tag_dup(song->tag) : NULL; - decoder.stream_tag = NULL; - decoder.decoder_tag = NULL; - decoder.chunk = NULL; - - dc->state = DECODE_STATE_START; - - decoder_command_finished_locked(dc); - - pcm_convert_init(&decoder.conv_state); - - ret = song_is_file(song) - ? decoder_run_file(&decoder, uri) - : decoder_run_stream(&decoder, uri); - - decoder_unlock(dc); - - pcm_convert_deinit(&decoder.conv_state); - - /* flush the last chunk */ - - if (decoder.chunk != NULL) - decoder_flush_chunk(&decoder); - - if (decoder.song_tag != NULL) - tag_free(decoder.song_tag); - - if (decoder.stream_tag != NULL) - tag_free(decoder.stream_tag); - - if (decoder.decoder_tag != NULL) - tag_free(decoder.decoder_tag); - - decoder_lock(dc); - - dc->state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR; -} - -static void -decoder_run(struct decoder_control *dc) -{ - const struct song *song = dc->song; - char *uri; - - assert(song != NULL); - - if (song_is_file(song)) - uri = map_song_fs(song); - else - uri = song_get_uri(song); - - if (uri == NULL) { - dc->state = DECODE_STATE_ERROR; - decoder_command_finished_locked(dc); - return; - } - - decoder_run_song(dc, song, uri); - g_free(uri); - -} - -static gpointer -decoder_task(gpointer arg) -{ - struct decoder_control *dc = arg; - - decoder_lock(dc); - - do { - assert(dc->state == DECODE_STATE_STOP || - dc->state == DECODE_STATE_ERROR); - - switch (dc->command) { - case DECODE_COMMAND_START: - dc_mixramp_start(dc, NULL); - dc_mixramp_prev_end(dc, dc->mixramp_end); - dc->mixramp_end = NULL; /* Don't free, it's copied above. */ - dc->replay_gain_prev_db = dc->replay_gain_db; - dc->replay_gain_db = 0; - - /* fall through */ - - case DECODE_COMMAND_SEEK: - decoder_run(dc); - break; - - case DECODE_COMMAND_STOP: - decoder_command_finished_locked(dc); - break; - - case DECODE_COMMAND_NONE: - decoder_wait(dc); - break; - } - } while (dc->command != DECODE_COMMAND_NONE || !dc->quit); - - decoder_unlock(dc); - - return NULL; -} - -void -decoder_thread_start(struct decoder_control *dc) -{ - GError *e = NULL; - - assert(dc->thread == NULL); - - dc->quit = false; - - dc->thread = g_thread_create(decoder_task, dc, true, &e); - if (dc->thread == NULL) - MPD_ERROR("Failed to spawn decoder task: %s", e->message); -} diff --git a/src/decoder_thread.h b/src/decoder_thread.h deleted file mode 100644 index 78f12a54a..000000000 --- a/src/decoder_thread.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_THREAD_H -#define MPD_DECODER_THREAD_H - -struct decoder_control; - -void -decoder_thread_start(struct decoder_control *dc); - -#endif diff --git a/src/despotify_utils.c b/src/despotify_utils.c deleted file mode 100644 index 7555d105d..000000000 --- a/src/despotify_utils.c +++ /dev/null @@ -1,121 +0,0 @@ -#include <glib.h> -#include <despotify.h> - -#include "tag.h" -#include "conf.h" -#include "despotify_utils.h" - -static struct despotify_session *g_session; -static void (*registered_callbacks[8])(struct despotify_session *, - int, void *, void *); -static void *registered_callback_data[8]; - -static void callback(struct despotify_session* ds, int sig, - void* data, G_GNUC_UNUSED void* callback_data) -{ - size_t i; - - for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { - void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i]; - void *cb_data = registered_callback_data[i]; - - if (cb) - cb(ds, sig, data, cb_data); - } -} - -bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), - void *cb_data) -{ - size_t i; - - for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { - - if (!registered_callbacks[i]) { - registered_callbacks[i] = cb; - registered_callback_data[i] = cb_data; - - return true; - } - } - - return false; -} - -void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)) -{ - size_t i; - - for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { - - if (registered_callbacks[i] == cb) { - registered_callbacks[i] = NULL; - } - } -} - - -struct tag *mpd_despotify_tag_from_track(struct ds_track *track) -{ - char tracknum[20]; - char comment[80]; - char date[20]; - struct tag *tag; - - tag = tag_new(); - - if (!track->has_meta_data) - return tag; - - g_snprintf(tracknum, sizeof(tracknum), "%d", track->tracknumber); - g_snprintf(date, sizeof(date), "%d", track->year); - g_snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted", - track->file_bitrate / 1000, track->geo_restricted ? "" : "not "); - tag_add_item(tag, TAG_TITLE, track->title); - tag_add_item(tag, TAG_ARTIST, track->artist->name); - tag_add_item(tag, TAG_TRACK, tracknum); - tag_add_item(tag, TAG_ALBUM, track->album); - tag_add_item(tag, TAG_DATE, date); - tag_add_item(tag, TAG_COMMENT, comment); - tag->time = track->length / 1000; - - return tag; -} - -struct despotify_session *mpd_despotify_get_session(void) -{ - const char *user; - const char *passwd; - bool high_bitrate; - - if (g_session) - return g_session; - - user = config_get_string(CONF_DESPOTIFY_USER, NULL); - passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, NULL); - high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true); - - if (user == NULL || passwd == NULL) { - g_debug("disabling despotify because account is not configured"); - return NULL; - } - if (!despotify_init()) { - g_debug("Can't initialize despotify\n"); - return false; - } - - g_session = despotify_init_client(callback, NULL, - high_bitrate, true); - if (!g_session) { - g_debug("Can't initialize despotify client\n"); - return false; - } - - if (!despotify_authenticate(g_session, user, passwd)) { - g_debug("Can't authenticate despotify session\n"); - despotify_exit(g_session); - return false; - } - - return g_session; -} diff --git a/src/directory.c b/src/directory.c deleted file mode 100644 index e886698d6..000000000 --- a/src/directory.c +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "directory.h" -#include "song.h" -#include "song_sort.h" -#include "playlist_vector.h" -#include "path.h" -#include "util/list_sort.h" -#include "db_visitor.h" -#include "db_lock.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -struct directory * -directory_new(const char *path, struct directory *parent) -{ - struct directory *directory; - size_t pathlen = strlen(path); - - assert(path != NULL); - assert((*path == 0) == (parent == NULL)); - - directory = g_malloc0(sizeof(*directory) - - sizeof(directory->path) + pathlen + 1); - INIT_LIST_HEAD(&directory->children); - INIT_LIST_HEAD(&directory->songs); - INIT_LIST_HEAD(&directory->playlists); - - directory->parent = parent; - memcpy(directory->path, path, pathlen + 1); - - return directory; -} - -void -directory_free(struct directory *directory) -{ - playlist_vector_deinit(&directory->playlists); - - struct song *song, *ns; - directory_for_each_song_safe(song, ns, directory) - song_free(song); - - struct directory *child, *n; - directory_for_each_child_safe(child, n, directory) - directory_free(child); - - g_free(directory); - /* this resets last dir returned */ - /*directory_get_path(NULL); */ -} - -void -directory_delete(struct directory *directory) -{ - assert(holding_db_lock()); - assert(directory != NULL); - assert(directory->parent != NULL); - - list_del(&directory->siblings); - directory_free(directory); -} - -const char * -directory_get_name(const struct directory *directory) -{ - assert(!directory_is_root(directory)); - assert(directory->path != NULL); - - const char *slash = strrchr(directory->path, '/'); - assert((slash == NULL) == directory_is_root(directory->parent)); - - return slash != NULL - ? slash + 1 - : directory->path; -} - -struct directory * -directory_new_child(struct directory *parent, const char *name_utf8) -{ - assert(holding_db_lock()); - assert(parent != NULL); - assert(name_utf8 != NULL); - assert(*name_utf8 != 0); - - char *allocated; - const char *path_utf8; - if (directory_is_root(parent)) { - allocated = NULL; - path_utf8 = name_utf8; - } else { - allocated = g_strconcat(directory_get_path(parent), - "/", name_utf8, NULL); - path_utf8 = allocated; - } - - struct directory *directory = directory_new(path_utf8, parent); - g_free(allocated); - - list_add_tail(&directory->siblings, &parent->children); - return directory; -} - -struct directory * -directory_get_child(const struct directory *directory, const char *name) -{ - assert(holding_db_lock()); - - struct directory *child; - directory_for_each_child(child, directory) - if (strcmp(directory_get_name(child), name) == 0) - return child; - - return NULL; -} - -void -directory_prune_empty(struct directory *directory) -{ - assert(holding_db_lock()); - - struct directory *child, *n; - directory_for_each_child_safe(child, n, directory) { - directory_prune_empty(child); - - if (directory_is_empty(child)) - directory_delete(child); - } -} - -struct directory * -directory_lookup_directory(struct directory *directory, const char *uri) -{ - assert(holding_db_lock()); - assert(uri != NULL); - - if (isRootDirectory(uri)) - return directory; - - char *duplicated = g_strdup(uri), *name = duplicated; - - while (1) { - char *slash = strchr(name, '/'); - if (slash == name) { - directory = NULL; - break; - } - - if (slash != NULL) - *slash = '\0'; - - directory = directory_get_child(directory, name); - if (directory == NULL || slash == NULL) - break; - - name = slash + 1; - } - - g_free(duplicated); - - return directory; -} - -void -directory_add_song(struct directory *directory, struct song *song) -{ - assert(holding_db_lock()); - assert(directory != NULL); - assert(song != NULL); - assert(song->parent == directory); - - list_add_tail(&song->siblings, &directory->songs); -} - -void -directory_remove_song(G_GNUC_UNUSED struct directory *directory, - struct song *song) -{ - assert(holding_db_lock()); - assert(directory != NULL); - assert(song != NULL); - assert(song->parent == directory); - - list_del(&song->siblings); -} - -struct song * -directory_get_song(const struct directory *directory, const char *name_utf8) -{ - assert(holding_db_lock()); - assert(directory != NULL); - assert(name_utf8 != NULL); - - struct song *song; - directory_for_each_song(song, directory) { - assert(song->parent == directory); - - if (strcmp(song->uri, name_utf8) == 0) - return song; - } - - return NULL; -} - -struct song * -directory_lookup_song(struct directory *directory, const char *uri) -{ - char *duplicated, *base; - - assert(holding_db_lock()); - assert(directory != NULL); - assert(uri != NULL); - - duplicated = g_strdup(uri); - base = strrchr(duplicated, '/'); - - if (base != NULL) { - *base++ = 0; - directory = directory_lookup_directory(directory, duplicated); - if (directory == NULL) { - g_free(duplicated); - return NULL; - } - } else - base = duplicated; - - struct song *song = directory_get_song(directory, base); - assert(song == NULL || song->parent == directory); - - g_free(duplicated); - return song; - -} - -static int -directory_cmp(G_GNUC_UNUSED void *priv, - struct list_head *_a, struct list_head *_b) -{ - const struct directory *a = (const struct directory *)_a; - const struct directory *b = (const struct directory *)_b; - return g_utf8_collate(a->path, b->path); -} - -void -directory_sort(struct directory *directory) -{ - assert(holding_db_lock()); - - list_sort(NULL, &directory->children, directory_cmp); - song_list_sort(&directory->songs); - - struct directory *child; - directory_for_each_child(child, directory) - directory_sort(child); -} - -bool -directory_walk(const struct directory *directory, bool recursive, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - assert(directory != NULL); - assert(visitor != NULL); - assert(error_r == NULL || *error_r == NULL); - - if (visitor->song != NULL) { - struct song *song; - directory_for_each_song(song, directory) - if (!visitor->song(song, ctx, error_r)) - return false; - } - - if (visitor->playlist != NULL) { - struct playlist_metadata *i; - directory_for_each_playlist(i, directory) - if (!visitor->playlist(i, directory, ctx, error_r)) - return false; - } - - struct directory *child; - directory_for_each_child(child, directory) { - if (visitor->directory != NULL && - !visitor->directory(child, ctx, error_r)) - return false; - - if (recursive && - !directory_walk(child, recursive, visitor, ctx, error_r)) - return false; - } - - return true; -} diff --git a/src/directory.h b/src/directory.h deleted file mode 100644 index b3cd9c8c9..000000000 --- a/src/directory.h +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DIRECTORY_H -#define MPD_DIRECTORY_H - -#include "check.h" -#include "util/list.h" - -#include <glib.h> -#include <stdbool.h> -#include <sys/types.h> - -#define DEVICE_INARCHIVE (dev_t)(-1) -#define DEVICE_CONTAINER (dev_t)(-2) - -#define directory_for_each_child(pos, directory) \ - list_for_each_entry(pos, &directory->children, siblings) - -#define directory_for_each_child_safe(pos, n, directory) \ - list_for_each_entry_safe(pos, n, &directory->children, siblings) - -#define directory_for_each_song(pos, directory) \ - list_for_each_entry(pos, &directory->songs, siblings) - -#define directory_for_each_song_safe(pos, n, directory) \ - list_for_each_entry_safe(pos, n, &directory->songs, siblings) - -#define directory_for_each_playlist(pos, directory) \ - list_for_each_entry(pos, &directory->playlists, siblings) - -#define directory_for_each_playlist_safe(pos, n, directory) \ - list_for_each_entry_safe(pos, n, &directory->playlists, siblings) - -struct song; -struct db_visitor; - -struct directory { - /** - * Pointers to the siblings of this directory within the - * parent directory. It is unused (undefined) in the root - * directory. - * - * This attribute is protected with the global #db_mutex. - * Read access in the update thread does not need protection. - */ - struct list_head siblings; - - /** - * A doubly linked list of child directories. - * - * This attribute is protected with the global #db_mutex. - * Read access in the update thread does not need protection. - */ - struct list_head children; - - /** - * A doubly linked list of songs within this directory. - * - * This attribute is protected with the global #db_mutex. - * Read access in the update thread does not need protection. - */ - struct list_head songs; - - struct list_head playlists; - - struct directory *parent; - time_t mtime; - ino_t inode; - dev_t device; - bool have_stat; /* not needed if ino_t == dev_t == 0 is impossible */ - char path[sizeof(long)]; -}; - -static inline bool -isRootDirectory(const char *name) -{ - return name[0] == 0 || (name[0] == '/' && name[1] == 0); -} - -/** - * Generic constructor for #directory object. - */ -G_GNUC_MALLOC -struct directory * -directory_new(const char *dirname, struct directory *parent); - -/** - * Create a new root #directory object. - */ -G_GNUC_MALLOC -static inline struct directory * -directory_new_root(void) -{ - return directory_new("", NULL); -} - -/** - * Free this #directory object (and the whole object tree within it), - * assuming it was already removed from the parent. - */ -void -directory_free(struct directory *directory); - -/** - * Remove this #directory object from its parent and free it. This - * must not be called with the root directory. - * - * Caller must lock the #db_mutex. - */ -void -directory_delete(struct directory *directory); - -static inline bool -directory_is_empty(const struct directory *directory) -{ - return list_empty(&directory->children) && - list_empty(&directory->songs) && - list_empty(&directory->playlists); -} - -static inline const char * -directory_get_path(const struct directory *directory) -{ - return directory->path; -} - -/** - * Is this the root directory of the music database? - */ -static inline bool -directory_is_root(const struct directory *directory) -{ - return directory->parent == NULL; -} - -/** - * Returns the base name of the directory. - */ -G_GNUC_PURE -const char * -directory_get_name(const struct directory *directory); - -/** - * Caller must lock the #db_mutex. - */ -G_GNUC_PURE -struct directory * -directory_get_child(const struct directory *directory, const char *name); - -/** - * Create a new #directory object as a child of the given one. - * - * Caller must lock the #db_mutex. - * - * @param parent the parent directory the new one will be added to - * @param name_utf8 the UTF-8 encoded name of the new sub directory - */ -G_GNUC_MALLOC -struct directory * -directory_new_child(struct directory *parent, const char *name_utf8); - -/** - * Look up a sub directory, and create the object if it does not - * exist. - * - * Caller must lock the #db_mutex. - */ -static inline struct directory * -directory_make_child(struct directory *directory, const char *name_utf8) -{ - struct directory *child = directory_get_child(directory, name_utf8); - if (child == NULL) - child = directory_new_child(directory, name_utf8); - return child; -} - -/** - * Caller must lock the #db_mutex. - */ -void -directory_prune_empty(struct directory *directory); - -/** - * Looks up a directory by its relative URI. - * - * @param directory the parent (or grandparent, ...) directory - * @param uri the relative URI - * @return the directory, or NULL if none was found - */ -struct directory * -directory_lookup_directory(struct directory *directory, const char *uri); - -/** - * Add a song object to this directory. Its "parent" attribute must - * be set already. - */ -void -directory_add_song(struct directory *directory, struct song *song); - -/** - * Remove a song object from this directory (which effectively - * invalidates the song object, because the "parent" attribute becomes - * stale), but does not free it. - */ -void -directory_remove_song(struct directory *directory, struct song *song); - -/** - * Look up a song in this directory by its name. - * - * Caller must lock the #db_mutex. - */ -G_GNUC_PURE -struct song * -directory_get_song(const struct directory *directory, const char *name_utf8); - -/** - * Looks up a song by its relative URI. - * - * Caller must lock the #db_mutex. - * - * @param directory the parent (or grandparent, ...) directory - * @param uri the relative URI - * @return the song, or NULL if none was found - */ -struct song * -directory_lookup_song(struct directory *directory, const char *uri); - -/** - * Sort all directory entries recursively. - * - * Caller must lock the #db_mutex. - */ -void -directory_sort(struct directory *directory); - -/** - * Caller must lock #db_mutex. - */ -bool -directory_walk(const struct directory *directory, bool recursive, - const struct db_visitor *visitor, void *ctx, - GError **error_r); - -#endif diff --git a/src/directory_save.c b/src/directory_save.c deleted file mode 100644 index de1df050a..000000000 --- a/src/directory_save.c +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "directory_save.h" -#include "directory.h" -#include "song.h" -#include "text_file.h" -#include "song_save.h" -#include "playlist_database.h" - -#include <assert.h> -#include <string.h> - -#define DIRECTORY_DIR "directory: " -#define DIRECTORY_MTIME "mtime: " -#define DIRECTORY_BEGIN "begin: " -#define DIRECTORY_END "end: " - -/** - * The quark used for GError.domain. - */ -static inline GQuark -directory_quark(void) -{ - return g_quark_from_static_string("directory"); -} - -void -directory_save(FILE *fp, const struct directory *directory) -{ - if (!directory_is_root(directory)) { - fprintf(fp, DIRECTORY_MTIME "%lu\n", - (unsigned long)directory->mtime); - - fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, - directory_get_path(directory)); - } - - struct directory *cur; - directory_for_each_child(cur, directory) { - char *base = g_path_get_basename(cur->path); - - fprintf(fp, DIRECTORY_DIR "%s\n", base); - g_free(base); - - directory_save(fp, cur); - - if (ferror(fp)) - return; - } - - struct song *song; - directory_for_each_song(song, directory) - song_save(fp, song); - - playlist_vector_save(fp, &directory->playlists); - - if (!directory_is_root(directory)) - fprintf(fp, DIRECTORY_END "%s\n", - directory_get_path(directory)); -} - -static struct directory * -directory_load_subdir(FILE *fp, struct directory *parent, const char *name, - GString *buffer, GError **error_r) -{ - const char *line; - bool success; - - if (directory_get_child(parent, name) != NULL) { - g_set_error(error_r, directory_quark(), 0, - "Duplicate subdirectory '%s'", name); - return NULL; - } - - struct directory *directory = directory_new_child(parent, name); - - line = read_text_line(fp, buffer); - if (line == NULL) { - g_set_error(error_r, directory_quark(), 0, - "Unexpected end of file"); - directory_delete(directory); - return NULL; - } - - if (g_str_has_prefix(line, DIRECTORY_MTIME)) { - directory->mtime = - g_ascii_strtoull(line + sizeof(DIRECTORY_MTIME) - 1, - NULL, 10); - - line = read_text_line(fp, buffer); - if (line == NULL) { - g_set_error(error_r, directory_quark(), 0, - "Unexpected end of file"); - directory_delete(directory); - return NULL; - } - } - - if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) { - g_set_error(error_r, directory_quark(), 0, - "Malformed line: %s", line); - directory_delete(directory); - return NULL; - } - - success = directory_load(fp, directory, buffer, error_r); - if (!success) { - directory_delete(directory); - return NULL; - } - - return directory; -} - -bool -directory_load(FILE *fp, struct directory *directory, - GString *buffer, GError **error) -{ - const char *line; - - while ((line = read_text_line(fp, buffer)) != NULL && - !g_str_has_prefix(line, DIRECTORY_END)) { - if (g_str_has_prefix(line, DIRECTORY_DIR)) { - struct directory *subdir = - directory_load_subdir(fp, directory, - line + sizeof(DIRECTORY_DIR) - 1, - buffer, error); - if (subdir == NULL) - return false; - } else if (g_str_has_prefix(line, SONG_BEGIN)) { - const char *name = line + sizeof(SONG_BEGIN) - 1; - struct song *song; - - if (directory_get_song(directory, name) != NULL) { - g_set_error(error, directory_quark(), 0, - "Duplicate song '%s'", name); - return NULL; - } - - song = song_load(fp, directory, name, - buffer, error); - if (song == NULL) - return false; - - directory_add_song(directory, song); - } else if (g_str_has_prefix(line, PLAYLIST_META_BEGIN)) { - /* duplicate the name, because - playlist_metadata_load() will overwrite the - buffer */ - char *name = g_strdup(line + sizeof(PLAYLIST_META_BEGIN) - 1); - - if (!playlist_metadata_load(fp, &directory->playlists, - name, buffer, error)) { - g_free(name); - return false; - } - - g_free(name); - } else { - g_set_error(error, directory_quark(), 0, - "Malformed line: %s", line); - return false; - } - } - - return true; -} diff --git a/src/directory_save.h b/src/directory_save.h deleted file mode 100644 index 2d0056830..000000000 --- a/src/directory_save.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DIRECTORY_SAVE_H -#define MPD_DIRECTORY_SAVE_H - -#include <glib.h> - -#include <stdbool.h> -#include <stdio.h> - -struct directory; - -void -directory_save(FILE *fp, const struct directory *directory); - -bool -directory_load(FILE *fp, struct directory *directory, - GString *buffer, GError **error); - -#endif diff --git a/src/dsd2pcm/dsd2pcm.hpp b/src/dsd2pcm/dsd2pcm.hpp index b1b2ae1c5..8f3f55197 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 726272f91..1fc698b36 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 b21555d06..000000000 --- a/src/dummy.cxx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Just a dummy C++ file that is linked to work around an automake - * bug: automake uses CXXLD when at least one source is C++, but when - * you link a static library with a C++ source, it uses CCLD. This - * causes linker problems (undefined reference to 'operator - * delete(void*)'), because CCLD does not link with libstdc++. - * - * By linking with this empty C++ source, automake decides to use - * CXXLD. - * - */ diff --git a/src/encoder/OggStream.hxx b/src/encoder/OggStream.hxx new file mode 100644 index 000000000..ce847f491 --- /dev/null +++ b/src/encoder/OggStream.hxx @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OGG_STREAM_HXX +#define MPD_OGG_STREAM_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +#include <assert.h> +#include <string.h> +#include <stdint.h> + +class OggStream { + ogg_stream_state state; + + bool flush; + +#ifndef NDEBUG + bool initialized; +#endif + +public: +#ifndef NDEBUG + OggStream():initialized(false) {} + ~OggStream() { + assert(!initialized); + } +#endif + + void Initialize(int serialno) { + assert(!initialized); + + ogg_stream_init(&state, serialno); + + /* set "flush" to true, so the caller gets the full + headers on the first read() */ + flush = true; + +#ifndef NDEBUG + initialized = true; +#endif + } + + void Reinitialize(int serialno) { + assert(initialized); + + ogg_stream_reset_serialno(&state, serialno); + + /* set "flush" to true, so the caller gets the full + headers on the first read() */ + flush = true; + } + + void Deinitialize() { + assert(initialized); + + ogg_stream_clear(&state); + +#ifndef NDEBUG + initialized = false; +#endif + } + + void Flush() { + assert(initialized); + + flush = true; + } + + void PacketIn(const ogg_packet &packet) { + assert(initialized); + + ogg_stream_packetin(&state, + const_cast<ogg_packet *>(&packet)); + } + + bool PageOut(ogg_page &page) { + int result = ogg_stream_pageout(&state, &page); + if (result == 0 && flush) { + flush = false; + result = ogg_stream_flush(&state, &page); + } + + return result != 0; + } + + size_t PageOut(void *_buffer, size_t size) { + ogg_page page; + if (!PageOut(page)) + return 0; + + assert(page.header_len > 0 || page.body_len > 0); + + size_t header_len = (size_t)page.header_len; + size_t body_len = (size_t)page.body_len; + assert(header_len <= size); + + if (header_len + body_len > size) + /* TODO: better overflow handling */ + body_len = size - header_len; + + uint8_t *buffer = (uint8_t *)_buffer; + memcpy(buffer, page.header, header_len); + memcpy(buffer + header_len, page.body, body_len); + + return header_len + body_len; + } +}; + +#endif diff --git a/src/encoder/OpusEncoderPlugin.cxx b/src/encoder/OpusEncoderPlugin.cxx new file mode 100644 index 000000000..8d2c0974b --- /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 000000000..f54377202 --- /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/VorbisEncoderPlugin.cxx b/src/encoder/VorbisEncoderPlugin.cxx new file mode 100644 index 000000000..dc7ef0d5e --- /dev/null +++ b/src/encoder/VorbisEncoderPlugin.cxx @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "VorbisEncoderPlugin.hxx" +#include "OggStream.hxx" +#include "encoder_api.h" +#include "encoder_plugin.h" +#include "tag.h" +#include "audio_format.h" +#include "mpd_error.h" + +#include <vorbis/vorbisenc.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "vorbis_encoder" + +struct vorbis_encoder { + /** the base class */ + struct encoder encoder; + + /* configuration */ + + float quality; + int bitrate; + + /* runtime information */ + + struct audio_format audio_format; + + vorbis_dsp_state vd; + vorbis_block vb; + vorbis_info vi; + + OggStream stream; + + vorbis_encoder() { + encoder_struct_init(&encoder, &vorbis_encoder_plugin); + } +}; + +static inline GQuark +vorbis_encoder_quark(void) +{ + return g_quark_from_static_string("vorbis_encoder"); +} + +static bool +vorbis_encoder_configure(struct vorbis_encoder *encoder, + const struct config_param *param, GError **error) +{ + const char *value = config_get_block_string(param, "quality", nullptr); + if (value != nullptr) { + /* a quality was configured (VBR) */ + + char *endptr; + encoder->quality = g_ascii_strtod(value, &endptr); + + if (*endptr != '\0' || encoder->quality < -1.0 || + encoder->quality > 10.0) { + g_set_error(error, vorbis_encoder_quark(), 0, + "quality \"%s\" is not a number in the " + "range -1 to 10, line %i", + value, param->line); + return false; + } + + if (config_get_block_string(param, "bitrate", nullptr) != nullptr) { + g_set_error(error, vorbis_encoder_quark(), 0, + "quality and bitrate are " + "both defined (line %i)", + param->line); + return false; + } + } else { + /* a bit rate was configured */ + + value = 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", + param->line); + return false; + } + + encoder->quality = -2.0; + + char *endptr; + encoder->bitrate = g_ascii_strtoll(value, &endptr, 10); + if (*endptr != '\0' || encoder->bitrate <= 0) { + g_set_error(error, vorbis_encoder_quark(), 0, + "bitrate at line %i should be a positive integer", + param->line); + return false; + } + } + + return true; +} + +static struct encoder * +vorbis_encoder_init(const struct config_param *param, GError **error) +{ + vorbis_encoder *encoder = new vorbis_encoder(); + + /* load configuration from "param" */ + if (!vorbis_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + delete encoder; + return nullptr; + } + + return &encoder->encoder; +} + +static void +vorbis_encoder_finish(struct encoder *_encoder) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + /* the real libvorbis/libogg cleanup was already performed by + vorbis_encoder_close(), so no real work here */ + delete encoder; +} + +static bool +vorbis_encoder_reinit(struct vorbis_encoder *encoder, GError **error) +{ + vorbis_info_init(&encoder->vi); + + if (encoder->quality >= -1.0) { + /* a quality was configured (VBR) */ + + if (0 != vorbis_encode_init_vbr(&encoder->vi, + encoder->audio_format.channels, + encoder->audio_format.sample_rate, + encoder->quality * 0.1)) { + g_set_error(error, vorbis_encoder_quark(), 0, + "error initializing vorbis vbr"); + vorbis_info_clear(&encoder->vi); + return false; + } + } else { + /* a bit rate was configured */ + + if (0 != vorbis_encode_init(&encoder->vi, + encoder->audio_format.channels, + encoder->audio_format.sample_rate, -1.0, + encoder->bitrate * 1000, -1.0)) { + g_set_error(error, vorbis_encoder_quark(), 0, + "error initializing vorbis encoder"); + vorbis_info_clear(&encoder->vi); + return false; + } + } + + vorbis_analysis_init(&encoder->vd, &encoder->vi); + vorbis_block_init(&encoder->vd, &encoder->vb); + encoder->stream.Initialize(g_random_int()); + + return true; +} + +static void +vorbis_encoder_headerout(struct vorbis_encoder *encoder, vorbis_comment *vc) +{ + ogg_packet packet, comments, codebooks; + + vorbis_analysis_headerout(&encoder->vd, vc, + &packet, &comments, &codebooks); + + encoder->stream.PacketIn(packet); + encoder->stream.PacketIn(comments); + encoder->stream.PacketIn(codebooks); +} + +static void +vorbis_encoder_send_header(struct vorbis_encoder *encoder) +{ + vorbis_comment vc; + + vorbis_comment_init(&vc); + vorbis_encoder_headerout(encoder, &vc); + vorbis_comment_clear(&vc); +} + +static bool +vorbis_encoder_open(struct encoder *_encoder, + struct audio_format *audio_format, + GError **error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + audio_format->format = SAMPLE_FORMAT_FLOAT; + + encoder->audio_format = *audio_format; + + if (!vorbis_encoder_reinit(encoder, error)) + return false; + + vorbis_encoder_send_header(encoder); + + return true; +} + +static void +vorbis_encoder_clear(struct vorbis_encoder *encoder) +{ + encoder->stream.Deinitialize(); + vorbis_block_clear(&encoder->vb); + vorbis_dsp_clear(&encoder->vd); + vorbis_info_clear(&encoder->vi); +} + +static void +vorbis_encoder_close(struct encoder *_encoder) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + vorbis_encoder_clear(encoder); +} + +static void +vorbis_encoder_blockout(struct vorbis_encoder *encoder) +{ + while (vorbis_analysis_blockout(&encoder->vd, &encoder->vb) == 1) { + vorbis_analysis(&encoder->vb, nullptr); + vorbis_bitrate_addblock(&encoder->vb); + + ogg_packet packet; + while (vorbis_bitrate_flushpacket(&encoder->vd, &packet)) + encoder->stream.PacketIn(packet); + } +} + +static bool +vorbis_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + encoder->stream.Flush(); + return true; +} + +static bool +vorbis_encoder_pre_tag(struct encoder *_encoder, G_GNUC_UNUSED GError **error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + vorbis_analysis_wrote(&encoder->vd, 0); + vorbis_encoder_blockout(encoder); + + /* reinitialize vorbis_dsp_state and vorbis_block to reset the + end-of-stream marker */ + vorbis_block_clear(&encoder->vb); + vorbis_dsp_clear(&encoder->vd); + vorbis_analysis_init(&encoder->vd, &encoder->vi); + vorbis_block_init(&encoder->vd, &encoder->vb); + + encoder->stream.Flush(); + return true; +} + +static void +copy_tag_to_vorbis_comment(vorbis_comment *vc, const struct tag *tag) +{ + for (unsigned i = 0; i < tag->num_items; i++) { + struct tag_item *item = tag->items[i]; + char *name = g_ascii_strup(tag_item_names[item->type], -1); + vorbis_comment_add_tag(vc, name, item->value); + g_free(name); + } +} + +static bool +vorbis_encoder_tag(struct encoder *_encoder, const struct tag *tag, + G_GNUC_UNUSED GError **error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + vorbis_comment comment; + + /* write the vorbis_comment object */ + + vorbis_comment_init(&comment); + copy_tag_to_vorbis_comment(&comment, tag); + + /* reset ogg_stream_state and begin a new stream */ + + encoder->stream.Reinitialize(g_random_int()); + + /* send that vorbis_comment to the ogg_stream_state */ + + vorbis_encoder_headerout(encoder, &comment); + vorbis_comment_clear(&comment); + + return true; +} + +static void +interleaved_to_vorbis_buffer(float **dest, const float *src, + unsigned num_frames, unsigned num_channels) +{ + for (unsigned i = 0; i < num_frames; i++) + for (unsigned j = 0; j < num_channels; j++) + dest[j][i] = *src++; +} + +static bool +vorbis_encoder_write(struct encoder *_encoder, + const void *data, size_t length, + G_GNUC_UNUSED GError **error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + unsigned num_frames = length + / audio_format_frame_size(&encoder->audio_format); + + /* this is for only 16-bit audio */ + + interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd, + num_frames), + (const float *)data, + num_frames, + encoder->audio_format.channels); + + vorbis_analysis_wrote(&encoder->vd, num_frames); + vorbis_encoder_blockout(encoder); + return true; +} + +static size_t +vorbis_encoder_read(struct encoder *_encoder, void *dest, size_t length) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + return encoder->stream.PageOut(dest, length); +} + +static const char * +vorbis_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +{ + return "audio/ogg"; +} + +const struct encoder_plugin vorbis_encoder_plugin = { + "vorbis", + vorbis_encoder_init, + vorbis_encoder_finish, + vorbis_encoder_open, + vorbis_encoder_close, + vorbis_encoder_pre_tag, + vorbis_encoder_flush, + vorbis_encoder_pre_tag, + vorbis_encoder_tag, + vorbis_encoder_write, + vorbis_encoder_read, + vorbis_encoder_get_mime_type, +}; diff --git a/src/encoder/VorbisEncoderPlugin.hxx b/src/encoder/VorbisEncoderPlugin.hxx new file mode 100644 index 000000000..4cddf1b11 --- /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 e32588e29..db6503fb0 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 3bb99ea28..53594d34e 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 48cdf139b..b973adf76 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 934b2ab24..ff184977b 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/vorbis_encoder.c b/src/encoder/vorbis_encoder.c deleted file mode 100644 index 468cf38ee..000000000 --- a/src/encoder/vorbis_encoder.c +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "encoder_api.h" -#include "encoder_plugin.h" -#include "tag.h" -#include "audio_format.h" -#include "mpd_error.h" - -#include <vorbis/vorbisenc.h> - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "vorbis_encoder" - -struct vorbis_encoder { - /** the base class */ - struct encoder encoder; - - /* configuration */ - - float quality; - int bitrate; - - /* runtime information */ - - struct audio_format audio_format; - - ogg_stream_state os; - - vorbis_dsp_state vd; - vorbis_block vb; - vorbis_info vi; - - bool flush; -}; - -extern const struct encoder_plugin vorbis_encoder_plugin; - -static inline GQuark -vorbis_encoder_quark(void) -{ - return g_quark_from_static_string("vorbis_encoder"); -} - -static bool -vorbis_encoder_configure(struct vorbis_encoder *encoder, - const struct config_param *param, GError **error) -{ - const char *value = config_get_block_string(param, "quality", NULL); - if (value != NULL) { - /* a quality was configured (VBR) */ - - char *endptr; - encoder->quality = g_ascii_strtod(value, &endptr); - - if (*endptr != '\0' || encoder->quality < -1.0 || - encoder->quality > 10.0) { - g_set_error(error, vorbis_encoder_quark(), 0, - "quality \"%s\" is not a number in the " - "range -1 to 10, line %i", - value, param->line); - return false; - } - - if (config_get_block_string(param, "bitrate", NULL) != NULL) { - g_set_error(error, vorbis_encoder_quark(), 0, - "quality and bitrate are " - "both defined (line %i)", - param->line); - return false; - } - } else { - /* a bit rate was configured */ - - value = config_get_block_string(param, "bitrate", NULL); - if (value == NULL) { - g_set_error(error, vorbis_encoder_quark(), 0, - "neither bitrate nor quality defined " - "at line %i", - param->line); - return false; - } - - encoder->quality = -2.0; - - char *endptr; - encoder->bitrate = g_ascii_strtoll(value, &endptr, 10); - if (*endptr != '\0' || encoder->bitrate <= 0) { - g_set_error(error, vorbis_encoder_quark(), 0, - "bitrate at line %i should be a positive integer", - param->line); - return false; - } - } - - return true; -} - -static struct encoder * -vorbis_encoder_init(const struct config_param *param, GError **error) -{ - struct vorbis_encoder *encoder = g_new(struct vorbis_encoder, 1); - encoder_struct_init(&encoder->encoder, &vorbis_encoder_plugin); - - /* load configuration from "param" */ - if (!vorbis_encoder_configure(encoder, param, error)) { - /* configuration has failed, roll back and return error */ - g_free(encoder); - return NULL; - } - - return &encoder->encoder; -} - -static void -vorbis_encoder_finish(struct encoder *_encoder) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - /* the real libvorbis/libogg cleanup was already performed by - vorbis_encoder_close(), so no real work here */ - g_free(encoder); -} - -static bool -vorbis_encoder_reinit(struct vorbis_encoder *encoder, GError **error) -{ - vorbis_info_init(&encoder->vi); - - if (encoder->quality >= -1.0) { - /* a quality was configured (VBR) */ - - if (0 != vorbis_encode_init_vbr(&encoder->vi, - encoder->audio_format.channels, - encoder->audio_format.sample_rate, - encoder->quality * 0.1)) { - g_set_error(error, vorbis_encoder_quark(), 0, - "error initializing vorbis vbr"); - vorbis_info_clear(&encoder->vi); - return false; - } - } else { - /* a bit rate was configured */ - - if (0 != vorbis_encode_init(&encoder->vi, - encoder->audio_format.channels, - encoder->audio_format.sample_rate, -1.0, - encoder->bitrate * 1000, -1.0)) { - g_set_error(error, vorbis_encoder_quark(), 0, - "error initializing vorbis encoder"); - vorbis_info_clear(&encoder->vi); - return false; - } - } - - vorbis_analysis_init(&encoder->vd, &encoder->vi); - vorbis_block_init(&encoder->vd, &encoder->vb); - ogg_stream_init(&encoder->os, g_random_int()); - - return true; -} - -static void -vorbis_encoder_headerout(struct vorbis_encoder *encoder, vorbis_comment *vc) -{ - ogg_packet packet, comments, codebooks; - - vorbis_analysis_headerout(&encoder->vd, vc, - &packet, &comments, &codebooks); - - ogg_stream_packetin(&encoder->os, &packet); - ogg_stream_packetin(&encoder->os, &comments); - ogg_stream_packetin(&encoder->os, &codebooks); -} - -static void -vorbis_encoder_send_header(struct vorbis_encoder *encoder) -{ - vorbis_comment vc; - - vorbis_comment_init(&vc); - vorbis_encoder_headerout(encoder, &vc); - vorbis_comment_clear(&vc); -} - -static bool -vorbis_encoder_open(struct encoder *_encoder, - struct audio_format *audio_format, - GError **error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - audio_format->format = SAMPLE_FORMAT_S16; - - encoder->audio_format = *audio_format; - - if (!vorbis_encoder_reinit(encoder, error)) - return false; - - vorbis_encoder_send_header(encoder); - - /* set "flush" to true, so the caller gets the full headers on - the first read() */ - encoder->flush = true; - - return true; -} - -static void -vorbis_encoder_clear(struct vorbis_encoder *encoder) -{ - ogg_stream_clear(&encoder->os); - vorbis_block_clear(&encoder->vb); - vorbis_dsp_clear(&encoder->vd); - vorbis_info_clear(&encoder->vi); -} - -static void -vorbis_encoder_close(struct encoder *_encoder) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - vorbis_encoder_clear(encoder); -} - -static void -vorbis_encoder_blockout(struct vorbis_encoder *encoder) -{ - while (vorbis_analysis_blockout(&encoder->vd, &encoder->vb) == 1) { - vorbis_analysis(&encoder->vb, NULL); - vorbis_bitrate_addblock(&encoder->vb); - - ogg_packet packet; - while (vorbis_bitrate_flushpacket(&encoder->vd, &packet)) - ogg_stream_packetin(&encoder->os, &packet); - } -} - -static bool -vorbis_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - encoder->flush = true; - return true; -} - -static bool -vorbis_encoder_pre_tag(struct encoder *_encoder, G_GNUC_UNUSED GError **error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - vorbis_analysis_wrote(&encoder->vd, 0); - vorbis_encoder_blockout(encoder); - - /* reinitialize vorbis_dsp_state and vorbis_block to reset the - end-of-stream marker */ - vorbis_block_clear(&encoder->vb); - vorbis_dsp_clear(&encoder->vd); - vorbis_analysis_init(&encoder->vd, &encoder->vi); - vorbis_block_init(&encoder->vd, &encoder->vb); - - encoder->flush = true; - return true; -} - -static void -copy_tag_to_vorbis_comment(vorbis_comment *vc, const struct tag *tag) -{ - for (unsigned i = 0; i < tag->num_items; i++) { - struct tag_item *item = tag->items[i]; - char *name = g_ascii_strup(tag_item_names[item->type], -1); - vorbis_comment_add_tag(vc, name, item->value); - g_free(name); - } -} - -static bool -vorbis_encoder_tag(struct encoder *_encoder, const struct tag *tag, - G_GNUC_UNUSED GError **error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - vorbis_comment comment; - - /* write the vorbis_comment object */ - - vorbis_comment_init(&comment); - copy_tag_to_vorbis_comment(&comment, tag); - - /* reset ogg_stream_state and begin a new stream */ - - ogg_stream_reset_serialno(&encoder->os, g_random_int()); - - /* send that vorbis_comment to the ogg_stream_state */ - - vorbis_encoder_headerout(encoder, &comment); - vorbis_comment_clear(&comment); - - /* the next vorbis_encoder_read() call should flush the - ogg_stream_state */ - - encoder->flush = true; - - return true; -} - -static void -pcm16_to_vorbis_buffer(float **dest, const int16_t *src, - unsigned num_frames, unsigned num_channels) -{ - for (unsigned i = 0; i < num_frames; i++) - for (unsigned j = 0; j < num_channels; j++) - dest[j][i] = *src++ / 32768.0; -} - -static bool -vorbis_encoder_write(struct encoder *_encoder, - const void *data, size_t length, - G_GNUC_UNUSED GError **error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - unsigned num_frames = length - / audio_format_frame_size(&encoder->audio_format); - - /* this is for only 16-bit audio */ - - pcm16_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd, - num_frames), - (const int16_t *)data, - num_frames, encoder->audio_format.channels); - - vorbis_analysis_wrote(&encoder->vd, num_frames); - vorbis_encoder_blockout(encoder); - return true; -} - -static size_t -vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - unsigned char *dest = _dest; - - ogg_page page; - int ret = ogg_stream_pageout(&encoder->os, &page); - if (ret == 0 && encoder->flush) { - encoder->flush = false; - ret = ogg_stream_flush(&encoder->os, &page); - - } - - if (ret == 0) - return 0; - - assert(page.header_len > 0 || page.body_len > 0); - - size_t nbytes = (size_t)page.header_len + (size_t)page.body_len; - - if (nbytes > length) - /* XXX better error handling */ - MPD_ERROR("buffer too small"); - - memcpy(dest, page.header, page.header_len); - memcpy(dest + page.header_len, page.body, page.body_len); - - return nbytes; -} - -static const char * -vorbis_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) -{ - return "audio/ogg"; -} - -const struct encoder_plugin vorbis_encoder_plugin = { - .name = "vorbis", - .init = vorbis_encoder_init, - .finish = vorbis_encoder_finish, - .open = vorbis_encoder_open, - .close = vorbis_encoder_close, - .end = vorbis_encoder_pre_tag, - .flush = vorbis_encoder_flush, - .pre_tag = vorbis_encoder_pre_tag, - .tag = vorbis_encoder_tag, - .write = vorbis_encoder_write, - .read = vorbis_encoder_read, - .get_mime_type = vorbis_encoder_get_mime_type, -}; diff --git a/src/encoder/wave_encoder.c b/src/encoder/wave_encoder.c index 9eeb4d513..9b3c21fca 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 2326c1099..029b4be34 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 fb1c9bf9c..31663c751 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 3a42d79f4..e0748a136 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 000000000..05e703441 --- /dev/null +++ b/src/event/BufferedSocket.cxx @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "BufferedSocket.hxx" +#include "SocketError.hxx" +#include "util/fifo_buffer.h" + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +BufferedSocket::~BufferedSocket() +{ + if (input != nullptr) + fifo_buffer_free(input); +} + +BufferedSocket::ssize_t +BufferedSocket::DirectRead(void *data, size_t length) +{ + const auto nbytes = SocketMonitor::Read((char *)data, length); + if (gcc_likely(nbytes > 0)) + return nbytes; + + if (nbytes == 0) { + OnSocketClosed(); + return -1; + } + + const auto code = GetSocketError(); + if (IsSocketErrorAgain(code)) + return 0; + + if (IsSocketErrorClosed(code)) + OnSocketClosed(); + else + OnSocketError(NewSocketError(code)); + return -1; +} + +bool +BufferedSocket::ReadToBuffer() +{ + assert(IsDefined()); + + if (input == nullptr) + input = fifo_buffer_new(8192); + + size_t length; + void *buffer = fifo_buffer_write(input, &length); + assert(buffer != nullptr); + + const auto nbytes = DirectRead(buffer, length); + if (nbytes > 0) + fifo_buffer_append(input, nbytes); + + return nbytes >= 0; +} + +bool +BufferedSocket::ResumeInput() +{ + assert(IsDefined()); + + if (input == nullptr) { + ScheduleRead(); + return true; + } + + while (true) { + size_t length; + const void *data = fifo_buffer_read(input, &length); + if (data == nullptr) { + ScheduleRead(); + return true; + } + + const auto result = OnSocketInput(data, length); + switch (result) { + case InputResult::MORE: + if (fifo_buffer_is_full(input)) { + // TODO + OnSocketError(g_error_new_literal(g_quark_from_static_string("buffered_socket"), + 0, "Input buffer is full")); + return false; + } + + ScheduleRead(); + return true; + + case InputResult::PAUSE: + CancelRead(); + return true; + + case InputResult::AGAIN: + continue; + + case InputResult::CLOSED: + return false; + } + } +} + +void +BufferedSocket::ConsumeInput(size_t nbytes) +{ + assert(IsDefined()); + + fifo_buffer_consume(input, nbytes); +} + +bool +BufferedSocket::OnSocketReady(unsigned flags) +{ + assert(IsDefined()); + + if (gcc_unlikely(flags & (ERROR|HANGUP))) { + OnSocketClosed(); + return false; + } + + if (flags & READ) { + assert(input == nullptr || !fifo_buffer_is_full(input)); + + if (!ReadToBuffer() || !ResumeInput()) + return false; + + if (input == nullptr || !fifo_buffer_is_full(input)) + ScheduleRead(); + } + + return true; +} diff --git a/src/event/BufferedSocket.hxx b/src/event/BufferedSocket.hxx new file mode 100644 index 000000000..86deb8d98 --- /dev/null +++ b/src/event/BufferedSocket.hxx @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_BUFFERED_SOCKET_HXX +#define MPD_BUFFERED_SOCKET_HXX + +#include "check.h" +#include "SocketMonitor.hxx" +#include "gcc.h" + +struct fifo_buffer; + +/** + * A #SocketMonitor specialization that adds an input buffer. + */ +class BufferedSocket : protected SocketMonitor { + fifo_buffer *input; + +public: + BufferedSocket(int _fd, EventLoop &_loop) + :SocketMonitor(_fd, _loop), input(nullptr) { + ScheduleRead(); + } + + ~BufferedSocket(); + + using SocketMonitor::IsDefined; + using SocketMonitor::Close; + using SocketMonitor::Write; + +private: + ssize_t DirectRead(void *data, size_t length); + + /** + * Receive data from the socket to the input buffer. + * + * @return false if the socket has been closed + */ + bool ReadToBuffer(); + +protected: + /** + * @return false if the socket has been closed + */ + bool ResumeInput(); + + /** + * Mark a portion of the input buffer "consumed". Only + * allowed to be called from OnSocketInput(). This method + * does not invalidate the pointer passed to OnSocketInput() + * yet. + */ + void ConsumeInput(size_t nbytes); + + enum class InputResult { + /** + * The method was successful, and it is ready to + * read more data. + */ + MORE, + + /** + * The method does not want to get more data for now. + * It will call ResumeInput() when it's ready for + * more. + */ + PAUSE, + + /** + * The method wants to be called again immediately, if + * there's more data in the buffer. + */ + AGAIN, + + /** + * The method has closed the socket. + */ + CLOSED, + }; + + virtual InputResult OnSocketInput(const void *data, size_t length) = 0; + virtual void OnSocketError(GError *error) = 0; + virtual void OnSocketClosed() = 0; + + virtual bool OnSocketReady(unsigned flags) override; +}; + +#endif diff --git a/src/event/FullyBufferedSocket.cxx b/src/event/FullyBufferedSocket.cxx new file mode 100644 index 000000000..a92cb68aa --- /dev/null +++ b/src/event/FullyBufferedSocket.cxx @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FullyBufferedSocket.hxx" +#include "SocketError.hxx" +#include "util/fifo_buffer.h" + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +#ifndef WIN32 +#include <sys/types.h> +#include <sys/socket.h> +#endif + +FullyBufferedSocket::ssize_t +FullyBufferedSocket::DirectWrite(const void *data, size_t length) +{ + const auto nbytes = SocketMonitor::Write((const char *)data, length); + if (gcc_unlikely(nbytes < 0)) { + const auto code = GetSocketError(); + if (IsSocketErrorAgain(code)) + return 0; + + Cancel(); + + if (IsSocketErrorClosed(code)) + OnSocketClosed(); + else + OnSocketError(NewSocketError(code)); + } + + return nbytes; +} + +bool +FullyBufferedSocket::WriteFromBuffer() +{ + assert(IsDefined()); + + size_t length; + const void *data = output.Read(&length); + if (data == nullptr) { + CancelWrite(); + return true; + } + + auto nbytes = DirectWrite(data, length); + if (gcc_unlikely(nbytes <= 0)) + return nbytes == 0; + + output.Consume(nbytes); + + if (output.IsEmpty()) + CancelWrite(); + + return true; +} + +bool +FullyBufferedSocket::Write(const void *data, size_t length) +{ + assert(IsDefined()); + +#if 0 + /* TODO: disabled because this would add overhead on some callers (the ones that often), but it may be useful */ + + if (output.IsEmpty()) { + /* try to write it directly first */ + const auto nbytes = DirectWrite(data, length); + if (gcc_likely(nbytes > 0)) { + data = (const uint8_t *)data + nbytes; + length -= nbytes; + if (length == 0) + return true; + } else if (nbytes < 0) + return false; + } +#endif + + if (!output.Append(data, length)) { + // TODO + OnSocketError(g_error_new_literal(g_quark_from_static_string("buffered_socket"), + 0, "Output buffer is full")); + return false; + } + + ScheduleWrite(); + return true; +} + +bool +FullyBufferedSocket::OnSocketReady(unsigned flags) +{ + const bool was_empty = output.IsEmpty(); + if (!BufferedSocket::OnSocketReady(flags)) + return false; + + if (was_empty && !output.IsEmpty()) + /* just in case the OnSocketInput() method has added + data to the output buffer: try to send it now + instead of waiting for the next event loop + iteration */ + flags |= WRITE; + + if (flags & WRITE) { + assert(!output.IsEmpty()); + + if (!WriteFromBuffer()) + return false; + } + + return true; +} diff --git a/src/event/FullyBufferedSocket.hxx b/src/event/FullyBufferedSocket.hxx new file mode 100644 index 000000000..c67c2c78d --- /dev/null +++ b/src/event/FullyBufferedSocket.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FULLY_BUFFERED_SOCKET_HXX +#define MPD_FULLY_BUFFERED_SOCKET_HXX + +#include "check.h" +#include "BufferedSocket.hxx" +#include "util/PeakBuffer.hxx" +#include "gcc.h" + +/** + * A #BufferedSocket specialization that adds an output buffer. + */ +class FullyBufferedSocket : protected BufferedSocket { + PeakBuffer output; + +public: + FullyBufferedSocket(int _fd, EventLoop &_loop, + size_t normal_size, size_t peak_size=0) + :BufferedSocket(_fd, _loop), + output(normal_size, peak_size) { + } + + using BufferedSocket::IsDefined; + using BufferedSocket::Close; + +private: + ssize_t DirectWrite(const void *data, size_t length); + + /** + * Send data from the output buffer to the socket. + * + * @return false if the socket has been closed + */ + bool WriteFromBuffer(); + +protected: + /** + * @return false if the socket has been closed + */ + bool Write(const void *data, size_t length); + + virtual bool OnSocketReady(unsigned flags) override; +}; + +#endif diff --git a/src/event/Loop.hxx b/src/event/Loop.hxx new file mode 100644 index 000000000..72731ea2b --- /dev/null +++ b/src/event/Loop.hxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_EVENT_LOOP_HXX +#define MPD_EVENT_LOOP_HXX + +#include "check.h" +#include "gcc.h" + +#include <glib.h> + +class EventLoop { + GMainContext *context; + GMainLoop *loop; + +public: + EventLoop() + :context(g_main_context_new()), + loop(g_main_loop_new(context, false)) {} + + struct Default {}; + EventLoop(gcc_unused Default _dummy) + :context(g_main_context_ref(g_main_context_default())), + loop(g_main_loop_new(context, false)) {} + + ~EventLoop() { + g_main_loop_unref(loop); + g_main_context_unref(context); + } + + GMainContext *GetContext() { + return context; + } + + void WakeUp() { + g_main_context_wakeup(context); + } + + void Break() { + g_main_loop_quit(loop); + } + + void Run() { + g_main_loop_run(loop); + } + + guint AddIdle(GSourceFunc function, gpointer data) { + GSource *source = g_idle_source_new(); + g_source_set_callback(source, function, data, NULL); + guint id = g_source_attach(source, GetContext()); + g_source_unref(source); + return id; + } + + GSource *AddTimeout(guint interval_ms, + GSourceFunc function, gpointer data) { + GSource *source = g_timeout_source_new(interval_ms); + g_source_set_callback(source, function, data, nullptr); + g_source_attach(source, GetContext()); + return source; + } + + GSource *AddTimeoutSeconds(guint interval_s, + GSourceFunc function, gpointer data) { + GSource *source = g_timeout_source_new_seconds(interval_s); + g_source_set_callback(source, function, data, nullptr); + g_source_attach(source, GetContext()); + return source; + } +}; + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event/MultiSocketMonitor.cxx b/src/event/MultiSocketMonitor.cxx new file mode 100644 index 000000000..6f20b907c --- /dev/null +++ b/src/event/MultiSocketMonitor.cxx @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MultiSocketMonitor.hxx" +#include "Loop.hxx" +#include "fd_util.h" +#include "gcc.h" + +#include <assert.h> + +/** + * The vtable for our GSource implementation. Unfortunately, we + * cannot declare it "const", because g_source_new() takes a non-const + * pointer, for whatever reason. + */ +static GSourceFuncs multi_socket_monitor_source_funcs = { + MultiSocketMonitor::Prepare, + MultiSocketMonitor::Check, + MultiSocketMonitor::Dispatch, + nullptr, + nullptr, + nullptr, +}; + +MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop) + :loop(_loop), + source((Source *)g_source_new(&multi_socket_monitor_source_funcs, + sizeof(*source))) { + source->monitor = this; + + g_source_attach(&source->base, loop.GetContext()); +} + +MultiSocketMonitor::~MultiSocketMonitor() +{ + g_source_destroy(&source->base); + g_source_unref(&source->base); + source = nullptr; +} + +bool +MultiSocketMonitor::Check() const +{ + if (CheckSockets()) + return true; + + for (const auto &i : fds) + if (i.revents != 0) + return true; + + return false; +} + +/* + * GSource methods + * + */ + +gboolean +MultiSocketMonitor::Prepare(GSource *_source, gint *timeout_r) +{ + Source &source = *(Source *)_source; + MultiSocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + return monitor.Prepare(timeout_r); +} + +gboolean +MultiSocketMonitor::Check(GSource *_source) +{ + const Source &source = *(const Source *)_source; + const MultiSocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + return monitor.Check(); +} + +gboolean +MultiSocketMonitor::Dispatch(GSource *_source, + gcc_unused GSourceFunc callback, + gcc_unused gpointer user_data) +{ + Source &source = *(Source *)_source; + MultiSocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + monitor.Dispatch(); + return true; +} diff --git a/src/event/MultiSocketMonitor.hxx b/src/event/MultiSocketMonitor.hxx new file mode 100644 index 000000000..bf0a221a2 --- /dev/null +++ b/src/event/MultiSocketMonitor.hxx @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MULTI_SOCKET_MONITOR_HXX +#define MPD_MULTI_SOCKET_MONITOR_HXX + +#include "check.h" +#include "gcc.h" +#include "glib_compat.h" + +#include <glib.h> + +#include <forward_list> + +#include <assert.h> + +#ifdef WIN32 +/* ERRORis a WIN32 macro that poisons our namespace; this is a + kludge to allow us to use it anyway */ +#ifdef ERROR +#undef ERROR +#endif +#endif + +class EventLoop; + +/** + * Monitor multiple sockets. + */ +class MultiSocketMonitor { + struct Source { + GSource base; + + MultiSocketMonitor *monitor; + }; + + EventLoop &loop; + Source *source; + std::forward_list<GPollFD> fds; + +public: + static constexpr unsigned READ = G_IO_IN; + static constexpr unsigned WRITE = G_IO_OUT; + static constexpr unsigned ERROR = G_IO_ERR; + static constexpr unsigned HANGUP = G_IO_HUP; + + MultiSocketMonitor(EventLoop &_loop); + ~MultiSocketMonitor(); + +public: + gcc_pure + gint64 GetTime() const { + return g_source_get_time(&source->base); + } + + void InvalidateSockets() { + /* no-op because GLib always calls the GSource's + "prepare" method before each poll() anyway */ + } + + void AddSocket(int fd, unsigned events) { + fds.push_front({fd, gushort(events), 0}); + g_source_add_poll(&source->base, &fds.front()); + } + + template<typename E> + void UpdateSocketList(E &&e) { + for (auto prev = fds.before_begin(), end = fds.end(), + i = std::next(prev); + i != end; i = std::next(prev)) { + assert(i->events != 0); + + unsigned events = e(i->fd); + if (events != 0) { + i->events = events; + prev = i; + } else { + g_source_remove_poll(&source->base, &*i); + fds.erase_after(prev); + } + } + } + +protected: + virtual void PrepareSockets(gcc_unused gint *timeout_r) {} + virtual bool CheckSockets() const { return false; } + virtual void DispatchSockets() = 0; + +public: + /* GSource callbacks */ + static gboolean Prepare(GSource *source, gint *timeout_r); + static gboolean Check(GSource *source); + static gboolean Dispatch(GSource *source, GSourceFunc callback, + gpointer user_data); + +private: + bool Prepare(gint *timeout_r) { + PrepareSockets(timeout_r); + return false; + } + + bool Check() const; + + void Dispatch() { + DispatchSockets(); + } +}; + +#endif diff --git a/src/event/ServerSocket.cxx b/src/event/ServerSocket.cxx new file mode 100644 index 000000000..119bfe1d7 --- /dev/null +++ b/src/event/ServerSocket.cxx @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#ifdef HAVE_STRUCT_UCRED +#define _GNU_SOURCE 1 +#endif + +#include "ServerSocket.hxx" +#include "SocketUtil.hxx" +#include "SocketError.hxx" +#include "event/SocketMonitor.hxx" +#include "resolver.h" +#include "fd_util.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netdb.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "listen" + +#define DEFAULT_PORT 6600 + +class OneServerSocket final : private SocketMonitor { + ServerSocket &parent; + + const unsigned serial; + + char *path; + + size_t address_length; + struct sockaddr *address; + +public: + OneServerSocket(EventLoop &_loop, ServerSocket &_parent, + unsigned _serial, + const struct sockaddr *_address, + size_t _address_length) + :SocketMonitor(_loop), + parent(_parent), serial(_serial), + path(nullptr), + address_length(_address_length), + address((sockaddr *)g_memdup(_address, _address_length)) + { + assert(_address != nullptr); + assert(_address_length > 0); + } + + OneServerSocket(const OneServerSocket &other) = delete; + OneServerSocket &operator=(const OneServerSocket &other) = delete; + + ~OneServerSocket() { + g_free(path); + g_free(address); + } + + unsigned GetSerial() const { + return serial; + } + + void SetPath(const char *_path) { + assert(path == nullptr); + + path = g_strdup(_path); + } + + bool Open(GError **error_r); + + using SocketMonitor::IsDefined; + using SocketMonitor::Close; + + char *ToString() const; + + void SetFD(int _fd) { + SocketMonitor::Open(_fd); + SocketMonitor::ScheduleRead(); + } + + void Accept(); + +private: + virtual bool OnSocketReady(unsigned flags) override; +}; + +static GQuark +server_socket_quark(void) +{ + return g_quark_from_static_string("server_socket"); +} + +/** + * Wraper for sockaddr_to_string() which never fails. + */ +char * +OneServerSocket::ToString() const +{ + char *p = sockaddr_to_string(address, address_length, nullptr); + if (p == nullptr) + p = g_strdup("[unknown]"); + return p; +} + +static int +get_remote_uid(int fd) +{ +#ifdef HAVE_STRUCT_UCRED + struct ucred cred; + socklen_t len = sizeof (cred); + + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0) + return 0; + + return cred.uid; +#else +#ifdef HAVE_GETPEEREID + uid_t euid; + gid_t egid; + + if (getpeereid(fd, &euid, &egid) == 0) + return euid; +#else + (void)fd; +#endif + return -1; +#endif +} + +inline void +OneServerSocket::Accept() +{ + struct sockaddr_storage peer_address; + size_t peer_address_length = sizeof(peer_address); + int peer_fd = + accept_cloexec_nonblock(Get(), (struct sockaddr*)&peer_address, + &peer_address_length); + if (peer_fd < 0) { + const SocketErrorMessage msg; + g_warning("accept() failed: %s", (const char *)msg); + return; + } + + if (socket_keepalive(peer_fd)) { + const SocketErrorMessage msg; + g_warning("Could not set TCP keepalive option: %s", + (const char *)msg); + } + + parent.OnAccept(peer_fd, + (const sockaddr &)peer_address, + peer_address_length, get_remote_uid(peer_fd)); +} + +bool +OneServerSocket::OnSocketReady(gcc_unused unsigned flags) +{ + Accept(); + return true; +} + +inline bool +OneServerSocket::Open(GError **error_r) +{ + assert(!IsDefined()); + + int _fd = socket_bind_listen(address->sa_family, + SOCK_STREAM, 0, + address, address_length, 5, + error_r); + if (_fd < 0) + return false; + + /* allow everybody to connect */ + + if (path != nullptr) + chmod(path, 0666); + + /* register in the GLib main loop */ + + SetFD(_fd); + + return true; +} + +ServerSocket::ServerSocket(EventLoop &_loop) + :loop(_loop), next_serial(1) {} + +/* this is just here to allow the OneServerSocket forward + declaration */ +ServerSocket::~ServerSocket() {} + +bool +ServerSocket::Open(GError **error_r) +{ + OneServerSocket *good = nullptr, *bad = nullptr; + GError *last_error = nullptr; + + for (auto &i : sockets) { + assert(i.GetSerial() > 0); + assert(good == nullptr || i.GetSerial() <= good->GetSerial()); + + if (bad != nullptr && i.GetSerial() != bad->GetSerial()) { + Close(); + g_propagate_error(error_r, last_error); + return false; + } + + GError *error = nullptr; + if (!i.Open(&error)) { + if (good != nullptr && good->GetSerial() == i.GetSerial()) { + char *address_string = i.ToString(); + char *good_string = good->ToString(); + g_warning("bind to '%s' failed: %s " + "(continuing anyway, because " + "binding to '%s' succeeded)", + address_string, error->message, + good_string); + g_free(address_string); + g_free(good_string); + g_error_free(error); + } else if (bad == nullptr) { + bad = &i; + + char *address_string = i.ToString(); + g_propagate_prefixed_error(&last_error, error, + "Failed to bind to '%s': ", + address_string); + g_free(address_string); + } else + g_error_free(error); + continue; + } + + /* mark this socket as "good", and clear previous + errors */ + + good = &i; + + if (bad != nullptr) { + bad = nullptr; + g_error_free(last_error); + last_error = nullptr; + } + } + + if (bad != nullptr) { + Close(); + g_propagate_error(error_r, last_error); + return false; + } + + return true; +} + +void +ServerSocket::Close() +{ + for (auto &i : sockets) + if (i.IsDefined()) + i.Close(); +} + +OneServerSocket & +ServerSocket::AddAddress(const sockaddr &address, size_t address_length) +{ + sockets.emplace_front(loop, *this, next_serial, + &address, address_length); + + return sockets.front(); +} + +bool +ServerSocket::AddFD(int fd, GError **error_r) +{ + assert(fd >= 0); + + struct sockaddr_storage address; + socklen_t address_length = sizeof(address); + if (getsockname(fd, (struct sockaddr *)&address, + &address_length) < 0) { + SetSocketError(error_r); + g_prefix_error(error_r, "Failed to get socket address"); + return false; + } + + OneServerSocket &s = AddAddress((const sockaddr &)address, + address_length); + s.SetFD(fd); + + return true; +} + +#ifdef HAVE_TCP + +inline void +ServerSocket::AddPortIPv4(unsigned port) +{ + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_port = htons(port); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + + AddAddress((const sockaddr &)sin, sizeof(sin)); +} + +#ifdef HAVE_IPV6 +inline void +ServerSocket::AddPortIPv6(unsigned port) +{ + struct sockaddr_in6 sin; + memset(&sin, 0, sizeof(sin)); + sin.sin6_port = htons(port); + sin.sin6_family = AF_INET6; + + AddAddress((const sockaddr &)sin, sizeof(sin)); +} +#endif /* HAVE_IPV6 */ + +#endif /* HAVE_TCP */ + +bool +ServerSocket::AddPort(unsigned port, GError **error_r) +{ +#ifdef HAVE_TCP + if (port == 0 || port > 0xffff) { + g_set_error(error_r, server_socket_quark(), 0, + "Invalid TCP port"); + return false; + } + +#ifdef HAVE_IPV6 + AddPortIPv6(port); +#endif + AddPortIPv4(port); + + ++next_serial; + + return true; +#else /* HAVE_TCP */ + (void)port; + + g_set_error(error_r, server_socket_quark(), 0, + "TCP support is disabled"); + return false; +#endif /* HAVE_TCP */ +} + +bool +ServerSocket::AddHost(const char *hostname, unsigned port, GError **error_r) +{ +#ifdef HAVE_TCP + struct addrinfo *ai = resolve_host_port(hostname, port, + AI_PASSIVE, SOCK_STREAM, + error_r); + if (ai == nullptr) + return false; + + for (const struct addrinfo *i = ai; i != nullptr; i = i->ai_next) + AddAddress(*i->ai_addr, i->ai_addrlen); + + freeaddrinfo(ai); + + ++next_serial; + + return true; +#else /* HAVE_TCP */ + (void)hostname; + (void)port; + + g_set_error(error_r, server_socket_quark(), 0, + "TCP support is disabled"); + return false; +#endif /* HAVE_TCP */ +} + +bool +ServerSocket::AddPath(const char *path, GError **error_r) +{ +#ifdef HAVE_UN + struct sockaddr_un s_un; + + size_t path_length = strlen(path); + if (path_length >= sizeof(s_un.sun_path)) { + g_set_error(error_r, server_socket_quark(), 0, + "UNIX socket path is too long"); + return false; + } + + unlink(path); + + s_un.sun_family = AF_UNIX; + memcpy(s_un.sun_path, path, path_length + 1); + + OneServerSocket &s = AddAddress((const sockaddr &)s_un, sizeof(s_un)); + s.SetPath(path); + + return true; +#else /* !HAVE_UN */ + (void)path; + + g_set_error(error_r, server_socket_quark(), 0, + "UNIX domain socket support is disabled"); + return false; +#endif /* !HAVE_UN */ +} + diff --git a/src/event/ServerSocket.hxx b/src/event/ServerSocket.hxx new file mode 100644 index 000000000..600cdf8a7 --- /dev/null +++ b/src/event/ServerSocket.hxx @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SERVER_SOCKET_HXX +#define MPD_SERVER_SOCKET_HXX + +#include "gerror.h" + +#include <forward_list> + +#include <stddef.h> + +struct sockaddr; +class EventLoop; + +typedef void (*server_socket_callback_t)(int fd, + const struct sockaddr *address, + size_t address_length, int uid, + void *ctx); + +class OneServerSocket; + +class ServerSocket { + friend class OneServerSocket; + + EventLoop &loop; + + std::forward_list<OneServerSocket> sockets; + + unsigned next_serial; + +public: + ServerSocket(EventLoop &_loop); + ~ServerSocket(); + + EventLoop &GetEventLoop() { + return loop; + } + +private: + OneServerSocket &AddAddress(const sockaddr &address, size_t length); + + /** + * Add a listener on a port on all IPv4 interfaces. + * + * @param port the TCP port + */ + void AddPortIPv4(unsigned port); + + /** + * Add a listener on a port on all IPv6 interfaces. + * + * @param port the TCP port + */ + void AddPortIPv6(unsigned port); + +public: + /** + * Add a listener on a port on all interfaces. + * + * @param port the TCP port + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return true on success + */ + bool AddPort(unsigned port, GError **error_r); + + /** + * Resolves a host name, and adds listeners on all addresses in the + * result set. + * + * @param hostname the host name to be resolved + * @param port the TCP port + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return true on success + */ + bool AddHost(const char *hostname, unsigned port, GError **error_r); + + /** + * Add a listener on a Unix domain socket. + * + * @param path the absolute socket path + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return true on success + */ + bool AddPath(const char *path, GError **error_r); + + /** + * Add a socket descriptor that is accepting connections. After this + * has been called, don't call server_socket_open(), because the + * socket is already open. + */ + bool AddFD(int fd, GError **error_r); + + bool Open(GError **error_r); + void Close(); + +protected: + virtual void OnAccept(int fd, const sockaddr &address, + size_t address_length, int uid) = 0; +}; + +#endif diff --git a/src/event/SocketMonitor.cxx b/src/event/SocketMonitor.cxx new file mode 100644 index 000000000..6efa69647 --- /dev/null +++ b/src/event/SocketMonitor.cxx @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SocketMonitor.hxx" +#include "Loop.hxx" +#include "fd_util.h" +#include "gcc.h" + +#include <assert.h> + +#ifdef WIN32 +#include <winsock2.h> +#else +#include <sys/types.h> +#include <sys/socket.h> +#endif + +/* + * GSource methods + * + */ + +gboolean +SocketMonitor::Prepare(gcc_unused GSource *source, gcc_unused gint *timeout_r) +{ + return false; +} + +gboolean +SocketMonitor::Check(GSource *_source) +{ + const Source &source = *(const Source *)_source; + const SocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + return monitor.Check(); +} + +gboolean +SocketMonitor::Dispatch(GSource *_source, + gcc_unused GSourceFunc callback, + gcc_unused gpointer user_data) +{ + Source &source = *(Source *)_source; + SocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + monitor.Dispatch(); + return true; +} + +/** + * The vtable for our GSource implementation. Unfortunately, we + * cannot declare it "const", because g_source_new() takes a non-const + * pointer, for whatever reason. + */ +static GSourceFuncs socket_monitor_source_funcs = { + SocketMonitor::Prepare, + SocketMonitor::Check, + SocketMonitor::Dispatch, + nullptr, + nullptr, + nullptr, +}; + +SocketMonitor::SocketMonitor(int _fd, EventLoop &_loop) + :fd(-1), loop(_loop), + source(nullptr) { + assert(_fd >= 0); + + Open(_fd); +} + +SocketMonitor::~SocketMonitor() +{ + if (IsDefined()) + Close(); +} + +void +SocketMonitor::Open(int _fd) +{ + assert(fd < 0); + assert(source == nullptr); + assert(_fd >= 0); + + fd = _fd; + poll = {fd, 0, 0}; + + source = (Source *)g_source_new(&socket_monitor_source_funcs, + sizeof(*source)); + source->monitor = this; + + g_source_attach(&source->base, loop.GetContext()); + g_source_add_poll(&source->base, &poll); +} + +int +SocketMonitor::Steal() +{ + assert(IsDefined()); + + Cancel(); + + int result = fd; + fd = -1; + + g_source_destroy(&source->base); + g_source_unref(&source->base); + source = nullptr; + + return result; +} + +void +SocketMonitor::Close() +{ + close_socket(Steal()); +} + +SocketMonitor::ssize_t +SocketMonitor::Read(void *data, size_t length) +{ + int flags = 0; +#ifdef MSG_DONTWAIT + flags |= MSG_DONTWAIT; +#endif + + return recv(Get(), (char *)data, length, flags); +} + +SocketMonitor::ssize_t +SocketMonitor::Write(const void *data, size_t length) +{ + int flags = 0; +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif +#ifdef MSG_DONTWAIT + flags |= MSG_DONTWAIT; +#endif + + return send(Get(), (const char *)data, length, flags); +} + +void +SocketMonitor::CommitEventFlags() +{ + loop.WakeUp(); +} diff --git a/src/event/SocketMonitor.hxx b/src/event/SocketMonitor.hxx new file mode 100644 index 000000000..c60b8efdf --- /dev/null +++ b/src/event/SocketMonitor.hxx @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SOCKET_MONITOR_HXX +#define MPD_SOCKET_MONITOR_HXX + +#include "check.h" + +#include <glib.h> + +#include <type_traits> + +#include <assert.h> +#include <stddef.h> + +#ifdef WIN32 +/* ERRORis a WIN32 macro that poisons our namespace; this is a + kludge to allow us to use it anyway */ +#ifdef ERROR +#undef ERROR +#endif +#endif + +class EventLoop; + +class SocketMonitor { + struct Source { + GSource base; + + SocketMonitor *monitor; + }; + + int fd; + EventLoop &loop; + Source *source; + GPollFD poll; + +public: + static constexpr unsigned READ = G_IO_IN; + static constexpr unsigned WRITE = G_IO_OUT; + static constexpr unsigned ERROR = G_IO_ERR; + static constexpr unsigned HANGUP = G_IO_HUP; + + typedef std::make_signed<size_t>::type ssize_t; + + SocketMonitor(EventLoop &_loop) + :fd(-1), loop(_loop), source(nullptr) {} + + SocketMonitor(int _fd, EventLoop &_loop); + + ~SocketMonitor(); + + bool IsDefined() const { + return fd >= 0; + } + + int Get() const { + assert(IsDefined()); + + return fd; + } + + void Open(int _fd); + + /** + * "Steal" the socket descriptor. This abandons the socket + * and puts the responsibility for closing it to the caller. + */ + int Steal(); + + void Close(); + + void Schedule(unsigned flags) { + poll.events = flags; + poll.revents &= flags; + CommitEventFlags(); + } + + void Cancel() { + poll.events = 0; + CommitEventFlags(); + } + + void ScheduleRead() { + poll.events |= READ|HANGUP|ERROR; + CommitEventFlags(); + } + + void ScheduleWrite() { + poll.events |= WRITE; + CommitEventFlags(); + } + + void CancelRead() { + poll.events &= ~(READ|HANGUP|ERROR); + CommitEventFlags(); + } + + void CancelWrite() { + poll.events &= ~WRITE; + CommitEventFlags(); + } + + ssize_t Read(void *data, size_t length); + ssize_t Write(const void *data, size_t length); + +protected: + /** + * @return false if the socket has been closed + */ + virtual bool OnSocketReady(unsigned flags) = 0; + +public: + /* GSource callbacks */ + static gboolean Prepare(GSource *source, gint *timeout_r); + static gboolean Check(GSource *source); + static gboolean Dispatch(GSource *source, GSourceFunc callback, + gpointer user_data); + +private: + void CommitEventFlags(); + + bool Check() const { + return (poll.revents & poll.events) != 0; + } + + void Dispatch() { + OnSocketReady(poll.revents & poll.events); + } +}; + +#endif diff --git a/src/event/TimeoutMonitor.cxx b/src/event/TimeoutMonitor.cxx new file mode 100644 index 000000000..e0bf997a0 --- /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 000000000..6914bcb61 --- /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 000000000..1a84f5645 --- /dev/null +++ b/src/event/WakeFD.cxx @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "WakeFD.hxx" +#include "fd_util.h" +#include "gcc.h" + +#include <unistd.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock2.h> +#include <cstring> /* for memset() */ +#endif + +#ifdef HAVE_EVENTFD +#include <sys/eventfd.h> +#endif + +#ifdef WIN32 +static bool PoorSocketPair(int fd[2]); +#endif + +bool +WakeFD::Create() +{ + assert(fds[0] == -1); + assert(fds[1] == -1); + +#ifdef WIN32 + return PoorSocketPair(fds); +#else +#ifdef HAVE_EVENTFD + fds[0] = eventfd_cloexec_nonblock(0, 0); + if (fds[0] >= 0) { + fds[1] = -2; + return true; + } +#endif + return pipe_cloexec_nonblock(fds) >= 0; +#endif +} + +void +WakeFD::Destroy() +{ +#ifdef WIN32 + closesocket(fds[0]); + closesocket(fds[1]); +#else + close(fds[0]); +#ifdef HAVE_EVENTFD + if (!IsEventFD()) +#endif + close(fds[1]); +#endif + +#ifndef NDEBUG + fds[0] = -1; + fds[1] = -1; +#endif +} + +bool +WakeFD::Read() +{ + assert(fds[0] >= 0); + +#ifdef WIN32 + assert(fds[1] >= 0); + char buffer[256]; + return recv(fds[0], buffer, sizeof(buffer), 0) > 0; +#else + +#ifdef HAVE_EVENTFD + if (IsEventFD()) { + eventfd_t value; + return read(fds[0], &value, + sizeof(value)) == (ssize_t)sizeof(value); + } +#endif + + assert(fds[1] >= 0); + + char buffer[256]; + return read(fds[0], buffer, sizeof(buffer)) > 0; +#endif +} + +void +WakeFD::Write() +{ + assert(fds[0] >= 0); + +#ifdef WIN32 + assert(fds[1] >= 0); + + send(fds[1], "", 1, 0); +#else + +#ifdef HAVE_EVENTFD + if (IsEventFD()) { + static constexpr eventfd_t value = 1; + gcc_unused ssize_t nbytes = + write(fds[0], &value, sizeof(value)); + return; + } +#endif + + assert(fds[1] >= 0); + + gcc_unused ssize_t nbytes = write(fds[1], "", 1); +#endif +} + +#ifdef WIN32 + +static void SafeCloseSocket(SOCKET s) +{ + int error = WSAGetLastError(); + closesocket(s); + WSASetLastError(error); +} + +/* Our poor man's socketpair() implementation + * Due to limited protocol/address family support and primitive error handling + * it's better to keep this as a private implementation detail of WakeFD + * rather than wide-available API. + */ +static bool PoorSocketPair(int fd[2]) +{ + assert (fd != nullptr); + + SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listen_socket == INVALID_SOCKET) + return false; + + sockaddr_in address; + std::memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + int ret = bind(listen_socket, + reinterpret_cast<sockaddr*>(&address), + sizeof(address)); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + return false; + } + + ret = listen(listen_socket, 1); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + return false; + } + + int address_len = sizeof(address); + ret = getsockname(listen_socket, + reinterpret_cast<sockaddr*>(&address), + &address_len); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + return false; + } + + SOCKET socket0 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (socket0 == INVALID_SOCKET) { + SafeCloseSocket(listen_socket); + return false; + } + + ret = connect(socket0, + reinterpret_cast<sockaddr*>(&address), + sizeof(address)); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + SafeCloseSocket(socket0); + return false; + } + + SOCKET socket1 = accept(listen_socket, nullptr, nullptr); + if (socket1 == INVALID_SOCKET) { + SafeCloseSocket(listen_socket); + SafeCloseSocket(socket0); + return false; + } + + SafeCloseSocket(listen_socket); + + u_long non_block = 1; + if (ioctlsocket(socket0, FIONBIO, &non_block) < 0 + || ioctlsocket(socket1, FIONBIO, &non_block) < 0) { + SafeCloseSocket(socket0); + SafeCloseSocket(socket1); + return false; + } + + fd[0] = static_cast<int>(socket0); + fd[1] = static_cast<int>(socket1); + + return true; +} + +#endif diff --git a/src/event/WakeFD.hxx b/src/event/WakeFD.hxx new file mode 100644 index 000000000..15b66b4cf --- /dev/null +++ b/src/event/WakeFD.hxx @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_WAKE_FD_HXX +#define MPD_WAKE_FD_HXX + +#include "check.h" + +#include <assert.h> + +/** + * This class can be used to wake up an I/O event loop. + * + * For optimization purposes, this class does not have a constructor + * or a destructor. + */ +class WakeFD { + int fds[2]; + +public: +#ifdef NDEBUG + WakeFD() = default; +#else + WakeFD():fds{-1, -1} {}; +#endif + + WakeFD(const WakeFD &other) = delete; + WakeFD &operator=(const WakeFD &other) = delete; + + bool Create(); + void Destroy(); + + int Get() const { + assert(fds[0] >= 0); +#ifndef HAVE_EVENTFD + assert(fds[1] >= 0); +#endif + + return fds[0]; + } + + /** + * Checks if Write() was called at least once since the last + * Read() call. + */ + bool Read(); + + /** + * Wakes up the reader. Multiple calls to this function will + * be combined to one wakeup. + */ + void Write(); + +private: +#ifdef HAVE_EVENTFD + bool IsEventFD() { + assert(fds[0] >= 0); + + return fds[1] == -2; + } +#endif +}; + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event_pipe.c b/src/event_pipe.c deleted file mode 100644 index d5c3b9564..000000000 --- a/src/event_pipe.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "event_pipe.h" -#include "fd_util.h" -#include "mpd_error.h" - -#include <stdbool.h> -#include <assert.h> -#include <glib.h> -#include <string.h> -#include <errno.h> -#include <sys/types.h> -#include <unistd.h> - -#ifdef WIN32 -/* for _O_BINARY */ -#include <fcntl.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "event_pipe" - -static int event_pipe[2]; -static GIOChannel *event_channel; -static guint event_pipe_source_id; -static GMutex *event_pipe_mutex; -static bool pipe_events[PIPE_EVENT_MAX]; -static event_pipe_callback_t event_pipe_callbacks[PIPE_EVENT_MAX]; - -/** - * Invoke the callback for a certain event. - */ -static void -event_pipe_invoke(enum pipe_event event) -{ - assert((unsigned)event < PIPE_EVENT_MAX); - assert(event_pipe_callbacks[event] != NULL); - - event_pipe_callbacks[event](); -} - -static gboolean -main_notify_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - G_GNUC_UNUSED gpointer data) -{ - char buffer[256]; - gsize bytes_read; - GError *error = NULL; - GIOStatus status = g_io_channel_read_chars(event_channel, - buffer, sizeof(buffer), - &bytes_read, &error); - if (status == G_IO_STATUS_ERROR) - MPD_ERROR("error reading from pipe: %s", error->message); - - bool events[PIPE_EVENT_MAX]; - g_mutex_lock(event_pipe_mutex); - memcpy(events, pipe_events, sizeof(events)); - memset(pipe_events, 0, sizeof(pipe_events)); - g_mutex_unlock(event_pipe_mutex); - - for (unsigned i = 0; i < PIPE_EVENT_MAX; ++i) - if (events[i]) - /* invoke the event handler */ - event_pipe_invoke(i); - - return true; -} - -void event_pipe_init(void) -{ - GIOChannel *channel; - int ret; - - ret = pipe_cloexec_nonblock(event_pipe); - if (ret < 0) - MPD_ERROR("Couldn't open pipe: %s", strerror(errno)); - -#ifndef G_OS_WIN32 - channel = g_io_channel_unix_new(event_pipe[0]); -#else - channel = g_io_channel_win32_new_fd(event_pipe[0]); -#endif - g_io_channel_set_encoding(channel, NULL, NULL); - g_io_channel_set_buffered(channel, false); - - event_pipe_source_id = g_io_add_watch(channel, G_IO_IN, - main_notify_event, NULL); - - event_channel = channel; - - event_pipe_mutex = g_mutex_new(); -} - -void event_pipe_deinit(void) -{ - g_mutex_free(event_pipe_mutex); - - g_source_remove(event_pipe_source_id); - g_io_channel_unref(event_channel); - -#ifndef WIN32 - /* By some strange reason this call hangs on Win32 */ - close(event_pipe[0]); -#endif - close(event_pipe[1]); -} - -void -event_pipe_register(enum pipe_event event, event_pipe_callback_t callback) -{ - assert((unsigned)event < PIPE_EVENT_MAX); - assert(event_pipe_callbacks[event] == NULL); - - event_pipe_callbacks[event] = callback; -} - -void event_pipe_emit(enum pipe_event event) -{ - ssize_t w; - - assert((unsigned)event < PIPE_EVENT_MAX); - - g_mutex_lock(event_pipe_mutex); - if (pipe_events[event]) { - /* already set: don't write */ - g_mutex_unlock(event_pipe_mutex); - return; - } - - pipe_events[event] = true; - g_mutex_unlock(event_pipe_mutex); - - w = write(event_pipe[1], "", 1); - if (w < 0 && errno != EAGAIN && errno != EINTR) - MPD_ERROR("error writing to pipe: %s", strerror(errno)); -} - -void event_pipe_emit_fast(enum pipe_event event) -{ - assert((unsigned)event < PIPE_EVENT_MAX); - - pipe_events[event] = true; - - G_GNUC_UNUSED ssize_t nbytes = write(event_pipe[1], "", 1); -} diff --git a/src/event_pipe.h b/src/event_pipe.h deleted file mode 100644 index 3734bb86c..000000000 --- a/src/event_pipe.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef EVENT_PIPE_H -#define EVENT_PIPE_H - -#include <glib.h> - -enum pipe_event { - /** database update was finished */ - PIPE_EVENT_UPDATE, - - /** during database update, a song was deleted */ - PIPE_EVENT_DELETE, - - /** an idle event was emitted */ - PIPE_EVENT_IDLE, - - /** must call playlist_sync() */ - PIPE_EVENT_PLAYLIST, - - /** the current song's tag has changed */ - PIPE_EVENT_TAG, - - /** SIGHUP received: reload configuration, roll log file */ - PIPE_EVENT_RELOAD, - - /** a hardware mixer plugin has detected a change */ - PIPE_EVENT_MIXER, - - /** shutdown requested */ - PIPE_EVENT_SHUTDOWN, - - PIPE_EVENT_MAX -}; - -typedef void (*event_pipe_callback_t)(void); - -void event_pipe_init(void); - -void event_pipe_deinit(void); - -void -event_pipe_register(enum pipe_event event, event_pipe_callback_t callback); - -void event_pipe_emit(enum pipe_event event); - -/** - * Similar to event_pipe_emit(), but aimed for use in signal handlers: - * it doesn't lock the mutex, and doesn't log on error. That makes it - * potentially lossy, but for its intended use, that does not matter. - */ -void event_pipe_emit_fast(enum pipe_event event); - -#endif /* MAIN_NOTIFY_H */ diff --git a/src/exclude.c b/src/exclude.c deleted file mode 100644 index 438039d30..000000000 --- a/src/exclude.c +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * The .mpdignore backend code. - * - */ - -#include "config.h" -#include "exclude.h" -#include "path.h" - -#include <assert.h> -#include <string.h> -#include <stdio.h> -#include <errno.h> - -GSList * -exclude_list_load(const char *path_fs) -{ - FILE *file; - char line[1024]; - GSList *list = NULL; - - assert(path_fs != NULL); - - file = fopen(path_fs, "r"); - if (file == NULL) { - if (errno != ENOENT) { - char *path_utf8 = fs_charset_to_utf8(path_fs); - g_debug("Failed to open %s: %s", - path_utf8, g_strerror(errno)); - g_free(path_utf8); - } - - return NULL; - } - - while (fgets(line, sizeof(line), file) != NULL) { - char *p = strchr(line, '#'); - if (p != NULL) - *p = 0; - - p = g_strstrip(line); - if (*p != 0) - list = g_slist_prepend(list, g_pattern_spec_new(p)); - } - - fclose(file); - - return list; -} - -void -exclude_list_free(GSList *list) -{ - while (list != NULL) { - GPatternSpec *pattern = list->data; - g_pattern_spec_free(pattern); - list = g_slist_remove(list, list->data); - } -} - -bool -exclude_list_check(GSList *list, const char *name_fs) -{ - assert(name_fs != NULL); - - /* XXX include full path name in check */ - - for (; list != NULL; list = list->next) { - GPatternSpec *pattern = list->data; - - if (g_pattern_match_string(pattern, name_fs)) - return true; - } - - return false; -} diff --git a/src/exclude.h b/src/exclude.h deleted file mode 100644 index 5b1229e29..000000000 --- a/src/exclude.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * The .mpdignore backend code. - * - */ - -#ifndef MPD_EXCLUDE_H -#define MPD_EXCLUDE_H - -#include <glib.h> - -#include <stdbool.h> - -/** - * Loads and parses a .mpdignore file. - */ -GSList * -exclude_list_load(const char *path_fs); - -/** - * Frees a list returned by exclude_list_load(). - */ -void -exclude_list_free(GSList *list); - -/** - * Checks whether one of the patterns in the .mpdignore file matches - * the specified file name. - */ -bool -exclude_list_check(GSList *list, const char *name_fs); - -#endif diff --git a/src/fd_util.c b/src/fd_util.c index 882b4c7d5..ea29d6eaa 100644 --- a/src/fd_util.c +++ b/src/fd_util.c @@ -49,6 +49,10 @@ #include <sys/inotify.h> #endif +#ifdef HAVE_EVENTFD +#include <sys/eventfd.h> +#endif + #ifndef WIN32 static int @@ -328,6 +332,16 @@ inotify_init_cloexec(void) #endif +#ifdef HAVE_EVENTFD + +int +eventfd_cloexec_nonblock(unsigned initval, int flags) +{ + return eventfd(initval, flags | EFD_CLOEXEC | EFD_NONBLOCK); +} + +#endif + int close_socket(int fd) { diff --git a/src/fd_util.h b/src/fd_util.h index dd4df7a13..e65c6a69b 100644 --- a/src/fd_util.h +++ b/src/fd_util.h @@ -51,6 +51,10 @@ struct sockaddr; +#ifdef __cplusplus +extern "C" { +#endif + /** * Wrapper for dup(), which sets the CLOEXEC flag on the new * descriptor. @@ -140,10 +144,25 @@ inotify_init_cloexec(void); #endif +#ifdef HAVE_EVENTFD + +/** + * Wrapper for eventfd() which sets the flags CLOEXEC and NONBLOCK + * flag (atomically if supported by the OS). + */ +int +eventfd_cloexec_nonblock(unsigned initval, int flags); + +#endif + /** * Portable wrapper for close(); use closesocket() on WIN32/WinSock. */ int close_socket(int fd); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif diff --git a/src/fifo_buffer.c b/src/fifo_buffer.c deleted file mode 100644 index 915fb0579..000000000 --- a/src/fifo_buffer.c +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "config.h" -#include "fifo_buffer.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -struct fifo_buffer { - size_t size, start, end; - unsigned char buffer[sizeof(size_t)]; -}; - -struct fifo_buffer * -fifo_buffer_new(size_t size) -{ - struct fifo_buffer *buffer; - - assert(size > 0); - - buffer = (struct fifo_buffer *)g_malloc(sizeof(*buffer) - - sizeof(buffer->buffer) + size); - - buffer->size = size; - buffer->start = 0; - buffer->end = 0; - - return buffer; -} - -static void -fifo_buffer_move(struct fifo_buffer *buffer); - -struct fifo_buffer * -fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size) -{ - if (buffer == NULL) - return new_size > 0 - ? fifo_buffer_new(new_size) - : NULL; - - /* existing data must fit in new size */ - assert(new_size >= buffer->end - buffer->start); - - if (new_size == 0) { - fifo_buffer_free(buffer); - return NULL; - } - - /* compress the buffer when we're shrinking and the tail of - the buffer would exceed the new size */ - if (buffer->end > new_size) - fifo_buffer_move(buffer); - - /* existing data must fit in new size: second check */ - assert(buffer->end <= new_size); - - buffer = g_realloc(buffer, sizeof(*buffer) - sizeof(buffer->buffer) + - new_size); - buffer->size = new_size; - return buffer; -} - -void -fifo_buffer_free(struct fifo_buffer *buffer) -{ - assert(buffer != NULL); - - g_free(buffer); -} - -size_t -fifo_buffer_capacity(const struct fifo_buffer *buffer) -{ - assert(buffer != NULL); - - return buffer->size; -} - -size_t -fifo_buffer_available(const struct fifo_buffer *buffer) -{ - assert(buffer != NULL); - - return buffer->end - buffer->start; -} - -void -fifo_buffer_clear(struct fifo_buffer *buffer) -{ - assert(buffer != NULL); - - buffer->start = 0; - buffer->end = 0; -} - -const void * -fifo_buffer_read(const struct fifo_buffer *buffer, size_t *length_r) -{ - assert(buffer != NULL); - assert(buffer->end >= buffer->start); - assert(length_r != NULL); - - if (buffer->start == buffer->end) - /* the buffer is empty */ - return NULL; - - *length_r = buffer->end - buffer->start; - return buffer->buffer + buffer->start; -} - -void -fifo_buffer_consume(struct fifo_buffer *buffer, size_t length) -{ - assert(buffer != NULL); - assert(buffer->end >= buffer->start); - assert(buffer->start + length <= buffer->end); - - buffer->start += length; -} - -/** - * Move data to the beginning of the buffer, to make room at the end. - */ -static void -fifo_buffer_move(struct fifo_buffer *buffer) -{ - if (buffer->start == 0) - return; - - if (buffer->end > buffer->start) - memmove(buffer->buffer, - buffer->buffer + buffer->start, - buffer->end - buffer->start); - - buffer->end -= buffer->start; - buffer->start = 0; -} - -void * -fifo_buffer_write(struct fifo_buffer *buffer, size_t *max_length_r) -{ - assert(buffer != NULL); - assert(buffer->end <= buffer->size); - assert(max_length_r != NULL); - - if (buffer->end == buffer->size) { - fifo_buffer_move(buffer); - if (buffer->end == buffer->size) - return NULL; - } else if (buffer->start > 0 && buffer->start == buffer->end) { - buffer->start = 0; - buffer->end = 0; - } - - *max_length_r = buffer->size - buffer->end; - return buffer->buffer + buffer->end; -} - -void -fifo_buffer_append(struct fifo_buffer *buffer, size_t length) -{ - assert(buffer != NULL); - assert(buffer->end >= buffer->start); - assert(buffer->end + length <= buffer->size); - - buffer->end += length; -} - -bool -fifo_buffer_is_empty(struct fifo_buffer *buffer) -{ - return buffer->start == buffer->end; -} - -bool -fifo_buffer_is_full(struct fifo_buffer *buffer) -{ - return buffer->start == 0 && buffer->end == buffer->size; -} diff --git a/src/fifo_buffer.h b/src/fifo_buffer.h deleted file mode 100644 index 3bdb23938..000000000 --- a/src/fifo_buffer.h +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** \file - * - * This is a general purpose FIFO buffer library. You may append data - * at the end, while another instance reads data from the beginning. - * It is optimized for zero-copy usage: you get pointers to the real - * buffer, where you may operate on. - * - * This library is not thread safe. - */ - -#ifndef MPD_FIFO_BUFFER_H -#define MPD_FIFO_BUFFER_H - -#include <stdbool.h> -#include <stddef.h> - -struct fifo_buffer; - -/** - * Creates a new #fifo_buffer object. Free this object with - * fifo_buffer_free(). - * - * @param size the size of the buffer in bytes - * @return the new #fifo_buffer object - */ -struct fifo_buffer * -fifo_buffer_new(size_t size); - -/** - * Change the capacity of the #fifo_buffer, while preserving existing - * data. - * - * @param buffer the old buffer, may be NULL - * @param new_size the requested new size of the #fifo_buffer; must - * not be smaller than the data which is stored in the old buffer - * @return the new buffer, may be NULL if the requested new size is 0 - */ -struct fifo_buffer * -fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size); - -/** - * Frees the resources consumed by this #fifo_buffer object. - */ -void -fifo_buffer_free(struct fifo_buffer *buffer); - -/** - * Return the capacity of the buffer, i.e. the size that was passed to - * fifo_buffer_new(). - */ -size_t -fifo_buffer_capacity(const struct fifo_buffer *buffer); - -/** - * Return the number of bytes currently stored in the buffer. - */ -size_t -fifo_buffer_available(const struct fifo_buffer *buffer); - -/** - * Clears all data currently in this #fifo_buffer object. This does - * not overwrite the actuall buffer; it just resets the internal - * pointers. - */ -void -fifo_buffer_clear(struct fifo_buffer *buffer); - -/** - * Reads from the beginning of the buffer. To remove consumed data - * from the buffer, call fifo_buffer_consume(). - * - * @param buffer the #fifo_buffer object - * @param length_r the maximum amount to read is returned here - * @return a pointer to the beginning of the buffer, or NULL if the - * buffer is empty - */ -const void * -fifo_buffer_read(const struct fifo_buffer *buffer, size_t *length_r); - -/** - * Marks data at the beginning of the buffer as "consumed". - * - * @param buffer the #fifo_buffer object - * @param length the number of bytes which were consumed - */ -void -fifo_buffer_consume(struct fifo_buffer *buffer, size_t length); - -/** - * Prepares writing to the buffer. This returns a buffer which you - * can write to. To commit the write operation, call - * fifo_buffer_append(). - * - * @param buffer the #fifo_buffer object - * @param max_length_r the maximum amount to write is returned here - * @return a pointer to the end of the buffer, or NULL if the buffer - * is already full - */ -void * -fifo_buffer_write(struct fifo_buffer *buffer, size_t *max_length_r); - -/** - * Commits the write operation initiated by fifo_buffer_write(). - * - * @param buffer the #fifo_buffer object - * @param length the number of bytes which were written - */ -void -fifo_buffer_append(struct fifo_buffer *buffer, size_t length); - -/** - * Checks if the buffer is empty. - */ -bool -fifo_buffer_is_empty(struct fifo_buffer *buffer); - -/** - * Checks if the buffer is full. - */ -bool -fifo_buffer_is_full(struct fifo_buffer *buffer); - -#endif diff --git a/src/filter/AutoConvertFilterPlugin.cxx b/src/filter/AutoConvertFilterPlugin.cxx new file mode 100644 index 000000000..55ee46948 --- /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/AutoConvertFilterPlugin.hxx b/src/filter/AutoConvertFilterPlugin.hxx new file mode 100644 index 000000000..7db72a345 --- /dev/null +++ b/src/filter/AutoConvertFilterPlugin.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_AUTOCONVERT_FILTER_PLUGIN_HXX +#define MPD_AUTOCONVERT_FILTER_PLUGIN_HXX + +class Filter; + +/** + * Creates a new "autoconvert" filter. When opened, it ensures that + * the input audio format isn't changed. If the underlying filter + * requests a different format, it automatically creates a + * convert_filter. + */ +Filter * +autoconvert_filter_new(Filter *filter); + +#endif diff --git a/src/filter/ChainFilterPlugin.cxx b/src/filter/ChainFilterPlugin.cxx new file mode 100644 index 000000000..c8666615f --- /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/ChainFilterPlugin.hxx b/src/filter/ChainFilterPlugin.hxx new file mode 100644 index 000000000..884c7ca19 --- /dev/null +++ b/src/filter/ChainFilterPlugin.hxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * A filter chain is a container for several filters. They are + * chained together, i.e. called in a row, one filter passing its + * output to the next one. + */ + +#ifndef MPD_FILTER_CHAIN_HXX +#define MPD_FILTER_CHAIN_HXX + +class Filter; + +/** + * Creates a new filter chain. + */ +Filter * +filter_chain_new(void); + +/** + * Appends a new filter at the end of the filter chain. You must call + * this function before the first filter_open() call. + * + * @param chain the filter chain created with filter_chain_new() + * @param filter the filter to be appended to #chain + */ +void +filter_chain_append(Filter &chain, const char *name, Filter *filter); + +#endif diff --git a/src/filter/ConvertFilterPlugin.cxx b/src/filter/ConvertFilterPlugin.cxx new file mode 100644 index 000000000..2c6907655 --- /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/ConvertFilterPlugin.hxx b/src/filter/ConvertFilterPlugin.hxx new file mode 100644 index 000000000..840bf496f --- /dev/null +++ b/src/filter/ConvertFilterPlugin.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONVERT_FILTER_PLUGIN_HXX +#define MPD_CONVERT_FILTER_PLUGIN_HXX + +class Filter; +struct audio_format; + +/** + * Sets the output audio format for the specified filter. You must + * call this after the filter has been opened. Since this audio + * format switch is a violation of the filter API, this filter must be + * the last in a chain. + */ +void +convert_filter_set(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 000000000..e18c5cdf9 --- /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 000000000..d68065a39 --- /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 000000000..13c8a4063 --- /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 "mixer_control.h" +#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/ReplayGainFilterPlugin.hxx b/src/filter/ReplayGainFilterPlugin.hxx new file mode 100644 index 000000000..dd8ceb953 --- /dev/null +++ b/src/filter/ReplayGainFilterPlugin.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX +#define MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX + +#include "replay_gain_info.h" + +class Filter; +struct mixer; + +/** + * Enables or disables the hardware mixer for applying replay gain. + * + * @param mixer the hardware mixer, or NULL to fall back to software + * volume + * @param base the base volume level for scale=1.0, between 1 and 100 + * (including). + */ +void +replay_gain_filter_set_mixer(Filter *_filter, struct mixer *mixer, + unsigned base); + +/** + * Sets a new #replay_gain_info at the beginning of a new song. + * + * @param info the new #replay_gain_info value, or NULL if no replay + * gain data is available for the current song + */ +void +replay_gain_filter_set_info(Filter *filter, const replay_gain_info *info); + +void +replay_gain_filter_set_mode(Filter *filter, enum replay_gain_mode mode); + +#endif diff --git a/src/filter/RouteFilterPlugin.cxx b/src/filter/RouteFilterPlugin.cxx new file mode 100644 index 000000000..559578938 --- /dev/null +++ b/src/filter/RouteFilterPlugin.cxx @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This filter copies audio data between channels. Useful for + * upmixing mono/stereo audio to surround speaker configurations. + * + * Its configuration consists of a "filter" section with a single + * "routes" entry, formatted as: \\ + * routes "0>1, 1>0, 2>2, 3>3, 3>4" \\ + * where each pair of numbers signifies a set of channels. + * Each source>dest pair leads to the data from channel #source + * being copied to channel #dest in the output. + * + * Example: \\ + * routes "0>0, 1>1, 0>2, 1>3"\\ + * upmixes stereo audio to a 4-speaker system, copying the front-left + * (0) to front left (0) and rear left (2), copying front-right (1) to + * front-right (1) and rear-right (3). + * + * If multiple sources are copied to the same destination channel, only + * one of them takes effect. + */ + +#include "config.h" +#include "conf.h" +#include "ConfigQuark.hxx" +#include "audio_format.h" +#include "audio_check.h" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "pcm_buffer.h" + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +class RouteFilter final : public Filter { + /** + * The minimum number of channels we need for output + * to be able to perform all the copies the user has specified + */ + unsigned char min_output_channels; + + /** + * The minimum number of input channels we need to + * copy all the data the user has requested. If fewer + * than this many are supplied by the input, undefined + * copy operations are given zeroed sources in stead. + */ + unsigned char min_input_channels; + + /** + * The set of copy operations to perform on each sample + * The index is an output channel to use, the value is + * a corresponding input channel from which to take the + * data. A -1 means "no source" + */ + signed char* sources; + + /** + * The actual input format of our signal, once opened + */ + struct audio_format input_format; + + /** + * The decided upon output format, once opened + */ + struct audio_format output_format; + + /** + * The size, in bytes, of each multichannel frame in the + * input buffer + */ + size_t input_frame_size; + + /** + * The size, in bytes, of each multichannel frame in the + * output buffer + */ + size_t output_frame_size; + + /** + * The output buffer used last time around, can be reused if the size doesn't differ. + */ + struct pcm_buffer output_buffer; + +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); +}; + +bool +RouteFilter::Configure(const config_param *param, GError **error_r) { + + /* TODO: + * With a more clever way of marking "don't copy to output N", + * This could easily be merged into a single loop with some + * dynamic g_realloc() instead of one count run and one g_malloc(). + */ + + gchar **tokens; + int number_of_copies; + + // A cowardly default, just passthrough stereo + const char *routes = + config_get_block_string(param, "routes", "0>0, 1>1"); + + min_input_channels = 0; + min_output_channels = 0; + + tokens = g_strsplit(routes, ",", 255); + number_of_copies = g_strv_length(tokens); + + // Start by figuring out a few basic things about the routing set + for (int c=0; c<number_of_copies; ++c) { + + // String and int representations of the source/destination + gchar **sd; + int source, dest; + + // Squeeze whitespace + g_strstrip(tokens[c]); + + // Split the a>b string into source and destination + sd = g_strsplit(tokens[c], ">", 2); + if (g_strv_length(sd) != 2) { + g_set_error(error_r, config_quark(), 1, + "Invalid copy around %d in routes spec: %s", + param->line, tokens[c]); + g_strfreev(sd); + g_strfreev(tokens); + return false; + } + + source = strtol(sd[0], NULL, 10); + dest = strtol(sd[1], NULL, 10); + + // Keep track of the highest channel numbers seen + // as either in- or outputs + if (source >= min_input_channels) + min_input_channels = source + 1; + if (dest >= min_output_channels) + min_output_channels = dest + 1; + + g_strfreev(sd); + } + + if (!audio_valid_channel_count(min_output_channels)) { + g_strfreev(tokens); + g_set_error(error_r, audio_format_quark(), 0, + "Invalid number of output channels requested: %d", + min_output_channels); + return false; + } + + // Allocate a map of "copy nothing to me" + sources = (signed char *) + g_malloc(min_output_channels * sizeof(signed char)); + + for (int i=0; i<min_output_channels; ++i) + sources[i] = -1; + + // Run through the spec again, and save the + // actual mapping output <- input + for (int c=0; c<number_of_copies; ++c) { + + // String and int representations of the source/destination + gchar **sd; + int source, dest; + + // Split the a>b string into source and destination + sd = g_strsplit(tokens[c], ">", 2); + if (g_strv_length(sd) != 2) { + g_set_error(error_r, config_quark(), 1, + "Invalid copy around %d in routes spec: %s", + param->line, tokens[c]); + g_strfreev(sd); + g_strfreev(tokens); + return false; + } + + source = strtol(sd[0], NULL, 10); + dest = strtol(sd[1], NULL, 10); + + sources[dest] = source; + + g_strfreev(sd); + } + + g_strfreev(tokens); + + return true; +} + +static Filter * +route_filter_init(const config_param *param, GError **error_r) +{ + RouteFilter *filter = new RouteFilter(); + if (!filter->Configure(param, error_r)) { + delete filter; + return nullptr; + } + + return filter; +} + +const struct audio_format * +RouteFilter::Open(audio_format &audio_format, gcc_unused GError **error_r) +{ + // Copy the input format for later reference + 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 + output_format = audio_format; + output_format.channels = min_output_channels; + + // Precalculate this simple value, to speed up allocation later + output_frame_size = audio_format_frame_size(&output_format); + + // This buffer grows as needed + pcm_buffer_init(&output_buffer); + + return &output_format; +} + +void +RouteFilter::Close() +{ + pcm_buffer_deinit(&output_buffer); +} + +const void * +RouteFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, gcc_unused GError **error_r) +{ + size_t number_of_frames = src_size / input_frame_size; + + size_t bytes_per_frame_per_channel = + 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 = (const uint8_t *)src; + + // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output + uint8_t *chan_destination; + + // Grow our reusable buffer, if needed, and set the moving pointer + *dest_size_r = number_of_frames * output_frame_size; + chan_destination = (uint8_t *) + 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<min_output_channels; ++c) { + if (sources[c] == -1 || + (unsigned)sources[c] >= input_format.channels) { + // No source for this destination output, + // give it zeroes as input + memset(chan_destination, + 0x00, + bytes_per_frame_per_channel); + } else { + // Get the data from channel sources[c] + // and copy it to the output + const uint8_t *data = base_source + + (sources[c] * bytes_per_frame_per_channel); + memcpy(chan_destination, + data, + bytes_per_frame_per_channel); + } + // Move on to the next output channel + chan_destination += bytes_per_frame_per_channel; + } + + + // Go on to the next N input samples + base_source += input_frame_size; + } + + // Here it is, ladies and gentlemen! Rerouted data! + return (void *) output_buffer.buffer; +} + +const struct filter_plugin route_filter_plugin = { + "route", + route_filter_init, +}; diff --git a/src/filter/VolumeFilterPlugin.cxx b/src/filter/VolumeFilterPlugin.cxx new file mode 100644 index 000000000..0689f5da5 --- /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/VolumeFilterPlugin.hxx b/src/filter/VolumeFilterPlugin.hxx new file mode 100644 index 000000000..822b7e93a --- /dev/null +++ b/src/filter/VolumeFilterPlugin.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_VOLUME_FILTER_PLUGIN_HXX +#define MPD_VOLUME_FILTER_PLUGIN_HXX + +class Filter; + +unsigned +volume_filter_get(const Filter *filter); + +void +volume_filter_set(Filter *filter, unsigned volume); + +#endif diff --git a/src/filter/autoconvert_filter_plugin.c b/src/filter/autoconvert_filter_plugin.c deleted file mode 100644 index 3826a0fb3..000000000 --- a/src/filter/autoconvert_filter_plugin.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "filter/autoconvert_filter_plugin.h" -#include "filter/convert_filter_plugin.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" -#include "conf.h" -#include "pcm_convert.h" -#include "audio_format.h" -#include "poison.h" - -#include <assert.h> -#include <string.h> - -struct autoconvert_filter { - struct filter base; - - /** - * The audio format being fed to the underlying filter. This - * plugin actually doesn't need this variable, we have it here - * just so our open() method doesn't return a stack pointer. - */ - struct audio_format in_audio_format; - - /** - * The underlying filter. - */ - struct filter *filter; - - /** - * A convert_filter, just in case conversion is needed. NULL - * if unused. - */ - struct filter *convert; -}; - -static void -autoconvert_filter_finish(struct filter *_filter) -{ - struct autoconvert_filter *filter = - (struct autoconvert_filter *)_filter; - - filter_free(filter->filter); - g_free(filter); -} - -static const struct audio_format * -autoconvert_filter_open(struct filter *_filter, - struct audio_format *in_audio_format, - GError **error_r) -{ - struct autoconvert_filter *filter = - (struct autoconvert_filter *)_filter; - const struct audio_format *out_audio_format; - - assert(audio_format_valid(in_audio_format)); - - /* open the "real" filter */ - - filter->in_audio_format = *in_audio_format; - - out_audio_format = filter_open(filter->filter, - &filter->in_audio_format, error_r); - if (out_audio_format == NULL) - return NULL; - - /* need to convert? */ - - if (!audio_format_equals(&filter->in_audio_format, in_audio_format)) { - /* yes - create a convert_filter */ - struct audio_format audio_format2 = *in_audio_format; - const struct audio_format *audio_format3; - - filter->convert = filter_new(&convert_filter_plugin, NULL, - error_r); - if (filter->convert == NULL) { - filter_close(filter->filter); - return NULL; - } - - audio_format3 = filter_open(filter->convert, &audio_format2, - error_r); - if (audio_format3 == NULL) { - filter_free(filter->convert); - filter_close(filter->filter); - return NULL; - } - - assert(audio_format_equals(&audio_format2, in_audio_format)); - - convert_filter_set(filter->convert, &filter->in_audio_format); - } else - /* no */ - filter->convert = NULL; - - return out_audio_format; -} - -static void -autoconvert_filter_close(struct filter *_filter) -{ - struct autoconvert_filter *filter = - (struct autoconvert_filter *)_filter; - - if (filter->convert != NULL) { - filter_close(filter->convert); - filter_free(filter->convert); - } - - filter_close(filter->filter); -} - -static const void * -autoconvert_filter_filter(struct filter *_filter, const void *src, - size_t src_size, size_t *dest_size_r, - GError **error_r) -{ - struct autoconvert_filter *filter = - (struct autoconvert_filter *)_filter; - - if (filter->convert != NULL) { - src = filter_filter(filter->convert, src, src_size, &src_size, - error_r); - if (src == NULL) - return NULL; - } - - return filter_filter(filter->filter, src, src_size, dest_size_r, - error_r); -} - -static const struct filter_plugin autoconvert_filter_plugin = { - .name = "convert", - .finish = autoconvert_filter_finish, - .open = autoconvert_filter_open, - .close = autoconvert_filter_close, - .filter = autoconvert_filter_filter, -}; - -struct filter * -autoconvert_filter_new(struct filter *_filter) -{ - struct autoconvert_filter *filter = - g_new(struct autoconvert_filter, 1); - - filter_init(&filter->base, &autoconvert_filter_plugin); - filter->filter = _filter; - - return &filter->base; -} diff --git a/src/filter/autoconvert_filter_plugin.h b/src/filter/autoconvert_filter_plugin.h deleted file mode 100644 index def08ab7e..000000000 --- a/src/filter/autoconvert_filter_plugin.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef AUTOCONVERT_FILTER_PLUGIN_H -#define AUTOCONVERT_FILTER_PLUGIN_H - -struct filter; - -/** - * Creates a new "autoconvert" filter. When opened, it ensures that - * the input audio format isn't changed. If the underlying filter - * requests a different format, it automatically creates a - * convert_filter. - */ -struct filter * -autoconvert_filter_new(struct filter *filter); - -#endif diff --git a/src/filter/chain_filter_plugin.c b/src/filter/chain_filter_plugin.c deleted file mode 100644 index 2c785a36f..000000000 --- a/src/filter/chain_filter_plugin.c +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "conf.h" -#include "filter/chain_filter_plugin.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" -#include "audio_format.h" - -#include <assert.h> - -struct filter_chain { - /** the base class */ - struct filter base; - - GSList *children; -}; - -static inline GQuark -filter_quark(void) -{ - return g_quark_from_static_string("filter"); -} - -static struct filter * -chain_filter_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct filter_chain *chain = g_new(struct filter_chain, 1); - - filter_init(&chain->base, &chain_filter_plugin); - chain->children = NULL; - - return &chain->base; -} - -static void -chain_free_child(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct filter *filter = data; - - filter_free(filter); -} - -static void -chain_filter_finish(struct filter *_filter) -{ - struct filter_chain *chain = (struct filter_chain *)_filter; - - g_slist_foreach(chain->children, chain_free_child, NULL); - g_slist_free(chain->children); - - g_free(chain); -} - -/** - * Close all filters in the chain until #until is reached. #until - * itself is not closed. - */ -static void -chain_close_until(struct filter_chain *chain, const struct filter *until) -{ - GSList *i = chain->children; - struct filter *filter; - - while (true) { - /* this assertion fails if #until does not exist - (anymore) */ - assert(i != NULL); - - if (i->data == until) - /* don't close this filter */ - break; - - /* close this filter */ - filter = i->data; - filter_close(filter); - - i = g_slist_next(i); - } -} - -static const struct audio_format * -chain_open_child(struct filter *filter, - const struct audio_format *prev_audio_format, - GError **error_r) -{ - struct audio_format conv_audio_format = *prev_audio_format; - const struct audio_format *next_audio_format; - - next_audio_format = filter_open(filter, &conv_audio_format, error_r); - if (next_audio_format == NULL) - return NULL; - - if (!audio_format_equals(&conv_audio_format, prev_audio_format)) { - struct audio_format_string s; - - filter_close(filter); - g_set_error(error_r, filter_quark(), 0, - "Audio format not supported by filter '%s': %s", - filter->plugin->name, - audio_format_to_string(prev_audio_format, &s)); - return NULL; - } - - return next_audio_format; -} - -static const struct audio_format * -chain_filter_open(struct filter *_filter, struct audio_format *in_audio_format, - GError **error_r) -{ - struct filter_chain *chain = (struct filter_chain *)_filter; - const struct audio_format *audio_format = in_audio_format; - - for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) { - struct filter *filter = i->data; - - audio_format = chain_open_child(filter, audio_format, error_r); - if (audio_format == NULL) { - /* rollback, close all children */ - chain_close_until(chain, filter); - return NULL; - } - } - - /* return the output format of the last filter */ - return audio_format; -} - -static void -chain_close_child(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct filter *filter = data; - - filter_close(filter); -} - -static void -chain_filter_close(struct filter *_filter) -{ - struct filter_chain *chain = (struct filter_chain *)_filter; - - g_slist_foreach(chain->children, chain_close_child, NULL); -} - -static const void * -chain_filter_filter(struct filter *_filter, - const void *src, size_t src_size, - size_t *dest_size_r, GError **error_r) -{ - struct filter_chain *chain = (struct filter_chain *)_filter; - - for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) { - struct filter *filter = i->data; - - /* feed the output of the previous filter as input - into the current one */ - src = filter_filter(filter, src, src_size, &src_size, error_r); - if (src == NULL) - return NULL; - } - - /* return the output of the last filter */ - *dest_size_r = src_size; - return src; -} - -const struct filter_plugin chain_filter_plugin = { - .name = "chain", - .init = chain_filter_init, - .finish = chain_filter_finish, - .open = chain_filter_open, - .close = chain_filter_close, - .filter = chain_filter_filter, -}; - -struct filter * -filter_chain_new(void) -{ - struct filter *filter = filter_new(&chain_filter_plugin, NULL, NULL); - /* chain_filter_init() never fails */ - assert(filter != NULL); - - return filter; -} - -void -filter_chain_append(struct filter *_chain, struct filter *filter) -{ - struct filter_chain *chain = (struct filter_chain *)_chain; - - chain->children = g_slist_append(chain->children, filter); -} - diff --git a/src/filter/chain_filter_plugin.h b/src/filter/chain_filter_plugin.h deleted file mode 100644 index 1dba46667..000000000 --- a/src/filter/chain_filter_plugin.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * A filter chain is a container for several filters. They are - * chained together, i.e. called in a row, one filter passing its - * output to the next one. - */ - -#ifndef MPD_FILTER_CHAIN_H -#define MPD_FILTER_CHAIN_H - -struct filter; - -/** - * Creates a new filter chain. - */ -struct filter * -filter_chain_new(void); - -/** - * Appends a new filter at the end of the filter chain. You must call - * this function before the first filter_open() call. - * - * @param chain the filter chain created with filter_chain_new() - * @param filter the filter to be appended to #chain - */ -void -filter_chain_append(struct filter *chain, struct filter *filter); - -#endif diff --git a/src/filter/convert_filter_plugin.c b/src/filter/convert_filter_plugin.c deleted file mode 100644 index c55b69af2..000000000 --- a/src/filter/convert_filter_plugin.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "filter/convert_filter_plugin.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" -#include "conf.h" -#include "pcm_convert.h" -#include "audio_format.h" -#include "poison.h" - -#include <assert.h> -#include <string.h> - -struct convert_filter { - struct filter base; - - /** - * The current convert, from 0 to #PCM_CONVERT_1. - */ - unsigned convert; - - /** - * The input audio format; PCM data is passed to the filter() - * method in this format. - */ - struct audio_format in_audio_format; - - /** - * The output audio format; the consumer of this plugin - * expects PCM data in this format. This defaults to - * #in_audio_format, and can be set with convert_filter_set(). - */ - struct audio_format out_audio_format; - - struct pcm_convert_state state; -}; - -static struct filter * -convert_filter_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct convert_filter *filter = g_new(struct convert_filter, 1); - - filter_init(&filter->base, &convert_filter_plugin); - return &filter->base; -} - -static void -convert_filter_finish(struct filter *filter) -{ - g_free(filter); -} - -static const struct audio_format * -convert_filter_open(struct filter *_filter, struct audio_format *audio_format, - G_GNUC_UNUSED GError **error_r) -{ - struct convert_filter *filter = (struct convert_filter *)_filter; - - assert(audio_format_valid(audio_format)); - - filter->in_audio_format = filter->out_audio_format = *audio_format; - pcm_convert_init(&filter->state); - - return &filter->in_audio_format; -} - -static void -convert_filter_close(struct filter *_filter) -{ - struct convert_filter *filter = (struct convert_filter *)_filter; - - pcm_convert_deinit(&filter->state); - - poison_undefined(&filter->in_audio_format, - sizeof(filter->in_audio_format)); - poison_undefined(&filter->out_audio_format, - sizeof(filter->out_audio_format)); -} - -static const void * -convert_filter_filter(struct filter *_filter, const void *src, size_t src_size, - size_t *dest_size_r, GError **error_r) -{ - struct convert_filter *filter = (struct convert_filter *)_filter; - const void *dest; - - if (audio_format_equals(&filter->in_audio_format, - &filter->out_audio_format)) { - /* optimized special case: no-op */ - *dest_size_r = src_size; - return src; - } - - dest = pcm_convert(&filter->state, &filter->in_audio_format, - src, src_size, - &filter->out_audio_format, dest_size_r, - error_r); - if (dest == NULL) - return NULL; - - return dest; -} - -const struct filter_plugin convert_filter_plugin = { - .name = "convert", - .init = convert_filter_init, - .finish = convert_filter_finish, - .open = convert_filter_open, - .close = convert_filter_close, - .filter = convert_filter_filter, -}; - -void -convert_filter_set(struct filter *_filter, - const struct audio_format *out_audio_format) -{ - struct convert_filter *filter = (struct convert_filter *)_filter; - - assert(filter != NULL); - assert(audio_format_valid(&filter->in_audio_format)); - assert(audio_format_valid(&filter->out_audio_format)); - assert(out_audio_format != NULL); - assert(audio_format_valid(out_audio_format)); - - filter->out_audio_format = *out_audio_format; -} diff --git a/src/filter/convert_filter_plugin.h b/src/filter/convert_filter_plugin.h deleted file mode 100644 index 156adf8e3..000000000 --- a/src/filter/convert_filter_plugin.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef CONVERT_FILTER_PLUGIN_H -#define CONVERT_FILTER_PLUGIN_H - -struct filter; -struct audio_format; - -/** - * Sets the output audio format for the specified filter. You must - * call this after the filter has been opened. Since this audio - * format switch is a violation of the filter API, this filter must be - * the last in a chain. - */ -void -convert_filter_set(struct filter *filter, - const struct audio_format *out_audio_format); - -#endif diff --git a/src/filter/normalize_filter_plugin.c b/src/filter/normalize_filter_plugin.c deleted file mode 100644 index 2151482e4..000000000 --- a/src/filter/normalize_filter_plugin.c +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" -#include "pcm_buffer.h" -#include "audio_format.h" -#include "AudioCompress/compress.h" - -#include <assert.h> -#include <string.h> - -struct normalize_filter { - struct filter filter; - - struct Compressor *compressor; - - struct pcm_buffer buffer; -}; - -static inline GQuark -normalize_quark(void) -{ - return g_quark_from_static_string("normalize"); -} - -static struct filter * -normalize_filter_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct normalize_filter *filter = g_new(struct normalize_filter, 1); - - filter_init(&filter->filter, &normalize_filter_plugin); - - return &filter->filter; -} - -static void -normalize_filter_finish(struct filter *filter) -{ - g_free(filter); -} - -static const struct audio_format * -normalize_filter_open(struct filter *_filter, - struct audio_format *audio_format, - G_GNUC_UNUSED GError **error_r) -{ - struct normalize_filter *filter = (struct normalize_filter *)_filter; - - audio_format->format = SAMPLE_FORMAT_S16; - - filter->compressor = Compressor_new(0); - - pcm_buffer_init(&filter->buffer); - - return audio_format; -} - -static void -normalize_filter_close(struct filter *_filter) -{ - struct normalize_filter *filter = (struct normalize_filter *)_filter; - - pcm_buffer_deinit(&filter->buffer); - Compressor_delete(filter->compressor); -} - -static const void * -normalize_filter_filter(struct filter *_filter, - const void *src, size_t src_size, size_t *dest_size_r, - G_GNUC_UNUSED GError **error_r) -{ - struct normalize_filter *filter = (struct normalize_filter *)_filter; - void *dest; - - dest = pcm_buffer_get(&filter->buffer, src_size); - - memcpy(dest, src, src_size); - - Compressor_Process_int16(filter->compressor, dest, src_size / 2); - - *dest_size_r = src_size; - return dest; -} - -const struct filter_plugin normalize_filter_plugin = { - .name = "normalize", - .init = normalize_filter_init, - .finish = normalize_filter_finish, - .open = normalize_filter_open, - .close = normalize_filter_close, - .filter = normalize_filter_filter, -}; diff --git a/src/filter/null_filter_plugin.c b/src/filter/null_filter_plugin.c deleted file mode 100644 index e7c998827..000000000 --- a/src/filter/null_filter_plugin.c +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This filter plugin does nothing. That is not quite useful, except - * for testing the filter core, or as a template for new filter - * plugins. - */ - -#include "config.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" - -#include <assert.h> - -struct null_filter { - struct filter filter; -}; - -static struct filter * -null_filter_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct null_filter *filter = g_new(struct null_filter, 1); - - filter_init(&filter->filter, &null_filter_plugin); - return &filter->filter; -} - -static void -null_filter_finish(struct filter *_filter) -{ - struct null_filter *filter = (struct null_filter *)_filter; - - g_free(filter); -} - -static const struct audio_format * -null_filter_open(struct filter *_filter, struct audio_format *audio_format, - G_GNUC_UNUSED GError **error_r) -{ - struct null_filter *filter = (struct null_filter *)_filter; - (void)filter; - - return audio_format; -} - -static void -null_filter_close(struct filter *_filter) -{ - struct null_filter *filter = (struct null_filter *)_filter; - (void)filter; -} - -static const void * -null_filter_filter(struct filter *_filter, - const void *src, size_t src_size, - size_t *dest_size_r, G_GNUC_UNUSED GError **error_r) -{ - struct null_filter *filter = (struct null_filter *)_filter; - (void)filter; - - /* return the unmodified source buffer */ - *dest_size_r = src_size; - return src; -} - -const struct filter_plugin null_filter_plugin = { - .name = "null", - .init = null_filter_init, - .finish = null_filter_finish, - .open = null_filter_open, - .close = null_filter_close, - .filter = null_filter_filter, -}; diff --git a/src/filter/replay_gain_filter_plugin.c b/src/filter/replay_gain_filter_plugin.c deleted file mode 100644 index 583a09f90..000000000 --- a/src/filter/replay_gain_filter_plugin.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "filter/replay_gain_filter_plugin.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" -#include "audio_format.h" -#include "pcm_buffer.h" -#include "pcm_volume.h" -#include "replay_gain_info.h" -#include "replay_gain_config.h" -#include "mixer_control.h" - -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "replay_gain" - -struct replay_gain_filter { - struct filter filter; - - /** - * If set, then this hardware mixer is used for applying - * replay gain, instead of the software volume library. - */ - struct mixer *mixer; - - /** - * The base volume level for scale=1.0, between 1 and 100 - * (including). - */ - unsigned base; - - enum replay_gain_mode mode; - - struct replay_gain_info info; - - /** - * The current volume, between 0 and a value that may or may not exceed - * #PCM_VOLUME_1. - * - * If the default value of true is used for replaygain_limit, the - * application of the volume to the signal will never cause clipping. - * - * On the other hand, if the user has set replaygain_limit to false, - * the chance of clipping is explicitly preferred if that's required to - * maintain a consistent audio level. Whether clipping will actually - * occur depends on what value the user is using for replaygain_preamp. - */ - unsigned volume; - - struct audio_format audio_format; - - struct pcm_buffer buffer; -}; - -static inline GQuark -replay_gain_quark(void) -{ - return g_quark_from_static_string("replay_gain"); -} - -/** - * Recalculates the new volume after a property was changed. - */ -static void -replay_gain_filter_update(struct replay_gain_filter *filter) -{ - if (filter->mode != REPLAY_GAIN_OFF) { - float scale = replay_gain_tuple_scale(&filter->info.tuples[filter->mode], - replay_gain_preamp, replay_gain_missing_preamp, replay_gain_limit); - g_debug("scale=%f\n", (double)scale); - - filter->volume = pcm_float_to_volume(scale); - } else - filter->volume = PCM_VOLUME_1; - - if (filter->mixer != NULL) { - /* update the hardware mixer volume */ - - unsigned volume = (filter->volume * filter->base) / PCM_VOLUME_1; - if (volume > 100) - volume = 100; - - GError *error = NULL; - if (!mixer_set_volume(filter->mixer, volume, &error)) { - g_warning("Failed to update hardware mixer: %s", - error->message); - g_error_free(error); - } - } -} - -static struct filter * -replay_gain_filter_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct replay_gain_filter *filter = g_new(struct replay_gain_filter, 1); - - filter_init(&filter->filter, &replay_gain_filter_plugin); - filter->mixer = NULL; - - filter->mode = replay_gain_get_real_mode(); - replay_gain_info_init(&filter->info); - filter->volume = PCM_VOLUME_1; - - return &filter->filter; -} - -static void -replay_gain_filter_finish(struct filter *filter) -{ - g_free(filter); -} - -static const struct audio_format * -replay_gain_filter_open(struct filter *_filter, - struct audio_format *audio_format, - G_GNUC_UNUSED GError **error_r) -{ - struct replay_gain_filter *filter = - (struct replay_gain_filter *)_filter; - - filter->audio_format = *audio_format; - pcm_buffer_init(&filter->buffer); - - return &filter->audio_format; -} - -static void -replay_gain_filter_close(struct filter *_filter) -{ - struct replay_gain_filter *filter = - (struct replay_gain_filter *)_filter; - - pcm_buffer_deinit(&filter->buffer); -} - -static const void * -replay_gain_filter_filter(struct filter *_filter, - const void *src, size_t src_size, - size_t *dest_size_r, GError **error_r) -{ - struct replay_gain_filter *filter = - (struct replay_gain_filter *)_filter; - bool success; - void *dest; - enum replay_gain_mode rg_mode; - - /* check if the mode has been changed since the last call */ - rg_mode = replay_gain_get_real_mode(); - - if (filter->mode != rg_mode) { - g_debug("replay gain mode has changed %d->%d\n", filter->mode, rg_mode); - filter->mode = rg_mode; - replay_gain_filter_update(filter); - } - - *dest_size_r = src_size; - - if (filter->volume == PCM_VOLUME_1) - /* optimized special case: 100% volume = no-op */ - return src; - - dest = pcm_buffer_get(&filter->buffer, src_size); - - if (filter->volume <= 0) { - /* optimized special case: 0% volume = memset(0) */ - /* XXX is this valid for all sample formats? What - about floating point? */ - memset(dest, 0, src_size); - return dest; - } - - memcpy(dest, src, src_size); - - success = pcm_volume(dest, src_size, filter->audio_format.format, - filter->volume); - if (!success) { - g_set_error(error_r, replay_gain_quark(), 0, - "pcm_volume() has failed"); - return NULL; - } - - return dest; -} - -const struct filter_plugin replay_gain_filter_plugin = { - .name = "replay_gain", - .init = replay_gain_filter_init, - .finish = replay_gain_filter_finish, - .open = replay_gain_filter_open, - .close = replay_gain_filter_close, - .filter = replay_gain_filter_filter, -}; - -void -replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer, - unsigned base) -{ - struct replay_gain_filter *filter = - (struct replay_gain_filter *)_filter; - - assert(mixer == NULL || (base > 0 && base <= 100)); - - filter->mixer = mixer; - filter->base = base; - - replay_gain_filter_update(filter); -} - -void -replay_gain_filter_set_info(struct filter *_filter, - const struct replay_gain_info *info) -{ - struct replay_gain_filter *filter = - (struct replay_gain_filter *)_filter; - - if (info != NULL) { - filter->info = *info; - replay_gain_info_complete(&filter->info); - } else - replay_gain_info_init(&filter->info); - - replay_gain_filter_update(filter); -} diff --git a/src/filter/replay_gain_filter_plugin.h b/src/filter/replay_gain_filter_plugin.h deleted file mode 100644 index 45b738e40..000000000 --- a/src/filter/replay_gain_filter_plugin.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef REPLAY_GAIN_FILTER_PLUGIN_H -#define REPLAY_GAIN_FILTER_PLUGIN_H - -#include "replay_gain_info.h" - -struct filter; -struct mixer; - -/** - * Enables or disables the hardware mixer for applying replay gain. - * - * @param mixer the hardware mixer, or NULL to fall back to software - * volume - * @param base the base volume level for scale=1.0, between 1 and 100 - * (including). - */ -void -replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer, - unsigned base); - -/** - * Sets a new #replay_gain_info at the beginning of a new song. - * - * @param info the new #replay_gain_info value, or NULL if no replay - * gain data is available for the current song - */ -void -replay_gain_filter_set_info(struct filter *filter, - const struct replay_gain_info *info); - -#endif diff --git a/src/filter/route_filter_plugin.c b/src/filter/route_filter_plugin.c deleted file mode 100644 index 3bf8677e5..000000000 --- a/src/filter/route_filter_plugin.c +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This filter copies audio data between channels. Useful for - * upmixing mono/stereo audio to surround speaker configurations. - * - * Its configuration consists of a "filter" section with a single - * "routes" entry, formatted as: \\ - * routes "0>1, 1>0, 2>2, 3>3, 3>4" \\ - * where each pair of numbers signifies a set of channels. - * Each source>dest pair leads to the data from channel #source - * being copied to channel #dest in the output. - * - * Example: \\ - * routes "0>0, 1>1, 0>2, 1>3"\\ - * upmixes stereo audio to a 4-speaker system, copying the front-left - * (0) to front left (0) and rear left (2), copying front-right (1) to - * front-right (1) and rear-right (3). - * - * If multiple sources are copied to the same destination channel, only - * one of them takes effect. - */ - -#include "config.h" -#include "conf.h" -#include "audio_format.h" -#include "audio_check.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" -#include "pcm_buffer.h" - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - - -struct route_filter { - - /** - * Inherit (and support cast to/from) filter - */ - struct filter base; - - /** - * The minimum number of channels we need for output - * to be able to perform all the copies the user has specified - */ - unsigned char min_output_channels; - - /** - * The minimum number of input channels we need to - * copy all the data the user has requested. If fewer - * than this many are supplied by the input, undefined - * copy operations are given zeroed sources in stead. - */ - unsigned char min_input_channels; - - /** - * The set of copy operations to perform on each sample - * The index is an output channel to use, the value is - * a corresponding input channel from which to take the - * data. A -1 means "no source" - */ - signed char* sources; - - /** - * The actual input format of our signal, once opened - */ - struct audio_format input_format; - - /** - * The decided upon output format, once opened - */ - struct audio_format output_format; - - /** - * The size, in bytes, of each multichannel frame in the - * input buffer - */ - size_t input_frame_size; - - /** - * The size, in bytes, of each multichannel frame in the - * output buffer - */ - size_t output_frame_size; - - /** - * The output buffer used last time around, can be reused if the size doesn't differ. - */ - struct pcm_buffer output_buffer; - -}; - -/** - * Parse the "routes" section, a string on the form - * a>b, c>d, e>f, ... - * where a... are non-unique, non-negative integers - * and input channel a gets copied to output channel b, etc. - * @param param the configuration block to read - * @param filter a route_filter whose min_channels and sources[] to set - * @return true on success, false on error - */ -static bool -route_filter_parse(const struct config_param *param, - struct route_filter *filter, - GError **error_r) { - - /* TODO: - * With a more clever way of marking "don't copy to output N", - * This could easily be merged into a single loop with some - * dynamic g_realloc() instead of one count run and one g_malloc(). - */ - - gchar **tokens; - int number_of_copies; - - // A cowardly default, just passthrough stereo - const char *routes = - config_get_block_string(param, "routes", "0>0, 1>1"); - - filter->min_input_channels = 0; - filter->min_output_channels = 0; - - tokens = g_strsplit(routes, ",", 255); - number_of_copies = g_strv_length(tokens); - - // Start by figuring out a few basic things about the routing set - for (int c=0; c<number_of_copies; ++c) { - - // String and int representations of the source/destination - gchar **sd; - int source, dest; - - // Squeeze whitespace - g_strstrip(tokens[c]); - - // Split the a>b string into source and destination - sd = g_strsplit(tokens[c], ">", 2); - if (g_strv_length(sd) != 2) { - g_set_error(error_r, config_quark(), 1, - "Invalid copy around %d in routes spec: %s", - param->line, tokens[c]); - g_strfreev(sd); - g_strfreev(tokens); - return false; - } - - source = strtol(sd[0], NULL, 10); - dest = strtol(sd[1], NULL, 10); - - // Keep track of the highest channel numbers seen - // as either in- or outputs - if (source >= filter->min_input_channels) - filter->min_input_channels = source + 1; - if (dest >= filter->min_output_channels) - filter->min_output_channels = dest + 1; - - g_strfreev(sd); - } - - if (!audio_valid_channel_count(filter->min_output_channels)) { - g_strfreev(tokens); - g_set_error(error_r, audio_format_quark(), 0, - "Invalid number of output channels requested: %d", - filter->min_output_channels); - return false; - } - - // Allocate a map of "copy nothing to me" - filter->sources = - g_malloc(filter->min_output_channels * sizeof(signed char)); - - for (int i=0; i<filter->min_output_channels; ++i) - filter->sources[i] = -1; - - // Run through the spec again, and save the - // actual mapping output <- input - for (int c=0; c<number_of_copies; ++c) { - - // String and int representations of the source/destination - gchar **sd; - int source, dest; - - // Split the a>b string into source and destination - sd = g_strsplit(tokens[c], ">", 2); - if (g_strv_length(sd) != 2) { - g_set_error(error_r, config_quark(), 1, - "Invalid copy around %d in routes spec: %s", - param->line, tokens[c]); - g_strfreev(sd); - g_strfreev(tokens); - return false; - } - - source = strtol(sd[0], NULL, 10); - dest = strtol(sd[1], NULL, 10); - - filter->sources[dest] = source; - - g_strfreev(sd); - } - - g_strfreev(tokens); - - return true; -} - -static struct filter * -route_filter_init(const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct route_filter *filter = g_new(struct route_filter, 1); - filter_init(&filter->base, &route_filter_plugin); - - // Allocate and set the filter->sources[] array - route_filter_parse(param, filter, error_r); - - return &filter->base; -} - -static void -route_filter_finish(struct filter *_filter) -{ - struct route_filter *filter = (struct route_filter *)_filter; - - g_free(filter->sources); - g_free(filter); -} - -static const struct audio_format * -route_filter_open(struct filter *_filter, struct audio_format *audio_format, - G_GNUC_UNUSED GError **error_r) -{ - struct route_filter *filter = (struct route_filter *)_filter; - - // Copy the input format for later reference - filter->input_format = *audio_format; - filter->input_frame_size = - audio_format_frame_size(&filter->input_format); - - // Decide on an output format which has enough channels, - // and is otherwise identical - filter->output_format = *audio_format; - filter->output_format.channels = filter->min_output_channels; - - // Precalculate this simple value, to speed up allocation later - filter->output_frame_size = - audio_format_frame_size(&filter->output_format); - - // This buffer grows as needed - pcm_buffer_init(&filter->output_buffer); - - return &filter->output_format; -} - -static void -route_filter_close(struct filter *_filter) -{ - struct route_filter *filter = (struct route_filter *)_filter; - - pcm_buffer_deinit(&filter->output_buffer); -} - -static const void * -route_filter_filter(struct filter *_filter, - const void *src, size_t src_size, - size_t *dest_size_r, G_GNUC_UNUSED GError **error_r) -{ - struct route_filter *filter = (struct route_filter *)_filter; - - size_t number_of_frames = src_size / filter->input_frame_size; - - size_t bytes_per_frame_per_channel = - audio_format_sample_size(&filter->input_format); - - // A moving pointer that always refers to channel 0 in the input, at the currently handled frame - const uint8_t *base_source = src; - - // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output - uint8_t *chan_destination; - - // Grow our reusable buffer, if needed, and set the moving pointer - *dest_size_r = number_of_frames * filter->output_frame_size; - chan_destination = pcm_buffer_get(&filter->output_buffer, *dest_size_r); - - - // Perform our copy operations, with N input channels and M output channels - for (unsigned int s=0; s<number_of_frames; ++s) { - - // Need to perform one copy per output channel - for (unsigned int c=0; c<filter->min_output_channels; ++c) { - if (filter->sources[c] == -1 || - (unsigned)filter->sources[c] >= filter->input_format.channels) { - // No source for this destination output, - // give it zeroes as input - memset(chan_destination, - 0x00, - bytes_per_frame_per_channel); - } else { - // Get the data from channel sources[c] - // and copy it to the output - const uint8_t *data = base_source + - (filter->sources[c] * bytes_per_frame_per_channel); - memcpy(chan_destination, - data, - bytes_per_frame_per_channel); - } - // Move on to the next output channel - chan_destination += bytes_per_frame_per_channel; - } - - - // Go on to the next N input samples - base_source += filter->input_frame_size; - } - - // Here it is, ladies and gentlemen! Rerouted data! - return (void *) filter->output_buffer.buffer; -} - -const struct filter_plugin route_filter_plugin = { - .name = "route", - .init = route_filter_init, - .finish = route_filter_finish, - .open = route_filter_open, - .close = route_filter_close, - .filter = route_filter_filter, -}; diff --git a/src/filter/volume_filter_plugin.c b/src/filter/volume_filter_plugin.c deleted file mode 100644 index 3260e8989..000000000 --- a/src/filter/volume_filter_plugin.c +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "filter/volume_filter_plugin.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" -#include "conf.h" -#include "pcm_buffer.h" -#include "pcm_volume.h" -#include "audio_format.h" - -#include <assert.h> -#include <string.h> - -struct volume_filter { - struct filter filter; - - /** - * The current volume, from 0 to #PCM_VOLUME_1. - */ - unsigned volume; - - struct audio_format audio_format; - - struct pcm_buffer buffer; -}; - -static inline GQuark -volume_quark(void) -{ - return g_quark_from_static_string("pcm_volume"); -} - -static struct filter * -volume_filter_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct volume_filter *filter = g_new(struct volume_filter, 1); - - filter_init(&filter->filter, &volume_filter_plugin); - filter->volume = PCM_VOLUME_1; - - return &filter->filter; -} - -static void -volume_filter_finish(struct filter *filter) -{ - g_free(filter); -} - -static const struct audio_format * -volume_filter_open(struct filter *_filter, struct audio_format *audio_format, - G_GNUC_UNUSED GError **error_r) -{ - struct volume_filter *filter = (struct volume_filter *)_filter; - - filter->audio_format = *audio_format; - pcm_buffer_init(&filter->buffer); - - return &filter->audio_format; -} - -static void -volume_filter_close(struct filter *_filter) -{ - struct volume_filter *filter = (struct volume_filter *)_filter; - - pcm_buffer_deinit(&filter->buffer); -} - -static const void * -volume_filter_filter(struct filter *_filter, const void *src, size_t src_size, - size_t *dest_size_r, GError **error_r) -{ - struct volume_filter *filter = (struct volume_filter *)_filter; - bool success; - void *dest; - - *dest_size_r = src_size; - - if (filter->volume >= PCM_VOLUME_1) - /* optimized special case: 100% volume = no-op */ - return src; - - dest = pcm_buffer_get(&filter->buffer, src_size); - - if (filter->volume <= 0) { - /* optimized special case: 0% volume = memset(0) */ - /* XXX is this valid for all sample formats? What - about floating point? */ - memset(dest, 0, src_size); - return dest; - } - - memcpy(dest, src, src_size); - - success = pcm_volume(dest, src_size, filter->audio_format.format, - filter->volume); - if (!success) { - g_set_error(error_r, volume_quark(), 0, - "pcm_volume() has failed"); - return NULL; - } - - return dest; -} - -const struct filter_plugin volume_filter_plugin = { - .name = "volume", - .init = volume_filter_init, - .finish = volume_filter_finish, - .open = volume_filter_open, - .close = volume_filter_close, - .filter = volume_filter_filter, -}; - -unsigned -volume_filter_get(const struct filter *_filter) -{ - const struct volume_filter *filter = - (const struct volume_filter *)_filter; - - assert(filter->filter.plugin == &volume_filter_plugin); - assert(filter->volume <= PCM_VOLUME_1); - - return filter->volume; -} - -void -volume_filter_set(struct filter *_filter, unsigned volume) -{ - struct volume_filter *filter = (struct volume_filter *)_filter; - - assert(filter->filter.plugin == &volume_filter_plugin); - assert(volume <= PCM_VOLUME_1); - - filter->volume = volume; -} - diff --git a/src/filter/volume_filter_plugin.h b/src/filter/volume_filter_plugin.h deleted file mode 100644 index 5b16f7e57..000000000 --- a/src/filter/volume_filter_plugin.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef VOLUME_FILTER_PLUGIN_H -#define VOLUME_FILTER_PLUGIN_H - -struct filter; - -unsigned -volume_filter_get(const struct filter *filter); - -void -volume_filter_set(struct filter *filter, unsigned volume); - -#endif diff --git a/src/filter_config.c b/src/filter_config.c deleted file mode 100644 index ab9bdb0c5..000000000 --- a/src/filter_config.c +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "filter_config.h" -#include "config.h" -#include "conf.h" -#include "filter/chain_filter_plugin.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" - -#include <string.h> - - -static GQuark -filter_quark(void) -{ - return g_quark_from_static_string("filter"); -} - -/** - * Find the "filter" configuration block for the specified name. - * - * @param filter_template_name the name of the filter template - * @param error_r space to return an error description - * @return the configuration block, or NULL if none was configured - */ -static const struct config_param * -filter_plugin_config(const char *filter_template_name, GError **error_r) -{ - const struct config_param *param = NULL; - - while ((param = config_get_next_param(CONF_AUDIO_FILTER, param)) != NULL) { - const char *name = - config_get_block_string(param, "name", NULL); - if (name == NULL) { - g_set_error(error_r, filter_quark(), 1, - "filter configuration without 'name' name in line %d", - param->line); - return NULL; - } - - if (strcmp(name, filter_template_name) == 0) - return param; - } - - g_set_error(error_r, filter_quark(), 1, - "filter template not found: %s", - filter_template_name); - - return NULL; -} - -/** - * Builds a filter chain from a configuration string on the form - * "name1, name2, name3, ..." by looking up each name among the - * configured filter sections. - * @param chain the chain to append filters on - * @param spec the filter chain specification - * @param error_r space to return an error description - * @return the number of filters which were successfully added - */ -unsigned int -filter_chain_parse(struct filter *chain, const char *spec, GError **error_r) -{ - - // Split on comma - gchar** tokens = g_strsplit_set(spec, ",", 255); - - int added_filters = 0; - - // Add each name to the filter chain by instantiating an actual filter - char **template_names = tokens; - while (*template_names != NULL) { - struct filter *f; - const struct config_param *cfg; - - // Squeeze whitespace - g_strstrip(*template_names); - - cfg = filter_plugin_config(*template_names, error_r); - if (cfg == NULL) { - // The error has already been set, just stop. - break; - } - - // Instantiate one of those filter plugins with the template name as a hint - f = filter_configured_new(cfg, error_r); - if (f == NULL) { - // The error has already been set, just stop. - break; - } - - filter_chain_append(chain, f); - ++added_filters; - - ++template_names; - } - - g_strfreev(tokens); - - return added_filters; -} diff --git a/src/filter_config.h b/src/filter_config.h deleted file mode 100644 index 920cbc07c..000000000 --- a/src/filter_config.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Utility functions for filter configuration - */ - -#ifndef MPD_FILTER_CONFIG_H -#define MPD_FILTER_CONFIG_H - -#include "conf.h" -#include "filter/chain_filter_plugin.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" - - -/** - * Builds a filter chain from a configuration string on the form - * "name1, name2, name3, ..." by looking up each name among the - * configured filter sections. - * @param chain the chain to append filters on - * @param spec the filter chain specification - * @param error_r space to return an error description - * @return the number of filters which were successfully added - */ -unsigned int -filter_chain_parse(struct filter *chain, const char *spec, GError **error_r); - -#endif diff --git a/src/filter_internal.h b/src/filter_internal.h deleted file mode 100644 index 4e94599a2..000000000 --- a/src/filter_internal.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Internal stuff for the filter core and filter plugins. - */ - -#ifndef MPD_FILTER_INTERNAL_H -#define MPD_FILTER_INTERNAL_H - -struct filter { - const struct filter_plugin *plugin; -}; - -static inline void -filter_init(struct filter *filter, const struct filter_plugin *plugin) -{ - filter->plugin = plugin; -} - -#endif diff --git a/src/filter_plugin.c b/src/filter_plugin.c deleted file mode 100644 index 7173134b3..000000000 --- a/src/filter_plugin.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "filter_plugin.h" -#include "filter_internal.h" -#include "filter_registry.h" -#include "conf.h" - -#ifndef NDEBUG -#include "audio_format.h" -#endif - -#include <assert.h> - -struct filter * -filter_new(const struct filter_plugin *plugin, - const struct config_param *param, GError **error_r) -{ - assert(plugin != NULL); - assert(error_r == NULL || *error_r == NULL); - - return plugin->init(param, error_r); -} - -struct filter * -filter_configured_new(const struct config_param *param, GError **error_r) -{ - const char *plugin_name; - const struct filter_plugin *plugin; - - assert(param != NULL); - assert(error_r == NULL || *error_r == NULL); - - plugin_name = config_get_block_string(param, "plugin", NULL); - if (plugin_name == NULL) { - g_set_error(error_r, config_quark(), 0, - "No filter plugin specified"); - return NULL; - } - - plugin = filter_plugin_by_name(plugin_name); - if (plugin == NULL) { - g_set_error(error_r, config_quark(), 0, - "No such filter plugin: %s", plugin_name); - return NULL; - } - - return filter_new(plugin, param, error_r); -} - -void -filter_free(struct filter *filter) -{ - assert(filter != NULL); - - filter->plugin->finish(filter); -} - -const struct audio_format * -filter_open(struct filter *filter, struct audio_format *audio_format, - GError **error_r) -{ - const struct audio_format *out_audio_format; - - assert(filter != NULL); - assert(audio_format != NULL); - assert(audio_format_valid(audio_format)); - assert(error_r == NULL || *error_r == NULL); - - out_audio_format = filter->plugin->open(filter, audio_format, error_r); - - assert(out_audio_format == NULL || audio_format_valid(audio_format)); - assert(out_audio_format == NULL || - audio_format_valid(out_audio_format)); - - return out_audio_format; -} - -void -filter_close(struct filter *filter) -{ - assert(filter != NULL); - - filter->plugin->close(filter); -} - -const void * -filter_filter(struct filter *filter, const void *src, size_t src_size, - size_t *dest_size_r, - GError **error_r) -{ - assert(filter != NULL); - assert(src != NULL); - assert(src_size > 0); - assert(dest_size_r != NULL); - assert(error_r == NULL || *error_r == NULL); - - return filter->plugin->filter(filter, src, src_size, dest_size_r, error_r); -} diff --git a/src/filter_plugin.h b/src/filter_plugin.h deleted file mode 100644 index 58e34dfb2..000000000 --- a/src/filter_plugin.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This header declares the filter_plugin class. It describes a - * plugin API for objects which filter raw PCM data. - */ - -#ifndef MPD_FILTER_PLUGIN_H -#define MPD_FILTER_PLUGIN_H - -#include <glib.h> - -#include <stdbool.h> -#include <stddef.h> - -struct config_param; -struct filter; - -struct filter_plugin { - const char *name; - - /** - * Allocates and configures a filter. - */ - struct filter *(*init)(const struct config_param *param, - GError **error_r); - - /** - * Free instance data. - */ - void (*finish)(struct filter *filter); - - /** - * Opens a filter. - * - * @param audio_format the audio format of incoming data; the - * plugin may modify the object to enforce another input - * format - */ - const struct audio_format * - (*open)(struct filter *filter, - struct audio_format *audio_format, - GError **error_r); - - /** - * Closes a filter. - */ - void (*close)(struct filter *filter); - - /** - * Filters a block of PCM data. - */ - const void *(*filter)(struct filter *filter, - const void *src, size_t src_size, - size_t *dest_buffer_r, - GError **error_r); -}; - -/** - * Creates a new instance of the specified filter plugin. - * - * @param plugin the filter plugin - * @param param optional configuration section - * @param error location to store the error occurring, or NULL to - * ignore errors. - * @return a new filter object, or NULL on error - */ -struct filter * -filter_new(const struct filter_plugin *plugin, - const struct config_param *param, GError **error_r); - -/** - * Creates a new filter, loads configuration and the plugin name from - * the specified configuration section. - * - * @param param the configuration section - * @param error location to store the error occurring, or NULL to - * ignore errors. - * @return a new filter object, or NULL on error - */ -struct filter * -filter_configured_new(const struct config_param *param, GError **error_r); - -/** - * Deletes a filter. It must be closed prior to calling this - * function, see filter_close(). - * - * @param filter the filter object - */ -void -filter_free(struct filter *filter); - -/** - * Opens the filter, preparing it for filter_filter(). - * - * @param filter the filter object - * @param audio_format the audio format of incoming data; the plugin - * may modify the object to enforce another input format - * @param error location to store the error occurring, or NULL to - * ignore errors. - * @return the format of outgoing data - */ -const struct audio_format * -filter_open(struct filter *filter, struct audio_format *audio_format, - GError **error_r); - -/** - * Closes the filter. After that, you may call filter_open() again. - * - * @param filter the filter object - */ -void -filter_close(struct filter *filter); - -/** - * Filters a block of PCM data. - * - * @param filter the filter object - * @param src the input buffer - * @param src_size the size of #src_buffer in bytes - * @param dest_size_r the size of the returned buffer - * @param error location to store the error occurring, or NULL to - * ignore errors. - * @return the destination buffer on success (will be invalidated by - * filter_close() or filter_filter()), NULL on error - */ -const void * -filter_filter(struct filter *filter, const void *src, size_t src_size, - size_t *dest_size_r, - GError **error_r); - -#endif diff --git a/src/filter_registry.c b/src/filter_registry.c deleted file mode 100644 index dc1889398..000000000 --- a/src/filter_registry.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "filter_registry.h" -#include "filter_plugin.h" - -#include <stddef.h> -#include <string.h> - -const struct filter_plugin *const filter_plugins[] = { - &null_filter_plugin, - &route_filter_plugin, - &normalize_filter_plugin, - &volume_filter_plugin, - &replay_gain_filter_plugin, - NULL, -}; - -const struct filter_plugin * -filter_plugin_by_name(const char *name) -{ - for (unsigned i = 0; filter_plugins[i] != NULL; ++i) - if (strcmp(filter_plugins[i]->name, name) == 0) - return filter_plugins[i]; - - return NULL; -} diff --git a/src/filter_registry.h b/src/filter_registry.h deleted file mode 100644 index d3949c7c4..000000000 --- a/src/filter_registry.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This library manages all filter plugins which are enabled at - * compile time. - */ - -#ifndef MPD_FILTER_REGISTRY_H -#define MPD_FILTER_REGISTRY_H - -extern const struct filter_plugin null_filter_plugin; -extern const struct filter_plugin chain_filter_plugin; -extern const struct filter_plugin convert_filter_plugin; -extern const struct filter_plugin route_filter_plugin; -extern const struct filter_plugin normalize_filter_plugin; -extern const struct filter_plugin volume_filter_plugin; -extern const struct filter_plugin replay_gain_filter_plugin; - -const struct filter_plugin * -filter_plugin_by_name(const char *name); - -#endif diff --git a/src/fs/DirectoryReader.hxx b/src/fs/DirectoryReader.hxx new file mode 100644 index 000000000..caa1e90ec --- /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 000000000..70ab01fbd --- /dev/null +++ b/src/fs/FileSystem.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FileSystem.hxx" + +#include <errno.h> + +Path ReadLink(const Path &path) +{ +#ifdef WIN32 + (void)path; + errno = EINVAL; + return Path::Null(); +#else + char buffer[MPD_PATH_MAX]; + ssize_t size = readlink(path.c_str(), buffer, MPD_PATH_MAX); + if (size < 0) + return Path::Null(); + if (size >= MPD_PATH_MAX) { + errno = ENOMEM; + return Path::Null(); + } + buffer[size] = '\0'; + return Path::FromFS(buffer); +#endif +} diff --git a/src/fs/FileSystem.hxx b/src/fs/FileSystem.hxx new file mode 100644 index 000000000..2e1701c87 --- /dev/null +++ b/src/fs/FileSystem.hxx @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FS_FILESYSTEM_HXX +#define MPD_FS_FILESYSTEM_HXX + +#include "check.h" +#include "fd_util.h" + +#include "Path.hxx" + +#include <sys/stat.h> +#include <unistd.h> +#include <assert.h> +#include <stdio.h> + +namespace FOpenMode { +/** + * Open mode for reading text files. + */ +constexpr Path::const_pointer ReadText = "r"; + +/** + * Open mode for reading binary files. + */ +constexpr Path::const_pointer ReadBinary = "rb"; + +/** + * Open mode for writing text files. + */ +constexpr Path::const_pointer WriteText = "w"; + +/** + * Open mode for writing binary files. + */ +constexpr Path::const_pointer WriteBinary = "wb"; + +/** + * Open mode for appending text files. + */ +constexpr Path::const_pointer AppendText = "a"; + +/** + * Open mode for appending binary files. + */ +constexpr Path::const_pointer AppendBinary = "ab"; +} + +/** + * Wrapper for fopen() that uses #Path names. + */ +static inline FILE *FOpen(const Path &file, Path::const_pointer mode) +{ + return fopen(file.c_str(), mode); +} + +/** + * Wrapper for open_cloexec() that uses #Path names. + */ +static inline int OpenFile(const Path &file, int flags, int mode) +{ + return open_cloexec(file.c_str(), flags, mode); +} + +/** + * Wrapper for rename() that uses #Path names. + */ +static inline bool RenameFile(const Path &oldpath, const Path &newpath) +{ + return rename(oldpath.c_str(), newpath.c_str()) == 0; +} + +/** + * Wrapper for stat() that uses #Path names. + */ +static inline bool StatFile(const Path &file, struct stat &buf, + bool follow_symlinks = true) +{ +#ifdef WIN32 + (void)follow_symlinks; + return stat(file.c_str(), &buf) == 0; +#else + int ret = follow_symlinks + ? stat(file.c_str(), &buf) + : lstat(file.c_str(), &buf); + return ret == 0; +#endif +} + +/** + * Wrapper for unlink() that uses #Path names. + */ +static inline bool RemoveFile(const Path &file) +{ + return unlink(file.c_str()) == 0; +} + +/** + * Wrapper for readlink() that uses #Path names. + */ +Path ReadLink(const Path &path); + +/** + * Wrapper for access() that uses #Path names. + */ +static inline bool CheckAccess(const Path &path, int mode) +{ +#ifdef WIN32 + (void)path; + (void)mode; + return true; +#else + return access(path.c_str(), mode) == 0; +#endif +} + +/** + * Checks if #Path exists and is a regular file. + */ +static inline bool FileExists(const Path &path, + bool follow_symlinks = true) +{ + struct stat buf; + return StatFile(path, buf, follow_symlinks) && S_ISREG(buf.st_mode); +} + +/** + * Checks if #Path exists and is a directory. + */ +static inline bool DirectoryExists(const Path &path, + bool follow_symlinks = true) +{ + struct stat buf; + return StatFile(path, buf, follow_symlinks) && S_ISDIR(buf.st_mode); +} + +/** + * Checks if #Path exists. + */ +static inline bool PathExists(const Path &path, + bool follow_symlinks = true) +{ + struct stat buf; + return StatFile(path, buf, follow_symlinks); +} + +#endif diff --git a/src/fs/Path.cxx b/src/fs/Path.cxx new file mode 100644 index 000000000..cb808b36c --- /dev/null +++ b/src/fs/Path.cxx @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "fs/Path.hxx" +#include "conf.h" +#include "mpd_error.h" +#include "gcc.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#ifdef G_OS_WIN32 +#include <windows.h> // for GetACP() +#include <stdio.h> // for sprintf() +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "path" + +/** + * Maximal number of bytes required to represent path name in UTF-8 + * (including nul-terminator). + * This value is a rought estimate of upper bound. + * It's based on path name limit in bytes (MPD_PATH_MAX) + * and assumption that some weird encoding could represent some UTF-8 4 byte + * sequences with single byte. + */ +#define MPD_PATH_MAX_UTF8 ((MPD_PATH_MAX - 1) * 4 + 1) + +std::string fs_charset; + +std::string Path::ToUTF8(const_pointer path_fs) +{ + if (path_fs == nullptr) + return std::string(); + + GIConv conv = g_iconv_open("utf-8", fs_charset.c_str()); + if (conv == reinterpret_cast<GIConv>(-1)) + return std::string(); + + // g_iconv() does not need nul-terminator, + // std::string could be created without it too. + char path_utf8[MPD_PATH_MAX_UTF8 - 1]; + char *in = const_cast<char *>(path_fs); + char *out = path_utf8; + size_t in_left = strlen(path_fs); + size_t out_left = sizeof(path_utf8); + + size_t ret = g_iconv(conv, &in, &in_left, &out, &out_left); + + g_iconv_close(conv); + + if (ret == static_cast<size_t>(-1) || in_left > 0) + return std::string(); + + return std::string(path_utf8, sizeof(path_utf8) - out_left); +} + +Path Path::FromUTF8(const char *path_utf8) +{ + gchar *p; + + p = g_convert(path_utf8, -1, + fs_charset.c_str(), "utf-8", + NULL, NULL, NULL); + + return Path(Donate(), p); +} + +gcc_pure +static bool +IsSupportedCharset(const char *charset) +{ + /* convert a space to check if the charset is valid */ + char *test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL); + if (test == NULL) + return false; + + g_free(test); + return true; +} + +static void +SetFSCharset(const char *charset) +{ + assert(charset != NULL); + + if (!IsSupportedCharset(charset)) + MPD_ERROR("invalid filesystem charset: %s", charset); + + fs_charset = charset; + + g_debug("SetFSCharset: fs charset is: %s", fs_charset.c_str()); +} + +const std::string &Path::GetFSCharset() +{ + return fs_charset; +} + +void Path::GlobalInit() +{ + const char *charset = NULL; + + charset = config_get_string(CONF_FS_CHARSET, NULL); + if (charset == NULL) { +#ifndef G_OS_WIN32 + const gchar **encodings; + g_get_filename_charsets(&encodings); + + if (encodings[0] != NULL && *encodings[0] != '\0') + charset = encodings[0]; +#else /* G_OS_WIN32 */ + /* Glib claims that file system encoding is always utf-8 + * on native Win32 (i.e. not Cygwin). + * However this is true only if <gstdio.h> helpers are used. + * MPD uses regular <stdio.h> functions. + * Those functions use encoding determined by GetACP(). */ + static char win_charset[13]; + sprintf(win_charset, "cp%u", GetACP()); + charset = win_charset; +#endif + } + + if (charset) { + SetFSCharset(charset); + } else { + g_message("setting filesystem charset to ISO-8859-1"); + SetFSCharset("ISO-8859-1"); + } +} diff --git a/src/fs/Path.hxx b/src/fs/Path.hxx new file mode 100644 index 000000000..eaab2bde5 --- /dev/null +++ b/src/fs/Path.hxx @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FS_PATH_HXX +#define MPD_FS_PATH_HXX + +#include "check.h" +#include "gcc.h" + +#include <glib.h> + +#include <algorithm> +#include <string> + +#include <assert.h> +#include <string.h> +#include <limits.h> + +#if !defined(MPD_PATH_MAX) +# if defined(WIN32) +# define MPD_PATH_MAX 260 +# elif defined(MAXPATHLEN) +# define MPD_PATH_MAX MAXPATHLEN +# elif defined(PATH_MAX) +# define MPD_PATH_MAX PATH_MAX +# else +# define MPD_PATH_MAX 256 +# endif +#endif + +/** + * A path name in the native file system character set. + */ +class Path { +public: + typedef char value_type; + typedef value_type *pointer; + typedef const value_type *const_pointer; + +private: + pointer value; + + struct Donate {}; + + /** + * Donate the allocated pointer to a new #Path object. + */ + constexpr Path(Donate, pointer _value):value(_value) {} + + /** + * Release memory allocated by the value, but do not clear the + * value pointer. + */ + void Free() { + /* free() can be optimized by gcc, while g_free() can + not: when the compiler knows that the value is + nullptr, it will not emit a free() call in the + inlined destructor; however on Windows, we need to + call g_free(), because the value has been allocated + by GLib, and on Windows, this matters */ +#ifdef WIN32 + g_free(value); +#else + free(value); +#endif + } + +public: + /** + * Copy a #Path object. + */ + Path(const Path &other) + :value(g_strdup(other.value)) {} + + /** + * Move a #Path object. + */ + Path(Path &&other):value(other.value) { + other.value = nullptr; + } + + ~Path() { + Free(); + } + + /** + * Return a "nulled" instance. Its IsNull() method will + * return true. Such an object must not be used. + * + * @see IsNull() + */ + gcc_const + static Path Null() { + return Path(Donate(), nullptr); + } + + /** + * Join two path components with the path separator. + */ + gcc_pure gcc_nonnull_all + static Path Build(const_pointer a, const_pointer b) { + return Path(Donate(), g_build_filename(a, b, nullptr)); + } + + gcc_pure gcc_nonnull_all + static Path Build(const_pointer a, const Path &b) { + return Build(a, b.c_str()); + } + + gcc_pure gcc_nonnull_all + static Path Build(const Path &a, const_pointer b) { + return Build(a.c_str(), b); + } + + gcc_pure + static Path Build(const Path &a, const Path &b) { + return Build(a.c_str(), b.c_str()); + } + + /** + * Convert a C string that is already in the filesystem + * character set to a #Path instance. + */ + gcc_pure + static Path FromFS(const_pointer fs) { + return Path(Donate(), g_strdup(fs)); + } + + /** + * Convert a UTF-8 C string to a #Path instance. + * Returns return a "nulled" instance on error. + */ + gcc_pure + static Path FromUTF8(const char *path_utf8); + + /** + * Convert the path to UTF-8. + * Returns empty string on error or if #path_fs is null pointer. + */ + gcc_pure + static std::string ToUTF8(const_pointer path_fs); + + /** + * Performs global one-time initialization of this class. + */ + static void GlobalInit(); + + /** + * Gets file system character set name. + */ + static const std::string &GetFSCharset(); + + /** + * Copy a #Path object. + */ + Path &operator=(const Path &other) { + if (this != &other) { + Free(); + value = g_strdup(other.value); + } + + return *this; + } + + /** + * Move a #Path object. + */ + Path &operator=(Path &&other) { + std::swap(value, other.value); + return *this; + } + + /** + * Steal the allocated value. This object has an undefined + * value, and the caller is response for freeing this method's + * return value. + */ + pointer Steal() { + pointer result = value; + value = nullptr; + return result; + } + + /** + * Check if this is a "nulled" instance. A "nulled" instance + * must not be used. + */ + bool IsNull() const { + return value == nullptr; + } + + /** + * Clear this object's value, make it "nulled". + * + * @see IsNull() + */ + void SetNull() { + Free(); + value = nullptr; + } + + gcc_pure + bool empty() const { + assert(value != nullptr); + + return *value == 0; + } + + /** + * @return the length of this string in number of "value_type" + * elements (which may not be the number of characters). + */ + gcc_pure + size_t length() const { + assert(value != nullptr); + + return strlen(value); + } + + /** + * Returns the value as a const C string. The returned + * pointer is invalidated whenever the value of life of this + * instance ends. + */ + gcc_pure + const_pointer c_str() const { + assert(value != nullptr); + + return value; + } + + /** + * Convert the path to UTF-8. + * Returns empty string on error or if this instance is "nulled" + * (#IsNull returns true). + */ + std::string ToUTF8() const { + return ToUTF8(value); + } + + /** + * Gets directory name of this path. + * Returns a "nulled" instance on error. + */ + Path GetDirectoryName() const { + assert(value != nullptr); + return Path(Donate(), g_path_get_dirname(value)); + } +}; + +#endif @@ -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/gerror.h b/src/gerror.h new file mode 100644 index 000000000..fe4c54da9 --- /dev/null +++ b/src/gerror.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_GERROR_H +#define MPD_GERROR_H + +typedef struct _GError GError; + +#endif diff --git a/src/glib_compat.h b/src/glib_compat.h index 97d1fdc0c..a16b9c6eb 100644 --- a/src/glib_compat.h +++ b/src/glib_compat.h @@ -28,17 +28,6 @@ #include <glib.h> -#if !GLIB_CHECK_VERSION(2,18,0) - -static inline void -g_set_error_literal(GError **err, GQuark domain, gint code, - const gchar *message) -{ - g_set_error(err, domain, code, "%s", message); -} - -#endif - #if !GLIB_CHECK_VERSION(2,28,0) static inline gint64 diff --git a/src/glib_socket.h b/src/glib_socket.h deleted file mode 100644 index 46fab37dd..000000000 --- a/src/glib_socket.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_GLIB_SOCKET_H -#define MPD_GLIB_SOCKET_H - -#include <glib.h> - -/** - * Portable wrapper for g_io_channel_unix_new() or - * g_io_channel_win32_new_socket(). - */ -G_GNUC_MALLOC -static inline GIOChannel * -g_io_channel_new_socket(int fd) -{ -#ifdef G_OS_WIN32 - return g_io_channel_win32_new_socket(fd); -#else - return g_io_channel_unix_new(fd); -#endif -} - -#endif diff --git a/src/icy_metadata.c b/src/icy_metadata.c deleted file mode 100644 index 32953e69f..000000000 --- a/src/icy_metadata.c +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "icy_metadata.h" -#include "tag.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "icy_metadata" - -void -icy_deinit(struct icy_metadata *im) -{ - if (!icy_defined(im)) - return; - - if (im->data_rest == 0 && im->meta_size > 0) - g_free(im->meta_data); - - if (im->tag != NULL) - tag_free(im->tag); -} - -void -icy_reset(struct icy_metadata *im) -{ - if (!icy_defined(im)) - return; - - icy_deinit(im); - - im->data_rest = im->data_size; - im->meta_size = 0; -} - -size_t -icy_data(struct icy_metadata *im, size_t length) -{ - assert(length > 0); - - if (!icy_defined(im)) - return length; - - if (im->data_rest == 0) - return 0; - - if (length >= im->data_rest) { - length = im->data_rest; - im->data_rest = 0; - } else - im->data_rest -= length; - - return length; -} - -static void -icy_add_item(struct tag *tag, enum tag_type type, const char *value) -{ - size_t length = strlen(value); - - if (length >= 2 && value[0] == '\'' && value[length - 1] == '\'') { - /* strip the single quotes */ - ++value; - length -= 2; - } - - if (length > 0) - tag_add_item_n(tag, type, value, length); -} - -static void -icy_parse_tag_item(struct tag *tag, const char *item) -{ - gchar **p = g_strsplit(item, "=", 0); - - if (p[0] != NULL && p[1] != NULL) { - if (strcmp(p[0], "StreamTitle") == 0) - icy_add_item(tag, TAG_TITLE, p[1]); - else - g_debug("unknown icy-tag: '%s'", p[0]); - } - - g_strfreev(p); -} - -static struct tag * -icy_parse_tag(const char *p) -{ - struct tag *tag = tag_new(); - gchar **items = g_strsplit(p, ";", 0); - - for (unsigned i = 0; items[i] != NULL; ++i) - icy_parse_tag_item(tag, items[i]); - - g_strfreev(items); - - return tag; -} - -size_t -icy_meta(struct icy_metadata *im, const void *data, size_t length) -{ - const unsigned char *p = data; - - assert(icy_defined(im)); - assert(im->data_rest == 0); - assert(length > 0); - - if (im->meta_size == 0) { - /* read meta_size from the first byte of a meta - block */ - im->meta_size = *p++ * 16; - if (im->meta_size == 0) { - /* special case: no metadata */ - im->data_rest = im->data_size; - return 1; - } - - /* 1 byte was consumed (must be re-added later for the - return value */ - --length; - - /* initialize metadata reader, allocate enough - memory (+1 for the null terminator) */ - im->meta_position = 0; - im->meta_data = g_malloc(im->meta_size + 1); - } - - assert(im->meta_position < im->meta_size); - - if (length > im->meta_size - im->meta_position) - length = im->meta_size - im->meta_position; - - memcpy(im->meta_data + im->meta_position, p, length); - im->meta_position += length; - - if (p != data) - /* re-add the first byte (which contained meta_size) */ - ++length; - - if (im->meta_position == im->meta_size) { - /* null-terminate the string */ - - im->meta_data[im->meta_size] = 0; - - /* parse */ - - if (im->tag != NULL) - tag_free(im->tag); - - im->tag = icy_parse_tag(im->meta_data); - g_free(im->meta_data); - - /* change back to normal data mode */ - - im->meta_size = 0; - im->data_rest = im->data_size; - } - - return length; -} diff --git a/src/icy_metadata.h b/src/icy_metadata.h deleted file mode 100644 index 9797122ca..000000000 --- a/src/icy_metadata.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef ICY_METADATA_H -#define ICY_METADATA_H - -#include <stdbool.h> -#include <stddef.h> - -struct icy_metadata { - size_t data_size, data_rest; - - size_t meta_size, meta_position; - char *meta_data; - - struct tag *tag; -}; - -/** - * Initialize a disabled icy_metadata object. - */ -static inline void -icy_clear(struct icy_metadata *im) -{ - im->data_size = 0; -} - -/** - * Initialize an enabled icy_metadata object with the specified - * data_size (from the icy-metaint HTTP response header). - */ -static inline void -icy_start(struct icy_metadata *im, size_t data_size) -{ - im->data_size = im->data_rest = data_size; - im->meta_size = 0; - im->tag = NULL; -} - -/** - * Resets the icy_metadata. Call this after rewinding the stream. - */ -void -icy_reset(struct icy_metadata *im); - -void -icy_deinit(struct icy_metadata *im); - -/** - * Checks whether the icy_metadata object is enabled. - */ -static inline bool -icy_defined(const struct icy_metadata *im) -{ - return im->data_size > 0; -} - -/** - * Evaluates data. Returns the number of bytes of normal data which - * can be read by the caller, but not more than "length". If the - * return value is smaller than "length", the caller should invoke - * icy_meta(). - */ -size_t -icy_data(struct icy_metadata *im, size_t length); - -/** - * Reads metadata from the stream. Returns the number of bytes - * consumed. If the return value is smaller than "length", the caller - * should invoke icy_data(). - */ -size_t -icy_meta(struct icy_metadata *im, const void *data, size_t length); - -static inline struct tag * -icy_tag(struct icy_metadata *im) -{ - struct tag *tag = im->tag; - im->tag = NULL; - return tag; -} - -#endif diff --git a/src/icy_server.c b/src/icy_server.c deleted file mode 100644 index b6c89eaf6..000000000 --- a/src/icy_server.c +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "icy_server.h" - -#include <glib.h> - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "icy_server" - -char* -icy_server_metadata_header(const char *name, - const char *genre, const char *url, - const char *content_type, int metaint) -{ - return g_strdup_printf("ICY 200 OK\r\n" - "icy-notice1:<BR>This stream requires an audio player!<BR>\r\n" /* TODO */ - "icy-notice2:MPD - The music player daemon<BR>\r\n" - "icy-name: %s\r\n" /* TODO */ - "icy-genre: %s\r\n" /* TODO */ - "icy-url: %s\r\n" /* TODO */ - "icy-pub:1\r\n" - "icy-metaint:%d\r\n" - /* TODO "icy-br:%d\r\n" */ - "Content-Type: %s\r\n" - "Connection: close\r\n" - "Pragma: no-cache\r\n" - "Cache-Control: no-cache, no-store\r\n" - "\r\n", - name, - genre, - url, - metaint, - /* bitrate, */ - content_type); -} - -static char * -icy_server_metadata_string(const char *stream_title, const char* stream_url) -{ - gchar *icy_metadata; - guint meta_length; - - // The leading n is a placeholder for the length information - icy_metadata = g_strdup_printf("nStreamTitle='%s';" - "StreamUrl='%s';", - stream_title, - stream_url); - - g_return_val_if_fail(icy_metadata, NULL); - - meta_length = strlen(icy_metadata); - - meta_length--; // subtract placeholder - - meta_length = ((int)meta_length / 16) + 1; - - icy_metadata[0] = meta_length; - - if (meta_length > 255) { - g_free(icy_metadata); - return NULL; - } - - return icy_metadata; -} - -struct page* -icy_server_metadata_page(const struct tag *tag, ...) -{ - va_list args; - const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES]; - gint last_item, item; - guint position; - gchar *icy_string; - struct page *icy_metadata; - gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata - - // "StreamTitle='';StreamUrl='';" - // = 4081 - 28 - stream_title[0] = '\0'; - - last_item = -1; - - va_start(args, tag); - while (1) { - enum tag_type type; - const gchar *tag_item; - - type = va_arg(args, enum tag_type); - - if (type == TAG_NUM_OF_ITEM_TYPES) - break; - - tag_item = tag_get_value(tag, type); - - if (tag_item) - tag_items[++last_item] = tag_item; - } - va_end(args); - - position = item = 0; - while (position < sizeof(stream_title) && item <= last_item) { - gint length = 0; - - length = g_strlcpy(stream_title + position, - tag_items[item++], - sizeof(stream_title) - position); - - position += length; - - if (item <= last_item) { - length = g_strlcpy(stream_title + position, - " - ", - sizeof(stream_title) - position); - - position += length; - } - } - - icy_string = icy_server_metadata_string(stream_title, ""); - - if (icy_string == NULL) - return NULL; - - icy_metadata = page_new_copy(icy_string, (icy_string[0] * 16) + 1); - - g_free(icy_string); - - return icy_metadata; -} diff --git a/src/icy_server.h b/src/icy_server.h deleted file mode 100644 index 04f21d2ad..000000000 --- a/src/icy_server.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef ICY_SERVER_H -#define ICY_SERVER_H - -#include "page.h" -#include "tag.h" - -#include <stdarg.h> - -char* -icy_server_metadata_header(const char *name, - const char *genre, const char *url, - const char *content_type, int metaint); - -struct page* -icy_server_metadata_page(const struct tag *tag, ...); - -#endif diff --git a/src/idle.c b/src/idle.c deleted file mode 100644 index 2d174d78a..000000000 --- a/src/idle.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Support library for the "idle" command. - * - */ - -#include "config.h" -#include "idle.h" -#include "event_pipe.h" - -#include <assert.h> -#include <glib.h> - -static unsigned idle_flags; -static GMutex *idle_mutex = NULL; - -static const char *const idle_names[] = { - "database", - "stored_playlist", - "playlist", - "player", - "mixer", - "output", - "options", - "sticker", - "update", - "subscription", - "message", - NULL -}; - -void -idle_init(void) -{ - g_assert(idle_mutex == NULL); - idle_mutex = g_mutex_new(); -} - -void -idle_deinit(void) -{ - g_assert(idle_mutex != NULL); - g_mutex_free(idle_mutex); - idle_mutex = NULL; -} - -void -idle_add(unsigned flags) -{ - assert(flags != 0); - - g_mutex_lock(idle_mutex); - idle_flags |= flags; - g_mutex_unlock(idle_mutex); - - event_pipe_emit(PIPE_EVENT_IDLE); -} - -unsigned -idle_get(void) -{ - unsigned flags; - - g_mutex_lock(idle_mutex); - flags = idle_flags; - idle_flags = 0; - g_mutex_unlock(idle_mutex); - - return flags; -} - -const char*const* -idle_get_names(void) -{ - return idle_names; -} diff --git a/src/idle.h b/src/idle.h deleted file mode 100644 index 0156933c0..000000000 --- a/src/idle.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Support library for the "idle" command. - * - */ - -#ifndef MPD_IDLE_H -#define MPD_IDLE_H - -enum { - /** song database has been updated*/ - IDLE_DATABASE = 0x1, - - /** a stored playlist has been modified, created, deleted or - renamed */ - IDLE_STORED_PLAYLIST = 0x2, - - /** the current playlist has been modified */ - IDLE_PLAYLIST = 0x4, - - /** the player state has changed: play, stop, pause, seek, ... */ - IDLE_PLAYER = 0x8, - - /** the volume has been modified */ - IDLE_MIXER = 0x10, - - /** an audio output device has been enabled or disabled */ - IDLE_OUTPUT = 0x20, - - /** options have changed: crossfade, random, repeat, ... */ - IDLE_OPTIONS = 0x40, - - /** a sticker has been modified. */ - IDLE_STICKER = 0x80, - - /** a database update has started or finished. */ - IDLE_UPDATE = 0x100, - - /** a client has subscribed or unsubscribed to/from a channel */ - IDLE_SUBSCRIPTION = 0x200, - - /** a message on the subscribed channel was receivedd */ - IDLE_MESSAGE = 0x400, -}; - -/** - * Initialize the mutex - */ -void -idle_init(void); - -/** - * Destroy the mutex - */ -void -idle_deinit(void); - -/** - * Adds idle flag (with bitwise "or") and queues notifications to all - * clients. - */ -void -idle_add(unsigned flags); - -/** - * Atomically reads and resets the global idle flags value. - */ -unsigned -idle_get(void); - -/** - * Get idle names - */ -const char*const* -idle_get_names(void); - -#endif diff --git a/src/inotify_queue.c b/src/inotify_queue.c deleted file mode 100644 index d5e2228c3..000000000 --- a/src/inotify_queue.c +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "inotify_queue.h" -#include "update.h" - -#include <glib.h> - -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "inotify" - -enum { - /** - * Wait this long after the last change before calling - * update_enqueue(). This increases the probability that - * updates can be bundled. - */ - INOTIFY_UPDATE_DELAY_S = 5, -}; - -static GSList *inotify_queue; -static guint queue_source_id; - -void -mpd_inotify_queue_init(void) -{ -} - -static void -free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - g_free(data); -} - -void -mpd_inotify_queue_finish(void) -{ - if (queue_source_id != 0) - g_source_remove(queue_source_id); - - g_slist_foreach(inotify_queue, free_callback, NULL); - g_slist_free(inotify_queue); -} - -static gboolean -mpd_inotify_run_update(G_GNUC_UNUSED gpointer data) -{ - unsigned id; - - while (inotify_queue != NULL) { - char *uri_utf8 = inotify_queue->data; - - id = update_enqueue(uri_utf8, false); - if (id == 0) - /* retry later */ - return true; - - g_debug("updating '%s' job=%u", uri_utf8, id); - - g_free(uri_utf8); - inotify_queue = g_slist_delete_link(inotify_queue, - inotify_queue); - } - - /* done, remove the timer event by returning false */ - queue_source_id = 0; - return false; -} - -static bool -path_in(const char *path, const char *possible_parent) -{ - size_t length = strlen(possible_parent); - - return path[0] == 0 || - (memcmp(possible_parent, path, length) == 0 && - (path[length] == 0 || path[length] == '/')); -} - -void -mpd_inotify_enqueue(char *uri_utf8) -{ - GSList *old_queue = inotify_queue; - - if (queue_source_id != 0) - g_source_remove(queue_source_id); - queue_source_id = g_timeout_add_seconds(INOTIFY_UPDATE_DELAY_S, - mpd_inotify_run_update, NULL); - - inotify_queue = NULL; - while (old_queue != NULL) { - char *current_uri = old_queue->data; - - if (path_in(uri_utf8, current_uri)) { - /* already enqueued */ - g_free(uri_utf8); - inotify_queue = g_slist_concat(inotify_queue, - old_queue); - return; - } - - old_queue = g_slist_delete_link(old_queue, old_queue); - - if (path_in(current_uri, uri_utf8)) - /* existing path is a sub-path of the new - path; we can dequeue the existing path and - update the new path instead */ - g_free(current_uri); - else - /* move the existing path to the new queue */ - inotify_queue = g_slist_prepend(inotify_queue, - current_uri); - } - - inotify_queue = g_slist_prepend(inotify_queue, uri_utf8); -} diff --git a/src/inotify_queue.h b/src/inotify_queue.h deleted file mode 100644 index cfc28ebfe..000000000 --- a/src/inotify_queue.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INOTIFY_QUEUE_H -#define MPD_INOTIFY_QUEUE_H - -void -mpd_inotify_queue_init(void); - -void -mpd_inotify_queue_finish(void); - -void -mpd_inotify_enqueue(char *uri_utf8); - -#endif diff --git a/src/inotify_source.c b/src/inotify_source.c deleted file mode 100644 index e415f5e72..000000000 --- a/src/inotify_source.c +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "inotify_source.h" -#include "fifo_buffer.h" -#include "fd_util.h" -#include "mpd_error.h" - -#include <sys/inotify.h> -#include <unistd.h> -#include <errno.h> -#include <stdbool.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "inotify" - -struct mpd_inotify_source { - int fd; - - GIOChannel *channel; - - /** - * The channel's source id in the GLib main loop. - */ - guint id; - - struct fifo_buffer *buffer; - - mpd_inotify_callback_t callback; - void *callback_ctx; -}; - -/** - * A GQuark for GError instances. - */ -static inline GQuark -mpd_inotify_quark(void) -{ - return g_quark_from_static_string("inotify"); -} - -static gboolean -mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source, - G_GNUC_UNUSED GIOCondition condition, - gpointer data) -{ - struct mpd_inotify_source *source = data; - void *dest; - size_t length; - ssize_t nbytes; - const struct inotify_event *event; - - dest = fifo_buffer_write(source->buffer, &length); - if (dest == NULL) - MPD_ERROR("buffer full"); - - nbytes = read(source->fd, dest, length); - if (nbytes < 0) - MPD_ERROR("failed to read from inotify: %s", - g_strerror(errno)); - if (nbytes == 0) - MPD_ERROR("end of file from inotify"); - - fifo_buffer_append(source->buffer, nbytes); - - while (true) { - const char *name; - - event = fifo_buffer_read(source->buffer, &length); - if (event == NULL || length < sizeof(*event) || - length < sizeof(*event) + event->len) - break; - - if (event->len > 0 && event->name[event->len - 1] == 0) - name = event->name; - else - name = NULL; - - source->callback(event->wd, event->mask, name, - source->callback_ctx); - fifo_buffer_consume(source->buffer, - sizeof(*event) + event->len); - } - - return true; -} - -struct mpd_inotify_source * -mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx, - GError **error_r) -{ - struct mpd_inotify_source *source = - g_new(struct mpd_inotify_source, 1); - - source->fd = inotify_init_cloexec(); - if (source->fd < 0) { - g_set_error(error_r, mpd_inotify_quark(), errno, - "inotify_init() has failed: %s", - g_strerror(errno)); - g_free(source); - return NULL; - } - - source->buffer = fifo_buffer_new(4096); - - source->channel = g_io_channel_unix_new(source->fd); - source->id = g_io_add_watch(source->channel, G_IO_IN, - mpd_inotify_in_event, source); - - source->callback = callback; - source->callback_ctx = callback_ctx; - - return source; -} - -void -mpd_inotify_source_free(struct mpd_inotify_source *source) -{ - g_source_remove(source->id); - g_io_channel_unref(source->channel); - fifo_buffer_free(source->buffer); - close(source->fd); - g_free(source); -} - -int -mpd_inotify_source_add(struct mpd_inotify_source *source, - const char *path_fs, unsigned mask, - GError **error_r) -{ - int wd = inotify_add_watch(source->fd, path_fs, mask); - if (wd < 0) - g_set_error(error_r, mpd_inotify_quark(), errno, - "inotify_add_watch() has failed: %s", - g_strerror(errno)); - - return wd; -} - -void -mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd) -{ - int ret = inotify_rm_watch(source->fd, wd); - if (ret < 0 && errno != EINVAL) - g_warning("inotify_rm_watch() has failed: %s", - g_strerror(errno)); - - /* EINVAL may happen here when the file has been deleted; the - kernel seems to auto-unregister deleted files */ -} diff --git a/src/inotify_source.h b/src/inotify_source.h deleted file mode 100644 index f92e18e39..000000000 --- a/src/inotify_source.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INOTIFY_SOURCE_H -#define MPD_INOTIFY_SOURCE_H - -#include <glib.h> - -typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask, - const char *name, void *ctx); - -struct mpd_inotify_source; - -/** - * Creates a new inotify source and registers it in the GLib main - * loop. - * - * @param a callback invoked for events received from the kernel - */ -struct mpd_inotify_source * -mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx, - GError **error_r); - -void -mpd_inotify_source_free(struct mpd_inotify_source *source); - -/** - * Adds a path to the notify list. - * - * @return a watch descriptor or -1 on error - */ -int -mpd_inotify_source_add(struct mpd_inotify_source *source, - const char *path_fs, unsigned mask, - GError **error_r); - -/** - * Removes a path from the notify list. - * - * @param wd the watch descriptor returned by mpd_inotify_source_add() - */ -void -mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd); - -#endif diff --git a/src/inotify_update.c b/src/inotify_update.c deleted file mode 100644 index 3f4a8c0c4..000000000 --- a/src/inotify_update.c +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "inotify_update.h" -#include "inotify_source.h" -#include "inotify_queue.h" -#include "database.h" -#include "mapper.h" -#include "path.h" - -#include <assert.h> -#include <sys/inotify.h> -#include <sys/stat.h> -#include <stdbool.h> -#include <string.h> -#include <dirent.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "inotify" - -enum { - IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF - |IN_MOVE|IN_MOVE_SELF -#ifdef IN_ONLYDIR - |IN_ONLYDIR -#endif -}; - -struct watch_directory { - struct watch_directory *parent; - - char *name; - - int descriptor; - - GList *children; -}; - -static struct mpd_inotify_source *inotify_source; - -static unsigned inotify_max_depth; -static struct watch_directory inotify_root; -static GTree *inotify_directories; - -static gint -compare(gconstpointer a, gconstpointer b) -{ - if (a < b) - return -1; - else if (a > b) - return 1; - else - return 0; -} - -static void -tree_add_watch_directory(struct watch_directory *directory) -{ - g_tree_insert(inotify_directories, - GINT_TO_POINTER(directory->descriptor), directory); -} - -static void -tree_remove_watch_directory(struct watch_directory *directory) -{ - G_GNUC_UNUSED - bool found = g_tree_remove(inotify_directories, - GINT_TO_POINTER(directory->descriptor)); - assert(found); -} - -static struct watch_directory * -tree_find_watch_directory(int wd) -{ - return g_tree_lookup(inotify_directories, GINT_TO_POINTER(wd)); -} - -static void -remove_watch_directory(struct watch_directory *directory) -{ - assert(directory != NULL); - - if (directory->parent == NULL) { - g_warning("music directory was removed - " - "cannot continue to watch it"); - return; - } - - assert(directory->parent->children != NULL); - - tree_remove_watch_directory(directory); - - while (directory->children != NULL) - remove_watch_directory(directory->children->data); - - directory->parent->children = - g_list_remove(directory->parent->children, directory); - - mpd_inotify_source_rm(inotify_source, directory->descriptor); - g_free(directory->name); - g_slice_free(struct watch_directory, directory); -} - -static char * -watch_directory_get_uri_fs(const struct watch_directory *directory) -{ - char *parent_uri, *uri; - - if (directory->parent == NULL) - return NULL; - - parent_uri = watch_directory_get_uri_fs(directory->parent); - if (parent_uri == NULL) - return g_strdup(directory->name); - - uri = g_strconcat(parent_uri, "/", directory->name, NULL); - g_free(parent_uri); - - return uri; -} - -/* we don't look at "." / ".." nor files with newlines in their name */ -static bool skip_path(const char *path) -{ - return (path[0] == '.' && path[1] == 0) || - (path[0] == '.' && path[1] == '.' && path[2] == 0) || - strchr(path, '\n') != NULL; -} - -static void -recursive_watch_subdirectories(struct watch_directory *directory, - const char *path_fs, unsigned depth) -{ - GError *error = NULL; - DIR *dir; - struct dirent *ent; - - assert(directory != NULL); - assert(depth <= inotify_max_depth); - assert(path_fs != NULL); - - ++depth; - - if (depth > inotify_max_depth) - return; - - dir = opendir(path_fs); - if (dir == NULL) { - g_warning("Failed to open directory %s: %s", - path_fs, g_strerror(errno)); - return; - } - - while ((ent = readdir(dir))) { - char *child_path_fs; - struct stat st; - int ret; - struct watch_directory *child; - - if (skip_path(ent->d_name)) - continue; - - child_path_fs = g_strconcat(path_fs, "/", ent->d_name, NULL); - ret = stat(child_path_fs, &st); - if (ret < 0) { - g_warning("Failed to stat %s: %s", - child_path_fs, g_strerror(errno)); - g_free(child_path_fs); - continue; - } - - if (!S_ISDIR(st.st_mode)) { - g_free(child_path_fs); - continue; - } - - ret = mpd_inotify_source_add(inotify_source, child_path_fs, - IN_MASK, &error); - if (ret < 0) { - g_warning("Failed to register %s: %s", - child_path_fs, error->message); - g_error_free(error); - error = NULL; - g_free(child_path_fs); - continue; - } - - child = tree_find_watch_directory(ret); - if (child != NULL) { - /* already being watched */ - g_free(child_path_fs); - continue; - } - - child = g_slice_new(struct watch_directory); - child->parent = directory; - child->name = g_strdup(ent->d_name); - child->descriptor = ret; - child->children = NULL; - - directory->children = g_list_prepend(directory->children, - child); - - tree_add_watch_directory(child); - - recursive_watch_subdirectories(child, child_path_fs, depth); - g_free(child_path_fs); - } - - closedir(dir); -} - -G_GNUC_PURE -static unsigned -watch_directory_depth(const struct watch_directory *d) -{ - assert(d != NULL); - - unsigned depth = 0; - while ((d = d->parent) != NULL) - ++depth; - - return depth; -} - -static void -mpd_inotify_callback(int wd, unsigned mask, - G_GNUC_UNUSED const char *name, G_GNUC_UNUSED void *ctx) -{ - struct watch_directory *directory; - char *uri_fs; - - /*g_debug("wd=%d mask=0x%x name='%s'", wd, mask, name);*/ - - directory = tree_find_watch_directory(wd); - if (directory == NULL) - return; - - uri_fs = watch_directory_get_uri_fs(directory); - - if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) { - g_free(uri_fs); - remove_watch_directory(directory); - return; - } - - if ((mask & (IN_ATTRIB|IN_CREATE|IN_MOVE)) != 0 && - (mask & IN_ISDIR) != 0) { - /* a sub directory was changed: register those in - inotify */ - const char *root = mapper_get_music_directory_fs(); - const char *path_fs; - char *allocated = NULL; - - if (uri_fs != NULL) - path_fs = allocated = - g_strconcat(root, "/", uri_fs, NULL); - else - path_fs = root; - - recursive_watch_subdirectories(directory, path_fs, - watch_directory_depth(directory)); - g_free(allocated); - } - - if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0 || - /* at the maximum depth, we watch out for newly created - directories */ - (watch_directory_depth(directory) == inotify_max_depth && - (mask & (IN_CREATE|IN_ISDIR)) == (IN_CREATE|IN_ISDIR))) { - /* a file was changed, or a directory was - moved/deleted: queue a database update */ - char *uri_utf8 = uri_fs != NULL - ? fs_charset_to_utf8(uri_fs) - : g_strdup(""); - - if (uri_utf8 != NULL) - /* this function will take care of freeing - uri_utf8 */ - mpd_inotify_enqueue(uri_utf8); - } - - g_free(uri_fs); -} - -void -mpd_inotify_init(unsigned max_depth) -{ - GError *error = NULL; - - g_debug("initializing inotify"); - - const char *path = mapper_get_music_directory_fs(); - if (path == NULL) { - g_debug("no music directory configured"); - return; - } - - inotify_source = mpd_inotify_source_new(mpd_inotify_callback, NULL, - &error); - if (inotify_source == NULL) { - g_warning("%s", error->message); - g_error_free(error); - return; - } - - inotify_max_depth = max_depth; - - inotify_root.name = g_strdup(path); - inotify_root.descriptor = mpd_inotify_source_add(inotify_source, path, - IN_MASK, &error); - if (inotify_root.descriptor < 0) { - g_warning("%s", error->message); - g_error_free(error); - mpd_inotify_source_free(inotify_source); - inotify_source = NULL; - return; - } - - inotify_directories = g_tree_new(compare); - tree_add_watch_directory(&inotify_root); - - recursive_watch_subdirectories(&inotify_root, path, 0); - - mpd_inotify_queue_init(); - - g_debug("watching music directory"); -} - -static gboolean -free_watch_directory(G_GNUC_UNUSED gpointer key, gpointer value, - G_GNUC_UNUSED gpointer data) -{ - struct watch_directory *directory = value; - - g_free(directory->name); - g_list_free(directory->children); - - if (directory != &inotify_root) - g_slice_free(struct watch_directory, directory); - - return false; -} - -void -mpd_inotify_finish(void) -{ - if (inotify_source == NULL) - return; - - mpd_inotify_queue_finish(); - mpd_inotify_source_free(inotify_source); - - g_tree_foreach(inotify_directories, free_watch_directory, NULL); - g_tree_destroy(inotify_directories); -} diff --git a/src/inotify_update.h b/src/inotify_update.h deleted file mode 100644 index ca75c0f45..000000000 --- a/src/inotify_update.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INOTIFY_UPDATE_H -#define MPD_INOTIFY_UPDATE_H - -#include "check.h" - -#ifdef HAVE_INOTIFY_INIT - -void -mpd_inotify_init(unsigned max_depth); - -void -mpd_inotify_finish(void); - -#else /* !HAVE_INOTIFY_INIT */ - -static inline void -mpd_inotify_init(G_GNUC_UNUSED unsigned max_depth) -{ -} - -static inline void -mpd_inotify_finish(void) -{ -} - -#endif /* !HAVE_INOTIFY_INIT */ - -#endif diff --git a/src/input/ArchiveInputPlugin.cxx b/src/input/ArchiveInputPlugin.cxx new file mode 100644 index 000000000..0d856527f --- /dev/null +++ b/src/input/ArchiveInputPlugin.cxx @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ArchiveInputPlugin.hxx" +#include "ArchiveLookup.hxx" +#include "ArchiveList.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "InputPlugin.hxx" + +#include <glib.h> + +/** + * select correct archive plugin to handle the input stream + * may allow stacking of archive plugins. for example for handling + * tar.gz a gzip handler opens file (through inputfile stream) + * then it opens a tar handler and sets gzip inputstream as + * parent_stream so tar plugin fetches file data from gzip + * plugin and gzip fetches file from disk + */ +static struct input_stream * +input_archive_open(const char *pathname, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + const struct archive_plugin *arplug; + char *archive, *filename, *suffix, *pname; + struct input_stream *is; + + if (!g_path_is_absolute(pathname)) + return NULL; + + pname = g_strdup(pathname); + // archive_lookup will modify pname when true is returned + if (!archive_lookup(pname, &archive, &filename, &suffix)) { + g_debug("not an archive, lookup %s failed\n", pname); + g_free(pname); + return NULL; + } + + //check which archive plugin to use (by ext) + arplug = archive_plugin_from_suffix(suffix); + if (!arplug) { + g_warning("can't handle archive %s\n",archive); + g_free(pname); + return NULL; + } + + auto file = archive_file_open(arplug, archive, error_r); + if (file == NULL) { + g_free(pname); + return NULL; + } + + //setup fileops + is = file->OpenStream(filename, mutex, cond, error_r); + g_free(pname); + file->Close(); + + return is; +} + +const struct input_plugin input_plugin_archive = { + "archive", + nullptr, + nullptr, + input_archive_open, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, +}; diff --git a/src/input/ArchiveInputPlugin.hxx b/src/input/ArchiveInputPlugin.hxx new file mode 100644 index 000000000..96fcd0dd1 --- /dev/null +++ b/src/input/ArchiveInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_ARCHIVE_HXX +#define MPD_INPUT_ARCHIVE_HXX + +extern const struct input_plugin input_plugin_archive; + +#endif diff --git a/src/input/CdioParanoiaInputPlugin.cxx b/src/input/CdioParanoiaInputPlugin.cxx new file mode 100644 index 000000000..f0fa835b3 --- /dev/null +++ b/src/input/CdioParanoiaInputPlugin.cxx @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * CD-Audio handling (requires libcdio_paranoia) + */ + +#include "config.h" +#include "CdioParanoiaInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" + +#include <stdio.h> +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> +#include <glib.h> +#include <assert.h> + +#include <cdio/paranoia.h> +#include <cdio/cd_types.h> + +struct CdioParanoiaInputStream { + struct input_stream base; + + cdrom_drive_t *drv; + CdIo_t *cdio; + cdrom_paranoia_t *para; + + lsn_t lsn_from, lsn_to; + int lsn_relofs; + + int trackno; + + char buffer[CDIO_CD_FRAMESIZE_RAW]; + int buffer_lsn; + + CdioParanoiaInputStream(const char *uri, Mutex &mutex, Cond &cond, + int _trackno) + :base(input_plugin_cdio_paranoia, uri, mutex, cond), + drv(nullptr), cdio(nullptr), para(nullptr), + trackno(_trackno) + { + } + + ~CdioParanoiaInputStream() { + if (para != nullptr) + cdio_paranoia_free(para); + if (drv != nullptr) + cdio_cddap_close_no_free_cdio(drv); + if (cdio != nullptr) + cdio_destroy(cdio); + } +}; + +static inline GQuark +cdio_quark(void) +{ + return g_quark_from_static_string("cdio"); +} + +static void +input_cdio_close(struct input_stream *is) +{ + CdioParanoiaInputStream *i = (CdioParanoiaInputStream *)is; + + delete i; +} + +struct cdio_uri { + char device[64]; + int track; +}; + +static bool +parse_cdio_uri(struct cdio_uri *dest, const char *src, GError **error_r) +{ + if (!g_str_has_prefix(src, "cdda://")) + return false; + + src += 7; + + if (*src == 0) { + /* play the whole CD in the default drive */ + dest->device[0] = 0; + dest->track = -1; + return true; + } + + const char *slash = strrchr(src, '/'); + if (slash == nullptr) { + /* play the whole CD in the specified drive */ + g_strlcpy(dest->device, src, sizeof(dest->device)); + dest->track = -1; + return true; + } + + size_t device_length = slash - src; + if (device_length >= sizeof(dest->device)) + device_length = sizeof(dest->device) - 1; + + memcpy(dest->device, src, device_length); + dest->device[device_length] = 0; + + const char *track = slash + 1; + + char *endptr; + dest->track = strtoul(track, &endptr, 10); + if (*endptr != 0) { + g_set_error(error_r, cdio_quark(), 0, + "Malformed track number"); + return false; + } + + if (endptr == track) + /* play the whole CD */ + dest->track = -1; + + return true; +} + +static char * +cdio_detect_device(void) +{ + char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO, + false); + if (devices == nullptr) + return nullptr; + + char *device = g_strdup(devices[0]); + cdio_free_device_list(devices); + + return device; +} + +static struct input_stream * +input_cdio_open(const char *uri, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + struct cdio_uri parsed_uri; + if (!parse_cdio_uri(&parsed_uri, uri, error_r)) + return nullptr; + + CdioParanoiaInputStream *i = + new CdioParanoiaInputStream(uri, mutex, cond, + parsed_uri.track); + + /* get list of CD's supporting CD-DA */ + char *device = parsed_uri.device[0] != 0 + ? g_strdup(parsed_uri.device) + : cdio_detect_device(); + if (device == nullptr) { + g_set_error(error_r, cdio_quark(), 0, + "Unable find or access a CD-ROM drive with an audio CD in it."); + delete i; + return nullptr; + } + + /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */ + i->cdio = cdio_open(device, DRIVER_UNKNOWN); + g_free(device); + + i->drv = cdio_cddap_identify_cdio(i->cdio, 1, nullptr); + + if ( !i->drv ) { + g_set_error(error_r, cdio_quark(), 0, + "Unable to identify audio CD disc."); + delete i; + return nullptr; + } + + cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT); + + if ( 0 != cdio_cddap_open(i->drv) ) { + g_set_error(error_r, cdio_quark(), 0, "Unable to open disc."); + delete i; + return nullptr; + } + + bool reverse_endian; + switch (data_bigendianp(i->drv)) { + case -1: + g_debug("cdda: drive returns unknown audio data"); + reverse_endian = false; + break; + case 0: + g_debug("cdda: drive returns audio data Little Endian."); + reverse_endian = G_BYTE_ORDER == G_BIG_ENDIAN; + break; + case 1: + g_debug("cdda: drive returns audio data Big Endian."); + reverse_endian = G_BYTE_ORDER == G_LITTLE_ENDIAN; + break; + default: + g_set_error(error_r, cdio_quark(), 0, + "Drive returns unknown data type %d", + data_bigendianp(i->drv)); + delete i; + return nullptr; + } + + i->lsn_relofs = 0; + + if (i->trackno >= 0) { + i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno); + i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno); + } else { + i->lsn_from = 0; + i->lsn_to = cdio_get_disc_last_lsn(i->cdio); + } + + i->para = cdio_paranoia_init(i->drv); + + /* Set reading mode for full paranoia, but allow skipping sectors. */ + paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP); + + /* seek to beginning of the track */ + cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET); + + i->base.ready = true; + i->base.seekable = true; + i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW; + + /* hack to make MPD select the "pcm" decoder plugin */ + i->base.mime = g_strdup(reverse_endian + ? "audio/x-mpd-cdda-pcm-reverse" + : "audio/x-mpd-cdda-pcm"); + + return &i->base; +} + +static bool +input_cdio_seek(struct input_stream *is, + goffset offset, int whence, GError **error_r) +{ + CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; + + /* calculate absolute offset */ + switch (whence) { + case SEEK_SET: + break; + case SEEK_CUR: + offset += cis->base.offset; + break; + case SEEK_END: + offset += cis->base.size; + break; + } + + if (offset < 0 || offset > cis->base.size) { + g_set_error(error_r, cdio_quark(), 0, + "Invalid offset to seek %ld (%ld)", + (long int)offset, (long int)cis->base.size); + return false; + } + + /* simple case */ + if (offset == cis->base.offset) + return true; + + /* calculate current LSN */ + cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW; + cis->base.offset = offset; + + cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET); + + return true; +} + +static size_t +input_cdio_read(struct input_stream *is, void *ptr, size_t length, + GError **error_r) +{ + CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; + size_t nbytes = 0; + int diff; + size_t len, maxwrite; + int16_t *rbuf; + char *s_err, *s_mess; + char *wptr = (char *) ptr; + + while (length > 0) { + + + /* end of track ? */ + if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to) + break; + + //current sector was changed ? + if (cis->lsn_relofs != cis->buffer_lsn) { + rbuf = cdio_paranoia_read(cis->para, nullptr); + + s_err = cdda_errors(cis->drv); + if (s_err) { + g_warning("paranoia_read: %s", s_err ); + free(s_err); + } + s_mess = cdda_messages(cis->drv); + if (s_mess) { + free(s_mess); + } + if (!rbuf) { + g_set_error(error_r, cdio_quark(), 0, + "paranoia read error. Stopping."); + return 0; + } + //store current buffer + memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW); + cis->buffer_lsn = cis->lsn_relofs; + } else { + //use cached sector + rbuf = (int16_t*) cis->buffer; + } + + //correct offset + diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW; + + assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW); + + maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer + len = (length < maxwrite? length : maxwrite); + + //skip diff bytes from this lsn + memcpy(wptr, ((char*)rbuf) + diff, len); + //update pointer + wptr += len; + nbytes += len; + + //update offset + cis->base.offset += len; + cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW; + //update length + length -= len; + } + + return nbytes; +} + +static bool +input_cdio_eof(struct input_stream *is) +{ + CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; + + return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to); +} + +const struct input_plugin input_plugin_cdio_paranoia = { + "cdio_paranoia", + nullptr, + nullptr, + input_cdio_open, + input_cdio_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_cdio_read, + input_cdio_eof, + input_cdio_seek, +}; diff --git a/src/input/CdioParanoiaInputPlugin.hxx b/src/input/CdioParanoiaInputPlugin.hxx new file mode 100644 index 000000000..80d98b4bf --- /dev/null +++ b/src/input/CdioParanoiaInputPlugin.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX +#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX + +/** + * An input plugin based on libcdio_paranoia library. + */ +extern const struct input_plugin input_plugin_cdio_paranoia; + +#endif diff --git a/src/input/CurlInputPlugin.cxx b/src/input/CurlInputPlugin.cxx new file mode 100644 index 000000000..fe944b752 --- /dev/null +++ b/src/input/CurlInputPlugin.cxx @@ -0,0 +1,1187 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CurlInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "conf.h" +#include "tag.h" +#include "IcyMetaDataParser.hxx" +#include "event/MultiSocketMonitor.hxx" +#include "event/Loop.hxx" +#include "IOThread.hxx" + +#include <assert.h> + +#if defined(WIN32) + #include <winsock2.h> +#else + #include <sys/select.h> +#endif + +#include <string.h> +#include <errno.h> + +#include <list> +#include <forward_list> + +#include <curl/curl.h> +#include <glib.h> + +#if LIBCURL_VERSION_NUM < 0x071200 +#error libcurl is too old +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_curl" + +/** + * Do not buffer more than this number of bytes. It should be a + * reasonable limit that doesn't make low-end machines suffer too + * much, but doesn't cause stuttering on high-latency lines. + */ +static const size_t CURL_MAX_BUFFERED = 512 * 1024; + +/** + * Resume the stream at this number of bytes after it has been paused. + */ +static const size_t CURL_RESUME_AT = 384 * 1024; + +/** + * Buffers created by input_curl_writefunction(). + */ +class CurlInputBuffer { + /** size of the payload */ + size_t size; + + /** how much has been consumed yet? */ + size_t consumed; + + /** the payload */ + uint8_t *data; + +public: + CurlInputBuffer(const void *_data, size_t _size) + :size(_size), consumed(0), data(new uint8_t[size]) { + memcpy(data, _data, size); + } + + ~CurlInputBuffer() { + delete[] data; + } + + CurlInputBuffer(const CurlInputBuffer &) = delete; + CurlInputBuffer &operator=(const CurlInputBuffer &) = delete; + + const void *Begin() const { + return data + consumed; + } + + size_t TotalSize() const { + return size; + } + + size_t Available() const { + return size - consumed; + } + + /** + * Mark a part of the buffer as consumed. + * + * @return false if the buffer is now empty + */ + bool Consume(size_t length) { + assert(consumed < size); + + consumed += length; + if (consumed < size) + return true; + + assert(consumed == size); + return false; + } + + bool Read(void *dest, size_t length) { + assert(consumed + length <= size); + + memcpy(dest, data + consumed, length); + return Consume(length); + } +}; + +struct input_curl { + struct input_stream base; + + /* some buffers which were passed to libcurl, which we have + too free */ + char *range; + struct curl_slist *request_headers; + + /** the curl handles */ + CURL *easy; + + /** list of buffers, where input_curl_writefunction() appends + to, and input_curl_read() reads from them */ + std::list<CurlInputBuffer> buffers; + + /** + * Is the connection currently paused? That happens when the + * buffer was getting too large. It will be unpaused when the + * buffer is below the threshold again. + */ + bool paused; + + /** error message provided by libcurl */ + char error[CURL_ERROR_SIZE]; + + /** parser for icy-metadata */ + IcyMetaDataParser icy; + + /** the stream name from the icy-name response header */ + char *meta_name; + + /** the tag object ready to be requested via + input_stream_tag() */ + 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" */ +static struct curl_slist *http_200_aliases; + +/** HTTP proxy settings */ +static const char *proxy, *proxy_user, *proxy_password; +static unsigned proxy_port; + +static struct { + CURLM *multi; + + /** + * A linked list of all active HTTP requests. An active + * request is one that doesn't have the "eof" flag set. + */ + std::forward_list<input_curl *> requests; + + CurlSockets *sockets; +} curl; + +static inline GQuark +curl_quark(void) +{ + return g_quark_from_static_string("curl"); +} + +/** + * Find a request by its CURL "easy" handle. + * + * Runs in the I/O thread. No lock needed. + */ +static struct input_curl * +input_curl_find_request(CURL *easy) +{ + assert(io_thread_inside()); + + for (auto c : curl.requests) + if (c->easy == easy) + return c; + + return NULL; +} + +static gpointer +input_curl_resume(gpointer data) +{ + assert(io_thread_inside()); + + struct input_curl *c = (struct input_curl *)data; + + if (c->paused) { + c->paused = false; + curl_easy_pause(c->easy, CURLPAUSE_CONT); + } + + return NULL; +} + +/** + * Calculates the GLib event bit mask for one file descriptor, + * obtained from three #fd_set objects filled by curl_multi_fdset(). + */ +static unsigned +input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds) +{ + gushort events = 0; + + if (FD_ISSET(fd, rfds)) { + events |= G_IO_IN | G_IO_HUP | G_IO_ERR; + FD_CLR(fd, rfds); + } + + if (FD_ISSET(fd, wfds)) { + events |= G_IO_OUT | G_IO_ERR; + FD_CLR(fd, wfds); + } + + if (FD_ISSET(fd, efds)) { + events |= G_IO_HUP | G_IO_ERR; + FD_CLR(fd, efds); + } + + return events; +} + +/** + * Updates all registered GPollFD objects, unregisters old ones, + * registers new ones. + * + * Runs in the I/O thread. No lock needed. + */ +void +CurlSockets::UpdateSockets() +{ + assert(io_thread_inside()); + + fd_set rfds, wfds, efds; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + + int max_fd; + CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds, + &efds, &max_fd); + if (mcode != CURLM_OK) { + g_warning("curl_multi_fdset() failed: %s\n", + curl_multi_strerror(mcode)); + return; + } + + UpdateSocketList([&rfds, &wfds, &efds](int fd){ + return input_curl_fd_events(fd, &rfds, + &wfds, &efds); + }); + + for (int fd = 0; fd <= max_fd; ++fd) { + unsigned events = input_curl_fd_events(fd, &rfds, &wfds, &efds); + if (events != 0) + AddSocket(fd, events); + } +} + +/** + * Runs in the I/O thread. No lock needed. + */ +static bool +input_curl_easy_add(struct input_curl *c, GError **error_r) +{ + assert(io_thread_inside()); + assert(c != NULL); + assert(c->easy != NULL); + assert(input_curl_find_request(c->easy) == NULL); + + curl.requests.push_front(c); + + CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy); + if (mcode != CURLM_OK) { + g_set_error(error_r, curl_quark(), mcode, + "curl_multi_add_handle() failed: %s", + curl_multi_strerror(mcode)); + return false; + } + + curl.sockets->InvalidateSockets(); + + return true; +} + +struct easy_add_params { + struct input_curl *c; + GError **error_r; +}; + +static gpointer +input_curl_easy_add_callback(gpointer data) +{ + const struct easy_add_params *params = + (const struct easy_add_params *)data; + + bool success = input_curl_easy_add(params->c, params->error_r); + return GUINT_TO_POINTER(success); +} + +/** + * Call input_curl_easy_add() in the I/O thread. May be called from + * any thread. Caller must not hold a mutex. + */ +static bool +input_curl_easy_add_indirect(struct input_curl *c, GError **error_r) +{ + assert(c != NULL); + assert(c->easy != NULL); + + struct easy_add_params params = { + c, + error_r, + }; + + gpointer result = + io_thread_call(input_curl_easy_add_callback, ¶ms); + return GPOINTER_TO_UINT(result); +} + +/** + * Frees the current "libcurl easy" handle, and everything associated + * with it. + * + * Runs in the I/O thread. + */ +static void +input_curl_easy_free(struct input_curl *c) +{ + assert(io_thread_inside()); + assert(c != NULL); + + if (c->easy == NULL) + return; + + curl.requests.remove(c); + + curl_multi_remove_handle(curl.multi, c->easy); + curl_easy_cleanup(c->easy); + c->easy = NULL; + + curl_slist_free_all(c->request_headers); + c->request_headers = NULL; + + g_free(c->range); + c->range = NULL; +} + +static gpointer +input_curl_easy_free_callback(gpointer data) +{ + struct input_curl *c = (struct input_curl *)data; + + input_curl_easy_free(c); + curl.sockets->InvalidateSockets(); + + return NULL; +} + +/** + * Frees the current "libcurl easy" handle, and everything associated + * with it. + * + * The mutex must not be locked. + */ +static void +input_curl_easy_free_indirect(struct input_curl *c) +{ + io_thread_call(input_curl_easy_free_callback, c); + assert(c->easy == NULL); +} + +/** + * Abort and free all HTTP requests. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static void +input_curl_abort_all_requests(GError *error) +{ + assert(io_thread_inside()); + assert(error != NULL); + + while (!curl.requests.empty()) { + struct input_curl *c = curl.requests.front(); + assert(c->postponed_error == NULL); + + input_curl_easy_free(c); + + const ScopeLock protect(c->base.mutex); + + c->postponed_error = g_error_copy(error); + c->base.ready = true; + + c->base.cond.broadcast(); + } + + g_error_free(error); + +} + +/** + * A HTTP request is finished. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static void +input_curl_request_done(struct input_curl *c, CURLcode result, long status) +{ + assert(io_thread_inside()); + assert(c != NULL); + assert(c->easy == NULL); + assert(c->postponed_error == NULL); + + const ScopeLock protect(c->base.mutex); + + if (result != CURLE_OK) { + c->postponed_error = g_error_new(curl_quark(), result, + "curl failed: %s", + c->error); + } else if (status < 200 || status >= 300) { + c->postponed_error = g_error_new(curl_quark(), 0, + "got HTTP status %ld", + status); + } + + c->base.ready = true; + + c->base.cond.broadcast(); +} + +static void +input_curl_handle_done(CURL *easy_handle, CURLcode result) +{ + struct input_curl *c = input_curl_find_request(easy_handle); + assert(c != NULL); + + long status = 0; + curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status); + + input_curl_easy_free(c); + input_curl_request_done(c, result, status); +} + +/** + * Check for finished HTTP responses. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static void +input_curl_info_read(void) +{ + assert(io_thread_inside()); + + CURLMsg *msg; + int msgs_in_queue; + + while ((msg = curl_multi_info_read(curl.multi, + &msgs_in_queue)) != NULL) { + if (msg->msg == CURLMSG_DONE) + input_curl_handle_done(msg->easy_handle, msg->data.result); + } +} + +/** + * Give control to CURL. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static bool +input_curl_perform(void) +{ + assert(io_thread_inside()); + + CURLMcode mcode; + + do { + int running_handles; + mcode = curl_multi_perform(curl.multi, &running_handles); + } while (mcode == CURLM_CALL_MULTI_PERFORM); + + if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { + GError *error = g_error_new(curl_quark(), mcode, + "curl_multi_perform() failed: %s", + curl_multi_strerror(mcode)); + input_curl_abort_all_requests(error); + return false; + } + + return true; +} + +void +CurlSockets::PrepareSockets(gint *timeout_r) +{ + UpdateSockets(); + + have_timeout = false; + + long timeout2; + CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2); + if (mcode == CURLM_OK) { + if (timeout2 >= 0) + absolute_timeout = GetTime() + timeout2 * 1000; + + if (timeout2 >= 0 && timeout2 < 10) + /* CURL 7.21.1 likes to report "timeout=0", + which means we're running in a busy loop. + Quite a bad idea to waste so much CPU. + Let's use a lower limit of 10ms. */ + timeout2 = 10; + + *timeout_r = timeout2; + + have_timeout = timeout2 >= 0; + } else + g_warning("curl_multi_timeout() failed: %s\n", + curl_multi_strerror(mcode)); +} + +bool +CurlSockets::CheckSockets() const +{ + /* when a timeout has expired, we need to call + curl_multi_perform(), even if there was no file descriptor + event */ + return have_timeout && GetTime() >= absolute_timeout; +} + +void +CurlSockets::DispatchSockets() +{ + if (input_curl_perform()) + input_curl_info_read(); +} + +/* + * input_plugin methods + * + */ + +static bool +input_curl_init(const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + CURLcode code = curl_global_init(CURL_GLOBAL_ALL); + if (code != CURLE_OK) { + g_set_error(error_r, curl_quark(), code, + "curl_global_init() failed: %s\n", + curl_easy_strerror(code)); + return false; + } + + http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK"); + + proxy = config_get_block_string(param, "proxy", NULL); + proxy_port = config_get_block_unsigned(param, "proxy_port", 0); + proxy_user = config_get_block_string(param, "proxy_user", NULL); + proxy_password = config_get_block_string(param, "proxy_password", + NULL); + + if (proxy == NULL) { + /* deprecated proxy configuration */ + proxy = config_get_string(CONF_HTTP_PROXY_HOST, NULL); + proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0); + proxy_user = config_get_string(CONF_HTTP_PROXY_USER, NULL); + proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD, + ""); + } + + curl.multi = curl_multi_init(); + if (curl.multi == NULL) { + g_set_error(error_r, curl_quark(), 0, + "curl_multi_init() failed"); + return false; + } + + curl.sockets = new CurlSockets(io_thread_get()); + + return true; +} + +static gpointer +curl_destroy_sources(G_GNUC_UNUSED gpointer data) +{ + delete curl.sockets; + + return NULL; +} + +static void +input_curl_finish(void) +{ + assert(curl.requests.empty()); + + io_thread_call(curl_destroy_sources, NULL); + + curl_multi_cleanup(curl.multi); + + curl_slist_free_all(http_200_aliases); + + curl_global_cleanup(); +} + +/** + * Determine the total sizes of all buffers, including portions that + * have already been consumed. + * + * The caller must lock the mutex. + */ +G_GNUC_PURE +static size_t +curl_total_buffer_size(const struct input_curl *c) +{ + size_t total = 0; + + for (const auto &i : c->buffers) + total += i.TotalSize(); + + return total; +} + +input_curl::~input_curl() +{ + if (tag != NULL) + tag_free(tag); + g_free(meta_name); + + input_curl_easy_free_indirect(this); + + if (postponed_error != NULL) + g_error_free(postponed_error); +} + +static bool +input_curl_check(struct input_stream *is, GError **error_r) +{ + struct input_curl *c = (struct input_curl *)is; + + bool success = c->postponed_error == NULL; + if (!success) { + g_propagate_error(error_r, c->postponed_error); + c->postponed_error = NULL; + } + + return success; +} + +static struct tag * +input_curl_tag(struct input_stream *is) +{ + struct input_curl *c = (struct input_curl *)is; + struct tag *tag = c->tag; + + c->tag = NULL; + return tag; +} + +static bool +fill_buffer(struct input_curl *c, GError **error_r) +{ + while (c->easy != NULL && c->buffers.empty()) + c->base.cond.wait(c->base.mutex); + + if (c->postponed_error != NULL) { + g_propagate_error(error_r, c->postponed_error); + c->postponed_error = NULL; + return false; + } + + return !c->buffers.empty(); +} + +static size_t +read_from_buffer(IcyMetaDataParser &icy, std::list<CurlInputBuffer> &buffers, + void *dest0, size_t length) +{ + auto &buffer = buffers.front(); + uint8_t *dest = (uint8_t *)dest0; + size_t nbytes = 0; + + if (length > buffer.Available()) + length = buffer.Available(); + + while (true) { + size_t chunk; + + chunk = icy.Data(length); + if (chunk > 0) { + const bool empty = !buffer.Read(dest, chunk); + + nbytes += chunk; + dest += chunk; + length -= chunk; + + if (empty) { + buffers.pop_front(); + break; + } + + if (length == 0) + break; + } + + chunk = icy.Meta(buffer.Begin(), length); + if (chunk > 0) { + const bool empty = !buffer.Consume(chunk); + + length -= chunk; + + if (empty) { + buffers.pop_front(); + break; + } + + if (length == 0) + break; + } + } + + return nbytes; +} + +static void +copy_icy_tag(struct input_curl *c) +{ + struct tag *tag = c->icy.ReadTag(); + + if (tag == NULL) + return; + + if (c->tag != NULL) + tag_free(c->tag); + + if (c->meta_name != NULL && !tag_has_type(tag, TAG_NAME)) + tag_add_item(tag, TAG_NAME, c->meta_name); + + c->tag = tag; +} + +static bool +input_curl_available(struct input_stream *is) +{ + struct input_curl *c = (struct input_curl *)is; + + return c->postponed_error != NULL || c->easy == NULL || + !c->buffers.empty(); +} + +static size_t +input_curl_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + struct input_curl *c = (struct input_curl *)is; + bool success; + size_t nbytes = 0; + char *dest = (char *)ptr; + + do { + /* fill the buffer */ + + success = fill_buffer(c, error_r); + if (!success) + return 0; + + /* send buffer contents */ + + while (size > 0 && !c->buffers.empty()) { + size_t copy = read_from_buffer(c->icy, c->buffers, + dest + nbytes, size); + + nbytes += copy; + size -= copy; + } + } while (nbytes == 0); + + if (c->icy.IsDefined()) + copy_icy_tag(c); + + is->offset += (goffset)nbytes; + + if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) { + c->base.mutex.unlock(); + io_thread_call(input_curl_resume, c); + c->base.mutex.lock(); + } + + return nbytes; +} + +static void +input_curl_close(struct input_stream *is) +{ + struct input_curl *c = (struct input_curl *)is; + + delete c; +} + +static bool +input_curl_eof(G_GNUC_UNUSED struct input_stream *is) +{ + struct input_curl *c = (struct input_curl *)is; + + return c->easy == NULL && c->buffers.empty(); +} + +/** called by curl when new data is available */ +static size_t +input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) +{ + struct input_curl *c = (struct input_curl *)stream; + char name[64]; + + size *= nmemb; + + const char *header = (const char *)ptr; + const char *end = header + size; + + const char *value = (const char *)memchr(header, ':', size); + if (value == NULL || (size_t)(value - header) >= sizeof(name)) + return size; + + memcpy(name, header, value - header); + name[value - header] = 0; + + /* skip the colon */ + + ++value; + + /* strip the value */ + + while (value < end && g_ascii_isspace(*value)) + ++value; + + while (end > value && g_ascii_isspace(end[-1])) + --end; + + if (g_ascii_strcasecmp(name, "accept-ranges") == 0) { + /* a stream with icy-metadata is not seekable */ + if (!c->icy.IsDefined()) + c->base.seekable = true; + } else if (g_ascii_strcasecmp(name, "content-length") == 0) { + char buffer[64]; + + if ((size_t)(end - header) >= sizeof(buffer)) + return size; + + memcpy(buffer, value, end - value); + buffer[end - value] = 0; + + c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10); + } else if (g_ascii_strcasecmp(name, "content-type") == 0) { + c->base.mime.assign(value, end); + } else if (g_ascii_strcasecmp(name, "icy-name") == 0 || + g_ascii_strcasecmp(name, "ice-name") == 0 || + g_ascii_strcasecmp(name, "x-audiocast-name") == 0) { + g_free(c->meta_name); + c->meta_name = g_strndup(value, end - value); + + if (c->tag != NULL) + tag_free(c->tag); + + c->tag = tag_new(); + tag_add_item(c->tag, TAG_NAME, c->meta_name); + } else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) { + char buffer[64]; + size_t icy_metaint; + + if ((size_t)(end - header) >= sizeof(buffer) || + c->icy.IsDefined()) + return size; + + memcpy(buffer, value, end - value); + buffer[end - value] = 0; + + icy_metaint = g_ascii_strtoull(buffer, NULL, 10); + g_debug("icy-metaint=%zu", icy_metaint); + + if (icy_metaint > 0) { + c->icy.Start(icy_metaint); + + /* a stream with icy-metadata is not + seekable */ + c->base.seekable = false; + } + } + + return size; +} + +/** called by curl when new data is available */ +static size_t +input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) +{ + struct input_curl *c = (struct input_curl *)stream; + + size *= nmemb; + if (size == 0) + return 0; + + const ScopeLock protect(c->base.mutex); + + if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) { + c->paused = true; + return CURL_WRITEFUNC_PAUSE; + } + + c->buffers.emplace_back(ptr, size); + c->base.ready = true; + + c->base.cond.broadcast(); + return size; +} + +static bool +input_curl_easy_init(struct input_curl *c, GError **error_r) +{ + CURLcode code; + + c->easy = curl_easy_init(); + if (c->easy == NULL) { + g_set_error(error_r, curl_quark(), 0, + "curl_easy_init() failed"); + return false; + } + + curl_easy_setopt(c->easy, CURLOPT_USERAGENT, + "Music Player Daemon " VERSION); + curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION, + input_curl_headerfunction); + curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c); + curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION, + input_curl_writefunction); + curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c); + curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases); + curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(c->easy, CURLOPT_NETRC, 1); + curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5); + curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true); + curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error); + curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l); + curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l); + curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l); + + if (proxy != NULL) + curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy); + + if (proxy_port > 0) + curl_easy_setopt(c->easy, CURLOPT_PROXYPORT, (long)proxy_port); + + if (proxy_user != NULL && proxy_password != NULL) { + char *proxy_auth_str = + g_strconcat(proxy_user, ":", proxy_password, NULL); + curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str); + g_free(proxy_auth_str); + } + + code = curl_easy_setopt(c->easy, CURLOPT_URL, c->base.uri.c_str()); + if (code != CURLE_OK) { + g_set_error(error_r, curl_quark(), code, + "curl_easy_setopt() failed: %s", + curl_easy_strerror(code)); + return false; + } + + c->request_headers = NULL; + c->request_headers = curl_slist_append(c->request_headers, + "Icy-Metadata: 1"); + curl_easy_setopt(c->easy, CURLOPT_HTTPHEADER, c->request_headers); + + return true; +} + +static bool +input_curl_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) +{ + struct input_curl *c = (struct input_curl *)is; + bool ret; + + assert(is->ready); + + if (whence == SEEK_SET && offset == is->offset) + /* no-op */ + return true; + + if (!is->seekable) + return false; + + /* calculate the absolute offset */ + + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is->offset; + break; + + case SEEK_END: + if (is->size < 0) + /* stream size is not known */ + return false; + + offset += is->size; + break; + + default: + return false; + } + + if (offset < 0) + return false; + + /* check if we can fast-forward the buffer */ + + while (offset > is->offset && !c->buffers.empty()) { + auto &buffer = c->buffers.front(); + size_t length = buffer.Available(); + if (offset - is->offset < (goffset)length) + length = offset - is->offset; + + const bool empty = !buffer.Consume(length); + if (empty) + c->buffers.pop_front(); + + is->offset += length; + } + + if (offset == is->offset) + return true; + + /* close the old connection and open a new one */ + + c->base.mutex.unlock(); + + input_curl_easy_free_indirect(c); + c->buffers.clear(); + + is->offset = offset; + if (is->offset == is->size) { + /* seek to EOF: simulate empty result; avoid + triggering a "416 Requested Range Not Satisfiable" + response */ + return true; + } + + ret = input_curl_easy_init(c, error_r); + if (!ret) + return false; + + /* send the "Range" header */ + + if (is->offset > 0) { + c->range = g_strdup_printf("%lld-", (long long)is->offset); + curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range); + } + + c->base.ready = false; + + if (!input_curl_easy_add_indirect(c, error_r)) + return false; + + c->base.mutex.lock(); + + while (!c->base.ready) + c->base.cond.wait(c->base.mutex); + + if (c->postponed_error != NULL) { + g_propagate_error(error_r, c->postponed_error); + c->postponed_error = NULL; + return false; + } + + return true; +} + +static struct input_stream * +input_curl_open(const char *url, Mutex &mutex, Cond &cond, + GError **error_r) +{ + if (strncmp(url, "http://", 7) != 0) + return NULL; + + struct input_curl *c = new input_curl(url, mutex, cond); + + if (!input_curl_easy_init(c, error_r)) { + delete c; + return NULL; + } + + if (!input_curl_easy_add_indirect(c, error_r)) { + delete c; + return NULL; + } + + return &c->base; +} + +const struct input_plugin input_plugin_curl = { + "curl", + input_curl_init, + input_curl_finish, + input_curl_open, + input_curl_close, + input_curl_check, + nullptr, + input_curl_tag, + input_curl_available, + input_curl_read, + input_curl_eof, + input_curl_seek, +}; diff --git a/src/input/CurlInputPlugin.hxx b/src/input/CurlInputPlugin.hxx new file mode 100644 index 000000000..20d1309d8 --- /dev/null +++ b/src/input/CurlInputPlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_CURL_HXX +#define MPD_INPUT_CURL_HXX + +struct input_stream; + +extern const struct input_plugin input_plugin_curl; + +#endif diff --git a/src/input/DespotifyInputPlugin.cxx b/src/input/DespotifyInputPlugin.cxx new file mode 100644 index 000000000..1e5a8c606 --- /dev/null +++ b/src/input/DespotifyInputPlugin.cxx @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2011-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DespotifyInputPlugin.hxx" +#include "DespotifyUtils.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "tag.h" + +extern "C" { +#include <despotify.h> +} + +#include <glib.h> + +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include <stdio.h> + +struct DespotifyInputStream { + struct input_stream base; + + struct despotify_session *session; + struct ds_track *track; + struct tag *tag; + struct ds_pcm_data pcm; + size_t len_available; + bool eof; + + DespotifyInputStream(const char *uri, + Mutex &mutex, Cond &cond, + despotify_session *_session, + ds_track *_track) + :base(input_plugin_despotify, uri, mutex, cond), + session(_session), track(_track), + tag(mpd_despotify_tag_from_track(track)), + len_available(0), eof(false) { + + memset(&pcm, 0, sizeof(pcm)); + + /* Despotify outputs pcm data */ + base.mime = g_strdup("audio/x-mpd-cdda-pcm"); + base.ready = true; + } + + ~DespotifyInputStream() { + if (tag != NULL) + tag_free(tag); + + despotify_free_track(track); + } +}; + +static void +refill_buffer(DespotifyInputStream *ctx) +{ + /* Wait until there is data */ + while (1) { + int rc = despotify_get_pcm(ctx->session, &ctx->pcm); + + if (rc == 0 && ctx->pcm.len) { + ctx->len_available = ctx->pcm.len; + break; + } + if (ctx->eof == true) + break; + + if (rc < 0) { + g_debug("despotify_get_pcm error\n"); + ctx->eof = true; + break; + } + + /* Wait a while until next iteration */ + usleep(50 * 1000); + } +} + +static void callback(G_GNUC_UNUSED struct despotify_session* ds, + int sig, G_GNUC_UNUSED void* data, void* callback_data) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data; + + switch (sig) { + case DESPOTIFY_NEW_TRACK: + break; + + case DESPOTIFY_TIME_TELL: + break; + + case DESPOTIFY_TRACK_PLAY_ERROR: + g_debug("Track play error\n"); + ctx->eof = true; + ctx->len_available = 0; + break; + + case DESPOTIFY_END_OF_PLAYLIST: + ctx->eof = true; + g_debug("End of playlist: %d\n", ctx->eof); + break; + } +} + + +static struct input_stream * +input_despotify_open(const char *url, + Mutex &mutex, Cond &cond, + G_GNUC_UNUSED GError **error_r) +{ + struct despotify_session *session; + struct ds_link *ds_link; + struct ds_track *track; + + if (!g_str_has_prefix(url, "spt://")) + return NULL; + + session = mpd_despotify_get_session(); + if (!session) + return NULL; + + ds_link = despotify_link_from_uri(url + 6); + if (!ds_link) { + g_debug("Can't find %s\n", url); + return NULL; + } + if (ds_link->type != LINK_TYPE_TRACK) { + despotify_free_link(ds_link); + return NULL; + } + + track = despotify_link_get_track(session, ds_link); + despotify_free_link(ds_link); + if (!track) + return NULL; + + DespotifyInputStream *ctx = + new DespotifyInputStream(url, mutex, cond, + session, track); + + if (!mpd_despotify_register_callback(callback, ctx)) { + delete ctx; + return NULL; + } + + if (despotify_play(ctx->session, ctx->track, false) == false) { + mpd_despotify_unregister_callback(callback); + delete ctx; + return NULL; + } + + return &ctx->base; +} + +static size_t +input_despotify_read(struct input_stream *is, void *ptr, size_t size, + G_GNUC_UNUSED GError **error_r) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + size_t to_cpy = size; + + if (ctx->len_available == 0) + refill_buffer(ctx); + + if (ctx->len_available < size) + to_cpy = ctx->len_available; + memcpy(ptr, ctx->pcm.buf, to_cpy); + ctx->len_available -= to_cpy; + + is->offset += to_cpy; + + return to_cpy; +} + +static void +input_despotify_close(struct input_stream *is) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + + mpd_despotify_unregister_callback(callback); + delete ctx; +} + +static bool +input_despotify_eof(struct input_stream *is) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + + return ctx->eof; +} + +static bool +input_despotify_seek(G_GNUC_UNUSED struct input_stream *is, + G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence, + G_GNUC_UNUSED GError **error_r) +{ + return false; +} + +static struct tag * +input_despotify_tag(struct input_stream *is) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + struct tag *tag = ctx->tag; + + ctx->tag = NULL; + + return tag; +} + +const struct input_plugin input_plugin_despotify = { + "spt", + nullptr, + nullptr, + input_despotify_open, + input_despotify_close, + nullptr, + nullptr, + .tag = input_despotify_tag, + nullptr, + input_despotify_read, + input_despotify_eof, + input_despotify_seek, +}; diff --git a/src/input/DespotifyInputPlugin.hxx b/src/input/DespotifyInputPlugin.hxx new file mode 100644 index 000000000..00d699408 --- /dev/null +++ b/src/input/DespotifyInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2011-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef INPUT_DESPOTIFY_HXX +#define INPUT_DESPOTIFY_HXX + +extern const struct input_plugin input_plugin_despotify; + +#endif diff --git a/src/input/FfmpegInputPlugin.cxx b/src/input/FfmpegInputPlugin.cxx new file mode 100644 index 000000000..1660f177d --- /dev/null +++ b/src/input/FfmpegInputPlugin.cxx @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "FfmpegInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" + +extern "C" { +#include <libavutil/avutil.h> +#include <libavformat/avio.h> +#include <libavformat/avformat.h> +} + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_ffmpeg" + +struct FfmpegInputStream { + struct input_stream base; + + AVIOContext *h; + + bool eof; + + FfmpegInputStream(const char *uri, Mutex &mutex, Cond &cond, + AVIOContext *_h) + :base(input_plugin_ffmpeg, uri, mutex, cond), + h(_h), eof(false) { + base.ready = true; + base.seekable = (h->seekable & AVIO_SEEKABLE_NORMAL) != 0; + base.size = avio_size(h); + + /* hack to make MPD select the "ffmpeg" decoder plugin + - since avio.h doesn't tell us the MIME type of the + resource, we can't select a decoder plugin, but the + "ffmpeg" plugin is quite good at auto-detection */ + base.mime = g_strdup("audio/x-mpd-ffmpeg"); + } + + ~FfmpegInputStream() { + avio_close(h); + } +}; + +static inline GQuark +ffmpeg_quark(void) +{ + return g_quark_from_static_string("ffmpeg"); +} + +static inline bool +input_ffmpeg_supported(void) +{ + void *opaque = nullptr; + return avio_enum_protocols(&opaque, 0) != nullptr; +} + +static bool +input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + av_register_all(); + + /* disable this plugin if there's no registered protocol */ + if (!input_ffmpeg_supported()) { + g_set_error(error_r, ffmpeg_quark(), 0, + "No protocol"); + return false; + } + + return true; +} + +static struct input_stream * +input_ffmpeg_open(const char *uri, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + if (!g_str_has_prefix(uri, "gopher://") && + !g_str_has_prefix(uri, "rtp://") && + !g_str_has_prefix(uri, "rtsp://") && + !g_str_has_prefix(uri, "rtmp://") && + !g_str_has_prefix(uri, "rtmpt://") && + !g_str_has_prefix(uri, "rtmps://")) + return nullptr; + + AVIOContext *h; + int ret = avio_open(&h, uri, AVIO_FLAG_READ); + if (ret != 0) { + g_set_error(error_r, ffmpeg_quark(), ret, + "libavformat failed to open the URI"); + return nullptr; + } + + auto *i = new FfmpegInputStream(uri, mutex, cond, h); + return &i->base; +} + +static size_t +input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + FfmpegInputStream *i = (FfmpegInputStream *)is; + + int ret = avio_read(i->h, (unsigned char *)ptr, size); + if (ret <= 0) { + if (ret < 0) + g_set_error(error_r, ffmpeg_quark(), 0, + "url_read() failed"); + + i->eof = true; + return false; + } + + is->offset += ret; + return (size_t)ret; +} + +static void +input_ffmpeg_close(struct input_stream *is) +{ + FfmpegInputStream *i = (FfmpegInputStream *)is; + + delete i; +} + +static bool +input_ffmpeg_eof(struct input_stream *is) +{ + FfmpegInputStream *i = (FfmpegInputStream *)is; + + return i->eof; +} + +static bool +input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence, + G_GNUC_UNUSED GError **error_r) +{ + FfmpegInputStream *i = (FfmpegInputStream *)is; + int64_t ret = avio_seek(i->h, offset, whence); + + if (ret >= 0) { + i->eof = false; + return true; + } else { + g_set_error(error_r, ffmpeg_quark(), 0, "url_seek() failed"); + return false; + } +} + +const struct input_plugin input_plugin_ffmpeg = { + "ffmpeg", + input_ffmpeg_init, + nullptr, + input_ffmpeg_open, + input_ffmpeg_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_ffmpeg_read, + input_ffmpeg_eof, + input_ffmpeg_seek, +}; diff --git a/src/input/FfmpegInputPlugin.hxx b/src/input/FfmpegInputPlugin.hxx new file mode 100644 index 000000000..d5e3a8d9b --- /dev/null +++ b/src/input/FfmpegInputPlugin.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_INPUT_PLUGIN_HXX +#define MPD_FFMPEG_INPUT_PLUGIN_HXX + +/** + * An input plugin based on libavformat's "avio" library. + */ +extern const struct input_plugin input_plugin_ffmpeg; + +#endif diff --git a/src/input/FileInputPlugin.cxx b/src/input/FileInputPlugin.cxx new file mode 100644 index 000000000..2eecf32b6 --- /dev/null +++ b/src/input/FileInputPlugin.cxx @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "FileInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "fd_util.h" +#include "open.h" +#include "io_error.h" + +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <glib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_file" + +struct FileInputStream { + struct input_stream base; + + int fd; + + FileInputStream(const char *path, int _fd, off_t size, + Mutex &mutex, Cond &cond) + :base(input_plugin_file, path, mutex, cond), + fd(_fd) { + base.size = size; + base.seekable = true; + base.ready = true; + } + + ~FileInputStream() { + close(fd); + } +}; + +static struct input_stream * +input_file_open(const char *filename, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + int fd, ret; + struct stat st; + + if (!g_path_is_absolute(filename)) + return nullptr; + + fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0); + if (fd < 0) { + if (errno != ENOENT && errno != ENOTDIR) + g_set_error(error_r, errno_quark(), errno, + "Failed to open \"%s\": %s", + filename, g_strerror(errno)); + return nullptr; + } + + ret = fstat(fd, &st); + if (ret < 0) { + g_set_error(error_r, errno_quark(), errno, + "Failed to stat \"%s\": %s", + filename, g_strerror(errno)); + close(fd); + return nullptr; + } + + if (!S_ISREG(st.st_mode)) { + g_set_error(error_r, errno_quark(), 0, + "Not a regular file: %s", filename); + close(fd); + return nullptr; + } + +#ifdef POSIX_FADV_SEQUENTIAL + posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL); +#endif + + FileInputStream *fis = new FileInputStream(filename, fd, st.st_size, + mutex, cond); + return &fis->base; +} + +static bool +input_file_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) +{ + FileInputStream *fis = (FileInputStream *)is; + + offset = (goffset)lseek(fis->fd, (off_t)offset, whence); + if (offset < 0) { + g_set_error(error_r, errno_quark(), errno, + "Failed to seek: %s", g_strerror(errno)); + return false; + } + + is->offset = offset; + return true; +} + +static size_t +input_file_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + FileInputStream *fis = (FileInputStream *)is; + ssize_t nbytes; + + nbytes = read(fis->fd, ptr, size); + if (nbytes < 0) { + g_set_error(error_r, errno_quark(), errno, + "Failed to read: %s", g_strerror(errno)); + return 0; + } + + is->offset += nbytes; + return (size_t)nbytes; +} + +static void +input_file_close(struct input_stream *is) +{ + FileInputStream *fis = (FileInputStream *)is; + + delete fis; +} + +static bool +input_file_eof(struct input_stream *is) +{ + return is->offset >= is->size; +} + +const struct input_plugin input_plugin_file = { + "file", + nullptr, + nullptr, + input_file_open, + input_file_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_file_read, + input_file_eof, + input_file_seek, +}; diff --git a/src/input/FileInputPlugin.hxx b/src/input/FileInputPlugin.hxx new file mode 100644 index 000000000..aacfd0b5d --- /dev/null +++ b/src/input/FileInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_FILE_HXX +#define MPD_INPUT_FILE_HXX + +extern const struct input_plugin input_plugin_file; + +#endif diff --git a/src/input/MmsInputPlugin.cxx b/src/input/MmsInputPlugin.cxx new file mode 100644 index 000000000..b347eb92b --- /dev/null +++ b/src/input/MmsInputPlugin.cxx @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MmsInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" + +#include <glib.h> +#include <libmms/mmsx.h> + +#include <string.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_mms" + +struct MmsInputStream { + struct input_stream base; + + mmsx_t *mms; + + bool eof; + + MmsInputStream(const char *uri, + Mutex &mutex, Cond &cond, + mmsx_t *_mms) + :base(input_plugin_mms, uri, mutex, cond), + mms(_mms), eof(false) { + /* XX is this correct? at least this selects the ffmpeg + decoder, which seems to work fine*/ + base.mime = g_strdup("audio/x-ms-wma"); + + base.ready = true; + } + + ~MmsInputStream() { + mmsx_close(mms); + } +}; + +static inline GQuark +mms_quark(void) +{ + return g_quark_from_static_string("mms"); +} + +static struct input_stream * +input_mms_open(const char *url, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + if (!g_str_has_prefix(url, "mms://") && + !g_str_has_prefix(url, "mmsh://") && + !g_str_has_prefix(url, "mmst://") && + !g_str_has_prefix(url, "mmsu://")) + return nullptr; + + const auto mms = mmsx_connect(nullptr, nullptr, url, 128 * 1024); + if (mms == nullptr) { + g_set_error(error_r, mms_quark(), 0, "mmsx_connect() failed"); + return nullptr; + } + + auto m = new MmsInputStream(url, mutex, cond, mms); + return &m->base; +} + +static size_t +input_mms_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + MmsInputStream *m = (MmsInputStream *)is; + int ret; + + ret = mmsx_read(nullptr, m->mms, (char *)ptr, size); + if (ret <= 0) { + if (ret < 0) { + g_set_error(error_r, mms_quark(), errno, + "mmsx_read() failed: %s", + g_strerror(errno)); + } + + m->eof = true; + return false; + } + + is->offset += ret; + + return (size_t)ret; +} + +static void +input_mms_close(struct input_stream *is) +{ + MmsInputStream *m = (MmsInputStream *)is; + + delete m; +} + +static bool +input_mms_eof(struct input_stream *is) +{ + MmsInputStream *m = (MmsInputStream *)is; + + return m->eof; +} + +static bool +input_mms_seek(G_GNUC_UNUSED struct input_stream *is, + G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence, + G_GNUC_UNUSED GError **error_r) +{ + return false; +} + +const struct input_plugin input_plugin_mms = { + "mms", + nullptr, + nullptr, + input_mms_open, + input_mms_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_mms_read, + input_mms_eof, + input_mms_seek, +}; diff --git a/src/input/mms_input_plugin.h b/src/input/MmsInputPlugin.hxx index d6aa593f2..d6aa593f2 100644 --- a/src/input/mms_input_plugin.h +++ b/src/input/MmsInputPlugin.hxx diff --git a/src/input/RewindInputPlugin.cxx b/src/input/RewindInputPlugin.cxx new file mode 100644 index 000000000..d93d7d1ce --- /dev/null +++ b/src/input/RewindInputPlugin.cxx @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "RewindInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <stdio.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_rewind" + +extern const struct input_plugin rewind_input_plugin; + +struct RewindInputStream { + struct input_stream base; + + struct input_stream *input; + + /** + * The read position within the buffer. Undefined as long as + * ReadingFromBuffer() returns false. + */ + size_t head; + + /** + * The write/append position within the buffer. + */ + size_t tail; + + /** + * The size of this buffer is the maximum number of bytes + * which can be rewinded cheaply without passing the "seek" + * call to CURL. + * + * The origin of this buffer is always the beginning of the + * stream (offset 0). + */ + char buffer[64 * 1024]; + + RewindInputStream(input_stream *_input) + :base(rewind_input_plugin, _input->uri.c_str(), + _input->mutex, _input->cond), + input(_input), tail(0) { + } + + ~RewindInputStream() { + input_stream_close(input); + } + + /** + * Are we currently reading from the buffer, and does the + * buffer contain more data for the next read operation? + */ + bool ReadingFromBuffer() const { + return tail > 0 && base.offset < input->offset; + } + + /** + * Copy public attributes from the underlying input stream to the + * "rewind" input stream. This function is called when a method of + * the underlying stream has returned, which may have modified these + * attributes. + */ + void CopyAttributes() { + struct input_stream *dest = &base; + const struct input_stream *src = input; + + assert(dest != src); + + bool dest_ready = dest->ready; + + dest->ready = src->ready; + dest->seekable = src->seekable; + dest->size = src->size; + dest->offset = src->offset; + + if (!dest_ready && src->ready) + dest->mime = src->mime; + } +}; + +static void +input_rewind_close(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + delete r; +} + +static bool +input_rewind_check(struct input_stream *is, GError **error_r) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return input_stream_check(r->input, error_r); +} + +static void +input_rewind_update(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + if (!r->ReadingFromBuffer()) + r->CopyAttributes(); +} + +static struct tag * +input_rewind_tag(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return input_stream_tag(r->input); +} + +static bool +input_rewind_available(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return input_stream_available(r->input); +} + +static size_t +input_rewind_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + RewindInputStream *r = (RewindInputStream *)is; + + if (r->ReadingFromBuffer()) { + /* buffered read */ + + assert(r->head == (size_t)is->offset); + assert(r->tail == (size_t)r->input->offset); + + if (size > r->tail - r->head) + size = r->tail - r->head; + + memcpy(ptr, r->buffer + r->head, size); + r->head += size; + is->offset += size; + + return size; + } else { + /* pass method call to underlying stream */ + + size_t nbytes = input_stream_read(r->input, ptr, size, error_r); + + if (r->input->offset > (goffset)sizeof(r->buffer)) + /* disable buffering */ + r->tail = 0; + else if (r->tail == (size_t)is->offset) { + /* append to buffer */ + + memcpy(r->buffer + r->tail, ptr, nbytes); + r->tail += nbytes; + + assert(r->tail == (size_t)r->input->offset); + } + + r->CopyAttributes(); + + return nbytes; + } +} + +static bool +input_rewind_eof(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return !r->ReadingFromBuffer() && input_stream_eof(r->input); +} + +static bool +input_rewind_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) +{ + RewindInputStream *r = (RewindInputStream *)is; + + assert(is->ready); + + if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) { + /* buffered seek */ + + assert(!r->ReadingFromBuffer() || + r->head == (size_t)is->offset); + assert(r->tail == (size_t)r->input->offset); + + r->head = (size_t)offset; + is->offset = offset; + + return true; + } else { + bool success = input_stream_seek(r->input, offset, whence, + error_r); + r->CopyAttributes(); + + /* disable the buffer, because r->input has left the + buffered range now */ + r->tail = 0; + + return success; + } +} + +const struct input_plugin rewind_input_plugin = { + nullptr, + nullptr, + nullptr, + nullptr, + input_rewind_close, + input_rewind_check, + input_rewind_update, + input_rewind_tag, + input_rewind_available, + input_rewind_read, + input_rewind_eof, + input_rewind_seek, +}; + +struct input_stream * +input_rewind_open(struct input_stream *is) +{ + assert(is != NULL); + assert(is->offset == 0); + + if (is->seekable) + /* seekable resources don't need this plugin */ + return is; + + RewindInputStream *c = new RewindInputStream(is); + return &c->base; +} diff --git a/src/input/RewindInputPlugin.hxx b/src/input/RewindInputPlugin.hxx new file mode 100644 index 000000000..cf21e92f1 --- /dev/null +++ b/src/input/RewindInputPlugin.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * A wrapper for an input_stream object which allows cheap buffered + * rewinding. This is useful while detecting the stream codec (let + * each decoder plugin peek a portion from the stream). + */ + +#ifndef MPD_INPUT_REWIND_HXX +#define MPD_INPUT_REWIND_HXX + +#include "check.h" + +struct input_stream; + +struct input_stream * +input_rewind_open(struct input_stream *is); + +#endif diff --git a/src/input/SoupInputPlugin.cxx b/src/input/SoupInputPlugin.cxx new file mode 100644 index 000000000..e9767c20e --- /dev/null +++ b/src/input/SoupInputPlugin.cxx @@ -0,0 +1,492 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.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> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_soup" + +/** + * Do not buffer more than this number of bytes. It should be a + * reasonable limit that doesn't make low-end machines suffer too + * much, but doesn't cause stuttering on high-latency lines. + */ +static const size_t SOUP_MAX_BUFFERED = 512 * 1024; + +/** + * Resume the stream at this number of bytes after it has been paused. + */ +static const size_t SOUP_RESUME_AT = 384 * 1024; + +static SoupURI *soup_proxy; +static SoupSession *soup_session; + +struct SoupInputStream { + struct input_stream base; + + SoupMessage *msg; + + GQueue *buffers; + + size_t current_consumed; + + size_t total_buffered; + + bool alive, pause, eof; + + /** + * Set when the session callback has been invoked, when it is + * safe to free this object. + */ + bool completed; + + GError *postponed_error; + + 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 +soup_quark(void) +{ + return g_quark_from_static_string("soup"); +} + +static bool +input_soup_init(const struct config_param *param, GError **error_r) +{ + assert(soup_proxy == NULL); + assert(soup_session == NULL); + + g_type_init(); + + const char *proxy = config_get_block_string(param, "proxy", NULL); + + if (proxy != NULL) { + soup_proxy = soup_uri_new(proxy); + if (soup_proxy == NULL) { + g_set_error(error_r, soup_quark(), 0, + "failed to parse proxy setting"); + return false; + } + } + + soup_session = + soup_session_async_new_with_options(SOUP_SESSION_PROXY_URI, + soup_proxy, + SOUP_SESSION_ASYNC_CONTEXT, + io_thread_get().GetContext(), + NULL); + + return true; +} + +static void +input_soup_finish(void) +{ + assert(soup_session != NULL); + + soup_session_abort(soup_session); + g_object_unref(G_OBJECT(soup_session)); + + if (soup_proxy != NULL) + soup_uri_free(soup_proxy); +} + +/** + * Copy the error from the SoupMessage object to + * input_soup::postponed_error. + * + * @return true if there was no error + */ +bool +SoupInputStream::CopyError(const SoupMessage *src) +{ + if (SOUP_STATUS_IS_SUCCESSFUL(src->status_code)) + return true; + + if (src->status_code == SOUP_STATUS_CANCELLED) + /* failure, but don't generate a GError, because this + status was caused by _close() */ + return false; + + if (postponed_error != nullptr) + /* there's already a GError, don't overwrite it */ + return false; + + if (SOUP_STATUS_IS_TRANSPORT_ERROR(src->status_code)) + postponed_error = + g_error_new(soup_quark(), src->status_code, + "HTTP client error: %s", + src->reason_phrase); + else + postponed_error = + g_error_new(soup_quark(), src->status_code, + "got HTTP status: %d %s", + src->status_code, src->reason_phrase); + + return false; +} + +static void +input_soup_session_callback(G_GNUC_UNUSED SoupSession *session, + SoupMessage *msg, gpointer user_data) +{ + SoupInputStream *s = (SoupInputStream *)user_data; + + assert(msg == s->msg); + assert(!s->completed); + + const ScopeLock protect(s->base.mutex); + + if (!s->base.ready) + s->CopyError(msg); + + s->base.ready = true; + s->alive = false; + s->completed = true; + + s->base.cond.broadcast(); +} + +static void +input_soup_got_headers(SoupMessage *msg, gpointer user_data) +{ + SoupInputStream *s = (SoupInputStream *)user_data; + + s->base.mutex.lock(); + + if (!s->CopyError(msg)) { + s->base.mutex.unlock(); + + soup_session_cancel_message(soup_session, msg, + SOUP_STATUS_CANCELLED); + return; + } + + s->base.ready = true; + s->base.cond.broadcast(); + s->base.mutex.unlock(); + + soup_message_body_set_accumulate(msg->response_body, false); +} + +static void +input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) +{ + SoupInputStream *s = (SoupInputStream *)user_data; + + assert(msg == s->msg); + + const ScopeLock protect(s->base.mutex); + + g_queue_push_tail(s->buffers, soup_buffer_copy(chunk)); + s->total_buffered += chunk->length; + + if (s->total_buffered >= SOUP_MAX_BUFFERED && !s->pause) { + s->pause = true; + soup_session_pause_message(soup_session, msg); + } + + s->base.cond.broadcast(); + s->base.mutex.unlock(); +} + +static void +input_soup_got_body(G_GNUC_UNUSED SoupMessage *msg, gpointer user_data) +{ + SoupInputStream *s = (SoupInputStream *)user_data; + + assert(msg == s->msg); + + const ScopeLock protect(s->base.mutex); + + s->base.ready = true; + s->eof = true; + s->alive = false; + + s->base.cond.broadcast(); + s->base.mutex.unlock(); +} + +inline bool +SoupInputStream::WaitData() +{ + while (true) { + if (eof) + return true; + + if (!alive) + return false; + + if (!g_queue_is_empty(buffers)) + return true; + + assert(current_consumed == 0); + + base.cond.wait(base.mutex); + } +} + +static gpointer +input_soup_queue(gpointer data) +{ + SoupInputStream *s = (SoupInputStream *)data; + + soup_session_queue_message(soup_session, s->msg, + input_soup_session_callback, s); + + return NULL; +} + +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 GCC_CHECK_VERSION(4,6) +#pragma GCC diagnostic push + /* the libsoup macro SOUP_METHOD_GET discards the "const" + attribute of the g_intern_static_string() return value; + don't make the gcc warning fatal: */ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + + msg = soup_message_new(SOUP_METHOD_GET, uri); + +#if GCC_CHECK_VERSION(4,6) +#pragma GCC diagnostic pop +#endif + + soup_message_set_flags(msg, SOUP_MESSAGE_NO_REDIRECT); + + soup_message_headers_append(msg->request_headers, "User-Agent", + "Music Player Daemon " VERSION); + + 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); + + io_thread_call(input_soup_queue, this); +} + +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) +{ + SoupInputStream *s = (SoupInputStream *)data; + + if (!s->completed) + soup_session_cancel_message(soup_session, s->msg, + SOUP_STATUS_CANCELLED); + + return NULL; +} + +SoupInputStream::~SoupInputStream() +{ + base.mutex.lock(); + + if (!completed) { + /* the messages's session callback hasn't been invoked + yet; cancel it and wait for completion */ + + base.mutex.unlock(); + + io_thread_call(input_soup_cancel, this); + + base.mutex.lock(); + while (!completed) + base.cond.wait(base.mutex); + } + + base.mutex.unlock(); + + SoupBuffer *buffer; + while ((buffer = (SoupBuffer *)g_queue_pop_head(buffers)) != NULL) + soup_buffer_free(buffer); + g_queue_free(buffers); + + if (postponed_error != NULL) + g_error_free(postponed_error); +} + +static void +input_soup_close(struct input_stream *is) +{ + SoupInputStream *s = (SoupInputStream *)is; + + delete s; +} + +static bool +input_soup_check(struct input_stream *is, GError **error_r) +{ + SoupInputStream *s = (SoupInputStream *)is; + + bool success = s->postponed_error == NULL; + if (!success) { + g_propagate_error(error_r, s->postponed_error); + s->postponed_error = NULL; + } + + return success; +} + +static bool +input_soup_available(struct input_stream *is) +{ + SoupInputStream *s = (SoupInputStream *)is; + + return s->eof || !s->alive || !g_queue_is_empty(s->buffers); +} + +inline size_t +SoupInputStream::Read(void *ptr, size_t size, GError **error_r) +{ + if (!WaitData()) { + assert(!alive); + + 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 = (char *)ptr, *p = p0, *p_end = p0 + size; + + while (p < p_end) { + SoupBuffer *buffer = (SoupBuffer *) + g_queue_pop_head(buffers); + if (buffer == NULL) { + assert(current_consumed == 0); + break; + } + + assert(current_consumed < buffer->length); + assert(total_buffered >= buffer->length); + + const char *q = buffer->data; + q += current_consumed; + + size_t remaining = buffer->length - current_consumed; + size_t nbytes = p_end - p; + if (nbytes > remaining) + nbytes = remaining; + + memcpy(p, q, nbytes); + p += nbytes; + + current_consumed += remaining; + if (current_consumed >= buffer->length) { + /* done with this buffer */ + total_buffered -= buffer->length; + soup_buffer_free(buffer); + current_consumed = 0; + } else { + /* partial read */ + assert(p == p_end); + + g_queue_push_head(buffers, buffer); + } + } + + if (pause && total_buffered < SOUP_RESUME_AT) { + pause = false; + soup_session_unpause_message(soup_session, msg); + } + + size_t nbytes = p - p0; + 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) +{ + SoupInputStream *s = (SoupInputStream *)is; + + return !s->alive && g_queue_is_empty(s->buffers); +} + +const struct input_plugin input_plugin_soup = { + "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/SoupInputPlugin.hxx b/src/input/SoupInputPlugin.hxx new file mode 100644 index 000000000..4c089b39b --- /dev/null +++ b/src/input/SoupInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_SOUP_HXX +#define MPD_INPUT_SOUP_HXX + +extern const struct input_plugin input_plugin_soup; + +#endif diff --git a/src/input/archive_input_plugin.c b/src/input/archive_input_plugin.c deleted file mode 100644 index 4a038b9e2..000000000 --- a/src/input/archive_input_plugin.c +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input/archive_input_plugin.h" -#include "archive_api.h" -#include "archive_list.h" -#include "input_plugin.h" - -#include <glib.h> - -/** - * select correct archive plugin to handle the input stream - * may allow stacking of archive plugins. for example for handling - * tar.gz a gzip handler opens file (through inputfile stream) - * then it opens a tar handler and sets gzip inputstream as - * parent_stream so tar plugin fetches file data from gzip - * plugin and gzip fetches file from disk - */ -static struct input_stream * -input_archive_open(const char *pathname, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - const struct archive_plugin *arplug; - struct archive_file *file; - char *archive, *filename, *suffix, *pname; - struct input_stream *is; - - if (!g_path_is_absolute(pathname)) - return NULL; - - pname = g_strdup(pathname); - // archive_lookup will modify pname when true is returned - if (!archive_lookup(pname, &archive, &filename, &suffix)) { - g_debug("not an archive, lookup %s failed\n", pname); - g_free(pname); - return NULL; - } - - //check which archive plugin to use (by ext) - arplug = archive_plugin_from_suffix(suffix); - if (!arplug) { - g_warning("can't handle archive %s\n",archive); - g_free(pname); - return NULL; - } - - file = archive_file_open(arplug, archive, error_r); - if (file == NULL) - return NULL; - - //setup fileops - is = archive_file_open_stream(file, filename, mutex, cond, - error_r); - archive_file_close(file); - g_free(pname); - - return is; -} - -const struct input_plugin input_plugin_archive = { - .name = "archive", - .open = input_archive_open, -}; diff --git a/src/input/archive_input_plugin.h b/src/input/archive_input_plugin.h deleted file mode 100644 index 51095f37f..000000000 --- a/src/input/archive_input_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_ARCHIVE_H -#define MPD_INPUT_ARCHIVE_H - -extern const struct input_plugin input_plugin_archive; - -#endif diff --git a/src/input/cdio_paranoia_input_plugin.c b/src/input/cdio_paranoia_input_plugin.c deleted file mode 100644 index 1de7623a1..000000000 --- a/src/input/cdio_paranoia_input_plugin.c +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * CD-Audio handling (requires libcdio_paranoia) - */ - -#include "config.h" -#include "input/cdio_paranoia_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "refcount.h" - -#include <stdio.h> -#include <stdint.h> -#include <stddef.h> -#include <string.h> -#include <stdlib.h> -#include <glib.h> -#include <assert.h> - -#include <cdio/paranoia.h> -#include <cdio/cd_types.h> - -struct input_cdio_paranoia { - struct input_stream base; - - cdrom_drive_t *drv; - CdIo_t *cdio; - cdrom_paranoia_t *para; - - lsn_t lsn_from, lsn_to; - int lsn_relofs; - - int trackno; - - char buffer[CDIO_CD_FRAMESIZE_RAW]; - int buffer_lsn; -}; - -static inline GQuark -cdio_quark(void) -{ - return g_quark_from_static_string("cdio"); -} - -static void -input_cdio_close(struct input_stream *is) -{ - struct input_cdio_paranoia *i = (struct input_cdio_paranoia *)is; - - if (i->para) - cdio_paranoia_free(i->para); - if (i->drv) - cdio_cddap_close_no_free_cdio( i->drv); - if (i->cdio) - cdio_destroy( i->cdio ); - - input_stream_deinit(&i->base); - g_free(i); -} - -struct cdio_uri { - char device[64]; - int track; -}; - -static bool -parse_cdio_uri(struct cdio_uri *dest, const char *src, GError **error_r) -{ - if (!g_str_has_prefix(src, "cdda://")) - return false; - - src += 7; - - if (*src == 0) { - /* play the whole CD in the default drive */ - dest->device[0] = 0; - dest->track = -1; - return true; - } - - const char *slash = strrchr(src, '/'); - if (slash == NULL) { - /* play the whole CD in the specified drive */ - g_strlcpy(dest->device, src, sizeof(dest->device)); - dest->track = -1; - return true; - } - - size_t device_length = slash - src; - if (device_length >= sizeof(dest->device)) - device_length = sizeof(dest->device) - 1; - - memcpy(dest->device, src, device_length); - dest->device[device_length] = 0; - - const char *track = slash + 1; - - char *endptr; - dest->track = strtoul(track, &endptr, 10); - if (*endptr != 0) { - g_set_error(error_r, cdio_quark(), 0, - "Malformed track number"); - return false; - } - - if (endptr == track) - /* play the whole CD */ - dest->track = -1; - - return true; -} - -static char * -cdio_detect_device(void) -{ - char **devices = cdio_get_devices_with_cap(NULL, CDIO_FS_AUDIO, false); - if (devices == NULL) - return NULL; - - char *device = g_strdup(devices[0]); - cdio_free_device_list(devices); - - return device; -} - -static struct input_stream * -input_cdio_open(const char *uri, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct input_cdio_paranoia *i; - - struct cdio_uri parsed_uri; - if (!parse_cdio_uri(&parsed_uri, uri, error_r)) - return NULL; - - i = g_new(struct input_cdio_paranoia, 1); - input_stream_init(&i->base, &input_plugin_cdio_paranoia, uri, - mutex, cond); - - /* initialize everything (should be already) */ - i->drv = NULL; - i->cdio = NULL; - i->para = NULL; - i->trackno = parsed_uri.track; - - /* get list of CD's supporting CD-DA */ - char *device = parsed_uri.device[0] != 0 - ? g_strdup(parsed_uri.device) - : cdio_detect_device(); - if (device == NULL) { - g_set_error(error_r, cdio_quark(), 0, - "Unable find or access a CD-ROM drive with an audio CD in it."); - input_cdio_close(&i->base); - return NULL; - } - - /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */ - i->cdio = cdio_open(device, DRIVER_UNKNOWN); - g_free(device); - - i->drv = cdio_cddap_identify_cdio(i->cdio, 1, NULL); - - if ( !i->drv ) { - g_set_error(error_r, cdio_quark(), 0, - "Unable to identify audio CD disc."); - input_cdio_close(&i->base); - return NULL; - } - - cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT); - - if ( 0 != cdio_cddap_open(i->drv) ) { - g_set_error(error_r, cdio_quark(), 0, "Unable to open disc."); - input_cdio_close(&i->base); - return NULL; - } - - bool reverse_endian; - switch (data_bigendianp(i->drv)) { - case -1: - g_debug("cdda: drive returns unknown audio data"); - reverse_endian = false; - break; - case 0: - g_debug("cdda: drive returns audio data Little Endian."); - reverse_endian = G_BYTE_ORDER == G_BIG_ENDIAN; - break; - case 1: - g_debug("cdda: drive returns audio data Big Endian."); - reverse_endian = G_BYTE_ORDER == G_LITTLE_ENDIAN; - break; - default: - g_set_error(error_r, cdio_quark(), 0, - "Drive returns unknown data type %d", - data_bigendianp(i->drv)); - input_cdio_close(&i->base); - return NULL; - } - - i->lsn_relofs = 0; - - if (i->trackno >= 0) { - i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno); - i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno); - } else { - i->lsn_from = 0; - i->lsn_to = cdio_get_disc_last_lsn(i->cdio); - } - - i->para = cdio_paranoia_init(i->drv); - - /* Set reading mode for full paranoia, but allow skipping sectors. */ - paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP); - - /* seek to beginning of the track */ - cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET); - - i->base.ready = true; - i->base.seekable = true; - i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW; - - /* hack to make MPD select the "pcm" decoder plugin */ - i->base.mime = g_strdup(reverse_endian - ? "audio/x-mpd-cdda-pcm-reverse" - : "audio/x-mpd-cdda-pcm"); - - return &i->base; -} - -static bool -input_cdio_seek(struct input_stream *is, - goffset offset, int whence, GError **error_r) -{ - struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; - - /* calculate absolute offset */ - switch (whence) { - case SEEK_SET: - break; - case SEEK_CUR: - offset += cis->base.offset; - break; - case SEEK_END: - offset += cis->base.size; - break; - } - - if (offset < 0 || offset > cis->base.size) { - g_set_error(error_r, cdio_quark(), 0, - "Invalid offset to seek %ld (%ld)", - (long int)offset, (long int)cis->base.size); - return false; - } - - /* simple case */ - if (offset == cis->base.offset) - return true; - - /* calculate current LSN */ - cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW; - cis->base.offset = offset; - - cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET); - - return true; -} - -static size_t -input_cdio_read(struct input_stream *is, void *ptr, size_t length, - GError **error_r) -{ - struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; - size_t nbytes = 0; - int diff; - size_t len, maxwrite; - int16_t *rbuf; - char *s_err, *s_mess; - char *wptr = (char *) ptr; - - while (length > 0) { - - - /* end of track ? */ - if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to) - break; - - //current sector was changed ? - if (cis->lsn_relofs != cis->buffer_lsn) { - rbuf = cdio_paranoia_read(cis->para, NULL); - - s_err = cdda_errors(cis->drv); - if (s_err) { - g_warning("paranoia_read: %s", s_err ); - free(s_err); - } - s_mess = cdda_messages(cis->drv); - if (s_mess) { - free(s_mess); - } - if (!rbuf) { - g_set_error(error_r, cdio_quark(), 0, - "paranoia read error. Stopping."); - return 0; - } - //store current buffer - memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW); - cis->buffer_lsn = cis->lsn_relofs; - } else { - //use cached sector - rbuf = (int16_t*) cis->buffer; - } - - //correct offset - diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW; - - assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW); - - maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer - len = (length < maxwrite? length : maxwrite); - - //skip diff bytes from this lsn - memcpy(wptr, ((char*)rbuf) + diff, len); - //update pointer - wptr += len; - nbytes += len; - - //update offset - cis->base.offset += len; - cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW; - //update length - length -= len; - } - - return nbytes; -} - -static bool -input_cdio_eof(struct input_stream *is) -{ - struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; - - return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to); -} - -const struct input_plugin input_plugin_cdio_paranoia = { - .name = "cdio_paranoia", - .open = input_cdio_open, - .close = input_cdio_close, - .seek = input_cdio_seek, - .read = input_cdio_read, - .eof = input_cdio_eof -}; diff --git a/src/input/cdio_paranoia_input_plugin.h b/src/input/cdio_paranoia_input_plugin.h deleted file mode 100644 index 71c5cbe8d..000000000 --- a/src/input/cdio_paranoia_input_plugin.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CDIO_PARANOIA_INPUT_PLUGIN_H -#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_H - -/** - * An input plugin based on libcdio_paranoia library. - */ -extern const struct input_plugin input_plugin_cdio_paranoia; - -#endif diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c deleted file mode 100644 index 3f191141e..000000000 --- a/src/input/curl_input_plugin.c +++ /dev/null @@ -1,1301 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input/curl_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "conf.h" -#include "tag.h" -#include "icy_metadata.h" -#include "io_thread.h" -#include "glib_compat.h" - -#include <assert.h> - -#if defined(WIN32) - #include <winsock2.h> -#else - #include <sys/select.h> -#endif - -#include <string.h> -#include <errno.h> - -#include <curl/curl.h> -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_curl" - -/** - * Do not buffer more than this number of bytes. It should be a - * reasonable limit that doesn't make low-end machines suffer too - * much, but doesn't cause stuttering on high-latency lines. - */ -static const size_t CURL_MAX_BUFFERED = 512 * 1024; - -/** - * Resume the stream at this number of bytes after it has been paused. - */ -static const size_t CURL_RESUME_AT = 384 * 1024; - -/** - * Buffers created by input_curl_writefunction(). - */ -struct buffer { - /** size of the payload */ - size_t size; - - /** how much has been consumed yet? */ - size_t consumed; - - /** the payload */ - unsigned char data[sizeof(long)]; -}; - -struct input_curl { - struct input_stream base; - - /* some buffers which were passed to libcurl, which we have - too free */ - char *url, *range; - struct curl_slist *request_headers; - - /** the curl handles */ - CURL *easy; - - /** the GMainLoop source used to poll all CURL file - descriptors */ - GSource *source; - - /** the source id of #source */ - guint source_id; - - /** a linked list of all registered GPollFD objects */ - GSList *fds; - - /** list of buffers, where input_curl_writefunction() appends - to, and input_curl_read() reads from them */ - GQueue *buffers; - -#if LIBCURL_VERSION_NUM >= 0x071200 - /** - * Is the connection currently paused? That happens when the - * buffer was getting too large. It will be unpaused when the - * buffer is below the threshold again. - */ - bool paused; -#endif - - /** error message provided by libcurl */ - char error[CURL_ERROR_SIZE]; - - /** parser for icy-metadata */ - struct icy_metadata icy_metadata; - - /** the stream name from the icy-name response header */ - char *meta_name; - - /** the tag object ready to be requested via - input_stream_tag() */ - struct tag *tag; - - GError *postponed_error; -}; - -/** libcurl should accept "ICY 200 OK" */ -static struct curl_slist *http_200_aliases; - -/** HTTP proxy settings */ -static const char *proxy, *proxy_user, *proxy_password; -static unsigned proxy_port; - -static struct { - CURLM *multi; - - /** - * A linked list of all active HTTP requests. An active - * request is one that doesn't have the "eof" flag set. - */ - GSList *requests; - - /** - * The GMainLoop source used to poll all CURL file - * descriptors. - */ - GSource *source; - - /** - * The source id of #source. - */ - guint source_id; - - GSList *fds; - -#if LIBCURL_VERSION_NUM >= 0x070f04 - /** - * Did CURL give us a timeout? If yes, then we need to call - * curl_multi_perform(), even if there was no event on any - * file descriptor. - */ - bool timeout; - - /** - * The absolute time stamp when the timeout expires. This is - * used in the GSource method check(). - */ - gint64 absolute_timeout; -#endif -} curl; - -static inline GQuark -curl_quark(void) -{ - return g_quark_from_static_string("curl"); -} - -/** - * Find a request by its CURL "easy" handle. - * - * Runs in the I/O thread. No lock needed. - */ -static struct input_curl * -input_curl_find_request(CURL *easy) -{ - assert(io_thread_inside()); - - for (GSList *i = curl.requests; i != NULL; i = g_slist_next(i)) { - struct input_curl *c = i->data; - if (c->easy == easy) - return c; - } - - return NULL; -} - -#if LIBCURL_VERSION_NUM >= 0x071200 - -static gpointer -input_curl_resume(gpointer data) -{ - assert(io_thread_inside()); - - struct input_curl *c = data; - - if (c->paused) { - c->paused = false; - curl_easy_pause(c->easy, CURLPAUSE_CONT); - } - - return NULL; -} - -#endif - -/** - * Calculates the GLib event bit mask for one file descriptor, - * obtained from three #fd_set objects filled by curl_multi_fdset(). - */ -static gushort -input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds) -{ - gushort events = 0; - - if (FD_ISSET(fd, rfds)) { - events |= G_IO_IN | G_IO_HUP | G_IO_ERR; - FD_CLR(fd, rfds); - } - - if (FD_ISSET(fd, wfds)) { - events |= G_IO_OUT | G_IO_ERR; - FD_CLR(fd, wfds); - } - - if (FD_ISSET(fd, efds)) { - events |= G_IO_HUP | G_IO_ERR; - FD_CLR(fd, efds); - } - - return events; -} - -/** - * Updates all registered GPollFD objects, unregisters old ones, - * registers new ones. - * - * Runs in the I/O thread. No lock needed. - */ -static void -curl_update_fds(void) -{ - assert(io_thread_inside()); - - fd_set rfds, wfds, efds; - - FD_ZERO(&rfds); - FD_ZERO(&wfds); - FD_ZERO(&efds); - - int max_fd; - CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds, - &efds, &max_fd); - if (mcode != CURLM_OK) { - g_warning("curl_multi_fdset() failed: %s\n", - curl_multi_strerror(mcode)); - return; - } - - GSList *fds = curl.fds; - curl.fds = NULL; - - while (fds != NULL) { - GPollFD *poll_fd = fds->data; - gushort events = input_curl_fd_events(poll_fd->fd, &rfds, - &wfds, &efds); - - assert(poll_fd->events != 0); - - fds = g_slist_remove(fds, poll_fd); - - if (events != poll_fd->events) - g_source_remove_poll(curl.source, poll_fd); - - if (events != 0) { - if (events != poll_fd->events) { - poll_fd->events = events; - g_source_add_poll(curl.source, poll_fd); - } - - curl.fds = g_slist_prepend(curl.fds, poll_fd); - } else { - g_free(poll_fd); - } - } - - for (int fd = 0; fd <= max_fd; ++fd) { - gushort events = input_curl_fd_events(fd, &rfds, &wfds, &efds); - if (events != 0) { - GPollFD *poll_fd = g_new(GPollFD, 1); - poll_fd->fd = fd; - poll_fd->events = events; - g_source_add_poll(curl.source, poll_fd); - curl.fds = g_slist_prepend(curl.fds, poll_fd); - } - } -} - -/** - * Runs in the I/O thread. No lock needed. - */ -static bool -input_curl_easy_add(struct input_curl *c, GError **error_r) -{ - assert(io_thread_inside()); - assert(c != NULL); - assert(c->easy != NULL); - assert(input_curl_find_request(c->easy) == NULL); - - curl.requests = g_slist_prepend(curl.requests, c); - - CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy); - if (mcode != CURLM_OK) { - g_set_error(error_r, curl_quark(), mcode, - "curl_multi_add_handle() failed: %s", - curl_multi_strerror(mcode)); - return false; - } - - curl_update_fds(); - - return true; -} - -struct easy_add_params { - struct input_curl *c; - GError **error_r; -}; - -static gpointer -input_curl_easy_add_callback(gpointer data) -{ - const struct easy_add_params *params = data; - - bool success = input_curl_easy_add(params->c, params->error_r); - return GUINT_TO_POINTER(success); -} - -/** - * Call input_curl_easy_add() in the I/O thread. May be called from - * any thread. Caller must not hold a mutex. - */ -static bool -input_curl_easy_add_indirect(struct input_curl *c, GError **error_r) -{ - assert(c != NULL); - assert(c->easy != NULL); - - struct easy_add_params params = { - .c = c, - .error_r = error_r, - }; - - gpointer result = - io_thread_call(input_curl_easy_add_callback, ¶ms); - return GPOINTER_TO_UINT(result); -} - -/** - * Frees the current "libcurl easy" handle, and everything associated - * with it. - * - * Runs in the I/O thread. - */ -static void -input_curl_easy_free(struct input_curl *c) -{ - assert(io_thread_inside()); - assert(c != NULL); - - if (c->easy == NULL) - return; - - curl.requests = g_slist_remove(curl.requests, c); - - curl_multi_remove_handle(curl.multi, c->easy); - curl_easy_cleanup(c->easy); - c->easy = NULL; - - curl_slist_free_all(c->request_headers); - c->request_headers = NULL; - - g_free(c->range); - c->range = NULL; -} - -static gpointer -input_curl_easy_free_callback(gpointer data) -{ - struct input_curl *c = data; - - input_curl_easy_free(c); - curl_update_fds(); - - return NULL; -} - -/** - * Frees the current "libcurl easy" handle, and everything associated - * with it. - * - * The mutex must not be locked. - */ -static void -input_curl_easy_free_indirect(struct input_curl *c) -{ - io_thread_call(input_curl_easy_free_callback, c); - assert(c->easy == NULL); -} - -/** - * Abort and free all HTTP requests. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -static void -input_curl_abort_all_requests(GError *error) -{ - assert(io_thread_inside()); - assert(error != NULL); - - while (curl.requests != NULL) { - struct input_curl *c = curl.requests->data; - assert(c->postponed_error == NULL); - - input_curl_easy_free(c); - - g_mutex_lock(c->base.mutex); - c->postponed_error = g_error_copy(error); - c->base.ready = true; - g_cond_broadcast(c->base.cond); - g_mutex_unlock(c->base.mutex); - } - - g_error_free(error); - -} - -/** - * A HTTP request is finished. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -static void -input_curl_request_done(struct input_curl *c, CURLcode result, long status) -{ - assert(io_thread_inside()); - assert(c != NULL); - assert(c->easy == NULL); - assert(c->postponed_error == NULL); - - g_mutex_lock(c->base.mutex); - - if (result != CURLE_OK) { - c->postponed_error = g_error_new(curl_quark(), result, - "curl failed: %s", - c->error); - } else if (status < 200 || status >= 300) { - c->postponed_error = g_error_new(curl_quark(), 0, - "got HTTP status %ld", - status); - } - - c->base.ready = true; - g_cond_broadcast(c->base.cond); - g_mutex_unlock(c->base.mutex); -} - -static void -input_curl_handle_done(CURL *easy_handle, CURLcode result) -{ - struct input_curl *c = input_curl_find_request(easy_handle); - assert(c != NULL); - - long status = 0; - curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status); - - input_curl_easy_free(c); - input_curl_request_done(c, result, status); -} - -/** - * Check for finished HTTP responses. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -static void -input_curl_info_read(void) -{ - assert(io_thread_inside()); - - CURLMsg *msg; - int msgs_in_queue; - - while ((msg = curl_multi_info_read(curl.multi, - &msgs_in_queue)) != NULL) { - if (msg->msg == CURLMSG_DONE) - input_curl_handle_done(msg->easy_handle, msg->data.result); - } -} - -/** - * Give control to CURL. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -static bool -input_curl_perform(void) -{ - assert(io_thread_inside()); - - CURLMcode mcode; - - do { - int running_handles; - mcode = curl_multi_perform(curl.multi, &running_handles); - } while (mcode == CURLM_CALL_MULTI_PERFORM); - - if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { - GError *error = g_error_new(curl_quark(), mcode, - "curl_multi_perform() failed: %s", - curl_multi_strerror(mcode)); - input_curl_abort_all_requests(error); - return false; - } - - return true; -} - -/* - * GSource methods - * - */ - -/** - * The GSource prepare() method implementation. - */ -static gboolean -input_curl_source_prepare(G_GNUC_UNUSED GSource *source, gint *timeout_r) -{ - curl_update_fds(); - -#if LIBCURL_VERSION_NUM >= 0x070f04 - curl.timeout = false; - - long timeout2; - CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2); - if (mcode == CURLM_OK) { - if (timeout2 >= 0) - curl.absolute_timeout = g_source_get_time(source) - + timeout2 * 1000; - - if (timeout2 >= 0 && timeout2 < 10) - /* CURL 7.21.1 likes to report "timeout=0", - which means we're running in a busy loop. - Quite a bad idea to waste so much CPU. - Let's use a lower limit of 10ms. */ - timeout2 = 10; - - *timeout_r = timeout2; - - curl.timeout = timeout2 >= 0; - } else - g_warning("curl_multi_timeout() failed: %s\n", - curl_multi_strerror(mcode)); -#else - (void)timeout_r; -#endif - - return false; -} - -/** - * The GSource check() method implementation. - */ -static gboolean -input_curl_source_check(G_GNUC_UNUSED GSource *source) -{ -#if LIBCURL_VERSION_NUM >= 0x070f04 - if (curl.timeout) { - /* when a timeout has expired, we need to call - curl_multi_perform(), even if there was no file - descriptor event */ - - if (g_source_get_time(source) >= curl.absolute_timeout) - return true; - } -#endif - - for (GSList *i = curl.fds; i != NULL; i = i->next) { - GPollFD *poll_fd = i->data; - if (poll_fd->revents != 0) - return true; - } - - return false; -} - -/** - * The GSource dispatch() method implementation. The callback isn't - * used, because we're handling all events directly. - */ -static gboolean -input_curl_source_dispatch(G_GNUC_UNUSED GSource *source, - G_GNUC_UNUSED GSourceFunc callback, - G_GNUC_UNUSED gpointer user_data) -{ - if (input_curl_perform()) - input_curl_info_read(); - - return true; -} - -/** - * The vtable for our GSource implementation. Unfortunately, we - * cannot declare it "const", because g_source_new() takes a non-const - * pointer, for whatever reason. - */ -static GSourceFuncs curl_source_funcs = { - .prepare = input_curl_source_prepare, - .check = input_curl_source_check, - .dispatch = input_curl_source_dispatch, -}; - -/* - * input_plugin methods - * - */ - -static bool -input_curl_init(const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - CURLcode code = curl_global_init(CURL_GLOBAL_ALL); - if (code != CURLE_OK) { - g_set_error(error_r, curl_quark(), code, - "curl_global_init() failed: %s\n", - curl_easy_strerror(code)); - return false; - } - - http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK"); - - proxy = config_get_block_string(param, "proxy", NULL); - proxy_port = config_get_block_unsigned(param, "proxy_port", 0); - proxy_user = config_get_block_string(param, "proxy_user", NULL); - proxy_password = config_get_block_string(param, "proxy_password", - NULL); - - if (proxy == NULL) { - /* deprecated proxy configuration */ - proxy = config_get_string(CONF_HTTP_PROXY_HOST, NULL); - proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0); - proxy_user = config_get_string(CONF_HTTP_PROXY_USER, NULL); - proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD, - ""); - } - - curl.multi = curl_multi_init(); - if (curl.multi == NULL) { - g_set_error(error_r, curl_quark(), 0, - "curl_multi_init() failed"); - return false; - } - - curl.source = g_source_new(&curl_source_funcs, sizeof(*curl.source)); - curl.source_id = g_source_attach(curl.source, io_thread_context()); - - return true; -} - -static gpointer -curl_destroy_sources(G_GNUC_UNUSED gpointer data) -{ - g_source_destroy(curl.source); - - return NULL; -} - -static void -input_curl_finish(void) -{ - assert(curl.requests == NULL); - - io_thread_call(curl_destroy_sources, NULL); - - curl_multi_cleanup(curl.multi); - - curl_slist_free_all(http_200_aliases); - - curl_global_cleanup(); -} - -#if LIBCURL_VERSION_NUM >= 0x071200 - -/** - * Determine the total sizes of all buffers, including portions that - * have already been consumed. - * - * The caller must lock the mutex. - */ -G_GNUC_PURE -static size_t -curl_total_buffer_size(const struct input_curl *c) -{ - size_t total = 0; - - for (GList *i = g_queue_peek_head_link(c->buffers); - i != NULL; i = g_list_next(i)) { - struct buffer *buffer = i->data; - total += buffer->size; - } - - return total; -} - -#endif - -static void -buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct buffer *buffer = data; - - assert(buffer->consumed <= buffer->size); - - g_free(buffer); -} - -static void -input_curl_flush_buffers(struct input_curl *c) -{ - g_queue_foreach(c->buffers, buffer_free_callback, NULL); - g_queue_clear(c->buffers); -} - -/** - * Frees this stream, including the input_stream struct. - */ -static void -input_curl_free(struct input_curl *c) -{ - if (c->tag != NULL) - tag_free(c->tag); - g_free(c->meta_name); - - input_curl_easy_free_indirect(c); - input_curl_flush_buffers(c); - - g_queue_free(c->buffers); - - if (c->postponed_error != NULL) - g_error_free(c->postponed_error); - - g_free(c->url); - input_stream_deinit(&c->base); - g_free(c); -} - -static bool -input_curl_check(struct input_stream *is, GError **error_r) -{ - struct input_curl *c = (struct input_curl *)is; - - bool success = c->postponed_error == NULL; - if (!success) { - g_propagate_error(error_r, c->postponed_error); - c->postponed_error = NULL; - } - - return success; -} - -static struct tag * -input_curl_tag(struct input_stream *is) -{ - struct input_curl *c = (struct input_curl *)is; - struct tag *tag = c->tag; - - c->tag = NULL; - return tag; -} - -static bool -fill_buffer(struct input_curl *c, GError **error_r) -{ - while (c->easy != NULL && g_queue_is_empty(c->buffers)) - g_cond_wait(c->base.cond, c->base.mutex); - - if (c->postponed_error != NULL) { - g_propagate_error(error_r, c->postponed_error); - c->postponed_error = NULL; - return false; - } - - return !g_queue_is_empty(c->buffers); -} - -/** - * Mark a part of the buffer object as consumed. - */ -static struct buffer * -consume_buffer(struct buffer *buffer, size_t length) -{ - assert(buffer != NULL); - assert(buffer->consumed < buffer->size); - - buffer->consumed += length; - if (buffer->consumed < buffer->size) - return buffer; - - assert(buffer->consumed == buffer->size); - - g_free(buffer); - - return NULL; -} - -static size_t -read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers, - void *dest0, size_t length) -{ - struct buffer *buffer = g_queue_pop_head(buffers); - uint8_t *dest = dest0; - size_t nbytes = 0; - - assert(buffer->size > 0); - assert(buffer->consumed < buffer->size); - - if (length > buffer->size - buffer->consumed) - length = buffer->size - buffer->consumed; - - while (true) { - size_t chunk; - - chunk = icy_data(icy_metadata, length); - if (chunk > 0) { - memcpy(dest, buffer->data + buffer->consumed, - chunk); - buffer = consume_buffer(buffer, chunk); - - nbytes += chunk; - dest += chunk; - length -= chunk; - - if (length == 0) - break; - - assert(buffer != NULL); - } - - chunk = icy_meta(icy_metadata, buffer->data + buffer->consumed, - length); - if (chunk > 0) { - buffer = consume_buffer(buffer, chunk); - - length -= chunk; - - if (length == 0) - break; - - assert(buffer != NULL); - } - } - - if (buffer != NULL) - g_queue_push_head(buffers, buffer); - - return nbytes; -} - -static void -copy_icy_tag(struct input_curl *c) -{ - struct tag *tag = icy_tag(&c->icy_metadata); - - if (tag == NULL) - return; - - if (c->tag != NULL) - tag_free(c->tag); - - if (c->meta_name != NULL && !tag_has_type(tag, TAG_NAME)) - tag_add_item(tag, TAG_NAME, c->meta_name); - - c->tag = tag; -} - -static bool -input_curl_available(struct input_stream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - return c->postponed_error != NULL || c->easy == NULL || - !g_queue_is_empty(c->buffers); -} - -static size_t -input_curl_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct input_curl *c = (struct input_curl *)is; - bool success; - size_t nbytes = 0; - char *dest = ptr; - - do { - /* fill the buffer */ - - success = fill_buffer(c, error_r); - if (!success) - return 0; - - /* send buffer contents */ - - while (size > 0 && !g_queue_is_empty(c->buffers)) { - size_t copy = read_from_buffer(&c->icy_metadata, c->buffers, - dest + nbytes, size); - - nbytes += copy; - size -= copy; - } - } while (nbytes == 0); - - if (icy_defined(&c->icy_metadata)) - copy_icy_tag(c); - - is->offset += (goffset)nbytes; - -#if LIBCURL_VERSION_NUM >= 0x071200 - if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) { - g_mutex_unlock(c->base.mutex); - io_thread_call(input_curl_resume, c); - g_mutex_lock(c->base.mutex); - } -#endif - - return nbytes; -} - -static void -input_curl_close(struct input_stream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - input_curl_free(c); -} - -static bool -input_curl_eof(G_GNUC_UNUSED struct input_stream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - return c->easy == NULL && g_queue_is_empty(c->buffers); -} - -/** called by curl when new data is available */ -static size_t -input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) -{ - struct input_curl *c = (struct input_curl *)stream; - const char *header = ptr, *end, *value; - char name[64]; - - size *= nmemb; - end = header + size; - - value = memchr(header, ':', size); - if (value == NULL || (size_t)(value - header) >= sizeof(name)) - return size; - - memcpy(name, header, value - header); - name[value - header] = 0; - - /* skip the colon */ - - ++value; - - /* strip the value */ - - while (value < end && g_ascii_isspace(*value)) - ++value; - - while (end > value && g_ascii_isspace(end[-1])) - --end; - - if (g_ascii_strcasecmp(name, "accept-ranges") == 0) { - /* a stream with icy-metadata is not seekable */ - if (!icy_defined(&c->icy_metadata)) - c->base.seekable = true; - } else if (g_ascii_strcasecmp(name, "content-length") == 0) { - char buffer[64]; - - if ((size_t)(end - header) >= sizeof(buffer)) - return size; - - memcpy(buffer, value, end - value); - buffer[end - value] = 0; - - c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10); - } else if (g_ascii_strcasecmp(name, "content-type") == 0) { - g_free(c->base.mime); - c->base.mime = g_strndup(value, end - value); - } else if (g_ascii_strcasecmp(name, "icy-name") == 0 || - g_ascii_strcasecmp(name, "ice-name") == 0 || - g_ascii_strcasecmp(name, "x-audiocast-name") == 0) { - g_free(c->meta_name); - c->meta_name = g_strndup(value, end - value); - - if (c->tag != NULL) - tag_free(c->tag); - - c->tag = tag_new(); - tag_add_item(c->tag, TAG_NAME, c->meta_name); - } else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) { - char buffer[64]; - size_t icy_metaint; - - if ((size_t)(end - header) >= sizeof(buffer) || - icy_defined(&c->icy_metadata)) - return size; - - memcpy(buffer, value, end - value); - buffer[end - value] = 0; - - icy_metaint = g_ascii_strtoull(buffer, NULL, 10); - g_debug("icy-metaint=%zu", icy_metaint); - - if (icy_metaint > 0) { - icy_start(&c->icy_metadata, icy_metaint); - - /* a stream with icy-metadata is not - seekable */ - c->base.seekable = false; - } - } - - return size; -} - -/** called by curl when new data is available */ -static size_t -input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) -{ - struct input_curl *c = (struct input_curl *)stream; - struct buffer *buffer; - - size *= nmemb; - if (size == 0) - return 0; - - g_mutex_lock(c->base.mutex); - -#if LIBCURL_VERSION_NUM >= 0x071200 - if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) { - c->paused = true; - g_mutex_unlock(c->base.mutex); - return CURL_WRITEFUNC_PAUSE; - } -#endif - - buffer = g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size); - buffer->size = size; - buffer->consumed = 0; - memcpy(buffer->data, ptr, size); - - g_queue_push_tail(c->buffers, buffer); - c->base.ready = true; - - g_cond_broadcast(c->base.cond); - g_mutex_unlock(c->base.mutex); - - return size; -} - -static bool -input_curl_easy_init(struct input_curl *c, GError **error_r) -{ - CURLcode code; - - c->easy = curl_easy_init(); - if (c->easy == NULL) { - g_set_error(error_r, curl_quark(), 0, - "curl_easy_init() failed"); - return false; - } - - curl_easy_setopt(c->easy, CURLOPT_USERAGENT, - "Music Player Daemon " VERSION); - curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION, - input_curl_headerfunction); - curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c); - curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION, - input_curl_writefunction); - curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c); - curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases); - curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(c->easy, CURLOPT_NETRC, 1); - curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5); - curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true); - curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error); - curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l); - curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l); - curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l); - - if (proxy != NULL) - curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy); - - if (proxy_port > 0) - curl_easy_setopt(c->easy, CURLOPT_PROXYPORT, (long)proxy_port); - - if (proxy_user != NULL && proxy_password != NULL) { - char *proxy_auth_str = - g_strconcat(proxy_user, ":", proxy_password, NULL); - curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str); - g_free(proxy_auth_str); - } - - code = curl_easy_setopt(c->easy, CURLOPT_URL, c->url); - if (code != CURLE_OK) { - g_set_error(error_r, curl_quark(), code, - "curl_easy_setopt() failed: %s", - curl_easy_strerror(code)); - return false; - } - - c->request_headers = NULL; - c->request_headers = curl_slist_append(c->request_headers, - "Icy-Metadata: 1"); - curl_easy_setopt(c->easy, CURLOPT_HTTPHEADER, c->request_headers); - - return true; -} - -static bool -input_curl_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r) -{ - struct input_curl *c = (struct input_curl *)is; - bool ret; - - assert(is->ready); - - if (whence == SEEK_SET && offset == is->offset) - /* no-op */ - return true; - - if (!is->seekable) - return false; - - /* calculate the absolute offset */ - - switch (whence) { - case SEEK_SET: - break; - - case SEEK_CUR: - offset += is->offset; - break; - - case SEEK_END: - if (is->size < 0) - /* stream size is not known */ - return false; - - offset += is->size; - break; - - default: - return false; - } - - if (offset < 0) - return false; - - /* check if we can fast-forward the buffer */ - - while (offset > is->offset && !g_queue_is_empty(c->buffers)) { - struct buffer *buffer; - size_t length; - - buffer = (struct buffer *)g_queue_pop_head(c->buffers); - - length = buffer->size - buffer->consumed; - if (offset - is->offset < (goffset)length) - length = offset - is->offset; - - buffer = consume_buffer(buffer, length); - if (buffer != NULL) - g_queue_push_head(c->buffers, buffer); - - is->offset += length; - } - - if (offset == is->offset) - return true; - - /* close the old connection and open a new one */ - - g_mutex_unlock(c->base.mutex); - - input_curl_easy_free_indirect(c); - input_curl_flush_buffers(c); - - is->offset = offset; - if (is->offset == is->size) { - /* seek to EOF: simulate empty result; avoid - triggering a "416 Requested Range Not Satisfiable" - response */ - return true; - } - - ret = input_curl_easy_init(c, error_r); - if (!ret) - return false; - - /* send the "Range" header */ - - if (is->offset > 0) { - c->range = g_strdup_printf("%lld-", (long long)is->offset); - curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range); - } - - c->base.ready = false; - - if (!input_curl_easy_add_indirect(c, error_r)) - return false; - - g_mutex_lock(c->base.mutex); - - while (!c->base.ready) - g_cond_wait(c->base.cond, c->base.mutex); - - if (c->postponed_error != NULL) { - g_propagate_error(error_r, c->postponed_error); - c->postponed_error = NULL; - return false; - } - - return true; -} - -static struct input_stream * -input_curl_open(const char *url, GMutex *mutex, GCond *cond, - GError **error_r) -{ - assert(mutex != NULL); - assert(cond != NULL); - - struct input_curl *c; - - if (strncmp(url, "http://", 7) != 0) - return NULL; - - c = g_new0(struct input_curl, 1); - input_stream_init(&c->base, &input_plugin_curl, url, - mutex, cond); - - c->url = g_strdup(url); - c->buffers = g_queue_new(); - - icy_clear(&c->icy_metadata); - c->tag = NULL; - - c->postponed_error = NULL; - -#if LIBCURL_VERSION_NUM >= 0x071200 - c->paused = false; -#endif - - if (!input_curl_easy_init(c, error_r)) { - input_curl_free(c); - return NULL; - } - - if (!input_curl_easy_add_indirect(c, error_r)) { - input_curl_free(c); - return NULL; - } - - return &c->base; -} - -const struct input_plugin input_plugin_curl = { - .name = "curl", - .init = input_curl_init, - .finish = input_curl_finish, - - .open = input_curl_open, - .close = input_curl_close, - .check = input_curl_check, - .tag = input_curl_tag, - .available = input_curl_available, - .read = input_curl_read, - .eof = input_curl_eof, - .seek = input_curl_seek, -}; diff --git a/src/input/curl_input_plugin.h b/src/input/curl_input_plugin.h deleted file mode 100644 index c6e71bf40..000000000 --- a/src/input/curl_input_plugin.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_CURL_H -#define MPD_INPUT_CURL_H - -struct input_stream; - -extern const struct input_plugin input_plugin_curl; - -#endif diff --git a/src/input/despotify_input_plugin.c b/src/input/despotify_input_plugin.c deleted file mode 100644 index 200a0afd6..000000000 --- a/src/input/despotify_input_plugin.c +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (C) 2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input/despotify_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "tag.h" -#include "despotify_utils.h" - -#include <glib.h> - -#include <unistd.h> -#include <string.h> -#include <errno.h> -#include <despotify.h> - -#include <stdio.h> - -struct input_despotify { - struct input_stream base; - - struct despotify_session *session; - struct ds_track *track; - struct tag *tag; - struct ds_pcm_data pcm; - size_t len_available; - bool eof; -}; - - -static void -refill_buffer(struct input_despotify *ctx) -{ - /* Wait until there is data */ - while (1) { - int rc = despotify_get_pcm(ctx->session, &ctx->pcm); - - if (rc == 0 && ctx->pcm.len) { - ctx->len_available = ctx->pcm.len; - break; - } - if (ctx->eof == true) - break; - - if (rc < 0) { - g_debug("despotify_get_pcm error\n"); - ctx->eof = true; - break; - } - - /* Wait a while until next iteration */ - usleep(50 * 1000); - } -} - -static void callback(G_GNUC_UNUSED struct despotify_session* ds, - int sig, G_GNUC_UNUSED void* data, void* callback_data) -{ - struct input_despotify *ctx = (struct input_despotify *)callback_data; - - switch (sig) { - case DESPOTIFY_NEW_TRACK: - break; - - case DESPOTIFY_TIME_TELL: - break; - - case DESPOTIFY_TRACK_PLAY_ERROR: - g_debug("Track play error\n"); - ctx->eof = true; - ctx->len_available = 0; - break; - - case DESPOTIFY_END_OF_PLAYLIST: - ctx->eof = true; - g_debug("End of playlist: %d\n", ctx->eof); - break; - } -} - - -static struct input_stream * -input_despotify_open(const char *url, - GMutex *mutex, GCond *cond, - G_GNUC_UNUSED GError **error_r) -{ - struct input_despotify *ctx; - struct despotify_session *session; - struct ds_link *ds_link; - struct ds_track *track; - - if (!g_str_has_prefix(url, "spt://")) - return NULL; - - session = mpd_despotify_get_session(); - if (!session) - return NULL; - - ds_link = despotify_link_from_uri(url + 6); - if (!ds_link) { - g_debug("Can't find %s\n", url); - return NULL; - } - if (ds_link->type != LINK_TYPE_TRACK) { - despotify_free_link(ds_link); - return NULL; - } - - ctx = g_new(struct input_despotify, 1); - memset(ctx, 0, sizeof(*ctx)); - - track = despotify_link_get_track(session, ds_link); - despotify_free_link(ds_link); - if (!track) { - g_free(ctx); - return NULL; - } - - input_stream_init(&ctx->base, &input_plugin_despotify, url, - mutex, cond); - ctx->session = session; - ctx->track = track; - ctx->tag = mpd_despotify_tag_from_track(track); - ctx->eof = false; - /* Despotify outputs pcm data */ - ctx->base.mime = g_strdup("audio/x-mpd-cdda-pcm"); - ctx->base.ready = true; - - if (!mpd_despotify_register_callback(callback, ctx)) { - despotify_free_link(ds_link); - - return NULL; - } - - if (despotify_play(ctx->session, ctx->track, false) == false) { - despotify_free_track(ctx->track); - g_free(ctx); - return NULL; - } - - return &ctx->base; -} - -static size_t -input_despotify_read(struct input_stream *is, void *ptr, size_t size, - G_GNUC_UNUSED GError **error_r) -{ - struct input_despotify *ctx = (struct input_despotify *)is; - size_t to_cpy = size; - - if (ctx->len_available == 0) - refill_buffer(ctx); - - if (ctx->len_available < size) - to_cpy = ctx->len_available; - memcpy(ptr, ctx->pcm.buf, to_cpy); - ctx->len_available -= to_cpy; - - is->offset += to_cpy; - - return to_cpy; -} - -static void -input_despotify_close(struct input_stream *is) -{ - struct input_despotify *ctx = (struct input_despotify *)is; - - if (ctx->tag != NULL) - tag_free(ctx->tag); - - mpd_despotify_unregister_callback(callback); - despotify_free_track(ctx->track); - input_stream_deinit(&ctx->base); - g_free(ctx); -} - -static bool -input_despotify_eof(struct input_stream *is) -{ - struct input_despotify *ctx = (struct input_despotify *)is; - - return ctx->eof; -} - -static bool -input_despotify_seek(G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence, - G_GNUC_UNUSED GError **error_r) -{ - return false; -} - -static struct tag * -input_despotify_tag(struct input_stream *is) -{ - struct input_despotify *ctx = (struct input_despotify *)is; - struct tag *tag = ctx->tag; - - ctx->tag = NULL; - - return tag; -} - -const struct input_plugin input_plugin_despotify = { - .name = "spt", - .open = input_despotify_open, - .close = input_despotify_close, - .read = input_despotify_read, - .eof = input_despotify_eof, - .seek = input_despotify_seek, - .tag = input_despotify_tag, -}; diff --git a/src/input/despotify_input_plugin.h b/src/input/despotify_input_plugin.h deleted file mode 100644 index 4c070d882..000000000 --- a/src/input/despotify_input_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef INPUT_DESPOTIFY_H -#define INPUT_DESPOTIFY_H - -extern const struct input_plugin input_plugin_despotify; - -#endif diff --git a/src/input/ffmpeg_input_plugin.c b/src/input/ffmpeg_input_plugin.c deleted file mode 100644 index 6d339a067..000000000 --- a/src/input/ffmpeg_input_plugin.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input/ffmpeg_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" - -#include <libavutil/avutil.h> -#include <libavformat/avio.h> -#include <libavformat/avformat.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_ffmpeg" - -struct input_ffmpeg { - struct input_stream base; - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - AVIOContext *h; -#else - URLContext *h; -#endif - - bool eof; -}; - -static inline GQuark -ffmpeg_quark(void) -{ - return g_quark_from_static_string("ffmpeg"); -} - -static inline bool -input_ffmpeg_supported(void) -{ -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - void *opaque = NULL; - return avio_enum_protocols(&opaque, 0) != NULL; -#else - return av_protocol_next(NULL) != NULL; -#endif -} - -static bool -input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - av_register_all(); - - /* disable this plugin if there's no registered protocol */ - if (!input_ffmpeg_supported()) { - g_set_error(error_r, ffmpeg_quark(), 0, - "No protocol"); - return false; - } - - return true; -} - -static struct input_stream * -input_ffmpeg_open(const char *uri, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct input_ffmpeg *i; - - if (!g_str_has_prefix(uri, "gopher://") && - !g_str_has_prefix(uri, "rtp://") && - !g_str_has_prefix(uri, "rtsp://") && - !g_str_has_prefix(uri, "rtmp://") && - !g_str_has_prefix(uri, "rtmpt://") && - !g_str_has_prefix(uri, "rtmps://")) - return NULL; - - i = g_new(struct input_ffmpeg, 1); - input_stream_init(&i->base, &input_plugin_ffmpeg, uri, - mutex, cond); - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0) - int ret = avio_open(&i->h, uri, AVIO_FLAG_READ); -#elif LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - int ret = avio_open(&i->h, uri, AVIO_RDONLY); -#else - int ret = url_open(&i->h, uri, URL_RDONLY); -#endif - if (ret != 0) { - g_free(i); - g_set_error(error_r, ffmpeg_quark(), ret, - "libavformat failed to open the URI"); - return NULL; - } - - i->eof = false; - - i->base.ready = true; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - i->base.seekable = (i->h->seekable & AVIO_SEEKABLE_NORMAL) != 0; - i->base.size = avio_size(i->h); -#else - i->base.seekable = !i->h->is_streamed; - i->base.size = url_filesize(i->h); -#endif - - /* hack to make MPD select the "ffmpeg" decoder plugin - since - avio.h doesn't tell us the MIME type of the resource, we - can't select a decoder plugin, but the "ffmpeg" plugin is - quite good at auto-detection */ - i->base.mime = g_strdup("audio/x-mpd-ffmpeg"); - - return &i->base; -} - -static size_t -input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct input_ffmpeg *i = (struct input_ffmpeg *)is; - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - int ret = avio_read(i->h, ptr, size); -#else - int ret = url_read(i->h, ptr, size); -#endif - if (ret <= 0) { - if (ret < 0) - g_set_error(error_r, ffmpeg_quark(), 0, - "url_read() failed"); - - i->eof = true; - return false; - } - - is->offset += ret; - return (size_t)ret; -} - -static void -input_ffmpeg_close(struct input_stream *is) -{ - struct input_ffmpeg *i = (struct input_ffmpeg *)is; - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - avio_close(i->h); -#else - url_close(i->h); -#endif - input_stream_deinit(&i->base); - g_free(i); -} - -static bool -input_ffmpeg_eof(struct input_stream *is) -{ - struct input_ffmpeg *i = (struct input_ffmpeg *)is; - - return i->eof; -} - -static bool -input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence, - G_GNUC_UNUSED GError **error_r) -{ - struct input_ffmpeg *i = (struct input_ffmpeg *)is; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - int64_t ret = avio_seek(i->h, offset, whence); -#else - int64_t ret = url_seek(i->h, offset, whence); -#endif - - if (ret >= 0) { - i->eof = false; - return true; - } else { - g_set_error(error_r, ffmpeg_quark(), 0, "url_seek() failed"); - return false; - } -} - -const struct input_plugin input_plugin_ffmpeg = { - .name = "ffmpeg", - .init = input_ffmpeg_init, - .open = input_ffmpeg_open, - .close = input_ffmpeg_close, - .read = input_ffmpeg_read, - .eof = input_ffmpeg_eof, - .seek = input_ffmpeg_seek, -}; diff --git a/src/input/ffmpeg_input_plugin.h b/src/input/ffmpeg_input_plugin.h deleted file mode 100644 index 393836ca5..000000000 --- a/src/input/ffmpeg_input_plugin.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FFMPEG_INPUT_PLUGIN_H -#define MPD_FFMPEG_INPUT_PLUGIN_H - -/** - * An input plugin based on libavformat's "avio" library. - */ -extern const struct input_plugin input_plugin_ffmpeg; - -#endif diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c deleted file mode 100644 index 5ee3f200b..000000000 --- a/src/input/file_input_plugin.c +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "input/file_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "fd_util.h" -#include "open.h" - -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <string.h> -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_file" - -struct file_input_stream { - struct input_stream base; - - int fd; -}; - -static inline GQuark -file_quark(void) -{ - return g_quark_from_static_string("file"); -} - -static struct input_stream * -input_file_open(const char *filename, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - int fd, ret; - struct stat st; - struct file_input_stream *fis; - - if (!g_path_is_absolute(filename)) - return NULL; - - fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0); - if (fd < 0) { - if (errno != ENOENT && errno != ENOTDIR) - g_set_error(error_r, file_quark(), errno, - "Failed to open \"%s\": %s", - filename, g_strerror(errno)); - return NULL; - } - - ret = fstat(fd, &st); - if (ret < 0) { - g_set_error(error_r, file_quark(), errno, - "Failed to stat \"%s\": %s", - filename, g_strerror(errno)); - close(fd); - return NULL; - } - - if (!S_ISREG(st.st_mode)) { - g_set_error(error_r, file_quark(), 0, - "Not a regular file: %s", filename); - close(fd); - return NULL; - } - -#ifdef POSIX_FADV_SEQUENTIAL - posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL); -#endif - - fis = g_new(struct file_input_stream, 1); - input_stream_init(&fis->base, &input_plugin_file, filename, - mutex, cond); - - fis->base.size = st.st_size; - fis->base.seekable = true; - fis->base.ready = true; - - fis->fd = fd; - - return &fis->base; -} - -static bool -input_file_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r) -{ - struct file_input_stream *fis = (struct file_input_stream *)is; - - offset = (goffset)lseek(fis->fd, (off_t)offset, whence); - if (offset < 0) { - g_set_error(error_r, file_quark(), errno, - "Failed to seek: %s", g_strerror(errno)); - return false; - } - - is->offset = offset; - return true; -} - -static size_t -input_file_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct file_input_stream *fis = (struct file_input_stream *)is; - ssize_t nbytes; - - nbytes = read(fis->fd, ptr, size); - if (nbytes < 0) { - g_set_error(error_r, file_quark(), errno, - "Failed to read: %s", g_strerror(errno)); - return 0; - } - - is->offset += nbytes; - return (size_t)nbytes; -} - -static void -input_file_close(struct input_stream *is) -{ - struct file_input_stream *fis = (struct file_input_stream *)is; - - close(fis->fd); - input_stream_deinit(&fis->base); - g_free(fis); -} - -static bool -input_file_eof(struct input_stream *is) -{ - return is->offset >= is->size; -} - -const struct input_plugin input_plugin_file = { - .name = "file", - .open = input_file_open, - .close = input_file_close, - .read = input_file_read, - .eof = input_file_eof, - .seek = input_file_seek, -}; diff --git a/src/input/file_input_plugin.h b/src/input/file_input_plugin.h deleted file mode 100644 index f24769d57..000000000 --- a/src/input/file_input_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_FILE_H -#define MPD_INPUT_FILE_H - -extern const struct input_plugin input_plugin_file; - -#endif diff --git a/src/input/mms_input_plugin.c b/src/input/mms_input_plugin.c deleted file mode 100644 index cff15125b..000000000 --- a/src/input/mms_input_plugin.c +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input/mms_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" - -#include <glib.h> -#include <libmms/mmsx.h> - -#include <string.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_mms" - -struct input_mms { - struct input_stream base; - - mmsx_t *mms; - - bool eof; -}; - -static inline GQuark -mms_quark(void) -{ - return g_quark_from_static_string("mms"); -} - -static struct input_stream * -input_mms_open(const char *url, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct input_mms *m; - - if (!g_str_has_prefix(url, "mms://") && - !g_str_has_prefix(url, "mmsh://") && - !g_str_has_prefix(url, "mmst://") && - !g_str_has_prefix(url, "mmsu://")) - return NULL; - - m = g_new(struct input_mms, 1); - input_stream_init(&m->base, &input_plugin_mms, url, - mutex, cond); - - m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024); - if (m->mms == NULL) { - g_free(m); - g_set_error(error_r, mms_quark(), 0, "mmsx_connect() failed"); - return NULL; - } - - m->eof = false; - - /* XX is this correct? at least this selects the ffmpeg - decoder, which seems to work fine*/ - m->base.mime = g_strdup("audio/x-ms-wma"); - - m->base.ready = true; - - return &m->base; -} - -static size_t -input_mms_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct input_mms *m = (struct input_mms *)is; - int ret; - - ret = mmsx_read(NULL, m->mms, ptr, size); - if (ret <= 0) { - if (ret < 0) { - g_set_error(error_r, mms_quark(), errno, - "mmsx_read() failed: %s", - g_strerror(errno)); - } - - m->eof = true; - return false; - } - - is->offset += ret; - - return (size_t)ret; -} - -static void -input_mms_close(struct input_stream *is) -{ - struct input_mms *m = (struct input_mms *)is; - - mmsx_close(m->mms); - input_stream_deinit(&m->base); - g_free(m); -} - -static bool -input_mms_eof(struct input_stream *is) -{ - struct input_mms *m = (struct input_mms *)is; - - return m->eof; -} - -static bool -input_mms_seek(G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence, - G_GNUC_UNUSED GError **error_r) -{ - return false; -} - -const struct input_plugin input_plugin_mms = { - .name = "mms", - .open = input_mms_open, - .close = input_mms_close, - .read = input_mms_read, - .eof = input_mms_eof, - .seek = input_mms_seek, -}; diff --git a/src/input/rewind_input_plugin.c b/src/input/rewind_input_plugin.c deleted file mode 100644 index cf06fc57b..000000000 --- a/src/input/rewind_input_plugin.c +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input/rewind_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "tag.h" - -#include <glib.h> - -#include <assert.h> -#include <stdio.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_rewind" - -struct input_rewind { - struct input_stream base; - - struct input_stream *input; - - /** - * The read position within the buffer. Undefined as long as - * reading_from_buffer() returns false. - */ - size_t head; - - /** - * The write/append position within the buffer. - */ - size_t tail; - - /** - * The size of this buffer is the maximum number of bytes - * which can be rewinded cheaply without passing the "seek" - * call to CURL. - * - * The origin of this buffer is always the beginning of the - * stream (offset 0). - */ - char buffer[64 * 1024]; -}; - -/** - * Are we currently reading from the buffer, and does the buffer - * contain more data for the next read operation? - */ -static bool -reading_from_buffer(const struct input_rewind *r) -{ - return r->tail > 0 && r->base.offset < r->input->offset; -} - -/** - * Copy public attributes from the underlying input stream to the - * "rewind" input stream. This function is called when a method of - * the underlying stream has returned, which may have modified these - * attributes. - */ -static void -copy_attributes(struct input_rewind *r) -{ - struct input_stream *dest = &r->base; - const struct input_stream *src = r->input; - - assert(dest != src); - assert(src->mime == NULL || dest->mime != src->mime); - - bool dest_ready = dest->ready; - - dest->ready = src->ready; - dest->seekable = src->seekable; - dest->size = src->size; - dest->offset = src->offset; - - if (!dest_ready && src->ready) { - g_free(dest->mime); - dest->mime = g_strdup(src->mime); - } -} - -static void -input_rewind_close(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - input_stream_close(r->input); - - input_stream_deinit(&r->base); - g_free(r); -} - -static bool -input_rewind_check(struct input_stream *is, GError **error_r) -{ - struct input_rewind *r = (struct input_rewind *)is; - - return input_stream_check(r->input, error_r); -} - -static void -input_rewind_update(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - if (!reading_from_buffer(r)) - copy_attributes(r); -} - -static struct tag * -input_rewind_tag(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - return input_stream_tag(r->input); -} - -static bool -input_rewind_available(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - return input_stream_available(r->input); -} - -static size_t -input_rewind_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct input_rewind *r = (struct input_rewind *)is; - - if (reading_from_buffer(r)) { - /* buffered read */ - - assert(r->head == (size_t)is->offset); - assert(r->tail == (size_t)r->input->offset); - - if (size > r->tail - r->head) - size = r->tail - r->head; - - memcpy(ptr, r->buffer + r->head, size); - r->head += size; - is->offset += size; - - return size; - } else { - /* pass method call to underlying stream */ - - size_t nbytes = input_stream_read(r->input, ptr, size, error_r); - - if (r->input->offset > (goffset)sizeof(r->buffer)) - /* disable buffering */ - r->tail = 0; - else if (r->tail == (size_t)is->offset) { - /* append to buffer */ - - memcpy(r->buffer + r->tail, ptr, nbytes); - r->tail += nbytes; - - assert(r->tail == (size_t)r->input->offset); - } - - copy_attributes(r); - - return nbytes; - } -} - -static bool -input_rewind_eof(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - return !reading_from_buffer(r) && input_stream_eof(r->input); -} - -static bool -input_rewind_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r) -{ - struct input_rewind *r = (struct input_rewind *)is; - - assert(is->ready); - - if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) { - /* buffered seek */ - - assert(!reading_from_buffer(r) || - r->head == (size_t)is->offset); - assert(r->tail == (size_t)r->input->offset); - - r->head = (size_t)offset; - is->offset = offset; - - return true; - } else { - bool success = input_stream_seek(r->input, offset, whence, - error_r); - copy_attributes(r); - - /* disable the buffer, because r->input has left the - buffered range now */ - r->tail = 0; - - return success; - } -} - -static const struct input_plugin rewind_input_plugin = { - .close = input_rewind_close, - .check = input_rewind_check, - .update = input_rewind_update, - .tag = input_rewind_tag, - .available = input_rewind_available, - .read = input_rewind_read, - .eof = input_rewind_eof, - .seek = input_rewind_seek, -}; - -struct input_stream * -input_rewind_open(struct input_stream *is) -{ - struct input_rewind *c; - - assert(is != NULL); - assert(is->offset == 0); - - if (is->seekable) - /* seekable resources don't need this plugin */ - return is; - - c = g_new(struct input_rewind, 1); - input_stream_init(&c->base, &rewind_input_plugin, is->uri, - is->mutex, is->cond); - c->tail = 0; - c->input = is; - - return &c->base; -} diff --git a/src/input/rewind_input_plugin.h b/src/input/rewind_input_plugin.h deleted file mode 100644 index 83abe257a..000000000 --- a/src/input/rewind_input_plugin.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * A wrapper for an input_stream object which allows cheap buffered - * rewinding. This is useful while detecting the stream codec (let - * each decoder plugin peek a portion from the stream). - */ - -#ifndef MPD_INPUT_REWIND_H -#define MPD_INPUT_REWIND_H - -#include "check.h" - -struct input_stream; - -struct input_stream * -input_rewind_open(struct input_stream *is); - -#endif diff --git a/src/input/soup_input_plugin.c b/src/input/soup_input_plugin.c deleted file mode 100644 index fc903b48c..000000000 --- a/src/input/soup_input_plugin.c +++ /dev/null @@ -1,473 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input/soup_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "io_thread.h" -#include "conf.h" - -#include <libsoup/soup-uri.h> -#include <libsoup/soup-session-async.h> - -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_soup" - -/** - * Do not buffer more than this number of bytes. It should be a - * reasonable limit that doesn't make low-end machines suffer too - * much, but doesn't cause stuttering on high-latency lines. - */ -static const size_t SOUP_MAX_BUFFERED = 512 * 1024; - -/** - * Resume the stream at this number of bytes after it has been paused. - */ -static const size_t SOUP_RESUME_AT = 384 * 1024; - -static SoupURI *soup_proxy; -static SoupSession *soup_session; - -struct input_soup { - struct input_stream base; - - SoupMessage *msg; - - GQueue *buffers; - - size_t current_consumed; - - size_t total_buffered; - - bool alive, pause, eof; - - /** - * Set when the session callback has been invoked, when it is - * safe to free this object. - */ - bool completed; - - GError *postponed_error; -}; - -static inline GQuark -soup_quark(void) -{ - return g_quark_from_static_string("soup"); -} - -static bool -input_soup_init(const struct config_param *param, GError **error_r) -{ - assert(soup_proxy == NULL); - assert(soup_session == NULL); - - g_type_init(); - - const char *proxy = config_get_block_string(param, "proxy", NULL); - - if (proxy != NULL) { - soup_proxy = soup_uri_new(proxy); - if (soup_proxy == NULL) { - g_set_error(error_r, soup_quark(), 0, - "failed to parse proxy setting"); - return false; - } - } - - soup_session = - soup_session_async_new_with_options(SOUP_SESSION_PROXY_URI, - soup_proxy, - SOUP_SESSION_ASYNC_CONTEXT, - io_thread_context(), - NULL); - - return true; -} - -static void -input_soup_finish(void) -{ - assert(soup_session != NULL); - - soup_session_abort(soup_session); - g_object_unref(G_OBJECT(soup_session)); - - if (soup_proxy != NULL) - soup_uri_free(soup_proxy); -} - -/** - * Copy the error from the SoupMessage object to - * input_soup::postponed_error. - * - * @return true if there was no error - */ -static bool -input_soup_copy_error(struct input_soup *s, const SoupMessage *msg) -{ - if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) - return true; - - if (msg->status_code == SOUP_STATUS_CANCELLED) - /* failure, but don't generate a GError, because this - status was caused by _close() */ - return false; - - if (s->postponed_error != NULL) - /* there's already a GError, don't overwrite it */ - return false; - - if (SOUP_STATUS_IS_TRANSPORT_ERROR(msg->status_code)) - s->postponed_error = - g_error_new(soup_quark(), msg->status_code, - "HTTP client error: %s", - msg->reason_phrase); - else - s->postponed_error = - g_error_new(soup_quark(), msg->status_code, - "got HTTP status: %d %s", - msg->status_code, msg->reason_phrase); - - return false; -} - -static void -input_soup_session_callback(G_GNUC_UNUSED SoupSession *session, - SoupMessage *msg, gpointer user_data) -{ - struct input_soup *s = user_data; - - assert(msg == s->msg); - assert(!s->completed); - - g_mutex_lock(s->base.mutex); - - if (!s->base.ready) - input_soup_copy_error(s, msg); - - s->base.ready = true; - s->alive = false; - s->completed = true; - - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); -} - -static void -input_soup_got_headers(SoupMessage *msg, gpointer user_data) -{ - struct input_soup *s = user_data; - - g_mutex_lock(s->base.mutex); - - if (!input_soup_copy_error(s, msg)) { - g_mutex_unlock(s->base.mutex); - - soup_session_cancel_message(soup_session, msg, - SOUP_STATUS_CANCELLED); - return; - } - - s->base.ready = true; - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); - - soup_message_body_set_accumulate(msg->response_body, false); -} - -static void -input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) -{ - struct input_soup *s = user_data; - - assert(msg == s->msg); - - g_mutex_lock(s->base.mutex); - - g_queue_push_tail(s->buffers, soup_buffer_copy(chunk)); - s->total_buffered += chunk->length; - - if (s->total_buffered >= SOUP_MAX_BUFFERED && !s->pause) { - s->pause = true; - soup_session_pause_message(soup_session, msg); - } - - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); -} - -static void -input_soup_got_body(G_GNUC_UNUSED SoupMessage *msg, gpointer user_data) -{ - struct input_soup *s = user_data; - - assert(msg == s->msg); - - g_mutex_lock(s->base.mutex); - - s->base.ready = true; - s->eof = true; - s->alive = false; - - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); -} - -static bool -input_soup_wait_data(struct input_soup *s) -{ - while (true) { - if (s->eof) - return true; - - if (!s->alive) - return false; - - if (!g_queue_is_empty(s->buffers)) - return true; - - assert(s->current_consumed == 0); - - g_cond_wait(s->base.cond, s->base.mutex); - } -} - -static gpointer -input_soup_queue(gpointer data) -{ - struct input_soup *s = data; - - soup_session_queue_message(soup_session, s->msg, - input_soup_session_callback, s); - - return NULL; -} - -static struct input_stream * -input_soup_open(const char *uri, - GMutex *mutex, GCond *cond, - G_GNUC_UNUSED GError **error_r) -{ - if (strncmp(uri, "http://", 7) != 0) - return NULL; - - struct input_soup *s = g_new(struct input_soup, 1); - input_stream_init(&s->base, &input_plugin_soup, uri, - mutex, cond); - - s->buffers = g_queue_new(); - s->current_consumed = 0; - s->total_buffered = 0; - -#if GCC_CHECK_VERSION(4,6) -#pragma GCC diagnostic push - /* the libsoup macro SOUP_METHOD_GET discards the "const" - attribute of the g_intern_static_string() return value; - don't make the gcc warning fatal: */ -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - - s->msg = soup_message_new(SOUP_METHOD_GET, uri); - -#if GCC_CHECK_VERSION(4,6) -#pragma GCC diagnostic pop -#endif - - soup_message_set_flags(s->msg, SOUP_MESSAGE_NO_REDIRECT); - - soup_message_headers_append(s->msg->request_headers, "User-Agent", - "Music Player Daemon " VERSION); - - g_signal_connect(s->msg, "got-headers", - G_CALLBACK(input_soup_got_headers), s); - g_signal_connect(s->msg, "got-chunk", - G_CALLBACK(input_soup_got_chunk), s); - g_signal_connect(s->msg, "got-body", - G_CALLBACK(input_soup_got_body), s); - - s->alive = true; - s->pause = false; - s->eof = false; - s->completed = false; - s->postponed_error = NULL; - - io_thread_call(input_soup_queue, s); - - return &s->base; -} - -static gpointer -input_soup_cancel(gpointer data) -{ - struct input_soup *s = data; - - if (!s->completed) - soup_session_cancel_message(soup_session, s->msg, - SOUP_STATUS_CANCELLED); - - return NULL; -} - -static void -input_soup_close(struct input_stream *is) -{ - struct input_soup *s = (struct input_soup *)is; - - g_mutex_lock(s->base.mutex); - - if (!s->completed) { - /* the messages's session callback hasn't been invoked - yet; cancel it and wait for completion */ - - g_mutex_unlock(s->base.mutex); - - io_thread_call(input_soup_cancel, s); - - g_mutex_lock(s->base.mutex); - while (!s->completed) - g_cond_wait(s->base.cond, s->base.mutex); - } - - g_mutex_unlock(s->base.mutex); - - SoupBuffer *buffer; - while ((buffer = g_queue_pop_head(s->buffers)) != NULL) - soup_buffer_free(buffer); - g_queue_free(s->buffers); - - if (s->postponed_error != NULL) - g_error_free(s->postponed_error); - - input_stream_deinit(&s->base); - g_free(s); -} - -static bool -input_soup_check(struct input_stream *is, GError **error_r) -{ - struct input_soup *s = (struct input_soup *)is; - - bool success = s->postponed_error == NULL; - if (!success) { - g_propagate_error(error_r, s->postponed_error); - s->postponed_error = NULL; - } - - return success; -} - -static bool -input_soup_available(struct input_stream *is) -{ - struct input_soup *s = (struct input_soup *)is; - - return s->eof || !s->alive || !g_queue_is_empty(s->buffers); -} - -static size_t -input_soup_read(struct input_stream *is, void *ptr, size_t size, - G_GNUC_UNUSED GError **error_r) -{ - struct input_soup *s = (struct input_soup *)is; - - if (!input_soup_wait_data(s)) { - assert(!s->alive); - - if (s->postponed_error != NULL) { - g_propagate_error(error_r, s->postponed_error); - s->postponed_error = NULL; - } else - g_set_error_literal(error_r, soup_quark(), 0, - "HTTP failure"); - return 0; - } - - char *p0 = ptr, *p = p0, *p_end = p0 + size; - - while (p < p_end) { - SoupBuffer *buffer = g_queue_pop_head(s->buffers); - if (buffer == NULL) { - assert(s->current_consumed == 0); - break; - } - - assert(s->current_consumed < buffer->length); - assert(s->total_buffered >= buffer->length); - - const char *q = buffer->data; - q += s->current_consumed; - - size_t remaining = buffer->length - s->current_consumed; - size_t nbytes = p_end - p; - if (nbytes > remaining) - nbytes = remaining; - - memcpy(p, q, nbytes); - p += nbytes; - - s->current_consumed += remaining; - if (s->current_consumed >= buffer->length) { - /* done with this buffer */ - s->total_buffered -= buffer->length; - soup_buffer_free(buffer); - s->current_consumed = 0; - } else { - /* partial read */ - assert(p == p_end); - - g_queue_push_head(s->buffers, buffer); - } - } - - if (s->pause && s->total_buffered < SOUP_RESUME_AT) { - s->pause = false; - soup_session_unpause_message(soup_session, s->msg); - } - - size_t nbytes = p - p0; - s->base.offset += nbytes; - - return nbytes; -} - -static bool -input_soup_eof(G_GNUC_UNUSED struct input_stream *is) -{ - struct input_soup *s = (struct input_soup *)is; - - return !s->alive && g_queue_is_empty(s->buffers); -} - -const struct input_plugin input_plugin_soup = { - .name = "soup", - .init = input_soup_init, - .finish = input_soup_finish, - - .open = input_soup_open, - .close = input_soup_close, - .check = input_soup_check, - .available = input_soup_available, - .read = input_soup_read, - .eof = input_soup_eof, -}; diff --git a/src/input/soup_input_plugin.h b/src/input/soup_input_plugin.h deleted file mode 100644 index 689b2d971..000000000 --- a/src/input/soup_input_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_SOUP_H -#define MPD_INPUT_SOUP_H - -extern const struct input_plugin input_plugin_soup; - -#endif diff --git a/src/input_init.c b/src/input_init.c deleted file mode 100644 index 771d648d1..000000000 --- a/src/input_init.c +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input_init.h" -#include "input_plugin.h" -#include "input_registry.h" -#include "conf.h" - -#include <assert.h> -#include <string.h> - -static inline GQuark -input_quark(void) -{ - return g_quark_from_static_string("input"); -} - -/** - * Find the "input" configuration block for the specified plugin. - * - * @param plugin_name the name of the input plugin - * @return the configuration block, or NULL if none was configured - */ -static const struct config_param * -input_plugin_config(const char *plugin_name, GError **error_r) -{ - const struct config_param *param = NULL; - - while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) { - const char *name = - config_get_block_string(param, "plugin", NULL); - if (name == NULL) { - g_set_error(error_r, input_quark(), 0, - "input configuration without 'plugin' name in line %d", - param->line); - return NULL; - } - - if (strcmp(name, plugin_name) == 0) - return param; - } - - return NULL; -} - -bool -input_stream_global_init(GError **error_r) -{ - GError *error = NULL; - - for (unsigned i = 0; input_plugins[i] != NULL; ++i) { - const struct input_plugin *plugin = input_plugins[i]; - - assert(plugin->name != NULL); - assert(*plugin->name != 0); - assert(plugin->open != NULL); - - const struct config_param *param = - input_plugin_config(plugin->name, &error); - if (param == NULL && error != NULL) { - g_propagate_error(error_r, error); - return false; - } - - if (!config_get_block_bool(param, "enabled", true)) - /* the plugin is disabled in mpd.conf */ - continue; - - if (plugin->init == NULL || plugin->init(param, &error)) - input_plugins_enabled[i] = true; - else { - g_propagate_prefixed_error(error_r, error, - "Failed to initialize input plugin '%s': ", - plugin->name); - return false; - } - } - - return true; -} - -void input_stream_global_finish(void) -{ - input_plugins_for_each_enabled(plugin) - if (plugin->finish != NULL) - plugin->finish(); -} diff --git a/src/input_init.h b/src/input_init.h deleted file mode 100644 index ad92cda08..000000000 --- a/src/input_init.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_INIT_H -#define MPD_INPUT_INIT_H - -#include "check.h" - -#include <glib.h> -#include <stdbool.h> - -/** - * Initializes this library and all input_stream implementations. - * - * @param error_r location to store the error occurring, or NULL to - * ignore errors - */ -bool -input_stream_global_init(GError **error_r); - -/** - * Deinitializes this library and all input_stream implementations. - */ -void input_stream_global_finish(void); - -#endif diff --git a/src/input_internal.c b/src/input_internal.c deleted file mode 100644 index 92a71856e..000000000 --- a/src/input_internal.c +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input_internal.h" -#include "input_stream.h" - -#include <assert.h> - -void -input_stream_init(struct input_stream *is, const struct input_plugin *plugin, - const char *uri, GMutex *mutex, GCond *cond) -{ - assert(is != NULL); - assert(plugin != NULL); - assert(uri != NULL); - - is->plugin = plugin; - is->uri = g_strdup(uri); - is->mutex = mutex; - is->cond = cond; - is->ready = false; - is->seekable = false; - is->size = -1; - is->offset = 0; - is->mime = NULL; -} - -void -input_stream_deinit(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - g_free(is->uri); - g_free(is->mime); -} - -void -input_stream_signal_client(struct input_stream *is) -{ - if (is->cond != NULL) - g_cond_broadcast(is->cond); -} - -void -input_stream_set_ready(struct input_stream *is) -{ - g_mutex_lock(is->mutex); - - if (!is->ready) { - is->ready = true; - input_stream_signal_client(is); - } - - g_mutex_unlock(is->mutex); -} diff --git a/src/input_internal.h b/src/input_internal.h deleted file mode 100644 index d95142e46..000000000 --- a/src/input_internal.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_INTERNAL_H -#define MPD_INPUT_INTERNAL_H - -#include "check.h" - -#include <glib.h> - -struct input_stream; -struct input_plugin; - -void -input_stream_init(struct input_stream *is, const struct input_plugin *plugin, - const char *uri, GMutex *mutex, GCond *cond); - -void -input_stream_deinit(struct input_stream *is); - -void -input_stream_signal_client(struct input_stream *is); - -void -input_stream_set_ready(struct input_stream *is); - -#endif diff --git a/src/input_plugin.h b/src/input_plugin.h deleted file mode 100644 index 6b0c77c85..000000000 --- a/src/input_plugin.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_PLUGIN_H -#define MPD_INPUT_PLUGIN_H - -#include "input_stream.h" - -#include <stddef.h> -#include <stdbool.h> -#include <sys/types.h> - -struct config_param; -struct input_stream; - -struct input_plugin { - const char *name; - - /** - * Global initialization. This method is called when MPD starts. - * - * @param error_r location to store the error occurring, or - * NULL to ignore errors - * @return true on success, false if the plugin should be - * disabled - */ - bool (*init)(const struct config_param *param, GError **error_r); - - /** - * Global deinitialization. Called once before MPD shuts - * down (only if init() has returned true). - */ - void (*finish)(void); - - struct input_stream *(*open)(const char *uri, - GMutex *mutex, GCond *cond, - GError **error_r); - void (*close)(struct input_stream *is); - - /** - * Check for errors that may have occurred in the I/O thread. - * May be unimplemented for synchronous plugins. - * - * @return false on error - */ - bool (*check)(struct input_stream *is, GError **error_r); - - /** - * Update the public attributes. Call before access. Can be - * NULL if the plugin always keeps its attributes up to date. - */ - void (*update)(struct input_stream *is); - - struct tag *(*tag)(struct input_stream *is); - - /** - * Returns true if the next read operation will not block: - * either data is available, or end-of-stream has been - * reached, or an error has occurred. - * - * If this method is unimplemented, then it is assumed that - * reading will never block. - */ - bool (*available)(struct input_stream *is); - - size_t (*read)(struct input_stream *is, void *ptr, size_t size, - GError **error_r); - bool (*eof)(struct input_stream *is); - bool (*seek)(struct input_stream *is, goffset offset, int whence, - GError **error_r); -}; - -#endif diff --git a/src/input_registry.c b/src/input_registry.c deleted file mode 100644 index 5987d5da2..000000000 --- a/src/input_registry.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input_registry.h" -#include "input/file_input_plugin.h" - -#ifdef ENABLE_ARCHIVE -#include "input/archive_input_plugin.h" -#endif - -#ifdef ENABLE_CURL -#include "input/curl_input_plugin.h" -#endif - -#ifdef ENABLE_SOUP -#include "input/soup_input_plugin.h" -#endif - -#ifdef HAVE_FFMPEG -#include "input/ffmpeg_input_plugin.h" -#endif - -#ifdef ENABLE_MMS -#include "input/mms_input_plugin.h" -#endif - -#ifdef ENABLE_CDIO_PARANOIA -#include "input/cdio_paranoia_input_plugin.h" -#endif - -#ifdef ENABLE_DESPOTIFY -#include "input/despotify_input_plugin.h" -#endif - -#include <glib.h> - -const struct input_plugin *const input_plugins[] = { - &input_plugin_file, -#ifdef ENABLE_ARCHIVE - &input_plugin_archive, -#endif -#ifdef ENABLE_CURL - &input_plugin_curl, -#endif -#ifdef ENABLE_SOUP - &input_plugin_soup, -#endif -#ifdef HAVE_FFMPEG - &input_plugin_ffmpeg, -#endif -#ifdef ENABLE_MMS - &input_plugin_mms, -#endif -#ifdef ENABLE_CDIO_PARANOIA - &input_plugin_cdio_paranoia, -#endif -#ifdef ENABLE_DESPOTIFY - &input_plugin_despotify, -#endif - NULL -}; - -bool input_plugins_enabled[G_N_ELEMENTS(input_plugins) - 1]; diff --git a/src/input_registry.h b/src/input_registry.h deleted file mode 100644 index 4f5fff8da..000000000 --- a/src/input_registry.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_REGISTRY_H -#define MPD_INPUT_REGISTRY_H - -#include "check.h" - -#include <stdbool.h> - -/** - * NULL terminated list of all input plugins which were enabled at - * compile time. - */ -extern const struct input_plugin *const input_plugins[]; - -extern bool input_plugins_enabled[]; - -#define input_plugins_for_each(plugin) \ - for (const struct input_plugin *plugin, \ - *const*input_plugin_iterator = &input_plugins[0]; \ - (plugin = *input_plugin_iterator) != NULL; \ - ++input_plugin_iterator) - -#define input_plugins_for_each_enabled(plugin) \ - input_plugins_for_each(plugin) \ - if (input_plugins_enabled[input_plugin_iterator - input_plugins]) - -#endif diff --git a/src/input_stream.c b/src/input_stream.c deleted file mode 100644 index e445dca6c..000000000 --- a/src/input_stream.c +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input_stream.h" -#include "input_registry.h" -#include "input_plugin.h" -#include "input/rewind_input_plugin.h" - -#include <glib.h> -#include <assert.h> - -static inline GQuark -input_quark(void) -{ - return g_quark_from_static_string("input"); -} - -struct input_stream * -input_stream_open(const char *url, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - GError *error = NULL; - - assert(mutex != NULL); - assert(error_r == NULL || *error_r == NULL); - - input_plugins_for_each_enabled(plugin) { - struct input_stream *is; - - is = plugin->open(url, mutex, cond, &error); - if (is != NULL) { - assert(is->plugin != NULL); - assert(is->plugin->close != NULL); - assert(is->plugin->read != NULL); - assert(is->plugin->eof != NULL); - assert(!is->seekable || is->plugin->seek != NULL); - - is = input_rewind_open(is); - - return is; - } else if (error != NULL) { - g_propagate_error(error_r, error); - return NULL; - } - } - - g_set_error(error_r, input_quark(), 0, "Unrecognized URI"); - return NULL; -} - -bool -input_stream_check(struct input_stream *is, GError **error_r) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - return is->plugin->check == NULL || - is->plugin->check(is, error_r); -} - -void -input_stream_update(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->plugin->update != NULL) - is->plugin->update(is); -} - -void -input_stream_wait_ready(struct input_stream *is) -{ - assert(is != NULL); - assert(is->mutex != NULL); - assert(is->cond != NULL); - - while (true) { - input_stream_update(is); - if (is->ready) - break; - - g_cond_wait(is->cond, is->mutex); - } -} - -void -input_stream_lock_wait_ready(struct input_stream *is) -{ - assert(is != NULL); - assert(is->mutex != NULL); - assert(is->cond != NULL); - - g_mutex_lock(is->mutex); - input_stream_wait_ready(is); - g_mutex_unlock(is->mutex); -} - -bool -input_stream_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->plugin->seek == NULL) - return false; - - return is->plugin->seek(is, offset, whence, error_r); -} - -bool -input_stream_lock_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->plugin->seek == NULL) - return false; - - if (is->mutex == NULL) - /* no locking */ - return input_stream_seek(is, offset, whence, error_r); - - g_mutex_lock(is->mutex); - bool success = input_stream_seek(is, offset, whence, error_r); - g_mutex_unlock(is->mutex); - return success; -} - -struct tag * -input_stream_tag(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - return is->plugin->tag != NULL - ? is->plugin->tag(is) - : NULL; -} - -struct tag * -input_stream_lock_tag(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->plugin->tag == NULL) - return false; - - if (is->mutex == NULL) - /* no locking */ - return input_stream_tag(is); - - g_mutex_lock(is->mutex); - struct tag *tag = input_stream_tag(is); - g_mutex_unlock(is->mutex); - return tag; -} - -bool -input_stream_available(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - return is->plugin->available != NULL - ? is->plugin->available(is) - : true; -} - -size_t -input_stream_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - assert(ptr != NULL); - assert(size > 0); - - return is->plugin->read(is, ptr, size, error_r); -} - -size_t -input_stream_lock_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - assert(ptr != NULL); - assert(size > 0); - - if (is->mutex == NULL) - /* no locking */ - return input_stream_read(is, ptr, size, error_r); - - g_mutex_lock(is->mutex); - size_t nbytes = input_stream_read(is, ptr, size, error_r); - g_mutex_unlock(is->mutex); - return nbytes; -} - -void input_stream_close(struct input_stream *is) -{ - is->plugin->close(is); -} - -bool input_stream_eof(struct input_stream *is) -{ - return is->plugin->eof(is); -} - -bool -input_stream_lock_eof(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->mutex == NULL) - /* no locking */ - return input_stream_eof(is); - - g_mutex_lock(is->mutex); - bool eof = input_stream_eof(is); - g_mutex_unlock(is->mutex); - return eof; -} - diff --git a/src/input_stream.h b/src/input_stream.h index 10ad97161..24dda1eee 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 000000000..930ced108 --- /dev/null +++ b/src/io_error.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_IO_ERROR_H +#define MPD_IO_ERROR_H + +#include <glib.h> + +#include <errno.h> + +/** + * A GQuark for GError for I/O errors. The code is an errno value. + */ +G_GNUC_CONST +static inline GQuark +errno_quark(void) +{ + return g_quark_from_static_string("errno"); +} + +static inline void +set_error_errno(GError **error_r) +{ + g_set_error_literal(error_r, errno_quark(), errno, + g_strerror(errno)); +} + +static inline GError * +new_error_errno(void) +{ + return g_error_new_literal(errno_quark(), errno, + g_strerror(errno)); +} + +#endif diff --git a/src/io_thread.c b/src/io_thread.c deleted file mode 100644 index 7c080adcb..000000000 --- a/src/io_thread.c +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "io_thread.h" - -#include <assert.h> - -static struct { - GMutex *mutex; - GCond *cond; - - GMainContext *context; - GMainLoop *loop; - GThread *thread; -} io; - -void -io_thread_run(void) -{ - assert(io_thread_inside()); - assert(io.context != NULL); - assert(io.loop != NULL); - - g_main_loop_run(io.loop); -} - -static gpointer -io_thread_func(G_GNUC_UNUSED gpointer arg) -{ - /* lock+unlock to synchronize with io_thread_start(), to be - sure that io.thread is set */ - g_mutex_lock(io.mutex); - g_mutex_unlock(io.mutex); - - io_thread_run(); - return NULL; -} - -void -io_thread_init(void) -{ - assert(io.context == NULL); - assert(io.loop == NULL); - assert(io.thread == NULL); - - io.mutex = g_mutex_new(); - io.cond = g_cond_new(); - io.context = g_main_context_new(); - io.loop = g_main_loop_new(io.context, false); -} - -bool -io_thread_start(GError **error_r) -{ - assert(io.context != NULL); - assert(io.loop != NULL); - assert(io.thread == NULL); - - g_mutex_lock(io.mutex); - io.thread = g_thread_create(io_thread_func, NULL, true, error_r); - g_mutex_unlock(io.mutex); - if (io.thread == NULL) - return false; - - return true; -} - -void -io_thread_quit(void) -{ - assert(io.loop != NULL); - - g_main_loop_quit(io.loop); -} - -void -io_thread_deinit(void) -{ - if (io.thread != NULL) { - io_thread_quit(); - - g_thread_join(io.thread); - } - - if (io.loop != NULL) - g_main_loop_unref(io.loop); - - if (io.context != NULL) - g_main_context_unref(io.context); - - g_cond_free(io.cond); - g_mutex_free(io.mutex); -} - -GMainContext * -io_thread_context(void) -{ - return io.context; -} - -bool -io_thread_inside(void) -{ - return io.thread != NULL && g_thread_self() == io.thread; -} - -guint -io_thread_idle_add(GSourceFunc function, gpointer data) -{ - GSource *source = g_idle_source_new(); - g_source_set_callback(source, function, data, NULL); - guint id = g_source_attach(source, io.context); - g_source_unref(source); - return id; -} - -GSource * -io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data) -{ - GSource *source = g_timeout_source_new(interval_ms); - g_source_set_callback(source, function, data, NULL); - g_source_attach(source, io.context); - return source; -} - -GSource * -io_thread_timeout_add_seconds(guint interval, - GSourceFunc function, gpointer data) -{ - GSource *source = g_timeout_source_new_seconds(interval); - g_source_set_callback(source, function, data, NULL); - g_source_attach(source, io.context); - return source; -} - -struct call_data { - GThreadFunc function; - gpointer data; - bool done; - gpointer result; -}; - -static gboolean -io_thread_call_func(gpointer _data) -{ - struct call_data *data = _data; - - gpointer result = data->function(data->data); - - g_mutex_lock(io.mutex); - data->done = true; - data->result = result; - g_cond_broadcast(io.cond); - g_mutex_unlock(io.mutex); - - return false; -} - -gpointer -io_thread_call(GThreadFunc function, gpointer _data) -{ - assert(io.thread != NULL); - - if (io_thread_inside()) - /* we're already in the I/O thread - no - synchronization needed */ - return function(_data); - - struct call_data data = { - .function = function, - .data = _data, - .done = false, - }; - - io_thread_idle_add(io_thread_call_func, &data); - - g_mutex_lock(io.mutex); - while (!data.done) - g_cond_wait(io.cond, io.mutex); - g_mutex_unlock(io.mutex); - - return data.result; -} diff --git a/src/io_thread.h b/src/io_thread.h deleted file mode 100644 index 8ff5a71e5..000000000 --- a/src/io_thread.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_IO_THREAD_H -#define MPD_IO_THREAD_H - -#include <glib.h> -#include <stdbool.h> - -void -io_thread_init(void); - -bool -io_thread_start(GError **error_r); - -/** - * Run the I/O event loop synchronously in the current thread. This - * can be called instead of io_thread_start(). For testing purposes - * only. - */ -void -io_thread_run(void); - -/** - * Ask the I/O thread to quit, but does not wait for it. Usually, you - * don't need to call this function, because io_thread_deinit() - * includes this. - */ -void -io_thread_quit(void); - -void -io_thread_deinit(void); - -G_GNUC_PURE -GMainContext * -io_thread_context(void); - -/** - * Is the current thread the I/O thread? - */ -G_GNUC_PURE -bool -io_thread_inside(void); - -guint -io_thread_idle_add(GSourceFunc function, gpointer data); - -G_GNUC_MALLOC -GSource * -io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data); - -G_GNUC_MALLOC -GSource * -io_thread_timeout_add_seconds(guint interval, - GSourceFunc function, gpointer data); - -/** - * Call a function synchronously in the I/O thread. - */ -gpointer -io_thread_call(GThreadFunc function, gpointer data); - -#endif diff --git a/src/listen.c b/src/listen.c deleted file mode 100644 index 90e13b9c1..000000000 --- a/src/listen.c +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "listen.h" -#include "server_socket.h" -#include "client.h" -#include "conf.h" -#include "main.h" - -#include <string.h> -#include <assert.h> - -#ifdef ENABLE_SYSTEMD_DAEMON -#include <systemd/sd-daemon.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "listen" - -#define DEFAULT_PORT 6600 - -static struct server_socket *listen_socket; -int listen_port; - -static void -listen_callback(int fd, const struct sockaddr *address, - size_t address_length, int uid, G_GNUC_UNUSED void *ctx) -{ - client_new(global_player_control, fd, address, address_length, uid); -} - -static bool -listen_add_config_param(unsigned int port, - const struct config_param *param, - GError **error_r) -{ - assert(param != NULL); - - if (0 == strcmp(param->value, "any")) { - return server_socket_add_port(listen_socket, port, error_r); - } else if (param->value[0] == '/') { - return server_socket_add_path(listen_socket, param->value, - error_r); - } else { - return server_socket_add_host(listen_socket, param->value, - port, error_r); - } -} - -static bool -listen_systemd_activation(GError **error_r) -{ -#ifdef ENABLE_SYSTEMD_DAEMON - int n = sd_listen_fds(true); - if (n <= 0) { - if (n < 0) - g_warning("sd_listen_fds() failed: %s", - g_strerror(-n)); - return false; - } - - for (int i = SD_LISTEN_FDS_START, end = SD_LISTEN_FDS_START + n; - i != end; ++i) - if (!server_socket_add_fd(listen_socket, i, error_r)) - return false; - - return true; -#else - (void)error_r; - return false; -#endif -} - -bool -listen_global_init(GError **error_r) -{ - int port = config_get_positive(CONF_PORT, DEFAULT_PORT); - const struct config_param *param = - config_get_next_param(CONF_BIND_TO_ADDRESS, NULL); - bool success; - GError *error = NULL; - - listen_socket = server_socket_new(listen_callback, NULL); - - if (listen_systemd_activation(&error)) - return true; - - if (error != NULL) { - g_propagate_error(error_r, error); - return false; - } - - if (param != NULL) { - /* "bind_to_address" is configured, create listeners - for all values */ - - do { - success = listen_add_config_param(port, param, &error); - if (!success) { - g_propagate_prefixed_error(error_r, error, - "Failed to listen on %s (line %i): ", - param->value, param->line); - return false; - } - - param = config_get_next_param(CONF_BIND_TO_ADDRESS, - param); - } while (param != NULL); - } else { - /* no "bind_to_address" configured, bind the - configured port on all interfaces */ - - success = server_socket_add_port(listen_socket, port, error_r); - if (!success) { - g_propagate_prefixed_error(error_r, error, - "Failed to listen on *:%d: ", - port); - return false; - } - } - - if (!server_socket_open(listen_socket, error_r)) - return false; - - listen_port = port; - return true; -} - -void listen_global_finish(void) -{ - g_debug("listen_global_finish called"); - - assert(listen_socket != NULL); - - server_socket_free(listen_socket); -} diff --git a/src/listen.h b/src/listen.h deleted file mode 100644 index 246e83706..000000000 --- a/src/listen.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_LISTEN_H -#define MPD_LISTEN_H - -#include <glib.h> - -#include <stdbool.h> - -extern int listen_port; - -bool -listen_global_init(GError **error_r); - -void listen_global_finish(void); - -#endif diff --git a/src/locate.c b/src/locate.c deleted file mode 100644 index c9684d2b6..000000000 --- a/src/locate.c +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "locate.h" -#include "path.h" -#include "tag.h" -#include "song.h" - -#include <glib.h> - -#include <stdlib.h> - -#define LOCATE_TAG_FILE_KEY "file" -#define LOCATE_TAG_FILE_KEY_OLD "filename" -#define LOCATE_TAG_ANY_KEY "any" - -int -locate_parse_type(const char *str) -{ - if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY) || - 0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY_OLD)) - return LOCATE_TAG_FILE_TYPE; - - if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY)) - return LOCATE_TAG_ANY_TYPE; - - enum tag_type i = tag_name_parse_i(str); - if (i != TAG_NUM_OF_ITEM_TYPES) - return i; - - return -1; -} - -static bool -locate_item_init(struct locate_item *item, - const char *type_string, const char *needle) -{ - item->tag = locate_parse_type(type_string); - - if (item->tag < 0) - return false; - - item->needle = g_strdup(needle); - - return true; -} - -void -locate_item_list_free(struct locate_item_list *list) -{ - for (unsigned i = 0; i < list->length; ++i) - g_free(list->items[i].needle); - - g_free(list); -} - -struct locate_item_list * -locate_item_list_new(unsigned length) -{ - struct locate_item_list *list = - g_malloc0(sizeof(*list) - sizeof(list->items[0]) + - length * sizeof(list->items[0])); - list->length = length; - - return list; -} - -struct locate_item_list * -locate_item_list_parse(char *argv[], int argc) -{ - if (argc % 2 != 0) - return NULL; - - struct locate_item_list *list = locate_item_list_new(argc / 2); - - for (unsigned i = 0; i < list->length; ++i) { - if (!locate_item_init(&list->items[i], argv[i * 2], - argv[i * 2 + 1])) { - locate_item_list_free(list); - return NULL; - } - } - - return list; -} - -struct locate_item_list * -locate_item_list_casefold(const struct locate_item_list *list) -{ - struct locate_item_list *new_list = locate_item_list_new(list->length); - - for (unsigned i = 0; i < list->length; i++){ - new_list->items[i].needle = - g_utf8_casefold(list->items[i].needle, -1); - new_list->items[i].tag = list->items[i].tag; - } - - return new_list; -} - -void -locate_item_free(struct locate_item *item) -{ - g_free(item->needle); - g_free(item); -} - -static bool -locate_tag_search(const struct song *song, enum tag_type type, const char *str) -{ - bool ret = false; - - if (type == LOCATE_TAG_FILE_TYPE || (int)type == LOCATE_TAG_ANY_TYPE) { - char *uri = song_get_uri(song); - char *p = g_utf8_casefold(uri, -1); - g_free(uri); - - if (strstr(p, str)) - ret = true; - g_free(p); - if (ret == 1 || type == LOCATE_TAG_FILE_TYPE) - return ret; - } - - if (!song->tag) - return false; - - bool visited_types[TAG_NUM_OF_ITEM_TYPES]; - memset(visited_types, 0, sizeof(visited_types)); - - for (unsigned i = 0; i < song->tag->num_items && !ret; i++) { - visited_types[song->tag->items[i]->type] = true; - if ((int)type != LOCATE_TAG_ANY_TYPE && - song->tag->items[i]->type != type) { - continue; - } - - char *duplicate = g_utf8_casefold(song->tag->items[i]->value, -1); - if (*str && strstr(duplicate, str)) - ret = true; - g_free(duplicate); - } - - /** If the search critieron was not visited during the sweep - * through the song's tag, it means this field is absent from - * the tag or empty. Thus, if the searched string is also - * empty (first char is a \0), then it's a match as well and - * we should return true. - */ - if (!*str && !visited_types[type]) - return true; - - return ret; -} - -bool -locate_song_search(const struct song *song, - const struct locate_item_list *criteria) -{ - for (unsigned i = 0; i < criteria->length; i++) - if (!locate_tag_search(song, criteria->items[i].tag, - criteria->items[i].needle)) - return false; - - return true; -} - -static bool -locate_tag_match(const struct song *song, enum tag_type type, const char *str) -{ - if (type == LOCATE_TAG_FILE_TYPE || (int)type == LOCATE_TAG_ANY_TYPE) { - char *uri = song_get_uri(song); - bool matches = strcmp(str, uri) == 0; - g_free(uri); - - if (matches) - return true; - - if (type == LOCATE_TAG_FILE_TYPE) - return false; - } - - if (!song->tag) - return false; - - bool visited_types[TAG_NUM_OF_ITEM_TYPES]; - memset(visited_types, 0, sizeof(visited_types)); - - for (unsigned i = 0; i < song->tag->num_items; i++) { - visited_types[song->tag->items[i]->type] = true; - if ((int)type != LOCATE_TAG_ANY_TYPE && - song->tag->items[i]->type != type) { - continue; - } - - if (0 == strcmp(str, song->tag->items[i]->value)) - return true; - } - - /** If the search critieron was not visited during the sweep - * through the song's tag, it means this field is absent from - * the tag or empty. Thus, if the searched string is also - * empty (first char is a \0), then it's a match as well and - * we should return true. - */ - if (!*str && !visited_types[type]) - return true; - - return false; -} - -bool -locate_song_match(const struct song *song, - const struct locate_item_list *criteria) -{ - for (unsigned i = 0; i < criteria->length; i++) - if (!locate_tag_match(song, criteria->items[i].tag, - criteria->items[i].needle)) - return false; - - return true; -} diff --git a/src/locate.h b/src/locate.h deleted file mode 100644 index ec20ded24..000000000 --- a/src/locate.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_LOCATE_H -#define MPD_LOCATE_H - -#include "gcc.h" - -#include <stdint.h> -#include <stdbool.h> - -#define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10 -#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20 - -struct song; - -/* struct used for search, find, list queries */ -struct locate_item { - int8_t tag; - /* what we are looking for */ - char *needle; -}; - -/** - * An array of struct locate_item objects. - */ -struct locate_item_list { - /** number of items */ - unsigned length; - - /** this is a variable length array */ - struct locate_item items[1]; -}; - -int -locate_parse_type(const char *str); - -/** - * Allocates a new struct locate_item_list, and initializes all - * members with zero bytes. - */ -struct locate_item_list * -locate_item_list_new(unsigned length); - -/* return number of items or -1 on error */ -gcc_nonnull(1) -struct locate_item_list * -locate_item_list_parse(char *argv[], int argc); - -/** - * Duplicate the struct locate_item_list object and convert all - * needles with g_utf8_casefold(). - */ -gcc_nonnull(1) -struct locate_item_list * -locate_item_list_casefold(const struct locate_item_list *list); - -gcc_nonnull(1) -void -locate_item_list_free(struct locate_item_list *list); - -gcc_nonnull(1) -void -locate_item_free(struct locate_item *item); - -gcc_nonnull(1,2) -bool -locate_song_search(const struct song *song, - const struct locate_item_list *criteria); - -gcc_nonnull(1,2) -bool -locate_song_match(const struct song *song, - const struct locate_item_list *criteria); - -#endif diff --git a/src/log.c b/src/log.c deleted file mode 100644 index 2d3c7cafd..000000000 --- a/src/log.c +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "log.h" -#include "conf.h" -#include "utils.h" -#include "fd_util.h" -#include "mpd_error.h" - -#include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <string.h> -#include <stdarg.h> -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <time.h> -#include <unistd.h> -#include <errno.h> -#include <glib.h> - -#ifdef HAVE_SYSLOG -#include <syslog.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "log" - -#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO - -#define LOG_DATE_BUF_SIZE 16 -#define LOG_DATE_LEN (LOG_DATE_BUF_SIZE - 1) - -static GLogLevelFlags log_threshold = G_LOG_LEVEL_MESSAGE; - -static const char *log_charset; - -static bool stdout_mode = true; -static int out_fd; -static char *out_filename; - -static void redirect_logs(int fd) -{ - assert(fd >= 0); - if (dup2(fd, STDOUT_FILENO) < 0) - MPD_ERROR("problems dup2 stdout : %s\n", strerror(errno)); - if (dup2(fd, STDERR_FILENO) < 0) - MPD_ERROR("problems dup2 stderr : %s\n", strerror(errno)); -} - -static const char *log_date(void) -{ - static char buf[LOG_DATE_BUF_SIZE]; - time_t t = time(NULL); - strftime(buf, LOG_DATE_BUF_SIZE, "%b %d %H:%M : ", localtime(&t)); - return buf; -} - -/** - * Determines the length of the string excluding trailing whitespace - * characters. - */ -static int -chomp_length(const char *p) -{ - size_t length = strlen(p); - - while (length > 0 && g_ascii_isspace(p[length - 1])) - --length; - - return (int)length; -} - -static void -file_log_func(const gchar *log_domain, - GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - char *converted; - - if (log_level > log_threshold) - return; - - if (log_charset != NULL) { - converted = g_convert_with_fallback(message, -1, - log_charset, "utf-8", - NULL, NULL, NULL, NULL); - if (converted != NULL) - message = converted; - } else - converted = NULL; - - if (log_domain == NULL) - log_domain = ""; - - fprintf(stderr, "%s%s%s%.*s\n", - stdout_mode ? "" : log_date(), - log_domain, *log_domain == 0 ? "" : ": ", - chomp_length(message), message); - - g_free(converted); -} - -static void -log_init_stdout(void) -{ - g_log_set_default_handler(file_log_func, NULL); -} - -static int -open_log_file(void) -{ - assert(out_filename != NULL); - - return open_cloexec(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); -} - -static bool -log_init_file(unsigned line, GError **error_r) -{ - assert(out_filename != NULL); - - out_fd = open_log_file(); - if (out_fd < 0) { - g_set_error(error_r, log_quark(), errno, - "failed to open log file \"%s\" (config line %u): %s", - out_filename, line, g_strerror(errno)); - return false; - } - - g_log_set_default_handler(file_log_func, NULL); - return true; -} - -#ifdef HAVE_SYSLOG - -static int -glib_to_syslog_level(GLogLevelFlags log_level) -{ - switch (log_level & G_LOG_LEVEL_MASK) { - case G_LOG_LEVEL_ERROR: - case G_LOG_LEVEL_CRITICAL: - return LOG_ERR; - - case G_LOG_LEVEL_WARNING: - return LOG_WARNING; - - case G_LOG_LEVEL_MESSAGE: - return LOG_NOTICE; - - case G_LOG_LEVEL_INFO: - return LOG_INFO; - - case G_LOG_LEVEL_DEBUG: - return LOG_DEBUG; - - default: - return LOG_NOTICE; - } -} - -static void -syslog_log_func(const gchar *log_domain, - GLogLevelFlags log_level, const gchar *message, - G_GNUC_UNUSED gpointer user_data) -{ - if (stdout_mode) { - /* fall back to the file log function during - startup */ - file_log_func(log_domain, log_level, - message, user_data); - return; - } - - if (log_level > log_threshold) - return; - - if (log_domain == NULL) - log_domain = ""; - - syslog(glib_to_syslog_level(log_level), "%s%s%.*s", - log_domain, *log_domain == 0 ? "" : ": ", - chomp_length(message), message); -} - -static void -log_init_syslog(void) -{ - assert(out_filename == NULL); - - openlog(PACKAGE, 0, LOG_DAEMON); - g_log_set_default_handler(syslog_log_func, NULL); -} - -#endif - -static inline GLogLevelFlags -parse_log_level(const char *value, unsigned line) -{ - if (0 == strcmp(value, "default")) - return G_LOG_LEVEL_MESSAGE; - if (0 == strcmp(value, "secure")) - return LOG_LEVEL_SECURE; - else if (0 == strcmp(value, "verbose")) - return G_LOG_LEVEL_DEBUG; - else { - MPD_ERROR("unknown log level \"%s\" at line %u\n", - value, line); - return G_LOG_LEVEL_MESSAGE; - } -} - -void -log_early_init(bool verbose) -{ - if (verbose) - log_threshold = G_LOG_LEVEL_DEBUG; - - log_init_stdout(); -} - -bool -log_init(bool verbose, bool use_stdout, GError **error_r) -{ - const struct config_param *param; - - g_get_charset(&log_charset); - - if (verbose) - log_threshold = G_LOG_LEVEL_DEBUG; - else if ((param = config_get_param(CONF_LOG_LEVEL)) != NULL) - log_threshold = parse_log_level(param->value, param->line); - - if (use_stdout) { - log_init_stdout(); - return true; - } else { - param = config_get_param(CONF_LOG_FILE); - if (param == NULL) { -#ifdef HAVE_SYSLOG - /* no configuration: default to syslog (if - available) */ - log_init_syslog(); - return true; -#else - g_set_error(error_r, log_quark(), 0, - "config parameter \"%s\" not found", - CONF_LOG_FILE); - return false; -#endif -#ifdef HAVE_SYSLOG - } else if (strcmp(param->value, "syslog") == 0) { - log_init_syslog(); - return true; -#endif - } else { - out_filename = config_dup_path(CONF_LOG_FILE, error_r); - return out_filename != NULL && - log_init_file(param->line, error_r); - } - } -} - -static void -close_log_files(void) -{ - if (stdout_mode) - return; - -#ifdef HAVE_SYSLOG - if (out_filename == NULL) - closelog(); -#endif -} - -void -log_deinit(void) -{ - close_log_files(); - g_free(out_filename); -} - - -void setup_log_output(bool use_stdout) -{ - fflush(NULL); - if (!use_stdout) { -#ifndef WIN32 - if (out_filename == NULL) - out_fd = open("/dev/null", O_WRONLY); -#endif - - if (out_fd >= 0) { - redirect_logs(out_fd); - close(out_fd); - } - - stdout_mode = false; - log_charset = NULL; - } -} - -int cycle_log_files(void) -{ - int fd; - - if (stdout_mode || out_filename == NULL) - return 0; - assert(out_filename); - - g_debug("Cycling log files...\n"); - close_log_files(); - - fd = open_log_file(); - if (fd < 0) { - g_warning("error re-opening log file: %s\n", out_filename); - return -1; - } - - redirect_logs(fd); - g_debug("Done cycling log files\n"); - return 0; -} diff --git a/src/log.h b/src/log.h deleted file mode 100644 index 683ff3e9f..000000000 --- a/src/log.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_LOG_H -#define MPD_LOG_H - -#include <glib.h> -#include <stdbool.h> - -G_GNUC_CONST -static inline GQuark -log_quark(void) -{ - return g_quark_from_static_string("log"); -} - -/** - * Configure a logging destination for daemon startup, before the - * configuration file is read. This allows the daemon to use the - * logging library (and the command line verbose level) before it's - * daemonized. - * - * @param verbose true when the program is started with --verbose - */ -void -log_early_init(bool verbose); - -bool -log_init(bool verbose, bool use_stdout, GError **error_r); - -void -log_deinit(void); - -void setup_log_output(bool use_stdout); - -int cycle_log_files(void); - -#endif /* LOG_H */ diff --git a/src/ls.c b/src/ls.c deleted file mode 100644 index 310c2d7b6..000000000 --- a/src/ls.c +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "ls.h" -#include "uri.h" -#include "client.h" - -#include <assert.h> -#include <string.h> - - -/** - * file:// is not included in remoteUrlPrefixes, the connection method - * is detected at runtime and displayed as a urlhandler if the client is - * connected by IPC socket. - */ -static const char *remoteUrlPrefixes[] = { -#if defined(ENABLE_CURL) || defined(ENABLE_SOUP) - "http://", -#endif -#ifdef ENABLE_MMS - "mms://", - "mmsh://", - "mmst://", - "mmsu://", -#endif -#ifdef HAVE_FFMPEG - "gopher://", - "rtp://", - "rtsp://", - "rtmp://", - "rtmpt://", - "rtmps://", -#endif -#ifdef ENABLE_CDIO_PARANOIA - "cdda://", -#endif -#ifdef ENABLE_DESPOTIFY - "spt://", -#endif - NULL -}; - -void print_supported_uri_schemes_to_fp(FILE *fp) -{ - const char **prefixes = remoteUrlPrefixes; - -#ifdef HAVE_UN - fprintf(fp, " file://"); -#endif - while (*prefixes) { - fprintf(fp, " %s", *prefixes); - prefixes++; - } - fprintf(fp,"\n"); -} - -void print_supported_uri_schemes(struct client *client) -{ - const char **prefixes = remoteUrlPrefixes; - - while (*prefixes) { - client_printf(client, "handler: %s\n", *prefixes); - prefixes++; - } -} - -bool uri_supported_scheme(const char *uri) -{ - const char **urlPrefixes = remoteUrlPrefixes; - - assert(uri_has_scheme(uri)); - - while (*urlPrefixes) { - if (g_str_has_prefix(uri, *urlPrefixes)) - return true; - urlPrefixes++; - } - - return false; -} diff --git a/src/ls.cxx b/src/ls.cxx new file mode 100644 index 000000000..9bb56c898 --- /dev/null +++ b/src/ls.cxx @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ls.hxx" + +extern "C" { +#include "uri.h" +} + +#include "Client.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + + +/** + * file:// is not included in remoteUrlPrefixes, the connection method + * is detected at runtime and displayed as a urlhandler if the client is + * connected by IPC socket. + */ +static const char *remoteUrlPrefixes[] = { +#if defined(ENABLE_CURL) || defined(ENABLE_SOUP) + "http://", +#endif +#ifdef ENABLE_MMS + "mms://", + "mmsh://", + "mmst://", + "mmsu://", +#endif +#ifdef HAVE_FFMPEG + "gopher://", + "rtp://", + "rtsp://", + "rtmp://", + "rtmpt://", + "rtmps://", +#endif +#ifdef ENABLE_CDIO_PARANOIA + "cdda://", +#endif +#ifdef ENABLE_DESPOTIFY + "spt://", +#endif + NULL +}; + +void print_supported_uri_schemes_to_fp(FILE *fp) +{ + const char **prefixes = remoteUrlPrefixes; + +#ifdef HAVE_UN + fprintf(fp, " file://"); +#endif + while (*prefixes) { + fprintf(fp, " %s", *prefixes); + prefixes++; + } + fprintf(fp,"\n"); +} + +void print_supported_uri_schemes(Client *client) +{ + const char **prefixes = remoteUrlPrefixes; + + while (*prefixes) { + client_printf(client, "handler: %s\n", *prefixes); + prefixes++; + } +} + +bool uri_supported_scheme(const char *uri) +{ + const char **urlPrefixes = remoteUrlPrefixes; + + assert(uri_has_scheme(uri)); + + while (*urlPrefixes) { + if (g_str_has_prefix(uri, *urlPrefixes)) + return true; + urlPrefixes++; + } + + return false; +} diff --git a/src/ls.h b/src/ls.h deleted file mode 100644 index 15cb01160..000000000 --- a/src/ls.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_LS_H -#define MPD_LS_H - -#include <stdbool.h> -#include <stdio.h> - -struct client; - -/** - * Checks whether the scheme of the specified URI is supported by MPD. - * It is not allowed to pass an URI without a scheme, check with - * uri_has_scheme() first. - */ -bool uri_supported_scheme(const char *url); - -/** - * Send a list of supported URI schemes to the client. This is the - * response to the "urlhandlers" command. - */ -void print_supported_uri_schemes(struct client *client); - -/** - * Send a list of supported URI schemes to a file pointer. - */ -void print_supported_uri_schemes_to_fp(FILE *fp); - -#endif diff --git a/src/ls.hxx b/src/ls.hxx new file mode 100644 index 000000000..8ae5a58fd --- /dev/null +++ b/src/ls.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_LS_HXX +#define MPD_LS_HXX + +#include <stdio.h> + +class Client; + +/** + * Checks whether the scheme of the specified URI is supported by MPD. + * It is not allowed to pass an URI without a scheme, check with + * uri_has_scheme() first. + */ +bool uri_supported_scheme(const char *url); + +/** + * Send a list of supported URI schemes to the client. This is the + * response to the "urlhandlers" command. + */ +void print_supported_uri_schemes(Client *client); + +/** + * Send a list of supported URI schemes to a file pointer. + */ +void print_supported_uri_schemes_to_fp(FILE *fp); + +#endif diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 12f8d86f6..000000000 --- a/src/main.c +++ /dev/null @@ -1,541 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "main.h" -#include "daemon.h" -#include "io_thread.h" -#include "client.h" -#include "client_idle.h" -#include "idle.h" -#include "command.h" -#include "playlist.h" -#include "stored_playlist.h" -#include "database.h" -#include "update.h" -#include "player_thread.h" -#include "listen.h" -#include "cmdline.h" -#include "conf.h" -#include "path.h" -#include "mapper.h" -#include "chunk.h" -#include "player_control.h" -#include "stats.h" -#include "sig_handlers.h" -#include "audio_config.h" -#include "output_all.h" -#include "volume.h" -#include "log.h" -#include "permission.h" -#include "pcm_resample.h" -#include "replay_gain_config.h" -#include "decoder_list.h" -#include "input_init.h" -#include "playlist_list.h" -#include "state_file.h" -#include "tag.h" -#include "dbUtils.h" -#include "zeroconf.h" -#include "event_pipe.h" -#include "tag_pool.h" -#include "mpd_error.h" - -#ifdef ENABLE_INOTIFY -#include "inotify_update.h" -#endif - -#ifdef ENABLE_SQLITE -#include "sticker.h" -#endif - -#ifdef ENABLE_ARCHIVE -#include "archive_list.h" -#endif - -#include <glib.h> - -#include <unistd.h> -#include <stdlib.h> -#include <errno.h> -#include <string.h> - -#ifdef HAVE_LOCALE_H -#include <locale.h> -#endif - -#ifdef WIN32 -#include <winsock2.h> -#include <ws2tcpip.h> -#endif - -enum { - DEFAULT_BUFFER_SIZE = 2048, - DEFAULT_BUFFER_BEFORE_PLAY = 10, -}; - -GThread *main_task; -GMainLoop *main_loop; - -GCond *main_cond; - -struct player_control *global_player_control; - -static bool -glue_daemonize_init(const struct options *options, GError **error_r) -{ - GError *error = NULL; - - char *pid_file = config_dup_path(CONF_PID_FILE, &error); - if (pid_file == NULL && error != NULL) { - g_propagate_error(error_r, error); - return false; - } - - daemonize_init(config_get_string(CONF_USER, NULL), - config_get_string(CONF_GROUP, NULL), - pid_file); - g_free(pid_file); - - if (options->kill) - daemonize_kill(); - - return true; -} - -static bool -glue_mapper_init(GError **error_r) -{ - GError *error = NULL; - char *music_dir = config_dup_path(CONF_MUSIC_DIR, &error); - if (music_dir == NULL && error != NULL) { - g_propagate_error(error_r, error); - return false; - } - - char *playlist_dir = config_dup_path(CONF_PLAYLIST_DIR, &error); - if (playlist_dir == NULL && error != NULL) { - g_propagate_error(error_r, error); - return false; - } - - if (music_dir == NULL) - music_dir = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC)); - - mapper_init(music_dir, playlist_dir); - - g_free(music_dir); - g_free(playlist_dir); - return true; -} - -/** - * Returns the database. If this function returns false, this has not - * succeeded, and the caller should create the database after the - * process has been daemonized. - */ -static bool -glue_db_init_and_load(void) -{ - const struct config_param *path = config_get_param(CONF_DB_FILE); - - GError *error = NULL; - bool ret; - - if (!mapper_has_music_directory()) { - if (path != NULL) - g_message("Found " CONF_DB_FILE " setting without " - CONF_MUSIC_DIR " - disabling database"); - db_init(NULL, NULL); - return true; - } - - if (path == NULL) - MPD_ERROR(CONF_DB_FILE " setting missing"); - - if (!db_init(path, &error)) - MPD_ERROR("%s", error->message); - - ret = db_load(&error); - if (!ret) - MPD_ERROR("%s", error->message); - - /* run database update after daemonization? */ - return db_exists(); -} - -/** - * Configure and initialize the sticker subsystem. - */ -static void -glue_sticker_init(void) -{ -#ifdef ENABLE_SQLITE - GError *error = NULL; - char *sticker_file = config_dup_path(CONF_STICKER_FILE, &error); - if (sticker_file == NULL && error != NULL) - MPD_ERROR("%s", error->message); - - if (!sticker_global_init(sticker_file, &error)) - MPD_ERROR("%s", error->message); - - g_free(sticker_file); -#endif -} - -static bool -glue_state_file_init(GError **error_r) -{ - GError *error = NULL; - - char *path = config_dup_path(CONF_STATE_FILE, &error); - if (path == NULL && error != NULL) { - g_propagate_error(error_r, error); - return false; - } - - state_file_init(path, global_player_control); - g_free(path); - - return true; -} - -/** - * Windows-only initialization of the Winsock2 library. - */ -static void winsock_init(void) -{ -#ifdef WIN32 - WSADATA sockinfo; - int retval; - - retval = WSAStartup(MAKEWORD(2, 2), &sockinfo); - if(retval != 0) - { - MPD_ERROR("Attempt to open Winsock2 failed; error code %d\n", - retval); - } - - if (LOBYTE(sockinfo.wVersion) != 2) - { - MPD_ERROR("We use Winsock2 but your version is either too new " - "or old; please install Winsock 2.x\n"); - } -#endif -} - -/** - * Initialize the decoder and player core, including the music pipe. - */ -static void -initialize_decoder_and_player(void) -{ - const struct config_param *param; - char *test; - size_t buffer_size; - float perc; - unsigned buffered_chunks; - unsigned buffered_before_play; - - param = config_get_param(CONF_AUDIO_BUFFER_SIZE); - if (param != NULL) { - long tmp = strtol(param->value, &test, 10); - if (*test != '\0' || tmp <= 0 || tmp == LONG_MAX) - MPD_ERROR("buffer size \"%s\" is not a positive integer, " - "line %i\n", param->value, param->line); - buffer_size = tmp; - } else - buffer_size = DEFAULT_BUFFER_SIZE; - - buffer_size *= 1024; - - buffered_chunks = buffer_size / CHUNK_SIZE; - - if (buffered_chunks >= 1 << 15) - MPD_ERROR("buffer size \"%li\" is too big\n", (long)buffer_size); - - param = config_get_param(CONF_BUFFER_BEFORE_PLAY); - if (param != NULL) { - perc = strtod(param->value, &test); - if (*test != '%' || perc < 0 || perc > 100) { - MPD_ERROR("buffered before play \"%s\" is not a positive " - "percentage and less than 100 percent, line %i", - param->value, param->line); - } - } else - perc = DEFAULT_BUFFER_BEFORE_PLAY; - - buffered_before_play = (perc / 100) * buffered_chunks; - if (buffered_before_play > buffered_chunks) - buffered_before_play = buffered_chunks; - - global_player_control = pc_new(buffered_chunks, buffered_before_play); -} - -/** - * event_pipe callback function for PIPE_EVENT_IDLE - */ -static void -idle_event_emitted(void) -{ - /* send "idle" notificaions to all subscribed - clients */ - unsigned flags = idle_get(); - if (flags != 0) - client_manager_idle_add(flags); -} - -/** - * event_pipe callback function for PIPE_EVENT_SHUTDOWN - */ -static void -shutdown_event_emitted(void) -{ - g_main_loop_quit(main_loop); -} - -int main(int argc, char *argv[]) -{ -#ifdef WIN32 - return win32_main(argc, argv); -#else - return mpd_main(argc, argv); -#endif -} - -int mpd_main(int argc, char *argv[]) -{ - struct options options; - clock_t start; - bool create_db; - GError *error = NULL; - bool success; - - daemonize_close_stdin(); - -#ifdef HAVE_LOCALE_H - /* initialize locale */ - setlocale(LC_CTYPE,""); -#endif - - g_set_application_name("Music Player Daemon"); - - /* enable GLib's thread safety code */ - g_thread_init(NULL); - - io_thread_init(); - winsock_init(); - idle_init(); - tag_pool_init(); - config_global_init(); - - success = parse_cmdline(argc, argv, &options, &error); - if (!success) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - if (!glue_daemonize_init(&options, &error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - stats_global_init(); - tag_lib_init(); - - if (!log_init(options.verbose, options.log_stderr, &error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - success = listen_global_init(&error); - if (!success) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - daemonize_set_user(); - - main_task = g_thread_self(); - main_loop = g_main_loop_new(NULL, FALSE); - main_cond = g_cond_new(); - - event_pipe_init(); - event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted); - event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_event_emitted); - - path_global_init(); - - if (!glue_mapper_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - initPermissions(); - playlist_global_init(); - spl_global_init(); -#ifdef ENABLE_ARCHIVE - archive_plugin_init_all(); -#endif - - if (!pcm_resample_global_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - decoder_plugin_init_all(); - update_global_init(); - - create_db = !glue_db_init_and_load(); - - glue_sticker_init(); - - command_init(); - initialize_decoder_and_player(); - volume_init(); - initAudioConfig(); - audio_output_all_init(global_player_control); - client_manager_init(); - replay_gain_global_init(); - - if (!input_stream_global_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - playlist_list_global_init(); - - daemonize(options.daemon); - - setup_log_output(options.log_stderr); - - initSigHandlers(); - - if (!io_thread_start(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - initZeroconf(); - - player_create(global_player_control); - - if (create_db) { - /* the database failed to load: recreate the - database */ - unsigned job = update_enqueue(NULL, true); - if (job == 0) - MPD_ERROR("directory update failed"); - } - - if (!glue_state_file_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - success = config_get_bool(CONF_AUTO_UPDATE, false); -#ifdef ENABLE_INOTIFY - if (success && mapper_has_music_directory()) - mpd_inotify_init(config_get_unsigned(CONF_AUTO_UPDATE_DEPTH, - G_MAXUINT)); -#else - if (success) - g_warning("inotify: auto_update was disabled. enable during compilation phase"); -#endif - - config_global_check(); - - /* enable all audio outputs (if not already done by - playlist_state_restore() */ - pc_update_audio(global_player_control); - -#ifdef WIN32 - win32_app_started(); -#endif - - /* run the main loop */ - g_main_loop_run(main_loop); - -#ifdef WIN32 - win32_app_stopping(); -#endif - - /* cleanup */ - - g_main_loop_unref(main_loop); - -#ifdef ENABLE_INOTIFY - mpd_inotify_finish(); -#endif - - state_file_finish(global_player_control); - pc_kill(global_player_control); - finishZeroconf(); - client_manager_deinit(); - listen_global_finish(); - playlist_global_finish(); - - start = clock(); - db_finish(); - g_debug("db_finish took %f seconds", - ((float)(clock()-start))/CLOCKS_PER_SEC); - -#ifdef ENABLE_SQLITE - sticker_global_finish(); -#endif - - g_cond_free(main_cond); - event_pipe_deinit(); - - playlist_list_global_finish(); - input_stream_global_finish(); - audio_output_all_finish(); - volume_finish(); - mapper_finish(); - path_global_finish(); - finishPermissions(); - pc_free(global_player_control); - command_finish(); - update_global_finish(); - decoder_plugin_deinit_all(); -#ifdef ENABLE_ARCHIVE - archive_plugin_deinit_all(); -#endif - config_global_finish(); - tag_pool_deinit(); - idle_deinit(); - stats_global_finish(); - io_thread_deinit(); - daemonize_finish(); -#ifdef WIN32 - WSACleanup(); -#endif - - log_deinit(); - return EXIT_SUCCESS; -} diff --git a/src/main.h b/src/main.h deleted file mode 100644 index 2a7d75910..000000000 --- a/src/main.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MAIN_H -#define MAIN_H - -#include <glib.h> - -extern GThread *main_task; - -extern GMainLoop *main_loop; - -extern GCond *main_cond; - -extern struct player_control *global_player_control; - -/** - * A entry point for application. - * On non-Windows platforms this is called directly from main() - * On Windows platform this is called from win32_main() - * after doing some initialization. - */ -int mpd_main(int argc, char *argv[]); - -#ifdef WIN32 - -/** - * If program is run as windows service performs nessesary initialization - * and then calls mpd_main() with specified arguments. - * If program is run as a regular application calls mpd_main() immediately. - */ -int -win32_main(int argc, char *argv[]); - -/** - * When running as a service reports to service control manager - * that our service is started. - * When running as a console application enables console handler that will - * trigger PIPE_EVENT_SHUTDOWN when user closes console window - * or presses Ctrl+C. - * This function should be called just before entering main loop. - */ -void -win32_app_started(void); - -/** - * When running as a service reports to service control manager - * that our service is about to stop. - * When running as a console application enables console handler that will - * catch all shutdown requests and ignore them. - * This function should be called just after leaving main loop. - */ -void -win32_app_stopping(void); - -#endif - -#endif diff --git a/src/main_win32.c b/src/main_win32.c deleted file mode 100644 index aac7ad886..000000000 --- a/src/main_win32.c +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "main.h" - -#ifdef WIN32 - -#include "mpd_error.h" -#include "event_pipe.h" - -#include <glib.h> - -#include <windows.h> - -static int service_argc; -static char **service_argv; -static char service_name[] = ""; -static BOOL ignore_console_events; -static SERVICE_STATUS_HANDLE service_handle; - -static void WINAPI -service_main(DWORD argc, CHAR *argv[]); - -static SERVICE_TABLE_ENTRY service_registry[] = { - {service_name, service_main}, - {NULL, NULL} -}; - -static void -service_notify_status(DWORD status_code) -{ - SERVICE_STATUS current_status; - - current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; - current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING - ? 0 - : SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP; - - current_status.dwCurrentState = status_code; - current_status.dwWin32ExitCode = NO_ERROR; - current_status.dwCheckPoint = 0; - current_status.dwWaitHint = 1000; - - SetServiceStatus(service_handle, ¤t_status); -} - -static DWORD WINAPI -service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type, - G_GNUC_UNUSED void *event_data, G_GNUC_UNUSED void *context) -{ - switch (control) { - case SERVICE_CONTROL_SHUTDOWN: - case SERVICE_CONTROL_STOP: - event_pipe_emit(PIPE_EVENT_SHUTDOWN); - return NO_ERROR; - default: - return NO_ERROR; - } -} - -static void WINAPI -service_main(G_GNUC_UNUSED DWORD argc, G_GNUC_UNUSED CHAR *argv[]) -{ - DWORD error_code; - gchar* error_message; - - service_handle = - RegisterServiceCtrlHandlerEx(service_name, - service_dispatcher, NULL); - - if (service_handle == 0) { - error_code = GetLastError(); - error_message = g_win32_error_message(error_code); - MPD_ERROR("RegisterServiceCtrlHandlerEx() failed: %s", - error_message); - } - - service_notify_status(SERVICE_START_PENDING); - mpd_main(service_argc, service_argv); - service_notify_status(SERVICE_STOPPED); -} - -static BOOL WINAPI -console_handler(DWORD event) -{ - switch (event) { - case CTRL_C_EVENT: - case CTRL_CLOSE_EVENT: - if (!ignore_console_events) - event_pipe_emit(PIPE_EVENT_SHUTDOWN); - return TRUE; - default: - return FALSE; - } -} - -int win32_main(int argc, char *argv[]) -{ - DWORD error_code; - gchar* error_message; - - service_argc = argc; - service_argv = argv; - - if (StartServiceCtrlDispatcher(service_registry)) - return 0; /* run as service successefully */ - - error_code = GetLastError(); - if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) { - /* running as console app */ - SetConsoleTitle("Music Player Daemon"); - ignore_console_events = TRUE; - SetConsoleCtrlHandler(console_handler, TRUE); - return mpd_main(argc, argv); - } - - error_message = g_win32_error_message(error_code); - MPD_ERROR("StartServiceCtrlDispatcher() failed: %s", error_message); -} - -void win32_app_started() -{ - if (service_handle != 0) - service_notify_status(SERVICE_RUNNING); - else - ignore_console_events = FALSE; -} - -void win32_app_stopping() -{ - if (service_handle != 0) - service_notify_status(SERVICE_STOP_PENDING); - else - ignore_console_events = TRUE; -} - -#endif diff --git a/src/mapper.c b/src/mapper.c deleted file mode 100644 index 7db74b1af..000000000 --- a/src/mapper.c +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Maps directory and song objects to file system paths. - */ - -#include "config.h" -#include "mapper.h" -#include "directory.h" -#include "song.h" -#include "path.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <dirent.h> - -/** - * The absolute path of the music directory encoded in UTF-8. - */ -static char *music_dir_utf8; -static size_t music_dir_utf8_length; - -/** - * The absolute path of the music directory encoded in the filesystem - * character set. - */ -static char *music_dir_fs; -static size_t music_dir_fs_length; - -/** - * The absolute path of the playlist directory encoded in the - * filesystem character set. - */ -static char *playlist_dir_fs; - -/** - * Duplicate a string, chop all trailing slashes. - */ -static char * -strdup_chop_slash(const char *path_fs) -{ - size_t length = strlen(path_fs); - - while (length > 0 && path_fs[length - 1] == G_DIR_SEPARATOR) - --length; - - return g_strndup(path_fs, length); -} - -static void -check_directory(const char *path) -{ - struct stat st; - if (stat(path, &st) < 0) { - g_warning("Failed to stat directory \"%s\": %s", - path, g_strerror(errno)); - return; - } - - if (!S_ISDIR(st.st_mode)) { - g_warning("Not a directory: %s", path); - return; - } - -#ifndef WIN32 - char *x = g_build_filename(path, ".", NULL); - if (stat(x, &st) < 0 && errno == EACCES) - g_warning("No permission to traverse (\"execute\") directory: %s", - path); - g_free(x); -#endif - - DIR *dir = opendir(path); - if (dir != NULL) - closedir(dir); - else if (errno == EACCES) - g_warning("No permission to read directory: %s", path); -} - -static void -mapper_set_music_dir(const char *path_utf8) -{ - music_dir_utf8 = strdup_chop_slash(path_utf8); - music_dir_utf8_length = strlen(music_dir_utf8); - - music_dir_fs = utf8_to_fs_charset(music_dir_utf8); - check_directory(music_dir_fs); - music_dir_fs_length = strlen(music_dir_fs); -} - -static void -mapper_set_playlist_dir(const char *path_utf8) -{ - playlist_dir_fs = utf8_to_fs_charset(path_utf8); - check_directory(playlist_dir_fs); -} - -void mapper_init(const char *_music_dir, const char *_playlist_dir) -{ - if (_music_dir != NULL) - mapper_set_music_dir(_music_dir); - - if (_playlist_dir != NULL) - mapper_set_playlist_dir(_playlist_dir); -} - -void mapper_finish(void) -{ - g_free(music_dir_utf8); - g_free(music_dir_fs); - g_free(playlist_dir_fs); -} - -const char * -mapper_get_music_directory_utf8(void) -{ - return music_dir_utf8; -} - -const char * -mapper_get_music_directory_fs(void) -{ - return music_dir_fs; -} - -const char * -map_to_relative_path(const char *path_utf8) -{ - return music_dir_utf8 != NULL && - memcmp(path_utf8, music_dir_utf8, - music_dir_utf8_length) == 0 && - G_IS_DIR_SEPARATOR(path_utf8[music_dir_utf8_length]) - ? path_utf8 + music_dir_utf8_length + 1 - : path_utf8; -} - -char * -map_uri_fs(const char *uri) -{ - char *uri_fs, *path_fs; - - assert(uri != NULL); - assert(*uri != '/'); - - if (music_dir_fs == NULL) - return NULL; - - uri_fs = utf8_to_fs_charset(uri); - if (uri_fs == NULL) - return NULL; - - path_fs = g_build_filename(music_dir_fs, uri_fs, NULL); - g_free(uri_fs); - - return path_fs; -} - -char * -map_directory_fs(const struct directory *directory) -{ - assert(music_dir_utf8 != NULL); - assert(music_dir_fs != NULL); - - if (directory_is_root(directory)) - return g_strdup(music_dir_fs); - - return map_uri_fs(directory_get_path(directory)); -} - -char * -map_directory_child_fs(const struct directory *directory, const char *name) -{ - assert(music_dir_utf8 != NULL); - assert(music_dir_fs != NULL); - - char *name_fs, *parent_fs, *path; - - /* check for invalid or unauthorized base names */ - if (*name == 0 || strchr(name, '/') != NULL || - strcmp(name, ".") == 0 || strcmp(name, "..") == 0) - return NULL; - - parent_fs = map_directory_fs(directory); - if (parent_fs == NULL) - return NULL; - - name_fs = utf8_to_fs_charset(name); - if (name_fs == NULL) { - g_free(parent_fs); - return NULL; - } - - path = g_build_filename(parent_fs, name_fs, NULL); - g_free(parent_fs); - g_free(name_fs); - - return path; -} - -char * -map_song_fs(const struct song *song) -{ - assert(song_is_file(song)); - - if (song_in_database(song)) - return map_directory_child_fs(song->parent, song->uri); - else - return utf8_to_fs_charset(song->uri); -} - -char * -map_fs_to_utf8(const char *path_fs) -{ - if (music_dir_fs != NULL && - strncmp(path_fs, music_dir_fs, music_dir_fs_length) == 0 && - G_IS_DIR_SEPARATOR(path_fs[music_dir_fs_length])) - /* remove musicDir prefix */ - path_fs += music_dir_fs_length + 1; - else if (G_IS_DIR_SEPARATOR(path_fs[0])) - /* not within musicDir */ - return NULL; - - while (path_fs[0] == G_DIR_SEPARATOR) - ++path_fs; - - return fs_charset_to_utf8(path_fs); -} - -const char * -map_spl_path(void) -{ - return playlist_dir_fs; -} - -char * -map_spl_utf8_to_fs(const char *name) -{ - char *filename_utf8, *filename_fs, *path; - - if (playlist_dir_fs == NULL) - return NULL; - - filename_utf8 = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL); - filename_fs = utf8_to_fs_charset(filename_utf8); - g_free(filename_utf8); - if (filename_fs == NULL) - return NULL; - - path = g_build_filename(playlist_dir_fs, filename_fs, NULL); - g_free(filename_fs); - - return path; -} diff --git a/src/mapper.h b/src/mapper.h deleted file mode 100644 index d6184a175..000000000 --- a/src/mapper.h +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Maps directory and song objects to file system paths. - */ - -#ifndef MPD_MAPPER_H -#define MPD_MAPPER_H - -#include <glib.h> -#include <stdbool.h> - -#define PLAYLIST_FILE_SUFFIX ".m3u" - -struct directory; -struct song; - -void mapper_init(const char *_music_dir, const char *_playlist_dir); - -void mapper_finish(void); - -/** - * Return the absolute path of the music directory encoded in UTF-8. - */ -G_GNUC_CONST -const char * -mapper_get_music_directory_utf8(void); - -/** - * Return the absolute path of the music directory encoded in the - * filesystem character set. - */ -G_GNUC_CONST -const char * -mapper_get_music_directory_fs(void); - -/** - * Returns true if a music directory was configured. - */ -G_GNUC_CONST -static inline bool -mapper_has_music_directory(void) -{ - return mapper_get_music_directory_utf8() != NULL; -} - -/** - * If the specified absolute path points inside the music directory, - * this function converts it to a relative path. If not, it returns - * the unmodified string pointer. - */ -G_GNUC_PURE -const char * -map_to_relative_path(const char *path_utf8); - -/** - * Determines the absolute file system path of a relative URI. This - * is basically done by converting the URI to the file system charset - * and prepending the music directory. - */ -G_GNUC_MALLOC -char * -map_uri_fs(const char *uri); - -/** - * Determines the file system path of a directory object. - * - * @param directory the directory object - * @return the path in file system encoding, or NULL if mapping failed - */ -G_GNUC_MALLOC -char * -map_directory_fs(const struct directory *directory); - -/** - * Determines the file system path of a directory's child (may be a - * sub directory or a song). - * - * @param directory the parent directory object - * @param name the child's name in UTF-8 - * @return the path in file system encoding, or NULL if mapping failed - */ -G_GNUC_MALLOC -char * -map_directory_child_fs(const struct directory *directory, const char *name); - -/** - * Determines the file system path of a song. This must not be a - * remote song. - * - * @param song the song object - * @return the path in file system encoding, or NULL if mapping failed - */ -G_GNUC_MALLOC -char * -map_song_fs(const struct song *song); - -/** - * Maps a file system path (relative to the music directory or - * absolute) to a relative path in UTF-8 encoding. - * - * @param path_fs a path in file system encoding - * @return the relative path in UTF-8, or NULL if mapping failed - */ -G_GNUC_MALLOC -char * -map_fs_to_utf8(const char *path_fs); - -/** - * Returns the playlist directory. - */ -G_GNUC_CONST -const char * -map_spl_path(void); - -/** - * Maps a playlist name (without the ".m3u" suffix) to a file system - * path. The return value is allocated on the heap and must be freed - * with g_free(). - * - * @return the path in file system encoding, or NULL if mapping failed - */ -G_GNUC_PURE -char * -map_spl_utf8_to_fs(const char *name); - -#endif diff --git a/src/mixer/AlsaMixerPlugin.cxx b/src/mixer/AlsaMixerPlugin.cxx new file mode 100644 index 000000000..17f8b9a6f --- /dev/null +++ b/src/mixer/AlsaMixerPlugin.cxx @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "mixer_api.h" +#include "output_api.h" +#include "GlobalEvents.hxx" +#include "Main.hxx" +#include "event/MultiSocketMonitor.hxx" + +#include <algorithm> + +#include <glib.h> +#include <alsa/asoundlib.h> + +#define VOLUME_MIXER_ALSA_DEFAULT "default" +#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" +#define VOLUME_MIXER_ALSA_INDEX_DEFAULT 0 + +class AlsaMixerMonitor final : private MultiSocketMonitor { + snd_mixer_t *const mixer; + +public: + AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer) + :MultiSocketMonitor(_loop), mixer(_mixer) {} + +private: + virtual void PrepareSockets(gcc_unused gint *timeout_r) override; + virtual void DispatchSockets() override; +}; + +struct alsa_mixer { + /** the base mixer class */ + struct mixer base; + + const char *device; + const char *control; + unsigned int index; + + snd_mixer_t *handle; + snd_mixer_elem_t *elem; + long volume_min; + long volume_max; + int volume_set; + + AlsaMixerMonitor *monitor; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +alsa_mixer_quark(void) +{ + return g_quark_from_static_string("alsa_mixer"); +} + +void +AlsaMixerMonitor::PrepareSockets(gcc_unused gint *timeout_r) +{ + int count = snd_mixer_poll_descriptors_count(mixer); + if (count < 0) + count = 0; + + struct pollfd *pfds = g_new(struct pollfd, count); + count = snd_mixer_poll_descriptors(mixer, pfds, count); + if (count < 0) + count = 0; + + struct pollfd *end = pfds + count; + + UpdateSocketList([pfds, end](int fd) -> unsigned { + auto i = std::find_if(pfds, end, [fd](const struct pollfd &pfd){ + return pfd.fd == fd; + }); + if (i == end) + return 0; + + auto events = i->events; + i->events = 0; + return events; + }); + + for (auto i = pfds; i != end; ++i) + if (i->events != 0) + AddSocket(i->fd, i->events); + + g_free(pfds); +} + +void +AlsaMixerMonitor::DispatchSockets() +{ + snd_mixer_handle_events(mixer); +} + +/* + * libasound callbacks + * + */ + +static int +alsa_mixer_elem_callback(G_GNUC_UNUSED snd_mixer_elem_t *elem, unsigned mask) +{ + if (mask & SND_CTL_EVENT_MASK_VALUE) + GlobalEvents::Emit(GlobalEvents::MIXER); + + return 0; +} + +/* + * mixer_plugin methods + * + */ + +static struct mixer * +alsa_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct alsa_mixer *am = g_new(struct alsa_mixer, 1); + + mixer_init(&am->base, &alsa_mixer_plugin); + + am->device = config_get_block_string(param, "mixer_device", + VOLUME_MIXER_ALSA_DEFAULT); + am->control = config_get_block_string(param, "mixer_control", + VOLUME_MIXER_ALSA_CONTROL_DEFAULT); + am->index = config_get_block_unsigned(param, "mixer_index", + VOLUME_MIXER_ALSA_INDEX_DEFAULT); + + return &am->base; +} + +static void +alsa_mixer_finish(struct mixer *data) +{ + struct alsa_mixer *am = (struct alsa_mixer *)data; + + g_free(am); + + /* free libasound's config cache */ + snd_config_update_free_global(); +} + +G_GNUC_PURE +static snd_mixer_elem_t * +alsa_mixer_lookup_elem(snd_mixer_t *handle, const char *name, unsigned idx) +{ + for (snd_mixer_elem_t *elem = snd_mixer_first_elem(handle); + elem != NULL; elem = snd_mixer_elem_next(elem)) { + if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE && + g_ascii_strcasecmp(snd_mixer_selem_get_name(elem), + name) == 0 && + snd_mixer_selem_get_index(elem) == idx) + return elem; + } + + return NULL; +} + +static bool +alsa_mixer_setup(struct alsa_mixer *am, GError **error_r) +{ + int err; + + if ((err = snd_mixer_attach(am->handle, am->device)) < 0) { + g_set_error(error_r, alsa_mixer_quark(), err, + "failed to attach to %s: %s", + am->device, snd_strerror(err)); + return false; + } + + if ((err = snd_mixer_selem_register(am->handle, NULL, + NULL)) < 0) { + g_set_error(error_r, alsa_mixer_quark(), err, + "snd_mixer_selem_register() failed: %s", + snd_strerror(err)); + return false; + } + + if ((err = snd_mixer_load(am->handle)) < 0) { + g_set_error(error_r, alsa_mixer_quark(), err, + "snd_mixer_load() failed: %s\n", + snd_strerror(err)); + return false; + } + + am->elem = alsa_mixer_lookup_elem(am->handle, am->control, am->index); + if (am->elem == NULL) { + g_set_error(error_r, alsa_mixer_quark(), 0, + "no such mixer control: %s", am->control); + return false; + } + + snd_mixer_selem_get_playback_volume_range(am->elem, + &am->volume_min, + &am->volume_max); + + snd_mixer_elem_set_callback(am->elem, alsa_mixer_elem_callback); + + am->monitor = new AlsaMixerMonitor(*main_loop, am->handle); + + return true; +} + +static bool +alsa_mixer_open(struct mixer *data, GError **error_r) +{ + struct alsa_mixer *am = (struct alsa_mixer *)data; + int err; + + am->volume_set = -1; + + err = snd_mixer_open(&am->handle, 0); + if (err < 0) { + g_set_error(error_r, alsa_mixer_quark(), err, + "snd_mixer_open() failed: %s", snd_strerror(err)); + return false; + } + + if (!alsa_mixer_setup(am, error_r)) { + snd_mixer_close(am->handle); + return false; + } + + return true; +} + +static void +alsa_mixer_close(struct mixer *data) +{ + struct alsa_mixer *am = (struct alsa_mixer *)data; + + assert(am->handle != NULL); + + delete am->monitor; + + snd_mixer_elem_set_callback(am->elem, NULL); + snd_mixer_close(am->handle); +} + +static int +alsa_mixer_get_volume(struct mixer *mixer, GError **error_r) +{ + struct alsa_mixer *am = (struct alsa_mixer *)mixer; + int err; + int ret; + long level; + + assert(am->handle != NULL); + + err = snd_mixer_handle_events(am->handle); + if (err < 0) { + g_set_error(error_r, alsa_mixer_quark(), err, + "snd_mixer_handle_events() failed: %s", + snd_strerror(err)); + return false; + } + + err = snd_mixer_selem_get_playback_volume(am->elem, + SND_MIXER_SCHN_FRONT_LEFT, + &level); + if (err < 0) { + g_set_error(error_r, alsa_mixer_quark(), err, + "failed to read ALSA volume: %s", + snd_strerror(err)); + return false; + } + + ret = ((am->volume_set / 100.0) * (am->volume_max - am->volume_min) + + am->volume_min) + 0.5; + if (am->volume_set > 0 && ret == level) { + ret = am->volume_set; + } else { + ret = (int)(100 * (((float)(level - am->volume_min)) / + (am->volume_max - am->volume_min)) + 0.5); + } + + return ret; +} + +static bool +alsa_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) +{ + struct alsa_mixer *am = (struct alsa_mixer *)mixer; + float vol; + long level; + int err; + + assert(am->handle != NULL); + + vol = volume; + + am->volume_set = vol + 0.5; + + level = (long)(((vol / 100.0) * (am->volume_max - am->volume_min) + + am->volume_min) + 0.5); + level = level > am->volume_max ? am->volume_max : level; + level = level < am->volume_min ? am->volume_min : level; + + err = snd_mixer_selem_set_playback_volume_all(am->elem, level); + if (err < 0) { + g_set_error(error_r, alsa_mixer_quark(), err, + "failed to set ALSA volume: %s", + snd_strerror(err)); + return false; + } + + return true; +} + +const struct mixer_plugin alsa_mixer_plugin = { + alsa_mixer_init, + alsa_mixer_finish, + alsa_mixer_open, + alsa_mixer_close, + alsa_mixer_get_volume, + alsa_mixer_set_volume, + true, +}; diff --git a/src/mixer/OssMixerPlugin.cxx b/src/mixer/OssMixerPlugin.cxx new file mode 100644 index 000000000..490a65414 --- /dev/null +++ b/src/mixer/OssMixerPlugin.cxx @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "mixer_api.h" +#include "output_api.h" +#include "fd_util.h" + +#include <glib.h> + +#include <assert.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> + +#if defined(__OpenBSD__) || defined(__NetBSD__) +# include <soundcard.h> +#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ +# include <sys/soundcard.h> +#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ + +#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" + +struct oss_mixer { + /** the base mixer class */ + struct mixer base; + + const char *device; + const char *control; + + int device_fd; + int volume_control; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +oss_mixer_quark(void) +{ + return g_quark_from_static_string("oss_mixer"); +} + +static int +oss_find_mixer(const char *name) +{ + const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; + size_t name_length = strlen(name); + + for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (g_ascii_strncasecmp(name, labels[i], name_length) == 0 && + (labels[i][name_length] == 0 || + labels[i][name_length] == ' ')) + return i; + } + return -1; +} + +static struct mixer * +oss_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param, + GError **error_r) +{ + struct oss_mixer *om = g_new(struct oss_mixer, 1); + + mixer_init(&om->base, &oss_mixer_plugin); + + om->device = config_get_block_string(param, "mixer_device", + VOLUME_MIXER_OSS_DEFAULT); + om->control = config_get_block_string(param, "mixer_control", NULL); + + if (om->control != NULL) { + om->volume_control = oss_find_mixer(om->control); + if (om->volume_control < 0) { + g_free(om); + g_set_error(error_r, oss_mixer_quark(), 0, + "no such mixer control: %s", om->control); + return NULL; + } + } else + om->volume_control = SOUND_MIXER_PCM; + + return &om->base; +} + +static void +oss_mixer_finish(struct mixer *data) +{ + struct oss_mixer *om = (struct oss_mixer *) data; + + g_free(om); +} + +static void +oss_mixer_close(struct mixer *data) +{ + struct oss_mixer *om = (struct oss_mixer *) data; + + assert(om->device_fd >= 0); + + close(om->device_fd); +} + +static bool +oss_mixer_open(struct mixer *data, GError **error_r) +{ + struct oss_mixer *om = (struct oss_mixer *) data; + + om->device_fd = open_cloexec(om->device, O_RDONLY, 0); + if (om->device_fd < 0) { + g_set_error(error_r, oss_mixer_quark(), errno, + "failed to open %s: %s", + om->device, g_strerror(errno)); + return false; + } + + if (om->control) { + int devmask = 0; + + if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { + g_set_error(error_r, oss_mixer_quark(), errno, + "READ_DEVMASK failed: %s", + g_strerror(errno)); + oss_mixer_close(data); + return false; + } + + if (((1 << om->volume_control) & devmask) == 0) { + g_set_error(error_r, oss_mixer_quark(), 0, + "mixer control \"%s\" not usable", + om->control); + oss_mixer_close(data); + return false; + } + } + return true; +} + +static int +oss_mixer_get_volume(struct mixer *mixer, GError **error_r) +{ + struct oss_mixer *om = (struct oss_mixer *)mixer; + int left, right, level; + int ret; + + assert(om->device_fd >= 0); + + ret = ioctl(om->device_fd, MIXER_READ(om->volume_control), &level); + if (ret < 0) { + g_set_error(error_r, oss_mixer_quark(), errno, + "failed to read OSS volume: %s", + g_strerror(errno)); + return false; + } + + left = level & 0xff; + right = (level & 0xff00) >> 8; + + if (left != right) { + g_warning("volume for left and right is not the same, \"%i\" and " + "\"%i\"\n", left, right); + } + + return left; +} + +static bool +oss_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) +{ + struct oss_mixer *om = (struct oss_mixer *)mixer; + int level; + int ret; + + assert(om->device_fd >= 0); + assert(volume <= 100); + + level = (volume << 8) + volume; + + ret = ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level); + if (ret < 0) { + g_set_error(error_r, oss_mixer_quark(), errno, + "failed to set OSS volume: %s", + g_strerror(errno)); + return false; + } + + return true; +} + +const struct mixer_plugin oss_mixer_plugin = { + oss_mixer_init, + oss_mixer_finish, + oss_mixer_open, + oss_mixer_close, + oss_mixer_get_volume, + oss_mixer_set_volume, + true, +}; diff --git a/src/mixer/PulseMixerPlugin.cxx b/src/mixer/PulseMixerPlugin.cxx new file mode 100644 index 000000000..65dbc01fe --- /dev/null +++ b/src/mixer/PulseMixerPlugin.cxx @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PulseMixerPlugin.h" +#include "mixer_api.h" +#include "output/pulse_output_plugin.h" +#include "conf.h" +#include "GlobalEvents.hxx" + +#include <glib.h> + +#include <pulse/thread-mainloop.h> +#include <pulse/context.h> +#include <pulse/introspect.h> +#include <pulse/stream.h> +#include <pulse/subscribe.h> +#include <pulse/error.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "pulse_mixer" + +struct pulse_mixer { + struct mixer base; + + struct pulse_output *output; + + bool online; + struct pa_cvolume volume; + +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +pulse_mixer_quark(void) +{ + return g_quark_from_static_string("pulse_mixer"); +} + +static void +pulse_mixer_offline(struct pulse_mixer *pm) +{ + if (!pm->online) + return; + + pm->online = false; + + GlobalEvents::Emit(GlobalEvents::MIXER); +} + +/** + * Callback invoked by pulse_mixer_update(). Receives the new mixer + * value. + */ +static void +pulse_mixer_volume_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i, + int eol, void *userdata) +{ + struct pulse_mixer *pm = (struct pulse_mixer *)userdata; + + if (eol) + return; + + if (i == NULL) { + pulse_mixer_offline(pm); + return; + } + + pm->online = true; + pm->volume = i->volume; + + GlobalEvents::Emit(GlobalEvents::MIXER); +} + +static void +pulse_mixer_update(struct pulse_mixer *pm, + struct pa_context *context, struct pa_stream *stream) +{ + pa_operation *o; + + assert(context != NULL); + assert(stream != NULL); + assert(pa_stream_get_state(stream) == PA_STREAM_READY); + + o = pa_context_get_sink_input_info(context, + pa_stream_get_index(stream), + pulse_mixer_volume_cb, pm); + if (o == NULL) { + g_warning("pa_context_get_sink_input_info() failed: %s", + pa_strerror(pa_context_errno(context))); + pulse_mixer_offline(pm); + return; + } + + pa_operation_unref(o); +} + +void +pulse_mixer_on_connect(G_GNUC_UNUSED struct pulse_mixer *pm, + struct pa_context *context) +{ + pa_operation *o; + + assert(context != NULL); + + o = pa_context_subscribe(context, + (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT, + NULL, NULL); + if (o == NULL) { + g_warning("pa_context_subscribe() failed: %s", + pa_strerror(pa_context_errno(context))); + return; + } + + pa_operation_unref(o); +} + +void +pulse_mixer_on_disconnect(struct pulse_mixer *pm) +{ + pulse_mixer_offline(pm); +} + +void +pulse_mixer_on_change(struct pulse_mixer *pm, + struct pa_context *context, struct pa_stream *stream) +{ + pulse_mixer_update(pm, context, stream); +} + +static struct mixer * +pulse_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, + GError **error_r) +{ + struct pulse_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 nullptr; + } + + struct pulse_mixer *pm = g_new(struct pulse_mixer,1); + mixer_init(&pm->base, &pulse_mixer_plugin); + + pm->online = false; + pm->output = po; + + pulse_output_set_mixer(po, pm); + + return &pm->base; +} + +static void +pulse_mixer_finish(struct mixer *data) +{ + struct pulse_mixer *pm = (struct pulse_mixer *) data; + + pulse_output_clear_mixer(pm->output, pm); + + /* free resources */ + + g_free(pm); +} + +static int +pulse_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) +{ + struct pulse_mixer *pm = (struct pulse_mixer *) mixer; + int ret; + + pulse_output_lock(pm->output); + + ret = pm->online + ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM) + : -1; + + pulse_output_unlock(pm->output); + + return ret; +} + +static bool +pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) +{ + struct pulse_mixer *pm = (struct pulse_mixer *) mixer; + struct pa_cvolume cvolume; + bool success; + + pulse_output_lock(pm->output); + + if (!pm->online) { + pulse_output_unlock(pm->output); + g_set_error(error_r, pulse_mixer_quark(), 0, "disconnected"); + return false; + } + + pa_cvolume_set(&cvolume, pm->volume.channels, + (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5); + success = pulse_output_set_volume(pm->output, &cvolume, error_r); + if (success) + pm->volume = cvolume; + + pulse_output_unlock(pm->output); + + return success; +} + +const struct mixer_plugin pulse_mixer_plugin = { + pulse_mixer_init, + pulse_mixer_finish, + nullptr, + nullptr, + pulse_mixer_get_volume, + pulse_mixer_set_volume, + false, +}; diff --git a/src/mixer/PulseMixerPlugin.h b/src/mixer/PulseMixerPlugin.h new file mode 100644 index 000000000..f432c44a0 --- /dev/null +++ b/src/mixer/PulseMixerPlugin.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PULSE_MIXER_PLUGIN_H +#define MPD_PULSE_MIXER_PLUGIN_H + +#include <pulse/def.h> + +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); + +void +pulse_mixer_on_disconnect(struct pulse_mixer *pm); + +void +pulse_mixer_on_change(struct pulse_mixer *pm, + struct pa_context *context, struct pa_stream *stream); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/mixer/RoarMixerPlugin.cxx b/src/mixer/RoarMixerPlugin.cxx new file mode 100644 index 000000000..2803203b7 --- /dev/null +++ b/src/mixer/RoarMixerPlugin.cxx @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#include "config.h" +#include "mixer_api.h" +#include "output_api.h" +#include "output/RoarOutputPlugin.hxx" + +struct RoarMixer { + /** the base mixer class */ + struct mixer base; + RoarOutput *self; + + RoarMixer(RoarOutput *_output):self(_output) { + mixer_init(&base, &roar_mixer_plugin); + } +}; + +static struct mixer * +roar_mixer_init(void *ao, gcc_unused const struct config_param *param, + gcc_unused GError **error_r) +{ + RoarMixer *self = new RoarMixer((RoarOutput *)ao); + return &self->base; +} + +static void +roar_mixer_finish(struct mixer *data) +{ + RoarMixer *self = (RoarMixer *) data; + + delete self; +} + +static int +roar_mixer_get_volume(struct mixer *mixer, gcc_unused GError **error_r) +{ + RoarMixer *self = (RoarMixer *)mixer; + return roar_output_get_volume(self->self); +} + +static bool +roar_mixer_set_volume(struct mixer *mixer, unsigned volume, + gcc_unused GError **error_r) +{ + RoarMixer *self = (RoarMixer *)mixer; + return roar_output_set_volume(self->self, volume); +} + +const struct mixer_plugin roar_mixer_plugin = { + roar_mixer_init, + roar_mixer_finish, + nullptr, + nullptr, + roar_mixer_get_volume, + roar_mixer_set_volume, + false, +}; diff --git a/src/mixer/SoftwareMixerPlugin.cxx b/src/mixer/SoftwareMixerPlugin.cxx new file mode 100644 index 000000000..16463938f --- /dev/null +++ b/src/mixer/SoftwareMixerPlugin.cxx @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SoftwareMixerPlugin.hxx" +#include "mixer_api.h" +#include "FilterPlugin.hxx" +#include "FilterRegistry.hxx" +#include "filter/VolumeFilterPlugin.hxx" +#include "PcmVolume.hxx" + +#include <assert.h> +#include <math.h> + +struct software_mixer { + /** the base mixer class */ + struct mixer base; + + Filter *filter; + + unsigned volume; +}; + +static struct mixer * +software_mixer_init(G_GNUC_UNUSED void *ao, + G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct software_mixer *sm = g_new(struct software_mixer, 1); + + mixer_init(&sm->base, &software_mixer_plugin); + + sm->filter = filter_new(&volume_filter_plugin, NULL, NULL); + assert(sm->filter != NULL); + + sm->volume = 100; + + return &sm->base; +} + +static void +software_mixer_finish(struct mixer *data) +{ + struct software_mixer *sm = (struct software_mixer *)data; + + g_free(sm); +} + +static int +software_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) +{ + struct software_mixer *sm = (struct software_mixer *)mixer; + + return sm->volume; +} + +static bool +software_mixer_set_volume(struct mixer *mixer, unsigned volume, + G_GNUC_UNUSED GError **error_r) +{ + struct software_mixer *sm = (struct software_mixer *)mixer; + + assert(volume <= 100); + + sm->volume = volume; + + if (volume >= 100) + volume = PCM_VOLUME_1; + else if (volume > 0) + volume = pcm_float_to_volume((exp(volume / 25.0) - 1) / + (54.5981500331F - 1)); + + volume_filter_set(sm->filter, volume); + return true; +} + +const struct mixer_plugin software_mixer_plugin = { + software_mixer_init, + software_mixer_finish, + nullptr, + nullptr, + software_mixer_get_volume, + software_mixer_set_volume, + true, +}; + +Filter * +software_mixer_get_filter(struct mixer *mixer) +{ + struct software_mixer *sm = (struct software_mixer *)mixer; + + assert(sm->base.plugin == &software_mixer_plugin); + + return sm->filter; +} diff --git a/src/mixer/SoftwareMixerPlugin.hxx b/src/mixer/SoftwareMixerPlugin.hxx new file mode 100644 index 000000000..33e9e6c6f --- /dev/null +++ b/src/mixer/SoftwareMixerPlugin.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SOFTWARE_MIXER_PLUGIN_HXX +#define MPD_SOFTWARE_MIXER_PLUGIN_HXX + +struct mixer; +class Filter; + +/** + * Returns the (volume) filter associated with this mixer. All users + * of this mixer plugin should install this filter. + */ +Filter * +software_mixer_get_filter(struct mixer *mixer); + +#endif diff --git a/src/mixer/alsa_mixer_plugin.c b/src/mixer/alsa_mixer_plugin.c deleted file mode 100644 index 22e4e22bd..000000000 --- a/src/mixer/alsa_mixer_plugin.c +++ /dev/null @@ -1,431 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "mixer_api.h" -#include "output_api.h" -#include "event_pipe.h" - -#include <glib.h> -#include <alsa/asoundlib.h> - -#define VOLUME_MIXER_ALSA_DEFAULT "default" -#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" -#define VOLUME_MIXER_ALSA_INDEX_DEFAULT 0 - -struct alsa_mixer_source { - GSource source; - - snd_mixer_t *mixer; - - /** a linked list of all registered GPollFD objects */ - GSList *fds; -}; - -struct alsa_mixer { - /** the base mixer class */ - struct mixer base; - - const char *device; - const char *control; - unsigned int index; - - snd_mixer_t *handle; - snd_mixer_elem_t *elem; - long volume_min; - long volume_max; - int volume_set; - - struct alsa_mixer_source *source; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -alsa_mixer_quark(void) -{ - return g_quark_from_static_string("alsa_mixer"); -} - -/* - * GSource helper functions - * - */ - -static GSList ** -find_fd(GSList **list_r, int fd) -{ - while (true) { - GSList *list = *list_r; - if (list == NULL) - return NULL; - - GPollFD *p = list->data; - if (p->fd == fd) - return list_r; - - list_r = &list->next; - } -} - -static void -alsa_mixer_update_fd(struct alsa_mixer_source *source, const struct pollfd *p, - GSList **old_r) -{ - GSList **found_r = find_fd(old_r, p->fd); - if (found_r == NULL) { - /* new fd */ - GPollFD *q = g_new(GPollFD, 1); - q->fd = p->fd; - q->events = p->events; - g_source_add_poll(&source->source, q); - source->fds = g_slist_prepend(source->fds, q); - return; - } - - GSList *found = *found_r; - *found_r = found->next; - - GPollFD *q = found->data; - if (q->events != p->events) { - /* refresh events */ - g_source_remove_poll(&source->source, q); - q->events = p->events; - g_source_add_poll(&source->source, q); - } - - found->next = source->fds; - source->fds = found; -} - -static void -alsa_mixer_update_fds(struct alsa_mixer_source *source) -{ - int count = snd_mixer_poll_descriptors_count(source->mixer); - if (count < 0) - count = 0; - - struct pollfd *pfds = g_new(struct pollfd, count); - count = snd_mixer_poll_descriptors(source->mixer, pfds, count); - if (count < 0) - count = 0; - - GSList *old = source->fds; - source->fds = NULL; - - for (int i = 0; i < count; ++i) - alsa_mixer_update_fd(source, &pfds[i], &old); - g_free(pfds); - - for (; old != NULL; old = old->next) { - GPollFD *q = old->data; - g_source_remove_poll(&source->source, q); - g_free(q); - } - - g_slist_free(old); -} - -/* - * GSource methods - * - */ - -static gboolean -alsa_mixer_source_prepare(GSource *_source, G_GNUC_UNUSED gint *timeout_r) -{ - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - alsa_mixer_update_fds(source); - - return false; -} - -static gboolean -alsa_mixer_source_check(GSource *_source) -{ - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - - for (const GSList *i = source->fds; i != NULL; i = i->next) { - const GPollFD *poll_fd = i->data; - if (poll_fd->revents != 0) - return true; - } - - return false; -} - -static gboolean -alsa_mixer_source_dispatch(GSource *_source, - G_GNUC_UNUSED GSourceFunc callback, - G_GNUC_UNUSED gpointer user_data) -{ - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - - snd_mixer_handle_events(source->mixer); - return true; -} - -static void -alsa_mixer_source_finalize(GSource *_source) -{ - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - - for (GSList *i = source->fds; i != NULL; i = i->next) - g_free(i->data); - - g_slist_free(source->fds); -} - -static GSourceFuncs alsa_mixer_source_funcs = { - .prepare = alsa_mixer_source_prepare, - .check = alsa_mixer_source_check, - .dispatch = alsa_mixer_source_dispatch, - .finalize = alsa_mixer_source_finalize, -}; - -/* - * libasound callbacks - * - */ - -static int -alsa_mixer_elem_callback(G_GNUC_UNUSED snd_mixer_elem_t *elem, unsigned mask) -{ - if (mask & SND_CTL_EVENT_MASK_VALUE) - event_pipe_emit(PIPE_EVENT_MIXER); - - return 0; -} - -/* - * mixer_plugin methods - * - */ - -static struct mixer * -alsa_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct alsa_mixer *am = g_new(struct alsa_mixer, 1); - - mixer_init(&am->base, &alsa_mixer_plugin); - - am->device = config_get_block_string(param, "mixer_device", - VOLUME_MIXER_ALSA_DEFAULT); - am->control = config_get_block_string(param, "mixer_control", - VOLUME_MIXER_ALSA_CONTROL_DEFAULT); - am->index = config_get_block_unsigned(param, "mixer_index", - VOLUME_MIXER_ALSA_INDEX_DEFAULT); - - return &am->base; -} - -static void -alsa_mixer_finish(struct mixer *data) -{ - struct alsa_mixer *am = (struct alsa_mixer *)data; - - g_free(am); - - /* free libasound's config cache */ - snd_config_update_free_global(); -} - -G_GNUC_PURE -static snd_mixer_elem_t * -alsa_mixer_lookup_elem(snd_mixer_t *handle, const char *name, unsigned idx) -{ - for (snd_mixer_elem_t *elem = snd_mixer_first_elem(handle); - elem != NULL; elem = snd_mixer_elem_next(elem)) { - if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE && - g_ascii_strcasecmp(snd_mixer_selem_get_name(elem), - name) == 0 && - snd_mixer_selem_get_index(elem) == idx) - return elem; - } - - return NULL; -} - -static bool -alsa_mixer_setup(struct alsa_mixer *am, GError **error_r) -{ - int err; - - if ((err = snd_mixer_attach(am->handle, am->device)) < 0) { - g_set_error(error_r, alsa_mixer_quark(), err, - "failed to attach to %s: %s", - am->device, snd_strerror(err)); - return false; - } - - if ((err = snd_mixer_selem_register(am->handle, NULL, - NULL)) < 0) { - g_set_error(error_r, alsa_mixer_quark(), err, - "snd_mixer_selem_register() failed: %s", - snd_strerror(err)); - return false; - } - - if ((err = snd_mixer_load(am->handle)) < 0) { - g_set_error(error_r, alsa_mixer_quark(), err, - "snd_mixer_load() failed: %s\n", - snd_strerror(err)); - return false; - } - - am->elem = alsa_mixer_lookup_elem(am->handle, am->control, am->index); - if (am->elem == NULL) { - g_set_error(error_r, alsa_mixer_quark(), 0, - "no such mixer control: %s", am->control); - return false; - } - - snd_mixer_selem_get_playback_volume_range(am->elem, - &am->volume_min, - &am->volume_max); - - snd_mixer_elem_set_callback(am->elem, alsa_mixer_elem_callback); - - am->source = (struct alsa_mixer_source *) - g_source_new(&alsa_mixer_source_funcs, sizeof(*am->source)); - am->source->mixer = am->handle; - am->source->fds = NULL; - g_source_attach(&am->source->source, g_main_context_default()); - - return true; -} - -static bool -alsa_mixer_open(struct mixer *data, GError **error_r) -{ - struct alsa_mixer *am = (struct alsa_mixer *)data; - int err; - - am->volume_set = -1; - - err = snd_mixer_open(&am->handle, 0); - if (err < 0) { - g_set_error(error_r, alsa_mixer_quark(), err, - "snd_mixer_open() failed: %s", snd_strerror(err)); - return false; - } - - if (!alsa_mixer_setup(am, error_r)) { - snd_mixer_close(am->handle); - return false; - } - - return true; -} - -static void -alsa_mixer_close(struct mixer *data) -{ - struct alsa_mixer *am = (struct alsa_mixer *)data; - - assert(am->handle != NULL); - - g_source_destroy(&am->source->source); - g_source_unref(&am->source->source); - - snd_mixer_elem_set_callback(am->elem, NULL); - snd_mixer_close(am->handle); -} - -static int -alsa_mixer_get_volume(struct mixer *mixer, GError **error_r) -{ - struct alsa_mixer *am = (struct alsa_mixer *)mixer; - int err; - int ret; - long level; - - assert(am->handle != NULL); - - err = snd_mixer_handle_events(am->handle); - if (err < 0) { - g_set_error(error_r, alsa_mixer_quark(), err, - "snd_mixer_handle_events() failed: %s", - snd_strerror(err)); - return false; - } - - err = snd_mixer_selem_get_playback_volume(am->elem, - SND_MIXER_SCHN_FRONT_LEFT, - &level); - if (err < 0) { - g_set_error(error_r, alsa_mixer_quark(), err, - "failed to read ALSA volume: %s", - snd_strerror(err)); - return false; - } - - ret = ((am->volume_set / 100.0) * (am->volume_max - am->volume_min) - + am->volume_min) + 0.5; - if (am->volume_set > 0 && ret == level) { - ret = am->volume_set; - } else { - ret = (int)(100 * (((float)(level - am->volume_min)) / - (am->volume_max - am->volume_min)) + 0.5); - } - - return ret; -} - -static bool -alsa_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) -{ - struct alsa_mixer *am = (struct alsa_mixer *)mixer; - float vol; - long level; - int err; - - assert(am->handle != NULL); - - vol = volume; - - am->volume_set = vol + 0.5; - - level = (long)(((vol / 100.0) * (am->volume_max - am->volume_min) + - am->volume_min) + 0.5); - level = level > am->volume_max ? am->volume_max : level; - level = level < am->volume_min ? am->volume_min : level; - - err = snd_mixer_selem_set_playback_volume_all(am->elem, level); - if (err < 0) { - g_set_error(error_r, alsa_mixer_quark(), err, - "failed to set ALSA volume: %s", - snd_strerror(err)); - return false; - } - - return true; -} - -const struct mixer_plugin alsa_mixer_plugin = { - .init = alsa_mixer_init, - .finish = alsa_mixer_finish, - .open = alsa_mixer_open, - .close = alsa_mixer_close, - .get_volume = alsa_mixer_get_volume, - .set_volume = alsa_mixer_set_volume, - .global = true, -}; diff --git a/src/mixer/oss_mixer_plugin.c b/src/mixer/oss_mixer_plugin.c deleted file mode 100644 index 608f1f9b8..000000000 --- a/src/mixer/oss_mixer_plugin.c +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "mixer_api.h" -#include "output_api.h" -#include "fd_util.h" - -#include <glib.h> - -#include <assert.h> -#include <sys/stat.h> -#include <sys/ioctl.h> -#include <fcntl.h> -#include <errno.h> -#include <stdlib.h> -#include <unistd.h> - -#if defined(__OpenBSD__) || defined(__NetBSD__) -# include <soundcard.h> -#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ -# include <sys/soundcard.h> -#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ - -#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" - -struct oss_mixer { - /** the base mixer class */ - struct mixer base; - - const char *device; - const char *control; - - int device_fd; - int volume_control; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -oss_mixer_quark(void) -{ - return g_quark_from_static_string("oss_mixer"); -} - -static int -oss_find_mixer(const char *name) -{ - const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; - size_t name_length = strlen(name); - - for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) { - if (g_ascii_strncasecmp(name, labels[i], name_length) == 0 && - (labels[i][name_length] == 0 || - labels[i][name_length] == ' ')) - return i; - } - return -1; -} - -static struct mixer * -oss_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param, - GError **error_r) -{ - struct oss_mixer *om = g_new(struct oss_mixer, 1); - - mixer_init(&om->base, &oss_mixer_plugin); - - om->device = config_get_block_string(param, "mixer_device", - VOLUME_MIXER_OSS_DEFAULT); - om->control = config_get_block_string(param, "mixer_control", NULL); - - if (om->control != NULL) { - om->volume_control = oss_find_mixer(om->control); - if (om->volume_control < 0) { - g_free(om); - g_set_error(error_r, oss_mixer_quark(), 0, - "no such mixer control: %s", om->control); - return NULL; - } - } else - om->volume_control = SOUND_MIXER_PCM; - - return &om->base; -} - -static void -oss_mixer_finish(struct mixer *data) -{ - struct oss_mixer *om = (struct oss_mixer *) data; - - g_free(om); -} - -static void -oss_mixer_close(struct mixer *data) -{ - struct oss_mixer *om = (struct oss_mixer *) data; - - assert(om->device_fd >= 0); - - close(om->device_fd); -} - -static bool -oss_mixer_open(struct mixer *data, GError **error_r) -{ - struct oss_mixer *om = (struct oss_mixer *) data; - - om->device_fd = open_cloexec(om->device, O_RDONLY, 0); - if (om->device_fd < 0) { - g_set_error(error_r, oss_mixer_quark(), errno, - "failed to open %s: %s", - om->device, g_strerror(errno)); - return false; - } - - if (om->control) { - int devmask = 0; - - if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { - g_set_error(error_r, oss_mixer_quark(), errno, - "READ_DEVMASK failed: %s", - g_strerror(errno)); - oss_mixer_close(data); - return false; - } - - if (((1 << om->volume_control) & devmask) == 0) { - g_set_error(error_r, oss_mixer_quark(), 0, - "mixer control \"%s\" not usable", - om->control); - oss_mixer_close(data); - return false; - } - } - return true; -} - -static int -oss_mixer_get_volume(struct mixer *mixer, GError **error_r) -{ - struct oss_mixer *om = (struct oss_mixer *)mixer; - int left, right, level; - int ret; - - assert(om->device_fd >= 0); - - ret = ioctl(om->device_fd, MIXER_READ(om->volume_control), &level); - if (ret < 0) { - g_set_error(error_r, oss_mixer_quark(), errno, - "failed to read OSS volume: %s", - g_strerror(errno)); - return false; - } - - left = level & 0xff; - right = (level & 0xff00) >> 8; - - if (left != right) { - g_warning("volume for left and right is not the same, \"%i\" and " - "\"%i\"\n", left, right); - } - - return left; -} - -static bool -oss_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) -{ - struct oss_mixer *om = (struct oss_mixer *)mixer; - int level; - int ret; - - assert(om->device_fd >= 0); - assert(volume <= 100); - - level = (volume << 8) + volume; - - ret = ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level); - if (ret < 0) { - g_set_error(error_r, oss_mixer_quark(), errno, - "failed to set OSS volume: %s", - g_strerror(errno)); - return false; - } - - return true; -} - -const struct mixer_plugin oss_mixer_plugin = { - .init = oss_mixer_init, - .finish = oss_mixer_finish, - .open = oss_mixer_open, - .close = oss_mixer_close, - .get_volume = oss_mixer_get_volume, - .set_volume = oss_mixer_set_volume, - .global = true, -}; diff --git a/src/mixer/pulse_mixer_plugin.c b/src/mixer/pulse_mixer_plugin.c deleted file mode 100644 index a82c032b3..000000000 --- a/src/mixer/pulse_mixer_plugin.c +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pulse_mixer_plugin.h" -#include "mixer_api.h" -#include "output/pulse_output_plugin.h" -#include "conf.h" -#include "event_pipe.h" - -#include <glib.h> - -#include <pulse/thread-mainloop.h> -#include <pulse/context.h> -#include <pulse/introspect.h> -#include <pulse/stream.h> -#include <pulse/subscribe.h> -#include <pulse/error.h> - -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pulse_mixer" - -struct pulse_mixer { - struct mixer base; - - struct pulse_output *output; - - bool online; - struct pa_cvolume volume; - -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -pulse_mixer_quark(void) -{ - return g_quark_from_static_string("pulse_mixer"); -} - -static void -pulse_mixer_offline(struct pulse_mixer *pm) -{ - if (!pm->online) - return; - - pm->online = false; - - event_pipe_emit(PIPE_EVENT_MIXER); -} - -/** - * Callback invoked by pulse_mixer_update(). Receives the new mixer - * value. - */ -static void -pulse_mixer_volume_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i, - int eol, void *userdata) -{ - struct pulse_mixer *pm = userdata; - - if (eol) - return; - - if (i == NULL) { - pulse_mixer_offline(pm); - return; - } - - pm->online = true; - pm->volume = i->volume; - - event_pipe_emit(PIPE_EVENT_MIXER); -} - -static void -pulse_mixer_update(struct pulse_mixer *pm, - struct pa_context *context, struct pa_stream *stream) -{ - pa_operation *o; - - assert(context != NULL); - assert(stream != NULL); - assert(pa_stream_get_state(stream) == PA_STREAM_READY); - - o = pa_context_get_sink_input_info(context, - pa_stream_get_index(stream), - pulse_mixer_volume_cb, pm); - if (o == NULL) { - g_warning("pa_context_get_sink_input_info() failed: %s", - pa_strerror(pa_context_errno(context))); - pulse_mixer_offline(pm); - return; - } - - pa_operation_unref(o); -} - -void -pulse_mixer_on_connect(G_GNUC_UNUSED struct pulse_mixer *pm, - struct pa_context *context) -{ - pa_operation *o; - - assert(context != NULL); - - o = pa_context_subscribe(context, - (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT, - NULL, NULL); - if (o == NULL) { - g_warning("pa_context_subscribe() failed: %s", - pa_strerror(pa_context_errno(context))); - return; - } - - pa_operation_unref(o); -} - -void -pulse_mixer_on_disconnect(struct pulse_mixer *pm) -{ - pulse_mixer_offline(pm); -} - -void -pulse_mixer_on_change(struct pulse_mixer *pm, - struct pa_context *context, struct pa_stream *stream) -{ - pulse_mixer_update(pm, context, stream); -} - -static struct mixer * -pulse_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, - GError **error_r) -{ - struct pulse_mixer *pm; - struct pulse_output *po = ao; - - if (ao == NULL) { - g_set_error(error_r, pulse_mixer_quark(), 0, - "The pulse mixer cannot work without the audio output"); - return false; - } - - pm = g_new(struct pulse_mixer,1); - mixer_init(&pm->base, &pulse_mixer_plugin); - - pm->online = false; - pm->output = po; - - pulse_output_set_mixer(po, pm); - - return &pm->base; -} - -static void -pulse_mixer_finish(struct mixer *data) -{ - struct pulse_mixer *pm = (struct pulse_mixer *) data; - - pulse_output_clear_mixer(pm->output, pm); - - /* free resources */ - - g_free(pm); -} - -static int -pulse_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) -{ - struct pulse_mixer *pm = (struct pulse_mixer *) mixer; - int ret; - - pulse_output_lock(pm->output); - - ret = pm->online - ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM) - : -1; - - pulse_output_unlock(pm->output); - - return ret; -} - -static bool -pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) -{ - struct pulse_mixer *pm = (struct pulse_mixer *) mixer; - struct pa_cvolume cvolume; - bool success; - - pulse_output_lock(pm->output); - - if (!pm->online) { - pulse_output_unlock(pm->output); - g_set_error(error_r, pulse_mixer_quark(), 0, "disconnected"); - return false; - } - - pa_cvolume_set(&cvolume, pm->volume.channels, - (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5); - success = pulse_output_set_volume(pm->output, &cvolume, error_r); - if (success) - pm->volume = cvolume; - - pulse_output_unlock(pm->output); - - return success; -} - -const struct mixer_plugin pulse_mixer_plugin = { - .init = pulse_mixer_init, - .finish = pulse_mixer_finish, - .get_volume = pulse_mixer_get_volume, - .set_volume = pulse_mixer_set_volume, -}; diff --git a/src/mixer/pulse_mixer_plugin.h b/src/mixer/pulse_mixer_plugin.h deleted file mode 100644 index 461633d37..000000000 --- a/src/mixer/pulse_mixer_plugin.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PULSE_MIXER_PLUGIN_H -#define MPD_PULSE_MIXER_PLUGIN_H - -#include <pulse/def.h> - -struct pulse_mixer; -struct pa_context; -struct pa_stream; - -void -pulse_mixer_on_connect(struct pulse_mixer *pm, struct pa_context *context); - -void -pulse_mixer_on_disconnect(struct pulse_mixer *pm); - -void -pulse_mixer_on_change(struct pulse_mixer *pm, - struct pa_context *context, struct pa_stream *stream); - -#endif diff --git a/src/mixer/roar_mixer_plugin.c b/src/mixer/roar_mixer_plugin.c deleted file mode 100644 index 47d3c17f9..000000000 --- a/src/mixer/roar_mixer_plugin.c +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft - * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - - -#include "config.h" -#include "mixer_api.h" -#include "output_api.h" -#include "output/roar_output_plugin.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> -#include <unistd.h> - -typedef struct roar_mpd_mixer -{ - /** the base mixer class */ - struct mixer base; - struct roar *self; -} roar_mixer_t; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -roar_mixer_quark(void) -{ - return g_quark_from_static_string("roar_mixer"); -} - -static struct mixer * -roar_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - roar_mixer_t *self = g_new(roar_mixer_t, 1); - self->self = ao; - - mixer_init(&self->base, &roar_mixer_plugin); - - return &self->base; -} - -static void -roar_mixer_finish(struct mixer *data) -{ - roar_mixer_t *self = (roar_mixer_t *) data; - - g_free(self); -} - -static void -roar_mixer_close(G_GNUC_UNUSED struct mixer *data) -{ -} - -static bool -roar_mixer_open(G_GNUC_UNUSED struct mixer *data, - G_GNUC_UNUSED GError **error_r) -{ - return true; -} - -static int -roar_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) -{ - roar_mixer_t *self = (roar_mixer_t *)mixer; - return roar_output_get_volume(self->self); -} - -static bool -roar_mixer_set_volume(struct mixer *mixer, unsigned volume, - G_GNUC_UNUSED GError **error_r) -{ - roar_mixer_t *self = (roar_mixer_t *)mixer; - return roar_output_set_volume(self->self, volume); -} - -const struct mixer_plugin roar_mixer_plugin = { - .init = roar_mixer_init, - .finish = roar_mixer_finish, - .open = roar_mixer_open, - .close = roar_mixer_close, - .get_volume = roar_mixer_get_volume, - .set_volume = roar_mixer_set_volume, - .global = false, -}; diff --git a/src/mixer/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c deleted file mode 100644 index 0206c3b99..000000000 --- a/src/mixer/software_mixer_plugin.c +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "software_mixer_plugin.h" -#include "mixer_api.h" -#include "filter_plugin.h" -#include "filter_registry.h" -#include "filter/volume_filter_plugin.h" -#include "pcm_volume.h" - -#include <assert.h> -#include <math.h> - -struct software_mixer { - /** the base mixer class */ - struct mixer base; - - struct filter *filter; - - unsigned volume; -}; - -static struct mixer * -software_mixer_init(G_GNUC_UNUSED void *ao, - G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct software_mixer *sm = g_new(struct software_mixer, 1); - - mixer_init(&sm->base, &software_mixer_plugin); - - sm->filter = filter_new(&volume_filter_plugin, NULL, NULL); - assert(sm->filter != NULL); - - sm->volume = 100; - - return &sm->base; -} - -static void -software_mixer_finish(struct mixer *data) -{ - struct software_mixer *sm = (struct software_mixer *)data; - - g_free(sm); -} - -static int -software_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) -{ - struct software_mixer *sm = (struct software_mixer *)mixer; - - return sm->volume; -} - -static bool -software_mixer_set_volume(struct mixer *mixer, unsigned volume, - G_GNUC_UNUSED GError **error_r) -{ - struct software_mixer *sm = (struct software_mixer *)mixer; - - assert(volume <= 100); - - sm->volume = volume; - - if (volume >= 100) - volume = PCM_VOLUME_1; - else if (volume > 0) - volume = pcm_float_to_volume((exp(volume / 25.0) - 1) / - (54.5981500331F - 1)); - - volume_filter_set(sm->filter, volume); - return true; -} - -const struct mixer_plugin software_mixer_plugin = { - .init = software_mixer_init, - .finish = software_mixer_finish, - .get_volume = software_mixer_get_volume, - .set_volume = software_mixer_set_volume, - .global = true, -}; - -struct filter * -software_mixer_get_filter(struct mixer *mixer) -{ - struct software_mixer *sm = (struct software_mixer *)mixer; - - assert(sm->base.plugin == &software_mixer_plugin); - - return sm->filter; -} diff --git a/src/mixer/software_mixer_plugin.h b/src/mixer/software_mixer_plugin.h deleted file mode 100644 index ee2b2023c..000000000 --- a/src/mixer/software_mixer_plugin.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef SOFTWARE_MIXER_PLUGIN_H -#define SOFTWARE_MIXER_PLUGIN_H - -struct mixer; -struct filter; - -/** - * Returns the (volume) filter associated with this mixer. All users - * of this mixer plugin should install this filter. - */ -struct filter * -software_mixer_get_filter(struct mixer *mixer); - -#endif diff --git a/src/mixer/winmm_mixer_plugin.c b/src/mixer/winmm_mixer_plugin.c index ceddf6afd..99da60cce 100644 --- a/src/mixer/winmm_mixer_plugin.c +++ b/src/mixer/winmm_mixer_plugin.c @@ -22,6 +22,8 @@ #include "output_api.h" #include "output/winmm_output_plugin.h" +#include <mmsystem.h> + #include <assert.h> #include <math.h> #include <windows.h> diff --git a/src/mixer_all.c b/src/mixer_all.c deleted file mode 100644 index 95ba90793..000000000 --- a/src/mixer_all.c +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "mixer_all.h" -#include "mixer_control.h" -#include "output_all.h" -#include "output_plugin.h" -#include "output_internal.h" -#include "pcm_volume.h" -#include "mixer_api.h" -#include "mixer_list.h" - -#include <glib.h> - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mixer" - -static int -output_mixer_get_volume(unsigned i) -{ - struct audio_output *output; - struct mixer *mixer; - int volume; - GError *error = NULL; - - assert(i < audio_output_count()); - - output = audio_output_get(i); - if (!output->enabled) - return -1; - - mixer = output->mixer; - if (mixer == NULL) - return -1; - - volume = mixer_get_volume(mixer, &error); - if (volume < 0 && error != NULL) { - g_warning("Failed to read mixer for '%s': %s", - output->name, error->message); - g_error_free(error); - } - - return volume; -} - -int -mixer_all_get_volume(void) -{ - unsigned count = audio_output_count(), ok = 0; - int volume, total = 0; - - for (unsigned i = 0; i < count; i++) { - volume = output_mixer_get_volume(i); - if (volume >= 0) { - total += volume; - ++ok; - } - } - - if (ok == 0) - return -1; - - return total / ok; -} - -static bool -output_mixer_set_volume(unsigned i, unsigned volume) -{ - struct audio_output *output; - struct mixer *mixer; - bool success; - GError *error = NULL; - - assert(i < audio_output_count()); - assert(volume <= 100); - - output = audio_output_get(i); - if (!output->enabled) - return false; - - mixer = output->mixer; - if (mixer == NULL) - return false; - - success = mixer_set_volume(mixer, volume, &error); - if (!success && error != NULL) { - g_warning("Failed to set mixer for '%s': %s", - output->name, error->message); - g_error_free(error); - } - - return success; -} - -bool -mixer_all_set_volume(unsigned volume) -{ - bool success = false; - unsigned count = audio_output_count(); - - assert(volume <= 100); - - for (unsigned i = 0; i < count; i++) - success = output_mixer_set_volume(i, volume) - || success; - - return success; -} - -static int -output_mixer_get_software_volume(unsigned i) -{ - struct audio_output *output; - struct mixer *mixer; - - assert(i < audio_output_count()); - - output = audio_output_get(i); - if (!output->enabled) - return -1; - - mixer = output->mixer; - if (mixer == NULL || mixer->plugin != &software_mixer_plugin) - return -1; - - return mixer_get_volume(mixer, NULL); -} - -int -mixer_all_get_software_volume(void) -{ - unsigned count = audio_output_count(), ok = 0; - int volume, total = 0; - - for (unsigned i = 0; i < count; i++) { - volume = output_mixer_get_software_volume(i); - if (volume >= 0) { - total += volume; - ++ok; - } - } - - if (ok == 0) - return -1; - - return total / ok; -} - -void -mixer_all_set_software_volume(unsigned volume) -{ - unsigned count = audio_output_count(); - - assert(volume <= PCM_VOLUME_1); - - for (unsigned i = 0; i < count; i++) { - struct audio_output *output = audio_output_get(i); - if (output->mixer != NULL && - output->mixer->plugin == &software_mixer_plugin) - mixer_set_volume(output->mixer, volume, NULL); - } -} diff --git a/src/mixer_all.h b/src/mixer_all.h deleted file mode 100644 index fe873e713..000000000 --- a/src/mixer_all.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Functions which affect the mixers of all audio outputs. - */ - -#ifndef MPD_MIXER_ALL_H -#define MPD_MIXER_ALL_H - -#include <stdbool.h> - -/** - * Returns the average volume of all available mixers (range 0..100). - * Returns -1 if no mixer can be queried. - */ -int -mixer_all_get_volume(void); - -/** - * Sets the volume on all available mixers. - * - * @param volume the volume (range 0..100) - * @return true on success, false on failure - */ -bool -mixer_all_set_volume(unsigned volume); - -/** - * Similar to mixer_all_get_volume(), but gets the volume only for - * software mixers. See #software_mixer_plugin. This function fails - * if no software mixer is configured. - */ -int -mixer_all_get_software_volume(void); - -/** - * Similar to mixer_all_set_volume(), but sets the volume only for - * software mixers. See #software_mixer_plugin. This function cannot - * fail, because the underlying software mixers cannot fail either. - */ -void -mixer_all_set_software_volume(unsigned volume); - -#endif diff --git a/src/mixer_api.h b/src/mixer_api.h index 29c1e00ca..f0c9a0937 100644 --- a/src/mixer_api.h +++ b/src/mixer_api.h @@ -46,7 +46,15 @@ struct mixer { bool failed; }; +#ifdef __cplusplus +extern "C" { +#endif + void mixer_init(struct mixer *mixer, const struct mixer_plugin *plugin); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/mixer_control.h b/src/mixer_control.h index 6c3468aca..46219e5dd 100644 --- a/src/mixer_control.h +++ b/src/mixer_control.h @@ -25,7 +25,7 @@ #ifndef MPD_MIXER_CONTROL_H #define MPD_MIXER_CONTROL_H -#include <glib.h> +#include "gerror.h" #include <stdbool.h> @@ -33,6 +33,10 @@ 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 +64,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_plugin.h b/src/mixer_plugin.h index 9532b95cb..2f3beed1d 100644 --- a/src/mixer_plugin.h +++ b/src/mixer_plugin.h @@ -27,7 +27,7 @@ #ifndef MPD_MIXER_PLUGIN_H #define MPD_MIXER_PLUGIN_H -#include <glib.h> +#include "gerror.h" #include <stdbool.h> diff --git a/src/mpd_error.h b/src/mpd_error.h index 219738ced..e0b7d29a4 100644 --- a/src/mpd_error.h +++ b/src/mpd_error.h @@ -20,6 +20,7 @@ #ifndef MPD_ERROR_H #define MPD_ERROR_H +#include <glib.h> #include <stdlib.h> /* This macro is used as an intermediate step to a proper error handling diff --git a/src/notify.c b/src/notify.c deleted file mode 100644 index 3c0112c91..000000000 --- a/src/notify.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "notify.h" - -void notify_init(struct notify *notify) -{ - notify->mutex = g_mutex_new(); - notify->cond = g_cond_new(); - notify->pending = false; -} - -void notify_deinit(struct notify *notify) -{ - g_mutex_free(notify->mutex); - g_cond_free(notify->cond); -} - -void notify_wait(struct notify *notify) -{ - g_mutex_lock(notify->mutex); - while (!notify->pending) - g_cond_wait(notify->cond, notify->mutex); - notify->pending = false; - g_mutex_unlock(notify->mutex); -} - -void notify_signal(struct notify *notify) -{ - g_mutex_lock(notify->mutex); - notify->pending = true; - g_cond_signal(notify->cond); - g_mutex_unlock(notify->mutex); -} - -void notify_clear(struct notify *notify) -{ - g_mutex_lock(notify->mutex); - notify->pending = false; - g_mutex_unlock(notify->mutex); -} diff --git a/src/notify.cxx b/src/notify.cxx new file mode 100644 index 000000000..64018968c --- /dev/null +++ b/src/notify.cxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "notify.hxx" + +void +notify::Wait() +{ + const ScopeLock protect(mutex); + while (!pending) + cond.wait(mutex); + pending = false; +} + +void +notify::Signal() +{ + const ScopeLock protect(mutex); + pending = true; + cond.signal(); +} + +void +notify::Clear() +{ + const ScopeLock protect(mutex); + pending = false; +} diff --git a/src/notify.h b/src/notify.h deleted file mode 100644 index 40821690c..000000000 --- a/src/notify.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_NOTIFY_H -#define MPD_NOTIFY_H - -#include <glib.h> - -#include <stdbool.h> - -struct notify { - GMutex *mutex; - GCond *cond; - bool pending; -}; - -void notify_init(struct notify *notify); - -void notify_deinit(struct notify *notify); - -/** - * Wait for a notification. Return immediately if we have already - * been notified since we last returned from notify_wait(). - */ -void notify_wait(struct notify *notify); - -/** - * Notify the thread. This function never blocks. - */ -void notify_signal(struct notify *notify); - -/** - * Clears a pending notification. - */ -void notify_clear(struct notify *notify); - -#endif diff --git a/src/notify.hxx b/src/notify.hxx new file mode 100644 index 000000000..6b9e95368 --- /dev/null +++ b/src/notify.hxx @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NOTIFY_HXX +#define MPD_NOTIFY_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +struct notify { + Mutex mutex; + Cond cond; + bool pending; + +#ifndef WIN32 + constexpr +#endif + notify():pending(false) {} + + /** + * Wait for a notification. Return immediately if we have already + * been notified since we last returned from notify_wait(). + */ + void Wait(); + + /** + * Notify the thread. This function never blocks. + */ + void Signal(); + + /** + * Clears a pending notification. + */ + void Clear(); +}; + +#endif diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/AlsaOutputPlugin.cxx new file mode 100644 index 000000000..1badeb63d --- /dev/null +++ b/src/output/AlsaOutputPlugin.cxx @@ -0,0 +1,855 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "AlsaOutputPlugin.hxx" +#include "output_api.h" +#include "mixer_list.h" +#include "pcm_export.h" + +#include <glib.h> +#include <alsa/asoundlib.h> + +#include <string> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "alsa" + +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API + +static const char default_device[] = "default"; + +enum { + MPD_ALSA_BUFFER_TIME_US = 500000, +}; + +#define MPD_ALSA_RETRY_NR 5 + +typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, + snd_pcm_uframes_t size); + +struct AlsaOutput { + struct audio_output base; + + struct pcm_export_state pcm_export; + + /** + * The configured name of the ALSA device; empty for the + * default device + */ + std::string device; + + /** use memory mapped I/O? */ + bool use_mmap; + + /** + * Enable DSD over USB according to the dCS suggested + * standard? + * + * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf + */ + bool dsd_usb; + + /** libasound's buffer_time setting (in microseconds) */ + unsigned int buffer_time; + + /** libasound's period_time setting (in microseconds) */ + unsigned int period_time; + + /** the mode flags passed to snd_pcm_open */ + int mode; + + /** the libasound PCM device handle */ + snd_pcm_t *pcm; + + /** + * a pointer to the libasound writei() function, which is + * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the + * use_mmap configuration + */ + alsa_writei_t *writei; + + /** + * The size of one audio frame passed to method play(). + */ + size_t in_frame_size; + + /** + * The size of one audio frame passed to libasound. + */ + size_t out_frame_size; + + /** + * The size of one period, in number of frames. + */ + snd_pcm_uframes_t period_frames; + + /** + * The number of frames written in the current period. + */ + snd_pcm_uframes_t period_position; + + /** + * This buffer gets allocated after opening the ALSA device. + * It contains silence samples, enough to fill one period (see + * #period_frames). + */ + void *silence; + + AlsaOutput():mode(0), writei(snd_pcm_writei) { + } + + bool Init(const config_param *param, GError **error_r) { + return ao_base_init(&base, &alsa_output_plugin, + param, error_r); + } + + void Deinit() { + ao_base_finish(&base); + } +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +alsa_output_quark(void) +{ + return g_quark_from_static_string("alsa_output"); +} + +static const char * +alsa_device(const AlsaOutput *ad) +{ + return ad->device.empty() ? default_device : ad->device.c_str(); +} + +static void +alsa_configure(AlsaOutput *ad, const struct config_param *param) +{ + ad->device = config_get_block_string(param, "device", ""); + + ad->use_mmap = config_get_block_bool(param, "use_mmap", false); + + ad->dsd_usb = config_get_block_bool(param, "dsd_usb", false); + + ad->buffer_time = config_get_block_unsigned(param, "buffer_time", + MPD_ALSA_BUFFER_TIME_US); + ad->period_time = config_get_block_unsigned(param, "period_time", 0); + +#ifdef SND_PCM_NO_AUTO_RESAMPLE + if (!config_get_block_bool(param, "auto_resample", true)) + ad->mode |= SND_PCM_NO_AUTO_RESAMPLE; +#endif + +#ifdef SND_PCM_NO_AUTO_CHANNELS + if (!config_get_block_bool(param, "auto_channels", true)) + ad->mode |= SND_PCM_NO_AUTO_CHANNELS; +#endif + +#ifdef SND_PCM_NO_AUTO_FORMAT + if (!config_get_block_bool(param, "auto_format", true)) + ad->mode |= SND_PCM_NO_AUTO_FORMAT; +#endif +} + +static struct audio_output * +alsa_init(const struct config_param *param, GError **error_r) +{ + AlsaOutput *ad = new AlsaOutput(); + + if (!ad->Init(param, error_r)) { + delete ad; + return NULL; + } + + alsa_configure(ad, param); + + return &ad->base; +} + +static void +alsa_finish(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + ad->Deinit(); + delete ad; + + /* free libasound's config cache */ + snd_config_update_free_global(); +} + +static bool +alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + pcm_export_init(&ad->pcm_export); + return true; +} + +static void +alsa_output_disable(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + pcm_export_deinit(&ad->pcm_export); +} + +static bool +alsa_test_default_device(void) +{ + snd_pcm_t *handle; + + int ret = snd_pcm_open(&handle, default_device, + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (ret) { + g_message("Error opening default ALSA device: %s\n", + snd_strerror(-ret)); + return false; + } else + snd_pcm_close(handle); + + return true; +} + +static snd_pcm_format_t +get_bitformat(enum sample_format sample_format) +{ + switch (sample_format) { + case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: + return SND_PCM_FORMAT_UNKNOWN; + + case SAMPLE_FORMAT_S8: + return SND_PCM_FORMAT_S8; + + case SAMPLE_FORMAT_S16: + return SND_PCM_FORMAT_S16; + + case SAMPLE_FORMAT_S24_P32: + return SND_PCM_FORMAT_S24; + + case SAMPLE_FORMAT_S32: + return SND_PCM_FORMAT_S32; + + case SAMPLE_FORMAT_FLOAT: + return SND_PCM_FORMAT_FLOAT; + } + + assert(false); + return SND_PCM_FORMAT_UNKNOWN; +} + +static snd_pcm_format_t +byteswap_bitformat(snd_pcm_format_t fmt) +{ + switch(fmt) { + case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE; + case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE; + case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE; + case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE; + case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE; + + case SND_PCM_FORMAT_S24_3BE: + return SND_PCM_FORMAT_S24_3LE; + + case SND_PCM_FORMAT_S24_3LE: + return SND_PCM_FORMAT_S24_3BE; + + case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE; + default: return SND_PCM_FORMAT_UNKNOWN; + } +} + +static snd_pcm_format_t +alsa_to_packed_format(snd_pcm_format_t fmt) +{ + switch (fmt) { + case SND_PCM_FORMAT_S24_LE: + return SND_PCM_FORMAT_S24_3LE; + + case SND_PCM_FORMAT_S24_BE: + return SND_PCM_FORMAT_S24_3BE; + + default: + return SND_PCM_FORMAT_UNKNOWN; + } +} + +static int +alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + snd_pcm_format_t fmt, bool *packed_r) +{ + int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); + if (err == 0) + *packed_r = false; + + if (err != -EINVAL) + return err; + + fmt = alsa_to_packed_format(fmt); + if (fmt == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); + if (err == 0) + *packed_r = true; + + return err; +} + +/** + * Attempts to configure the specified sample format, and tries the + * reversed host byte order if was not supported. + */ +static int +alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + enum sample_format sample_format, + bool *packed_r, bool *reverse_endian_r) +{ + snd_pcm_format_t alsa_format = get_bitformat(sample_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, + packed_r); + if (err == 0) + *reverse_endian_r = false; + + if (err != -EINVAL) + return err; + + alsa_format = byteswap_bitformat(alsa_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r); + if (err == 0) + *reverse_endian_r = true; + + return err; +} + +/** + * Configure a sample format, and probe other formats if that fails. + */ +static int +alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + struct audio_format *audio_format, + bool *packed_r, bool *reverse_endian_r) +{ + /* try the input format first */ + + int err = alsa_output_try_format(pcm, hwparams, + sample_format(audio_format->format), + packed_r, reverse_endian_r); + + /* if unsupported by the hardware, try other formats */ + + static const enum sample_format probe_formats[] = { + SAMPLE_FORMAT_S24_P32, + SAMPLE_FORMAT_S32, + SAMPLE_FORMAT_S16, + SAMPLE_FORMAT_S8, + SAMPLE_FORMAT_UNDEFINED, + }; + + for (unsigned i = 0; + err == -EINVAL && probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; + ++i) { + const enum sample_format mpd_format = probe_formats[i]; + if (mpd_format == audio_format->format) + continue; + + err = alsa_output_try_format(pcm, hwparams, mpd_format, + packed_r, reverse_endian_r); + if (err == 0) + audio_format->format = mpd_format; + } + + return err; +} + +/** + * Set up the snd_pcm_t object which was opened by the caller. Set up + * the configured settings and the audio format. + */ +static bool +alsa_setup(AlsaOutput *ad, struct audio_format *audio_format, + bool *packed_r, bool *reverse_endian_r, GError **error) +{ + unsigned int sample_rate = audio_format->sample_rate; + unsigned int channels = audio_format->channels; + int err; + const char *cmd = NULL; + int retry = MPD_ALSA_RETRY_NR; + unsigned int period_time, period_time_ro; + unsigned int buffer_time; + + period_time_ro = period_time = ad->period_time; +configure_hw: + /* configure HW params */ + snd_pcm_hw_params_t *hwparams; + snd_pcm_hw_params_alloca(&hwparams); + cmd = "snd_pcm_hw_params_any"; + err = snd_pcm_hw_params_any(ad->pcm, hwparams); + if (err < 0) + goto error; + + if (ad->use_mmap) { + err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, + SND_PCM_ACCESS_MMAP_INTERLEAVED); + if (err < 0) { + g_warning("Cannot set mmap'ed mode on ALSA device \"%s\": %s\n", + alsa_device(ad), snd_strerror(-err)); + g_warning("Falling back to direct write mode\n"); + ad->use_mmap = false; + } else + ad->writei = snd_pcm_mmap_writei; + } + + if (!ad->use_mmap) { + cmd = "snd_pcm_hw_params_set_access"; + err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) + goto error; + ad->writei = snd_pcm_writei; + } + + err = alsa_output_setup_format(ad->pcm, hwparams, audio_format, + packed_r, reverse_endian_r); + if (err < 0) { + g_set_error(error, alsa_output_quark(), err, + "ALSA device \"%s\" does not support format %s: %s", + alsa_device(ad), + sample_format_to_string(sample_format(audio_format->format)), + snd_strerror(-err)); + return false; + } + + snd_pcm_format_t format; + if (snd_pcm_hw_params_get_format(hwparams, &format) == 0) + g_debug("format=%s (%s)", snd_pcm_format_name(format), + snd_pcm_format_description(format)); + + err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams, + &channels); + if (err < 0) { + g_set_error(error, alsa_output_quark(), err, + "ALSA device \"%s\" does not support %i channels: %s", + alsa_device(ad), (int)audio_format->channels, + snd_strerror(-err)); + return false; + } + audio_format->channels = (int8_t)channels; + + err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams, + &sample_rate, NULL); + if (err < 0 || sample_rate == 0) { + g_set_error(error, alsa_output_quark(), err, + "ALSA device \"%s\" does not support %u Hz audio", + alsa_device(ad), audio_format->sample_rate); + return false; + } + audio_format->sample_rate = sample_rate; + + snd_pcm_uframes_t buffer_size_min, buffer_size_max; + snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min); + snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max); + unsigned buffer_time_min, buffer_time_max; + snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0); + snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0); + g_debug("buffer: size=%u..%u time=%u..%u", + (unsigned)buffer_size_min, (unsigned)buffer_size_max, + buffer_time_min, buffer_time_max); + + snd_pcm_uframes_t period_size_min, period_size_max; + snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0); + snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0); + unsigned period_time_min, period_time_max; + snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0); + snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0); + g_debug("period: size=%u..%u time=%u..%u", + (unsigned)period_size_min, (unsigned)period_size_max, + period_time_min, period_time_max); + + if (ad->buffer_time > 0) { + buffer_time = ad->buffer_time; + cmd = "snd_pcm_hw_params_set_buffer_time_near"; + err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams, + &buffer_time, NULL); + if (err < 0) + goto error; + } else { + err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time, + NULL); + if (err < 0) + buffer_time = 0; + } + + if (period_time_ro == 0 && buffer_time >= 10000) { + period_time_ro = period_time = buffer_time / 4; + + g_debug("default period_time = buffer_time/4 = %u/4 = %u", + buffer_time, period_time); + } + + if (period_time_ro > 0) { + period_time = period_time_ro; + cmd = "snd_pcm_hw_params_set_period_time_near"; + err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams, + &period_time, NULL); + if (err < 0) + goto error; + } + + cmd = "snd_pcm_hw_params"; + err = snd_pcm_hw_params(ad->pcm, hwparams); + if (err == -EPIPE && --retry > 0 && period_time_ro > 0) { + period_time_ro = period_time_ro >> 1; + goto configure_hw; + } else if (err < 0) + goto error; + if (retry != MPD_ALSA_RETRY_NR) + g_debug("ALSA period_time set to %d\n", period_time); + + snd_pcm_uframes_t alsa_buffer_size; + cmd = "snd_pcm_hw_params_get_buffer_size"; + err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size); + if (err < 0) + goto error; + + snd_pcm_uframes_t alsa_period_size; + cmd = "snd_pcm_hw_params_get_period_size"; + err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size, + NULL); + if (err < 0) + goto error; + + /* configure SW params */ + snd_pcm_sw_params_t *swparams; + snd_pcm_sw_params_alloca(&swparams); + + cmd = "snd_pcm_sw_params_current"; + err = snd_pcm_sw_params_current(ad->pcm, swparams); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params_set_start_threshold"; + err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams, + alsa_buffer_size - + alsa_period_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params_set_avail_min"; + err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams, + alsa_period_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params"; + err = snd_pcm_sw_params(ad->pcm, swparams); + if (err < 0) + goto error; + + g_debug("buffer_size=%u period_size=%u", + (unsigned)alsa_buffer_size, (unsigned)alsa_period_size); + + if (alsa_period_size == 0) + /* this works around a SIGFPE bug that occurred when + an ALSA driver indicated period_size==0; this + caused a division by zero in alsa_play(). By using + the fallback "1", we make sure that this won't + happen again. */ + alsa_period_size = 1; + + ad->period_frames = alsa_period_size; + ad->period_position = 0; + + ad->silence = g_malloc(snd_pcm_frames_to_bytes(ad->pcm, + alsa_period_size)); + snd_pcm_format_set_silence(format, ad->silence, + alsa_period_size * channels); + + return true; + +error: + g_set_error(error, alsa_output_quark(), err, + "Error opening ALSA device \"%s\" (%s): %s", + alsa_device(ad), cmd, snd_strerror(-err)); + return false; +} + +static bool +alsa_setup_dsd(AlsaOutput *ad, struct audio_format *audio_format, + bool *shift8_r, bool *packed_r, bool *reverse_endian_r, + GError **error_r) +{ + assert(ad->dsd_usb); + assert(audio_format->format == SAMPLE_FORMAT_DSD); + + /* pass 24 bit to alsa_setup() */ + + struct audio_format usb_format = *audio_format; + usb_format.format = SAMPLE_FORMAT_S24_P32; + usb_format.sample_rate /= 2; + + const struct audio_format check = usb_format; + + if (!alsa_setup(ad, &usb_format, packed_r, reverse_endian_r, error_r)) + return false; + + /* if the device allows only 32 bit, shift all DSD-over-USB + samples left by 8 bit and leave the lower 8 bit cleared; + the DSD-over-USB documentation does not specify whether + this is legal, but there is anecdotical evidence that this + is possible (and the only option for some devices) */ + *shift8_r = usb_format.format == SAMPLE_FORMAT_S32; + if (usb_format.format == SAMPLE_FORMAT_S32) + usb_format.format = SAMPLE_FORMAT_S24_P32; + + if (!audio_format_equals(&usb_format, &check)) { + /* no bit-perfect playback, which is required + for DSD over USB */ + g_set_error(error_r, alsa_output_quark(), 0, + "Failed to configure DSD-over-USB on ALSA device \"%s\"", + alsa_device(ad)); + g_free(ad->silence); + return false; + } + + return true; +} + +static bool +alsa_setup_or_dsd(AlsaOutput *ad, struct audio_format *audio_format, + GError **error_r) +{ + bool shift8 = false, packed, reverse_endian; + + const bool dsd_usb = ad->dsd_usb && + audio_format->format == SAMPLE_FORMAT_DSD; + const bool success = dsd_usb + ? alsa_setup_dsd(ad, audio_format, + &shift8, &packed, &reverse_endian, + error_r) + : alsa_setup(ad, audio_format, &packed, &reverse_endian, + error_r); + if (!success) + return false; + + pcm_export_open(&ad->pcm_export, + sample_format(audio_format->format), + audio_format->channels, + dsd_usb, shift8, packed, reverse_endian); + return true; +} + +static bool +alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + int err = snd_pcm_open(&ad->pcm, alsa_device(ad), + SND_PCM_STREAM_PLAYBACK, ad->mode); + if (err < 0) { + g_set_error(error, alsa_output_quark(), err, + "Failed to open ALSA device \"%s\": %s", + alsa_device(ad), snd_strerror(err)); + return false; + } + + g_debug("opened %s type=%s", snd_pcm_name(ad->pcm), + snd_pcm_type_name(snd_pcm_type(ad->pcm))); + + if (!alsa_setup_or_dsd(ad, audio_format, error)) { + snd_pcm_close(ad->pcm); + return false; + } + + ad->in_frame_size = audio_format_frame_size(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(AlsaOutput *ad, int err) +{ + if (err == -EPIPE) { + g_debug("Underrun on ALSA device \"%s\"\n", alsa_device(ad)); + } else if (err == -ESTRPIPE) { + g_debug("ALSA device \"%s\" was suspended\n", alsa_device(ad)); + } + + switch (snd_pcm_state(ad->pcm)) { + case SND_PCM_STATE_PAUSED: + err = snd_pcm_pause(ad->pcm, /* disable */ 0); + break; + case SND_PCM_STATE_SUSPENDED: + err = snd_pcm_resume(ad->pcm); + if (err == -EAGAIN) + return 0; + /* fall-through to snd_pcm_prepare: */ + case SND_PCM_STATE_SETUP: + case SND_PCM_STATE_XRUN: + ad->period_position = 0; + err = snd_pcm_prepare(ad->pcm); + + if (err == 0) { + /* this works around a driver bug observed on + the Raspberry Pi: after snd_pcm_drop(), the + whole ring buffer must be invalidated, but + the snd_pcm_prepare() call above makes the + driver play random data that just happens + to be still in the buffer; by adding and + cancelling some silence, this bug does not + occur */ + alsa_write_silence(ad, ad->period_frames); + + /* cancel the silence data right away to avoid + increasing latency; even though this + function call invalidates the portion of + silence, the driver seems to avoid the + bug */ + snd_pcm_reset(ad->pcm); + } + + break; + case SND_PCM_STATE_DISCONNECTED: + break; + /* this is no error, so just keep running */ + case SND_PCM_STATE_RUNNING: + err = 0; + break; + default: + /* unknown state, do nothing */ + break; + } + + return err; +} + +static void +alsa_drain(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) + return; + + if (ad->period_position > 0) { + /* generate some silence to finish the partial + period */ + snd_pcm_uframes_t nframes = + ad->period_frames - ad->period_position; + alsa_write_silence(ad, nframes); + } + + snd_pcm_drain(ad->pcm); + + ad->period_position = 0; +} + +static void +alsa_cancel(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + ad->period_position = 0; + + snd_pcm_drop(ad->pcm); +} + +static void +alsa_close(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + snd_pcm_close(ad->pcm); + g_free(ad->silence); +} + +static size_t +alsa_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + assert(size % ad->in_frame_size == 0); + + chunk = pcm_export(&ad->pcm_export, chunk, size, &size); + + assert(size % ad->out_frame_size == 0); + + size /= ad->out_frame_size; + + while (true) { + snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); + if (ret > 0) { + ad->period_position = (ad->period_position + ret) + % ad->period_frames; + + size_t bytes_written = ret * ad->out_frame_size; + return pcm_export_source_size(&ad->pcm_export, + bytes_written); + } + + if (ret < 0 && ret != -EAGAIN && ret != -EINTR && + alsa_recover(ad, ret) < 0) { + g_set_error(error, alsa_output_quark(), errno, + "%s", snd_strerror(-errno)); + return 0; + } + } +} + +const struct audio_output_plugin alsa_output_plugin = { + "alsa", + alsa_test_default_device, + alsa_init, + alsa_finish, + alsa_output_enable, + alsa_output_disable, + alsa_open, + alsa_close, + nullptr, + nullptr, + alsa_play, + alsa_drain, + alsa_cancel, + nullptr, + + &alsa_mixer_plugin, +}; diff --git a/src/output/AlsaOutputPlugin.hxx b/src/output/AlsaOutputPlugin.hxx new file mode 100644 index 000000000..dc7e639a8 --- /dev/null +++ b/src/output/AlsaOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ALSA_OUTPUT_PLUGIN_HXX +#define MPD_ALSA_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin alsa_output_plugin; + +#endif diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx new file mode 100644 index 000000000..0a00ee2f9 --- /dev/null +++ b/src/output/HttpdClient.cxx @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "HttpdClient.hxx" +#include "HttpdInternal.hxx" +#include "util/fifo_buffer.h" +#include "Page.hxx" +#include "IcyMetaDataServer.hxx" +#include "SocketError.hxx" + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "httpd_output" + +HttpdClient::~HttpdClient() +{ + if (state == RESPONSE) { + if (current_page != nullptr) + current_page->Unref(); + + for (auto page : pages) + page->Unref(); + } + + if (metadata) + metadata->Unref(); +} + +void +HttpdClient::Close() +{ + httpd->RemoveClient(*this); +} + +void +HttpdClient::LockClose() +{ + const ScopeLock protect(httpd->mutex); + Close(); +} + +void +HttpdClient::BeginResponse() +{ + assert(state != RESPONSE); + + state = RESPONSE; + current_page = nullptr; + + httpd->SendHeader(*this); +} + +/** + * Handle a line of the HTTP request. + */ +bool +HttpdClient::HandleLine(const char *line) +{ + assert(state != RESPONSE); + + if (state == REQUEST) { + if (strncmp(line, "GET /", 5) != 0) { + /* only GET is supported */ + g_warning("malformed request line from client"); + return false; + } + + line = strchr(line + 5, ' '); + if (line == nullptr || strncmp(line + 1, "HTTP/", 5) != 0) { + /* HTTP/0.9 without request headers */ + BeginResponse(); + return true; + } + + /* after the request line, request headers follow */ + state = HEADERS; + return true; + } else { + if (*line == 0) { + /* empty line: request is finished */ + BeginResponse(); + return true; + } + + if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) { + /* Send icy metadata */ + metadata_requested = metadata_supported; + return true; + } + + if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { + /* Send as dlna */ + dlna_streaming_requested = true; + /* metadata is not supported by dlna streaming, so disable it */ + metadata_supported = false; + metadata_requested = false; + return true; + } + + /* expect more request headers */ + return true; + } +} + +/** + * Sends the status line and response headers to the client. + */ +bool +HttpdClient::SendResponse() +{ + char buffer[1024]; + assert(state == RESPONSE); + + if (dlna_streaming_requested) { + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 206 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: 10000\r\n" + "Content-RangeX: 0-1000000/1000000\r\n" + "transferMode.dlna.org: Streaming\r\n" + "Accept-Ranges: bytes\r\n" + "Connection: close\r\n" + "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" + "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" + "\r\n", + httpd->content_type); + + } else if (metadata_requested) { + gchar *metadata_header; + + metadata_header = + icy_server_metadata_header(httpd->name, httpd->genre, + httpd->website, + httpd->content_type, + metaint); + + g_strlcpy(buffer, metadata_header, sizeof(buffer)); + + g_free(metadata_header); + + } else { /* revert to a normal HTTP request */ + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + httpd->content_type); + } + + ssize_t nbytes = SocketMonitor::Write(buffer, strlen(buffer)); + if (gcc_unlikely(nbytes < 0)) { + const SocketErrorMessage msg; + g_warning("failed to write to client: %s", (const char *)msg); + Close(); + return false; + } + + return true; +} + +HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop, + bool _metadata_supported) + :BufferedSocket(_fd, _loop), + httpd(_httpd), + state(REQUEST), + dlna_streaming_requested(false), + metadata_supported(_metadata_supported), + metadata_requested(false), metadata_sent(true), + metaint(8192), /*TODO: just a std value */ + metadata(nullptr), + metadata_current_position(0), metadata_fill(0) +{ +} + +size_t +HttpdClient::GetQueueSize() const +{ + if (state != RESPONSE) + return 0; + + size_t size = 0; + for (auto page : pages) + size += page->size; + return size; +} + +void +HttpdClient::CancelQueue() +{ + if (state != RESPONSE) + return; + + for (auto page : pages) + page->Unref(); + pages.clear(); + + if (current_page == nullptr) + CancelWrite(); +} + +ssize_t +HttpdClient::TryWritePage(const Page &page, size_t position) +{ + assert(position < page.size); + + return Write(page.data + position, page.size - position); +} + +ssize_t +HttpdClient::TryWritePageN(const Page &page, size_t position, ssize_t n) +{ + return n >= 0 + ? Write(page.data + position, n) + : TryWritePage(page, position); +} + +ssize_t +HttpdClient::GetBytesTillMetaData() const +{ + if (metadata_requested && + current_page->size - current_position > metaint - metadata_fill) + return metaint - metadata_fill; + + return -1; +} + +inline bool +HttpdClient::TryWrite() +{ + const ScopeLock protect(httpd->mutex); + + assert(state == RESPONSE); + + if (current_page == nullptr) { + if (pages.empty()) { + /* another thread has removed the event source + while this thread was waiting for + httpd->mutex */ + CancelWrite(); + return true; + } + + current_page = pages.front(); + pages.pop_front(); + current_position = 0; + } + + const ssize_t bytes_to_write = GetBytesTillMetaData(); + if (bytes_to_write == 0) { + if (!metadata_sent) { + ssize_t nbytes = TryWritePage(*metadata, + metadata_current_position); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + g_warning("failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + metadata_current_position += nbytes; + + if (metadata->size - metadata_current_position == 0) { + metadata_fill = 0; + metadata_current_position = 0; + metadata_sent = true; + } + } else { + guchar empty_data = 0; + + ssize_t nbytes = Write(&empty_data, 1); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + g_warning("failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + metadata_fill = 0; + metadata_current_position = 0; + } + } else { + ssize_t nbytes = + TryWritePageN(*current_page, current_position, + bytes_to_write); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + g_warning("failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + current_position += nbytes; + assert(current_position <= current_page->size); + + if (metadata_requested) + metadata_fill += nbytes; + + if (current_position >= current_page->size) { + current_page->Unref(); + current_page = nullptr; + + if (pages.empty()) + /* all pages are sent: remove the + event source */ + CancelWrite(); + } + } + + return true; +} + +void +HttpdClient::PushPage(Page *page) +{ + if (state != RESPONSE) + /* the client is still writing the HTTP request */ + return; + + page->Ref(); + pages.push_back(page); + + ScheduleWrite(); +} + +void +HttpdClient::PushMetaData(Page *page) +{ + if (metadata) { + metadata->Unref(); + metadata = nullptr; + } + + g_return_if_fail (page); + + page->Ref(); + metadata = page; + metadata_sent = false; +} + +bool +HttpdClient::OnSocketReady(unsigned flags) +{ + if (!BufferedSocket::OnSocketReady(flags)) + return false; + + if (flags & WRITE) + if (!TryWrite()) + return false; + + return true; +} + +BufferedSocket::InputResult +HttpdClient::OnSocketInput(const void *data, size_t length) +{ + if (state == RESPONSE) { + g_warning("unexpected input from client"); + LockClose(); + return InputResult::CLOSED; + } + + const char *line = (const char *)data; + const char *newline = (const char *)memchr(line, '\n', length); + if (newline == nullptr) + return InputResult::MORE; + + ConsumeInput(newline + 1 - line); + + if (newline > line && newline[-1] == '\r') + --newline; + + /* terminate the string at the end of the line; the const_cast + is a dirty hack */ + *const_cast<char *>(newline) = 0; + + if (!HandleLine(line)) { + assert(state == RESPONSE); + LockClose(); + return InputResult::CLOSED; + } + + if (state == RESPONSE && !SendResponse()) + return InputResult::CLOSED; + + return InputResult::AGAIN; +} + +void +HttpdClient::OnSocketError(GError *error) +{ + g_warning("error on HTTP client: %s", error->message); + g_error_free(error); +} + +void +HttpdClient::OnSocketClosed() +{ + LockClose(); +} diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx new file mode 100644 index 000000000..46196d2e3 --- /dev/null +++ b/src/output/HttpdClient.hxx @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_HTTPD_CLIENT_HXX +#define MPD_OUTPUT_HTTPD_CLIENT_HXX + +#include "event/BufferedSocket.hxx" +#include "gcc.h" + +#include <list> + +#include <stddef.h> + +struct HttpdOutput; +class Page; + +class HttpdClient final : public BufferedSocket { + /** + * The httpd output object this client is connected to. + */ + HttpdOutput *const httpd; + + /** + * The current state of the client. + */ + enum { + /** reading the request line */ + REQUEST, + + /** reading the request headers */ + HEADERS, + + /** sending the HTTP response */ + RESPONSE, + } state; + + /** + * A queue of #Page objects to be sent to the client. + */ + std::list<Page *> pages; + + /** + * The #page which is currently being sent to the client. + */ + Page *current_page; + + /** + * The amount of bytes which were already sent from + * #current_page. + */ + size_t current_position; + + /** + * If DLNA streaming was an option. + */ + bool dlna_streaming_requested; + + /* ICY */ + + /** + * Do we support sending Icy-Metadata to the client? This is + * disabled if the httpd audio output uses encoder tags. + */ + bool metadata_supported; + + /** + * If we should sent icy metadata. + */ + bool metadata_requested; + + /** + * If the current metadata was already sent to the client. + */ + bool metadata_sent; + + /** + * The amount of streaming data between each metadata block + */ + guint metaint; + + /** + * The metadata as #Page which is currently being sent to the client. + */ + Page *metadata; + + /* + * The amount of bytes which were already sent from the metadata. + */ + size_t metadata_current_position; + + /** + * The amount of streaming data sent to the client + * since the last icy information was sent. + */ + guint metadata_fill; + +public: + /** + * @param httpd the HTTP output device + * @param fd the socket file descriptor + */ + HttpdClient(HttpdOutput *httpd, int _fd, EventLoop &_loop, + bool _metadata_supported); + + /** + * Note: this does not remove the client from the + * #HttpdOutput object. + */ + ~HttpdClient(); + + /** + * Frees the client and removes it from the server's client list. + */ + void Close(); + + void LockClose(); + + /** + * Returns the total size of this client's page queue. + */ + gcc_pure + size_t GetQueueSize() const; + + /** + * Clears the page queue. + */ + void CancelQueue(); + + /** + * Handle a line of the HTTP request. + */ + bool HandleLine(const char *line); + + /** + * Switch the client to the "RESPONSE" state. + */ + void BeginResponse(); + + /** + * Sends the status line and response headers to the client. + */ + bool SendResponse(); + + gcc_pure + ssize_t GetBytesTillMetaData() const; + + ssize_t TryWritePage(const Page &page, size_t position); + ssize_t TryWritePageN(const Page &page, size_t position, ssize_t n); + + bool TryWrite(); + + /** + * Appends a page to the client's queue. + */ + void PushPage(Page *page); + + /** + * Sends the passed metadata. + */ + void PushMetaData(Page *page); + +protected: + virtual bool OnSocketReady(unsigned flags) override; + virtual InputResult OnSocketInput(const void *data, + size_t length) override; + virtual void OnSocketError(GError *error) override; + virtual void OnSocketClosed() override; +}; + +#endif diff --git a/src/output/HttpdInternal.hxx b/src/output/HttpdInternal.hxx new file mode 100644 index 000000000..4b526bcde --- /dev/null +++ b/src/output/HttpdInternal.hxx @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Internal declarations for the "httpd" audio output plugin. + */ + +#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H +#define MPD_OUTPUT_HTTPD_INTERNAL_H + +#include "output_internal.h" +#include "timer.h" +#include "thread/Mutex.hxx" +#include "event/ServerSocket.hxx" + +#include <forward_list> + +struct config_param; +class EventLoop; +class ServerSocket; +class HttpdClient; +class Page; + +struct HttpdOutput final : private ServerSocket { + struct audio_output base; + + /** + * True if the audio output is open and accepts client + * connections. + */ + bool open; + + /** + * The configured encoder plugin. + */ + struct encoder *encoder; + + /** + * Number of bytes which were fed into the encoder, without + * ever receiving new output. This is used to estimate + * whether MPD should manually flush the encoder, to avoid + * buffer underruns in the client. + */ + size_t unflushed_input; + + /** + * The MIME type produced by the #encoder. + */ + const char *content_type; + + /** + * This mutex protects the listener socket and the client + * list. + */ + mutable Mutex mutex; + + /** + * A #timer object to synchronize this output with the + * wallclock. + */ + struct timer *timer; + + /** + * The header page, which is sent to every client on connect. + */ + Page *header; + + /** + * The metadata, which is sent to every client. + */ + Page *metadata; + + /** + * The configured name. + */ + char const *name; + /** + * The configured genre. + */ + char const *genre; + /** + * The configured website address. + */ + char const *website; + + /** + * A linked list containing all clients which are currently + * connected. + */ + std::forward_list<HttpdClient> clients; + + /** + * A temporary buffer for the httpd_output_read_page() + * function. + */ + char buffer[32768]; + + /** + * The maximum and current number of clients connected + * at the same time. + */ + guint clients_max, clients_cnt; + + HttpdOutput(EventLoop &_loop); + ~HttpdOutput(); + + bool Configure(const config_param *param, GError **error_r); + + bool Bind(GError **error_r); + void Unbind(); + + /** + * Caller must lock the mutex. + */ + bool OpenEncoder(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 000000000..cb515e657 --- /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/HttpdOutputPlugin.hxx b/src/output/HttpdOutputPlugin.hxx new file mode 100644 index 000000000..c74d2bd4a --- /dev/null +++ b/src/output/HttpdOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_HTTPD_OUTPUT_PLUGIN_HXX +#define MPD_HTTPD_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin httpd_output_plugin; + +#endif diff --git a/src/output/NullOutputPlugin.cxx b/src/output/NullOutputPlugin.cxx new file mode 100644 index 000000000..3596cbdcb --- /dev/null +++ b/src/output/NullOutputPlugin.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 "NullOutputPlugin.hxx" +#include "output_api.h" +#include "timer.h" + +#include <glib.h> + +#include <assert.h> + +struct NullOutput { + struct audio_output base; + + bool sync; + + struct timer *timer; +}; + +static struct audio_output * +null_init(const struct config_param *param, GError **error_r) +{ + NullOutput *nd = g_new(NullOutput, 1); + + if (!ao_base_init(&nd->base, &null_output_plugin, param, error_r)) { + g_free(nd); + return NULL; + } + + nd->sync = config_get_block_bool(param, "sync", true); + + return &nd->base; +} + +static void +null_finish(struct audio_output *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + ao_base_finish(&nd->base); + g_free(nd); +} + +static bool +null_open(struct audio_output *ao, struct audio_format *audio_format, + G_GNUC_UNUSED GError **error) +{ + NullOutput *nd = (NullOutput *)ao; + + if (nd->sync) + nd->timer = timer_new(audio_format); + + return true; +} + +static void +null_close(struct audio_output *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + if (nd->sync) + timer_free(nd->timer); +} + +static unsigned +null_delay(struct audio_output *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + return nd->sync && nd->timer->started + ? timer_delay(nd->timer) + : 0; +} + +static size_t +null_play(struct audio_output *ao, G_GNUC_UNUSED const void *chunk, size_t size, + G_GNUC_UNUSED GError **error) +{ + NullOutput *nd = (NullOutput *)ao; + struct timer *timer = nd->timer; + + if (!nd->sync) + return size; + + if (!timer->started) + timer_start(timer); + timer_add(timer, size); + + return size; +} + +static void +null_cancel(struct audio_output *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + if (!nd->sync) + return; + + timer_reset(nd->timer); +} + +const struct audio_output_plugin null_output_plugin = { + "null", + nullptr, + null_init, + null_finish, + nullptr, + nullptr, + null_open, + null_close, + null_delay, + nullptr, + null_play, + nullptr, + null_cancel, + nullptr, + nullptr, +}; diff --git a/src/output/NullOutputPlugin.hxx b/src/output/NullOutputPlugin.hxx new file mode 100644 index 000000000..a58f1cb13 --- /dev/null +++ b/src/output/NullOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NULL_OUTPUT_PLUGIN_HXX +#define MPD_NULL_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin null_output_plugin; + +#endif diff --git a/src/output/OSXOutputPlugin.cxx b/src/output/OSXOutputPlugin.cxx new file mode 100644 index 000000000..5a04fe1db --- /dev/null +++ b/src/output/OSXOutputPlugin.cxx @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OSXOutputPlugin.hxx" +#include "output_api.h" +#include "util/fifo_buffer.h" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +#include <glib.h> +#include <CoreAudio/AudioHardware.h> +#include <AudioUnit/AudioUnit.h> +#include <CoreServices/CoreServices.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "osx" + +struct OSXOutput { + struct audio_output base; + + /* configuration settings */ + OSType component_subtype; + /* only applicable with kAudioUnitSubType_HALOutput */ + const char *device_name; + + AudioUnit au; + Mutex mutex; + Cond condition; + + struct fifo_buffer *buffer; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +osx_output_quark(void) +{ + return g_quark_from_static_string("osx_output"); +} + +static bool +osx_output_test_default_device(void) +{ + /* on a Mac, this is always the default plugin, if nothing + else is configured */ + return true; +} + +static void +osx_output_configure(OSXOutput *oo, const struct config_param *param) +{ + const char *device = config_get_block_string(param, "device", NULL); + + if (device == NULL || 0 == strcmp(device, "default")) { + oo->component_subtype = kAudioUnitSubType_DefaultOutput; + oo->device_name = NULL; + } + else if (0 == strcmp(device, "system")) { + oo->component_subtype = kAudioUnitSubType_SystemOutput; + oo->device_name = NULL; + } + else { + oo->component_subtype = kAudioUnitSubType_HALOutput; + /* XXX am I supposed to g_strdup() this? */ + oo->device_name = device; + } +} + +static struct audio_output * +osx_output_init(const struct config_param *param, GError **error_r) +{ + OSXOutput *oo = new OSXOutput(); + if (!ao_base_init(&oo->base, &osx_output_plugin, param, error_r)) { + delete oo; + return NULL; + } + + osx_output_configure(oo, param); + + return &oo->base; +} + +static void +osx_output_finish(struct audio_output *ao) +{ + OSXOutput *oo = (OSXOutput *)ao; + + delete oo; +} + +static bool +osx_output_set_device(OSXOutput *oo, GError **error) +{ + bool ret = true; + OSStatus status; + UInt32 size, numdevices; + AudioDeviceID *deviceids = NULL; + char name[256]; + unsigned int i; + + if (oo->component_subtype != kAudioUnitSubType_HALOutput) + goto done; + + /* how many audio devices are there? */ + status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, + &size, + NULL); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine number of OS X audio devices: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + + /* what are the available audio device IDs? */ + numdevices = size / sizeof(AudioDeviceID); + deviceids = new AudioDeviceID[numdevices]; + status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, + &size, + deviceids); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine OS X audio device IDs: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + + /* which audio device matches oo->device_name? */ + for (i = 0; i < numdevices; i++) { + size = sizeof(name); + status = AudioDeviceGetProperty(deviceids[i], 0, false, + kAudioDevicePropertyDeviceName, + &size, name); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine OS X device name " + "(device %u): %s", + (unsigned int) deviceids[i], + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + if (strcmp(oo->device_name, name) == 0) { + g_debug("found matching device: ID=%u, name=%s", + (unsigned int) deviceids[i], name); + break; + } + } + if (i == numdevices) { + g_warning("Found no audio device with name '%s' " + "(will use default audio device)", + oo->device_name); + goto done; + } + + status = AudioUnitSetProperty(oo->au, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &(deviceids[i]), + sizeof(AudioDeviceID)); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to set OS X audio output device: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + g_debug("set OS X audio output device ID=%u, name=%s", + (unsigned int) deviceids[i], name); + +done: + delete[] deviceids; + return ret; +} + +static OSStatus +osx_render(void *vdata, + G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags, + G_GNUC_UNUSED const AudioTimeStamp *in_timestamp, + G_GNUC_UNUSED UInt32 in_bus_number, + G_GNUC_UNUSED UInt32 in_number_frames, + AudioBufferList *buffer_list) +{ + OSXOutput *od = (OSXOutput *) vdata; + AudioBuffer *buffer = &buffer_list->mBuffers[0]; + size_t buffer_size = buffer->mDataByteSize; + + assert(od->buffer != NULL); + + od->mutex.lock(); + + size_t nbytes; + const void *src = fifo_buffer_read(od->buffer, &nbytes); + + if (src != NULL) { + if (nbytes > buffer_size) + nbytes = buffer_size; + + memcpy(buffer->mData, src, nbytes); + fifo_buffer_consume(od->buffer, nbytes); + } else + nbytes = 0; + + od->condition.signal(); + od->mutex.unlock(); + + buffer->mDataByteSize = nbytes; + + unsigned i; + for (i = 1; i < buffer_list->mNumberBuffers; ++i) { + buffer = &buffer_list->mBuffers[i]; + buffer->mDataByteSize = 0; + } + + return 0; +} + +static bool +osx_output_enable(struct audio_output *ao, GError **error_r) +{ + OSXOutput *oo = (OSXOutput *)ao; + + ComponentDescription desc; + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = oo->component_subtype; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + Component comp = FindNextComponent(NULL, &desc); + if (comp == 0) { + g_set_error(error_r, osx_output_quark(), 0, + "Error finding OS X component"); + return false; + } + + OSStatus status = OpenAComponent(comp, &oo->au); + if (status != noErr) { + g_set_error(error_r, osx_output_quark(), status, + "Unable to open OS X component: %s", + GetMacOSStatusCommentString(status)); + return false; + } + + if (!osx_output_set_device(oo, error_r)) { + CloseComponent(oo->au); + return false; + } + + AURenderCallbackStruct callback; + callback.inputProc = osx_render; + callback.inputProcRefCon = oo; + + ComponentResult result = + AudioUnitSetProperty(oo->au, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, 0, + &callback, sizeof(callback)); + if (result != noErr) { + CloseComponent(oo->au); + g_set_error(error_r, osx_output_quark(), result, + "unable to set callback for OS X audio unit"); + return false; + } + + return true; +} + +static void +osx_output_disable(struct audio_output *ao) +{ + OSXOutput *oo = (OSXOutput *)ao; + + CloseComponent(oo->au); +} + +static void +osx_output_cancel(struct audio_output *ao) +{ + OSXOutput *od = (OSXOutput *)ao; + + const ScopeLock protect(od->mutex); + fifo_buffer_clear(od->buffer); +} + +static void +osx_output_close(struct audio_output *ao) +{ + OSXOutput *od = (OSXOutput *)ao; + + AudioOutputUnitStop(od->au); + AudioUnitUninitialize(od->au); + + fifo_buffer_free(od->buffer); +} + +static bool +osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) +{ + OSXOutput *od = (OSXOutput *)ao; + + AudioStreamBasicDescription stream_description; + stream_description.mSampleRate = audio_format->sample_rate; + stream_description.mFormatID = kAudioFormatLinearPCM; + stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + + switch (audio_format->format) { + case SAMPLE_FORMAT_S8: + stream_description.mBitsPerChannel = 8; + break; + + case SAMPLE_FORMAT_S16: + stream_description.mBitsPerChannel = 16; + break; + + case SAMPLE_FORMAT_S32: + stream_description.mBitsPerChannel = 32; + break; + + default: + audio_format->format = SAMPLE_FORMAT_S32; + stream_description.mBitsPerChannel = 32; + break; + } + +#if G_BYTE_ORDER == G_BIG_ENDIAN + stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; +#endif + + stream_description.mBytesPerPacket = + audio_format_frame_size(audio_format); + stream_description.mFramesPerPacket = 1; + stream_description.mBytesPerFrame = stream_description.mBytesPerPacket; + stream_description.mChannelsPerFrame = audio_format->channels; + + ComponentResult result = + AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, + &stream_description, + sizeof(stream_description)); + if (result != noErr) { + g_set_error(error, osx_output_quark(), result, + "Unable to set format on OS X device"); + return false; + } + + OSStatus status = AudioUnitInitialize(od->au); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to initialize OS X audio unit: %s", + GetMacOSStatusCommentString(status)); + return false; + } + + /* create a buffer of 1s */ + od->buffer = fifo_buffer_new(audio_format->sample_rate * + audio_format_frame_size(audio_format)); + + status = AudioOutputUnitStart(od->au); + if (status != 0) { + AudioUnitUninitialize(od->au); + g_set_error(error, osx_output_quark(), status, + "unable to start audio output: %s", + GetMacOSStatusCommentString(status)); + return false; + } + + return true; +} + +static size_t +osx_output_play(struct audio_output *ao, const void *chunk, size_t size, + G_GNUC_UNUSED GError **error) +{ + OSXOutput *od = (OSXOutput *)ao; + + const ScopeLock protect(od->mutex); + + void *dest; + size_t max_length; + + while (true) { + dest = fifo_buffer_write(od->buffer, &max_length); + if (dest != NULL) + break; + + /* wait for some free space in the buffer */ + od->condition.wait(od->mutex); + } + + if (size > max_length) + size = max_length; + + memcpy(dest, chunk, size); + fifo_buffer_append(od->buffer, size); + + return size; +} + +const struct audio_output_plugin osx_output_plugin = { + "osx", + osx_output_test_default_device, + osx_output_init, + osx_output_finish, + osx_output_enable, + osx_output_disable, + osx_output_open, + osx_output_close, + nullptr, + nullptr, + osx_output_play, + nullptr, + osx_output_cancel, + nullptr, + nullptr, +}; diff --git a/src/output/OSXOutputPlugin.hxx b/src/output/OSXOutputPlugin.hxx new file mode 100644 index 000000000..2a4172880 --- /dev/null +++ b/src/output/OSXOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OSX_OUTPUT_PLUGIN_HXX +#define MPD_OSX_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin osx_output_plugin; + +#endif diff --git a/src/output/OssOutputPlugin.cxx b/src/output/OssOutputPlugin.cxx new file mode 100644 index 000000000..ace88b6f4 --- /dev/null +++ b/src/output/OssOutputPlugin.cxx @@ -0,0 +1,793 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OssOutputPlugin.hxx" +#include "output_api.h" +#include "mixer_list.h" +#include "fd_util.h" + +#include <glib.h> + +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "oss" + +#if defined(__OpenBSD__) || defined(__NetBSD__) +# include <soundcard.h> +#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ +# include <sys/soundcard.h> +#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ + +/* We got bug reports from FreeBSD users who said that the two 24 bit + formats generate white noise on FreeBSD, but 32 bit works. This is + a workaround until we know what exactly is expected by the kernel + audio drivers. */ +#ifndef __linux__ +#undef AFMT_S24_PACKED +#undef AFMT_S24_NE +#endif + +#ifdef AFMT_S24_PACKED +#include "pcm_export.h" +#endif + +struct oss_data { + struct audio_output base; + +#ifdef AFMT_S24_PACKED + struct pcm_export_state pcm_export; +#endif + + int fd; + const char *device; + + /** + * The current input audio format. This is needed to reopen + * the device after cancel(). + */ + struct audio_format audio_format; + + /** + * The current OSS audio format. This is needed to reopen the + * device after cancel(). + */ + int oss_format; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +oss_output_quark(void) +{ + return g_quark_from_static_string("oss_output"); +} + +static struct oss_data * +oss_data_new(void) +{ + struct oss_data *ret = g_new(struct oss_data, 1); + + ret->device = NULL; + ret->fd = -1; + + return ret; +} + +static void +oss_data_free(struct oss_data *od) +{ + g_free(od); +} + +enum oss_stat { + OSS_STAT_NO_ERROR = 0, + OSS_STAT_NOT_CHAR_DEV = -1, + OSS_STAT_NO_PERMS = -2, + OSS_STAT_DOESN_T_EXIST = -3, + OSS_STAT_OTHER = -4, +}; + +static enum oss_stat +oss_stat_device(const char *device, int *errno_r) +{ + struct stat st; + + if (0 == stat(device, &st)) { + if (!S_ISCHR(st.st_mode)) { + return OSS_STAT_NOT_CHAR_DEV; + } + } else { + *errno_r = errno; + + switch (errno) { + case ENOENT: + case ENOTDIR: + return OSS_STAT_DOESN_T_EXIST; + case EACCES: + return OSS_STAT_NO_PERMS; + default: + return OSS_STAT_OTHER; + } + } + + return OSS_STAT_NO_ERROR; +} + +static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; + +static bool +oss_output_test_default_device(void) +{ + int fd, i; + + for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { + fd = open_cloexec(default_devices[i], O_WRONLY, 0); + + if (fd >= 0) { + close(fd); + return true; + } + g_warning("Error opening OSS device \"%s\": %s\n", + default_devices[i], g_strerror(errno)); + } + + return false; +} + +static struct audio_output * +oss_open_default(GError **error) +{ + int err[G_N_ELEMENTS(default_devices)]; + enum oss_stat ret[G_N_ELEMENTS(default_devices)]; + + 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(); + if (!ao_base_init(&od->base, &oss_output_plugin, NULL, + error)) { + g_free(od); + return NULL; + } + + od->device = default_devices[i]; + return &od->base; + } + } + + for (int i = G_N_ELEMENTS(default_devices); --i >= 0; ) { + const char *dev = default_devices[i]; + switch(ret[i]) { + case OSS_STAT_NO_ERROR: + /* never reached */ + break; + case OSS_STAT_DOESN_T_EXIST: + g_warning("%s not found\n", dev); + break; + case OSS_STAT_NOT_CHAR_DEV: + g_warning("%s is not a character device\n", dev); + break; + case OSS_STAT_NO_PERMS: + g_warning("%s: permission denied\n", dev); + break; + case OSS_STAT_OTHER: + g_warning("Error accessing %s: %s\n", + dev, g_strerror(err[i])); + } + } + + g_set_error(error, oss_output_quark(), 0, + "error trying to open default OSS device"); + return NULL; +} + +static struct audio_output * +oss_output_init(const struct config_param *param, GError **error) +{ + const char *device = config_get_block_string(param, "device", NULL); + if (device != NULL) { + struct oss_data *od = oss_data_new(); + if (!ao_base_init(&od->base, &oss_output_plugin, param, + error)) { + g_free(od); + return NULL; + } + + od->device = device; + return &od->base; + } + + return oss_open_default(error); +} + +static void +oss_output_finish(struct audio_output *ao) +{ + struct oss_data *od = (struct oss_data *)ao; + + ao_base_finish(&od->base); + oss_data_free(od); +} + +#ifdef AFMT_S24_PACKED + +static bool +oss_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) +{ + struct oss_data *od = (struct oss_data *)ao; + + pcm_export_init(&od->pcm_export); + return true; +} + +static void +oss_output_disable(struct audio_output *ao) +{ + struct oss_data *od = (struct oss_data *)ao; + + pcm_export_deinit(&od->pcm_export); +} + +#endif + +static void +oss_close(struct oss_data *od) +{ + if (od->fd >= 0) + close(od->fd); + od->fd = -1; +} + +/** + * A tri-state type for oss_try_ioctl(). + */ +enum oss_setup_result { + SUCCESS, + ERROR, + UNSUPPORTED, +}; + +/** + * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is + * returned. If the parameter is not supported, UNSUPPORTED is + * returned. Any other failure returns ERROR and allocates a GError. + */ +static enum oss_setup_result +oss_try_ioctl_r(int fd, unsigned long request, int *value_r, + const char *msg, GError **error_r) +{ + assert(fd >= 0); + assert(value_r != NULL); + assert(msg != NULL); + assert(error_r == NULL || *error_r == NULL); + + int ret = ioctl(fd, request, value_r); + if (ret >= 0) + return SUCCESS; + + if (errno == EINVAL) + return UNSUPPORTED; + + g_set_error(error_r, oss_output_quark(), errno, + "%s: %s", msg, g_strerror(errno)); + return ERROR; +} + +/** + * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is + * returned. If the parameter is not supported, UNSUPPORTED is + * returned. Any other failure returns ERROR and allocates a GError. + */ +static enum oss_setup_result +oss_try_ioctl(int fd, unsigned long request, int value, + const char *msg, GError **error_r) +{ + return oss_try_ioctl_r(fd, request, &value, msg, error_r); +} + +/** + * Set up the channel number, and attempts to find alternatives if the + * specified number is not supported. + */ +static bool +oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r) +{ + const char *const msg = "Failed to set channel count"; + int channels = audio_format->channels; + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_channel_count(channels)) + break; + + audio_format->channels = channels; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + + for (unsigned i = 1; i < 2; ++i) { + if (i == audio_format->channels) + /* don't try that again */ + continue; + + channels = i; + result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_channel_count(channels)) + break; + + audio_format->channels = channels; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + return false; +} + +/** + * Set up the sample rate, and attempts to find alternatives if the + * specified sample rate is not supported. + */ +static bool +oss_setup_sample_rate(int fd, struct audio_format *audio_format, + GError **error_r) +{ + const char *const msg = "Failed to set sample rate"; + int sample_rate = audio_format->sample_rate; + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_sample_rate(sample_rate)) + break; + + audio_format->sample_rate = sample_rate; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + + static const int sample_rates[] = { 48000, 44100, 0 }; + for (unsigned i = 0; sample_rates[i] != 0; ++i) { + sample_rate = sample_rates[i]; + if (sample_rate == (int)audio_format->sample_rate) + continue; + + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_sample_rate(sample_rate)) + break; + + audio_format->sample_rate = sample_rate; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + return false; +} + +/** + * Convert a MPD sample format to its OSS counterpart. Returns + * AFMT_QUERY if there is no direct counterpart. + */ +static int +sample_format_to_oss(enum sample_format format) +{ + switch (format) { + case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_FLOAT: + case SAMPLE_FORMAT_DSD: + return AFMT_QUERY; + + case SAMPLE_FORMAT_S8: + return AFMT_S8; + + case SAMPLE_FORMAT_S16: + return AFMT_S16_NE; + + case SAMPLE_FORMAT_S24_P32: +#ifdef AFMT_S24_NE + return AFMT_S24_NE; +#else + return AFMT_QUERY; +#endif + + case SAMPLE_FORMAT_S32: +#ifdef AFMT_S32_NE + return AFMT_S32_NE; +#else + return AFMT_QUERY; +#endif + } + + return AFMT_QUERY; +} + +/** + * Convert an OSS sample format to its MPD counterpart. Returns + * SAMPLE_FORMAT_UNDEFINED if there is no direct counterpart. + */ +static enum sample_format +sample_format_from_oss(int format) +{ + switch (format) { + case AFMT_S8: + return SAMPLE_FORMAT_S8; + + case AFMT_S16_NE: + return SAMPLE_FORMAT_S16; + +#ifdef AFMT_S24_PACKED + case AFMT_S24_PACKED: + return SAMPLE_FORMAT_S24_P32; +#endif + +#ifdef AFMT_S24_NE + case AFMT_S24_NE: + return SAMPLE_FORMAT_S24_P32; +#endif + +#ifdef AFMT_S32_NE + case AFMT_S32_NE: + return SAMPLE_FORMAT_S32; +#endif + + default: + return SAMPLE_FORMAT_UNDEFINED; + } +} + +/** + * Probe one sample format. + * + * @return the selected sample format or SAMPLE_FORMAT_UNDEFINED on + * error + */ +static enum oss_setup_result +oss_probe_sample_format(int fd, enum sample_format sample_format, + enum sample_format *sample_format_r, + int *oss_format_r, +#ifdef AFMT_S24_PACKED + struct pcm_export_state *pcm_export, +#endif + GError **error_r) +{ + int oss_format = sample_format_to_oss(sample_format); + if (oss_format == AFMT_QUERY) + return UNSUPPORTED; + + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, + "Failed to set sample format", error_r); + +#ifdef AFMT_S24_PACKED + if (result == UNSUPPORTED && sample_format == SAMPLE_FORMAT_S24_P32) { + /* if the driver doesn't support padded 24 bit, try + packed 24 bit */ + oss_format = AFMT_S24_PACKED; + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, + "Failed to set sample format", error_r); + } +#endif + + if (result != SUCCESS) + return result; + + sample_format = sample_format_from_oss(oss_format); + if (sample_format == SAMPLE_FORMAT_UNDEFINED) + return UNSUPPORTED; + + *sample_format_r = sample_format; + *oss_format_r = oss_format; + +#ifdef AFMT_S24_PACKED + pcm_export_open(pcm_export, sample_format, 0, false, false, + oss_format == AFMT_S24_PACKED, + oss_format == AFMT_S24_PACKED && + G_BYTE_ORDER != G_LITTLE_ENDIAN); +#endif + + return SUCCESS; +} + +/** + * Set up the sample format, and attempts to find alternatives if the + * specified format is not supported. + */ +static bool +oss_setup_sample_format(int fd, struct audio_format *audio_format, + int *oss_format_r, +#ifdef AFMT_S24_PACKED + struct pcm_export_state *pcm_export, +#endif + GError **error_r) +{ + enum sample_format mpd_format; + enum oss_setup_result result = + oss_probe_sample_format(fd, sample_format(audio_format->format), + &mpd_format, oss_format_r, +#ifdef AFMT_S24_PACKED + pcm_export, +#endif + error_r); + switch (result) { + case SUCCESS: + audio_format->format = mpd_format; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + + if (result != UNSUPPORTED) + return result == SUCCESS; + + /* the requested sample format is not available - probe for + other formats supported by MPD */ + + static const enum sample_format sample_formats[] = { + SAMPLE_FORMAT_S24_P32, + SAMPLE_FORMAT_S32, + SAMPLE_FORMAT_S16, + SAMPLE_FORMAT_S8, + SAMPLE_FORMAT_UNDEFINED /* sentinel */ + }; + + for (unsigned i = 0; sample_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) { + mpd_format = sample_formats[i]; + if (mpd_format == audio_format->format) + /* don't try that again */ + continue; + + result = oss_probe_sample_format(fd, mpd_format, + &mpd_format, oss_format_r, +#ifdef AFMT_S24_PACKED + pcm_export, +#endif + error_r); + switch (result) { + case SUCCESS: + audio_format->format = mpd_format; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + g_set_error_literal(error_r, oss_output_quark(), EINVAL, + "Failed to set sample format"); + return false; +} + +/** + * Sets up the OSS device which was opened before. + */ +static bool +oss_setup(struct oss_data *od, struct audio_format *audio_format, + GError **error_r) +{ + return oss_setup_channels(od->fd, audio_format, error_r) && + oss_setup_sample_rate(od->fd, audio_format, error_r) && + oss_setup_sample_format(od->fd, audio_format, &od->oss_format, +#ifdef AFMT_S24_PACKED + &od->pcm_export, +#endif + error_r); +} + +/** + * Reopen the device with the saved audio_format, without any probing. + */ +static bool +oss_reopen(struct oss_data *od, GError **error_r) +{ + assert(od->fd < 0); + + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { + g_set_error(error_r, oss_output_quark(), errno, + "Error opening OSS device \"%s\": %s", + od->device, g_strerror(errno)); + return false; + } + + enum oss_setup_result result; + + const char *const msg1 = "Failed to set channel count"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS, + od->audio_format.channels, msg1, error_r); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg1); + return false; + } + + const char *const msg2 = "Failed to set sample rate"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED, + od->audio_format.sample_rate, msg2, error_r); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg2); + return false; + } + + const char *const msg3 = "Failed to set sample format"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, + od->oss_format, + msg3, error_r); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg3); + return false; + } + + return true; +} + +static bool +oss_output_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error) +{ + struct oss_data *od = (struct oss_data *)ao; + + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { + g_set_error(error, oss_output_quark(), errno, + "Error opening OSS device \"%s\": %s", + od->device, g_strerror(errno)); + return false; + } + + if (!oss_setup(od, audio_format, error)) { + oss_close(od); + return false; + } + + od->audio_format = *audio_format; + return true; +} + +static void +oss_output_close(struct audio_output *ao) +{ + struct oss_data *od = (struct oss_data *)ao; + + oss_close(od); +} + +static void +oss_output_cancel(struct audio_output *ao) +{ + struct oss_data *od = (struct oss_data *)ao; + + if (od->fd >= 0) { + ioctl(od->fd, SNDCTL_DSP_RESET, 0); + oss_close(od); + } +} + +static size_t +oss_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) +{ + struct oss_data *od = (struct oss_data *)ao; + ssize_t ret; + + /* reopen the device since it was closed by dropBufferedAudio */ + if (od->fd < 0 && !oss_reopen(od, error)) + return 0; + +#ifdef AFMT_S24_PACKED + chunk = pcm_export(&od->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->pcm_export, ret); +#endif + return ret; + } + + if (ret < 0 && errno != EINTR) { + g_set_error(error, oss_output_quark(), errno, + "Write error on %s: %s", + od->device, g_strerror(errno)); + return 0; + } + } +} + +const struct audio_output_plugin oss_output_plugin = { + "oss", + oss_output_test_default_device, + oss_output_init, + oss_output_finish, +#ifdef AFMT_S24_PACKED + oss_output_enable, + oss_output_disable, +#else + nullptr, + nullptr, +#endif + oss_output_open, + oss_output_close, + nullptr, + nullptr, + oss_output_play, + nullptr, + oss_output_cancel, + nullptr, + + &oss_mixer_plugin, +}; diff --git a/src/output/OssOutputPlugin.hxx b/src/output/OssOutputPlugin.hxx new file mode 100644 index 000000000..6c5c9530b --- /dev/null +++ b/src/output/OssOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OSS_OUTPUT_PLUGIN_HXX +#define MPD_OSS_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin oss_output_plugin; + +#endif diff --git a/src/output/RoarOutputPlugin.cxx b/src/output/RoarOutputPlugin.cxx new file mode 100644 index 000000000..43aeb09a2 --- /dev/null +++ b/src/output/RoarOutputPlugin.cxx @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "RoarOutputPlugin.hxx" +#include "output_api.h" +#include "mixer_list.h" +#include "thread/Mutex.hxx" + +#include <glib.h> + +#include <roaraudio.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "roaraudio" + +struct RoarOutput { + struct audio_output base; + + roar_vs_t * vss; + int err; + char *host; + char *name; + int role; + struct roar_connection con; + struct roar_audio_info info; + Mutex mutex; + volatile bool alive; + + RoarOutput() + :err(ROAR_ERROR_NONE), + host(nullptr), name(nullptr) {} + + ~RoarOutput() { + g_free(host); + g_free(name); + } +}; + +static inline GQuark +roar_output_quark(void) +{ + return g_quark_from_static_string("roar_output"); +} + +static int +roar_output_get_volume_locked(RoarOutput *roar) +{ + if (roar->vss == nullptr || !roar->alive) + return -1; + + float l, r; + int error; + if (roar_vs_volume_get(roar->vss, &l, &r, &error) < 0) + return -1; + + return (l + r) * 50; +} + +int +roar_output_get_volume(RoarOutput *roar) +{ + const ScopeLock protect(roar->mutex); + return roar_output_get_volume_locked(roar); +} + +static bool +roar_output_set_volume_locked(RoarOutput *roar, unsigned volume) +{ + assert(volume <= 100); + + if (roar->vss == nullptr || !roar->alive) + return false; + + int error; + float level = volume / 100.0; + + roar_vs_volume_mono(roar->vss, level, &error); + return true; +} + +bool +roar_output_set_volume(RoarOutput *roar, unsigned volume) +{ + const ScopeLock protect(roar->mutex); + return roar_output_set_volume_locked(roar, volume); +} + +static void +roar_configure(RoarOutput *self, const struct config_param *param) +{ + 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 != nullptr + ? roar_str2role(role) + : ROAR_ROLE_MUSIC; +} + +static struct audio_output * +roar_init(const struct config_param *param, GError **error_r) +{ + RoarOutput *self = new RoarOutput(); + + if (!ao_base_init(&self->base, &roar_output_plugin, param, error_r)) { + delete self; + return nullptr; + } + + roar_configure(self, param); + return &self->base; +} + +static void +roar_finish(struct audio_output *ao) +{ + RoarOutput *self = (RoarOutput *)ao; + + ao_base_finish(&self->base); + delete self; +} + +static void +roar_use_audio_format(struct roar_audio_info *info, + struct audio_format *audio_format) +{ + info->rate = audio_format->sample_rate; + info->channels = audio_format->channels; + info->codec = ROAR_CODEC_PCM_S; + + switch (audio_format->format) { + case SAMPLE_FORMAT_UNDEFINED: + info->bits = 16; + audio_format->format = SAMPLE_FORMAT_S16; + break; + + case SAMPLE_FORMAT_S8: + info->bits = 8; + break; + + case SAMPLE_FORMAT_S16: + info->bits = 16; + break; + + case SAMPLE_FORMAT_S24_P32: + info->bits = 32; + audio_format->format = SAMPLE_FORMAT_S32; + break; + + case SAMPLE_FORMAT_S32: + info->bits = 32; + break; + } +} + +static bool +roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) +{ + RoarOutput *self = (RoarOutput *)ao; + const ScopeLock protect(self->mutex); + + if (roar_simple_connect(&(self->con), self->host, self->name) < 0) + { + g_set_error(error, roar_output_quark(), 0, + "Failed to connect to Roar server"); + return false; + } + + self->vss = roar_vs_new_from_con(&(self->con), &(self->err)); + + if (self->vss == nullptr || self->err != ROAR_ERROR_NONE) + { + g_set_error(error, roar_output_quark(), 0, + "Failed to connect to server"); + return false; + } + + roar_use_audio_format(&self->info, audio_format); + + if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY, + &(self->err)) < 0) + { + g_set_error(error, roar_output_quark(), 0, "Failed to start stream"); + return false; + } + roar_vs_role(self->vss, self->role, &(self->err)); + self->alive = true; + + return true; +} + +static void +roar_close(struct audio_output *ao) +{ + RoarOutput *self = (RoarOutput *)ao; + const ScopeLock protect(self->mutex); + + self->alive = false; + + if (self->vss != nullptr) + roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err)); + self->vss = nullptr; + roar_disconnect(&(self->con)); +} + +static void +roar_cancel_locked(RoarOutput *self) +{ + if (self->vss == nullptr) + return; + + roar_vs_t *vss = self->vss; + self->vss = nullptr; + roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); + self->alive = false; + + vss = roar_vs_new_from_con(&(self->con), &(self->err)); + if (vss == nullptr) + return; + + if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY, + &(self->err)) < 0) { + roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); + g_warning("Failed to start stream"); + return; + } + + roar_vs_role(vss, self->role, &(self->err)); + self->vss = vss; + self->alive = true; +} + +static void +roar_cancel(struct audio_output *ao) +{ + RoarOutput *self = (RoarOutput *)ao; + + const ScopeLock protect(self->mutex); + roar_cancel_locked(self); +} + +static size_t +roar_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) +{ + RoarOutput *self = (RoarOutput *)ao; + ssize_t rc; + + if (self->vss == nullptr) + { + g_set_error(error, roar_output_quark(), 0, "Connection is invalid"); + return 0; + } + + rc = roar_vs_write(self->vss, chunk, size, &(self->err)); + if ( rc <= 0 ) + { + g_set_error(error, roar_output_quark(), 0, "Failed to play data"); + return 0; + } + + return rc; +} + +static const char* +roar_tag_convert(enum tag_type type, bool *is_uuid) +{ + *is_uuid = false; + switch (type) + { + case TAG_ARTIST: + case TAG_ALBUM_ARTIST: + return "AUTHOR"; + case TAG_ALBUM: + return "ALBUM"; + case TAG_TITLE: + return "TITLE"; + case TAG_TRACK: + return "TRACK"; + case TAG_NAME: + return "NAME"; + case TAG_GENRE: + return "GENRE"; + case TAG_DATE: + return "DATE"; + case TAG_PERFORMER: + return "PERFORMER"; + case TAG_COMMENT: + return "COMMENT"; + case TAG_DISC: + return "DISCID"; + case TAG_COMPOSER: +#ifdef ROAR_META_TYPE_COMPOSER + return "COMPOSER"; +#else + return "AUTHOR"; +#endif + case TAG_MUSICBRAINZ_ARTISTID: + case TAG_MUSICBRAINZ_ALBUMID: + case TAG_MUSICBRAINZ_ALBUMARTISTID: + case TAG_MUSICBRAINZ_TRACKID: + *is_uuid = true; + return "HASH"; + + default: + return nullptr; + } +} + +static void +roar_send_tag(struct audio_output *ao, const struct tag *meta) +{ + RoarOutput *self = (RoarOutput *)ao; + + if (self->vss == nullptr) + return; + + const ScopeLock protect(self->mutex); + + size_t cnt = 1; + struct roar_keyval vals[32]; + memset(vals, 0, sizeof(vals)); + char uuid_buf[32][64]; + + char timebuf[16]; + snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", + meta->time / 3600, (meta->time % 3600) / 60, meta->time % 60); + + vals[0].key = g_strdup("LENGTH"); + vals[0].value = timebuf; + + for (unsigned i = 0; i < meta->num_items && cnt < 32; i++) + { + bool is_uuid = false; + const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid); + if (key != nullptr) + { + if (is_uuid) + { + snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s", + meta->items[i]->value); + vals[cnt].key = g_strdup(key); + vals[cnt].value = uuid_buf[cnt]; + } + else + { + vals[cnt].key = g_strdup(key); + vals[cnt].value = meta->items[i]->value; + } + cnt++; + } + } + + roar_vs_meta(self->vss, vals, cnt, &(self->err)); + + for (unsigned i = 0; i < 32; i++) + g_free(vals[i].key); +} + +const struct audio_output_plugin roar_output_plugin = { + "roar", + nullptr, + roar_init, + roar_finish, + nullptr, + nullptr, + roar_open, + roar_close, + nullptr, + roar_send_tag, + roar_play, + nullptr, + roar_cancel, + nullptr, + &roar_mixer_plugin, +}; diff --git a/src/output/RoarOutputPlugin.hxx b/src/output/RoarOutputPlugin.hxx new file mode 100644 index 000000000..faa4b4d5c --- /dev/null +++ b/src/output/RoarOutputPlugin.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ROAR_OUTPUT_PLUGIN_H +#define MPD_ROAR_OUTPUT_PLUGIN_H + +struct RoarOutput; + +extern const struct audio_output_plugin roar_output_plugin; + +int +roar_output_get_volume(RoarOutput *roar); + +bool +roar_output_set_volume(RoarOutput *roar, unsigned volume); + +#endif diff --git a/src/output/alsa_output_plugin.c b/src/output/alsa_output_plugin.c deleted file mode 100644 index d8b184273..000000000 --- a/src/output/alsa_output_plugin.c +++ /dev/null @@ -1,819 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "alsa_output_plugin.h" -#include "output_api.h" -#include "mixer_list.h" -#include "pcm_export.h" - -#include <glib.h> -#include <alsa/asoundlib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "alsa" - -#define ALSA_PCM_NEW_HW_PARAMS_API -#define ALSA_PCM_NEW_SW_PARAMS_API - -static const char default_device[] = "default"; - -enum { - MPD_ALSA_BUFFER_TIME_US = 500000, -}; - -#define MPD_ALSA_RETRY_NR 5 - -typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, - snd_pcm_uframes_t size); - -struct alsa_data { - struct audio_output base; - - struct pcm_export_state export; - - /** the configured name of the ALSA device; NULL for the - default device */ - char *device; - - /** use memory mapped I/O? */ - bool use_mmap; - - /** - * Enable DSD over USB according to the dCS suggested - * standard? - * - * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf - */ - bool dsd_usb; - - /** libasound's buffer_time setting (in microseconds) */ - unsigned int buffer_time; - - /** libasound's period_time setting (in microseconds) */ - unsigned int period_time; - - /** the mode flags passed to snd_pcm_open */ - int mode; - - /** the libasound PCM device handle */ - snd_pcm_t *pcm; - - /** - * a pointer to the libasound writei() function, which is - * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the - * use_mmap configuration - */ - alsa_writei_t *writei; - - /** - * The size of one audio frame passed to method play(). - */ - size_t in_frame_size; - - /** - * The size of one audio frame passed to libasound. - */ - size_t out_frame_size; - - /** - * The size of one period, in number of frames. - */ - snd_pcm_uframes_t period_frames; - - /** - * The number of frames written in the current period. - */ - snd_pcm_uframes_t period_position; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -alsa_output_quark(void) -{ - return g_quark_from_static_string("alsa_output"); -} - -static const char * -alsa_device(const struct alsa_data *ad) -{ - return ad->device != NULL ? ad->device : default_device; -} - -static struct alsa_data * -alsa_data_new(void) -{ - struct alsa_data *ret = g_new(struct alsa_data, 1); - - ret->mode = 0; - ret->writei = snd_pcm_writei; - - return ret; -} - -static void -alsa_configure(struct alsa_data *ad, const struct config_param *param) -{ - ad->device = config_dup_block_string(param, "device", NULL); - - ad->use_mmap = config_get_block_bool(param, "use_mmap", false); - - ad->dsd_usb = config_get_block_bool(param, "dsd_usb", false); - - ad->buffer_time = config_get_block_unsigned(param, "buffer_time", - MPD_ALSA_BUFFER_TIME_US); - ad->period_time = config_get_block_unsigned(param, "period_time", 0); - -#ifdef SND_PCM_NO_AUTO_RESAMPLE - if (!config_get_block_bool(param, "auto_resample", true)) - ad->mode |= SND_PCM_NO_AUTO_RESAMPLE; -#endif - -#ifdef SND_PCM_NO_AUTO_CHANNELS - if (!config_get_block_bool(param, "auto_channels", true)) - ad->mode |= SND_PCM_NO_AUTO_CHANNELS; -#endif - -#ifdef SND_PCM_NO_AUTO_FORMAT - if (!config_get_block_bool(param, "auto_format", true)) - ad->mode |= SND_PCM_NO_AUTO_FORMAT; -#endif -} - -static struct audio_output * -alsa_init(const struct config_param *param, GError **error_r) -{ - struct alsa_data *ad = alsa_data_new(); - - if (!ao_base_init(&ad->base, &alsa_output_plugin, param, error_r)) { - g_free(ad); - return NULL; - } - - alsa_configure(ad, param); - - return &ad->base; -} - -static void -alsa_finish(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - ao_base_finish(&ad->base); - - g_free(ad->device); - g_free(ad); - - /* free libasound's config cache */ - snd_config_update_free_global(); -} - -static bool -alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - pcm_export_init(&ad->export); - return true; -} - -static void -alsa_output_disable(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - pcm_export_deinit(&ad->export); -} - -static bool -alsa_test_default_device(void) -{ - snd_pcm_t *handle; - - int ret = snd_pcm_open(&handle, default_device, - SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); - if (ret) { - g_message("Error opening default ALSA device: %s\n", - snd_strerror(-ret)); - return false; - } else - snd_pcm_close(handle); - - return true; -} - -static snd_pcm_format_t -get_bitformat(enum sample_format sample_format) -{ - switch (sample_format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - return SND_PCM_FORMAT_UNKNOWN; - - case SAMPLE_FORMAT_S8: - return SND_PCM_FORMAT_S8; - - case SAMPLE_FORMAT_S16: - return SND_PCM_FORMAT_S16; - - case SAMPLE_FORMAT_S24_P32: - return SND_PCM_FORMAT_S24; - - case SAMPLE_FORMAT_S32: - return SND_PCM_FORMAT_S32; - - case SAMPLE_FORMAT_FLOAT: - return SND_PCM_FORMAT_FLOAT; - } - - assert(false); - return SND_PCM_FORMAT_UNKNOWN; -} - -static snd_pcm_format_t -byteswap_bitformat(snd_pcm_format_t fmt) -{ - switch(fmt) { - case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE; - case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE; - case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE; - case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE; - case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE; - - case SND_PCM_FORMAT_S24_3BE: - return SND_PCM_FORMAT_S24_3LE; - - case SND_PCM_FORMAT_S24_3LE: - return SND_PCM_FORMAT_S24_3BE; - - case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE; - default: return SND_PCM_FORMAT_UNKNOWN; - } -} - -static snd_pcm_format_t -alsa_to_packed_format(snd_pcm_format_t fmt) -{ - switch (fmt) { - case SND_PCM_FORMAT_S24_LE: - return SND_PCM_FORMAT_S24_3LE; - - case SND_PCM_FORMAT_S24_BE: - return SND_PCM_FORMAT_S24_3BE; - - default: - return SND_PCM_FORMAT_UNKNOWN; - } -} - -static int -alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - snd_pcm_format_t fmt, bool *packed_r) -{ - int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); - if (err == 0) - *packed_r = false; - - if (err != -EINVAL) - return err; - - fmt = alsa_to_packed_format(fmt); - if (fmt == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; - - err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); - if (err == 0) - *packed_r = true; - - return err; -} - -/** - * Attempts to configure the specified sample format, and tries the - * reversed host byte order if was not supported. - */ -static int -alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - enum sample_format sample_format, - bool *packed_r, bool *reverse_endian_r) -{ - snd_pcm_format_t alsa_format = get_bitformat(sample_format); - if (alsa_format == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; - - int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, - packed_r); - if (err == 0) - *reverse_endian_r = false; - - if (err != -EINVAL) - return err; - - alsa_format = byteswap_bitformat(alsa_format); - if (alsa_format == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; - - err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r); - if (err == 0) - *reverse_endian_r = true; - - return err; -} - -/** - * Configure a sample format, and probe other formats if that fails. - */ -static int -alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - struct audio_format *audio_format, - bool *packed_r, bool *reverse_endian_r) -{ - /* try the input format first */ - - int err = alsa_output_try_format(pcm, hwparams, audio_format->format, - packed_r, reverse_endian_r); - - /* if unsupported by the hardware, try other formats */ - - static const enum sample_format probe_formats[] = { - SAMPLE_FORMAT_S24_P32, - SAMPLE_FORMAT_S32, - SAMPLE_FORMAT_S16, - SAMPLE_FORMAT_S8, - SAMPLE_FORMAT_UNDEFINED, - }; - - for (unsigned i = 0; - err == -EINVAL && probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; - ++i) { - const enum sample_format mpd_format = probe_formats[i]; - if (mpd_format == audio_format->format) - continue; - - err = alsa_output_try_format(pcm, hwparams, mpd_format, - packed_r, reverse_endian_r); - if (err == 0) - audio_format->format = mpd_format; - } - - return err; -} - -/** - * Set up the snd_pcm_t object which was opened by the caller. Set up - * the configured settings and the audio format. - */ -static bool -alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, - bool *packed_r, bool *reverse_endian_r, GError **error) -{ - snd_pcm_hw_params_t *hwparams; - snd_pcm_sw_params_t *swparams; - unsigned int sample_rate = audio_format->sample_rate; - unsigned int channels = audio_format->channels; - snd_pcm_uframes_t alsa_buffer_size; - snd_pcm_uframes_t alsa_period_size; - int err; - const char *cmd = NULL; - int retry = MPD_ALSA_RETRY_NR; - unsigned int period_time, period_time_ro; - unsigned int buffer_time; - - period_time_ro = period_time = ad->period_time; -configure_hw: - /* configure HW params */ - snd_pcm_hw_params_alloca(&hwparams); - cmd = "snd_pcm_hw_params_any"; - err = snd_pcm_hw_params_any(ad->pcm, hwparams); - if (err < 0) - goto error; - - if (ad->use_mmap) { - err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, - SND_PCM_ACCESS_MMAP_INTERLEAVED); - if (err < 0) { - g_warning("Cannot set mmap'ed mode on ALSA device \"%s\": %s\n", - alsa_device(ad), snd_strerror(-err)); - g_warning("Falling back to direct write mode\n"); - ad->use_mmap = false; - } else - ad->writei = snd_pcm_mmap_writei; - } - - if (!ad->use_mmap) { - cmd = "snd_pcm_hw_params_set_access"; - err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, - SND_PCM_ACCESS_RW_INTERLEAVED); - if (err < 0) - goto error; - ad->writei = snd_pcm_writei; - } - - err = alsa_output_setup_format(ad->pcm, hwparams, audio_format, - packed_r, reverse_endian_r); - if (err < 0) { - g_set_error(error, alsa_output_quark(), err, - "ALSA device \"%s\" does not support format %s: %s", - alsa_device(ad), - sample_format_to_string(audio_format->format), - snd_strerror(-err)); - return false; - } - - snd_pcm_format_t format; - if (snd_pcm_hw_params_get_format(hwparams, &format) == 0) - g_debug("format=%s (%s)", snd_pcm_format_name(format), - snd_pcm_format_description(format)); - - err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams, - &channels); - if (err < 0) { - g_set_error(error, alsa_output_quark(), err, - "ALSA device \"%s\" does not support %i channels: %s", - alsa_device(ad), (int)audio_format->channels, - snd_strerror(-err)); - return false; - } - audio_format->channels = (int8_t)channels; - - err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams, - &sample_rate, NULL); - if (err < 0 || sample_rate == 0) { - g_set_error(error, alsa_output_quark(), err, - "ALSA device \"%s\" does not support %u Hz audio", - alsa_device(ad), audio_format->sample_rate); - return false; - } - audio_format->sample_rate = sample_rate; - - snd_pcm_uframes_t buffer_size_min, buffer_size_max; - snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min); - snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max); - unsigned buffer_time_min, buffer_time_max; - snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0); - snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0); - g_debug("buffer: size=%u..%u time=%u..%u", - (unsigned)buffer_size_min, (unsigned)buffer_size_max, - buffer_time_min, buffer_time_max); - - snd_pcm_uframes_t period_size_min, period_size_max; - snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0); - snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0); - unsigned period_time_min, period_time_max; - snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0); - snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0); - g_debug("period: size=%u..%u time=%u..%u", - (unsigned)period_size_min, (unsigned)period_size_max, - period_time_min, period_time_max); - - if (ad->buffer_time > 0) { - buffer_time = ad->buffer_time; - cmd = "snd_pcm_hw_params_set_buffer_time_near"; - err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams, - &buffer_time, NULL); - if (err < 0) - goto error; - } else { - err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time, - NULL); - if (err < 0) - buffer_time = 0; - } - - if (period_time_ro == 0 && buffer_time >= 10000) { - period_time_ro = period_time = buffer_time / 4; - - g_debug("default period_time = buffer_time/4 = %u/4 = %u", - buffer_time, period_time); - } - - if (period_time_ro > 0) { - period_time = period_time_ro; - cmd = "snd_pcm_hw_params_set_period_time_near"; - err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams, - &period_time, NULL); - if (err < 0) - goto error; - } - - cmd = "snd_pcm_hw_params"; - err = snd_pcm_hw_params(ad->pcm, hwparams); - if (err == -EPIPE && --retry > 0 && period_time_ro > 0) { - period_time_ro = period_time_ro >> 1; - goto configure_hw; - } else if (err < 0) - goto error; - if (retry != MPD_ALSA_RETRY_NR) - g_debug("ALSA period_time set to %d\n", period_time); - - cmd = "snd_pcm_hw_params_get_buffer_size"; - err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size); - if (err < 0) - goto error; - - cmd = "snd_pcm_hw_params_get_period_size"; - err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size, - NULL); - if (err < 0) - goto error; - - /* configure SW params */ - snd_pcm_sw_params_alloca(&swparams); - - cmd = "snd_pcm_sw_params_current"; - err = snd_pcm_sw_params_current(ad->pcm, swparams); - if (err < 0) - goto error; - - cmd = "snd_pcm_sw_params_set_start_threshold"; - err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams, - alsa_buffer_size - - alsa_period_size); - if (err < 0) - goto error; - - cmd = "snd_pcm_sw_params_set_avail_min"; - err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams, - alsa_period_size); - if (err < 0) - goto error; - - cmd = "snd_pcm_sw_params"; - err = snd_pcm_sw_params(ad->pcm, swparams); - if (err < 0) - goto error; - - g_debug("buffer_size=%u period_size=%u", - (unsigned)alsa_buffer_size, (unsigned)alsa_period_size); - - if (alsa_period_size == 0) - /* this works around a SIGFPE bug that occurred when - an ALSA driver indicated period_size==0; this - caused a division by zero in alsa_play(). By using - the fallback "1", we make sure that this won't - happen again. */ - alsa_period_size = 1; - - ad->period_frames = alsa_period_size; - ad->period_position = 0; - - return true; - -error: - g_set_error(error, alsa_output_quark(), err, - "Error opening ALSA device \"%s\" (%s): %s", - alsa_device(ad), cmd, snd_strerror(-err)); - return false; -} - -static bool -alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format, - bool *shift8_r, bool *packed_r, bool *reverse_endian_r, - GError **error_r) -{ - assert(ad->dsd_usb); - assert(audio_format->format == SAMPLE_FORMAT_DSD); - - /* pass 24 bit to alsa_setup() */ - - struct audio_format usb_format = *audio_format; - usb_format.format = SAMPLE_FORMAT_S24_P32; - usb_format.sample_rate /= 2; - - const struct audio_format check = usb_format; - - if (!alsa_setup(ad, &usb_format, packed_r, reverse_endian_r, error_r)) - return false; - - /* if the device allows only 32 bit, shift all DSD-over-USB - samples left by 8 bit and leave the lower 8 bit cleared; - the DSD-over-USB documentation does not specify whether - this is legal, but there is anecdotical evidence that this - is possible (and the only option for some devices) */ - *shift8_r = usb_format.format == SAMPLE_FORMAT_S32; - if (usb_format.format == SAMPLE_FORMAT_S32) - usb_format.format = SAMPLE_FORMAT_S24_P32; - - if (!audio_format_equals(&usb_format, &check)) { - /* no bit-perfect playback, which is required - for DSD over USB */ - g_set_error(error_r, alsa_output_quark(), 0, - "Failed to configure DSD-over-USB on ALSA device \"%s\"", - alsa_device(ad)); - return false; - } - - return true; -} - -static bool -alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format, - GError **error_r) -{ - bool shift8 = false, packed, reverse_endian; - - const bool dsd_usb = ad->dsd_usb && - audio_format->format == SAMPLE_FORMAT_DSD; - const bool success = dsd_usb - ? alsa_setup_dsd(ad, audio_format, - &shift8, &packed, &reverse_endian, - error_r) - : alsa_setup(ad, audio_format, &packed, &reverse_endian, - error_r); - if (!success) - return false; - - pcm_export_open(&ad->export, - audio_format->format, audio_format->channels, - dsd_usb, shift8, packed, reverse_endian); - return true; -} - -static bool -alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - int err; - bool success; - - err = snd_pcm_open(&ad->pcm, alsa_device(ad), - SND_PCM_STREAM_PLAYBACK, ad->mode); - if (err < 0) { - g_set_error(error, alsa_output_quark(), err, - "Failed to open ALSA device \"%s\": %s", - alsa_device(ad), snd_strerror(err)); - return false; - } - - g_debug("opened %s type=%s", snd_pcm_name(ad->pcm), - snd_pcm_type_name(snd_pcm_type(ad->pcm))); - - success = alsa_setup_or_dsd(ad, audio_format, error); - if (!success) { - snd_pcm_close(ad->pcm); - return false; - } - - ad->in_frame_size = audio_format_frame_size(audio_format); - ad->out_frame_size = pcm_export_frame_size(&ad->export, audio_format); - - return true; -} - -static int -alsa_recover(struct alsa_data *ad, int err) -{ - if (err == -EPIPE) { - g_debug("Underrun on ALSA device \"%s\"\n", alsa_device(ad)); - } else if (err == -ESTRPIPE) { - g_debug("ALSA device \"%s\" was suspended\n", alsa_device(ad)); - } - - switch (snd_pcm_state(ad->pcm)) { - case SND_PCM_STATE_PAUSED: - err = snd_pcm_pause(ad->pcm, /* disable */ 0); - break; - case SND_PCM_STATE_SUSPENDED: - err = snd_pcm_resume(ad->pcm); - if (err == -EAGAIN) - return 0; - /* fall-through to snd_pcm_prepare: */ - case SND_PCM_STATE_SETUP: - case SND_PCM_STATE_XRUN: - ad->period_position = 0; - err = snd_pcm_prepare(ad->pcm); - break; - case SND_PCM_STATE_DISCONNECTED: - break; - /* this is no error, so just keep running */ - case SND_PCM_STATE_RUNNING: - err = 0; - break; - default: - /* unknown state, do nothing */ - break; - } - - return err; -} - -static void -alsa_drain(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) - return; - - if (ad->period_position > 0) { - /* generate some silence to finish the partial - period */ - snd_pcm_uframes_t nframes = - ad->period_frames - ad->period_position; - size_t nbytes = nframes * ad->out_frame_size; - void *buffer = g_malloc(nbytes); - snd_pcm_hw_params_t *params; - snd_pcm_format_t format; - unsigned channels; - - snd_pcm_hw_params_alloca(¶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); - } - - snd_pcm_drain(ad->pcm); - - ad->period_position = 0; -} - -static void -alsa_cancel(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - ad->period_position = 0; - - snd_pcm_drop(ad->pcm); -} - -static void -alsa_close(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - snd_pcm_close(ad->pcm); -} - -static size_t -alsa_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - assert(size % ad->in_frame_size == 0); - - chunk = pcm_export(&ad->export, chunk, size, &size); - - assert(size % ad->out_frame_size == 0); - - size /= ad->out_frame_size; - - while (true) { - snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); - if (ret > 0) { - ad->period_position = (ad->period_position + ret) - % ad->period_frames; - - size_t bytes_written = ret * ad->out_frame_size; - return pcm_export_source_size(&ad->export, - bytes_written); - } - - if (ret < 0 && ret != -EAGAIN && ret != -EINTR && - alsa_recover(ad, ret) < 0) { - g_set_error(error, alsa_output_quark(), errno, - "%s", snd_strerror(-errno)); - return 0; - } - } -} - -const struct audio_output_plugin alsa_output_plugin = { - .name = "alsa", - .test_default_device = alsa_test_default_device, - .init = alsa_init, - .finish = alsa_finish, - .enable = alsa_output_enable, - .disable = alsa_output_disable, - .open = alsa_open, - .play = alsa_play, - .drain = alsa_drain, - .cancel = alsa_cancel, - .close = alsa_close, - - .mixer_plugin = &alsa_mixer_plugin, -}; diff --git a/src/output/alsa_output_plugin.h b/src/output/alsa_output_plugin.h deleted file mode 100644 index daa1f3615..000000000 --- a/src/output/alsa_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ALSA_OUTPUT_PLUGIN_H -#define MPD_ALSA_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin alsa_output_plugin; - -#endif diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c index 022be0b4a..3d6171ae2 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 72de90457..000000000 --- a/src/output/httpd_client.c +++ /dev/null @@ -1,764 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "httpd_client.h" -#include "httpd_internal.h" -#include "fifo_buffer.h" -#include "page.h" -#include "icy_server.h" -#include "glib_socket.h" - -#include <stdbool.h> -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "httpd_output" - -struct httpd_client { - /** - * The httpd output object this client is connected to. - */ - struct httpd_output *httpd; - - /** - * The TCP socket. - */ - GIOChannel *channel; - - /** - * The GLib main loop source id for reading from the socket, - * and to detect errors. - */ - guint read_source_id; - - /** - * The GLib main loop source id for writing to the socket. If - * 0, then there is no event source currently (because there - * are no queued pages). - */ - guint write_source_id; - - /** - * For buffered reading. This pointer is only valid while the - * HTTP request is read. - */ - struct fifo_buffer *input; - - /** - * The current state of the client. - */ - enum { - /** reading the request line */ - REQUEST, - - /** reading the request headers */ - HEADERS, - - /** sending the HTTP response */ - RESPONSE, - } state; - - /** - * A queue of #page objects to be sent to the client. - */ - GQueue *pages; - - /** - * The #page which is currently being sent to the client. - */ - struct page *current_page; - - /** - * The amount of bytes which were already sent from - * #current_page. - */ - size_t current_position; - - /** - * If DLNA streaming was an option. - */ - bool dlna_streaming_requested; - - /* ICY */ - - /** - * Do we support sending Icy-Metadata to the client? This is - * disabled if the httpd audio output uses encoder tags. - */ - bool metadata_supported; - - /** - * If we should sent icy metadata. - */ - bool metadata_requested; - - /** - * If the current metadata was already sent to the client. - */ - bool metadata_sent; - - /** - * The amount of streaming data between each metadata block - */ - guint metaint; - - /** - * The metadata as #page which is currently being sent to the client. - */ - struct page *metadata; - - /* - * The amount of bytes which were already sent from the metadata. - */ - size_t metadata_current_position; - - /** - * The amount of streaming data sent to the client - * since the last icy information was sent. - */ - guint metadata_fill; -}; - -static void -httpd_client_unref_page(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct page *page = data; - - page_unref(page); -} - -void -httpd_client_free(struct httpd_client *client) -{ - assert(client != NULL); - - if (client->state == RESPONSE) { - if (client->write_source_id != 0) - g_source_remove(client->write_source_id); - - if (client->current_page != NULL) - page_unref(client->current_page); - - g_queue_foreach(client->pages, httpd_client_unref_page, NULL); - g_queue_free(client->pages); - } else - fifo_buffer_free(client->input); - - if (client->metadata) - page_unref (client->metadata); - - g_source_remove(client->read_source_id); - g_io_channel_unref(client->channel); - g_free(client); -} - -/** - * Frees the client and removes it from the server's client list. - */ -static void -httpd_client_close(struct httpd_client *client) -{ - assert(client != NULL); - - httpd_output_remove_client(client->httpd, client); - httpd_client_free(client); -} - -/** - * Switch the client to the "RESPONSE" state. - */ -static void -httpd_client_begin_response(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - client->state = RESPONSE; - client->write_source_id = 0; - client->pages = g_queue_new(); - client->current_page = NULL; - - httpd_output_send_header(client->httpd, client); -} - -/** - * Handle a line of the HTTP request. - */ -static bool -httpd_client_handle_line(struct httpd_client *client, const char *line) -{ - assert(client->state != RESPONSE); - - if (client->state == REQUEST) { - if (strncmp(line, "GET /", 5) != 0) { - /* only GET is supported */ - g_warning("malformed request line from client"); - return false; - } - - line = strchr(line + 5, ' '); - if (line == NULL || strncmp(line + 1, "HTTP/", 5) != 0) { - /* HTTP/0.9 without request headers */ - httpd_client_begin_response(client); - return true; - } - - /* after the request line, request headers follow */ - client->state = HEADERS; - return true; - } else { - if (*line == 0) { - /* empty line: request is finished */ - httpd_client_begin_response(client); - return true; - } - - if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) { - /* Send icy metadata */ - client->metadata_requested = - client->metadata_supported; - return true; - } - - if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { - /* Send as dlna */ - client->dlna_streaming_requested = true; - /* metadata is not supported by dlna streaming, so disable it */ - client->metadata_supported = false; - client->metadata_requested = false; - return true; - } - - /* expect more request headers */ - return true; - } -} - -/** - * Check if a complete line of input is present in the input buffer, - * and duplicates it. It is removed from the input buffer. The - * return value has to be freed with g_free(). - */ -static char * -httpd_client_read_line(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - const char *p, *newline; - size_t length; - char *line; - - p = fifo_buffer_read(client->input, &length); - if (p == NULL) - /* empty input buffer */ - return NULL; - - newline = memchr(p, '\n', length); - if (newline == NULL) - /* incomplete line */ - return NULL; - - line = g_strndup(p, newline - p); - fifo_buffer_consume(client->input, newline - p + 1); - - /* remove trailing whitespace (e.g. '\r') */ - return g_strchomp(line); -} - -/** - * Sends the status line and response headers to the client. - */ -static bool -httpd_client_send_response(struct httpd_client *client) -{ - char buffer[1024]; - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->state == RESPONSE); - - if (client->dlna_streaming_requested) { - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 206 OK\r\n" - "Content-Type: %s\r\n" - "Content-Length: 10000\r\n" - "Content-RangeX: 0-1000000/1000000\r\n" - "transferMode.dlna.org: Streaming\r\n" - "Accept-Ranges: bytes\r\n" - "Connection: close\r\n" - "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" - "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" - "\r\n", - client->httpd->content_type); - - } else if (client->metadata_requested) { - gchar *metadata_header; - - metadata_header = icy_server_metadata_header( - client->httpd->name, - client->httpd->genre, - client->httpd->website, - client->httpd->content_type, - client->metaint); - - g_strlcpy(buffer, metadata_header, sizeof(buffer)); - - g_free(metadata_header); - - } else { /* revert to a normal HTTP request */ - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 200 OK\r\n" - "Content-Type: %s\r\n" - "Connection: close\r\n" - "Pragma: no-cache\r\n" - "Cache-Control: no-cache, no-store\r\n" - "\r\n", - client->httpd->content_type); - } - - status = g_io_channel_write_chars(client->channel, - buffer, strlen(buffer), - &bytes_written, &error); - - switch (status) { - case G_IO_STATUS_NORMAL: - case G_IO_STATUS_AGAIN: - return true; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - httpd_client_close(client); - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - g_warning("failed to write to client: %s", error->message); - g_error_free(error); - - httpd_client_close(client); - return false; - } - - /* unreachable */ - httpd_client_close(client); - return false; -} - -/** - * Data has been received from the client and it is appended to the - * input buffer. - */ -static bool -httpd_client_received(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - char *line; - bool success; - - while ((line = httpd_client_read_line(client)) != NULL) { - success = httpd_client_handle_line(client, line); - g_free(line); - if (!success) { - assert(client->state != RESPONSE); - return false; - } - - if (client->state == RESPONSE) { - if (!fifo_buffer_is_empty(client->input)) { - g_warning("unexpected input from client"); - return false; - } - - fifo_buffer_free(client->input); - - return httpd_client_send_response(client); - } - } - - return true; -} - -static bool -httpd_client_read(struct httpd_client *client) -{ - char *p; - size_t max_length; - GError *error = NULL; - GIOStatus status; - gsize bytes_read; - - if (client->state == RESPONSE) { - /* the client has already sent the request, and he - must not send more */ - char buffer[1]; - - status = g_io_channel_read_chars(client->channel, buffer, - sizeof(buffer), &bytes_read, - NULL); - if (status == G_IO_STATUS_NORMAL) - g_warning("unexpected input from client"); - - return false; - } - - p = fifo_buffer_write(client->input, &max_length); - if (p == NULL) { - g_warning("buffer overflow"); - return false; - } - - status = g_io_channel_read_chars(client->channel, p, max_length, - &bytes_read, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - fifo_buffer_append(client->input, bytes_read); - return httpd_client_received(client); - - case G_IO_STATUS_AGAIN: - /* try again later, after select() */ - return true; - - case G_IO_STATUS_EOF: - /* peer disconnected */ - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - g_warning("failed to read from client: %s", - error->message); - g_error_free(error); - return false; - } - - /* unreachable */ - return false; -} - -static gboolean -httpd_client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct httpd_client *client = data; - struct httpd_output *httpd = client->httpd; - bool ret; - - g_mutex_lock(httpd->mutex); - - if (condition == G_IO_IN && httpd_client_read(client)) { - ret = true; - } else { - httpd_client_close(client); - ret = false; - } - - g_mutex_unlock(httpd->mutex); - - return ret; -} - -struct httpd_client * -httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported) -{ - struct httpd_client *client = g_new(struct httpd_client, 1); - - client->httpd = httpd; - - client->channel = g_io_channel_new_socket(fd); - - /* GLib is responsible for closing the file descriptor */ - g_io_channel_set_close_on_unref(client->channel, true); - /* NULL encoding means the stream is binary safe */ - g_io_channel_set_encoding(client->channel, NULL, NULL); - /* we prefer to do buffering */ - g_io_channel_set_buffered(client->channel, false); - - client->read_source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - httpd_client_in_event, client); - - client->input = fifo_buffer_new(4096); - client->state = REQUEST; - - client->dlna_streaming_requested = false; - client->metadata_supported = metadata_supported; - client->metadata_requested = false; - client->metadata_sent = true; - client->metaint = 8192; /*TODO: just a std value */ - client->metadata = NULL; - client->metadata_current_position = 0; - client->metadata_fill = 0; - - return client; -} - -static void -httpd_client_add_page_size(gpointer data, gpointer user_data) -{ - struct page *page = data; - size_t *size = user_data; - - *size += page->size; -} - -size_t -httpd_client_queue_size(const struct httpd_client *client) -{ - size_t size = 0; - - if (client->state != RESPONSE) - return 0; - - g_queue_foreach(client->pages, httpd_client_add_page_size, &size); - return size; -} - -void -httpd_client_cancel(struct httpd_client *client) -{ - if (client->state != RESPONSE) - return; - - g_queue_foreach(client->pages, httpd_client_unref_page, NULL); - g_queue_clear(client->pages); - - if (client->write_source_id != 0 && client->current_page == NULL) { - g_source_remove(client->write_source_id); - client->write_source_id = 0; - } -} - -static GIOStatus -write_page_to_channel(GIOChannel *channel, - const struct page *page, size_t position, - gsize *bytes_written_r, GError **error) -{ - assert(channel != NULL); - assert(page != NULL); - assert(position < page->size); - - return g_io_channel_write_chars(channel, - (const gchar*)page->data + position, - page->size - position, - bytes_written_r, error); -} - -static GIOStatus -write_n_bytes_to_channel(GIOChannel *channel, const struct page *page, - size_t position, gint n, - gsize *bytes_written_r, GError **error) -{ - GIOStatus status; - - assert(channel != NULL); - assert(page != NULL); - assert(position < page->size); - - if (n == -1) { - status = write_page_to_channel (channel, page, position, - bytes_written_r, error); - } else { - status = g_io_channel_write_chars(channel, - (const gchar*)page->data + position, - n, bytes_written_r, error); - } - - return status; -} - -static gint -bytes_left_till_metadata (struct httpd_client *client) -{ - assert(client != NULL); - - if (client->metadata_requested && - client->current_page->size - client->current_position - > client->metaint - client->metadata_fill) - return client->metaint - client->metadata_fill; - - return -1; -} - -static gboolean -httpd_client_out_event(GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, gpointer data) -{ - struct httpd_client *client = data; - struct httpd_output *httpd = client->httpd; - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - gint bytes_to_write; - - g_mutex_lock(httpd->mutex); - - assert(condition == G_IO_OUT); - assert(client->state == RESPONSE); - - if (client->write_source_id == 0) { - /* another thread has removed the event source while - this thread was waiting for httpd->mutex */ - g_mutex_unlock(httpd->mutex); - return false; - } - - if (client->current_page == NULL) { - client->current_page = g_queue_pop_head(client->pages); - client->current_position = 0; - } - - bytes_to_write = bytes_left_till_metadata(client); - - if (bytes_to_write == 0) { - gint metadata_to_write; - - metadata_to_write = client->metadata_current_position; - - if (!client->metadata_sent) { - status = write_page_to_channel(source, - client->metadata, - metadata_to_write, - &bytes_written, &error); - - client->metadata_current_position += bytes_written; - - if (client->metadata->size - - client->metadata_current_position == 0) { - client->metadata_fill = 0; - client->metadata_current_position = 0; - client->metadata_sent = true; - } - } else { - struct page *empty_meta; - guchar empty_data = 0; - - empty_meta = page_new_copy(&empty_data, 1); - - status = write_page_to_channel(source, - empty_meta, - metadata_to_write, - &bytes_written, &error); - - client->metadata_current_position += bytes_written; - - if (empty_meta->size - - client->metadata_current_position == 0) { - client->metadata_fill = 0; - client->metadata_current_position = 0; - } - } - - bytes_written = 0; - } else { - status = write_n_bytes_to_channel(source, client->current_page, - client->current_position, bytes_to_write, - &bytes_written, &error); - } - - switch (status) { - case G_IO_STATUS_NORMAL: - client->current_position += bytes_written; - assert(client->current_position <= client->current_page->size); - - if (client->metadata_requested) - client->metadata_fill += bytes_written; - - if (client->current_position >= client->current_page->size) { - page_unref(client->current_page); - client->current_page = NULL; - - if (g_queue_is_empty(client->pages)) { - /* all pages are sent: remove the - event source */ - client->write_source_id = 0; - - g_mutex_unlock(httpd->mutex); - return false; - } - } - - g_mutex_unlock(httpd->mutex); - return true; - - case G_IO_STATUS_AGAIN: - g_mutex_unlock(httpd->mutex); - return true; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - g_warning("failed to write to client: %s", error->message); - g_error_free(error); - - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; - } - - /* unreachable */ - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; -} - -void -httpd_client_send(struct httpd_client *client, struct page *page) -{ - if (client->state != RESPONSE) - /* the client is still writing the HTTP request */ - return; - - page_ref(page); - g_queue_push_tail(client->pages, page); - - if (client->write_source_id == 0) - client->write_source_id = - g_io_add_watch(client->channel, G_IO_OUT, - httpd_client_out_event, client); -} - -void -httpd_client_send_metadata(struct httpd_client *client, struct page *page) -{ - if (client->metadata) { - page_unref(client->metadata); - client->metadata = NULL; - } - - g_return_if_fail (page); - - page_ref(page); - client->metadata = page; - client->metadata_sent = false; -} diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h deleted file mode 100644 index 739163f42..000000000 --- a/src/output/httpd_client.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_HTTPD_CLIENT_H -#define MPD_OUTPUT_HTTPD_CLIENT_H - -#include <glib.h> - -#include <stdbool.h> - -struct httpd_client; -struct httpd_output; -struct page; - -/** - * Creates a new #httpd_client object - * - * @param httpd the HTTP output device - * @param fd the socket file descriptor - */ -struct httpd_client * -httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported); - -/** - * Frees memory and resources allocated by the #httpd_client object. - * This does not remove it from the #httpd_output object. - */ -void -httpd_client_free(struct httpd_client *client); - -/** - * Returns the total size of this client's page queue. - */ -size_t -httpd_client_queue_size(const struct httpd_client *client); - -/** - * Clears the page queue. - */ -void -httpd_client_cancel(struct httpd_client *client); - -/** - * Appends a page to the client's queue. - */ -void -httpd_client_send(struct httpd_client *client, struct page *page); - -/** - * Sends the passed metadata. - */ -void -httpd_client_send_metadata(struct httpd_client *client, struct page *page); - -#endif diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h deleted file mode 100644 index 5dcb8ab9b..000000000 --- a/src/output/httpd_internal.h +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Internal declarations for the "httpd" audio output plugin. - */ - -#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H -#define MPD_OUTPUT_HTTPD_INTERNAL_H - -#include "output_internal.h" -#include "timer.h" - -#include <glib.h> - -#include <stdbool.h> - -struct httpd_client; - -struct httpd_output { - struct audio_output base; - - /** - * True if the audio output is open and accepts client - * connections. - */ - bool open; - - /** - * The configured encoder plugin. - */ - struct encoder *encoder; - - /** - * Number of bytes which were fed into the encoder, without - * ever receiving new output. This is used to estimate - * whether MPD should manually flush the encoder, to avoid - * buffer underruns in the client. - */ - size_t unflushed_input; - - /** - * The MIME type produced by the #encoder. - */ - const char *content_type; - - /** - * This mutex protects the listener socket and the client - * list. - */ - GMutex *mutex; - - /** - * A #timer object to synchronize this output with the - * wallclock. - */ - struct timer *timer; - - /** - * The listener socket. - */ - struct server_socket *server_socket; - - /** - * The header page, which is sent to every client on connect. - */ - struct page *header; - - /** - * The metadata, which is sent to every client. - */ - struct page *metadata; - - /** - * The configured name. - */ - char const *name; - /** - * The configured genre. - */ - char const *genre; - /** - * The configured website address. - */ - char const *website; - - /** - * A linked list containing all clients which are currently - * connected. - */ - GList *clients; - - /** - * A temporary buffer for the httpd_output_read_page() - * function. - */ - char buffer[32768]; - - /** - * The maximum and current number of clients connected - * at the same time. - */ - guint clients_max, clients_cnt; -}; - -/** - * Removes a client from the httpd_output.clients linked list. - */ -void -httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client); - -/** - * Sends the encoder header to the client. This is called right after - * the response headers have been sent. - */ -void -httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client); - -#endif diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c deleted file mode 100644 index 1d730df7f..000000000 --- a/src/output/httpd_output_plugin.c +++ /dev/null @@ -1,623 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "httpd_output_plugin.h" -#include "httpd_internal.h" -#include "httpd_client.h" -#include "output_api.h" -#include "encoder_plugin.h" -#include "encoder_list.h" -#include "resolver.h" -#include "page.h" -#include "icy_server.h" -#include "fd_util.h" -#include "server_socket.h" - -#include <assert.h> - -#include <sys/types.h> -#include <unistd.h> -#include <errno.h> - -#ifdef HAVE_LIBWRAP -#include <sys/socket.h> /* needed for AF_UNIX */ -#include <tcpd.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "httpd_output" - -/** - * The quark used for GError.domain. - */ -static inline GQuark -httpd_output_quark(void) -{ - return g_quark_from_static_string("httpd_output"); -} - -/** - * Check whether there is at least one client. - * - * Caller must lock the mutex. - */ -G_GNUC_PURE -static bool -httpd_output_has_clients(const struct httpd_output *httpd) -{ - return httpd->clients != NULL; -} - -/** - * Check whether there is at least one client. - */ -G_GNUC_PURE -static bool -httpd_output_lock_has_clients(const struct httpd_output *httpd) -{ - g_mutex_lock(httpd->mutex); - bool result = httpd_output_has_clients(httpd); - g_mutex_unlock(httpd->mutex); - return result; -} - -static void -httpd_listen_in_event(int fd, const struct sockaddr *address, - size_t address_length, int uid, void *ctx); - -static bool -httpd_output_bind(struct httpd_output *httpd, GError **error_r) -{ - httpd->open = false; - - g_mutex_lock(httpd->mutex); - bool success = server_socket_open(httpd->server_socket, error_r); - g_mutex_unlock(httpd->mutex); - - return success; -} - -static void -httpd_output_unbind(struct httpd_output *httpd) -{ - assert(!httpd->open); - - g_mutex_lock(httpd->mutex); - server_socket_close(httpd->server_socket); - g_mutex_unlock(httpd->mutex); -} - -static struct audio_output * -httpd_output_init(const struct config_param *param, - GError **error) -{ - struct httpd_output *httpd = g_new(struct httpd_output, 1); - if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) { - g_free(httpd); - return NULL; - } - - /* read configuration */ - httpd->name = - config_get_block_string(param, "name", "Set name in config"); - httpd->genre = - config_get_block_string(param, "genre", "Set genre in config"); - httpd->website = - config_get_block_string(param, "website", "Set website in config"); - - guint port = config_get_block_unsigned(param, "port", 8000); - - const char *encoder_name = - config_get_block_string(param, "encoder", "vorbis"); - const struct encoder_plugin *encoder_plugin = - encoder_plugin_get(encoder_name); - if (encoder_plugin == NULL) { - g_set_error(error, httpd_output_quark(), 0, - "No such encoder: %s", encoder_name); - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0); - - /* set up bind_to_address */ - - httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd); - - const char *bind_to_address = - config_get_block_string(param, "bind_to_address", NULL); - bool success = bind_to_address != NULL && - strcmp(bind_to_address, "any") != 0 - ? server_socket_add_host(httpd->server_socket, bind_to_address, - port, error) - : server_socket_add_port(httpd->server_socket, port, error); - if (!success) { - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - /* initialize metadata */ - httpd->metadata = NULL; - httpd->unflushed_input = 0; - - /* initialize encoder */ - - httpd->encoder = encoder_init(encoder_plugin, param, error); - if (httpd->encoder == NULL) { - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - /* determine content type */ - httpd->content_type = encoder_get_mime_type(httpd->encoder); - if (httpd->content_type == NULL) { - httpd->content_type = "application/octet-stream"; - } - - httpd->mutex = g_mutex_new(); - - return &httpd->base; -} - -static void -httpd_output_finish(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (httpd->metadata) - page_unref(httpd->metadata); - - encoder_finish(httpd->encoder); - server_socket_free(httpd->server_socket); - g_mutex_free(httpd->mutex); - ao_base_finish(&httpd->base); - g_free(httpd); -} - -/** - * Creates a new #httpd_client object and adds it into the - * httpd_output.clients linked list. - */ -static void -httpd_client_add(struct httpd_output *httpd, int fd) -{ - struct httpd_client *client = - httpd_client_new(httpd, fd, - httpd->encoder->plugin->tag == NULL); - - httpd->clients = g_list_prepend(httpd->clients, client); - httpd->clients_cnt++; - - /* pass metadata to client */ - if (httpd->metadata) - httpd_client_send_metadata(client, httpd->metadata); -} - -static void -httpd_listen_in_event(int fd, const struct sockaddr *address, - size_t address_length, G_GNUC_UNUSED int uid, void *ctx) -{ - struct httpd_output *httpd = ctx; - - /* the listener socket has become readable - a client has - connected */ - -#ifdef HAVE_LIBWRAP - if (address->sa_family != AF_UNIX) { - char *hostaddr = sockaddr_to_string(address, address_length, NULL); - const char *progname = g_get_prgname(); - - struct request_info req; - request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); - - fromhost(&req); - - if (!hosts_access(&req)) { - /* tcp wrappers says no */ - g_warning("libwrap refused connection (libwrap=%s) from %s", - progname, hostaddr); - g_free(hostaddr); - close_socket(fd); - g_mutex_unlock(httpd->mutex); - return; - } - - g_free(hostaddr); - } -#else - (void)address; - (void)address_length; -#endif /* HAVE_WRAP */ - - g_mutex_lock(httpd->mutex); - - if (fd >= 0) { - /* can we allow additional client */ - if (httpd->open && - (httpd->clients_max == 0 || - httpd->clients_cnt < httpd->clients_max)) - httpd_client_add(httpd, fd); - else - close_socket(fd); - } else if (fd < 0 && errno != EINTR) { - g_warning("accept() failed: %s", g_strerror(errno)); - } - - g_mutex_unlock(httpd->mutex); -} - -/** - * Reads data from the encoder (as much as available) and returns it - * as a new #page object. - */ -static struct page * -httpd_output_read_page(struct httpd_output *httpd) -{ - if (httpd->unflushed_input >= 65536) { - /* we have fed a lot of input into the encoder, but it - didn't give anything back yet - flush now to avoid - buffer underruns */ - encoder_flush(httpd->encoder, NULL); - httpd->unflushed_input = 0; - } - - size_t size = 0; - do { - size_t nbytes = encoder_read(httpd->encoder, - httpd->buffer + size, - sizeof(httpd->buffer) - size); - if (nbytes == 0) - break; - - httpd->unflushed_input = 0; - - size += nbytes; - } while (size < sizeof(httpd->buffer)); - - if (size == 0) - return NULL; - - return page_new_copy(httpd->buffer, size); -} - -static bool -httpd_output_encoder_open(struct httpd_output *httpd, - struct audio_format *audio_format, - GError **error) -{ - if (!encoder_open(httpd->encoder, audio_format, error)) - return false; - - /* we have to remember the encoder header, i.e. the first - bytes of encoder output after opening it, because it has to - be sent to every new client */ - httpd->header = httpd_output_read_page(httpd); - - httpd->unflushed_input = 0; - - return true; -} - -static bool -httpd_output_enable(struct audio_output *ao, GError **error_r) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - return httpd_output_bind(httpd, error_r); -} - -static void -httpd_output_disable(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - httpd_output_unbind(httpd); -} - -static bool -httpd_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - g_mutex_lock(httpd->mutex); - - /* open the encoder */ - - if (!httpd_output_encoder_open(httpd, audio_format, error)) { - g_mutex_unlock(httpd->mutex); - return false; - } - - /* initialize other attributes */ - - httpd->clients = NULL; - httpd->clients_cnt = 0; - httpd->timer = timer_new(audio_format); - - httpd->open = true; - - g_mutex_unlock(httpd->mutex); - return true; -} - -static void -httpd_client_delete(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - httpd_client_free(client); -} - -static void -httpd_output_close(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - g_mutex_lock(httpd->mutex); - - httpd->open = false; - - timer_free(httpd->timer); - - g_list_foreach(httpd->clients, httpd_client_delete, NULL); - g_list_free(httpd->clients); - - if (httpd->header != NULL) - page_unref(httpd->header); - - encoder_close(httpd->encoder); - - g_mutex_unlock(httpd->mutex); -} - -void -httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client) -{ - assert(httpd != NULL); - assert(client != NULL); - - httpd->clients = g_list_remove(httpd->clients, client); - httpd->clients_cnt--; -} - -void -httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client) -{ - if (httpd->header != NULL) - httpd_client_send(client, httpd->header); -} - -static unsigned -httpd_output_delay(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (!httpd_output_lock_has_clients(httpd) && httpd->base.pause) { - /* if there's no client and this output is paused, - then httpd_output_pause() will not do anything, it - will not fill the buffer and it will not update the - timer; therefore, we reset the timer here */ - timer_reset(httpd->timer); - - /* some arbitrary delay that is long enough to avoid - consuming too much CPU, and short enough to notice - new clients quickly enough */ - return 1000; - } - - return httpd->timer->started - ? timer_delay(httpd->timer) - : 0; -} - -static void -httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - if (httpd_client_queue_size(client) > 256 * 1024) { - g_debug("client is too slow, flushing its queue"); - httpd_client_cancel(client); - } -} - -static void -httpd_client_send_page(gpointer data, gpointer user_data) -{ - struct httpd_client *client = data; - struct page *page = user_data; - - httpd_client_send(client, page); -} - -/** - * Broadcasts a page struct to all clients. - */ -static void -httpd_output_broadcast_page(struct httpd_output *httpd, struct page *page) -{ - assert(page != NULL); - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_send_page, page); - g_mutex_unlock(httpd->mutex); -} - -/** - * Broadcasts data from the encoder to all clients. - */ -static void -httpd_output_encoder_to_clients(struct httpd_output *httpd) -{ - struct page *page; - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_check_queue, NULL); - g_mutex_unlock(httpd->mutex); - - while ((page = httpd_output_read_page(httpd)) != NULL) { - httpd_output_broadcast_page(httpd, page); - page_unref(page); - } -} - -static bool -httpd_output_encode_and_play(struct httpd_output *httpd, - const void *chunk, size_t size, GError **error) -{ - if (!encoder_write(httpd->encoder, chunk, size, error)) - return false; - - httpd->unflushed_input += size; - - httpd_output_encoder_to_clients(httpd); - - return true; -} - -static size_t -httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error_r) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (httpd_output_lock_has_clients(httpd)) { - if (!httpd_output_encode_and_play(httpd, chunk, size, error_r)) - return 0; - } - - if (!httpd->timer->started) - timer_start(httpd->timer); - timer_add(httpd->timer, size); - - return size; -} - -static bool -httpd_output_pause(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (httpd_output_lock_has_clients(httpd)) { - static const char silence[1020]; - return httpd_output_play(ao, silence, sizeof(silence), - NULL) > 0; - } else { - return true; - } -} - -static void -httpd_send_metadata(gpointer data, gpointer user_data) -{ - struct httpd_client *client = data; - struct page *icy_metadata = user_data; - - httpd_client_send_metadata(client, icy_metadata); -} - -static void -httpd_output_tag(struct audio_output *ao, const struct tag *tag) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - assert(tag != NULL); - - if (httpd->encoder->plugin->tag != NULL) { - /* embed encoder tags */ - - /* flush the current stream, and end it */ - - encoder_pre_tag(httpd->encoder, NULL); - httpd_output_encoder_to_clients(httpd); - - /* send the tag to the encoder - which starts a new - stream now */ - - encoder_tag(httpd->encoder, tag, NULL); - - /* the first page generated by the encoder will now be - used as the new "header" page, which is sent to all - new clients */ - - struct page *page = httpd_output_read_page(httpd); - if (page != NULL) { - if (httpd->header != NULL) - page_unref(httpd->header); - httpd->header = page; - httpd_output_broadcast_page(httpd, page); - } - } else { - /* use Icy-Metadata */ - - if (httpd->metadata != NULL) - page_unref (httpd->metadata); - - httpd->metadata = - icy_server_metadata_page(tag, TAG_ALBUM, - TAG_ARTIST, TAG_TITLE, - TAG_NUM_OF_ITEM_TYPES); - if (httpd->metadata != NULL) { - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, - httpd_send_metadata, httpd->metadata); - g_mutex_unlock(httpd->mutex); - } - } -} - -static void -httpd_client_cancel_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - httpd_client_cancel(client); -} - -static void -httpd_output_cancel(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL); - g_mutex_unlock(httpd->mutex); -} - -const struct audio_output_plugin httpd_output_plugin = { - .name = "httpd", - .init = httpd_output_init, - .finish = httpd_output_finish, - .enable = httpd_output_enable, - .disable = httpd_output_disable, - .open = httpd_output_open, - .close = httpd_output_close, - .delay = httpd_output_delay, - .send_tag = httpd_output_tag, - .play = httpd_output_play, - .pause = httpd_output_pause, - .cancel = httpd_output_cancel, -}; diff --git a/src/output/httpd_output_plugin.h b/src/output/httpd_output_plugin.h deleted file mode 100644 index d0eb1533f..000000000 --- a/src/output/httpd_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_HTTPD_OUTPUT_PLUGIN_H -#define MPD_HTTPD_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin httpd_output_plugin; - -#endif diff --git a/src/output/null_output_plugin.c b/src/output/null_output_plugin.c deleted file mode 100644 index 9d7588fff..000000000 --- a/src/output/null_output_plugin.c +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "null_output_plugin.h" -#include "output_api.h" -#include "timer.h" - -#include <glib.h> - -#include <assert.h> - -struct null_data { - struct audio_output base; - - bool sync; - - struct timer *timer; -}; - -static struct audio_output * -null_init(const struct config_param *param, GError **error_r) -{ - struct null_data *nd = g_new(struct null_data, 1); - - if (!ao_base_init(&nd->base, &null_output_plugin, param, error_r)) { - g_free(nd); - return NULL; - } - - nd->sync = config_get_block_bool(param, "sync", true); - - return &nd->base; -} - -static void -null_finish(struct audio_output *ao) -{ - struct null_data *nd = (struct null_data *)ao; - - ao_base_finish(&nd->base); - g_free(nd); -} - -static bool -null_open(struct audio_output *ao, struct audio_format *audio_format, - G_GNUC_UNUSED GError **error) -{ - struct null_data *nd = (struct null_data *)ao; - - if (nd->sync) - nd->timer = timer_new(audio_format); - - return true; -} - -static void -null_close(struct audio_output *ao) -{ - struct null_data *nd = (struct null_data *)ao; - - if (nd->sync) - timer_free(nd->timer); -} - -static unsigned -null_delay(struct audio_output *ao) -{ - struct null_data *nd = (struct null_data *)ao; - - return nd->sync && nd->timer->started - ? timer_delay(nd->timer) - : 0; -} - -static size_t -null_play(struct audio_output *ao, G_GNUC_UNUSED const void *chunk, size_t size, - G_GNUC_UNUSED GError **error) -{ - struct null_data *nd = (struct null_data *)ao; - struct timer *timer = nd->timer; - - if (!nd->sync) - return size; - - if (!timer->started) - timer_start(timer); - timer_add(timer, size); - - return size; -} - -static void -null_cancel(struct audio_output *ao) -{ - struct null_data *nd = (struct null_data *)ao; - - if (!nd->sync) - return; - - timer_reset(nd->timer); -} - -const struct audio_output_plugin null_output_plugin = { - .name = "null", - .init = null_init, - .finish = null_finish, - .open = null_open, - .close = null_close, - .delay = null_delay, - .play = null_play, - .cancel = null_cancel, -}; diff --git a/src/output/null_output_plugin.h b/src/output/null_output_plugin.h deleted file mode 100644 index 392bf0aa3..000000000 --- a/src/output/null_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_NULL_OUTPUT_PLUGIN_H -#define MPD_NULL_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin null_output_plugin; - -#endif diff --git a/src/output/oss_output_plugin.c b/src/output/oss_output_plugin.c deleted file mode 100644 index e366a4537..000000000 --- a/src/output/oss_output_plugin.c +++ /dev/null @@ -1,788 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "oss_output_plugin.h" -#include "output_api.h" -#include "mixer_list.h" -#include "fd_util.h" -#include "glib_compat.h" - -#include <glib.h> - -#include <sys/stat.h> -#include <sys/ioctl.h> -#include <fcntl.h> -#include <errno.h> -#include <stdlib.h> -#include <unistd.h> -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "oss" - -#if defined(__OpenBSD__) || defined(__NetBSD__) -# include <soundcard.h> -#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ -# include <sys/soundcard.h> -#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ - -/* We got bug reports from FreeBSD users who said that the two 24 bit - formats generate white noise on FreeBSD, but 32 bit works. This is - a workaround until we know what exactly is expected by the kernel - audio drivers. */ -#ifndef __linux__ -#undef AFMT_S24_PACKED -#undef AFMT_S24_NE -#endif - -#ifdef AFMT_S24_PACKED -#include "pcm_export.h" -#endif - -struct oss_data { - struct audio_output base; - -#ifdef AFMT_S24_PACKED - struct pcm_export_state export; -#endif - - int fd; - const char *device; - - /** - * The current input audio format. This is needed to reopen - * the device after cancel(). - */ - struct audio_format audio_format; - - /** - * The current OSS audio format. This is needed to reopen the - * device after cancel(). - */ - int oss_format; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -oss_output_quark(void) -{ - return g_quark_from_static_string("oss_output"); -} - -static struct oss_data * -oss_data_new(void) -{ - struct oss_data *ret = g_new(struct oss_data, 1); - - ret->device = NULL; - ret->fd = -1; - - return ret; -} - -static void -oss_data_free(struct oss_data *od) -{ - g_free(od); -} - -enum oss_stat { - OSS_STAT_NO_ERROR = 0, - OSS_STAT_NOT_CHAR_DEV = -1, - OSS_STAT_NO_PERMS = -2, - OSS_STAT_DOESN_T_EXIST = -3, - OSS_STAT_OTHER = -4, -}; - -static enum oss_stat -oss_stat_device(const char *device, int *errno_r) -{ - struct stat st; - - if (0 == stat(device, &st)) { - if (!S_ISCHR(st.st_mode)) { - return OSS_STAT_NOT_CHAR_DEV; - } - } else { - *errno_r = errno; - - switch (errno) { - case ENOENT: - case ENOTDIR: - return OSS_STAT_DOESN_T_EXIST; - case EACCES: - return OSS_STAT_NO_PERMS; - default: - return OSS_STAT_OTHER; - } - } - - return OSS_STAT_NO_ERROR; -} - -static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; - -static bool -oss_output_test_default_device(void) -{ - int fd, i; - - for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { - fd = open_cloexec(default_devices[i], O_WRONLY, 0); - - if (fd >= 0) { - close(fd); - return true; - } - g_warning("Error opening OSS device \"%s\": %s\n", - default_devices[i], g_strerror(errno)); - } - - return false; -} - -static struct audio_output * -oss_open_default(GError **error) -{ - int i; - int err[G_N_ELEMENTS(default_devices)]; - enum oss_stat ret[G_N_ELEMENTS(default_devices)]; - - for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { - ret[i] = oss_stat_device(default_devices[i], &err[i]); - if (ret[i] == OSS_STAT_NO_ERROR) { - struct oss_data *od = oss_data_new(); - if (!ao_base_init(&od->base, &oss_output_plugin, NULL, - error)) { - g_free(od); - return NULL; - } - - od->device = default_devices[i]; - return &od->base; - } - } - - for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { - const char *dev = default_devices[i]; - switch(ret[i]) { - case OSS_STAT_NO_ERROR: - /* never reached */ - break; - case OSS_STAT_DOESN_T_EXIST: - g_warning("%s not found\n", dev); - break; - case OSS_STAT_NOT_CHAR_DEV: - g_warning("%s is not a character device\n", dev); - break; - case OSS_STAT_NO_PERMS: - g_warning("%s: permission denied\n", dev); - break; - case OSS_STAT_OTHER: - g_warning("Error accessing %s: %s\n", - dev, g_strerror(err[i])); - } - } - - g_set_error(error, oss_output_quark(), 0, - "error trying to open default OSS device"); - return NULL; -} - -static struct audio_output * -oss_output_init(const struct config_param *param, GError **error) -{ - const char *device = config_get_block_string(param, "device", NULL); - if (device != NULL) { - struct oss_data *od = oss_data_new(); - if (!ao_base_init(&od->base, &oss_output_plugin, param, - error)) { - g_free(od); - return NULL; - } - - od->device = device; - return &od->base; - } - - return oss_open_default(error); -} - -static void -oss_output_finish(struct audio_output *ao) -{ - struct oss_data *od = (struct oss_data *)ao; - - ao_base_finish(&od->base); - oss_data_free(od); -} - -#ifdef AFMT_S24_PACKED - -static bool -oss_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) -{ - struct oss_data *od = (struct oss_data *)ao; - - pcm_export_init(&od->export); - return true; -} - -static void -oss_output_disable(struct audio_output *ao) -{ - struct oss_data *od = (struct oss_data *)ao; - - pcm_export_deinit(&od->export); -} - -#endif - -static void -oss_close(struct oss_data *od) -{ - if (od->fd >= 0) - close(od->fd); - od->fd = -1; -} - -/** - * A tri-state type for oss_try_ioctl(). - */ -enum oss_setup_result { - SUCCESS, - ERROR, - UNSUPPORTED, -}; - -/** - * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is - * returned. If the parameter is not supported, UNSUPPORTED is - * returned. Any other failure returns ERROR and allocates a GError. - */ -static enum oss_setup_result -oss_try_ioctl_r(int fd, unsigned long request, int *value_r, - const char *msg, GError **error_r) -{ - assert(fd >= 0); - assert(value_r != NULL); - assert(msg != NULL); - assert(error_r == NULL || *error_r == NULL); - - int ret = ioctl(fd, request, value_r); - if (ret >= 0) - return SUCCESS; - - if (errno == EINVAL) - return UNSUPPORTED; - - g_set_error(error_r, oss_output_quark(), errno, - "%s: %s", msg, g_strerror(errno)); - return ERROR; -} - -/** - * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is - * returned. If the parameter is not supported, UNSUPPORTED is - * returned. Any other failure returns ERROR and allocates a GError. - */ -static enum oss_setup_result -oss_try_ioctl(int fd, unsigned long request, int value, - const char *msg, GError **error_r) -{ - return oss_try_ioctl_r(fd, request, &value, msg, error_r); -} - -/** - * Set up the channel number, and attempts to find alternatives if the - * specified number is not supported. - */ -static bool -oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r) -{ - const char *const msg = "Failed to set channel count"; - int channels = audio_format->channels; - enum oss_setup_result result = - oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error_r); - switch (result) { - case SUCCESS: - if (!audio_valid_channel_count(channels)) - break; - - audio_format->channels = channels; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - - for (unsigned i = 1; i < 2; ++i) { - if (i == audio_format->channels) - /* don't try that again */ - continue; - - channels = i; - result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, - msg, error_r); - switch (result) { - case SUCCESS: - if (!audio_valid_channel_count(channels)) - break; - - audio_format->channels = channels; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - } - - g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); - return false; -} - -/** - * Set up the sample rate, and attempts to find alternatives if the - * specified sample rate is not supported. - */ -static bool -oss_setup_sample_rate(int fd, struct audio_format *audio_format, - GError **error_r) -{ - const char *const msg = "Failed to set sample rate"; - int sample_rate = audio_format->sample_rate; - enum oss_setup_result result = - oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, - msg, error_r); - switch (result) { - case SUCCESS: - if (!audio_valid_sample_rate(sample_rate)) - break; - - audio_format->sample_rate = sample_rate; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - - static const int sample_rates[] = { 48000, 44100, 0 }; - for (unsigned i = 0; sample_rates[i] != 0; ++i) { - sample_rate = sample_rates[i]; - if (sample_rate == (int)audio_format->sample_rate) - continue; - - result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, - msg, error_r); - switch (result) { - case SUCCESS: - if (!audio_valid_sample_rate(sample_rate)) - break; - - audio_format->sample_rate = sample_rate; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - } - - g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); - return false; -} - -/** - * Convert a MPD sample format to its OSS counterpart. Returns - * AFMT_QUERY if there is no direct counterpart. - */ -static int -sample_format_to_oss(enum sample_format format) -{ - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_FLOAT: - case SAMPLE_FORMAT_DSD: - return AFMT_QUERY; - - case SAMPLE_FORMAT_S8: - return AFMT_S8; - - case SAMPLE_FORMAT_S16: - return AFMT_S16_NE; - - case SAMPLE_FORMAT_S24_P32: -#ifdef AFMT_S24_NE - return AFMT_S24_NE; -#else - return AFMT_QUERY; -#endif - - case SAMPLE_FORMAT_S32: -#ifdef AFMT_S32_NE - return AFMT_S32_NE; -#else - return AFMT_QUERY; -#endif - } - - return AFMT_QUERY; -} - -/** - * Convert an OSS sample format to its MPD counterpart. Returns - * SAMPLE_FORMAT_UNDEFINED if there is no direct counterpart. - */ -static enum sample_format -sample_format_from_oss(int format) -{ - switch (format) { - case AFMT_S8: - return SAMPLE_FORMAT_S8; - - case AFMT_S16_NE: - return SAMPLE_FORMAT_S16; - -#ifdef AFMT_S24_PACKED - case AFMT_S24_PACKED: - return SAMPLE_FORMAT_S24_P32; -#endif - -#ifdef AFMT_S24_NE - case AFMT_S24_NE: - return SAMPLE_FORMAT_S24_P32; -#endif - -#ifdef AFMT_S32_NE - case AFMT_S32_NE: - return SAMPLE_FORMAT_S32; -#endif - - default: - return SAMPLE_FORMAT_UNDEFINED; - } -} - -/** - * Probe one sample format. - * - * @return the selected sample format or SAMPLE_FORMAT_UNDEFINED on - * error - */ -static enum oss_setup_result -oss_probe_sample_format(int fd, enum sample_format sample_format, - enum sample_format *sample_format_r, - int *oss_format_r, -#ifdef AFMT_S24_PACKED - struct pcm_export_state *export, -#endif - GError **error_r) -{ - int oss_format = sample_format_to_oss(sample_format); - if (oss_format == AFMT_QUERY) - return UNSUPPORTED; - - enum oss_setup_result result = - oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, - &oss_format, - "Failed to set sample format", error_r); - -#ifdef AFMT_S24_PACKED - if (result == UNSUPPORTED && sample_format == SAMPLE_FORMAT_S24_P32) { - /* if the driver doesn't support padded 24 bit, try - packed 24 bit */ - oss_format = AFMT_S24_PACKED; - result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, - &oss_format, - "Failed to set sample format", error_r); - } -#endif - - if (result != SUCCESS) - return result; - - sample_format = sample_format_from_oss(oss_format); - if (sample_format == SAMPLE_FORMAT_UNDEFINED) - return UNSUPPORTED; - - *sample_format_r = sample_format; - *oss_format_r = oss_format; - -#ifdef AFMT_S24_PACKED - pcm_export_open(export, sample_format, 0, false, false, - oss_format == AFMT_S24_PACKED, - oss_format == AFMT_S24_PACKED && - G_BYTE_ORDER != G_LITTLE_ENDIAN); -#endif - - return SUCCESS; -} - -/** - * Set up the sample format, and attempts to find alternatives if the - * specified format is not supported. - */ -static bool -oss_setup_sample_format(int fd, struct audio_format *audio_format, - int *oss_format_r, -#ifdef AFMT_S24_PACKED - struct pcm_export_state *export, -#endif - GError **error_r) -{ - enum sample_format mpd_format; - enum oss_setup_result result = - oss_probe_sample_format(fd, audio_format->format, - &mpd_format, oss_format_r, -#ifdef AFMT_S24_PACKED - export, -#endif - error_r); - switch (result) { - case SUCCESS: - audio_format->format = mpd_format; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - - if (result != UNSUPPORTED) - return result == SUCCESS; - - /* the requested sample format is not available - probe for - other formats supported by MPD */ - - static const enum sample_format sample_formats[] = { - SAMPLE_FORMAT_S24_P32, - SAMPLE_FORMAT_S32, - SAMPLE_FORMAT_S16, - SAMPLE_FORMAT_S8, - SAMPLE_FORMAT_UNDEFINED /* sentinel */ - }; - - for (unsigned i = 0; sample_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) { - mpd_format = sample_formats[i]; - if (mpd_format == audio_format->format) - /* don't try that again */ - continue; - - result = oss_probe_sample_format(fd, mpd_format, - &mpd_format, oss_format_r, -#ifdef AFMT_S24_PACKED - export, -#endif - error_r); - switch (result) { - case SUCCESS: - audio_format->format = mpd_format; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - } - - g_set_error_literal(error_r, oss_output_quark(), EINVAL, - "Failed to set sample format"); - return false; -} - -/** - * Sets up the OSS device which was opened before. - */ -static bool -oss_setup(struct oss_data *od, struct audio_format *audio_format, - GError **error_r) -{ - return oss_setup_channels(od->fd, audio_format, error_r) && - oss_setup_sample_rate(od->fd, audio_format, error_r) && - oss_setup_sample_format(od->fd, audio_format, &od->oss_format, -#ifdef AFMT_S24_PACKED - &od->export, -#endif - error_r); -} - -/** - * Reopen the device with the saved audio_format, without any probing. - */ -static bool -oss_reopen(struct oss_data *od, GError **error_r) -{ - assert(od->fd < 0); - - od->fd = open_cloexec(od->device, O_WRONLY, 0); - if (od->fd < 0) { - g_set_error(error_r, oss_output_quark(), errno, - "Error opening OSS device \"%s\": %s", - od->device, g_strerror(errno)); - return false; - } - - enum oss_setup_result result; - - const char *const msg1 = "Failed to set channel count"; - result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS, - od->audio_format.channels, msg1, error_r); - if (result != SUCCESS) { - oss_close(od); - if (result == UNSUPPORTED) - g_set_error(error_r, oss_output_quark(), EINVAL, - "%s", msg1); - return false; - } - - const char *const msg2 = "Failed to set sample rate"; - result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED, - od->audio_format.sample_rate, msg2, error_r); - if (result != SUCCESS) { - oss_close(od); - if (result == UNSUPPORTED) - g_set_error(error_r, oss_output_quark(), EINVAL, - "%s", msg2); - return false; - } - - const char *const msg3 = "Failed to set sample format"; - result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, - od->oss_format, - msg3, error_r); - if (result != SUCCESS) { - oss_close(od); - if (result == UNSUPPORTED) - g_set_error(error_r, oss_output_quark(), EINVAL, - "%s", msg3); - return false; - } - - return true; -} - -static bool -oss_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - struct oss_data *od = (struct oss_data *)ao; - - od->fd = open_cloexec(od->device, O_WRONLY, 0); - if (od->fd < 0) { - g_set_error(error, oss_output_quark(), errno, - "Error opening OSS device \"%s\": %s", - od->device, g_strerror(errno)); - return false; - } - - if (!oss_setup(od, audio_format, error)) { - oss_close(od); - return false; - } - - od->audio_format = *audio_format; - return true; -} - -static void -oss_output_close(struct audio_output *ao) -{ - struct oss_data *od = (struct oss_data *)ao; - - oss_close(od); -} - -static void -oss_output_cancel(struct audio_output *ao) -{ - struct oss_data *od = (struct oss_data *)ao; - - if (od->fd >= 0) { - ioctl(od->fd, SNDCTL_DSP_RESET, 0); - oss_close(od); - } -} - -static size_t -oss_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - struct oss_data *od = (struct oss_data *)ao; - ssize_t ret; - - /* reopen the device since it was closed by dropBufferedAudio */ - if (od->fd < 0 && !oss_reopen(od, error)) - return 0; - -#ifdef AFMT_S24_PACKED - chunk = pcm_export(&od->export, chunk, size, &size); -#endif - - while (true) { - ret = write(od->fd, chunk, size); - if (ret > 0) { -#ifdef AFMT_S24_PACKED - ret = pcm_export_source_size(&od->export, ret); -#endif - return ret; - } - - if (ret < 0 && errno != EINTR) { - g_set_error(error, oss_output_quark(), errno, - "Write error on %s: %s", - od->device, g_strerror(errno)); - return 0; - } - } -} - -const struct audio_output_plugin oss_output_plugin = { - .name = "oss", - .test_default_device = oss_output_test_default_device, - .init = oss_output_init, - .finish = oss_output_finish, -#ifdef AFMT_S24_PACKED - .enable = oss_output_enable, - .disable = oss_output_disable, -#endif - .open = oss_output_open, - .close = oss_output_close, - .play = oss_output_play, - .cancel = oss_output_cancel, - - .mixer_plugin = &oss_mixer_plugin, -}; diff --git a/src/output/oss_output_plugin.h b/src/output/oss_output_plugin.h deleted file mode 100644 index 2aecc2b3a..000000000 --- a/src/output/oss_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OSS_OUTPUT_PLUGIN_H -#define MPD_OSS_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin oss_output_plugin; - -#endif diff --git a/src/output/osx_output_plugin.c b/src/output/osx_output_plugin.c deleted file mode 100644 index cd51fca20..000000000 --- a/src/output/osx_output_plugin.c +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "osx_output_plugin.h" -#include "output_api.h" -#include "fifo_buffer.h" - -#include <glib.h> -#include <CoreAudio/AudioHardware.h> -#include <AudioUnit/AudioUnit.h> -#include <CoreServices/CoreServices.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "osx" - -struct osx_output { - struct audio_output base; - - /* configuration settings */ - OSType component_subtype; - /* only applicable with kAudioUnitSubType_HALOutput */ - const char *device_name; - - AudioUnit au; - GMutex *mutex; - GCond *condition; - - struct fifo_buffer *buffer; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -osx_output_quark(void) -{ - return g_quark_from_static_string("osx_output"); -} - -static bool -osx_output_test_default_device(void) -{ - /* on a Mac, this is always the default plugin, if nothing - else is configured */ - return true; -} - -static void -osx_output_configure(struct osx_output *oo, const struct config_param *param) -{ - const char *device = config_get_block_string(param, "device", NULL); - - if (device == NULL || 0 == strcmp(device, "default")) { - oo->component_subtype = kAudioUnitSubType_DefaultOutput; - oo->device_name = NULL; - } - else if (0 == strcmp(device, "system")) { - oo->component_subtype = kAudioUnitSubType_SystemOutput; - oo->device_name = NULL; - } - else { - oo->component_subtype = kAudioUnitSubType_HALOutput; - /* XXX am I supposed to g_strdup() this? */ - oo->device_name = device; - } -} - -static struct audio_output * -osx_output_init(const struct config_param *param, GError **error_r) -{ - struct osx_output *oo = g_new(struct osx_output, 1); - if (!ao_base_init(&oo->base, &osx_output_plugin, param, error_r)) { - g_free(oo); - return NULL; - } - - osx_output_configure(oo, param); - oo->mutex = g_mutex_new(); - oo->condition = g_cond_new(); - - return &oo->base; -} - -static void -osx_output_finish(struct audio_output *ao) -{ - struct osx_output *od = (struct osx_output *)ao; - - g_mutex_free(od->mutex); - g_cond_free(od->condition); - g_free(od); -} - -static bool -osx_output_set_device(struct osx_output *oo, GError **error) -{ - bool ret = true; - OSStatus status; - UInt32 size, numdevices; - AudioDeviceID *deviceids = NULL; - char name[256]; - unsigned int i; - - if (oo->component_subtype != kAudioUnitSubType_HALOutput) - goto done; - - /* how many audio devices are there? */ - status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, - &size, - NULL); - if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to determine number of OS X audio devices: %s", - GetMacOSStatusCommentString(status)); - ret = false; - goto done; - } - - /* what are the available audio device IDs? */ - numdevices = size / sizeof(AudioDeviceID); - deviceids = g_malloc(size); - status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, - &size, - deviceids); - if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to determine OS X audio device IDs: %s", - GetMacOSStatusCommentString(status)); - ret = false; - goto done; - } - - /* which audio device matches oo->device_name? */ - for (i = 0; i < numdevices; i++) { - size = sizeof(name); - status = AudioDeviceGetProperty(deviceids[i], 0, false, - kAudioDevicePropertyDeviceName, - &size, name); - if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to determine OS X device name " - "(device %u): %s", - (unsigned int) deviceids[i], - GetMacOSStatusCommentString(status)); - ret = false; - goto done; - } - if (strcmp(oo->device_name, name) == 0) { - g_debug("found matching device: ID=%u, name=%s", - (unsigned int) deviceids[i], name); - break; - } - } - if (i == numdevices) { - g_warning("Found no audio device with name '%s' " - "(will use default audio device)", - oo->device_name); - goto done; - } - - status = AudioUnitSetProperty(oo->au, - kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, - 0, - &(deviceids[i]), - sizeof(AudioDeviceID)); - if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to set OS X audio output device: %s", - GetMacOSStatusCommentString(status)); - ret = false; - goto done; - } - g_debug("set OS X audio output device ID=%u, name=%s", - (unsigned int) deviceids[i], name); - -done: - if (deviceids != NULL) - g_free(deviceids); - return ret; -} - -static OSStatus -osx_render(void *vdata, - G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags, - G_GNUC_UNUSED const AudioTimeStamp *in_timestamp, - G_GNUC_UNUSED UInt32 in_bus_number, - G_GNUC_UNUSED UInt32 in_number_frames, - AudioBufferList *buffer_list) -{ - struct osx_output *od = (struct osx_output *) vdata; - AudioBuffer *buffer = &buffer_list->mBuffers[0]; - size_t buffer_size = buffer->mDataByteSize; - - assert(od->buffer != NULL); - - g_mutex_lock(od->mutex); - - size_t nbytes; - const void *src = fifo_buffer_read(od->buffer, &nbytes); - - if (src != NULL) { - if (nbytes > buffer_size) - nbytes = buffer_size; - - memcpy(buffer->mData, src, nbytes); - fifo_buffer_consume(od->buffer, nbytes); - } else - nbytes = 0; - - g_cond_signal(od->condition); - g_mutex_unlock(od->mutex); - - buffer->mDataByteSize = nbytes; - - unsigned i; - for (i = 1; i < buffer_list->mNumberBuffers; ++i) { - buffer = &buffer_list->mBuffers[i]; - buffer->mDataByteSize = 0; - } - - return 0; -} - -static bool -osx_output_enable(struct audio_output *ao, GError **error_r) -{ - struct osx_output *oo = (struct osx_output *)ao; - - ComponentDescription desc; - desc.componentType = kAudioUnitType_Output; - desc.componentSubType = oo->component_subtype; - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - Component comp = FindNextComponent(NULL, &desc); - if (comp == 0) { - g_set_error(error_r, osx_output_quark(), 0, - "Error finding OS X component"); - return false; - } - - OSStatus status = OpenAComponent(comp, &oo->au); - if (status != noErr) { - g_set_error(error_r, osx_output_quark(), status, - "Unable to open OS X component: %s", - GetMacOSStatusCommentString(status)); - return false; - } - - if (!osx_output_set_device(oo, error_r)) { - CloseComponent(oo->au); - return false; - } - - AURenderCallbackStruct callback; - callback.inputProc = osx_render; - callback.inputProcRefCon = oo; - - ComponentResult result = - AudioUnitSetProperty(oo->au, - kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, 0, - &callback, sizeof(callback)); - if (result != noErr) { - CloseComponent(oo->au); - g_set_error(error_r, osx_output_quark(), result, - "unable to set callback for OS X audio unit"); - return false; - } - - return true; -} - -static void -osx_output_disable(struct audio_output *ao) -{ - struct osx_output *oo = (struct osx_output *)ao; - - CloseComponent(oo->au); -} - -static void -osx_output_cancel(struct audio_output *ao) -{ - struct osx_output *od = (struct osx_output *)ao; - - g_mutex_lock(od->mutex); - fifo_buffer_clear(od->buffer); - g_mutex_unlock(od->mutex); -} - -static void -osx_output_close(struct audio_output *ao) -{ - struct osx_output *od = (struct osx_output *)ao; - - AudioOutputUnitStop(od->au); - AudioUnitUninitialize(od->au); - - fifo_buffer_free(od->buffer); -} - -static bool -osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) -{ - struct osx_output *od = (struct osx_output *)ao; - - AudioStreamBasicDescription stream_description; - stream_description.mSampleRate = audio_format->sample_rate; - stream_description.mFormatID = kAudioFormatLinearPCM; - stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; - - switch (audio_format->format) { - case SAMPLE_FORMAT_S8: - stream_description.mBitsPerChannel = 8; - break; - - case SAMPLE_FORMAT_S16: - stream_description.mBitsPerChannel = 16; - break; - - case SAMPLE_FORMAT_S32: - stream_description.mBitsPerChannel = 32; - break; - - default: - audio_format->format = SAMPLE_FORMAT_S32; - stream_description.mBitsPerChannel = 32; - break; - } - -#if G_BYTE_ORDER == G_BIG_ENDIAN - stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; -#endif - - stream_description.mBytesPerPacket = - audio_format_frame_size(audio_format); - stream_description.mFramesPerPacket = 1; - stream_description.mBytesPerFrame = stream_description.mBytesPerPacket; - stream_description.mChannelsPerFrame = audio_format->channels; - - ComponentResult result = - AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, 0, - &stream_description, - sizeof(stream_description)); - if (result != noErr) { - g_set_error(error, osx_output_quark(), result, - "Unable to set format on OS X device"); - return false; - } - - OSStatus status = AudioUnitInitialize(od->au); - if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to initialize OS X audio unit: %s", - GetMacOSStatusCommentString(status)); - return false; - } - - /* create a buffer of 1s */ - od->buffer = fifo_buffer_new(audio_format->sample_rate * - audio_format_frame_size(audio_format)); - - status = AudioOutputUnitStart(od->au); - if (status != 0) { - AudioUnitUninitialize(od->au); - g_set_error(error, osx_output_quark(), status, - "unable to start audio output: %s", - GetMacOSStatusCommentString(status)); - return false; - } - - return true; -} - -static size_t -osx_output_play(struct audio_output *ao, const void *chunk, size_t size, - G_GNUC_UNUSED GError **error) -{ - struct osx_output *od = (struct osx_output *)ao; - - g_mutex_lock(od->mutex); - - void *dest; - size_t max_length; - - while (true) { - dest = fifo_buffer_write(od->buffer, &max_length); - if (dest != NULL) - break; - - /* wait for some free space in the buffer */ - g_cond_wait(od->condition, od->mutex); - } - - if (size > max_length) - size = max_length; - - memcpy(dest, chunk, size); - fifo_buffer_append(od->buffer, size); - - g_mutex_unlock(od->mutex); - - return size; -} - -const struct audio_output_plugin osx_output_plugin = { - .name = "osx", - .test_default_device = osx_output_test_default_device, - .init = osx_output_init, - .finish = osx_output_finish, - .enable = osx_output_enable, - .disable = osx_output_disable, - .open = osx_output_open, - .close = osx_output_close, - .play = osx_output_play, - .cancel = osx_output_cancel, -}; diff --git a/src/output/osx_output_plugin.h b/src/output/osx_output_plugin.h deleted file mode 100644 index 814702d4f..000000000 --- a/src/output/osx_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OSX_OUTPUT_PLUGIN_H -#define MPD_OSX_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin osx_output_plugin; - -#endif diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c index e267427df..457fa9f04 100644 --- a/src/output/pulse_output_plugin.c +++ b/src/output/pulse_output_plugin.c @@ -21,7 +21,7 @@ #include "pulse_output_plugin.h" #include "output_api.h" #include "mixer_list.h" -#include "mixer/pulse_mixer_plugin.h" +#include "mixer/PulseMixerPlugin.h" #include <glib.h> diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h index 02a51f27b..bcc8004a7 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/roar_output_plugin.c b/src/output/roar_output_plugin.c deleted file mode 100644 index 1c2c48321..000000000 --- a/src/output/roar_output_plugin.c +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft - * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "roar_output_plugin.h" -#include "output_api.h" -#include "mixer_list.h" -#include "roar_output_plugin.h" - -#include <glib.h> -#include <stdint.h> -#include <unistd.h> -#include <stdlib.h> -#include <string.h> -#include <stdint.h> - -#include <roaraudio.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "roaraudio" - -typedef struct roar -{ - struct audio_output base; - - roar_vs_t * vss; - int err; - char *host; - char *name; - int role; - struct roar_connection con; - struct roar_audio_info info; - GMutex *lock; - volatile bool alive; -} roar_t; - -static inline GQuark -roar_output_quark(void) -{ - return g_quark_from_static_string("roar_output"); -} - -static int -roar_output_get_volume_locked(struct roar *roar) -{ - if (roar->vss == NULL || !roar->alive) - return -1; - - float l, r; - int error; - if (roar_vs_volume_get(roar->vss, &l, &r, &error) < 0) - return -1; - - return (l + r) * 50; -} - -int -roar_output_get_volume(struct roar *roar) -{ - g_mutex_lock(roar->lock); - int volume = roar_output_get_volume_locked(roar); - g_mutex_unlock(roar->lock); - return volume; -} - -static bool -roar_output_set_volume_locked(struct roar *roar, unsigned volume) -{ - assert(volume <= 100); - - if (roar->vss == NULL || !roar->alive) - return false; - - int error; - float level = volume / 100.0; - - roar_vs_volume_mono(roar->vss, level, &error); - return true; -} - -bool -roar_output_set_volume(struct roar *roar, unsigned volume) -{ - g_mutex_lock(roar->lock); - bool success = roar_output_set_volume_locked(roar, volume); - g_mutex_unlock(roar->lock); - return success; -} - -static void -roar_configure(struct roar * self, const struct config_param *param) -{ - self->host = config_dup_block_string(param, "server", NULL); - self->name = config_dup_block_string(param, "name", "MPD"); - - const char *role = config_get_block_string(param, "role", "music"); - self->role = role != NULL - ? roar_str2role(role) - : ROAR_ROLE_MUSIC; -} - -static struct audio_output * -roar_init(const struct config_param *param, GError **error_r) -{ - struct roar *self = g_new0(struct roar, 1); - - if (!ao_base_init(&self->base, &roar_output_plugin, param, error_r)) { - g_free(self); - return NULL; - } - - self->lock = g_mutex_new(); - self->err = ROAR_ERROR_NONE; - roar_configure(self, param); - return &self->base; -} - -static void -roar_finish(struct audio_output *ao) -{ - struct roar *self = (struct roar *)ao; - - g_free(self->host); - g_free(self->name); - g_mutex_free(self->lock); - - ao_base_finish(&self->base); - g_free(self); -} - -static void -roar_use_audio_format(struct roar_audio_info *info, - struct audio_format *audio_format) -{ - info->rate = audio_format->sample_rate; - info->channels = audio_format->channels; - info->codec = ROAR_CODEC_PCM_S; - - switch (audio_format->format) { - case SAMPLE_FORMAT_UNDEFINED: - info->bits = 16; - audio_format->format = SAMPLE_FORMAT_S16; - break; - - case SAMPLE_FORMAT_S8: - info->bits = 8; - break; - - case SAMPLE_FORMAT_S16: - info->bits = 16; - break; - - case SAMPLE_FORMAT_S24_P32: - info->bits = 32; - audio_format->format = SAMPLE_FORMAT_S32; - break; - - case SAMPLE_FORMAT_S32: - info->bits = 32; - break; - } -} - -static bool -roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) -{ - struct roar *self = (struct roar *)ao; - g_mutex_lock(self->lock); - - if (roar_simple_connect(&(self->con), self->host, self->name) < 0) - { - g_set_error(error, roar_output_quark(), 0, - "Failed to connect to Roar server"); - g_mutex_unlock(self->lock); - return false; - } - - self->vss = roar_vs_new_from_con(&(self->con), &(self->err)); - - if (self->vss == NULL || self->err != ROAR_ERROR_NONE) - { - g_set_error(error, roar_output_quark(), 0, - "Failed to connect to server"); - g_mutex_unlock(self->lock); - return false; - } - - roar_use_audio_format(&self->info, audio_format); - - if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY, - &(self->err)) < 0) - { - g_set_error(error, roar_output_quark(), 0, "Failed to start stream"); - g_mutex_unlock(self->lock); - return false; - } - roar_vs_role(self->vss, self->role, &(self->err)); - self->alive = true; - - g_mutex_unlock(self->lock); - return true; -} - -static void -roar_close(struct audio_output *ao) -{ - struct roar *self = (struct roar *)ao; - g_mutex_lock(self->lock); - self->alive = false; - - if (self->vss != NULL) - roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err)); - self->vss = NULL; - roar_disconnect(&(self->con)); - g_mutex_unlock(self->lock); -} - -static void -roar_cancel_locked(struct roar *self) -{ - if (self->vss == NULL) - return; - - roar_vs_t *vss = self->vss; - self->vss = NULL; - roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); - self->alive = false; - - vss = roar_vs_new_from_con(&(self->con), &(self->err)); - if (vss == NULL) - return; - - if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY, - &(self->err)) < 0) { - roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); - g_warning("Failed to start stream"); - return; - } - - roar_vs_role(vss, self->role, &(self->err)); - self->vss = vss; - self->alive = true; -} - -static void -roar_cancel(struct audio_output *ao) -{ - struct roar *self = (struct roar *)ao; - - g_mutex_lock(self->lock); - roar_cancel_locked(self); - g_mutex_unlock(self->lock); -} - -static size_t -roar_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) -{ - struct roar *self = (struct roar *)ao; - ssize_t rc; - - if (self->vss == NULL) - { - g_set_error(error, roar_output_quark(), 0, "Connection is invalid"); - return 0; - } - - rc = roar_vs_write(self->vss, chunk, size, &(self->err)); - if ( rc <= 0 ) - { - g_set_error(error, roar_output_quark(), 0, "Failed to play data"); - return 0; - } - - return rc; -} - -static const char* -roar_tag_convert(enum tag_type type, bool *is_uuid) -{ - *is_uuid = false; - switch (type) - { - case TAG_ARTIST: - case TAG_ALBUM_ARTIST: - return "AUTHOR"; - case TAG_ALBUM: - return "ALBUM"; - case TAG_TITLE: - return "TITLE"; - case TAG_TRACK: - return "TRACK"; - case TAG_NAME: - return "NAME"; - case TAG_GENRE: - return "GENRE"; - case TAG_DATE: - return "DATE"; - case TAG_PERFORMER: - return "PERFORMER"; - case TAG_COMMENT: - return "COMMENT"; - case TAG_DISC: - return "DISCID"; - case TAG_COMPOSER: -#ifdef ROAR_META_TYPE_COMPOSER - return "COMPOSER"; -#else - return "AUTHOR"; -#endif - case TAG_MUSICBRAINZ_ARTISTID: - case TAG_MUSICBRAINZ_ALBUMID: - case TAG_MUSICBRAINZ_ALBUMARTISTID: - case TAG_MUSICBRAINZ_TRACKID: - *is_uuid = true; - return "HASH"; - - default: - return NULL; - } -} - -static void -roar_send_tag(struct audio_output *ao, const struct tag *meta) -{ - struct roar *self = (struct roar *)ao; - - if (self->vss == NULL) - return; - - g_mutex_lock(self->lock); - size_t cnt = 1; - struct roar_keyval vals[32]; - memset(vals, 0, sizeof(vals)); - char uuid_buf[32][64]; - - char timebuf[16]; - snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", - meta->time / 3600, (meta->time % 3600) / 60, meta->time % 60); - - vals[0].key = g_strdup("LENGTH"); - vals[0].value = timebuf; - - for (unsigned i = 0; i < meta->num_items && cnt < 32; i++) - { - bool is_uuid = false; - const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid); - if (key != NULL) - { - if (is_uuid) - { - snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s", - meta->items[i]->value); - vals[cnt].key = g_strdup(key); - vals[cnt].value = uuid_buf[cnt]; - } - else - { - vals[cnt].key = g_strdup(key); - vals[cnt].value = meta->items[i]->value; - } - cnt++; - } - } - - roar_vs_meta(self->vss, vals, cnt, &(self->err)); - - for (unsigned i = 0; i < 32; i++) - g_free(vals[i].key); - - g_mutex_unlock(self->lock); -} - -const struct audio_output_plugin roar_output_plugin = { - .name = "roar", - .init = roar_init, - .finish = roar_finish, - .open = roar_open, - .play = roar_play, - .cancel = roar_cancel, - .close = roar_close, - .send_tag = roar_send_tag, - - .mixer_plugin = &roar_mixer_plugin -}; diff --git a/src/output/roar_output_plugin.h b/src/output/roar_output_plugin.h deleted file mode 100644 index 78b628cc4..000000000 --- a/src/output/roar_output_plugin.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ROAR_OUTPUT_PLUGIN_H -#define MPD_ROAR_OUTPUT_PLUGIN_H - -#include <stdbool.h> - -struct roar; - -extern const struct audio_output_plugin roar_output_plugin; - -int -roar_output_get_volume(struct roar *roar); - -bool -roar_output_set_volume(struct roar *roar, unsigned volume); - -#endif diff --git a/src/output/shout_output_plugin.c b/src/output/shout_output_plugin.c index 56456a0ea..56d7a88b1 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/winmm_output_plugin.c b/src/output/winmm_output_plugin.c index 4d95834b9..c1b3af126 100644 --- a/src/output/winmm_output_plugin.c +++ b/src/output/winmm_output_plugin.c @@ -26,7 +26,6 @@ #include <stdlib.h> #include <string.h> -#include <windows.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "winmm_output" diff --git a/src/output/winmm_output_plugin.h b/src/output/winmm_output_plugin.h index 0605530e1..364356483 100644 --- a/src/output/winmm_output_plugin.h +++ b/src/output/winmm_output_plugin.h @@ -25,6 +25,7 @@ #ifdef ENABLE_WINMM_OUTPUT #include <windows.h> +#include <mmsystem.h> struct winmm_output; diff --git a/src/output_all.c b/src/output_all.c deleted file mode 100644 index f56cd04ee..000000000 --- a/src/output_all.c +++ /dev/null @@ -1,590 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_all.h" -#include "output_internal.h" -#include "output_control.h" -#include "chunk.h" -#include "conf.h" -#include "pipe.h" -#include "buffer.h" -#include "player_control.h" -#include "mpd_error.h" -#include "notify.h" - -#ifndef NDEBUG -#include "chunk.h" -#endif - -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "output" - -static struct audio_format input_audio_format; - -static struct audio_output **audio_outputs; -static unsigned int num_audio_outputs; - -/** - * The #music_buffer object where consumed chunks are returned. - */ -static struct music_buffer *g_music_buffer; - -/** - * The #music_pipe object which feeds all audio outputs. It is filled - * by audio_output_all_play(). - */ -static struct music_pipe *g_mp; - -/** - * The "elapsed_time" stamp of the most recently finished chunk. - */ -static float audio_output_all_elapsed_time = -1.0; - -unsigned int audio_output_count(void) -{ - return num_audio_outputs; -} - -struct audio_output * -audio_output_get(unsigned i) -{ - assert(i < num_audio_outputs); - - assert(audio_outputs[i] != NULL); - - return audio_outputs[i]; -} - -struct audio_output * -audio_output_find(const char *name) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_output_get(i); - - if (strcmp(ao->name, name) == 0) - return ao; - } - - /* name not found */ - return NULL; -} - -static unsigned -audio_output_config_count(void) -{ - unsigned int nr = 0; - const struct config_param *param = NULL; - - while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param))) - nr++; - if (!nr) - nr = 1; /* we'll always have at least one device */ - return nr; -} - -void -audio_output_all_init(struct player_control *pc) -{ - const struct config_param *param = NULL; - unsigned int i; - GError *error = NULL; - - notify_init(&audio_output_client_notify); - - num_audio_outputs = audio_output_config_count(); - audio_outputs = g_new(struct audio_output *, num_audio_outputs); - - for (i = 0; i < num_audio_outputs; i++) - { - unsigned int j; - - param = config_get_next_param(CONF_AUDIO_OUTPUT, param); - - /* only allow param to be NULL if there just one audioOutput */ - assert(param || (num_audio_outputs == 1)); - - struct audio_output *output = audio_output_new(param, pc, &error); - if (output == NULL) { - if (param != NULL) - MPD_ERROR("line %i: %s", - param->line, error->message); - else - MPD_ERROR("%s", error->message); - } - - audio_outputs[i] = output; - - /* require output names to be unique: */ - for (j = 0; j < i; j++) { - if (!strcmp(output->name, audio_outputs[j]->name)) { - MPD_ERROR("output devices with identical " - "names: %s\n", output->name); - } - } - } -} - -void -audio_output_all_finish(void) -{ - unsigned int i; - - for (i = 0; i < num_audio_outputs; i++) { - audio_output_disable(audio_outputs[i]); - audio_output_finish(audio_outputs[i]); - } - - g_free(audio_outputs); - audio_outputs = NULL; - num_audio_outputs = 0; - - notify_deinit(&audio_output_client_notify); -} - -void -audio_output_all_enable_disable(void) -{ - for (unsigned i = 0; i < num_audio_outputs; i++) { - struct audio_output *ao = audio_outputs[i]; - bool enabled; - - g_mutex_lock(ao->mutex); - enabled = ao->really_enabled; - g_mutex_unlock(ao->mutex); - - if (ao->enabled != enabled) { - if (ao->enabled) - audio_output_enable(ao); - else - audio_output_disable(ao); - } - } -} - -/** - * Determine if all (active) outputs have finished the current - * command. - */ -static bool -audio_output_all_finished(void) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_outputs[i]; - bool not_finished; - - g_mutex_lock(ao->mutex); - not_finished = audio_output_is_open(ao) && - !audio_output_command_is_finished(ao); - g_mutex_unlock(ao->mutex); - - if (not_finished) - return false; - } - - return true; -} - -static void audio_output_wait_all(void) -{ - while (!audio_output_all_finished()) - notify_wait(&audio_output_client_notify); -} - -/** - * Signals all audio outputs which are open. - */ -static void -audio_output_allow_play_all(void) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) - audio_output_allow_play(audio_outputs[i]); -} - -static void -audio_output_reset_reopen(struct audio_output *ao) -{ - g_mutex_lock(ao->mutex); - - if (!ao->open && ao->fail_timer != NULL) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = NULL; - } - - g_mutex_unlock(ao->mutex); -} - -/** - * Resets the "reopen" flag on all audio devices. MPD should - * immediately retry to open the device instead of waiting for the - * timeout when the user wants to start playback. - */ -static void -audio_output_all_reset_reopen(void) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_outputs[i]; - - audio_output_reset_reopen(ao); - } -} - -/** - * Opens all output devices which are enabled, but closed. - * - * @return true if there is at least open output device which is open - */ -static bool -audio_output_all_update(void) -{ - unsigned int i; - bool ret = false; - - if (!audio_format_defined(&input_audio_format)) - return false; - - for (i = 0; i < num_audio_outputs; ++i) - ret = audio_output_update(audio_outputs[i], - &input_audio_format, g_mp) || ret; - - return ret; -} - -bool -audio_output_all_play(struct music_chunk *chunk) -{ - bool ret; - unsigned int i; - - assert(g_music_buffer != NULL); - assert(g_mp != NULL); - assert(chunk != NULL); - assert(music_chunk_check_format(chunk, &input_audio_format)); - - ret = audio_output_all_update(); - if (!ret) - return false; - - music_pipe_push(g_mp, chunk); - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_play(audio_outputs[i]); - - return true; -} - -bool -audio_output_all_open(const struct audio_format *audio_format, - struct music_buffer *buffer) -{ - bool ret = false, enabled = false; - unsigned int i; - - assert(audio_format != NULL); - assert(buffer != NULL); - assert(g_music_buffer == NULL || g_music_buffer == buffer); - assert((g_mp == NULL) == (g_music_buffer == NULL)); - - g_music_buffer = buffer; - - /* the audio format must be the same as existing chunks in the - pipe */ - assert(g_mp == NULL || music_pipe_check_format(g_mp, audio_format)); - - if (g_mp == NULL) - g_mp = music_pipe_new(); - else - /* if the pipe hasn't been cleared, the the audio - format must not have changed */ - assert(music_pipe_empty(g_mp) || - audio_format_equals(audio_format, - &input_audio_format)); - - input_audio_format = *audio_format; - - audio_output_all_reset_reopen(); - audio_output_all_enable_disable(); - audio_output_all_update(); - - for (i = 0; i < num_audio_outputs; ++i) { - if (audio_outputs[i]->enabled) - enabled = true; - - if (audio_outputs[i]->open) - ret = true; - } - - if (!enabled) - g_warning("All audio outputs are disabled"); - - if (!ret) - /* close all devices if there was an error */ - audio_output_all_close(); - - return ret; -} - -/** - * Has the specified audio output already consumed this chunk? - */ -static bool -chunk_is_consumed_in(const struct audio_output *ao, - const struct music_chunk *chunk) -{ - if (!ao->open) - return true; - - if (ao->chunk == NULL) - return false; - - assert(chunk == ao->chunk || music_pipe_contains(g_mp, ao->chunk)); - - if (chunk != ao->chunk) { - assert(chunk->next != NULL); - return true; - } - - return ao->chunk_finished && chunk->next == NULL; -} - -/** - * Has this chunk been consumed by all audio outputs? - */ -static bool -chunk_is_consumed(const struct music_chunk *chunk) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - const struct audio_output *ao = audio_outputs[i]; - bool consumed; - - g_mutex_lock(ao->mutex); - consumed = chunk_is_consumed_in(ao, chunk); - g_mutex_unlock(ao->mutex); - - if (!consumed) - return false; - } - - return true; -} - -/** - * There's only one chunk left in the pipe (#g_mp), and all audio - * outputs have consumed it already. Clear the reference. - */ -static void -clear_tail_chunk(G_GNUC_UNUSED const struct music_chunk *chunk, bool *locked) -{ - assert(chunk->next == NULL); - assert(music_pipe_contains(g_mp, chunk)); - - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_outputs[i]; - - /* this mutex will be unlocked by the caller when it's - ready */ - g_mutex_lock(ao->mutex); - locked[i] = ao->open; - - if (!locked[i]) { - g_mutex_unlock(ao->mutex); - continue; - } - - assert(ao->chunk == chunk); - assert(ao->chunk_finished); - ao->chunk = NULL; - } -} - -unsigned -audio_output_all_check(void) -{ - const struct music_chunk *chunk; - bool is_tail; - struct music_chunk *shifted; - bool locked[num_audio_outputs]; - - assert(g_music_buffer != NULL); - assert(g_mp != NULL); - - while ((chunk = music_pipe_peek(g_mp)) != NULL) { - assert(!music_pipe_empty(g_mp)); - - if (!chunk_is_consumed(chunk)) - /* at least one output is not finished playing - this chunk */ - return music_pipe_size(g_mp); - - if (chunk->length > 0 && chunk->times >= 0.0) - /* only update elapsed_time if the chunk - provides a defined value */ - audio_output_all_elapsed_time = chunk->times; - - is_tail = chunk->next == NULL; - if (is_tail) - /* this is the tail of the pipe - clear the - chunk reference in all outputs */ - clear_tail_chunk(chunk, locked); - - /* remove the chunk from the pipe */ - shifted = music_pipe_shift(g_mp); - assert(shifted == chunk); - - if (is_tail) - /* unlock all audio outputs which were locked - by clear_tail_chunk() */ - for (unsigned i = 0; i < num_audio_outputs; ++i) - if (locked[i]) - g_mutex_unlock(audio_outputs[i]->mutex); - - /* return the chunk to the buffer */ - music_buffer_return(g_music_buffer, shifted); - } - - return 0; -} - -bool -audio_output_all_wait(struct player_control *pc, unsigned threshold) -{ - player_lock(pc); - - if (audio_output_all_check() < threshold) { - player_unlock(pc); - return true; - } - - player_wait(pc); - player_unlock(pc); - - return audio_output_all_check() < threshold; -} - -void -audio_output_all_pause(void) -{ - unsigned int i; - - audio_output_all_update(); - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_pause(audio_outputs[i]); - - audio_output_wait_all(); -} - -void -audio_output_all_drain(void) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) - audio_output_drain_async(audio_outputs[i]); - - audio_output_wait_all(); -} - -void -audio_output_all_cancel(void) -{ - unsigned int i; - - /* send the cancel() command to all audio outputs */ - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_cancel(audio_outputs[i]); - - audio_output_wait_all(); - - /* clear the music pipe and return all chunks to the buffer */ - - if (g_mp != NULL) - music_pipe_clear(g_mp, g_music_buffer); - - /* the audio outputs are now waiting for a signal, to - synchronize the cleared music pipe */ - - audio_output_allow_play_all(); - - /* invalidate elapsed_time */ - - audio_output_all_elapsed_time = -1.0; -} - -void -audio_output_all_close(void) -{ - unsigned int i; - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_close(audio_outputs[i]); - - if (g_mp != NULL) { - assert(g_music_buffer != NULL); - - music_pipe_clear(g_mp, g_music_buffer); - music_pipe_free(g_mp); - g_mp = NULL; - } - - g_music_buffer = NULL; - - audio_format_clear(&input_audio_format); - - audio_output_all_elapsed_time = -1.0; -} - -void -audio_output_all_release(void) -{ - unsigned int i; - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_release(audio_outputs[i]); - - if (g_mp != NULL) { - assert(g_music_buffer != NULL); - - music_pipe_clear(g_mp, g_music_buffer); - music_pipe_free(g_mp); - g_mp = NULL; - } - - g_music_buffer = NULL; - - audio_format_clear(&input_audio_format); - - audio_output_all_elapsed_time = -1.0; -} - -void -audio_output_all_song_border(void) -{ - /* clear the elapsed_time pointer at the beginning of a new - song */ - audio_output_all_elapsed_time = 0.0; -} - -float -audio_output_all_get_elapsed_time(void) -{ - return audio_output_all_elapsed_time; -} diff --git a/src/output_all.h b/src/output_all.h deleted file mode 100644 index 4eeb94f13..000000000 --- a/src/output_all.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Functions for dealing with all configured (enabled) audion outputs - * at once. - * - */ - -#ifndef OUTPUT_ALL_H -#define OUTPUT_ALL_H - -#include <stdbool.h> -#include <stddef.h> - -struct audio_format; -struct music_buffer; -struct music_chunk; -struct player_control; - -/** - * Global initialization: load audio outputs from the configuration - * file and initialize them. - */ -void -audio_output_all_init(struct player_control *pc); - -/** - * Global finalization: free memory occupied by audio outputs. All - */ -void -audio_output_all_finish(void); - -/** - * Returns the total number of audio output devices, including those - * who are disabled right now. - */ -unsigned int audio_output_count(void); - -/** - * Returns the "i"th audio output device. - */ -struct audio_output * -audio_output_get(unsigned i); - -/** - * Returns the audio output device with the specified name. Returns - * NULL if the name does not exist. - */ -struct audio_output * -audio_output_find(const char *name); - -/** - * Checks the "enabled" flag of all audio outputs, and if one has - * changed, commit the change. - */ -void -audio_output_all_enable_disable(void); - -/** - * Opens all audio outputs which are not disabled. - * - * @param audio_format the preferred audio format, or NULL to reuse - * the previous format - * @param buffer the #music_buffer where consumed #music_chunk objects - * should be returned - * @return true on success, false on failure - */ -bool -audio_output_all_open(const struct audio_format *audio_format, - struct music_buffer *buffer); - -/** - * Closes all audio outputs. - */ -void -audio_output_all_close(void); - -/** - * Closes all audio outputs. Outputs with the "always_on" flag are - * put into pause mode. - */ -void -audio_output_all_release(void); - -/** - * Enqueue a #music_chunk object for playing, i.e. pushes it to a - * #music_pipe. - * - * @param chunk the #music_chunk object to be played - * @return true on success, false if no audio output was able to play - * (all closed then) - */ -bool -audio_output_all_play(struct music_chunk *chunk); - -/** - * Checks if the output devices have drained their music pipe, and - * returns the consumed music chunks to the #music_buffer. - * - * @return the number of chunks to play left in the #music_pipe - */ -unsigned -audio_output_all_check(void); - -/** - * Checks if the size of the #music_pipe is below the #threshold. If - * not, it attempts to synchronize with all output threads, and waits - * until another #music_chunk is finished. - * - * @param threshold the maximum number of chunks in the pipe - * @return true if there are less than #threshold chunks in the pipe - */ -bool -audio_output_all_wait(struct player_control *pc, unsigned threshold); - -/** - * Puts all audio outputs into pause mode. Most implementations will - * simply close it then. - */ -void -audio_output_all_pause(void); - -/** - * Drain all audio outputs. - */ -void -audio_output_all_drain(void); - -/** - * Try to cancel data which may still be in the device's buffers. - */ -void -audio_output_all_cancel(void); - -/** - * Indicate that a new song will begin now. - */ -void -audio_output_all_song_border(void); - -/** - * Returns the "elapsed_time" stamp of the most recently finished - * chunk. A negative value is returned when no chunk has been - * finished yet. - */ -float -audio_output_all_get_elapsed_time(void); - -#endif diff --git a/src/output_command.c b/src/output_command.c deleted file mode 100644 index 3988f350a..000000000 --- a/src/output_command.c +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Glue functions for controlling the audio outputs over the MPD - * protocol. These functions perform extra validation on all - * parameters, because they might be from an untrusted source. - * - */ - -#include "config.h" -#include "output_command.h" -#include "output_all.h" -#include "output_internal.h" -#include "output_plugin.h" -#include "mixer_control.h" -#include "player_control.h" -#include "idle.h" - -extern unsigned audio_output_state_version; - -bool -audio_output_enable_index(unsigned idx) -{ - struct audio_output *ao; - - if (idx >= audio_output_count()) - return false; - - ao = audio_output_get(idx); - if (ao->enabled) - return true; - - ao->enabled = true; - idle_add(IDLE_OUTPUT); - - pc_update_audio(ao->player_control); - - ++audio_output_state_version; - - return true; -} - -bool -audio_output_disable_index(unsigned idx) -{ - struct audio_output *ao; - struct mixer *mixer; - - if (idx >= audio_output_count()) - return false; - - ao = audio_output_get(idx); - if (!ao->enabled) - return true; - - ao->enabled = false; - idle_add(IDLE_OUTPUT); - - mixer = ao->mixer; - if (mixer != NULL) { - mixer_close(mixer); - idle_add(IDLE_MIXER); - } - - pc_update_audio(ao->player_control); - - ++audio_output_state_version; - - return true; -} diff --git a/src/output_command.h b/src/output_command.h deleted file mode 100644 index eda30acc8..000000000 --- a/src/output_command.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Glue functions for controlling the audio outputs over the MPD - * protocol. These functions perform extra validation on all - * parameters, because they might be from an untrusted source. - * - */ - -#ifndef OUTPUT_COMMAND_H -#define OUTPUT_COMMAND_H - -#include <stdbool.h> - -/** - * Enables an audio output. Returns false if the specified output - * does not exist. - */ -bool -audio_output_enable_index(unsigned idx); - -/** - * Disables an audio output. Returns false if the specified output - * does not exist. - */ -bool -audio_output_disable_index(unsigned idx); - -#endif diff --git a/src/output_control.c b/src/output_control.c deleted file mode 100644 index 7b95be49b..000000000 --- a/src/output_control.c +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_control.h" -#include "output_api.h" -#include "output_internal.h" -#include "output_thread.h" -#include "mixer_control.h" -#include "mixer_plugin.h" -#include "filter_plugin.h" -#include "notify.h" - -#include <assert.h> -#include <stdlib.h> - -enum { - /** after a failure, wait this number of seconds before - automatically reopening the device */ - REOPEN_AFTER = 10, -}; - -struct notify audio_output_client_notify; - -/** - * Waits for command completion. - * - * @param ao the #audio_output instance; must be locked - */ -static void ao_command_wait(struct audio_output *ao) -{ - while (ao->command != AO_COMMAND_NONE) { - g_mutex_unlock(ao->mutex); - notify_wait(&audio_output_client_notify); - g_mutex_lock(ao->mutex); - } -} - -/** - * Sends a command to the #audio_output object, but does not wait for - * completion. - * - * @param ao the #audio_output instance; must be locked - */ -static void ao_command_async(struct audio_output *ao, - enum audio_output_command cmd) -{ - assert(ao->command == AO_COMMAND_NONE); - ao->command = cmd; - g_cond_signal(ao->cond); -} - -/** - * Sends a command to the #audio_output object and waits for - * completion. - * - * @param ao the #audio_output instance; must be locked - */ -static void -ao_command(struct audio_output *ao, enum audio_output_command cmd) -{ - ao_command_async(ao, cmd); - ao_command_wait(ao); -} - -/** - * Lock the #audio_output object and execute the command - * synchronously. - */ -static void -ao_lock_command(struct audio_output *ao, enum audio_output_command cmd) -{ - g_mutex_lock(ao->mutex); - ao_command(ao, cmd); - g_mutex_unlock(ao->mutex); -} - -void -audio_output_enable(struct audio_output *ao) -{ - if (ao->thread == NULL) { - if (ao->plugin->enable == NULL) { - /* don't bother to start the thread now if the - device doesn't even have a enable() method; - just assign the variable and we're done */ - ao->really_enabled = true; - return; - } - - audio_output_thread_start(ao); - } - - ao_lock_command(ao, AO_COMMAND_ENABLE); -} - -void -audio_output_disable(struct audio_output *ao) -{ - if (ao->thread == NULL) { - if (ao->plugin->disable == NULL) - ao->really_enabled = false; - else - /* if there's no thread yet, the device cannot - be enabled */ - assert(!ao->really_enabled); - - return; - } - - ao_lock_command(ao, AO_COMMAND_DISABLE); -} - -/** - * Object must be locked (and unlocked) by the caller. - */ -static bool -audio_output_open(struct audio_output *ao, - const struct audio_format *audio_format, - const struct music_pipe *mp) -{ - bool open; - - assert(ao != NULL); - assert(ao->allow_play); - assert(audio_format_valid(audio_format)); - assert(mp != NULL); - - if (ao->fail_timer != NULL) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = NULL; - } - - if (ao->open && - audio_format_equals(audio_format, &ao->in_audio_format)) { - assert(ao->pipe == mp || - (ao->always_on && ao->pause)); - - if (ao->pause) { - ao->chunk = NULL; - ao->pipe = mp; - - /* unpause with the CANCEL command; this is a - hack, but suits well for forcing the thread - to leave the ao_pause() thread, and we need - to flush the device buffer anyway */ - - /* we're not using audio_output_cancel() here, - because that function is asynchronous */ - ao_command(ao, AO_COMMAND_CANCEL); - } - - return true; - } - - ao->in_audio_format = *audio_format; - ao->chunk = NULL; - - ao->pipe = mp; - - if (ao->thread == NULL) - audio_output_thread_start(ao); - - ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN); - open = ao->open; - - if (open && ao->mixer != NULL) { - GError *error = NULL; - - if (!mixer_open(ao->mixer, &error)) { - g_warning("Failed to open mixer for '%s': %s", - ao->name, error->message); - g_error_free(error); - } - } - - return open; -} - -/** - * Same as audio_output_close(), but expects the lock to be held by - * the caller. - */ -static void -audio_output_close_locked(struct audio_output *ao) -{ - assert(ao != NULL); - assert(ao->allow_play); - - if (ao->mixer != NULL) - mixer_auto_close(ao->mixer); - - assert(!ao->open || ao->fail_timer == NULL); - - if (ao->open) - ao_command(ao, AO_COMMAND_CLOSE); - else if (ao->fail_timer != NULL) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = NULL; - } -} - -bool -audio_output_update(struct audio_output *ao, - const struct audio_format *audio_format, - const struct music_pipe *mp) -{ - assert(mp != NULL); - - g_mutex_lock(ao->mutex); - - if (ao->enabled && ao->really_enabled) { - if (ao->fail_timer == NULL || - g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) { - bool success = audio_output_open(ao, audio_format, mp); - g_mutex_unlock(ao->mutex); - return success; - } - } else if (audio_output_is_open(ao)) - audio_output_close_locked(ao); - - g_mutex_unlock(ao->mutex); - return false; -} - -void -audio_output_play(struct audio_output *ao) -{ - g_mutex_lock(ao->mutex); - - assert(ao->allow_play); - - if (audio_output_is_open(ao)) - g_cond_signal(ao->cond); - - g_mutex_unlock(ao->mutex); -} - -void audio_output_pause(struct audio_output *ao) -{ - if (ao->mixer != NULL && ao->plugin->pause == NULL) - /* the device has no pause mode: close the mixer, - unless its "global" flag is set (checked by - mixer_auto_close()) */ - mixer_auto_close(ao->mixer); - - g_mutex_lock(ao->mutex); - assert(ao->allow_play); - if (audio_output_is_open(ao)) - ao_command_async(ao, AO_COMMAND_PAUSE); - g_mutex_unlock(ao->mutex); -} - -void -audio_output_drain_async(struct audio_output *ao) -{ - g_mutex_lock(ao->mutex); - assert(ao->allow_play); - if (audio_output_is_open(ao)) - ao_command_async(ao, AO_COMMAND_DRAIN); - g_mutex_unlock(ao->mutex); -} - -void audio_output_cancel(struct audio_output *ao) -{ - g_mutex_lock(ao->mutex); - - if (audio_output_is_open(ao)) { - ao->allow_play = false; - ao_command_async(ao, AO_COMMAND_CANCEL); - } - - g_mutex_unlock(ao->mutex); -} - -void -audio_output_allow_play(struct audio_output *ao) -{ - g_mutex_lock(ao->mutex); - - ao->allow_play = true; - if (audio_output_is_open(ao)) - g_cond_signal(ao->cond); - - g_mutex_unlock(ao->mutex); -} - -void -audio_output_release(struct audio_output *ao) -{ - if (ao->always_on) - audio_output_pause(ao); - else - audio_output_close(ao); -} - -void audio_output_close(struct audio_output *ao) -{ - assert(ao != NULL); - assert(!ao->open || ao->fail_timer == NULL); - - g_mutex_lock(ao->mutex); - audio_output_close_locked(ao); - g_mutex_unlock(ao->mutex); -} - -void audio_output_finish(struct audio_output *ao) -{ - audio_output_close(ao); - - assert(ao->fail_timer == NULL); - - if (ao->thread != NULL) { - assert(ao->allow_play); - ao_lock_command(ao, AO_COMMAND_KILL); - g_thread_join(ao->thread); - ao->thread = NULL; - } - - audio_output_free(ao); -} diff --git a/src/output_control.h b/src/output_control.h deleted file mode 100644 index 874a53518..000000000 --- a/src/output_control.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_CONTROL_H -#define MPD_OUTPUT_CONTROL_H - -#include <glib.h> - -#include <stddef.h> -#include <stdbool.h> - -struct audio_output; -struct audio_format; -struct config_param; -struct music_pipe; -struct player_control; - -static inline GQuark -audio_output_quark(void) -{ - return g_quark_from_static_string("audio_output"); -} - -/** - * Enables the device. - */ -void -audio_output_enable(struct audio_output *ao); - -/** - * Disables the device. - */ -void -audio_output_disable(struct audio_output *ao); - -/** - * Opens or closes the device, depending on the "enabled" flag. - * - * @return true if the device is open - */ -bool -audio_output_update(struct audio_output *ao, - const struct audio_format *audio_format, - const struct music_pipe *mp); - -void -audio_output_play(struct audio_output *ao); - -void audio_output_pause(struct audio_output *ao); - -void -audio_output_drain_async(struct audio_output *ao); - -/** - * Clear the "allow_play" flag and send the "CANCEL" command - * asynchronously. To finish the operation, the caller has to call - * audio_output_allow_play(). - */ -void audio_output_cancel(struct audio_output *ao); - -/** - * Set the "allow_play" and signal the thread. - */ -void -audio_output_allow_play(struct audio_output *ao); - -void audio_output_close(struct audio_output *ao); - -/** - * Closes the audio output, but if the "always_on" flag is set, put it - * into pause mode instead. - */ -void -audio_output_release(struct audio_output *ao); - -void audio_output_finish(struct audio_output *ao); - -#endif diff --git a/src/output_finish.c b/src/output_finish.c deleted file mode 100644 index e11b43675..000000000 --- a/src/output_finish.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_internal.h" -#include "output_plugin.h" -#include "mixer_control.h" -#include "filter_plugin.h" - -#include <assert.h> - -void -ao_base_finish(struct audio_output *ao) -{ - assert(!ao->open); - assert(ao->fail_timer == NULL); - assert(ao->thread == NULL); - - if (ao->mixer != NULL) - mixer_free(ao->mixer); - - g_cond_free(ao->cond); - g_mutex_free(ao->mutex); - - if (ao->replay_gain_filter != NULL) - filter_free(ao->replay_gain_filter); - - if (ao->other_replay_gain_filter != NULL) - filter_free(ao->other_replay_gain_filter); - - filter_free(ao->filter); - - pcm_buffer_deinit(&ao->cross_fade_buffer); -} - -void -audio_output_free(struct audio_output *ao) -{ - assert(!ao->open); - assert(ao->fail_timer == NULL); - assert(ao->thread == NULL); - - ao_plugin_finish(ao); -} diff --git a/src/output_init.c b/src/output_init.c deleted file mode 100644 index c3b808e94..000000000 --- a/src/output_init.c +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_control.h" -#include "output_api.h" -#include "output_internal.h" -#include "output_list.h" -#include "audio_parser.h" -#include "mixer_control.h" -#include "mixer_type.h" -#include "mixer_list.h" -#include "mixer/software_mixer_plugin.h" -#include "filter_plugin.h" -#include "filter_registry.h" -#include "filter_config.h" -#include "filter/chain_filter_plugin.h" -#include "filter/autoconvert_filter_plugin.h" -#include "filter/replay_gain_filter_plugin.h" - -#include <glib.h> - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "output" - -#define AUDIO_OUTPUT_TYPE "type" -#define AUDIO_OUTPUT_NAME "name" -#define AUDIO_OUTPUT_FORMAT "format" -#define AUDIO_FILTERS "filters" - -static const struct audio_output_plugin * -audio_output_detect(GError **error) -{ - g_warning("Attempt to detect audio output device"); - - audio_output_plugins_for_each(plugin) { - if (plugin->test_default_device == NULL) - continue; - - g_warning("Attempting to detect a %s audio device", - plugin->name); - if (ao_plugin_test_default_device(plugin)) - return plugin; - } - - g_set_error(error, audio_output_quark(), 0, - "Unable to detect an audio device"); - return NULL; -} - -/** - * Determines the mixer type which should be used for the specified - * configuration block. - * - * This handles the deprecated options mixer_type (global) and - * mixer_enabled, if the mixer_type setting is not configured. - */ -static enum mixer_type -audio_output_mixer_type(const struct config_param *param) -{ - /* read the local "mixer_type" setting */ - const char *p = config_get_block_string(param, "mixer_type", NULL); - if (p != NULL) - return mixer_type_parse(p); - - /* try the local "mixer_enabled" setting next (deprecated) */ - if (!config_get_block_bool(param, "mixer_enabled", true)) - return MIXER_TYPE_NONE; - - /* fall back to the global "mixer_type" setting (also - deprecated) */ - return mixer_type_parse(config_get_string("mixer_type", "hardware")); -} - -static struct mixer * -audio_output_load_mixer(struct audio_output *ao, - const struct config_param *param, - const struct mixer_plugin *plugin, - struct filter *filter_chain, - GError **error_r) -{ - struct mixer *mixer; - - switch (audio_output_mixer_type(param)) { - case MIXER_TYPE_NONE: - case MIXER_TYPE_UNKNOWN: - return NULL; - - case MIXER_TYPE_HARDWARE: - if (plugin == NULL) - return NULL; - - return mixer_new(plugin, ao, param, error_r); - - case MIXER_TYPE_SOFTWARE: - mixer = mixer_new(&software_mixer_plugin, NULL, NULL, NULL); - assert(mixer != NULL); - - filter_chain_append(filter_chain, - software_mixer_get_filter(mixer)); - return mixer; - } - - assert(false); - return NULL; -} - -bool -ao_base_init(struct audio_output *ao, - const struct audio_output_plugin *plugin, - const struct config_param *param, GError **error_r) -{ - assert(ao != NULL); - assert(plugin != NULL); - assert(plugin->finish != NULL); - assert(plugin->open != NULL); - assert(plugin->close != NULL); - assert(plugin->play != NULL); - - GError *error = NULL; - - if (param) { - const char *p; - - ao->name = config_get_block_string(param, AUDIO_OUTPUT_NAME, - NULL); - if (ao->name == NULL) { - g_set_error(error_r, audio_output_quark(), 0, - "Missing \"name\" configuration"); - return false; - } - - p = config_get_block_string(param, AUDIO_OUTPUT_FORMAT, - NULL); - if (p != NULL) { - bool success = - audio_format_parse(&ao->config_audio_format, - p, true, error_r); - if (!success) - return false; - } else - audio_format_clear(&ao->config_audio_format); - } else { - ao->name = "default detected output"; - - audio_format_clear(&ao->config_audio_format); - } - - ao->plugin = plugin; - ao->always_on = config_get_block_bool(param, "always_on", false); - ao->enabled = config_get_block_bool(param, "enabled", true); - ao->really_enabled = false; - ao->open = false; - ao->pause = false; - ao->allow_play = true; - ao->fail_timer = NULL; - - pcm_buffer_init(&ao->cross_fade_buffer); - - /* set up the filter chain */ - - ao->filter = filter_chain_new(); - assert(ao->filter != NULL); - - /* create the normalization filter (if configured) */ - - if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) { - struct filter *normalize_filter = - filter_new(&normalize_filter_plugin, NULL, NULL); - assert(normalize_filter != NULL); - - filter_chain_append(ao->filter, - autoconvert_filter_new(normalize_filter)); - } - - filter_chain_parse(ao->filter, - config_get_block_string(param, AUDIO_FILTERS, ""), - &error - ); - - // It's not really fatal - Part of the filter chain has been set up already - // and even an empty one will work (if only with unexpected behaviour) - if (error != NULL) { - g_warning("Failed to initialize filter chain for '%s': %s", - ao->name, error->message); - g_error_free(error); - } - - ao->thread = NULL; - ao->command = AO_COMMAND_NONE; - ao->mutex = g_mutex_new(); - ao->cond = g_cond_new(); - - ao->mixer = NULL; - ao->replay_gain_filter = NULL; - ao->other_replay_gain_filter = NULL; - - /* done */ - - return true; -} - -static bool -audio_output_setup(struct audio_output *ao, const struct config_param *param, - GError **error_r) -{ - - /* create the replay_gain filter */ - - const char *replay_gain_handler = - config_get_block_string(param, "replay_gain_handler", - "software"); - - if (strcmp(replay_gain_handler, "none") != 0) { - ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin, - param, NULL); - assert(ao->replay_gain_filter != NULL); - - ao->replay_gain_serial = 0; - - ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin, - param, NULL); - assert(ao->other_replay_gain_filter != NULL); - - ao->other_replay_gain_serial = 0; - } else { - ao->replay_gain_filter = NULL; - ao->other_replay_gain_filter = NULL; - } - - /* set up the mixer */ - - GError *error = NULL; - ao->mixer = audio_output_load_mixer(ao, param, - ao->plugin->mixer_plugin, - ao->filter, &error); - if (ao->mixer == NULL && error != NULL) { - g_warning("Failed to initialize hardware mixer for '%s': %s", - ao->name, error->message); - g_error_free(error); - } - - /* use the hardware mixer for replay gain? */ - - if (strcmp(replay_gain_handler, "mixer") == 0) { - if (ao->mixer != NULL) - replay_gain_filter_set_mixer(ao->replay_gain_filter, - ao->mixer, 100); - else - g_warning("No such mixer for output '%s'", ao->name); - } else if (strcmp(replay_gain_handler, "software") != 0 && - ao->replay_gain_filter != NULL) { - g_set_error(error_r, audio_output_quark(), 0, - "Invalid \"replay_gain_handler\" value"); - return false; - } - - /* the "convert" filter must be the last one in the chain */ - - ao->convert_filter = filter_new(&convert_filter_plugin, NULL, NULL); - assert(ao->convert_filter != NULL); - - filter_chain_append(ao->filter, ao->convert_filter); - - return true; -} - -struct audio_output * -audio_output_new(const struct config_param *param, - struct player_control *pc, - GError **error_r) -{ - const struct audio_output_plugin *plugin; - - if (param) { - const char *p; - - p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL); - if (p == NULL) { - g_set_error(error_r, audio_output_quark(), 0, - "Missing \"type\" configuration"); - return false; - } - - plugin = audio_output_plugin_get(p); - if (plugin == NULL) { - g_set_error(error_r, audio_output_quark(), 0, - "No such audio output plugin: %s", p); - return false; - } - } else { - g_warning("No \"%s\" defined in config file\n", - CONF_AUDIO_OUTPUT); - - plugin = audio_output_detect(error_r); - if (plugin == NULL) - return false; - - g_message("Successfully detected a %s audio device", - plugin->name); - } - - struct audio_output *ao = ao_plugin_init(plugin, param, error_r); - if (ao == NULL) - return NULL; - - if (!audio_output_setup(ao, param, error_r)) { - ao_plugin_finish(ao); - return NULL; - } - - ao->player_control = pc; - return ao; -} diff --git a/src/output_internal.h b/src/output_internal.h index 9d975d789..201962a72 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_list.c b/src/output_list.c deleted file mode 100644 index 835c02bba..000000000 --- a/src/output_list.c +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_list.h" -#include "output_api.h" -#include "output/alsa_output_plugin.h" -#include "output/ao_output_plugin.h" -#include "output/ffado_output_plugin.h" -#include "output/fifo_output_plugin.h" -#include "output/httpd_output_plugin.h" -#include "output/jack_output_plugin.h" -#include "output/mvp_output_plugin.h" -#include "output/null_output_plugin.h" -#include "output/openal_output_plugin.h" -#include "output/oss_output_plugin.h" -#include "output/osx_output_plugin.h" -#include "output/pipe_output_plugin.h" -#include "output/pulse_output_plugin.h" -#include "output/recorder_output_plugin.h" -#include "output/roar_output_plugin.h" -#include "output/shout_output_plugin.h" -#include "output/solaris_output_plugin.h" -#include "output/winmm_output_plugin.h" - -const struct audio_output_plugin *const audio_output_plugins[] = { -#ifdef HAVE_SHOUT - &shout_output_plugin, -#endif - &null_output_plugin, -#ifdef HAVE_FIFO - &fifo_output_plugin, -#endif -#ifdef ENABLE_PIPE_OUTPUT - &pipe_output_plugin, -#endif -#ifdef HAVE_ALSA - &alsa_output_plugin, -#endif -#ifdef HAVE_ROAR - &roar_output_plugin, -#endif -#ifdef HAVE_AO - &ao_output_plugin, -#endif -#ifdef HAVE_OSS - &oss_output_plugin, -#endif -#ifdef HAVE_OPENAL - &openal_output_plugin, -#endif -#ifdef HAVE_OSX - &osx_output_plugin, -#endif -#ifdef ENABLE_SOLARIS_OUTPUT - &solaris_output_plugin, -#endif -#ifdef HAVE_PULSE - &pulse_output_plugin, -#endif -#ifdef HAVE_MVP - &mvp_output_plugin, -#endif -#ifdef HAVE_JACK - &jack_output_plugin, -#endif -#ifdef ENABLE_HTTPD_OUTPUT - &httpd_output_plugin, -#endif -#ifdef ENABLE_RECORDER_OUTPUT - &recorder_output_plugin, -#endif -#ifdef ENABLE_WINMM_OUTPUT - &winmm_output_plugin, -#endif -#ifdef ENABLE_FFADO_OUTPUT - &ffado_output_plugin, -#endif - NULL -}; - -const struct audio_output_plugin * -audio_output_plugin_get(const char *name) -{ - audio_output_plugins_for_each(plugin) - if (strcmp(plugin->name, name) == 0) - return plugin; - - return NULL; -} diff --git a/src/output_list.h b/src/output_list.h deleted file mode 100644 index 185ada716..000000000 --- a/src/output_list.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_LIST_H -#define MPD_OUTPUT_LIST_H - -extern const struct audio_output_plugin *const audio_output_plugins[]; - -const struct audio_output_plugin * -audio_output_plugin_get(const char *name); - -#define audio_output_plugins_for_each(plugin) \ - for (const struct audio_output_plugin *plugin, \ - *const*output_plugin_iterator = &audio_output_plugins[0]; \ - (plugin = *output_plugin_iterator) != NULL; ++output_plugin_iterator) - -#endif diff --git a/src/output_plugin.c b/src/output_plugin.c deleted file mode 100644 index 221570c1c..000000000 --- a/src/output_plugin.c +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_plugin.h" -#include "output_internal.h" - -struct audio_output * -ao_plugin_init(const struct audio_output_plugin *plugin, - const struct config_param *param, - GError **error) -{ - assert(plugin != NULL); - assert(plugin->init != NULL); - - return plugin->init(param, error); -} - -void -ao_plugin_finish(struct audio_output *ao) -{ - ao->plugin->finish(ao); -} - -bool -ao_plugin_enable(struct audio_output *ao, GError **error_r) -{ - return ao->plugin->enable != NULL - ? ao->plugin->enable(ao, error_r) - : true; -} - -void -ao_plugin_disable(struct audio_output *ao) -{ - if (ao->plugin->disable != NULL) - ao->plugin->disable(ao); -} - -bool -ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - return ao->plugin->open(ao, audio_format, error); -} - -void -ao_plugin_close(struct audio_output *ao) -{ - ao->plugin->close(ao); -} - -unsigned -ao_plugin_delay(struct audio_output *ao) -{ - return ao->plugin->delay != NULL - ? ao->plugin->delay(ao) - : 0; -} - -void -ao_plugin_send_tag(struct audio_output *ao, const struct tag *tag) -{ - if (ao->plugin->send_tag != NULL) - ao->plugin->send_tag(ao, tag); -} - -size_t -ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - return ao->plugin->play(ao, chunk, size, error); -} - -void -ao_plugin_drain(struct audio_output *ao) -{ - if (ao->plugin->drain != NULL) - ao->plugin->drain(ao); -} - -void -ao_plugin_cancel(struct audio_output *ao) -{ - if (ao->plugin->cancel != NULL) - ao->plugin->cancel(ao); -} - -bool -ao_plugin_pause(struct audio_output *ao) -{ - return ao->plugin->pause != NULL && ao->plugin->pause(ao); -} diff --git a/src/output_plugin.h b/src/output_plugin.h index 209ca6221..2b71ba6a6 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/output_print.c b/src/output_print.c deleted file mode 100644 index 483648ca2..000000000 --- a/src/output_print.c +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Protocol specific code for the audio output library. - * - */ - -#include "config.h" -#include "output_print.h" -#include "output_internal.h" -#include "output_all.h" -#include "client.h" - -void -printAudioDevices(struct client *client) -{ - unsigned n = audio_output_count(); - - for (unsigned i = 0; i < n; ++i) { - const struct audio_output *ao = audio_output_get(i); - - client_printf(client, - "outputid: %i\n" - "outputname: %s\n" - "outputenabled: %i\n", - i, ao->name, ao->enabled); - } -} diff --git a/src/output_print.h b/src/output_print.h deleted file mode 100644 index e02f4e9f5..000000000 --- a/src/output_print.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Protocol specific code for the audio output library. - * - */ - -#ifndef OUTPUT_PRINT_H -#define OUTPUT_PRINT_H - -struct client; - -void -printAudioDevices(struct client *client); - -#endif diff --git a/src/output_state.c b/src/output_state.c deleted file mode 100644 index 7bcafb36b..000000000 --- a/src/output_state.c +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Saving and loading the audio output states to/from the state file. - * - */ - -#include "config.h" -#include "output_state.h" -#include "output_internal.h" -#include "output_all.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> -#include <string.h> - -#define AUDIO_DEVICE_STATE "audio_device_state:" - -unsigned audio_output_state_version; - -void -audio_output_state_save(FILE *fp) -{ - unsigned n = audio_output_count(); - - assert(n > 0); - - for (unsigned i = 0; i < n; ++i) { - const struct audio_output *ao = audio_output_get(i); - - fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n", - ao->enabled, ao->name); - } -} - -bool -audio_output_state_read(const char *line) -{ - long value; - char *endptr; - const char *name; - struct audio_output *ao; - - if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE)) - return false; - - line += sizeof(AUDIO_DEVICE_STATE) - 1; - - value = strtol(line, &endptr, 10); - if (*endptr != ':' || (value != 0 && value != 1)) - return false; - - if (value != 0) - /* state is "enabled": no-op */ - return true; - - name = endptr + 1; - ao = audio_output_find(name); - if (ao == NULL) { - g_debug("Ignoring device state for '%s'", name); - return true; - } - - ao->enabled = false; - return true; -} - -unsigned -audio_output_state_get_version(void) -{ - return audio_output_state_version; -} diff --git a/src/output_state.h b/src/output_state.h deleted file mode 100644 index 320a3520a..000000000 --- a/src/output_state.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Saving and loading the audio output states to/from the state file. - * - */ - -#ifndef OUTPUT_STATE_H -#define OUTPUT_STATE_H - -#include <stdbool.h> -#include <stdio.h> - -bool -audio_output_state_read(const char *line); - -void -audio_output_state_save(FILE *fp); - -/** - * Generates a version number for the current state of the audio - * outputs. This is used by timer_save_state_file() to determine - * whether the state has changed and the state file should be saved. - */ -unsigned -audio_output_state_get_version(void); - -#endif diff --git a/src/output_thread.c b/src/output_thread.c deleted file mode 100644 index 4eef2ccdd..000000000 --- a/src/output_thread.c +++ /dev/null @@ -1,685 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_thread.h" -#include "output_api.h" -#include "output_internal.h" -#include "chunk.h" -#include "pipe.h" -#include "player_control.h" -#include "pcm_mix.h" -#include "filter_plugin.h" -#include "filter/convert_filter_plugin.h" -#include "filter/replay_gain_filter_plugin.h" -#include "mpd_error.h" -#include "notify.h" -#include "gcc.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "output" - -static void ao_command_finished(struct audio_output *ao) -{ - assert(ao->command != AO_COMMAND_NONE); - ao->command = AO_COMMAND_NONE; - - g_mutex_unlock(ao->mutex); - notify_signal(&audio_output_client_notify); - g_mutex_lock(ao->mutex); -} - -static bool -ao_enable(struct audio_output *ao) -{ - GError *error = NULL; - bool success; - - if (ao->really_enabled) - return true; - - g_mutex_unlock(ao->mutex); - success = ao_plugin_enable(ao, &error); - g_mutex_lock(ao->mutex); - if (!success) { - g_warning("Failed to enable \"%s\" [%s]: %s\n", - ao->name, ao->plugin->name, error->message); - g_error_free(error); - return false; - } - - ao->really_enabled = true; - return true; -} - -static void -ao_close(struct audio_output *ao, bool drain); - -static void -ao_disable(struct audio_output *ao) -{ - if (ao->open) - ao_close(ao, false); - - if (ao->really_enabled) { - ao->really_enabled = false; - - g_mutex_unlock(ao->mutex); - ao_plugin_disable(ao); - g_mutex_lock(ao->mutex); - } -} - -static const struct audio_format * -ao_filter_open(struct audio_output *ao, - struct audio_format *audio_format, - GError **error_r) -{ - assert(audio_format_valid(audio_format)); - - /* the replay_gain filter cannot fail here */ - if (ao->replay_gain_filter != NULL) - filter_open(ao->replay_gain_filter, audio_format, error_r); - if (ao->other_replay_gain_filter != NULL) - filter_open(ao->other_replay_gain_filter, audio_format, - error_r); - - const struct audio_format *af - = filter_open(ao->filter, audio_format, error_r); - if (af == NULL) { - if (ao->replay_gain_filter != NULL) - filter_close(ao->replay_gain_filter); - if (ao->other_replay_gain_filter != NULL) - filter_close(ao->other_replay_gain_filter); - } - - return af; -} - -static void -ao_filter_close(struct audio_output *ao) -{ - if (ao->replay_gain_filter != NULL) - filter_close(ao->replay_gain_filter); - if (ao->other_replay_gain_filter != NULL) - filter_close(ao->other_replay_gain_filter); - - filter_close(ao->filter); -} - -static void -ao_open(struct audio_output *ao) -{ - bool success; - GError *error = NULL; - const struct audio_format *filter_audio_format; - struct audio_format_string af_string; - - assert(!ao->open); - assert(ao->pipe != NULL); - assert(ao->chunk == NULL); - assert(audio_format_valid(&ao->in_audio_format)); - - if (ao->fail_timer != NULL) { - /* this can only happen when this - output thread fails while - audio_output_open() is run in the - player thread */ - g_timer_destroy(ao->fail_timer); - ao->fail_timer = NULL; - } - - /* enable the device (just in case the last enable has failed) */ - - if (!ao_enable(ao)) - /* still no luck */ - return; - - /* open the filter */ - - filter_audio_format = ao_filter_open(ao, &ao->in_audio_format, &error); - if (filter_audio_format == NULL) { - g_warning("Failed to open filter for \"%s\" [%s]: %s", - ao->name, ao->plugin->name, error->message); - g_error_free(error); - - ao->fail_timer = g_timer_new(); - return; - } - - assert(audio_format_valid(filter_audio_format)); - - ao->out_audio_format = *filter_audio_format; - audio_format_mask_apply(&ao->out_audio_format, - &ao->config_audio_format); - - g_mutex_unlock(ao->mutex); - success = ao_plugin_open(ao, &ao->out_audio_format, &error); - g_mutex_lock(ao->mutex); - - assert(!ao->open); - - if (!success) { - g_warning("Failed to open \"%s\" [%s]: %s", - ao->name, ao->plugin->name, error->message); - g_error_free(error); - - ao_filter_close(ao); - ao->fail_timer = g_timer_new(); - return; - } - - convert_filter_set(ao->convert_filter, &ao->out_audio_format); - - ao->open = true; - - g_debug("opened plugin=%s name=\"%s\" " - "audio_format=%s", - ao->plugin->name, ao->name, - audio_format_to_string(&ao->out_audio_format, &af_string)); - - if (!audio_format_equals(&ao->in_audio_format, - &ao->out_audio_format)) - g_debug("converting from %s", - audio_format_to_string(&ao->in_audio_format, - &af_string)); -} - -static void -ao_close(struct audio_output *ao, bool drain) -{ - assert(ao->open); - - ao->pipe = NULL; - - ao->chunk = NULL; - ao->open = false; - - g_mutex_unlock(ao->mutex); - - if (drain) - ao_plugin_drain(ao); - else - ao_plugin_cancel(ao); - - ao_plugin_close(ao); - ao_filter_close(ao); - - g_mutex_lock(ao->mutex); - - g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name); -} - -static void -ao_reopen_filter(struct audio_output *ao) -{ - const struct audio_format *filter_audio_format; - GError *error = NULL; - - ao_filter_close(ao); - filter_audio_format = ao_filter_open(ao, &ao->in_audio_format, &error); - if (filter_audio_format == NULL) { - g_warning("Failed to open filter for \"%s\" [%s]: %s", - ao->name, ao->plugin->name, error->message); - g_error_free(error); - - /* this is a little code duplication fro ao_close(), - but we cannot call this function because we must - not call filter_close(ao->filter) again */ - - ao->pipe = NULL; - - ao->chunk = NULL; - ao->open = false; - ao->fail_timer = g_timer_new(); - - g_mutex_unlock(ao->mutex); - ao_plugin_close(ao); - g_mutex_lock(ao->mutex); - - return; - } - - convert_filter_set(ao->convert_filter, &ao->out_audio_format); -} - -static void -ao_reopen(struct audio_output *ao) -{ - if (!audio_format_fully_defined(&ao->config_audio_format)) { - if (ao->open) { - const struct music_pipe *mp = ao->pipe; - ao_close(ao, true); - ao->pipe = mp; - } - - /* no audio format is configured: copy in->out, let - the output's open() method determine the effective - out_audio_format */ - ao->out_audio_format = ao->in_audio_format; - audio_format_mask_apply(&ao->out_audio_format, - &ao->config_audio_format); - } - - if (ao->open) - /* the audio format has changed, and all filters have - to be reconfigured */ - ao_reopen_filter(ao); - else - ao_open(ao); -} - -/** - * Wait until the output's delay reaches zero. - * - * @return true if playback should be continued, false if a command - * was issued - */ -static bool -ao_wait(struct audio_output *ao) -{ - while (true) { - unsigned delay = ao_plugin_delay(ao); - if (delay == 0) - return true; - - GTimeVal tv; - g_get_current_time(&tv); - g_time_val_add(&tv, delay * 1000); - (void)g_cond_timed_wait(ao->cond, ao->mutex, &tv); - - if (ao->command != AO_COMMAND_NONE) - return false; - } -} - -static const char * -ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, - struct filter *replay_gain_filter, - unsigned *replay_gain_serial_p, - size_t *length_r) -{ - assert(chunk != NULL); - assert(!music_chunk_is_empty(chunk)); - assert(music_chunk_check_format(chunk, &ao->in_audio_format)); - - const char *data = chunk->data; - size_t length = chunk->length; - - (void)ao; - - assert(length % audio_format_frame_size(&ao->in_audio_format) == 0); - - if (length > 0 && replay_gain_filter != NULL) { - if (chunk->replay_gain_serial != *replay_gain_serial_p) { - replay_gain_filter_set_info(replay_gain_filter, - chunk->replay_gain_serial != 0 - ? &chunk->replay_gain_info - : NULL); - *replay_gain_serial_p = chunk->replay_gain_serial; - } - - GError *error = NULL; - data = filter_filter(replay_gain_filter, data, length, - &length, &error); - if (data == NULL) { - g_warning("\"%s\" [%s] failed to filter: %s", - ao->name, ao->plugin->name, error->message); - g_error_free(error); - return NULL; - } - } - - *length_r = length; - return data; -} - -static const char * -ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, - size_t *length_r) -{ - GError *error = NULL; - - size_t length; - const char *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter, - &ao->replay_gain_serial, &length); - if (data == NULL) - return NULL; - - if (length == 0) { - /* empty chunk, nothing to do */ - *length_r = 0; - return data; - } - - /* cross-fade */ - - if (chunk->other != NULL) { - size_t other_length; - const char *other_data = - ao_chunk_data(ao, chunk->other, - ao->other_replay_gain_filter, - &ao->other_replay_gain_serial, - &other_length); - if (other_data == NULL) - return NULL; - - if (other_length == 0) { - *length_r = 0; - return data; - } - - /* if the "other" chunk is longer, then that trailer - is used as-is, without mixing; it is part of the - "next" song being faded in, and if there's a rest, - it means cross-fading ends here */ - - if (length > other_length) - length = other_length; - - char *dest = pcm_buffer_get(&ao->cross_fade_buffer, - other_length); - memcpy(dest, other_data, other_length); - if (!pcm_mix(dest, data, length, ao->in_audio_format.format, - 1.0 - chunk->mix_ratio)) { - g_warning("Cannot cross-fade format %s", - sample_format_to_string(ao->in_audio_format.format)); - return NULL; - } - - data = dest; - length = other_length; - } - - /* apply filter chain */ - - data = filter_filter(ao->filter, data, length, &length, &error); - if (data == NULL) { - g_warning("\"%s\" [%s] failed to filter: %s", - ao->name, ao->plugin->name, error->message); - g_error_free(error); - return NULL; - } - - *length_r = length; - return data; -} - -static bool -ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) -{ - GError *error = NULL; - - assert(ao != NULL); - assert(ao->filter != NULL); - - if (chunk->tag != NULL) { - g_mutex_unlock(ao->mutex); - ao_plugin_send_tag(ao, chunk->tag); - g_mutex_lock(ao->mutex); - } - - size_t size; -#if GCC_CHECK_VERSION(4,7) - /* workaround -Wmaybe-uninitialized false positive */ - size = 0; -#endif - const char *data = ao_filter_chunk(ao, chunk, &size); - if (data == NULL) { - ao_close(ao, false); - - /* don't automatically reopen this device for 10 - seconds */ - ao->fail_timer = g_timer_new(); - return false; - } - - while (size > 0 && ao->command == AO_COMMAND_NONE) { - size_t nbytes; - - if (!ao_wait(ao)) - break; - - g_mutex_unlock(ao->mutex); - nbytes = ao_plugin_play(ao, data, size, &error); - g_mutex_lock(ao->mutex); - if (nbytes == 0) { - /* play()==0 means failure */ - g_warning("\"%s\" [%s] failed to play: %s", - ao->name, ao->plugin->name, error->message); - g_error_free(error); - - ao_close(ao, false); - - /* don't automatically reopen this device for - 10 seconds */ - assert(ao->fail_timer == NULL); - ao->fail_timer = g_timer_new(); - - return false; - } - - assert(nbytes <= size); - assert(nbytes % audio_format_frame_size(&ao->out_audio_format) == 0); - - data += nbytes; - size -= nbytes; - } - - return true; -} - -static const struct music_chunk * -ao_next_chunk(struct audio_output *ao) -{ - return ao->chunk != NULL - /* continue the previous play() call */ - ? ao->chunk->next - /* get the first chunk from the pipe */ - : music_pipe_peek(ao->pipe); -} - -/** - * Plays all remaining chunks, until the tail of the pipe has been - * reached (and no more chunks are queued), or until a command is - * received. - * - * @return true if at least one chunk has been available, false if the - * tail of the pipe was already reached - */ -static bool -ao_play(struct audio_output *ao) -{ - bool success; - const struct music_chunk *chunk; - - assert(ao->pipe != NULL); - - chunk = ao_next_chunk(ao); - if (chunk == NULL) - /* no chunk available */ - return false; - - ao->chunk_finished = false; - - while (chunk != NULL && ao->command == AO_COMMAND_NONE) { - assert(!ao->chunk_finished); - - ao->chunk = chunk; - - success = ao_play_chunk(ao, chunk); - if (!success) { - assert(ao->chunk == NULL); - break; - } - - assert(ao->chunk == chunk); - chunk = chunk->next; - } - - ao->chunk_finished = true; - - g_mutex_unlock(ao->mutex); - player_lock_signal(ao->player_control); - g_mutex_lock(ao->mutex); - - return true; -} - -static void ao_pause(struct audio_output *ao) -{ - bool ret; - - g_mutex_unlock(ao->mutex); - ao_plugin_cancel(ao); - g_mutex_lock(ao->mutex); - - ao->pause = true; - ao_command_finished(ao); - - do { - if (!ao_wait(ao)) - break; - - g_mutex_unlock(ao->mutex); - ret = ao_plugin_pause(ao); - g_mutex_lock(ao->mutex); - - if (!ret) { - ao_close(ao, false); - break; - } - } while (ao->command == AO_COMMAND_NONE); - - ao->pause = false; -} - -static gpointer audio_output_task(gpointer arg) -{ - struct audio_output *ao = arg; - - g_mutex_lock(ao->mutex); - - while (1) { - switch (ao->command) { - case AO_COMMAND_NONE: - break; - - case AO_COMMAND_ENABLE: - ao_enable(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_DISABLE: - ao_disable(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_OPEN: - ao_open(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_REOPEN: - ao_reopen(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_CLOSE: - assert(ao->open); - assert(ao->pipe != NULL); - - ao_close(ao, false); - ao_command_finished(ao); - break; - - case AO_COMMAND_PAUSE: - if (!ao->open) { - /* the output has failed after - audio_output_all_pause() has - submitted the PAUSE command; bail - out */ - ao_command_finished(ao); - break; - } - - ao_pause(ao); - /* don't "break" here: this might cause - ao_play() to be called when command==CLOSE - ends the paused state - "continue" checks - the new command first */ - continue; - - case AO_COMMAND_DRAIN: - if (ao->open) { - assert(ao->chunk == NULL); - assert(music_pipe_peek(ao->pipe) == NULL); - - g_mutex_unlock(ao->mutex); - ao_plugin_drain(ao); - g_mutex_lock(ao->mutex); - } - - ao_command_finished(ao); - continue; - - case AO_COMMAND_CANCEL: - ao->chunk = NULL; - - if (ao->open) { - g_mutex_unlock(ao->mutex); - ao_plugin_cancel(ao); - g_mutex_lock(ao->mutex); - } - - ao_command_finished(ao); - continue; - - case AO_COMMAND_KILL: - ao->chunk = NULL; - ao_command_finished(ao); - g_mutex_unlock(ao->mutex); - return NULL; - } - - if (ao->open && ao->allow_play && ao_play(ao)) - /* don't wait for an event if there are more - chunks in the pipe */ - continue; - - if (ao->command == AO_COMMAND_NONE) - g_cond_wait(ao->cond, ao->mutex); - } -} - -void audio_output_thread_start(struct audio_output *ao) -{ - GError *e = NULL; - - assert(ao->command == AO_COMMAND_NONE); - - if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e))) - MPD_ERROR("Failed to spawn output task: %s\n", e->message); -} diff --git a/src/output_thread.h b/src/output_thread.h deleted file mode 100644 index 5ad9a7527..000000000 --- a/src/output_thread.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_THREAD_H -#define MPD_OUTPUT_THREAD_H - -struct audio_output; - -void audio_output_thread_start(struct audio_output *ao); - -#endif diff --git a/src/page.c b/src/page.c deleted file mode 100644 index e2e22791f..000000000 --- a/src/page.c +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "page.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -/** - * Allocates a new #page object, without filling the data element. - */ -static struct page * -page_new(size_t size) -{ - struct page *page = g_malloc(sizeof(*page) + size - - sizeof(page->data)); - - assert(size > 0); - - page->ref = 1; - page->size = size; - return page; -} - -struct page * -page_new_copy(const void *data, size_t size) -{ - struct page *page = page_new(size); - - assert(data != NULL); - - memcpy(page->data, data, size); - return page; -} - -struct page * -page_new_concat(const struct page *a, const struct page *b) -{ - struct page *page = page_new(a->size + b->size); - - memcpy(page->data, a->data, a->size); - memcpy(page->data + a->size, b->data, b->size); - - return page; -} - -void -page_ref(struct page *page) -{ - g_atomic_int_inc(&page->ref); -} - -static void -page_free(struct page *page) -{ - assert(page->ref == 0); - - g_free(page); -} - -bool -page_unref(struct page *page) -{ - bool unused = g_atomic_int_dec_and_test(&page->ref); - - if (unused) - page_free(page); - - return unused; -} diff --git a/src/page.h b/src/page.h deleted file mode 100644 index 8a3aaf396..000000000 --- a/src/page.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This is a library which manages reference counted buffers. - */ - -#ifndef MPD_PAGE_H -#define MPD_PAGE_H - -#include <stddef.h> -#include <stdbool.h> - -/** - * A dynamically allocated buffer which keeps track of its reference - * count. This is useful for passing buffers around, when several - * instances hold references to one buffer. - */ -struct page { - /** - * The number of references to this buffer. This library uses - * atomic functions to access it, i.e. no locks are required. - * As soon as this attribute reaches zero, the buffer is - * freed. - */ - int ref; - - /** - * The size of this buffer in bytes. - */ - size_t size; - - /** - * Dynamic array containing the buffer data. - */ - unsigned char data[sizeof(long)]; -}; - -/** - * Creates a new #page object, and copies data from the specified - * buffer. It is initialized with a reference count of 1. - * - * @param data the source buffer - * @param size the size of the source buffer - * @return the new #page object - */ -struct page * -page_new_copy(const void *data, size_t size); - -/** - * Concatenates two pages to a new page. - * - * @param a the first page - * @param b the second page, which is appended - */ -struct page * -page_new_concat(const struct page *a, const struct page *b); - -/** - * Increases the reference counter. - * - * @param page the #page object - */ -void -page_ref(struct page *page); - -/** - * Decreases the reference counter. If it reaches zero, the #page is - * freed. - * - * @param page the #page object - * @return true if the #page has been freed - */ -bool -page_unref(struct page *page); - -#endif diff --git a/src/path.c b/src/path.c deleted file mode 100644 index 59a91a0f7..000000000 --- a/src/path.c +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "path.h" -#include "conf.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -#ifdef G_OS_WIN32 -#include <windows.h> // for GetACP() -#include <stdio.h> // for sprintf() -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "path" - -static char *fs_charset; - -char * -fs_charset_to_utf8(const char *path_fs) -{ - return g_convert(path_fs, -1, - "utf-8", fs_charset, - NULL, NULL, NULL); -} - -char * -utf8_to_fs_charset(const char *path_utf8) -{ - gchar *p; - - p = g_convert(path_utf8, -1, - fs_charset, "utf-8", - NULL, NULL, NULL); - if (p == NULL) - /* fall back to UTF-8 */ - p = g_strdup(path_utf8); - - return p; -} - -static void -path_set_fs_charset(const char *charset) -{ - char *test; - - assert(charset != NULL); - - /* convert a space to ensure that the charset is valid */ - test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL); - if (test == NULL) - MPD_ERROR("invalid filesystem charset: %s", charset); - g_free(test); - - g_free(fs_charset); - fs_charset = g_strdup(charset); - - g_debug("path_set_fs_charset: fs charset is: %s", fs_charset); -} - -const char *path_get_fs_charset(void) -{ - return fs_charset; -} - -void path_global_init(void) -{ - const char *charset = NULL; - - charset = config_get_string(CONF_FS_CHARSET, NULL); - if (charset == NULL) { -#ifndef G_OS_WIN32 - const gchar **encodings; - g_get_filename_charsets(&encodings); - - if (encodings[0] != NULL && *encodings[0] != '\0') - charset = encodings[0]; -#else /* G_OS_WIN32 */ - /* Glib claims that file system encoding is always utf-8 - * on native Win32 (i.e. not Cygwin). - * However this is true only if <gstdio.h> helpers are used. - * MPD uses regular <stdio.h> functions. - * Those functions use encoding determined by GetACP(). */ - char win_charset[13]; - sprintf(win_charset, "cp%u", GetACP()); - charset = win_charset; -#endif - } - - if (charset) { - path_set_fs_charset(charset); - } else { - g_message("setting filesystem charset to ISO-8859-1"); - path_set_fs_charset("ISO-8859-1"); - } -} - -void path_global_finish(void) -{ - g_free(fs_charset); -} diff --git a/src/path.h b/src/path.h deleted file mode 100644 index 00c368e70..000000000 --- a/src/path.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PATH_H -#define MPD_PATH_H - -#include <limits.h> - -#if !defined(MPD_PATH_MAX) -# if defined(MAXPATHLEN) -# define MPD_PATH_MAX MAXPATHLEN -# elif defined(PATH_MAX) -# define MPD_PATH_MAX PATH_MAX -# else -# define MPD_PATH_MAX 256 -# endif -#endif - -void path_global_init(void); - -void path_global_finish(void); - -/** - * Converts a file name in the filesystem charset to UTF-8. Returns - * NULL on failure. - */ -char * -fs_charset_to_utf8(const char *path_fs); - -/** - * Converts a file name in UTF-8 to the filesystem charset. Returns a - * duplicate of the UTF-8 string on failure. - */ -char * -utf8_to_fs_charset(const char *path_utf8); - -const char *path_get_fs_charset(void); - -#endif diff --git a/src/pcm_buffer.h b/src/pcm_buffer.h index 4502976f6..5d6382d5e 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_channels.c b/src/pcm_channels.c deleted file mode 100644 index ec2bd69a5..000000000 --- a/src/pcm_channels.c +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_channels.h" -#include "pcm_buffer.h" -#include "pcm_utils.h" - -#include <assert.h> - -static void -pcm_convert_channels_16_1_to_2(int16_t *restrict dest, - const int16_t *restrict src, - const int16_t *restrict src_end) -{ - while (src < src_end) { - int16_t value = *src++; - - *dest++ = value; - *dest++ = value; - } -} - -static void -pcm_convert_channels_16_2_to_1(int16_t *restrict dest, - const int16_t *restrict src, - const int16_t *restrict src_end) -{ - while (src < src_end) { - int32_t a = *src++, b = *src++; - - *dest++ = (a + b) / 2; - } -} - -static void -pcm_convert_channels_16_n_to_2(int16_t *restrict dest, - unsigned src_channels, - const int16_t *restrict src, - const int16_t *restrict src_end) -{ - unsigned c; - - assert(src_channels > 0); - - while (src < src_end) { - int32_t sum = 0; - int16_t value; - - for (c = 0; c < src_channels; ++c) - sum += *src++; - value = sum / (int)src_channels; - - /* XXX this is actually only mono ... */ - *dest++ = value; - *dest++ = value; - } -} - -const int16_t * -pcm_convert_channels_16(struct pcm_buffer *buffer, - unsigned dest_channels, - unsigned src_channels, const int16_t *src, - size_t src_size, size_t *dest_size_r) -{ - assert(src_size % (sizeof(*src) * src_channels) == 0); - - size_t dest_size = src_size / src_channels * dest_channels; - *dest_size_r = dest_size; - - int16_t *dest = pcm_buffer_get(buffer, dest_size); - const int16_t *src_end = pcm_end_pointer(src, src_size); - - if (src_channels == 1 && dest_channels == 2) - pcm_convert_channels_16_1_to_2(dest, src, src_end); - else if (src_channels == 2 && dest_channels == 1) - pcm_convert_channels_16_2_to_1(dest, src, src_end); - else if (dest_channels == 2) - pcm_convert_channels_16_n_to_2(dest, src_channels, src, - src_end); - else - return NULL; - - return dest; -} - -static void -pcm_convert_channels_24_1_to_2(int32_t *restrict dest, - const int32_t *restrict src, - const int32_t *restrict src_end) -{ - while (src < src_end) { - int32_t value = *src++; - - *dest++ = value; - *dest++ = value; - } -} - -static void -pcm_convert_channels_24_2_to_1(int32_t *restrict dest, - const int32_t *restrict src, - const int32_t *restrict src_end) -{ - while (src < src_end) { - int32_t a = *src++, b = *src++; - - *dest++ = (a + b) / 2; - } -} - -static void -pcm_convert_channels_24_n_to_2(int32_t *restrict dest, - unsigned src_channels, - const int32_t *restrict src, - const int32_t *restrict src_end) -{ - unsigned c; - - assert(src_channels > 0); - - while (src < src_end) { - int32_t sum = 0; - int32_t value; - - for (c = 0; c < src_channels; ++c) - sum += *src++; - value = sum / (int)src_channels; - - /* XXX this is actually only mono ... */ - *dest++ = value; - *dest++ = value; - } -} - -const int32_t * -pcm_convert_channels_24(struct pcm_buffer *buffer, - unsigned dest_channels, - unsigned src_channels, const int32_t *src, - size_t src_size, size_t *dest_size_r) -{ - assert(src_size % (sizeof(*src) * src_channels) == 0); - - size_t dest_size = src_size / src_channels * dest_channels; - *dest_size_r = dest_size; - - int32_t *dest = pcm_buffer_get(buffer, dest_size); - const int32_t *src_end = pcm_end_pointer(src, src_size); - - if (src_channels == 1 && dest_channels == 2) - pcm_convert_channels_24_1_to_2(dest, src, src_end); - else if (src_channels == 2 && dest_channels == 1) - pcm_convert_channels_24_2_to_1(dest, src, src_end); - else if (dest_channels == 2) - pcm_convert_channels_24_n_to_2(dest, src_channels, src, - src_end); - else - return NULL; - - return dest; -} - -static void -pcm_convert_channels_32_1_to_2(int32_t *dest, const int32_t *src, - const int32_t *src_end) -{ - pcm_convert_channels_24_1_to_2(dest, src, src_end); -} - -static void -pcm_convert_channels_32_2_to_1(int32_t *restrict dest, - const int32_t *restrict src, - const int32_t *restrict src_end) -{ - while (src < src_end) { - int64_t a = *src++, b = *src++; - - *dest++ = (a + b) / 2; - } -} - -static void -pcm_convert_channels_32_n_to_2(int32_t *dest, - unsigned src_channels, const int32_t *src, - const int32_t *src_end) -{ - unsigned c; - - assert(src_channels > 0); - - while (src < src_end) { - int64_t sum = 0; - int32_t value; - - for (c = 0; c < src_channels; ++c) - sum += *src++; - value = sum / (int64_t)src_channels; - - /* XXX this is actually only mono ... */ - *dest++ = value; - *dest++ = value; - } -} - -const int32_t * -pcm_convert_channels_32(struct pcm_buffer *buffer, - unsigned dest_channels, - unsigned src_channels, const int32_t *src, - size_t src_size, size_t *dest_size_r) -{ - assert(src_size % (sizeof(*src) * src_channels) == 0); - - size_t dest_size = src_size / src_channels * dest_channels; - *dest_size_r = dest_size; - - int32_t *dest = pcm_buffer_get(buffer, dest_size); - const int32_t *src_end = pcm_end_pointer(src, src_size); - - if (src_channels == 1 && dest_channels == 2) - pcm_convert_channels_32_1_to_2(dest, src, src_end); - else if (src_channels == 2 && dest_channels == 1) - pcm_convert_channels_32_2_to_1(dest, src, src_end); - else if (dest_channels == 2) - pcm_convert_channels_32_n_to_2(dest, src_channels, src, - src_end); - else - return NULL; - - return dest; -} diff --git a/src/pcm_channels.h b/src/pcm_channels.h deleted file mode 100644 index 1e4a0991f..000000000 --- a/src/pcm_channels.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PCM_CHANNELS_H -#define MPD_PCM_CHANNELS_H - -#include <stdint.h> -#include <stddef.h> - -struct pcm_buffer; - -/** - * Changes the number of channels in 16 bit PCM data. - * - * @param buffer the destination pcm_buffer object - * @param dest_channels the number of channels requested - * @param src_channels the number of channels in the source buffer - * @param src the source PCM buffer - * @param src_size the number of bytes in #src - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const int16_t * -pcm_convert_channels_16(struct pcm_buffer *buffer, - unsigned dest_channels, - unsigned src_channels, const int16_t *src, - size_t src_size, size_t *dest_size_r); - -/** - * Changes the number of channels in 24 bit PCM data (aligned at 32 - * bit boundaries). - * - * @param buffer the destination pcm_buffer object - * @param dest_channels the number of channels requested - * @param src_channels the number of channels in the source buffer - * @param src the source PCM buffer - * @param src_size the number of bytes in #src - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const int32_t * -pcm_convert_channels_24(struct pcm_buffer *buffer, - unsigned dest_channels, - unsigned src_channels, const int32_t *src, - size_t src_size, size_t *dest_size_r); - -/** - * Changes the number of channels in 32 bit PCM data. - * - * @param buffer the destination pcm_buffer object - * @param dest_channels the number of channels requested - * @param src_channels the number of channels in the source buffer - * @param src the source PCM buffer - * @param src_size the number of bytes in #src - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const int32_t * -pcm_convert_channels_32(struct pcm_buffer *buffer, - unsigned dest_channels, - unsigned src_channels, const int32_t *src, - size_t src_size, size_t *dest_size_r); - -#endif diff --git a/src/pcm_convert.c b/src/pcm_convert.c deleted file mode 100644 index 63f9a1b98..000000000 --- a/src/pcm_convert.c +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_convert.h" -#include "pcm_channels.h" -#include "pcm_format.h" -#include "pcm_pack.h" -#include "audio_format.h" -#include "glib_compat.h" - -#include <assert.h> -#include <string.h> -#include <math.h> -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pcm" - -void pcm_convert_init(struct pcm_convert_state *state) -{ - memset(state, 0, sizeof(*state)); - - pcm_dsd_init(&state->dsd); - pcm_resample_init(&state->resample); - pcm_dither_24_init(&state->dither); - - pcm_buffer_init(&state->format_buffer); - pcm_buffer_init(&state->channels_buffer); -} - -void pcm_convert_deinit(struct pcm_convert_state *state) -{ - pcm_dsd_deinit(&state->dsd); - pcm_resample_deinit(&state->resample); - - pcm_buffer_deinit(&state->format_buffer); - pcm_buffer_deinit(&state->channels_buffer); -} - -void -pcm_convert_reset(struct pcm_convert_state *state) -{ - pcm_dsd_reset(&state->dsd); - pcm_resample_reset(&state->resample); -} - -static const void * -pcm_convert_channels(struct pcm_buffer *buffer, enum sample_format format, - uint8_t dest_channels, - uint8_t src_channels, const void *src, - size_t src_size, size_t *dest_size_r, - GError **error_r) -{ - const void *dest = NULL; - - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_S8: - case SAMPLE_FORMAT_FLOAT: - case SAMPLE_FORMAT_DSD: - g_set_error(error_r, pcm_convert_quark(), 0, - "Channel conversion not implemented for format '%s'", - sample_format_to_string(format)); - return NULL; - - case SAMPLE_FORMAT_S16: - dest = pcm_convert_channels_16(buffer, dest_channels, - src_channels, src, - src_size, dest_size_r); - break; - - case SAMPLE_FORMAT_S24_P32: - dest = pcm_convert_channels_24(buffer, dest_channels, - src_channels, src, - src_size, dest_size_r); - break; - - case SAMPLE_FORMAT_S32: - dest = pcm_convert_channels_32(buffer, dest_channels, - src_channels, src, - src_size, dest_size_r); - break; - } - - if (dest == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %u to %u channels " - "is not implemented", - src_channels, dest_channels); - return NULL; - } - - return dest; -} - -static const int16_t * -pcm_convert_16(struct pcm_convert_state *state, - const struct audio_format *src_format, - const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, size_t *dest_size_r, - GError **error_r) -{ - const int16_t *buf; - size_t len; - - assert(dest_format->format == SAMPLE_FORMAT_S16); - - buf = pcm_convert_to_16(&state->format_buffer, &state->dither, - src_format->format, src_buffer, src_size, - &len); - if (buf == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %s to 16 bit is not implemented", - sample_format_to_string(src_format->format)); - return NULL; - } - - if (src_format->channels != dest_format->channels) { - buf = pcm_convert_channels_16(&state->channels_buffer, - dest_format->channels, - src_format->channels, - buf, len, &len); - if (buf == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %u to %u channels " - "is not implemented", - src_format->channels, - dest_format->channels); - return NULL; - } - } - - if (src_format->sample_rate != dest_format->sample_rate) { - buf = pcm_resample_16(&state->resample, - dest_format->channels, - src_format->sample_rate, buf, len, - dest_format->sample_rate, &len, - error_r); - if (buf == NULL) - return NULL; - } - - *dest_size_r = len; - return buf; -} - -static const int32_t * -pcm_convert_24(struct pcm_convert_state *state, - const struct audio_format *src_format, - const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, size_t *dest_size_r, - GError **error_r) -{ - const int32_t *buf; - size_t len; - - assert(dest_format->format == SAMPLE_FORMAT_S24_P32); - - buf = pcm_convert_to_24(&state->format_buffer, src_format->format, - src_buffer, src_size, &len); - if (buf == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %s to 24 bit is not implemented", - sample_format_to_string(src_format->format)); - return NULL; - } - - if (src_format->channels != dest_format->channels) { - buf = pcm_convert_channels_24(&state->channels_buffer, - dest_format->channels, - src_format->channels, - buf, len, &len); - if (buf == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %u to %u channels " - "is not implemented", - src_format->channels, - dest_format->channels); - return NULL; - } - } - - if (src_format->sample_rate != dest_format->sample_rate) { - buf = pcm_resample_24(&state->resample, - dest_format->channels, - src_format->sample_rate, buf, len, - dest_format->sample_rate, &len, - error_r); - if (buf == NULL) - return NULL; - } - - *dest_size_r = len; - return buf; -} - -static const int32_t * -pcm_convert_32(struct pcm_convert_state *state, - const struct audio_format *src_format, - const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, size_t *dest_size_r, - GError **error_r) -{ - const int32_t *buf; - size_t len; - - assert(dest_format->format == SAMPLE_FORMAT_S32); - - buf = pcm_convert_to_32(&state->format_buffer, src_format->format, - src_buffer, src_size, &len); - if (buf == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %s to 32 bit is not implemented", - sample_format_to_string(src_format->format)); - return NULL; - } - - if (src_format->channels != dest_format->channels) { - buf = pcm_convert_channels_32(&state->channels_buffer, - dest_format->channels, - src_format->channels, - buf, len, &len); - if (buf == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %u to %u channels " - "is not implemented", - src_format->channels, - dest_format->channels); - return NULL; - } - } - - if (src_format->sample_rate != dest_format->sample_rate) { - buf = pcm_resample_32(&state->resample, - dest_format->channels, - src_format->sample_rate, buf, len, - dest_format->sample_rate, &len, - error_r); - if (buf == NULL) - return buf; - } - - *dest_size_r = len; - return buf; -} - -static const float * -pcm_convert_float(struct pcm_convert_state *state, - const struct audio_format *src_format, - const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, size_t *dest_size_r, - GError **error_r) -{ - const float *buffer = src_buffer; - size_t size = src_size; - - assert(dest_format->format == SAMPLE_FORMAT_FLOAT); - - /* convert channels first, hoping the source format is - supported (float is not) */ - - if (dest_format->channels != src_format->channels) { - buffer = pcm_convert_channels(&state->channels_buffer, - src_format->format, - dest_format->channels, - src_format->channels, - buffer, size, &size, error_r); - if (buffer == NULL) - return NULL; - } - - /* convert to float now */ - - buffer = pcm_convert_to_float(&state->format_buffer, - src_format->format, - buffer, size, &size); - if (buffer == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %s to float is not implemented", - sample_format_to_string(src_format->format)); - return NULL; - } - - /* resample with float, because this is the best format for - libsamplerate */ - - if (src_format->sample_rate != dest_format->sample_rate) { - buffer = pcm_resample_float(&state->resample, - dest_format->channels, - src_format->sample_rate, - buffer, size, - dest_format->sample_rate, &size, - error_r); - if (buffer == NULL) - return NULL; - } - - *dest_size_r = size; - return buffer; -} - -const void * -pcm_convert(struct pcm_convert_state *state, - const struct audio_format *src_format, - const void *src, size_t src_size, - const struct audio_format *dest_format, - size_t *dest_size_r, - GError **error_r) -{ - struct audio_format float_format; - if (src_format->format == SAMPLE_FORMAT_DSD) { - size_t f_size; - const float *f = pcm_dsd_to_float(&state->dsd, - src_format->channels, - false, src, src_size, - &f_size); - if (f == NULL) { - g_set_error_literal(error_r, pcm_convert_quark(), 0, - "DSD to PCM conversion failed"); - return NULL; - } - - float_format = *src_format; - float_format.format = SAMPLE_FORMAT_FLOAT; - - src_format = &float_format; - src = f; - src_size = f_size; - } - - switch (dest_format->format) { - case SAMPLE_FORMAT_S16: - return pcm_convert_16(state, - src_format, src, src_size, - dest_format, dest_size_r, - error_r); - - case SAMPLE_FORMAT_S24_P32: - return pcm_convert_24(state, - src_format, src, src_size, - dest_format, dest_size_r, - error_r); - - case SAMPLE_FORMAT_S32: - return pcm_convert_32(state, - src_format, src, src_size, - dest_format, dest_size_r, - error_r); - - case SAMPLE_FORMAT_FLOAT: - return pcm_convert_float(state, - src_format, src, src_size, - dest_format, dest_size_r, - error_r); - - default: - g_set_error(error_r, pcm_convert_quark(), 0, - "PCM conversion to %s is not implemented", - sample_format_to_string(dest_format->format)); - return NULL; - } -} diff --git a/src/pcm_convert.h b/src/pcm_convert.h deleted file mode 100644 index be11a6e41..000000000 --- a/src/pcm_convert.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PCM_CONVERT_H -#define PCM_CONVERT_H - -#include "pcm_dsd.h" -#include "pcm_resample.h" -#include "pcm_dither.h" -#include "pcm_buffer.h" - -struct audio_format; - -/** - * This object is statically allocated (within another struct), and - * holds buffer allocations and the state for all kinds of PCM - * conversions. - */ -struct pcm_convert_state { - struct pcm_dsd dsd; - - struct pcm_resample_state resample; - - struct pcm_dither dither; - - /** the buffer for converting the sample format */ - struct pcm_buffer format_buffer; - - /** the buffer for converting the channel count */ - struct pcm_buffer channels_buffer; -}; - -static inline GQuark -pcm_convert_quark(void) -{ - return g_quark_from_static_string("pcm_convert"); -} - -/** - * Initializes a pcm_convert_state object. - */ -void pcm_convert_init(struct pcm_convert_state *state); - -/** - * Deinitializes a pcm_convert_state object and frees allocated - * memory. - */ -void pcm_convert_deinit(struct pcm_convert_state *state); - -/** - * Reset the pcm_convert_state object. Use this at the boundary - * between two distinct songs and each time the format changes. - */ -void -pcm_convert_reset(struct pcm_convert_state *state); - -/** - * Converts PCM data between two audio formats. - * - * @param state an initialized pcm_convert_state object - * @param src_format the source audio format - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_format the requested destination audio format - * @param dest_size_r returns the number of bytes of the destination buffer - * @param error_r location to store the error occurring, or NULL to - * ignore errors - * @return the destination buffer, or NULL on error - */ -const void * -pcm_convert(struct pcm_convert_state *state, - const struct audio_format *src_format, - const void *src, size_t src_size, - const struct audio_format *dest_format, - size_t *dest_size_r, - GError **error_r); - -#endif diff --git a/src/pcm_dither.c b/src/pcm_dither.c deleted file mode 100644 index 4811946c8..000000000 --- a/src/pcm_dither.c +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_dither.h" -#include "pcm_prng.h" - -static int16_t -pcm_dither_sample_24_to_16(int32_t sample, struct pcm_dither *dither) -{ - int32_t output, rnd; - - enum { - from_bits = 24, - to_bits = 16, - scale_bits = from_bits - to_bits, - round = 1 << (scale_bits - 1), - mask = (1 << scale_bits) - 1, - ONE = 1 << (from_bits - 1), - MIN = -ONE, - MAX = ONE - 1 - }; - - sample += dither->error[0] - dither->error[1] + dither->error[2]; - - dither->error[2] = dither->error[1]; - dither->error[1] = dither->error[0] / 2; - - /* round */ - output = sample + round; - - rnd = pcm_prng(dither->random); - output += (rnd & mask) - (dither->random & mask); - - dither->random = rnd; - - /* clip */ - if (output > MAX) { - output = MAX; - - if (sample > MAX) - sample = MAX; - } else if (output < MIN) { - output = MIN; - - if (sample < MIN) - sample = MIN; - } - - output &= ~mask; - - dither->error[0] = sample - output; - - return (int16_t)(output >> scale_bits); -} - -void -pcm_dither_24_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, const int32_t *src_end) -{ - while (src < src_end) - *dest++ = pcm_dither_sample_24_to_16(*src++, dither); -} - -static int16_t -pcm_dither_sample_32_to_16(int32_t sample, struct pcm_dither *dither) -{ - return pcm_dither_sample_24_to_16(sample >> 8, dither); -} - -void -pcm_dither_32_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, const int32_t *src_end) -{ - while (src < src_end) - *dest++ = pcm_dither_sample_32_to_16(*src++, dither); -} diff --git a/src/pcm_dither.h b/src/pcm_dither.h deleted file mode 100644 index 046dea21e..000000000 --- a/src/pcm_dither.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PCM_DITHER_H -#define MPD_PCM_DITHER_H - -#include <stdint.h> - -struct pcm_dither { - int32_t error[3]; - int32_t random; -}; - -static inline void -pcm_dither_24_init(struct pcm_dither *dither) -{ - dither->error[0] = dither->error[1] = dither->error[2] = 0; - dither->random = 0; -} - -void -pcm_dither_24_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, const int32_t *src_end); - -void -pcm_dither_32_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, const int32_t *src_end); - -#endif diff --git a/src/pcm_export.h b/src/pcm_export.h index a7e7c3f68..005db48e0 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_format.c b/src/pcm_format.c deleted file mode 100644 index d3ea3acb0..000000000 --- a/src/pcm_format.c +++ /dev/null @@ -1,487 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_format.h" -#include "pcm_dither.h" -#include "pcm_buffer.h" -#include "pcm_pack.h" -#include "pcm_utils.h" - -static void -pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end) -{ - while (in < in_end) { - *out++ = *in++ << 8; - } -} - -static void -pcm_convert_24_to_16(struct pcm_dither *dither, - int16_t *out, const int32_t *in, const int32_t *in_end) -{ - pcm_dither_24_to_16(dither, out, in, in_end); -} - -static void -pcm_convert_32_to_16(struct pcm_dither *dither, - int16_t *out, const int32_t *in, const int32_t *in_end) -{ - pcm_dither_32_to_16(dither, out, in, in_end); -} - -static void -pcm_convert_float_to_16(int16_t *out, const float *in, const float *in_end) -{ - const unsigned OUT_BITS = 16; - const float factor = 1 << (OUT_BITS - 1); - - while (in < in_end) { - int sample = *in++ * factor; - *out++ = pcm_clamp_16(sample); - } -} - -static int16_t * -pcm_allocate_8_to_16(struct pcm_buffer *buffer, - const int8_t *src, size_t src_size, size_t *dest_size_r) -{ - int16_t *dest; - *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_8_to_16(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static int16_t * -pcm_allocate_24p32_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, - const int32_t *src, size_t src_size, - size_t *dest_size_r) -{ - int16_t *dest; - *dest_size_r = src_size / 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_24_to_16(dither, dest, src, - pcm_end_pointer(src, src_size)); - return dest; -} - -static int16_t * -pcm_allocate_32_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, - const int32_t *src, size_t src_size, - size_t *dest_size_r) -{ - int16_t *dest; - *dest_size_r = src_size / 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_32_to_16(dither, dest, src, - pcm_end_pointer(src, src_size)); - return dest; -} - -static int16_t * -pcm_allocate_float_to_16(struct pcm_buffer *buffer, - const float *src, size_t src_size, - size_t *dest_size_r) -{ - int16_t *dest; - *dest_size_r = src_size / 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_float_to_16(dest, src, - pcm_end_pointer(src, src_size)); - return dest; -} - -const int16_t * -pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r) -{ - assert(src_size % sample_format_size(src_format) == 0); - - switch (src_format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - break; - - case SAMPLE_FORMAT_S8: - return pcm_allocate_8_to_16(buffer, - src, src_size, dest_size_r); - - case SAMPLE_FORMAT_S16: - *dest_size_r = src_size; - return src; - - case SAMPLE_FORMAT_S24_P32: - return pcm_allocate_24p32_to_16(buffer, dither, src, src_size, - dest_size_r); - - case SAMPLE_FORMAT_S32: - return pcm_allocate_32_to_16(buffer, dither, src, src_size, - dest_size_r); - - case SAMPLE_FORMAT_FLOAT: - return pcm_allocate_float_to_16(buffer, src, src_size, - dest_size_r); - } - - return NULL; -} - -static void -pcm_convert_8_to_24(int32_t *out, const int8_t *in, const int8_t *in_end) -{ - while (in < in_end) - *out++ = *in++ << 16; -} - -static void -pcm_convert_16_to_24(int32_t *out, const int16_t *in, const int16_t *in_end) -{ - while (in < in_end) - *out++ = *in++ << 8; -} - -static void -pcm_convert_32_to_24(int32_t *restrict out, - const int32_t *restrict in, - const int32_t *restrict in_end) -{ - while (in < in_end) - *out++ = *in++ >> 8; -} - -static void -pcm_convert_float_to_24(int32_t *out, const float *in, const float *in_end) -{ - const unsigned OUT_BITS = 24; - const float factor = 1 << (OUT_BITS - 1); - - while (in < in_end) { - int sample = *in++ * factor; - *out++ = pcm_clamp_24(sample); - } -} - -static int32_t * -pcm_allocate_8_to_24(struct pcm_buffer *buffer, - const int8_t *src, size_t src_size, size_t *dest_size_r) -{ - int32_t *dest; - *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_8_to_24(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static int32_t * -pcm_allocate_16_to_24(struct pcm_buffer *buffer, - const int16_t *src, size_t src_size, size_t *dest_size_r) -{ - int32_t *dest; - *dest_size_r = src_size * 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_16_to_24(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static int32_t * -pcm_allocate_32_to_24(struct pcm_buffer *buffer, - const int32_t *src, size_t src_size, size_t *dest_size_r) -{ - *dest_size_r = src_size; - int32_t *dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_32_to_24(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static int32_t * -pcm_allocate_float_to_24(struct pcm_buffer *buffer, - const float *src, size_t src_size, - size_t *dest_size_r) -{ - *dest_size_r = src_size; - int32_t *dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_float_to_24(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -const int32_t * -pcm_convert_to_24(struct pcm_buffer *buffer, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r) -{ - assert(src_size % sample_format_size(src_format) == 0); - - switch (src_format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - break; - - case SAMPLE_FORMAT_S8: - return pcm_allocate_8_to_24(buffer, - src, src_size, dest_size_r); - - case SAMPLE_FORMAT_S16: - return pcm_allocate_16_to_24(buffer, - src, src_size, dest_size_r); - - case SAMPLE_FORMAT_S24_P32: - *dest_size_r = src_size; - return src; - - case SAMPLE_FORMAT_S32: - return pcm_allocate_32_to_24(buffer, src, src_size, - dest_size_r); - - case SAMPLE_FORMAT_FLOAT: - return pcm_allocate_float_to_24(buffer, src, src_size, - dest_size_r); - } - - return NULL; -} - -static void -pcm_convert_8_to_32(int32_t *out, const int8_t *in, const int8_t *in_end) -{ - while (in < in_end) - *out++ = *in++ << 24; -} - -static void -pcm_convert_16_to_32(int32_t *out, const int16_t *in, const int16_t *in_end) -{ - while (in < in_end) - *out++ = *in++ << 16; -} - -static void -pcm_convert_24_to_32(int32_t *restrict out, - const int32_t *restrict in, - const int32_t *restrict in_end) -{ - while (in < in_end) - *out++ = *in++ << 8; -} - -static int32_t * -pcm_allocate_8_to_32(struct pcm_buffer *buffer, - const int8_t *src, size_t src_size, size_t *dest_size_r) -{ - int32_t *dest; - *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_8_to_32(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static int32_t * -pcm_allocate_16_to_32(struct pcm_buffer *buffer, - const int16_t *src, size_t src_size, size_t *dest_size_r) -{ - int32_t *dest; - *dest_size_r = src_size * 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_16_to_32(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static int32_t * -pcm_allocate_24p32_to_32(struct pcm_buffer *buffer, - const int32_t *src, size_t src_size, - size_t *dest_size_r) -{ - *dest_size_r = src_size; - int32_t *dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_24_to_32(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static int32_t * -pcm_allocate_float_to_32(struct pcm_buffer *buffer, - const float *src, size_t src_size, - size_t *dest_size_r) -{ - /* convert to S24_P32 first */ - int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size, - dest_size_r); - - /* convert to 32 bit in-place */ - pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r)); - return dest; -} - -const int32_t * -pcm_convert_to_32(struct pcm_buffer *buffer, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r) -{ - assert(src_size % sample_format_size(src_format) == 0); - - switch (src_format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - break; - - case SAMPLE_FORMAT_S8: - return pcm_allocate_8_to_32(buffer, src, src_size, - dest_size_r); - - case SAMPLE_FORMAT_S16: - return pcm_allocate_16_to_32(buffer, src, src_size, - dest_size_r); - - case SAMPLE_FORMAT_S24_P32: - return pcm_allocate_24p32_to_32(buffer, src, src_size, - dest_size_r); - - case SAMPLE_FORMAT_S32: - *dest_size_r = src_size; - return src; - - case SAMPLE_FORMAT_FLOAT: - return pcm_allocate_float_to_32(buffer, src, src_size, - dest_size_r); - } - - return NULL; -} - -static void -pcm_convert_8_to_float(float *out, const int8_t *in, const int8_t *in_end) -{ - enum { in_bits = sizeof(*in) * 8 }; - static const float factor = 2.0f / (1 << in_bits); - while (in < in_end) - *out++ = (float)*in++ * factor; -} - -static void -pcm_convert_16_to_float(float *out, const int16_t *in, const int16_t *in_end) -{ - enum { in_bits = sizeof(*in) * 8 }; - static const float factor = 2.0f / (1 << in_bits); - while (in < in_end) - *out++ = (float)*in++ * factor; -} - -static void -pcm_convert_24_to_float(float *out, const int32_t *in, const int32_t *in_end) -{ - enum { in_bits = 24 }; - static const float factor = 2.0f / (1 << in_bits); - while (in < in_end) - *out++ = (float)*in++ * factor; -} - -static void -pcm_convert_32_to_float(float *out, const int32_t *in, const int32_t *in_end) -{ - enum { in_bits = sizeof(*in) * 8 }; - static const float factor = 0.5f / (1 << (in_bits - 2)); - while (in < in_end) - *out++ = (float)*in++ * factor; -} - -static float * -pcm_allocate_8_to_float(struct pcm_buffer *buffer, - const int8_t *src, size_t src_size, - size_t *dest_size_r) -{ - float *dest; - *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_8_to_float(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static float * -pcm_allocate_16_to_float(struct pcm_buffer *buffer, - const int16_t *src, size_t src_size, - size_t *dest_size_r) -{ - float *dest; - *dest_size_r = src_size * 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_16_to_float(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static float * -pcm_allocate_24p32_to_float(struct pcm_buffer *buffer, - const int32_t *src, size_t src_size, - size_t *dest_size_r) -{ - *dest_size_r = src_size; - float *dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_24_to_float(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static float * -pcm_allocate_32_to_float(struct pcm_buffer *buffer, - const int32_t *src, size_t src_size, - size_t *dest_size_r) -{ - *dest_size_r = src_size; - float *dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_32_to_float(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -const float * -pcm_convert_to_float(struct pcm_buffer *buffer, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r) -{ - switch (src_format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - break; - - case SAMPLE_FORMAT_S8: - return pcm_allocate_8_to_float(buffer, - src, src_size, dest_size_r); - - case SAMPLE_FORMAT_S16: - return pcm_allocate_16_to_float(buffer, - src, src_size, dest_size_r); - - case SAMPLE_FORMAT_S24_P32: - return pcm_allocate_24p32_to_float(buffer, - src, src_size, dest_size_r); - - case SAMPLE_FORMAT_S32: - return pcm_allocate_32_to_float(buffer, - src, src_size, dest_size_r); - - case SAMPLE_FORMAT_FLOAT: - *dest_size_r = src_size; - return src; - } - - return NULL; -} diff --git a/src/pcm_format.h b/src/pcm_format.h deleted file mode 100644 index 48bcd0662..000000000 --- a/src/pcm_format.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PCM_FORMAT_H -#define PCM_FORMAT_H - -#include "audio_format.h" - -#include <stdint.h> -#include <stddef.h> - -struct pcm_buffer; -struct pcm_dither; - -/** - * Converts PCM samples to 16 bit. If the source format is 24 bit, - * then dithering is applied. - * - * @param buffer a pcm_buffer object - * @param dither a pcm_dither object for 24-to-16 conversion - * @param bits the number of in the source buffer - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const int16_t * -pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r); - -/** - * Converts PCM samples to 24 bit (32 bit alignment). - * - * @param buffer a pcm_buffer object - * @param bits the number of in the source buffer - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const int32_t * -pcm_convert_to_24(struct pcm_buffer *buffer, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r); - -/** - * Converts PCM samples to 32 bit. - * - * @param buffer a pcm_buffer object - * @param bits the number of in the source buffer - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const int32_t * -pcm_convert_to_32(struct pcm_buffer *buffer, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r); - -/** - * Converts PCM samples to 32 bit floating point. - * - * @param buffer a pcm_buffer object - * @param bits the number of in the source buffer - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const float * -pcm_convert_to_float(struct pcm_buffer *buffer, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r); - -#endif diff --git a/src/pcm_mix.c b/src/pcm_mix.c deleted file mode 100644 index 6c6d1b4ab..000000000 --- a/src/pcm_mix.c +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_mix.h" -#include "pcm_volume.h" -#include "pcm_utils.h" -#include "audio_format.h" - -#include <glib.h> - -#include <math.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pcm" - -static void -pcm_add_vol_8(int8_t *buffer1, const int8_t *buffer2, - unsigned num_samples, int volume1, int volume2) -{ - while (num_samples > 0) { - int32_t sample1 = *buffer1; - int32_t sample2 = *buffer2++; - - sample1 = ((sample1 * volume1 + sample2 * volume2) + - pcm_volume_dither() + PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer1++ = pcm_range(sample1, 8); - --num_samples; - } -} - -static void -pcm_add_vol_16(int16_t *buffer1, const int16_t *buffer2, - unsigned num_samples, int volume1, int volume2) -{ - while (num_samples > 0) { - int32_t sample1 = *buffer1; - int32_t sample2 = *buffer2++; - - sample1 = ((sample1 * volume1 + sample2 * volume2) + - pcm_volume_dither() + PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer1++ = pcm_range(sample1, 16); - --num_samples; - } -} - -static void -pcm_add_vol_24(int32_t *buffer1, const int32_t *buffer2, - unsigned num_samples, unsigned volume1, unsigned volume2) -{ - while (num_samples > 0) { - int64_t sample1 = *buffer1; - int64_t sample2 = *buffer2++; - - sample1 = ((sample1 * volume1 + sample2 * volume2) + - pcm_volume_dither() + PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer1++ = pcm_range(sample1, 24); - --num_samples; - } -} - -static void -pcm_add_vol_32(int32_t *buffer1, const int32_t *buffer2, - unsigned num_samples, unsigned volume1, unsigned volume2) -{ - while (num_samples > 0) { - int64_t sample1 = *buffer1; - int64_t sample2 = *buffer2++; - - sample1 = ((sample1 * volume1 + sample2 * volume2) + - pcm_volume_dither() + PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer1++ = pcm_range_64(sample1, 32); - --num_samples; - } -} - -static void -pcm_add_vol_float(float *buffer1, const float *buffer2, - unsigned num_samples, float volume1, float volume2) -{ - while (num_samples > 0) { - float sample1 = *buffer1; - float sample2 = *buffer2++; - - sample1 = (sample1 * volume1 + sample2 * volume2); - *buffer1++ = sample1; - --num_samples; - } -} - -static bool -pcm_add_vol(void *buffer1, const void *buffer2, size_t size, - int vol1, int vol2, - enum sample_format format) -{ - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - /* not implemented */ - return false; - - case SAMPLE_FORMAT_S8: - pcm_add_vol_8((int8_t *)buffer1, (const int8_t *)buffer2, - size, vol1, vol2); - return true; - - case SAMPLE_FORMAT_S16: - pcm_add_vol_16((int16_t *)buffer1, (const int16_t *)buffer2, - size / 2, vol1, vol2); - return true; - - case SAMPLE_FORMAT_S24_P32: - pcm_add_vol_24((int32_t *)buffer1, (const int32_t *)buffer2, - size / 4, vol1, vol2); - return true; - - case SAMPLE_FORMAT_S32: - pcm_add_vol_32((int32_t *)buffer1, (const int32_t *)buffer2, - size / 4, vol1, vol2); - return true; - - case SAMPLE_FORMAT_FLOAT: - pcm_add_vol_float(buffer1, buffer2, size / 4, - pcm_volume_to_float(vol1), - pcm_volume_to_float(vol2)); - return true; - } - - /* unreachable */ - assert(false); - return false; -} - -static void -pcm_add_8(int8_t *buffer1, const int8_t *buffer2, unsigned num_samples) -{ - while (num_samples > 0) { - int32_t sample1 = *buffer1; - int32_t sample2 = *buffer2++; - - sample1 += sample2; - - *buffer1++ = pcm_range(sample1, 8); - --num_samples; - } -} - -static void -pcm_add_16(int16_t *buffer1, const int16_t *buffer2, unsigned num_samples) -{ - while (num_samples > 0) { - int32_t sample1 = *buffer1; - int32_t sample2 = *buffer2++; - - sample1 += sample2; - - *buffer1++ = pcm_range(sample1, 16); - --num_samples; - } -} - -static void -pcm_add_24(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples) -{ - while (num_samples > 0) { - int64_t sample1 = *buffer1; - int64_t sample2 = *buffer2++; - - sample1 += sample2; - - *buffer1++ = pcm_range(sample1, 24); - --num_samples; - } -} - -static void -pcm_add_32(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples) -{ - while (num_samples > 0) { - int64_t sample1 = *buffer1; - int64_t sample2 = *buffer2++; - - sample1 += sample2; - - *buffer1++ = pcm_range_64(sample1, 32); - --num_samples; - } -} - -static void -pcm_add_float(float *buffer1, const float *buffer2, unsigned num_samples) -{ - while (num_samples > 0) { - float sample1 = *buffer1; - float sample2 = *buffer2++; - *buffer1++ = sample1 + sample2; - --num_samples; - } -} - -static bool -pcm_add(void *buffer1, const void *buffer2, size_t size, - enum sample_format format) -{ - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - /* not implemented */ - return false; - - case SAMPLE_FORMAT_S8: - pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2, size); - return true; - - case SAMPLE_FORMAT_S16: - pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2, size / 2); - return true; - - case SAMPLE_FORMAT_S24_P32: - pcm_add_24((int32_t *)buffer1, (const int32_t *)buffer2, size / 4); - return true; - - case SAMPLE_FORMAT_S32: - pcm_add_32((int32_t *)buffer1, (const int32_t *)buffer2, size / 4); - return true; - - case SAMPLE_FORMAT_FLOAT: - pcm_add_float(buffer1, buffer2, size / 4); - return true; - } - - /* unreachable */ - assert(false); - return false; -} - -bool -pcm_mix(void *buffer1, const void *buffer2, size_t size, - enum sample_format format, float portion1) -{ - int vol1; - float s; - - /* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses NaN - * to signal mixing rather than fading */ - if (isnan(portion1)) - return pcm_add(buffer1, buffer2, size, format); - - s = sin(M_PI_2 * portion1); - s *= s; - - vol1 = s * PCM_VOLUME_1 + 0.5; - vol1 = vol1 > PCM_VOLUME_1 ? PCM_VOLUME_1 : (vol1 < 0 ? 0 : vol1); - - return pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format); -} diff --git a/src/pcm_mix.h b/src/pcm_mix.h deleted file mode 100644 index 0e58d01ee..000000000 --- a/src/pcm_mix.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PCM_MIX_H -#define PCM_MIX_H - -#include "audio_format.h" - -#include <stdbool.h> -#include <stddef.h> - -/* - * Linearly mixes two PCM buffers. Both must have the same length and - * the same audio format. The formula is: - * - * s1 := s1 * portion1 + s2 * (1 - portion1) - * - * @param buffer1 the first PCM buffer, and the destination buffer - * @param buffer2 the second PCM buffer - * @param size the size of both buffers in bytes - * @param format the sample format of both buffers - * @param portion1 a number between 0.0 and 1.0 specifying the portion - * of the first buffer in the mix; portion2 = (1.0 - portion1). The value - * NaN is used by the MixRamp code to specify that simple addition is required. - * - * @return true on success, false if the format is not supported - */ -G_GNUC_WARN_UNUSED_RESULT -bool -pcm_mix(void *buffer1, const void *buffer2, size_t size, - enum sample_format format, float portion1); - -#endif diff --git a/src/pcm_prng.h b/src/pcm_prng.h deleted file mode 100644 index 457ba4b66..000000000 --- a/src/pcm_prng.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PCM_PRNG_H -#define PCM_PRNG_H - -/** - * A very simple linear congruential PRNG. It's good enough for PCM - * dithering. - */ -static unsigned long -pcm_prng(unsigned long state) -{ - return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; -} - -#endif diff --git a/src/pcm_utils.h b/src/pcm_utils.h deleted file mode 100644 index 4ad896570..000000000 --- a/src/pcm_utils.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PCM_UTILS_H -#define MPD_PCM_UTILS_H - -#include <glib.h> - -#include <stdint.h> - -/** - * Add a byte count to the specified pointer. This is a utility - * function to convert a source pointer and a byte count to an "end" - * pointer for use in loops. - */ -static inline const void * -pcm_end_pointer(const void *p, size_t size) -{ - return (const char *)p + size; -} - -/** - * Check if the value is within the range of the provided bit size, - * and caps it if necessary. - */ -static inline int32_t -pcm_range(int32_t sample, unsigned bits) -{ - if (G_UNLIKELY(sample < (-1 << (bits - 1)))) - return -1 << (bits - 1); - if (G_UNLIKELY(sample >= (1 << (bits - 1)))) - return (1 << (bits - 1)) - 1; - return sample; -} - -/** - * Check if the value is within the range of the provided bit size, - * and caps it if necessary. - */ -static inline int64_t -pcm_range_64(int64_t sample, unsigned bits) -{ - if (G_UNLIKELY(sample < ((int64_t)-1 << (bits - 1)))) - return (int64_t)-1 << (bits - 1); - if (G_UNLIKELY(sample >= ((int64_t)1 << (bits - 1)))) - return ((int64_t)1 << (bits - 1)) - 1; - return sample; -} - -G_GNUC_CONST -static inline int16_t -pcm_clamp_16(int x) -{ - static const int32_t MIN_VALUE = G_MININT16; - static const int32_t MAX_VALUE = G_MAXINT16; - - if (G_UNLIKELY(x < MIN_VALUE)) - return MIN_VALUE; - if (G_UNLIKELY(x > MAX_VALUE)) - return MAX_VALUE; - return x; -} - -G_GNUC_CONST -static inline int32_t -pcm_clamp_24(int x) -{ - static const int32_t MIN_VALUE = -(1 << 23); - static const int32_t MAX_VALUE = (1 << 23) - 1; - - if (G_UNLIKELY(x < MIN_VALUE)) - return MIN_VALUE; - if (G_UNLIKELY(x > MAX_VALUE)) - return MAX_VALUE; - return x; -} - -#endif diff --git a/src/pcm_volume.c b/src/pcm_volume.c deleted file mode 100644 index 49c86026f..000000000 --- a/src/pcm_volume.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_volume.h" -#include "pcm_utils.h" -#include "audio_format.h" - -#include <glib.h> - -#include <stdint.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pcm_volume" - -static void -pcm_volume_change_8(int8_t *buffer, const int8_t *end, int volume) -{ - while (buffer < end) { - int32_t sample = *buffer; - - sample = (sample * volume + pcm_volume_dither() + - PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer++ = pcm_range(sample, 8); - } -} - -static void -pcm_volume_change_16(int16_t *buffer, const int16_t *end, int volume) -{ - while (buffer < end) { - int32_t sample = *buffer; - - sample = (sample * volume + pcm_volume_dither() + - PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer++ = pcm_range(sample, 16); - } -} - -#ifdef __i386__ -/** - * Optimized volume function for i386. Use the EDX:EAX 2*32 bit - * multiplication result instead of emulating 64 bit multiplication. - */ -static inline int32_t -pcm_volume_sample_24(int32_t sample, int32_t volume, G_GNUC_UNUSED int32_t dither) -{ - int32_t result; - - asm(/* edx:eax = sample * volume */ - "imul %2\n" - - /* "add %3, %1\n" dithering disabled for now, because we - have no overflow check - is dithering really important - here? */ - - /* eax = edx:eax / PCM_VOLUME_1 */ - "sal $22, %%edx\n" - "shr $10, %1\n" - "or %%edx, %1\n" - - : "=a"(result) - : "0"(sample), "r"(volume) /* , "r"(dither) */ - : "edx" - ); - - return result; -} -#endif - -static void -pcm_volume_change_24(int32_t *buffer, const int32_t *end, int volume) -{ - while (buffer < end) { -#ifdef __i386__ - /* assembly version for i386 */ - int32_t sample = *buffer; - - sample = pcm_volume_sample_24(sample, volume, - pcm_volume_dither()); -#else - /* portable version */ - int64_t sample = *buffer; - - sample = (sample * volume + pcm_volume_dither() + - PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; -#endif - *buffer++ = pcm_range(sample, 24); - } -} - -static void -pcm_volume_change_32(int32_t *buffer, const int32_t *end, int volume) -{ - while (buffer < end) { -#ifdef __i386__ - /* assembly version for i386 */ - int32_t sample = *buffer; - - *buffer++ = pcm_volume_sample_24(sample, volume, 0); -#else - /* portable version */ - int64_t sample = *buffer; - - sample = (sample * volume + pcm_volume_dither() + - PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - *buffer++ = pcm_range_64(sample, 32); -#endif - } -} - -static void -pcm_volume_change_float(float *buffer, const float *end, float volume) -{ - while (buffer < end) { - float sample = *buffer; - sample *= volume; - *buffer++ = sample; - } -} - -bool -pcm_volume(void *buffer, size_t length, - enum sample_format format, - int volume) -{ - if (volume == PCM_VOLUME_1) - return true; - - if (volume <= 0) { - memset(buffer, 0, length); - return true; - } - - const void *end = pcm_end_pointer(buffer, length); - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - /* not implemented */ - return false; - - case SAMPLE_FORMAT_S8: - pcm_volume_change_8(buffer, end, volume); - return true; - - case SAMPLE_FORMAT_S16: - pcm_volume_change_16(buffer, end, volume); - return true; - - case SAMPLE_FORMAT_S24_P32: - pcm_volume_change_24(buffer, end, volume); - return true; - - case SAMPLE_FORMAT_S32: - pcm_volume_change_32(buffer, end, volume); - return true; - - case SAMPLE_FORMAT_FLOAT: - pcm_volume_change_float(buffer, end, - pcm_volume_to_float(volume)); - return true; - } - - /* unreachable */ - assert(false); - return false; -} diff --git a/src/pcm_volume.h b/src/pcm_volume.h deleted file mode 100644 index 64e3c7641..000000000 --- a/src/pcm_volume.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PCM_VOLUME_H -#define PCM_VOLUME_H - -#include "pcm_prng.h" -#include "audio_format.h" - -#include <stdint.h> -#include <stdbool.h> - -enum { - /** this value means "100% volume" */ - PCM_VOLUME_1 = 1024, -}; - -struct audio_format; - -/** - * Converts a float value (0.0 = silence, 1.0 = 100% volume) to an - * integer volume value (1000 = 100%). - */ -static inline int -pcm_float_to_volume(float volume) -{ - return volume * PCM_VOLUME_1 + 0.5; -} - -static inline float -pcm_volume_to_float(int volume) -{ - return (float)volume / (float)PCM_VOLUME_1; -} - -/** - * Returns the next volume dithering number, between -511 and +511. - * This number is taken from a global PRNG, see pcm_prng(). - */ -static inline int -pcm_volume_dither(void) -{ - static unsigned long state; - uint32_t r; - - r = state = pcm_prng(state); - - return (r & 511) - ((r >> 9) & 511); -} - -/** - * Adjust the volume of the specified PCM buffer. - * - * @param buffer the PCM buffer - * @param length the length of the PCM buffer - * @param format the sample format of the PCM buffer - * @param volume the volume between 0 and #PCM_VOLUME_1 - * @return true on success, false if the audio format is not supported - */ -bool -pcm_volume(void *buffer, size_t length, - enum sample_format format, - int volume); - -#endif diff --git a/src/permission.c b/src/permission.c deleted file mode 100644 index cd52b9c86..000000000 --- a/src/permission.c +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "permission.h" -#include "conf.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <stdbool.h> -#include <string.h> - -#define PERMISSION_PASSWORD_CHAR '@' -#define PERMISSION_SEPERATOR "," - -#define PERMISSION_READ_STRING "read" -#define PERMISSION_ADD_STRING "add" -#define PERMISSION_CONTROL_STRING "control" -#define PERMISSION_ADMIN_STRING "admin" - -static GHashTable *permission_passwords; - -static unsigned permission_default; - -static unsigned parsePermissions(const char *string) -{ - unsigned permission = 0; - gchar **tokens; - - if (!string) - return 0; - - tokens = g_strsplit(string, PERMISSION_SEPERATOR, 0); - for (unsigned i = 0; tokens[i] != NULL; ++i) { - char *temp = tokens[i]; - - if (strcmp(temp, PERMISSION_READ_STRING) == 0) { - permission |= PERMISSION_READ; - } else if (strcmp(temp, PERMISSION_ADD_STRING) == 0) { - permission |= PERMISSION_ADD; - } else if (strcmp(temp, PERMISSION_CONTROL_STRING) == 0) { - permission |= PERMISSION_CONTROL; - } else if (strcmp(temp, PERMISSION_ADMIN_STRING) == 0) { - permission |= PERMISSION_ADMIN; - } else { - MPD_ERROR("unknown permission \"%s\"", temp); - } - } - - g_strfreev(tokens); - - return permission; -} - -void initPermissions(void) -{ - char *password; - unsigned permission; - const struct config_param *param; - - permission_passwords = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, NULL); - - permission_default = PERMISSION_READ | PERMISSION_ADD | - PERMISSION_CONTROL | PERMISSION_ADMIN; - - param = config_get_next_param(CONF_PASSWORD, NULL); - - if (param) { - permission_default = 0; - - do { - const char *separator = - strchr(param->value, PERMISSION_PASSWORD_CHAR); - - if (separator == NULL) - MPD_ERROR("\"%c\" not found in password string " - "\"%s\", line %i", - PERMISSION_PASSWORD_CHAR, - param->value, param->line); - - password = g_strndup(param->value, - separator - param->value); - - permission = parsePermissions(separator + 1); - - g_hash_table_replace(permission_passwords, - password, - GINT_TO_POINTER(permission)); - } while ((param = config_get_next_param(CONF_PASSWORD, param))); - } - - param = config_get_param(CONF_DEFAULT_PERMS); - - if (param) - permission_default = parsePermissions(param->value); -} - -int getPermissionFromPassword(char const* password, unsigned* permission) -{ - bool found; - gpointer key, value; - - found = g_hash_table_lookup_extended(permission_passwords, - password, &key, &value); - if (!found) - return -1; - - *permission = GPOINTER_TO_INT(value); - return 0; -} - -void finishPermissions(void) -{ - g_hash_table_destroy(permission_passwords); -} - -unsigned getDefaultPermissions(void) -{ - return permission_default; -} diff --git a/src/permission.h b/src/permission.h deleted file mode 100644 index 6c3771362..000000000 --- a/src/permission.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PERMISSION_H -#define MPD_PERMISSION_H - -#define PERMISSION_NONE 0 -#define PERMISSION_READ 1 -#define PERMISSION_ADD 2 -#define PERMISSION_CONTROL 4 -#define PERMISSION_ADMIN 8 - - -int getPermissionFromPassword(char const* password, unsigned* permission); - -void finishPermissions(void); - -unsigned getDefaultPermissions(void); - -void initPermissions(void); - -#endif diff --git a/src/pipe.c b/src/pipe.c deleted file mode 100644 index d8131432f..000000000 --- a/src/pipe.c +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pipe.h" -#include "buffer.h" -#include "chunk.h" - -#include <glib.h> - -#include <assert.h> - -struct music_pipe { - /** the first chunk */ - struct music_chunk *head; - - /** a pointer to the tail of the chunk */ - struct music_chunk **tail_r; - - /** the current number of chunks */ - unsigned size; - - /** a mutex which protects #head and #tail_r */ - GMutex *mutex; - -#ifndef NDEBUG - struct audio_format audio_format; -#endif -}; - -struct music_pipe * -music_pipe_new(void) -{ - struct music_pipe *mp = g_new(struct music_pipe, 1); - - mp->head = NULL; - mp->tail_r = &mp->head; - mp->size = 0; - mp->mutex = g_mutex_new(); - -#ifndef NDEBUG - audio_format_clear(&mp->audio_format); -#endif - - return mp; -} - -void -music_pipe_free(struct music_pipe *mp) -{ - assert(mp->head == NULL); - assert(mp->tail_r == &mp->head); - - g_mutex_free(mp->mutex); - g_free(mp); -} - -#ifndef NDEBUG - -bool -music_pipe_check_format(const struct music_pipe *pipe, - const struct audio_format *audio_format) -{ - assert(pipe != NULL); - assert(audio_format != NULL); - - return !audio_format_defined(&pipe->audio_format) || - audio_format_equals(&pipe->audio_format, audio_format); -} - -bool -music_pipe_contains(const struct music_pipe *mp, - const struct music_chunk *chunk) -{ - g_mutex_lock(mp->mutex); - - for (const struct music_chunk *i = mp->head; - i != NULL; i = i->next) { - if (i == chunk) { - g_mutex_unlock(mp->mutex); - return true; - } - } - - g_mutex_unlock(mp->mutex); - - return false; -} - -#endif - -const struct music_chunk * -music_pipe_peek(const struct music_pipe *mp) -{ - return mp->head; -} - -struct music_chunk * -music_pipe_shift(struct music_pipe *mp) -{ - struct music_chunk *chunk; - - g_mutex_lock(mp->mutex); - - chunk = mp->head; - if (chunk != NULL) { - assert(!music_chunk_is_empty(chunk)); - - mp->head = chunk->next; - --mp->size; - - if (mp->head == NULL) { - assert(mp->size == 0); - assert(mp->tail_r == &chunk->next); - - mp->tail_r = &mp->head; - } else { - assert(mp->size > 0); - assert(mp->tail_r != &chunk->next); - } - -#ifndef NDEBUG - /* poison the "next" reference */ - chunk->next = (void*)0x01010101; - - if (mp->size == 0) - audio_format_clear(&mp->audio_format); -#endif - } - - g_mutex_unlock(mp->mutex); - - return chunk; -} - -void -music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer) -{ - struct music_chunk *chunk; - - while ((chunk = music_pipe_shift(mp)) != NULL) - music_buffer_return(buffer, chunk); -} - -void -music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk) -{ - assert(!music_chunk_is_empty(chunk)); - assert(chunk->length == 0 || audio_format_valid(&chunk->audio_format)); - - g_mutex_lock(mp->mutex); - - assert(mp->size > 0 || !audio_format_defined(&mp->audio_format)); - assert(!audio_format_defined(&mp->audio_format) || - music_chunk_check_format(chunk, &mp->audio_format)); - -#ifndef NDEBUG - if (!audio_format_defined(&mp->audio_format) && chunk->length > 0) - mp->audio_format = chunk->audio_format; -#endif - - chunk->next = NULL; - *mp->tail_r = chunk; - mp->tail_r = &chunk->next; - - ++mp->size; - - g_mutex_unlock(mp->mutex); -} - -unsigned -music_pipe_size(const struct music_pipe *mp) -{ - g_mutex_lock(mp->mutex); - unsigned size = mp->size; - g_mutex_unlock(mp->mutex); - return size; -} diff --git a/src/pipe.h b/src/pipe.h deleted file mode 100644 index 84b9869e0..000000000 --- a/src/pipe.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PIPE_H -#define MPD_PIPE_H - -#include <glib.h> -#include <stdbool.h> - -#ifndef NDEBUG -struct audio_format; -#endif - -struct music_chunk; -struct music_buffer; - -/** - * A queue of #music_chunk objects. One party appends chunks at the - * tail, and the other consumes them from the head. - */ -struct music_pipe; - -/** - * Creates a new #music_pipe object. It is empty. - */ -G_GNUC_MALLOC -struct music_pipe * -music_pipe_new(void); - -/** - * Frees the object. It must be empty now. - */ -void -music_pipe_free(struct music_pipe *mp); - -#ifndef NDEBUG - -/** - * Checks if the audio format if the chunk is equal to the specified - * audio_format. - */ -bool -music_pipe_check_format(const struct music_pipe *pipe, - const struct audio_format *audio_format); - -/** - * Checks if the specified chunk is enqueued in the music pipe. - */ -bool -music_pipe_contains(const struct music_pipe *mp, - const struct music_chunk *chunk); - -#endif - -/** - * Returns the first #music_chunk from the pipe. Returns NULL if the - * pipe is empty. - */ -G_GNUC_PURE -const struct music_chunk * -music_pipe_peek(const struct music_pipe *mp); - -/** - * Removes the first chunk from the head, and returns it. - */ -struct music_chunk * -music_pipe_shift(struct music_pipe *mp); - -/** - * Clears the whole pipe and returns the chunks to the buffer. - * - * @param buffer the buffer object to return the chunks to - */ -void -music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer); - -/** - * Pushes a chunk to the tail of the pipe. - */ -void -music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk); - -/** - * Returns the number of chunks currently in this pipe. - */ -G_GNUC_PURE -unsigned -music_pipe_size(const struct music_pipe *mp); - -G_GNUC_PURE -static inline bool -music_pipe_empty(const struct music_pipe *mp) -{ - return music_pipe_size(mp) == 0; -} - -#endif diff --git a/src/player_control.c b/src/player_control.c deleted file mode 100644 index 90f616d77..000000000 --- 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 a77d31ec5..000000000 --- a/src/player_control.h +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYER_H -#define MPD_PLAYER_H - -#include "audio_format.h" - -#include <glib.h> - -#include <stdint.h> - -struct decoder_control; - -enum player_state { - PLAYER_STATE_STOP = 0, - PLAYER_STATE_PAUSE, - PLAYER_STATE_PLAY -}; - -enum player_command { - PLAYER_COMMAND_NONE = 0, - PLAYER_COMMAND_EXIT, - PLAYER_COMMAND_STOP, - PLAYER_COMMAND_PAUSE, - PLAYER_COMMAND_SEEK, - PLAYER_COMMAND_CLOSE_AUDIO, - - /** - * At least one audio_output.enabled flag has been modified; - * commit those changes to the output threads. - */ - PLAYER_COMMAND_UPDATE_AUDIO, - - /** player_control.next_song has been updated */ - PLAYER_COMMAND_QUEUE, - - /** - * cancel pre-decoding player_control.next_song; if the player - * has already started playing this song, it will completely - * stop - */ - PLAYER_COMMAND_CANCEL, - - /** - * Refresh status information in the #player_control struct, - * e.g. elapsed_time. - */ - PLAYER_COMMAND_REFRESH, -}; - -enum player_error { - PLAYER_ERROR_NOERROR = 0, - PLAYER_ERROR_FILE, - PLAYER_ERROR_AUDIO, - PLAYER_ERROR_SYSTEM, - PLAYER_ERROR_UNKTYPE, - PLAYER_ERROR_FILENOTFOUND, -}; - -struct player_status { - enum player_state state; - uint16_t bit_rate; - struct audio_format audio_format; - float total_time; - float elapsed_time; -}; - -struct player_control { - unsigned buffer_chunks; - - unsigned int buffered_before_play; - - /** the handle of the player thread, or NULL if the player - thread isn't running */ - GThread *thread; - - /** - * This lock protects #command, #state, #error. - */ - GMutex *mutex; - - /** - * Trigger this object after you have modified #command. - */ - GCond *cond; - - enum player_command command; - enum player_state state; - enum player_error error; - uint16_t bit_rate; - struct audio_format audio_format; - float total_time; - float elapsed_time; - struct song *next_song; - const struct song *errored_song; - double seek_where; - float cross_fade_seconds; - float mixramp_db; - float mixramp_delay_seconds; - double total_play_time; - - /** - * If this flag is set, then the player will be auto-paused at - * the end of the song, before the next song starts to play. - * - * This is a copy of the queue's "single" flag most of the - * time. - */ - bool border_pause; -}; - -struct player_control * -pc_new(unsigned buffer_chunks, unsigned buffered_before_play); - -void -pc_free(struct player_control *pc); - -/** - * Locks the #player_control object. - */ -static inline void -player_lock(struct player_control *pc) -{ - g_mutex_lock(pc->mutex); -} - -/** - * Unlocks the #player_control object. - */ -static inline void -player_unlock(struct player_control *pc) -{ - g_mutex_unlock(pc->mutex); -} - -/** - * Waits for a signal on the #player_control object. This function is - * only valid in the player thread. The object must be locked prior - * to calling this function. - */ -static inline void -player_wait(struct player_control *pc) -{ - g_cond_wait(pc->cond, pc->mutex); -} - -/** - * Waits for a signal on the #player_control object. This function is - * only valid in the player thread. The #decoder_control object must - * be locked prior to calling this function. - * - * Note the small difference to the player_wait() function! - */ -void -player_wait_decoder(struct player_control *pc, struct decoder_control *dc); - -/** - * Signals the #player_control object. The object should be locked - * prior to calling this function. - */ -static inline void -player_signal(struct player_control *pc) -{ - g_cond_signal(pc->cond); -} - -/** - * Signals the #player_control object. The object is temporarily - * locked by this function. - */ -static inline void -player_lock_signal(struct player_control *pc) -{ - player_lock(pc); - player_signal(pc); - player_unlock(pc); -} - -/** - * Call this function when the specified song pointer is about to be - * invalidated. This makes sure that player_control.errored_song does - * not point to an invalid pointer. - */ -void -pc_song_deleted(struct player_control *pc, const struct song *song); - -void -pc_play(struct player_control *pc, struct song *song); - -/** - * see PLAYER_COMMAND_CANCEL - */ -void -pc_cancel(struct player_control *pc); - -void -pc_set_pause(struct player_control *pc, bool pause_flag); - -void -pc_pause(struct player_control *pc); - -/** - * Set the player's #border_pause flag. - */ -void -pc_set_border_pause(struct player_control *pc, bool border_pause); - -void -pc_kill(struct player_control *pc); - -void -pc_get_status(struct player_control *pc, struct player_status *status); - -enum player_state -pc_get_state(struct player_control *pc); - -void -pc_clear_error(struct player_control *pc); - -/** - * Returns the human-readable message describing the last error during - * playback, NULL if no error occurred. The caller has to free the - * returned string. - */ -char * -pc_get_error_message(struct player_control *pc); - -enum player_error -pc_get_error(struct player_control *pc); - -void -pc_stop(struct player_control *pc); - -void -pc_update_audio(struct player_control *pc); - -void -pc_enqueue_song(struct player_control *pc, struct song *song); - -/** - * Makes the player thread seek the specified song to a position. - * - * @return true on success, false on failure (e.g. if MPD isn't - * playing currently) - */ -bool -pc_seek(struct player_control *pc, struct song *song, float seek_time); - -void -pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds); - -float -pc_get_cross_fade(const struct player_control *pc); - -void -pc_set_mixramp_db(struct player_control *pc, float mixramp_db); - -float -pc_get_mixramp_db(const struct player_control *pc); - -void -pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds); - -float -pc_get_mixramp_delay(const struct player_control *pc); - -double -pc_get_total_play_time(const struct player_control *pc); - -#endif diff --git a/src/player_thread.c b/src/player_thread.c deleted file mode 100644 index 593788caf..000000000 --- a/src/player_thread.c +++ /dev/null @@ -1,1171 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "player_thread.h" -#include "player_control.h" -#include "decoder_control.h" -#include "decoder_thread.h" -#include "output_all.h" -#include "pcm_volume.h" -#include "path.h" -#include "event_pipe.h" -#include "crossfade.h" -#include "song.h" -#include "tag.h" -#include "pipe.h" -#include "chunk.h" -#include "idle.h" -#include "main.h" -#include "buffer.h" -#include "mpd_error.h" - -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "player_thread" - -enum xfade_state { - XFADE_DISABLED = -1, - XFADE_UNKNOWN = 0, - XFADE_ENABLED = 1 -}; - -struct player { - struct player_control *pc; - - struct decoder_control *dc; - - struct music_pipe *pipe; - - /** - * are we waiting for buffered_before_play? - */ - bool buffering; - - /** - * true if the decoder is starting and did not provide data - * yet - */ - bool decoder_starting; - - /** - * is the player paused? - */ - bool paused; - - /** - * is there a new song in pc.next_song? - */ - bool queued; - - /** - * Was any audio output opened successfully? It might have - * failed meanwhile, but was not explicitly closed by the - * player thread. When this flag is unset, some output - * methods must not be called. - */ - bool output_open; - - /** - * the song currently being played - */ - struct song *song; - - /** - * is cross fading enabled? - */ - enum xfade_state xfade; - - /** - * has cross-fading begun? - */ - bool cross_fading; - - /** - * The number of chunks used for crossfading. - */ - unsigned cross_fade_chunks; - - /** - * The tag of the "next" song during cross-fade. It is - * postponed, and sent to the output thread when the new song - * really begins. - */ - struct tag *cross_fade_tag; - - /** - * The current audio format for the audio outputs. - */ - struct audio_format play_audio_format; - - /** - * The time stamp of the chunk most recently sent to the - * output thread. This attribute is only used if - * audio_output_all_get_elapsed_time() didn't return a usable - * value; the output thread can estimate the elapsed time more - * precisely. - */ - float elapsed_time; -}; - -static struct music_buffer *player_buffer; - -static void -player_command_finished_locked(struct player_control *pc) -{ - assert(pc->command != PLAYER_COMMAND_NONE); - - pc->command = PLAYER_COMMAND_NONE; - g_cond_signal(main_cond); -} - -static void -player_command_finished(struct player_control *pc) -{ - player_lock(pc); - player_command_finished_locked(pc); - player_unlock(pc); -} - -/** - * Start the decoder. - * - * Player lock is not held. - */ -static void -player_dc_start(struct player *player, struct music_pipe *pipe) -{ - struct player_control *pc = player->pc; - struct decoder_control *dc = player->dc; - - assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); - assert(pc->next_song != NULL); - - unsigned start_ms = pc->next_song->start_ms; - if (pc->command == PLAYER_COMMAND_SEEK) - start_ms += (unsigned)(pc->seek_where * 1000); - - dc_start(dc, pc->next_song, - start_ms, pc->next_song->end_ms, - player_buffer, pipe); -} - -/** - * Is the decoder still busy on the same song as the player? - * - * Note: this function does not check if the decoder is already - * finished. - */ -static bool -player_dc_at_current_song(const struct player *player) -{ - assert(player != NULL); - assert(player->pipe != NULL); - - return player->dc->pipe == player->pipe; -} - -/** - * Returns true if the decoder is decoding the next song (or has begun - * decoding it, or has finished doing it), and the player hasn't - * switched to that song yet. - */ -static bool -player_dc_at_next_song(const struct player *player) -{ - return player->dc->pipe != NULL && !player_dc_at_current_song(player); -} - -/** - * Stop the decoder and clears (and frees) its music pipe. - * - * Player lock is not held. - */ -static void -player_dc_stop(struct player *player) -{ - struct decoder_control *dc = player->dc; - - dc_stop(dc); - - if (dc->pipe != NULL) { - /* clear and free the decoder pipe */ - - music_pipe_clear(dc->pipe, player_buffer); - - if (dc->pipe != player->pipe) - music_pipe_free(dc->pipe); - - dc->pipe = NULL; - } -} - -/** - * After the decoder has been started asynchronously, wait for the - * "START" command to finish. The decoder may not be initialized yet, - * i.e. there is no audio_format information yet. - * - * The player lock is not held. - */ -static bool -player_wait_for_decoder(struct player *player) -{ - struct player_control *pc = player->pc; - struct decoder_control *dc = player->dc; - - assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); - assert(pc->next_song != NULL); - - player->queued = false; - - if (decoder_lock_has_failed(dc)) { - player_lock(pc); - pc->errored_song = dc->song; - pc->error = PLAYER_ERROR_FILE; - pc->next_song = NULL; - player_unlock(pc); - - return false; - } - - player->song = pc->next_song; - player->elapsed_time = 0.0; - - /* set the "starting" flag, which will be cleared by - player_check_decoder_startup() */ - player->decoder_starting = true; - - player_lock(pc); - - /* update player_control's song information */ - pc->total_time = song_get_duration(pc->next_song); - pc->bit_rate = 0; - audio_format_clear(&pc->audio_format); - - /* clear the queued song */ - pc->next_song = NULL; - - player_unlock(pc); - - /* call syncPlaylistWithQueue() in the main thread */ - event_pipe_emit(PIPE_EVENT_PLAYLIST); - - return true; -} - -/** - * Returns the real duration of the song, comprising the duration - * indicated by the decoder plugin. - */ -static double -real_song_duration(const struct song *song, double decoder_duration) -{ - assert(song != NULL); - - if (decoder_duration <= 0.0) - /* the decoder plugin didn't provide information; fall - back to song_get_duration() */ - return song_get_duration(song); - - if (song->end_ms > 0 && song->end_ms / 1000.0 < decoder_duration) - return (song->end_ms - song->start_ms) / 1000.0; - - return decoder_duration - song->start_ms / 1000.0; -} - -/** - * Wrapper for audio_output_all_open(). Upon failure, it pauses the - * player. - * - * @return true on success - */ -static bool -player_open_output(struct player *player) -{ - struct player_control *pc = player->pc; - - assert(audio_format_defined(&player->play_audio_format)); - assert(pc->state == PLAYER_STATE_PLAY || - pc->state == PLAYER_STATE_PAUSE); - - if (audio_output_all_open(&player->play_audio_format, player_buffer)) { - player->output_open = true; - player->paused = false; - - player_lock(pc); - pc->state = PLAYER_STATE_PLAY; - player_unlock(pc); - - return true; - } else { - player->output_open = false; - - /* pause: the user may resume playback as soon as an - audio output becomes available */ - player->paused = true; - - player_lock(pc); - pc->error = PLAYER_ERROR_AUDIO; - pc->state = PLAYER_STATE_PAUSE; - player_unlock(pc); - - idle_add(IDLE_PLAYER); - - return false; - } -} - -/** - * The decoder has acknowledged the "START" command (see - * player_wait_for_decoder()). This function checks if the decoder - * initialization has completed yet. - * - * The player lock is not held. - */ -static bool -player_check_decoder_startup(struct player *player) -{ - struct player_control *pc = player->pc; - struct decoder_control *dc = player->dc; - - assert(player->decoder_starting); - - decoder_lock(dc); - - if (decoder_has_failed(dc)) { - /* the decoder failed */ - decoder_unlock(dc); - - player_lock(pc); - pc->errored_song = dc->song; - pc->error = PLAYER_ERROR_FILE; - player_unlock(pc); - - return false; - } else if (!decoder_is_starting(dc)) { - /* the decoder is ready and ok */ - - decoder_unlock(dc); - - if (player->output_open && - !audio_output_all_wait(pc, 1)) - /* the output devices havn't finished playing - all chunks yet - wait for that */ - return true; - - player_lock(pc); - pc->total_time = real_song_duration(dc->song, dc->total_time); - pc->audio_format = dc->in_audio_format; - player_unlock(pc); - - player->play_audio_format = dc->out_audio_format; - player->decoder_starting = false; - - if (!player->paused && !player_open_output(player)) { - char *uri = song_get_uri(dc->song); - g_warning("problems opening audio device " - "while playing \"%s\"", uri); - g_free(uri); - - return true; - } - - return true; - } else { - /* the decoder is not yet ready; wait - some more */ - player_wait_decoder(pc, dc); - decoder_unlock(dc); - - return true; - } -} - -/** - * Sends a chunk of silence to the audio outputs. This is called when - * there is not enough decoded data in the pipe yet, to prevent - * underruns in the hardware buffers. - * - * The player lock is not held. - */ -static bool -player_send_silence(struct player *player) -{ - assert(player->output_open); - assert(audio_format_defined(&player->play_audio_format)); - - struct music_chunk *chunk = music_buffer_allocate(player_buffer); - if (chunk == NULL) { - g_warning("Failed to allocate silence buffer"); - return false; - } - -#ifndef NDEBUG - chunk->audio_format = player->play_audio_format; -#endif - - size_t frame_size = - audio_format_frame_size(&player->play_audio_format); - /* this formula ensures that we don't send - partial frames */ - unsigned num_frames = sizeof(chunk->data) / frame_size; - - chunk->times = -1.0; /* undefined time stamp */ - chunk->length = num_frames * frame_size; - memset(chunk->data, 0, chunk->length); - - if (!audio_output_all_play(chunk)) { - music_buffer_return(player_buffer, chunk); - return false; - } - - return true; -} - -/** - * This is the handler for the #PLAYER_COMMAND_SEEK command. - * - * The player lock is not held. - */ -static bool player_seek_decoder(struct player *player) -{ - struct player_control *pc = player->pc; - struct song *song = pc->next_song; - struct decoder_control *dc = player->dc; - - assert(pc->next_song != NULL); - - const unsigned start_ms = song->start_ms; - - if (decoder_current_song(dc) != song) { - /* the decoder is already decoding the "next" song - - stop it and start the previous song again */ - - player_dc_stop(player); - - /* clear music chunks which might still reside in the - pipe */ - music_pipe_clear(player->pipe, player_buffer); - - /* re-start the decoder */ - player_dc_start(player, player->pipe); - if (!player_wait_for_decoder(player)) { - /* decoder failure */ - player_command_finished(pc); - return false; - } - } else { - if (!player_dc_at_current_song(player)) { - /* the decoder is already decoding the "next" song, - but it is the same song file; exchange the pipe */ - music_pipe_clear(player->pipe, player_buffer); - music_pipe_free(player->pipe); - player->pipe = dc->pipe; - } - - pc->next_song = NULL; - player->queued = false; - } - - /* wait for the decoder to complete initialization */ - - while (player->decoder_starting) { - if (!player_check_decoder_startup(player)) { - /* decoder failure */ - player_command_finished(pc); - return false; - } - } - - /* send the SEEK command */ - - double where = pc->seek_where; - if (where > pc->total_time) - where = pc->total_time - 0.1; - if (where < 0.0) - where = 0.0; - - if (!dc_seek(dc, where + start_ms / 1000.0)) { - /* decoder failure */ - player_command_finished(pc); - return false; - } - - player->elapsed_time = where; - - player_command_finished(pc); - - player->xfade = XFADE_UNKNOWN; - - /* re-fill the buffer after seeking */ - player->buffering = true; - - audio_output_all_cancel(); - - return true; -} - -/** - * Player lock must be held before calling. - */ -static void player_process_command(struct player *player) -{ - struct player_control *pc = player->pc; - G_GNUC_UNUSED struct decoder_control *dc = player->dc; - - switch (pc->command) { - case PLAYER_COMMAND_NONE: - case PLAYER_COMMAND_STOP: - case PLAYER_COMMAND_EXIT: - case PLAYER_COMMAND_CLOSE_AUDIO: - break; - - case PLAYER_COMMAND_UPDATE_AUDIO: - player_unlock(pc); - audio_output_all_enable_disable(); - player_lock(pc); - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_QUEUE: - assert(pc->next_song != NULL); - assert(!player->queued); - assert(!player_dc_at_next_song(player)); - - player->queued = true; - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_PAUSE: - player_unlock(pc); - - player->paused = !player->paused; - if (player->paused) { - audio_output_all_pause(); - player_lock(pc); - - pc->state = PLAYER_STATE_PAUSE; - } else if (!audio_format_defined(&player->play_audio_format)) { - /* the decoder hasn't provided an audio format - yet - don't open the audio device yet */ - player_lock(pc); - - pc->state = PLAYER_STATE_PLAY; - } else { - player_open_output(player); - - player_lock(pc); - } - - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_SEEK: - player_unlock(pc); - player_seek_decoder(player); - player_lock(pc); - break; - - case PLAYER_COMMAND_CANCEL: - if (pc->next_song == NULL) { - /* the cancel request arrived too late, we're - already playing the queued song... stop - everything now */ - pc->command = PLAYER_COMMAND_STOP; - return; - } - - if (player_dc_at_next_song(player)) { - /* the decoder is already decoding the song - - stop it and reset the position */ - player_unlock(pc); - player_dc_stop(player); - player_lock(pc); - } - - pc->next_song = NULL; - player->queued = false; - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_REFRESH: - if (player->output_open && !player->paused) { - player_unlock(pc); - audio_output_all_check(); - player_lock(pc); - } - - pc->elapsed_time = audio_output_all_get_elapsed_time(); - if (pc->elapsed_time < 0.0) - pc->elapsed_time = player->elapsed_time; - - player_command_finished_locked(pc); - break; - } -} - -static void -update_song_tag(struct song *song, const struct tag *new_tag) -{ - if (song_is_file(song)) - /* don't update tags of local files, only remote - streams may change tags dynamically */ - return; - - struct tag *old_tag = song->tag; - song->tag = tag_dup(new_tag); - - if (old_tag != NULL) - tag_free(old_tag); - - /* the main thread will update the playlist version when he - receives this event */ - event_pipe_emit(PIPE_EVENT_TAG); - - /* notify all clients that the tag of the current song has - changed */ - idle_add(IDLE_PLAYER); -} - -/** - * Plays a #music_chunk object (after applying software volume). If - * it contains a (stream) tag, copy it to the current song, so MPD's - * playlist reflects the new stream tag. - * - * Player lock is not held. - */ -static bool -play_chunk(struct player_control *pc, - struct song *song, struct music_chunk *chunk, - const struct audio_format *format) -{ - assert(music_chunk_check_format(chunk, format)); - - if (chunk->tag != NULL) - update_song_tag(song, chunk->tag); - - if (chunk->length == 0) { - music_buffer_return(player_buffer, chunk); - return true; - } - - player_lock(pc); - pc->bit_rate = chunk->bit_rate; - player_unlock(pc); - - /* send the chunk to the audio outputs */ - - if (!audio_output_all_play(chunk)) - return false; - - pc->total_play_time += (double)chunk->length / - audio_format_time_to_size(format); - return true; -} - -/** - * Obtains the next chunk from the music pipe, optionally applies - * cross-fading, and sends it to all audio outputs. - * - * @return true on success, false on error (playback will be stopped) - */ -static bool -play_next_chunk(struct player *player) -{ - struct player_control *pc = player->pc; - struct decoder_control *dc = player->dc; - - if (!audio_output_all_wait(pc, 64)) - /* the output pipe is still large enough, don't send - another chunk */ - return true; - - unsigned cross_fade_position; - struct music_chunk *chunk = NULL; - if (player->xfade == XFADE_ENABLED && - player_dc_at_next_song(player) && - (cross_fade_position = music_pipe_size(player->pipe)) - <= player->cross_fade_chunks) { - /* perform cross fade */ - struct music_chunk *other_chunk = - music_pipe_shift(dc->pipe); - - if (!player->cross_fading) { - /* beginning of the cross fade - adjust - crossFadeChunks which might be bigger than - the remaining number of chunks in the old - song */ - player->cross_fade_chunks = cross_fade_position; - player->cross_fading = true; - } - - if (other_chunk != NULL) { - chunk = music_pipe_shift(player->pipe); - assert(chunk != NULL); - assert(chunk->other == NULL); - - /* don't send the tags of the new song (which - is being faded in) yet; postpone it until - the current song is faded out */ - player->cross_fade_tag = - tag_merge_replace(player->cross_fade_tag, - other_chunk->tag); - other_chunk->tag = NULL; - - if (isnan(pc->mixramp_delay_seconds)) { - chunk->mix_ratio = ((float)cross_fade_position) - / player->cross_fade_chunks; - } else { - chunk->mix_ratio = nan(""); - } - - if (music_chunk_is_empty(other_chunk)) { - /* the "other" chunk was a music_chunk - which had only a tag, but no music - data - we cannot cross-fade that; - but since this happens only at the - beginning of the new song, we can - easily recover by throwing it away - now */ - music_buffer_return(player_buffer, - other_chunk); - other_chunk = NULL; - } - - chunk->other = other_chunk; - } else { - /* there are not enough decoded chunks yet */ - - decoder_lock(dc); - - if (decoder_is_idle(dc)) { - /* the decoder isn't running, abort - cross fading */ - decoder_unlock(dc); - - player->xfade = XFADE_DISABLED; - } else { - /* wait for the decoder */ - decoder_signal(dc); - player_wait_decoder(pc, dc); - decoder_unlock(dc); - - return true; - } - } - } - - if (chunk == NULL) - chunk = music_pipe_shift(player->pipe); - - assert(chunk != NULL); - - /* insert the postponed tag if cross-fading is finished */ - - if (player->xfade != XFADE_ENABLED && player->cross_fade_tag != NULL) { - chunk->tag = tag_merge_replace(chunk->tag, - player->cross_fade_tag); - player->cross_fade_tag = NULL; - } - - /* play the current chunk */ - - if (!play_chunk(player->pc, player->song, chunk, - &player->play_audio_format)) { - music_buffer_return(player_buffer, chunk); - - player_lock(pc); - - pc->error = PLAYER_ERROR_AUDIO; - - /* pause: the user may resume playback as soon as an - audio output becomes available */ - pc->state = PLAYER_STATE_PAUSE; - player->paused = true; - - player_unlock(pc); - - idle_add(IDLE_PLAYER); - - return false; - } - - /* this formula should prevent that the decoder gets woken up - with each chunk; it is more efficient to make it decode a - larger block at a time */ - decoder_lock(dc); - if (!decoder_is_idle(dc) && - music_pipe_size(dc->pipe) <= (pc->buffered_before_play + - music_buffer_size(player_buffer) * 3) / 4) - decoder_signal(dc); - decoder_unlock(dc); - - return true; -} - -/** - * This is called at the border between two songs: the audio output - * has consumed all chunks of the current song, and we should start - * sending chunks from the next one. - * - * The player lock is not held. - * - * @return true on success, false on error (playback will be stopped) - */ -static bool -player_song_border(struct player *player) -{ - player->xfade = XFADE_UNKNOWN; - - char *uri = song_get_uri(player->song); - g_message("played \"%s\"", uri); - g_free(uri); - - music_pipe_free(player->pipe); - player->pipe = player->dc->pipe; - - audio_output_all_song_border(); - - if (!player_wait_for_decoder(player)) - return false; - - struct player_control *const pc = player->pc; - player_lock(pc); - - const bool border_pause = pc->border_pause; - if (border_pause) { - player->paused = true; - pc->state = PLAYER_STATE_PAUSE; - } - - player_unlock(pc); - - if (border_pause) - idle_add(IDLE_PLAYER); - - return true; -} - -/* - * The main loop of the player thread, during playback. This is - * basically a state machine, which multiplexes data between the - * decoder thread and the output threads. - */ -static void do_play(struct player_control *pc, struct decoder_control *dc) -{ - struct player player = { - .pc = pc, - .dc = dc, - .buffering = true, - .decoder_starting = false, - .paused = false, - .queued = true, - .output_open = false, - .song = NULL, - .xfade = XFADE_UNKNOWN, - .cross_fading = false, - .cross_fade_chunks = 0, - .cross_fade_tag = NULL, - .elapsed_time = 0.0, - }; - - player_unlock(pc); - - player.pipe = music_pipe_new(); - - player_dc_start(&player, player.pipe); - if (!player_wait_for_decoder(&player)) { - player_dc_stop(&player); - player_command_finished(pc); - music_pipe_free(player.pipe); - event_pipe_emit(PIPE_EVENT_PLAYLIST); - player_lock(pc); - return; - } - - player_lock(pc); - pc->state = PLAYER_STATE_PLAY; - - if (pc->command == PLAYER_COMMAND_SEEK) - player.elapsed_time = pc->seek_where; - - player_command_finished_locked(pc); - - while (true) { - player_process_command(&player); - if (pc->command == PLAYER_COMMAND_STOP || - pc->command == PLAYER_COMMAND_EXIT || - pc->command == PLAYER_COMMAND_CLOSE_AUDIO) { - player_unlock(pc); - audio_output_all_cancel(); - break; - } - - player_unlock(pc); - - if (player.buffering) { - /* buffering at the start of the song - wait - until the buffer is large enough, to - prevent stuttering on slow machines */ - - if (music_pipe_size(player.pipe) < pc->buffered_before_play && - !decoder_lock_is_idle(dc)) { - /* not enough decoded buffer space yet */ - - if (!player.paused && - player.output_open && - audio_output_all_check() < 4 && - !player_send_silence(&player)) - break; - - decoder_lock(dc); - /* XXX race condition: check decoder again */ - player_wait_decoder(pc, dc); - decoder_unlock(dc); - player_lock(pc); - continue; - } else { - /* buffering is complete */ - player.buffering = false; - } - } - - if (player.decoder_starting) { - /* wait until the decoder is initialized completely */ - - if (!player_check_decoder_startup(&player)) - break; - - player_lock(pc); - continue; - } - -#ifndef NDEBUG - /* - music_pipe_check_format(&play_audio_format, - player.next_song_chunk, - &dc->out_audio_format); - */ -#endif - - if (decoder_lock_is_idle(dc) && player.queued && - dc->pipe == player.pipe) { - /* the decoder has finished the current song; - make it decode the next song */ - - assert(dc->pipe == NULL || dc->pipe == player.pipe); - - player_dc_start(&player, music_pipe_new()); - } - - if (/* no cross-fading if MPD is going to pause at the - end of the current song */ - !pc->border_pause && - player_dc_at_next_song(&player) && - player.xfade == XFADE_UNKNOWN && - !decoder_lock_is_starting(dc)) { - /* enable cross fading in this song? if yes, - calculate how many chunks will be required - for it */ - player.cross_fade_chunks = - cross_fade_calc(pc->cross_fade_seconds, dc->total_time, - pc->mixramp_db, - pc->mixramp_delay_seconds, - dc->replay_gain_db, - dc->replay_gain_prev_db, - dc->mixramp_start, - dc->mixramp_prev_end, - &dc->out_audio_format, - &player.play_audio_format, - music_buffer_size(player_buffer) - - pc->buffered_before_play); - if (player.cross_fade_chunks > 0) { - player.xfade = XFADE_ENABLED; - player.cross_fading = false; - } else - /* cross fading is disabled or the - next song is too short */ - player.xfade = XFADE_DISABLED; - } - - if (player.paused) { - player_lock(pc); - - if (pc->command == PLAYER_COMMAND_NONE) - player_wait(pc); - continue; - } else if (!music_pipe_empty(player.pipe)) { - /* at least one music chunk is ready - send it - to the audio output */ - - play_next_chunk(&player); - } else if (audio_output_all_check() > 0) { - /* not enough data from decoder, but the - output thread is still busy, so it's - okay */ - - /* XXX synchronize in a better way */ - g_usleep(10000); - } else if (player_dc_at_next_song(&player)) { - /* at the beginning of a new song */ - - if (!player_song_border(&player)) - break; - } else if (decoder_lock_is_idle(dc)) { - /* check the size of the pipe again, because - the decoder thread may have added something - since we last checked */ - if (music_pipe_empty(player.pipe)) { - /* wait for the hardware to finish - playback */ - audio_output_all_drain(); - break; - } - } else if (player.output_open) { - /* the decoder is too busy and hasn't provided - new PCM data in time: send silence (if the - output pipe is empty) */ - if (!player_send_silence(&player)) - break; - } - - player_lock(pc); - } - - player_dc_stop(&player); - - music_pipe_clear(player.pipe, player_buffer); - music_pipe_free(player.pipe); - - if (player.cross_fade_tag != NULL) - tag_free(player.cross_fade_tag); - - player_lock(pc); - - if (player.queued) { - assert(pc->next_song != NULL); - pc->next_song = NULL; - } - - pc->state = PLAYER_STATE_STOP; - - player_unlock(pc); - - event_pipe_emit(PIPE_EVENT_PLAYLIST); - - player_lock(pc); -} - -static gpointer -player_task(gpointer arg) -{ - struct player_control *pc = arg; - - struct decoder_control *dc = dc_new(pc->cond); - decoder_thread_start(dc); - - player_buffer = music_buffer_new(pc->buffer_chunks); - - player_lock(pc); - - while (1) { - switch (pc->command) { - case PLAYER_COMMAND_SEEK: - case PLAYER_COMMAND_QUEUE: - assert(pc->next_song != NULL); - - do_play(pc, dc); - break; - - case PLAYER_COMMAND_STOP: - player_unlock(pc); - audio_output_all_cancel(); - player_lock(pc); - - /* fall through */ - - case PLAYER_COMMAND_PAUSE: - pc->next_song = NULL; - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_CLOSE_AUDIO: - player_unlock(pc); - - audio_output_all_release(); - - player_lock(pc); - player_command_finished_locked(pc); - -#ifndef NDEBUG - /* in the DEBUG build, check for leaked - music_chunk objects by freeing the - music_buffer */ - music_buffer_free(player_buffer); - player_buffer = music_buffer_new(pc->buffer_chunks); -#endif - - break; - - case PLAYER_COMMAND_UPDATE_AUDIO: - player_unlock(pc); - audio_output_all_enable_disable(); - player_lock(pc); - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_EXIT: - player_unlock(pc); - - dc_quit(dc); - dc_free(dc); - audio_output_all_close(); - music_buffer_free(player_buffer); - - player_command_finished(pc); - return NULL; - - case PLAYER_COMMAND_CANCEL: - pc->next_song = NULL; - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_REFRESH: - /* no-op when not playing */ - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_NONE: - player_wait(pc); - break; - } - } -} - -void -player_create(struct player_control *pc) -{ - assert(pc->thread == NULL); - - GError *e = NULL; - pc->thread = g_thread_create(player_task, pc, true, &e); - if (pc->thread == NULL) - MPD_ERROR("Failed to spawn player task: %s", e->message); -} diff --git a/src/player_thread.h b/src/player_thread.h deleted file mode 100644 index 7373eb438..000000000 --- a/src/player_thread.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* \file - * - * The player thread controls the playback. It acts as a bridge - * between the decoder thread and the output thread(s): it receives - * #music_chunk objects from the decoder, optionally mixes them - * (cross-fading), applies software volume, and sends them to the - * audio outputs via audio_output_all_play(). - * - * It is controlled by the main thread (the playlist code), see - * player_control.h. The playlist enqueues new songs into the player - * thread and sends it commands. - * - * The player thread itself does not do any I/O. It synchronizes with - * other threads via #GMutex and #GCond objects, and passes - * #music_chunk instances around in #music_pipe objects. - */ - -#ifndef MPD_PLAYER_THREAD_H -#define MPD_PLAYER_THREAD_H - -struct player_control; - -void -player_create(struct player_control *pc); - -#endif diff --git a/src/playlist.c b/src/playlist.c deleted file mode 100644 index dc6d8c340..000000000 --- a/src/playlist.c +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_internal.h" -#include "playlist_save.h" -#include "player_control.h" -#include "command.h" -#include "tag.h" -#include "song.h" -#include "conf.h" -#include "stored_playlist.h" -#include "idle.h" - -#include <glib.h> - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "playlist" - -void -playlist_increment_version_all(struct playlist *playlist) -{ - queue_modify_all(&playlist->queue); - idle_add(IDLE_PLAYLIST); -} - -void -playlist_tag_changed(struct playlist *playlist) -{ - if (!playlist->playing) - return; - - assert(playlist->current >= 0); - - queue_modify(&playlist->queue, playlist->current); - idle_add(IDLE_PLAYLIST); -} - -void -playlist_init(struct playlist *playlist) -{ - queue_init(&playlist->queue, - config_get_positive(CONF_MAX_PLAYLIST_LENGTH, - DEFAULT_PLAYLIST_MAX_LENGTH)); - - playlist->queued = -1; - playlist->current = -1; -} - -void -playlist_finish(struct playlist *playlist) -{ - queue_finish(&playlist->queue); -} - -/** - * Queue a song, addressed by its order number. - */ -static void -playlist_queue_song_order(struct playlist *playlist, struct player_control *pc, - unsigned order) -{ - struct song *song; - char *uri; - - assert(queue_valid_order(&playlist->queue, order)); - - playlist->queued = order; - - song = queue_get_order(&playlist->queue, order); - uri = song_get_uri(song); - g_debug("queue song %i:\"%s\"", playlist->queued, uri); - g_free(uri); - - pc_enqueue_song(pc, song); -} - -/** - * Called if the player thread has started playing the "queued" song. - */ -static void -playlist_song_started(struct playlist *playlist, struct player_control *pc) -{ - assert(pc->next_song == NULL); - assert(playlist->queued >= -1); - - /* queued song has started: copy queued to current, - and notify the clients */ - - int current = playlist->current; - playlist->current = playlist->queued; - playlist->queued = -1; - - if(playlist->queue.consume) - playlist_delete(playlist, pc, - queue_order_to_position(&playlist->queue, - current)); - - idle_add(IDLE_PLAYER); -} - -const struct song * -playlist_get_queued_song(struct playlist *playlist) -{ - if (!playlist->playing || playlist->queued < 0) - return NULL; - - return queue_get_order(&playlist->queue, playlist->queued); -} - -void -playlist_update_queued_song(struct playlist *playlist, - struct player_control *pc, - const struct song *prev) -{ - int next_order; - const struct song *next_song; - - if (!playlist->playing) - return; - - assert(!queue_is_empty(&playlist->queue)); - assert((playlist->queued < 0) == (prev == NULL)); - - next_order = playlist->current >= 0 - ? queue_next_order(&playlist->queue, playlist->current) - : 0; - - if (next_order == 0 && playlist->queue.random && - !playlist->queue.single) { - /* shuffle the song order again, so we get a different - order each time the playlist is played - completely */ - unsigned current_position = - queue_order_to_position(&playlist->queue, - playlist->current); - - queue_shuffle_order(&playlist->queue); - - /* make sure that the playlist->current still points to - the current song, after the song order has been - shuffled */ - playlist->current = - queue_position_to_order(&playlist->queue, - current_position); - } - - if (next_order >= 0) - next_song = queue_get_order(&playlist->queue, next_order); - else - next_song = NULL; - - if (prev != NULL && next_song != prev) { - /* clear the currently queued song */ - pc_cancel(pc); - playlist->queued = -1; - } - - if (next_order >= 0) { - if (next_song != prev) - playlist_queue_song_order(playlist, pc, next_order); - else - playlist->queued = next_order; - } -} - -void -playlist_play_order(struct playlist *playlist, struct player_control *pc, - int orderNum) -{ - struct song *song; - char *uri; - - playlist->playing = true; - playlist->queued = -1; - - song = queue_get_order(&playlist->queue, orderNum); - - uri = song_get_uri(song); - g_debug("play %i:\"%s\"", orderNum, uri); - g_free(uri); - - pc_play(pc, song); - playlist->current = orderNum; -} - -static void -playlist_resume_playback(struct playlist *playlist, struct player_control *pc); - -/** - * This is the "PLAYLIST" event handler. It is invoked by the player - * thread whenever it requests a new queued song, or when it exits. - */ -void -playlist_sync(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - /* this event has reached us out of sync: we aren't - playing anymore; ignore the event */ - return; - - player_lock(pc); - enum player_state pc_state = pc_get_state(pc); - const struct song *pc_next_song = pc->next_song; - player_unlock(pc); - - if (pc_state == PLAYER_STATE_STOP) - /* the player thread has stopped: check if playback - should be restarted with the next song. That can - happen if the playlist isn't filling the queue fast - enough */ - playlist_resume_playback(playlist, pc); - else { - /* check if the player thread has already started - playing the queued song */ - if (pc_next_song == NULL && playlist->queued != -1) - playlist_song_started(playlist, pc); - - player_lock(pc); - pc_next_song = pc->next_song; - player_unlock(pc); - - /* make sure the queued song is always set (if - possible) */ - if (pc_next_song == NULL && playlist->queued < 0) - playlist_update_queued_song(playlist, pc, NULL); - } -} - -/** - * The player has stopped for some reason. Check the error, and - * decide whether to re-start playback - */ -static void -playlist_resume_playback(struct playlist *playlist, struct player_control *pc) -{ - enum player_error error; - - assert(playlist->playing); - assert(pc_get_state(pc) == PLAYER_STATE_STOP); - - error = pc_get_error(pc); - if (error == PLAYER_ERROR_NOERROR) - playlist->error_count = 0; - else - ++playlist->error_count; - - if ((playlist->stop_on_error && error != PLAYER_ERROR_NOERROR) || - error == PLAYER_ERROR_AUDIO || error == PLAYER_ERROR_SYSTEM || - playlist->error_count >= queue_length(&playlist->queue)) - /* too many errors, or critical error: stop - playback */ - playlist_stop(playlist, pc); - else - /* continue playback at the next song */ - playlist_next(playlist, pc); -} - -bool -playlist_get_repeat(const struct playlist *playlist) -{ - return playlist->queue.repeat; -} - -bool -playlist_get_random(const struct playlist *playlist) -{ - return playlist->queue.random; -} - -bool -playlist_get_single(const struct playlist *playlist) -{ - return playlist->queue.single; -} - -bool -playlist_get_consume(const struct playlist *playlist) -{ - return playlist->queue.consume; -} - -void -playlist_set_repeat(struct playlist *playlist, struct player_control *pc, - bool status) -{ - if (status == playlist->queue.repeat) - return; - - struct queue *queue = &playlist->queue; - - queue->repeat = status; - - pc_set_border_pause(pc, queue->single && !queue->repeat); - - /* if the last song is currently being played, the "next song" - might change when repeat mode is toggled */ - playlist_update_queued_song(playlist, pc, - playlist_get_queued_song(playlist)); - - idle_add(IDLE_OPTIONS); -} - -static void -playlist_order(struct playlist *playlist) -{ - if (playlist->current >= 0) - /* update playlist.current, order==position now */ - playlist->current = queue_order_to_position(&playlist->queue, - playlist->current); - - queue_restore_order(&playlist->queue); -} - -void -playlist_set_single(struct playlist *playlist, struct player_control *pc, - bool status) -{ - if (status == playlist->queue.single) - return; - - struct queue *queue = &playlist->queue; - - queue->single = status; - - pc_set_border_pause(pc, queue->single && !queue->repeat); - - /* if the last song is currently being played, the "next song" - might change when single mode is toggled */ - playlist_update_queued_song(playlist, pc, - playlist_get_queued_song(playlist)); - - idle_add(IDLE_OPTIONS); -} - -void -playlist_set_consume(struct playlist *playlist, bool status) -{ - if (status == playlist->queue.consume) - return; - - playlist->queue.consume = status; - idle_add(IDLE_OPTIONS); -} - -void -playlist_set_random(struct playlist *playlist, struct player_control *pc, - bool status) -{ - const struct song *queued; - - if (status == playlist->queue.random) - return; - - queued = playlist_get_queued_song(playlist); - - playlist->queue.random = status; - - if (playlist->queue.random) { - /* shuffle the queue order, but preserve - playlist->current */ - - int current_position = - playlist->playing && playlist->current >= 0 - ? (int)queue_order_to_position(&playlist->queue, - playlist->current) - : -1; - - queue_shuffle_order(&playlist->queue); - - if (current_position >= 0) { - /* make sure the current song is the first in - the order list, so the whole rest of the - playlist is played after that */ - unsigned current_order = - queue_position_to_order(&playlist->queue, - current_position); - queue_swap_order(&playlist->queue, 0, current_order); - playlist->current = 0; - } else - playlist->current = -1; - } else - playlist_order(playlist); - - playlist_update_queued_song(playlist, pc, queued); - - idle_add(IDLE_OPTIONS); -} - -int -playlist_get_current_song(const struct playlist *playlist) -{ - if (playlist->current >= 0) - return queue_order_to_position(&playlist->queue, - playlist->current); - - return -1; -} - -int -playlist_get_next_song(const struct playlist *playlist) -{ - if (playlist->current >= 0) - { - if (playlist->queue.single == 1 && playlist->queue.repeat == 1) - return queue_order_to_position(&playlist->queue, - playlist->current); - else if (playlist->current + 1 < (int)queue_length(&playlist->queue)) - return queue_order_to_position(&playlist->queue, - playlist->current + 1); - else if (playlist->queue.repeat == 1) - return queue_order_to_position(&playlist->queue, 0); - } - - return -1; -} - -unsigned long -playlist_get_version(const struct playlist *playlist) -{ - return playlist->queue.version; -} - -int -playlist_get_length(const struct playlist *playlist) -{ - return queue_length(&playlist->queue); -} - -unsigned -playlist_get_song_id(const struct playlist *playlist, unsigned song) -{ - return queue_position_to_id(&playlist->queue, song); -} diff --git a/src/playlist.h b/src/playlist.h deleted file mode 100644 index a21bdf24a..000000000 --- a/src/playlist.h +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_H -#define MPD_PLAYLIST_H - -#include "queue.h" -#include "playlist_error.h" - -#include <stdbool.h> - -struct player_control; - -struct playlist { - /** - * The song queue - it contains the "real" playlist. - */ - struct queue queue; - - /** - * This value is true if the player is currently playing (or - * should be playing). - */ - bool playing; - - /** - * If true, then any error is fatal; if false, MPD will - * attempt to play the next song on non-fatal errors. During - * seeking, this flag is set. - */ - bool stop_on_error; - - /** - * Number of errors since playback was started. If this - * number exceeds the length of the playlist, MPD gives up, - * because all songs have been tried. - */ - unsigned error_count; - - /** - * The "current song pointer". This is the song which is - * played when we get the "play" command. It is also the song - * which is currently being played. - */ - int current; - - /** - * The "next" song to be played, when the current one - * finishes. The decoder thread may start decoding and - * buffering it, while the "current" song is still playing. - * - * This variable is only valid if #playing is true. - */ - int queued; -}; - -/** the global playlist object */ -extern struct playlist g_playlist; - -void -playlist_global_init(void); - -void -playlist_global_finish(void); - -void -playlist_init(struct playlist *playlist); - -void -playlist_finish(struct playlist *playlist); - -void -playlist_tag_changed(struct playlist *playlist); - -/** - * Returns the "queue" object of the global playlist instance. - */ -static inline const struct queue * -playlist_get_queue(const struct playlist *playlist) -{ - return &playlist->queue; -} - -void -playlist_clear(struct playlist *playlist, struct player_control *pc); - -/** - * Appends a local file (outside the music database) to the playlist. - * - * Note: the caller is responsible for checking permissions. - */ -enum playlist_result -playlist_append_file(struct playlist *playlist, struct player_control *pc, - const char *path_fs, unsigned *added_id); - -enum playlist_result -playlist_append_uri(struct playlist *playlist, struct player_control *pc, - const char *file, unsigned *added_id); - -enum playlist_result -playlist_append_song(struct playlist *playlist, struct player_control *pc, - struct song *song, unsigned *added_id); - -enum playlist_result -playlist_delete(struct playlist *playlist, struct player_control *pc, - unsigned song); - -/** - * Deletes a range of songs from the playlist. - * - * @param start the position of the first song to delete - * @param end the position after the last song to delete - */ -enum playlist_result -playlist_delete_range(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end); - -enum playlist_result -playlist_delete_id(struct playlist *playlist, struct player_control *pc, - unsigned song); - -void -playlist_stop(struct playlist *playlist, struct player_control *pc); - -enum playlist_result -playlist_play(struct playlist *playlist, struct player_control *pc, - int song); - -enum playlist_result -playlist_play_id(struct playlist *playlist, struct player_control *pc, - int song); - -void -playlist_next(struct playlist *playlist, struct player_control *pc); - -void -playlist_sync(struct playlist *playlist, struct player_control *pc); - -void -playlist_previous(struct playlist *playlist, struct player_control *pc); - -void -playlist_shuffle(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end); - -void -playlist_delete_song(struct playlist *playlist, struct player_control *pc, - const struct song *song); - -enum playlist_result -playlist_move_range(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end, int to); - -enum playlist_result -playlist_move_id(struct playlist *playlist, struct player_control *pc, - unsigned id, int to); - -enum playlist_result -playlist_swap_songs(struct playlist *playlist, struct player_control *pc, - unsigned song1, unsigned song2); - -enum playlist_result -playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc, - unsigned id1, unsigned id2); - -enum playlist_result -playlist_set_priority(struct playlist *playlist, struct player_control *pc, - unsigned start_position, unsigned end_position, - uint8_t priority); - -enum playlist_result -playlist_set_priority_id(struct playlist *playlist, struct player_control *pc, - unsigned song_id, uint8_t priority); - -bool -playlist_get_repeat(const struct playlist *playlist); - -void -playlist_set_repeat(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_random(const struct playlist *playlist); - -void -playlist_set_random(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_single(const struct playlist *playlist); - -void -playlist_set_single(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_consume(const struct playlist *playlist); - -void -playlist_set_consume(struct playlist *playlist, bool status); - -int -playlist_get_current_song(const struct playlist *playlist); - -int -playlist_get_next_song(const struct playlist *playlist); - -unsigned -playlist_get_song_id(const struct playlist *playlist, unsigned song); - -int -playlist_get_length(const struct playlist *playlist); - -unsigned long -playlist_get_version(const struct playlist *playlist); - -enum playlist_result -playlist_seek_song(struct playlist *playlist, struct player_control *pc, - unsigned song, float seek_time); - -enum playlist_result -playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, - unsigned id, float seek_time); - -/** - * Seek within the current song. Fails if MPD is not currently - * playing. - * - * @param time the time in seconds - * @param relative if true, then the specified time is relative to the - * current position - */ -enum playlist_result -playlist_seek_current(struct playlist *playlist, struct player_control *pc, - float seek_time, bool relative); - -void -playlist_increment_version_all(struct playlist *playlist); - -#endif diff --git a/src/playlist/AsxPlaylistPlugin.cxx b/src/playlist/AsxPlaylistPlugin.cxx new file mode 100644 index 000000000..25319ca6b --- /dev/null +++ b/src/playlist/AsxPlaylistPlugin.cxx @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "AsxPlaylistPlugin.hxx" +#include "MemoryPlaylistProvider.hxx" +#include "input_stream.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "asx" + +/** + * This is the state object for the GLib XML parser. + */ +struct AsxParser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + std::forward_list<SongPointer> songs; + + /** + * The current position in the XML file. + */ + enum { + ROOT, ENTRY, + } state; + + /** + * The current tag within the "entry" element. This is only + * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there + * is no (known) tag. + */ + enum tag_type tag; + + /** + * The current song. It is allocated after the "location" + * element. + */ + struct song *song; + + AsxParser() + :state(ROOT) {} + +}; + +static const gchar * +get_attribute(const gchar **attribute_names, const gchar **attribute_values, + const gchar *name) +{ + for (unsigned i = 0; attribute_names[i] != NULL; ++i) + if (g_ascii_strcasecmp(attribute_names[i], name) == 0) + return attribute_values[i]; + + return NULL; +} + +static void +asx_start_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + AsxParser *parser = (AsxParser *)user_data; + + switch (parser->state) { + case AsxParser::ROOT: + if (g_ascii_strcasecmp(element_name, "entry") == 0) { + parser->state = AsxParser::ENTRY; + parser->song = song_remote_new("asx:"); + parser->tag = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case AsxParser::ENTRY: + if (g_ascii_strcasecmp(element_name, "ref") == 0) { + const gchar *href = get_attribute(attribute_names, + attribute_values, + "href"); + if (href != NULL) { + /* create new song object, and copy + the existing tag over; we cannot + replace the existing song's URI, + because that attribute is + immutable */ + struct song *song = song_remote_new(href); + + if (parser->song != NULL) { + song->tag = parser->song->tag; + parser->song->tag = NULL; + song_free(parser->song); + } + + parser->song = song; + } + } else if (g_ascii_strcasecmp(element_name, "author") == 0) + /* is that correct? or should it be COMPOSER + or PERFORMER? */ + parser->tag = TAG_ARTIST; + else if (g_ascii_strcasecmp(element_name, "title") == 0) + parser->tag = TAG_TITLE; + + break; + } +} + +static void +asx_end_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + AsxParser *parser = (AsxParser *)user_data; + + switch (parser->state) { + case AsxParser::ROOT: + break; + + case AsxParser::ENTRY: + if (g_ascii_strcasecmp(element_name, "entry") == 0) { + if (strcmp(parser->song->uri, "asx:") != 0) + parser->songs.emplace_front(parser->song); + else + song_free(parser->song); + + parser->state = AsxParser::ROOT; + } else + parser->tag = TAG_NUM_OF_ITEM_TYPES; + + break; + } +} + +static void +asx_text(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + AsxParser *parser = (AsxParser *)user_data; + + switch (parser->state) { + case AsxParser::ROOT: + break; + + case AsxParser::ENTRY: + if (parser->tag != TAG_NUM_OF_ITEM_TYPES) { + if (parser->song->tag == NULL) + parser->song->tag = tag_new(); + tag_add_item_n(parser->song->tag, parser->tag, + text, text_len); + } + + break; + } +} + +static const GMarkupParser asx_parser = { + asx_start_element, + asx_end_element, + asx_text, + nullptr, + nullptr, +}; + +static void +asx_parser_destroy(gpointer data) +{ + AsxParser *parser = (AsxParser *)data; + + if (parser->state >= AsxParser::ENTRY) + song_free(parser->song); +} + +/* + * The playlist object + * + */ + +static struct playlist_provider * +asx_open_stream(struct input_stream *is) +{ + AsxParser parser; + GMarkupParseContext *context; + char buffer[1024]; + size_t nbytes; + bool success; + GError *error = NULL; + + /* parse the ASX XML file */ + + context = g_markup_parse_context_new(&asx_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, + &parser, asx_parser_destroy); + + while (true) { + nbytes = input_stream_lock_read(is, buffer, sizeof(buffer), + &error); + if (nbytes == 0) { + if (error != NULL) { + g_markup_parse_context_free(context); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + break; + } + + success = g_markup_parse_context_parse(context, buffer, nbytes, + &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + } + + success = g_markup_parse_context_end_parse(context, &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + + parser.songs.reverse(); + MemoryPlaylistProvider *playlist = + new MemoryPlaylistProvider(std::move(parser.songs)); + + g_markup_parse_context_free(context); + + return playlist; +} + +static const char *const asx_suffixes[] = { + "asx", + NULL +}; + +static const char *const asx_mime_types[] = { + "video/x-ms-asf", + NULL +}; + +const struct playlist_plugin asx_playlist_plugin = { + "asx", + + nullptr, + nullptr, + nullptr, + asx_open_stream, + nullptr, + nullptr, + + nullptr, + asx_suffixes, + asx_mime_types, +}; diff --git a/src/playlist/AsxPlaylistPlugin.hxx b/src/playlist/AsxPlaylistPlugin.hxx new file mode 100644 index 000000000..240c1824a --- /dev/null +++ b/src/playlist/AsxPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ASX_PLAYLIST_PLUGIN_HXX +#define MPD_ASX_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin asx_playlist_plugin; + +#endif diff --git a/src/playlist/CuePlaylistPlugin.cxx b/src/playlist/CuePlaylistPlugin.cxx new file mode 100644 index 000000000..07eb5e243 --- /dev/null +++ b/src/playlist/CuePlaylistPlugin.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 "CuePlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" +#include "tag.h" +#include "song.h" +#include "input_stream.h" + +extern "C" { +#include "text_input_stream.h" +#include "cue/cue_parser.h" +} + +#include <glib.h> +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cue" + +struct CuePlaylist { + struct playlist_provider base; + + struct input_stream *is; + struct text_input_stream *tis; + struct cue_parser *parser; +}; + +static struct playlist_provider * +cue_playlist_open_stream(struct input_stream *is) +{ + 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) +{ + CuePlaylist *playlist = (CuePlaylist *)_playlist; + + cue_parser_free(playlist->parser); + text_input_stream_free(playlist->tis); + g_free(playlist); +} + +static struct song * +cue_playlist_read(struct playlist_provider *_playlist) +{ + CuePlaylist *playlist = (CuePlaylist *)_playlist; + + struct song *song = cue_parser_get(playlist->parser); + if (song != NULL) + return song; + + const char *line; + while ((line = text_input_stream_read(playlist->tis)) != NULL) { + cue_parser_feed(playlist->parser, line); + song = cue_parser_get(playlist->parser); + if (song != NULL) + return song; + } + + cue_parser_finish(playlist->parser); + return cue_parser_get(playlist->parser); +} + +static const char *const cue_playlist_suffixes[] = { + "cue", + NULL +}; + +static const char *const cue_playlist_mime_types[] = { + "application/x-cue", + NULL +}; + +const struct playlist_plugin cue_playlist_plugin = { + "cue", + + nullptr, + nullptr, + nullptr, + cue_playlist_open_stream, + cue_playlist_close, + cue_playlist_read, + + nullptr, + cue_playlist_suffixes, + cue_playlist_mime_types, +}; diff --git a/src/playlist/CuePlaylistPlugin.hxx b/src/playlist/CuePlaylistPlugin.hxx new file mode 100644 index 000000000..cf5e3a8f0 --- /dev/null +++ b/src/playlist/CuePlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CUE_PLAYLIST_PLUGIN_HXX +#define MPD_CUE_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin cue_playlist_plugin; + +#endif diff --git a/src/playlist/DespotifyPlaylistPlugin.cxx b/src/playlist/DespotifyPlaylistPlugin.cxx new file mode 100644 index 000000000..25f12785a --- /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/DespotifyPlaylistPlugin.hxx b/src/playlist/DespotifyPlaylistPlugin.hxx new file mode 100644 index 000000000..c1e5b7f39 --- /dev/null +++ b/src/playlist/DespotifyPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2011-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX +#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin despotify_playlist_plugin; + +#endif diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/EmbeddedCuePlaylistPlugin.cxx new file mode 100644 index 000000000..04cb12eca --- /dev/null +++ b/src/playlist/EmbeddedCuePlaylistPlugin.cxx @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Playlist plugin that reads embedded cue sheets from the "CUESHEET" + * tag of a music file. + */ + +#include "config.h" +#include "EmbeddedCuePlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" +#include "tag.h" +#include "tag_handler.h" +#include "song.h" +#include "TagFile.hxx" + +extern "C" { +#include "tag_ape.h" +#include "tag_id3.h" +#include "cue/cue_parser.h" +} + +#include <glib.h> +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cue" + +struct embcue_playlist { + struct playlist_provider base; + + /** + * This is an override for the CUE's "FILE". An embedded CUE + * sheet must always point to the song file it is contained + * in. + */ + char *filename; + + /** + * The value of the file's "CUESHEET" tag. + */ + char *cuesheet; + + /** + * The offset of the next line within "cuesheet". + */ + char *next; + + struct cue_parser *parser; +}; + +static void +embcue_tag_pair(const char *name, const char *value, void *ctx) +{ + struct embcue_playlist *playlist = (struct embcue_playlist *)ctx; + + if (playlist->cuesheet == NULL && + g_ascii_strcasecmp(name, "cuesheet") == 0) + playlist->cuesheet = g_strdup(value); +} + +static const struct tag_handler embcue_tag_handler = { + nullptr, + nullptr, + embcue_tag_pair, +}; + +static struct playlist_provider * +embcue_playlist_open_uri(const char *uri, + gcc_unused Mutex &mutex, + gcc_unused Cond &cond) +{ + if (!g_path_is_absolute(uri)) + /* only local files supported */ + return NULL; + + struct embcue_playlist *playlist = g_new(struct embcue_playlist, 1); + playlist_provider_init(&playlist->base, &embcue_playlist_plugin); + playlist->cuesheet = NULL; + + tag_file_scan(uri, &embcue_tag_handler, playlist); + if (playlist->cuesheet == NULL) { + tag_ape_scan2(uri, &embcue_tag_handler, playlist); + if (playlist->cuesheet == NULL) + tag_id3_scan(uri, &embcue_tag_handler, playlist); + } + + if (playlist->cuesheet == NULL) { + /* no "CUESHEET" tag found */ + g_free(playlist); + return NULL; + } + + playlist->filename = g_path_get_basename(uri); + + playlist->next = playlist->cuesheet; + playlist->parser = cue_parser_new(); + + return &playlist->base; +} + +static void +embcue_playlist_close(struct playlist_provider *_playlist) +{ + struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist; + + cue_parser_free(playlist->parser); + g_free(playlist->cuesheet); + g_free(playlist->filename); + g_free(playlist); +} + +static struct song * +embcue_playlist_read(struct playlist_provider *_playlist) +{ + struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist; + + struct song *song = cue_parser_get(playlist->parser); + if (song != NULL) + return song; + + while (*playlist->next != 0) { + const char *line = playlist->next; + char *eol = strpbrk(playlist->next, "\r\n"); + if (eol != NULL) { + /* null-terminate the line */ + *eol = 0; + playlist->next = eol + 1; + } else + /* last line; put the "next" pointer to the + end of the buffer */ + playlist->next += strlen(line); + + cue_parser_feed(playlist->parser, line); + song = cue_parser_get(playlist->parser); + if (song != NULL) + return song_replace_uri(song, playlist->filename); + } + + cue_parser_finish(playlist->parser); + song = cue_parser_get(playlist->parser); + if (song != NULL) + song = song_replace_uri(song, playlist->filename); + return song; +} + +static const char *const embcue_playlist_suffixes[] = { + /* a few codecs that are known to be supported; there are + probably many more */ + "flac", + "mp3", "mp2", + "mp4", "mp4a", "m4b", + "ape", + "wv", + "ogg", "oga", + NULL +}; + +const struct playlist_plugin embcue_playlist_plugin = { + "cue", + + nullptr, + nullptr, + embcue_playlist_open_uri, + nullptr, + embcue_playlist_close, + embcue_playlist_read, + + embcue_playlist_suffixes, + nullptr, + nullptr, +}; diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.hxx b/src/playlist/EmbeddedCuePlaylistPlugin.hxx new file mode 100644 index 000000000..e306730f4 --- /dev/null +++ b/src/playlist/EmbeddedCuePlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_EMBCUE_PLAYLIST_PLUGIN_HXX +#define MPD_EMBCUE_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin embcue_playlist_plugin; + +#endif diff --git a/src/playlist/ExtM3uPlaylistPlugin.cxx b/src/playlist/ExtM3uPlaylistPlugin.cxx new file mode 100644 index 000000000..ce026dab8 --- /dev/null +++ b/src/playlist/ExtM3uPlaylistPlugin.cxx @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#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 ExtM3uPlaylist { + struct playlist_provider base; + + struct text_input_stream *tis; +}; + +static struct playlist_provider * +extm3u_open_stream(struct input_stream *is) +{ + ExtM3uPlaylist *playlist = g_new(ExtM3uPlaylist, 1); + playlist->tis = text_input_stream_new(is); + + 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 */ + text_input_stream_free(playlist->tis); + g_free(playlist); + return NULL; + } + + playlist_provider_init(&playlist->base, &extm3u_playlist_plugin); + return &playlist->base; +} + +static void +extm3u_close(struct playlist_provider *_playlist) +{ + ExtM3uPlaylist *playlist = (ExtM3uPlaylist *)_playlist; + + text_input_stream_free(playlist->tis); + g_free(playlist); +} + +/** + * Parse a EXTINF line. + * + * @param line the rest of the input line after the colon + */ +static struct tag * +extm3u_parse_tag(const char *line) +{ + long duration; + char *endptr; + const char *name; + struct tag *tag; + + duration = strtol(line, &endptr, 10); + if (endptr[0] != ',') + /* malformed line */ + return NULL; + + if (duration < 0) + /* 0 means unknown duration */ + duration = 0; + + name = strchug_fast_c(endptr + 1); + if (*name == 0 && duration == 0) + /* no information available; don't allocate a tag + object */ + return NULL; + + tag = tag_new(); + tag->time = duration; + + /* unfortunately, there is no real specification for the + EXTM3U format, so we must assume that the string after the + comma is opaque, and is just the song name*/ + if (*name != 0) + tag_add_item(tag, TAG_NAME, name); + + return tag; +} + +static struct song * +extm3u_read(struct playlist_provider *_playlist) +{ + ExtM3uPlaylist *playlist = (ExtM3uPlaylist *)_playlist; + struct tag *tag = NULL; + const char *line; + struct song *song; + + do { + line = text_input_stream_read(playlist->tis); + if (line == NULL) { + if (tag != NULL) + tag_free(tag); + return NULL; + } + + if (g_str_has_prefix(line, "#EXTINF:")) { + if (tag != NULL) + tag_free(tag); + tag = extm3u_parse_tag(line + 8); + continue; + } + + while (*line != 0 && g_ascii_isspace(*line)) + ++line; + } while (line[0] == '#' || *line == 0); + + song = song_remote_new(line); + song->tag = tag; + return song; +} + +static const char *const extm3u_suffixes[] = { + "m3u", + NULL +}; + +static const char *const extm3u_mime_types[] = { + "audio/x-mpegurl", + NULL +}; + +const struct playlist_plugin extm3u_playlist_plugin = { + "extm3u", + + nullptr, + nullptr, + nullptr, + extm3u_open_stream, + extm3u_close, + extm3u_read, + + nullptr, + extm3u_suffixes, + extm3u_mime_types, +}; diff --git a/src/playlist/ExtM3uPlaylistPlugin.hxx b/src/playlist/ExtM3uPlaylistPlugin.hxx new file mode 100644 index 000000000..844fba15c --- /dev/null +++ b/src/playlist/ExtM3uPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_EXTM3U_PLAYLIST_PLUGIN_HXX +#define MPD_EXTM3U_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin extm3u_playlist_plugin; + +#endif diff --git a/src/playlist/LastFMPlaylistPlugin.cxx b/src/playlist/LastFMPlaylistPlugin.cxx new file mode 100644 index 000000000..496388407 --- /dev/null +++ b/src/playlist/LastFMPlaylistPlugin.cxx @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "LastFMPlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" +#include "PlaylistRegistry.hxx" +#include "conf.h" +#include "song.h" +#include "input_stream.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +struct lastfm_playlist { + struct playlist_provider base; + + struct input_stream *is; + + struct playlist_provider *xspf; +}; + +static struct { + char *user; + char *md5; +} lastfm_config; + +static bool +lastfm_init(const struct config_param *param) +{ + const char *user = config_get_block_string(param, "user", NULL); + const char *passwd = config_get_block_string(param, "password", NULL); + + if (user == NULL || passwd == NULL) { + g_debug("disabling the last.fm playlist plugin " + "because account is not configured"); + return false; + } + + lastfm_config.user = g_uri_escape_string(user, NULL, false); + + if (strlen(passwd) != 32) + lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5, + passwd, strlen(passwd)); + else + lastfm_config.md5 = g_strdup(passwd); + + return true; +} + +static void +lastfm_finish(void) +{ + g_free(lastfm_config.user); + g_free(lastfm_config.md5); +} + +/** + * Simple data fetcher. + * @param url path or url of data to fetch. + * @return data fetched, or NULL on error. Must be freed with g_free. + */ +static char * +lastfm_get(const char *url, Mutex &mutex, Cond &cond) +{ + struct input_stream *input_stream; + GError *error = NULL; + char buffer[4096]; + size_t length = 0, nbytes; + + input_stream = input_stream_open(url, mutex, cond, &error); + if (input_stream == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + return NULL; + } + + mutex.lock(); + + input_stream_wait_ready(input_stream); + + do { + nbytes = input_stream_read(input_stream, buffer + length, + sizeof(buffer) - length, &error); + if (nbytes == 0) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + if (input_stream_eof(input_stream)) + break; + + /* I/O error */ + mutex.unlock(); + input_stream_close(input_stream); + return NULL; + } + + length += nbytes; + } while (length < sizeof(buffer)); + + mutex.unlock(); + + input_stream_close(input_stream); + return g_strndup(buffer, length); +} + +/** + * Ini-style value fetcher. + * @param response data through which to search. + * @param name name of value to search for. + * @return value for param name in param response or NULL on error. Free with g_free. + */ +static char * +lastfm_find(const char *response, const char *name) +{ + size_t name_length = strlen(name); + + while (true) { + const char *eol = strchr(response, '\n'); + if (eol == NULL) + return NULL; + + if (strncmp(response, name, name_length) == 0 && + response[name_length] == '=') { + response += name_length + 1; + return g_strndup(response, eol - response); + } + + response = eol + 1; + } +} + +static struct playlist_provider * +lastfm_open_uri(const char *uri, Mutex &mutex, Cond &cond) +{ + struct lastfm_playlist *playlist; + GError *error = NULL; + char *p, *q, *response, *session; + + /* handshake */ + + p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?" + "version=1.1.1&platform=linux&" + "username=", lastfm_config.user, "&" + "passwordmd5=", lastfm_config.md5, "&" + "debug=0&partner=", NULL); + response = lastfm_get(p, mutex, cond); + g_free(p); + if (response == NULL) + return NULL; + + /* extract session id from response */ + + session = lastfm_find(response, "session"); + g_free(response); + if (session == NULL) { + g_warning("last.fm handshake failed"); + return NULL; + } + + q = g_uri_escape_string(session, NULL, false); + g_free(session); + session = q; + + g_debug("session='%s'", session); + + /* "adjust" last.fm radio */ + + if (strlen(uri) > 9) { + char *escaped_uri; + + escaped_uri = g_uri_escape_string(uri, NULL, false); + + p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?" + "session=", session, "&url=", escaped_uri, "&debug=0", + NULL); + g_free(escaped_uri); + + response = lastfm_get(p, mutex, cond); + g_free(response); + g_free(p); + + if (response == NULL) { + g_free(session); + return NULL; + } + } + + /* create the playlist object */ + + playlist = g_new(struct lastfm_playlist, 1); + playlist_provider_init(&playlist->base, &lastfm_playlist_plugin); + + /* open the last.fm playlist */ + + p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?" + "sk=", session, "&discovery=0&desktop=1.5.1.31879", + NULL); + g_free(session); + + playlist->is = input_stream_open(p, mutex, cond, &error); + g_free(p); + + if (playlist->is == NULL) { + if (error != NULL) { + g_warning("Failed to load XSPF playlist: %s", + error->message); + g_error_free(error); + } else + g_warning("Failed to load XSPF playlist"); + g_free(playlist); + return NULL; + } + + mutex.lock(); + + input_stream_wait_ready(playlist->is); + + /* last.fm does not send a MIME type, we have to fake it here + :-( */ + input_stream_override_mime_type(playlist->is, "application/xspf+xml"); + + mutex.unlock(); + + /* parse the XSPF playlist */ + + playlist->xspf = playlist_list_open_stream(playlist->is, NULL); + if (playlist->xspf == NULL) { + input_stream_close(playlist->is); + g_free(playlist); + g_warning("Failed to parse XSPF playlist"); + return NULL; + } + + return &playlist->base; +} + +static void +lastfm_close(struct playlist_provider *_playlist) +{ + struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist; + + playlist_plugin_close(playlist->xspf); + input_stream_close(playlist->is); + g_free(playlist); +} + +static struct song * +lastfm_read(struct playlist_provider *_playlist) +{ + struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist; + + return playlist_plugin_read(playlist->xspf); +} + +static const char *const lastfm_schemes[] = { + "lastfm", + NULL +}; + +const struct playlist_plugin lastfm_playlist_plugin = { + "lastfm", + + lastfm_init, + lastfm_finish, + lastfm_open_uri, + nullptr, + lastfm_close, + lastfm_read, + + lastfm_schemes, + nullptr, + nullptr, +}; diff --git a/src/playlist/LastFMPlaylistPlugin.hxx b/src/playlist/LastFMPlaylistPlugin.hxx new file mode 100644 index 000000000..fe0e206d8 --- /dev/null +++ b/src/playlist/LastFMPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_LASTFM_PLAYLIST_PLUGIN_HXX +#define MPD_LASTFM_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin lastfm_playlist_plugin; + +#endif diff --git a/src/playlist/M3uPlaylistPlugin.cxx b/src/playlist/M3uPlaylistPlugin.cxx new file mode 100644 index 000000000..eeecd2779 --- /dev/null +++ b/src/playlist/M3uPlaylistPlugin.cxx @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "M3uPlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" +#include "song.h" + +extern "C" { +#include "text_input_stream.h" +} + +#include <glib.h> + +struct M3uPlaylist { + struct playlist_provider base; + + struct text_input_stream *tis; +}; + +static struct playlist_provider * +m3u_open_stream(struct input_stream *is) +{ + M3uPlaylist *playlist = g_new(M3uPlaylist, 1); + + playlist_provider_init(&playlist->base, &m3u_playlist_plugin); + playlist->tis = text_input_stream_new(is); + + return &playlist->base; +} + +static void +m3u_close(struct playlist_provider *_playlist) +{ + M3uPlaylist *playlist = (M3uPlaylist *)_playlist; + + text_input_stream_free(playlist->tis); + g_free(playlist); +} + +static struct song * +m3u_read(struct playlist_provider *_playlist) +{ + M3uPlaylist *playlist = (M3uPlaylist *)_playlist; + const char *line; + + do { + line = text_input_stream_read(playlist->tis); + if (line == NULL) + return NULL; + + while (*line != 0 && g_ascii_isspace(*line)) + ++line; + } while (line[0] == '#' || *line == 0); + + return song_remote_new(line); +} + +static const char *const m3u_suffixes[] = { + "m3u", + NULL +}; + +static const char *const m3u_mime_types[] = { + "audio/x-mpegurl", + NULL +}; + +const struct playlist_plugin m3u_playlist_plugin = { + "m3u", + + nullptr, + nullptr, + nullptr, + m3u_open_stream, + m3u_close, + m3u_read, + + nullptr, + m3u_suffixes, + m3u_mime_types, +}; diff --git a/src/playlist/M3uPlaylistPlugin.hxx b/src/playlist/M3uPlaylistPlugin.hxx new file mode 100644 index 000000000..a2058bb29 --- /dev/null +++ b/src/playlist/M3uPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_M3U_PLAYLIST_PLUGIN_HXX +#define MPD_M3U_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin m3u_playlist_plugin; + +#endif diff --git a/src/playlist/MemoryPlaylistProvider.cxx b/src/playlist/MemoryPlaylistProvider.cxx new file mode 100644 index 000000000..4fe3d6cef --- /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/playlist/MemoryPlaylistProvider.hxx b/src/playlist/MemoryPlaylistProvider.hxx new file mode 100644 index 000000000..246ffd10a --- /dev/null +++ b/src/playlist/MemoryPlaylistProvider.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MEMORY_PLAYLIST_PROVIDER_HXX +#define MPD_MEMORY_PLAYLIST_PROVIDER_HXX + +#include "PlaylistPlugin.hxx" +#include "SongPointer.hxx" + +#include <forward_list> + +struct song; + +class MemoryPlaylistProvider : public playlist_provider { + std::forward_list<SongPointer> songs; + +public: + MemoryPlaylistProvider(std::forward_list<SongPointer> &&_songs); + + song *Read(); +}; + +#endif diff --git a/src/playlist/PlsPlaylistPlugin.cxx b/src/playlist/PlsPlaylistPlugin.cxx new file mode 100644 index 000000000..3cf5f46e5 --- /dev/null +++ b/src/playlist/PlsPlaylistPlugin.cxx @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlsPlaylistPlugin.hxx" +#include "MemoryPlaylistProvider.hxx" +#include "input_stream.h" +#include "uri.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> + +static void +pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs) +{ + gchar *key; + gchar *value; + int length; + GError *error = NULL; + int num_entries = g_key_file_get_integer(keyfile, "playlist", + "NumberOfEntries", &error); + if (error) { + g_debug("Invalid PLS file: '%s'", error->message); + g_error_free(error); + error = NULL; + + /* Hack to work around shoutcast failure to comform to spec */ + num_entries = g_key_file_get_integer(keyfile, "playlist", + "numberofentries", &error); + if (error) { + g_error_free(error); + error = NULL; + } + } + + while (num_entries > 0) { + struct song *song; + key = g_strdup_printf("File%i", num_entries); + value = g_key_file_get_string(keyfile, "playlist", key, + &error); + if(error) { + g_debug("Invalid PLS entry %s: '%s'",key, error->message); + g_error_free(error); + g_free(key); + return; + } + g_free(key); + + song = song_remote_new(value); + g_free(value); + + key = g_strdup_printf("Title%i", num_entries); + value = g_key_file_get_string(keyfile, "playlist", key, + &error); + g_free(key); + if(error == NULL && value){ + if (song->tag == NULL) + song->tag = tag_new(); + tag_add_item(song->tag,TAG_TITLE, value); + } + /* Ignore errors? Most likely value not present */ + if(error) g_error_free(error); + error = NULL; + g_free(value); + + key = g_strdup_printf("Length%i", num_entries); + length = g_key_file_get_integer(keyfile, "playlist", key, + &error); + g_free(key); + if(error == NULL && length > 0){ + if (song->tag == NULL) + song->tag = tag_new(); + song->tag->time = length; + } + /* Ignore errors? Most likely value not present */ + if(error) g_error_free(error); + error = NULL; + + songs.emplace_front(song); + num_entries--; + } + +} + +static struct playlist_provider * +pls_open_stream(struct input_stream *is) +{ + GError *error = NULL; + size_t nbytes; + char buffer[1024]; + bool success; + GKeyFile *keyfile; + GString *kf_data = g_string_new(""); + + do { + nbytes = input_stream_lock_read(is, buffer, sizeof(buffer), + &error); + if (nbytes == 0) { + if (error != NULL) { + g_string_free(kf_data, TRUE); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + break; + } + + kf_data = g_string_append_len(kf_data, buffer,nbytes); + /* Limit to 64k */ + } while(kf_data->len < 65536); + + if (kf_data->len == 0) { + g_warning("KeyFile parser failed: No Data"); + g_string_free(kf_data, TRUE); + return NULL; + } + + keyfile = g_key_file_new(); + success = g_key_file_load_from_data(keyfile, + kf_data->str, kf_data->len, + G_KEY_FILE_NONE, &error); + + g_string_free(kf_data, TRUE); + + if (!success) { + g_warning("KeyFile parser failed: %s", error->message); + g_error_free(error); + g_key_file_free(keyfile); + return NULL; + } + + std::forward_list<SongPointer> songs; + pls_parser(keyfile, songs); + g_key_file_free(keyfile); + + songs.reverse(); + return new MemoryPlaylistProvider(std::move(songs)); +} + +static const char *const pls_suffixes[] = { + "pls", + NULL +}; + +static const char *const pls_mime_types[] = { + "audio/x-scpls", + NULL +}; + +const struct playlist_plugin pls_playlist_plugin = { + "pls", + + nullptr, + nullptr, + nullptr, + pls_open_stream, + nullptr, + nullptr, + + nullptr, + pls_suffixes, + pls_mime_types, +}; diff --git a/src/playlist/PlsPlaylistPlugin.hxx b/src/playlist/PlsPlaylistPlugin.hxx new file mode 100644 index 000000000..3fafd36d0 --- /dev/null +++ b/src/playlist/PlsPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLS_PLAYLIST_PLUGIN_HXX +#define MPD_PLS_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin pls_playlist_plugin; + +#endif diff --git a/src/playlist/RssPlaylistPlugin.cxx b/src/playlist/RssPlaylistPlugin.cxx new file mode 100644 index 000000000..3b69202e6 --- /dev/null +++ b/src/playlist/RssPlaylistPlugin.cxx @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "RssPlaylistPlugin.hxx" +#include "MemoryPlaylistProvider.hxx" +#include "input_stream.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "rss" + +/** + * This is the state object for the GLib XML parser. + */ +struct RssParser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + std::forward_list<SongPointer> songs; + + /** + * The current position in the XML file. + */ + enum { + ROOT, ITEM, + } state; + + /** + * The current tag within the "entry" element. This is only + * valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there + * is no (known) tag. + */ + enum tag_type tag; + + /** + * The current song. It is allocated after the "location" + * element. + */ + struct song *song; + + RssParser() + :state(ROOT) {} +}; + +static const gchar * +get_attribute(const gchar **attribute_names, const gchar **attribute_values, + const gchar *name) +{ + for (unsigned i = 0; attribute_names[i] != NULL; ++i) + if (g_ascii_strcasecmp(attribute_names[i], name) == 0) + return attribute_values[i]; + + return NULL; +} + +static void +rss_start_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + RssParser *parser = (RssParser *)user_data; + + switch (parser->state) { + case RssParser::ROOT: + if (g_ascii_strcasecmp(element_name, "item") == 0) { + parser->state = RssParser::ITEM; + parser->song = song_remote_new("rss:"); + parser->tag = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case RssParser::ITEM: + if (g_ascii_strcasecmp(element_name, "enclosure") == 0) { + const gchar *href = get_attribute(attribute_names, + attribute_values, + "url"); + if (href != NULL) { + /* create new song object, and copy + the existing tag over; we cannot + replace the existing song's URI, + because that attribute is + immutable */ + struct song *song = song_remote_new(href); + + if (parser->song != NULL) { + song->tag = parser->song->tag; + parser->song->tag = NULL; + song_free(parser->song); + } + + parser->song = song; + } + } else if (g_ascii_strcasecmp(element_name, "title") == 0) + parser->tag = TAG_TITLE; + else if (g_ascii_strcasecmp(element_name, "itunes:author") == 0) + parser->tag = TAG_ARTIST; + + break; + } +} + +static void +rss_end_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + RssParser *parser = (RssParser *)user_data; + + switch (parser->state) { + case RssParser::ROOT: + break; + + case RssParser::ITEM: + if (g_ascii_strcasecmp(element_name, "item") == 0) { + if (strcmp(parser->song->uri, "rss:") != 0) + parser->songs.emplace_front(parser->song); + else + song_free(parser->song); + + parser->state = RssParser::ROOT; + } else + parser->tag = TAG_NUM_OF_ITEM_TYPES; + + break; + } +} + +static void +rss_text(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + RssParser *parser = (RssParser *)user_data; + + switch (parser->state) { + case RssParser::ROOT: + break; + + case RssParser::ITEM: + if (parser->tag != TAG_NUM_OF_ITEM_TYPES) { + if (parser->song->tag == NULL) + parser->song->tag = tag_new(); + tag_add_item_n(parser->song->tag, parser->tag, + text, text_len); + } + + break; + } +} + +static const GMarkupParser rss_parser = { + rss_start_element, + rss_end_element, + rss_text, + nullptr, + nullptr, +}; + +static void +rss_parser_destroy(gpointer data) +{ + RssParser *parser = (RssParser *)data; + + if (parser->state >= RssParser::ITEM) + song_free(parser->song); +} + +/* + * The playlist object + * + */ + +static struct playlist_provider * +rss_open_stream(struct input_stream *is) +{ + RssParser parser; + GMarkupParseContext *context; + char buffer[1024]; + size_t nbytes; + bool success; + GError *error = NULL; + + /* parse the RSS XML file */ + + context = g_markup_parse_context_new(&rss_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, + &parser, rss_parser_destroy); + + while (true) { + nbytes = input_stream_lock_read(is, buffer, sizeof(buffer), + &error); + if (nbytes == 0) { + if (error != NULL) { + g_markup_parse_context_free(context); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + break; + } + + success = g_markup_parse_context_parse(context, buffer, nbytes, + &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + } + + success = g_markup_parse_context_end_parse(context, &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + + parser.songs.reverse(); + MemoryPlaylistProvider *playlist = + new MemoryPlaylistProvider(std::move(parser.songs)); + + g_markup_parse_context_free(context); + + return playlist; +} + +static const char *const rss_suffixes[] = { + "rss", + NULL +}; + +static const char *const rss_mime_types[] = { + "application/rss+xml", + "text/xml", + NULL +}; + +const struct playlist_plugin rss_playlist_plugin = { + "rss", + + nullptr, + nullptr, + nullptr, + rss_open_stream, + nullptr, + nullptr, + + nullptr, + rss_suffixes, + rss_mime_types, +}; diff --git a/src/playlist/RssPlaylistPlugin.hxx b/src/playlist/RssPlaylistPlugin.hxx new file mode 100644 index 000000000..f49f7e9cf --- /dev/null +++ b/src/playlist/RssPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_RSS_PLAYLIST_PLUGIN_HXX +#define MPD_RSS_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin rss_playlist_plugin; + +#endif diff --git a/src/playlist/SoundCloudPlaylistPlugin.cxx b/src/playlist/SoundCloudPlaylistPlugin.cxx new file mode 100644 index 000000000..5a258865c --- /dev/null +++ b/src/playlist/SoundCloudPlaylistPlugin.cxx @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SoundCloudPlaylistPlugin.hxx" +#include "MemoryPlaylistProvider.hxx" +#include "conf.h" +#include "input_stream.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> +#include <yajl/yajl_parse.h> + +#include <string.h> + +static struct { + char *apikey; +} soundcloud_config; + +static bool +soundcloud_init(const struct config_param *param) +{ + soundcloud_config.apikey = + config_dup_block_string(param, "apikey", NULL); + if (soundcloud_config.apikey == NULL) { + g_debug("disabling the soundcloud playlist plugin " + "because API key is not set"); + return false; + } + + return true; +} + +static void +soundcloud_finish(void) +{ + g_free(soundcloud_config.apikey); +} + +/** + * Construct a full soundcloud resolver URL from the given fragment. + * @param uri uri of a soundcloud page (or just the path) + * @return Constructed URL. Must be freed with g_free. + */ +static char * +soundcloud_resolve(const char* uri) { + char *u, *ru; + + if (g_str_has_prefix(uri, "http://")) { + u = g_strdup(uri); + } else if (g_str_has_prefix(uri, "soundcloud.com")) { + u = g_strconcat("http://", uri, NULL); + } else { + /* assume it's just a path on soundcloud.com */ + u = g_strconcat("http://soundcloud.com/", uri, NULL); + } + + ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=", + u, "&client_id=", soundcloud_config.apikey, NULL); + g_free(u); + + return ru; +} + +/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */ + +enum key { + Duration, + Title, + Stream_URL, + Other, +}; + +const char* key_str[] = { + "duration", + "title", + "stream_url", + NULL, +}; + +struct parse_data { + int key; + char* stream_url; + long duration; + char* title; + int got_url; /* nesting level of last stream_url */ + + std::forward_list<SongPointer> songs; +}; + +static int handle_integer(void *ctx, + long +#ifndef HAVE_YAJL1 + long +#endif + intval) +{ + struct parse_data *data = (struct parse_data *) ctx; + + switch (data->key) { + case Duration: + data->duration = intval; + break; + default: + break; + } + + return 1; +} + +static int handle_string(void *ctx, const unsigned char* stringval, +#ifdef HAVE_YAJL1 + unsigned int +#else + size_t +#endif + stringlen) +{ + struct parse_data *data = (struct parse_data *) ctx; + const char *s = (const char *) stringval; + + switch (data->key) { + case Title: + if (data->title != NULL) + g_free(data->title); + data->title = g_strndup(s, stringlen); + break; + case Stream_URL: + if (data->stream_url != NULL) + g_free(data->stream_url); + data->stream_url = g_strndup(s, stringlen); + data->got_url = 1; + break; + default: + break; + } + + return 1; +} + +static int handle_mapkey(void *ctx, const unsigned char* stringval, +#ifdef HAVE_YAJL1 + unsigned int +#else + size_t +#endif + stringlen) +{ + struct parse_data *data = (struct parse_data *) ctx; + + int i; + data->key = Other; + + for (i = 0; i < Other; ++i) { + if (strncmp((const char *)stringval, key_str[i], stringlen) == 0) { + data->key = i; + break; + } + } + + return 1; +} + +static int handle_start_map(void *ctx) +{ + struct parse_data *data = (struct parse_data *) ctx; + + if (data->got_url > 0) + data->got_url++; + + return 1; +} + +static int handle_end_map(void *ctx) +{ + struct parse_data *data = (struct parse_data *) ctx; + + if (data->got_url > 1) { + data->got_url--; + return 1; + } + + if (data->got_url == 0) + return 1; + + /* got_url == 1, track finished, make it into a song */ + data->got_url = 0; + + struct song *s; + struct tag *t; + char *u; + + u = g_strconcat(data->stream_url, "?client_id=", soundcloud_config.apikey, NULL); + s = song_remote_new(u); + g_free(u); + t = tag_new(); + t->time = data->duration / 1000; + if (data->title != NULL) + tag_add_item(t, TAG_NAME, data->title); + s->tag = t; + + data->songs.emplace_front(s); + + return 1; +} + +static yajl_callbacks parse_callbacks = { + NULL, + NULL, + handle_integer, + NULL, + NULL, + handle_string, + handle_start_map, + handle_mapkey, + handle_end_map, + NULL, + NULL, +}; + +/** + * Read JSON data and parse it using the given YAJL parser. + * @param url URL of the JSON data. + * @param hand YAJL parser handle. + * @return -1 on error, 0 on success. + */ +static int +soundcloud_parse_json(const char *url, yajl_handle hand, + Mutex &mutex, Cond &cond) +{ + struct input_stream *input_stream; + GError *error = NULL; + char buffer[4096]; + unsigned char *ubuffer = (unsigned char *)buffer; + size_t nbytes; + + input_stream = input_stream_open(url, mutex, cond, &error); + if (input_stream == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + return -1; + } + + mutex.lock(); + input_stream_wait_ready(input_stream); + + yajl_status stat; + int done = 0; + + while (!done) { + nbytes = input_stream_read(input_stream, buffer, sizeof(buffer), &error); + if (nbytes == 0) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + if (input_stream_eof(input_stream)) { + done = true; + } else { + mutex.unlock(); + input_stream_close(input_stream); + return -1; + } + } + + if (done) { +#ifdef HAVE_YAJL1 + stat = yajl_parse_complete(hand); +#else + stat = yajl_complete_parse(hand); +#endif + } else + stat = yajl_parse(hand, ubuffer, nbytes); + + if (stat != yajl_status_ok +#ifdef HAVE_YAJL1 + && stat != yajl_status_insufficient_data +#endif + ) + { + unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes); + g_warning("%s", str); + yajl_free_error(hand, str); + break; + } + } + + mutex.unlock(); + input_stream_close(input_stream); + + return 0; +} + +/** + * Parse a soundcloud:// URL and create a playlist. + * @param uri A soundcloud URL. Accepted forms: + * soundcloud://track/<track-id> + * soundcloud://playlist/<playlist-id> + * soundcloud://url/<url or path of soundcloud page> + */ + +static struct playlist_provider * +soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond) +{ + char *s, *p; + char *scheme, *arg, *rest; + s = g_strdup(uri); + scheme = s; + for (p = s; *p; p++) { + if (*p == ':' && *(p+1) == '/' && *(p+2) == '/') { + *p = 0; + p += 3; + break; + } + } + arg = p; + for (; *p; p++) { + if (*p == '/') { + *p = 0; + p++; + break; + } + } + rest = p; + + if (strcmp(scheme, "soundcloud") != 0) { + g_warning("incompatible scheme for soundcloud plugin: %s", scheme); + g_free(s); + return NULL; + } + + char *u = NULL; + if (strcmp(arg, "track") == 0) { + u = g_strconcat("http://api.soundcloud.com/tracks/", + rest, ".json?client_id=", soundcloud_config.apikey, NULL); + } else if (strcmp(arg, "playlist") == 0) { + u = g_strconcat("http://api.soundcloud.com/playlists/", + rest, ".json?client_id=", soundcloud_config.apikey, NULL); + } else if (strcmp(arg, "url") == 0) { + /* Translate to soundcloud resolver call. libcurl will automatically + follow the redirect to the right resource. */ + u = soundcloud_resolve(rest); + } + g_free(s); + + if (u == NULL) { + g_warning("unknown soundcloud URI"); + return NULL; + } + + yajl_handle hand; + struct parse_data data; + + data.got_url = 0; + data.title = NULL; + data.stream_url = NULL; +#ifdef HAVE_YAJL1 + hand = yajl_alloc(&parse_callbacks, NULL, NULL, (void *) &data); +#else + hand = yajl_alloc(&parse_callbacks, NULL, (void *) &data); +#endif + + int ret = soundcloud_parse_json(u, hand, mutex, cond); + + g_free(u); + yajl_free(hand); + if (data.title != NULL) + g_free(data.title); + if (data.stream_url != NULL) + g_free(data.stream_url); + + if (ret == -1) + return NULL; + + data.songs.reverse(); + return new MemoryPlaylistProvider(std::move(data.songs)); +} + +static const char *const soundcloud_schemes[] = { + "soundcloud", + NULL +}; + +const struct playlist_plugin soundcloud_playlist_plugin = { + "soundcloud", + + soundcloud_init, + soundcloud_finish, + soundcloud_open_uri, + nullptr, + nullptr, + nullptr, + + soundcloud_schemes, + nullptr, + nullptr, +}; + + diff --git a/src/playlist/SoundCloudPlaylistPlugin.hxx b/src/playlist/SoundCloudPlaylistPlugin.hxx new file mode 100644 index 000000000..7c121328c --- /dev/null +++ b/src/playlist/SoundCloudPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX +#define MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin soundcloud_playlist_plugin; + +#endif diff --git a/src/playlist/XspfPlaylistPlugin.cxx b/src/playlist/XspfPlaylistPlugin.cxx new file mode 100644 index 000000000..bd84d86b7 --- /dev/null +++ b/src/playlist/XspfPlaylistPlugin.cxx @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "XspfPlaylistPlugin.hxx" +#include "MemoryPlaylistProvider.hxx" +#include "input_stream.h" +#include "uri.h" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "xspf" + +/** + * This is the state object for the GLib XML parser. + */ +struct XspfParser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + std::forward_list<SongPointer> songs; + + /** + * The current position in the XML file. + */ + enum { + ROOT, PLAYLIST, TRACKLIST, TRACK, + LOCATION, + } state; + + /** + * The current tag within the "track" element. This is only + * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there + * is no (known) tag. + */ + enum tag_type tag; + + /** + * The current song. It is allocated after the "location" + * element. + */ + struct song *song; + + XspfParser() + :state(ROOT) {} +}; + +static void +xspf_start_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + G_GNUC_UNUSED const gchar **attribute_names, + G_GNUC_UNUSED const gchar **attribute_values, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + XspfParser *parser = (XspfParser *)user_data; + + switch (parser->state) { + case XspfParser::ROOT: + if (strcmp(element_name, "playlist") == 0) + parser->state = XspfParser::PLAYLIST; + + break; + + case XspfParser::PLAYLIST: + if (strcmp(element_name, "trackList") == 0) + parser->state = XspfParser::TRACKLIST; + + break; + + case XspfParser::TRACKLIST: + if (strcmp(element_name, "track") == 0) { + parser->state = XspfParser::TRACK; + parser->song = NULL; + parser->tag = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case XspfParser::TRACK: + if (strcmp(element_name, "location") == 0) + parser->state = XspfParser::LOCATION; + else if (strcmp(element_name, "title") == 0) + parser->tag = TAG_TITLE; + else if (strcmp(element_name, "creator") == 0) + /* TAG_COMPOSER would be more correct + according to the XSPF spec */ + parser->tag = TAG_ARTIST; + else if (strcmp(element_name, "annotation") == 0) + parser->tag = TAG_COMMENT; + else if (strcmp(element_name, "album") == 0) + parser->tag = TAG_ALBUM; + else if (strcmp(element_name, "trackNum") == 0) + parser->tag = TAG_TRACK; + + break; + + case XspfParser::LOCATION: + break; + } +} + +static void +xspf_end_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + XspfParser *parser = (XspfParser *)user_data; + + switch (parser->state) { + case XspfParser::ROOT: + break; + + case XspfParser::PLAYLIST: + if (strcmp(element_name, "playlist") == 0) + parser->state = XspfParser::ROOT; + + break; + + case XspfParser::TRACKLIST: + if (strcmp(element_name, "tracklist") == 0) + parser->state = XspfParser::PLAYLIST; + + break; + + case XspfParser::TRACK: + if (strcmp(element_name, "track") == 0) { + if (parser->song != NULL) + parser->songs.emplace_front(parser->song); + + parser->state = XspfParser::TRACKLIST; + } else + parser->tag = TAG_NUM_OF_ITEM_TYPES; + + break; + + case XspfParser::LOCATION: + parser->state = XspfParser::TRACK; + break; + } +} + +static void +xspf_text(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + XspfParser *parser = (XspfParser *)user_data; + + switch (parser->state) { + case XspfParser::ROOT: + case XspfParser::PLAYLIST: + case XspfParser::TRACKLIST: + break; + + case XspfParser::TRACK: + if (parser->song != NULL && + parser->tag != TAG_NUM_OF_ITEM_TYPES) { + if (parser->song->tag == NULL) + parser->song->tag = tag_new(); + tag_add_item_n(parser->song->tag, parser->tag, + text, text_len); + } + + break; + + case XspfParser::LOCATION: + if (parser->song == NULL) { + char *uri = g_strndup(text, text_len); + parser->song = song_remote_new(uri); + g_free(uri); + } + + break; + } +} + +static const GMarkupParser xspf_parser = { + xspf_start_element, + xspf_end_element, + xspf_text, + nullptr, + nullptr, +}; + +static void +xspf_parser_destroy(gpointer data) +{ + XspfParser *parser = (XspfParser *)data; + + if (parser->state >= XspfParser::TRACK && parser->song != NULL) + song_free(parser->song); +} + +/* + * The playlist object + * + */ + +static struct playlist_provider * +xspf_open_stream(struct input_stream *is) +{ + XspfParser parser; + GMarkupParseContext *context; + char buffer[1024]; + size_t nbytes; + bool success; + GError *error = NULL; + + /* parse the XSPF XML file */ + + context = g_markup_parse_context_new(&xspf_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, + &parser, xspf_parser_destroy); + + while (true) { + nbytes = input_stream_lock_read(is, buffer, sizeof(buffer), + &error); + if (nbytes == 0) { + if (error != NULL) { + g_markup_parse_context_free(context); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + break; + } + + success = g_markup_parse_context_parse(context, buffer, nbytes, + &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + } + + success = g_markup_parse_context_end_parse(context, &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + + parser.songs.reverse(); + MemoryPlaylistProvider *playlist = + new MemoryPlaylistProvider(std::move(parser.songs)); + + g_markup_parse_context_free(context); + + return playlist; +} + +static const char *const xspf_suffixes[] = { + "xspf", + NULL +}; + +static const char *const xspf_mime_types[] = { + "application/xspf+xml", + NULL +}; + +const struct playlist_plugin xspf_playlist_plugin = { + "xspf", + + nullptr, + nullptr, + nullptr, + xspf_open_stream, + nullptr, + nullptr, + + nullptr, + xspf_suffixes, + xspf_mime_types, +}; diff --git a/src/playlist/XspfPlaylistPlugin.hxx b/src/playlist/XspfPlaylistPlugin.hxx new file mode 100644 index 000000000..fc9bbd2c6 --- /dev/null +++ b/src/playlist/XspfPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_XSPF_PLAYLIST_PLUGIN_HXX +#define MPD_XSPF_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin xspf_playlist_plugin; + +#endif diff --git a/src/playlist/asx_playlist_plugin.c b/src/playlist/asx_playlist_plugin.c deleted file mode 100644 index 298687859..000000000 --- a/src/playlist/asx_playlist_plugin.c +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/asx_playlist_plugin.h" -#include "playlist_plugin.h" -#include "input_stream.h" -#include "song.h" -#include "tag.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "asx" - -/** - * This is the state object for the GLib XML parser. - */ -struct asx_parser { - /** - * The list of songs (in reverse order because that's faster - * while adding). - */ - GSList *songs; - - /** - * The current position in the XML file. - */ - enum { - ROOT, ENTRY, - } state; - - /** - * The current tag within the "entry" element. This is only - * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there - * is no (known) tag. - */ - enum tag_type tag; - - /** - * The current song. It is allocated after the "location" - * element. - */ - struct song *song; -}; - -static const gchar * -get_attribute(const gchar **attribute_names, const gchar **attribute_values, - const gchar *name) -{ - for (unsigned i = 0; attribute_names[i] != NULL; ++i) - if (g_ascii_strcasecmp(attribute_names[i], name) == 0) - return attribute_values[i]; - - return NULL; -} - -static void -asx_start_element(G_GNUC_UNUSED GMarkupParseContext *context, - const gchar *element_name, - const gchar **attribute_names, - const gchar **attribute_values, - gpointer user_data, G_GNUC_UNUSED GError **error) -{ - struct asx_parser *parser = user_data; - - switch (parser->state) { - case ROOT: - if (g_ascii_strcasecmp(element_name, "entry") == 0) { - parser->state = ENTRY; - parser->song = song_remote_new("asx:"); - parser->tag = TAG_NUM_OF_ITEM_TYPES; - } - - break; - - case ENTRY: - if (g_ascii_strcasecmp(element_name, "ref") == 0) { - const gchar *href = get_attribute(attribute_names, - attribute_values, - "href"); - if (href != NULL) { - /* create new song object, and copy - the existing tag over; we cannot - replace the existing song's URI, - because that attribute is - immutable */ - struct song *song = song_remote_new(href); - - if (parser->song != NULL) { - song->tag = parser->song->tag; - parser->song->tag = NULL; - song_free(parser->song); - } - - parser->song = song; - } - } else if (g_ascii_strcasecmp(element_name, "author") == 0) - /* is that correct? or should it be COMPOSER - or PERFORMER? */ - parser->tag = TAG_ARTIST; - else if (g_ascii_strcasecmp(element_name, "title") == 0) - parser->tag = TAG_TITLE; - - break; - } -} - -static void -asx_end_element(G_GNUC_UNUSED GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, G_GNUC_UNUSED GError **error) -{ - struct asx_parser *parser = user_data; - - switch (parser->state) { - case ROOT: - break; - - case ENTRY: - if (g_ascii_strcasecmp(element_name, "entry") == 0) { - if (strcmp(parser->song->uri, "asx:") != 0) - parser->songs = g_slist_prepend(parser->songs, - parser->song); - else - song_free(parser->song); - - parser->state = ROOT; - } else - parser->tag = TAG_NUM_OF_ITEM_TYPES; - - break; - } -} - -static void -asx_text(G_GNUC_UNUSED GMarkupParseContext *context, - const gchar *text, gsize text_len, - gpointer user_data, G_GNUC_UNUSED GError **error) -{ - struct asx_parser *parser = user_data; - - switch (parser->state) { - case ROOT: - break; - - case ENTRY: - if (parser->tag != TAG_NUM_OF_ITEM_TYPES) { - if (parser->song->tag == NULL) - parser->song->tag = tag_new(); - tag_add_item_n(parser->song->tag, parser->tag, - text, text_len); - } - - break; - } -} - -static const GMarkupParser asx_parser = { - .start_element = asx_start_element, - .end_element = asx_end_element, - .text = asx_text, -}; - -static void -song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct song *song = data; - - song_free(song); -} - -static void -asx_parser_destroy(gpointer data) -{ - struct asx_parser *parser = data; - - if (parser->state >= ENTRY) - song_free(parser->song); - - g_slist_foreach(parser->songs, song_free_callback, NULL); - g_slist_free(parser->songs); -} - -/* - * The playlist object - * - */ - -struct asx_playlist { - struct playlist_provider base; - - GSList *songs; -}; - -static struct playlist_provider * -asx_open_stream(struct input_stream *is) -{ - struct asx_parser parser = { - .songs = NULL, - .state = ROOT, - }; - struct asx_playlist *playlist; - GMarkupParseContext *context; - char buffer[1024]; - size_t nbytes; - bool success; - GError *error = NULL; - - /* parse the ASX XML file */ - - context = g_markup_parse_context_new(&asx_parser, - G_MARKUP_TREAT_CDATA_AS_TEXT, - &parser, asx_parser_destroy); - - while (true) { - nbytes = input_stream_lock_read(is, buffer, sizeof(buffer), - &error); - if (nbytes == 0) { - if (error != NULL) { - g_markup_parse_context_free(context); - g_warning("%s", error->message); - g_error_free(error); - return NULL; - } - - break; - } - - success = g_markup_parse_context_parse(context, buffer, nbytes, - &error); - if (!success) { - g_warning("XML parser failed: %s", error->message); - g_error_free(error); - g_markup_parse_context_free(context); - return NULL; - } - } - - success = g_markup_parse_context_end_parse(context, &error); - if (!success) { - g_warning("XML parser failed: %s", error->message); - g_error_free(error); - g_markup_parse_context_free(context); - return NULL; - } - - /* create a #asx_playlist object from the parsed song list */ - - playlist = g_new(struct asx_playlist, 1); - playlist_provider_init(&playlist->base, &asx_playlist_plugin); - playlist->songs = g_slist_reverse(parser.songs); - parser.songs = NULL; - - g_markup_parse_context_free(context); - - return &playlist->base; -} - -static void -asx_close(struct playlist_provider *_playlist) -{ - struct asx_playlist *playlist = (struct asx_playlist *)_playlist; - - g_slist_foreach(playlist->songs, song_free_callback, NULL); - g_slist_free(playlist->songs); - g_free(playlist); -} - -static struct song * -asx_read(struct playlist_provider *_playlist) -{ - struct asx_playlist *playlist = (struct asx_playlist *)_playlist; - struct song *song; - - if (playlist->songs == NULL) - return NULL; - - song = playlist->songs->data; - playlist->songs = g_slist_remove(playlist->songs, song); - - return song; -} - -static const char *const asx_suffixes[] = { - "asx", - NULL -}; - -static const char *const asx_mime_types[] = { - "video/x-ms-asf", - NULL -}; - -const struct playlist_plugin asx_playlist_plugin = { - .name = "asx", - - .open_stream = asx_open_stream, - .close = asx_close, - .read = asx_read, - - .suffixes = asx_suffixes, - .mime_types = asx_mime_types, -}; diff --git a/src/playlist/asx_playlist_plugin.h b/src/playlist/asx_playlist_plugin.h deleted file mode 100644 index 6c01c1209..000000000 --- a/src/playlist/asx_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin asx_playlist_plugin; - -#endif diff --git a/src/playlist/cue_playlist_plugin.c b/src/playlist/cue_playlist_plugin.c deleted file mode 100644 index b85de77d3..000000000 --- a/src/playlist/cue_playlist_plugin.c +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/cue_playlist_plugin.h" -#include "playlist_plugin.h" -#include "tag.h" -#include "song.h" -#include "cue/cue_parser.h" -#include "input_stream.h" -#include "text_input_stream.h" - -#include <glib.h> -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "cue" - -struct cue_playlist { - struct playlist_provider base; - - struct input_stream *is; - struct text_input_stream *tis; - struct cue_parser *parser; -}; - -static struct playlist_provider * -cue_playlist_open_stream(struct input_stream *is) -{ - struct cue_playlist *playlist = g_new(struct cue_playlist, 1); - playlist_provider_init(&playlist->base, &cue_playlist_plugin); - - playlist->is = is; - playlist->tis = text_input_stream_new(is); - playlist->parser = cue_parser_new(); - - - return &playlist->base; -} - -static void -cue_playlist_close(struct playlist_provider *_playlist) -{ - struct cue_playlist *playlist = (struct cue_playlist *)_playlist; - - cue_parser_free(playlist->parser); - text_input_stream_free(playlist->tis); - g_free(playlist); -} - -static struct song * -cue_playlist_read(struct playlist_provider *_playlist) -{ - struct cue_playlist *playlist = (struct cue_playlist *)_playlist; - - struct song *song = cue_parser_get(playlist->parser); - if (song != NULL) - return song; - - const char *line; - while ((line = text_input_stream_read(playlist->tis)) != NULL) { - cue_parser_feed(playlist->parser, line); - song = cue_parser_get(playlist->parser); - if (song != NULL) - return song; - } - - cue_parser_finish(playlist->parser); - return cue_parser_get(playlist->parser); -} - -static const char *const cue_playlist_suffixes[] = { - "cue", - NULL -}; - -static const char *const cue_playlist_mime_types[] = { - "application/x-cue", - NULL -}; - -const struct playlist_plugin cue_playlist_plugin = { - .name = "cue", - - .open_stream = cue_playlist_open_stream, - .close = cue_playlist_close, - .read = cue_playlist_read, - - .suffixes = cue_playlist_suffixes, - .mime_types = cue_playlist_mime_types, -}; diff --git a/src/playlist/cue_playlist_plugin.h b/src/playlist/cue_playlist_plugin.h deleted file mode 100644 index c02e2235a..000000000 --- a/src/playlist/cue_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin cue_playlist_plugin; - -#endif diff --git a/src/playlist/despotify_playlist_plugin.c b/src/playlist/despotify_playlist_plugin.c deleted file mode 100644 index 30b852c73..000000000 --- a/src/playlist/despotify_playlist_plugin.c +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/despotify_playlist_plugin.h" -#include "playlist_plugin.h" -#include "playlist_list.h" -#include "conf.h" -#include "uri.h" -#include "tag.h" -#include "song.h" -#include "input_stream.h" -#include "despotify_utils.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <stdlib.h> -#include <despotify.h> - -struct despotify_playlist { - struct playlist_provider base; - - struct despotify_session *session; - GSList *list; -}; - -static void -add_song(struct despotify_playlist *ctx, struct ds_track *track) -{ - const char *dsp_scheme = despotify_playlist_plugin.schemes[0]; - struct song *song; - char uri[128]; - char *ds_uri; - - /* Create a spt://... URI for MPD */ - g_snprintf(uri, sizeof(uri), "%s://", dsp_scheme); - ds_uri = uri + strlen(dsp_scheme) + 3; - - if (despotify_track_to_uri(track, ds_uri) != ds_uri) { - /* Should never really fail, but let's be sure */ - g_debug("Can't add track %s\n", track->title); - return; - } - - song = song_remote_new(uri); - song->tag = mpd_despotify_tag_from_track(track); - - ctx->list = g_slist_prepend(ctx->list, song); -} - -static bool -parse_track(struct despotify_playlist *ctx, - struct ds_link *link) -{ - struct ds_track *track; - - track = despotify_link_get_track(ctx->session, link); - if (!track) - return false; - add_song(ctx, track); - - return true; -} - -static bool -parse_playlist(struct despotify_playlist *ctx, - struct ds_link *link) -{ - struct ds_playlist *playlist; - struct ds_track *track; - - playlist = despotify_link_get_playlist(ctx->session, link); - if (!playlist) - return false; - - for (track = playlist->tracks; track; track = track->next) - add_song(ctx, track); - - return true; -} - -static bool -despotify_playlist_init(G_GNUC_UNUSED const struct config_param *param) -{ - return true; -} - -static void -despotify_playlist_finish(void) -{ -} - - -static struct playlist_provider * -despotify_playlist_open_uri(const char *url, G_GNUC_UNUSED GMutex *mutex, - G_GNUC_UNUSED GCond *cond) -{ - struct despotify_playlist *ctx; - struct despotify_session *session; - struct ds_link *link; - bool parse_result; - - session = mpd_despotify_get_session(); - if (!session) - goto clean_none; - - /* Get link without spt:// */ - link = despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3); - if (!link) { - g_debug("Can't find %s\n", url); - goto clean_none; - } - - ctx = g_new(struct despotify_playlist, 1); - - ctx->list = NULL; - ctx->session = session; - playlist_provider_init(&ctx->base, &despotify_playlist_plugin); - - switch (link->type) - { - case LINK_TYPE_TRACK: - parse_result = parse_track(ctx, link); - break; - case LINK_TYPE_PLAYLIST: - parse_result = parse_playlist(ctx, link); - break; - default: - parse_result = false; - break; - } - despotify_free_link(link); - if (!parse_result) - goto clean_playlist; - - ctx->list = g_slist_reverse(ctx->list); - - return &ctx->base; - -clean_playlist: - g_slist_free(ctx->list); -clean_none: - - return NULL; -} - -static void -track_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct song *song = (struct song *)data; - - song_free(song); -} - -static void -despotify_playlist_close(struct playlist_provider *_playlist) -{ - struct despotify_playlist *ctx = (struct despotify_playlist *)_playlist; - - g_slist_foreach(ctx->list, track_free_callback, NULL); - g_slist_free(ctx->list); - - g_free(ctx); -} - - -static struct song * -despotify_playlist_read(struct playlist_provider *_playlist) -{ - struct despotify_playlist *ctx = (struct despotify_playlist *)_playlist; - struct song *out; - - if (!ctx->list) - return NULL; - - /* Remove the current track */ - out = ctx->list->data; - ctx->list = g_slist_remove(ctx->list, out); - - return out; -} - - -static const char *const despotify_schemes[] = { - "spt", - NULL -}; - -const struct playlist_plugin despotify_playlist_plugin = { - .name = "despotify", - - .init = despotify_playlist_init, - .finish = despotify_playlist_finish, - .open_uri = despotify_playlist_open_uri, - .read = despotify_playlist_read, - .close = despotify_playlist_close, - - .schemes = despotify_schemes, -}; diff --git a/src/playlist/despotify_playlist_plugin.h b/src/playlist/despotify_playlist_plugin.h deleted file mode 100644 index f8ee20de0..000000000 --- a/src/playlist/despotify_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin despotify_playlist_plugin; - -#endif diff --git a/src/playlist/embcue_playlist_plugin.c b/src/playlist/embcue_playlist_plugin.c deleted file mode 100644 index 6d9a957f9..000000000 --- a/src/playlist/embcue_playlist_plugin.c +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Playlist plugin that reads embedded cue sheets from the "CUESHEET" - * tag of a music file. - */ - -#include "config.h" -#include "playlist/embcue_playlist_plugin.h" -#include "playlist_plugin.h" -#include "tag.h" -#include "tag_handler.h" -#include "tag_file.h" -#include "tag_ape.h" -#include "tag_id3.h" -#include "song.h" -#include "cue/cue_parser.h" - -#include <glib.h> -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "cue" - -struct embcue_playlist { - struct playlist_provider base; - - /** - * This is an override for the CUE's "FILE". An embedded CUE - * sheet must always point to the song file it is contained - * in. - */ - char *filename; - - /** - * The value of the file's "CUESHEET" tag. - */ - char *cuesheet; - - /** - * The offset of the next line within "cuesheet". - */ - char *next; - - struct cue_parser *parser; -}; - -static void -embcue_tag_pair(const char *name, const char *value, void *ctx) -{ - struct embcue_playlist *playlist = ctx; - - if (playlist->cuesheet == NULL && - g_ascii_strcasecmp(name, "cuesheet") == 0) - playlist->cuesheet = g_strdup(value); -} - -static const struct tag_handler embcue_tag_handler = { - .pair = embcue_tag_pair, -}; - -static struct playlist_provider * -embcue_playlist_open_uri(const char *uri, - G_GNUC_UNUSED GMutex *mutex, - G_GNUC_UNUSED GCond *cond) -{ - if (!g_path_is_absolute(uri)) - /* only local files supported */ - return NULL; - - struct embcue_playlist *playlist = g_new(struct embcue_playlist, 1); - playlist_provider_init(&playlist->base, &embcue_playlist_plugin); - playlist->cuesheet = NULL; - - tag_file_scan(uri, &embcue_tag_handler, playlist); - if (playlist->cuesheet == NULL) { - tag_ape_scan2(uri, &embcue_tag_handler, playlist); - if (playlist->cuesheet == NULL) - tag_id3_scan(uri, &embcue_tag_handler, playlist); - } - - if (playlist->cuesheet == NULL) { - /* no "CUESHEET" tag found */ - g_free(playlist); - return NULL; - } - - playlist->filename = g_path_get_basename(uri); - - playlist->next = playlist->cuesheet; - playlist->parser = cue_parser_new(); - - return &playlist->base; -} - -static void -embcue_playlist_close(struct playlist_provider *_playlist) -{ - struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist; - - cue_parser_free(playlist->parser); - g_free(playlist->cuesheet); - g_free(playlist->filename); - g_free(playlist); -} - -static struct song * -embcue_playlist_read(struct playlist_provider *_playlist) -{ - struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist; - - struct song *song = cue_parser_get(playlist->parser); - if (song != NULL) - return song; - - while (*playlist->next != 0) { - const char *line = playlist->next; - char *eol = strpbrk(playlist->next, "\r\n"); - if (eol != NULL) { - /* null-terminate the line */ - *eol = 0; - playlist->next = eol + 1; - } else - /* last line; put the "next" pointer to the - end of the buffer */ - playlist->next += strlen(line); - - cue_parser_feed(playlist->parser, line); - song = cue_parser_get(playlist->parser); - if (song != NULL) - return song_replace_uri(song, playlist->filename); - } - - cue_parser_finish(playlist->parser); - song = cue_parser_get(playlist->parser); - if (song != NULL) - song = song_replace_uri(song, playlist->filename); - return song; -} - -static const char *const embcue_playlist_suffixes[] = { - /* a few codecs that are known to be supported; there are - probably many more */ - "flac", - "mp3", "mp2", - "mp4", "mp4a", "m4b", - "ape", - "wv", - "ogg", "oga", - NULL -}; - -const struct playlist_plugin embcue_playlist_plugin = { - .name = "cue", - - .open_uri = embcue_playlist_open_uri, - .close = embcue_playlist_close, - .read = embcue_playlist_read, - - .suffixes = embcue_playlist_suffixes, - .mime_types = NULL, -}; diff --git a/src/playlist/embcue_playlist_plugin.h b/src/playlist/embcue_playlist_plugin.h deleted file mode 100644 index c5f21b27e..000000000 --- a/src/playlist/embcue_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_EMBCUE_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_EMBCUE_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin embcue_playlist_plugin; - -#endif diff --git a/src/playlist/extm3u_playlist_plugin.c b/src/playlist/extm3u_playlist_plugin.c deleted file mode 100644 index 19be8d1c4..000000000 --- a/src/playlist/extm3u_playlist_plugin.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/extm3u_playlist_plugin.h" -#include "playlist_plugin.h" -#include "text_input_stream.h" -#include "uri.h" -#include "song.h" -#include "tag.h" -#include "string_util.h" - -#include <glib.h> - -#include <string.h> -#include <stdlib.h> - -struct extm3u_playlist { - struct playlist_provider base; - - struct text_input_stream *tis; -}; - -static struct playlist_provider * -extm3u_open_stream(struct input_stream *is) -{ - struct extm3u_playlist *playlist; - const char *line; - - playlist = g_new(struct extm3u_playlist, 1); - playlist->tis = text_input_stream_new(is); - - line = text_input_stream_read(playlist->tis); - if (line == NULL || strcmp(line, "#EXTM3U") != 0) { - /* no EXTM3U header: fall back to the plain m3u - plugin */ - text_input_stream_free(playlist->tis); - g_free(playlist); - return NULL; - } - - playlist_provider_init(&playlist->base, &extm3u_playlist_plugin); - return &playlist->base; -} - -static void -extm3u_close(struct playlist_provider *_playlist) -{ - struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist; - - text_input_stream_free(playlist->tis); - g_free(playlist); -} - -/** - * Parse a EXTINF line. - * - * @param line the rest of the input line after the colon - */ -static struct tag * -extm3u_parse_tag(const char *line) -{ - long duration; - char *endptr; - const char *name; - struct tag *tag; - - duration = strtol(line, &endptr, 10); - if (endptr[0] != ',') - /* malformed line */ - return NULL; - - if (duration < 0) - /* 0 means unknown duration */ - duration = 0; - - name = strchug_fast_c(endptr + 1); - if (*name == 0 && duration == 0) - /* no information available; don't allocate a tag - object */ - return NULL; - - tag = tag_new(); - tag->time = duration; - - /* unfortunately, there is no real specification for the - EXTM3U format, so we must assume that the string after the - comma is opaque, and is just the song name*/ - if (*name != 0) - tag_add_item(tag, TAG_NAME, name); - - return tag; -} - -static struct song * -extm3u_read(struct playlist_provider *_playlist) -{ - struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist; - struct tag *tag = NULL; - const char *line; - struct song *song; - - do { - line = text_input_stream_read(playlist->tis); - if (line == NULL) { - if (tag != NULL) - tag_free(tag); - return NULL; - } - - if (g_str_has_prefix(line, "#EXTINF:")) { - if (tag != NULL) - tag_free(tag); - tag = extm3u_parse_tag(line + 8); - continue; - } - - while (*line != 0 && g_ascii_isspace(*line)) - ++line; - } while (line[0] == '#' || *line == 0); - - song = song_remote_new(line); - song->tag = tag; - return song; -} - -static const char *const extm3u_suffixes[] = { - "m3u", - NULL -}; - -static const char *const extm3u_mime_types[] = { - "audio/x-mpegurl", - NULL -}; - -const struct playlist_plugin extm3u_playlist_plugin = { - .name = "extm3u", - - .open_stream = extm3u_open_stream, - .close = extm3u_close, - .read = extm3u_read, - - .suffixes = extm3u_suffixes, - .mime_types = extm3u_mime_types, -}; diff --git a/src/playlist/extm3u_playlist_plugin.h b/src/playlist/extm3u_playlist_plugin.h deleted file mode 100644 index 5f611ac9c..000000000 --- a/src/playlist/extm3u_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin extm3u_playlist_plugin; - -#endif diff --git a/src/playlist/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c deleted file mode 100644 index ead14deaa..000000000 --- a/src/playlist/lastfm_playlist_plugin.c +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/lastfm_playlist_plugin.h" -#include "playlist_plugin.h" -#include "playlist_list.h" -#include "conf.h" -#include "uri.h" -#include "song.h" -#include "input_stream.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -struct lastfm_playlist { - struct playlist_provider base; - - struct input_stream *is; - - struct playlist_provider *xspf; -}; - -static struct { - char *user; - char *md5; -} lastfm_config; - -static bool -lastfm_init(const struct config_param *param) -{ - const char *user = config_get_block_string(param, "user", NULL); - const char *passwd = config_get_block_string(param, "password", NULL); - - if (user == NULL || passwd == NULL) { - g_debug("disabling the last.fm playlist plugin " - "because account is not configured"); - return false; - } - - lastfm_config.user = g_uri_escape_string(user, NULL, false); - - if (strlen(passwd) != 32) - lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5, - passwd, strlen(passwd)); - else - lastfm_config.md5 = g_strdup(passwd); - - return true; -} - -static void -lastfm_finish(void) -{ - g_free(lastfm_config.user); - g_free(lastfm_config.md5); -} - -/** - * Simple data fetcher. - * @param url path or url of data to fetch. - * @return data fetched, or NULL on error. Must be freed with g_free. - */ -static char * -lastfm_get(const char *url, GMutex *mutex, GCond *cond) -{ - struct input_stream *input_stream; - GError *error = NULL; - char buffer[4096]; - size_t length = 0, nbytes; - - input_stream = input_stream_open(url, mutex, cond, &error); - if (input_stream == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - - return NULL; - } - - g_mutex_lock(mutex); - - input_stream_wait_ready(input_stream); - - do { - nbytes = input_stream_read(input_stream, buffer + length, - sizeof(buffer) - length, &error); - if (nbytes == 0) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - - if (input_stream_eof(input_stream)) - break; - - /* I/O error */ - g_mutex_unlock(mutex); - input_stream_close(input_stream); - return NULL; - } - - length += nbytes; - } while (length < sizeof(buffer)); - - g_mutex_unlock(mutex); - - input_stream_close(input_stream); - return g_strndup(buffer, length); -} - -/** - * Ini-style value fetcher. - * @param response data through which to search. - * @param name name of value to search for. - * @return value for param name in param response or NULL on error. Free with g_free. - */ -static char * -lastfm_find(const char *response, const char *name) -{ - size_t name_length = strlen(name); - - while (true) { - const char *eol = strchr(response, '\n'); - if (eol == NULL) - return NULL; - - if (strncmp(response, name, name_length) == 0 && - response[name_length] == '=') { - response += name_length + 1; - return g_strndup(response, eol - response); - } - - response = eol + 1; - } -} - -static struct playlist_provider * -lastfm_open_uri(const char *uri, GMutex *mutex, GCond *cond) -{ - struct lastfm_playlist *playlist; - GError *error = NULL; - char *p, *q, *response, *session; - - /* handshake */ - - p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?" - "version=1.1.1&platform=linux&" - "username=", lastfm_config.user, "&" - "passwordmd5=", lastfm_config.md5, "&" - "debug=0&partner=", NULL); - response = lastfm_get(p, mutex, cond); - g_free(p); - if (response == NULL) - return NULL; - - /* extract session id from response */ - - session = lastfm_find(response, "session"); - g_free(response); - if (session == NULL) { - g_warning("last.fm handshake failed"); - return NULL; - } - - q = g_uri_escape_string(session, NULL, false); - g_free(session); - session = q; - - g_debug("session='%s'", session); - - /* "adjust" last.fm radio */ - - if (strlen(uri) > 9) { - char *escaped_uri; - - escaped_uri = g_uri_escape_string(uri, NULL, false); - - p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?" - "session=", session, "&url=", escaped_uri, "&debug=0", - NULL); - g_free(escaped_uri); - - response = lastfm_get(p, mutex, cond); - g_free(response); - g_free(p); - - if (response == NULL) { - g_free(session); - return NULL; - } - } - - /* create the playlist object */ - - playlist = g_new(struct lastfm_playlist, 1); - playlist_provider_init(&playlist->base, &lastfm_playlist_plugin); - - /* open the last.fm playlist */ - - p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?" - "sk=", session, "&discovery=0&desktop=1.5.1.31879", - NULL); - g_free(session); - - playlist->is = input_stream_open(p, mutex, cond, &error); - g_free(p); - - if (playlist->is == NULL) { - if (error != NULL) { - g_warning("Failed to load XSPF playlist: %s", - error->message); - g_error_free(error); - } else - g_warning("Failed to load XSPF playlist"); - g_free(playlist); - return NULL; - } - - g_mutex_lock(mutex); - - input_stream_wait_ready(playlist->is); - - /* last.fm does not send a MIME type, we have to fake it here - :-( */ - g_free(playlist->is->mime); - playlist->is->mime = g_strdup("application/xspf+xml"); - - g_mutex_unlock(mutex); - - /* parse the XSPF playlist */ - - playlist->xspf = playlist_list_open_stream(playlist->is, NULL); - if (playlist->xspf == NULL) { - input_stream_close(playlist->is); - g_free(playlist); - g_warning("Failed to parse XSPF playlist"); - return NULL; - } - - return &playlist->base; -} - -static void -lastfm_close(struct playlist_provider *_playlist) -{ - struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist; - - playlist_plugin_close(playlist->xspf); - input_stream_close(playlist->is); - g_free(playlist); -} - -static struct song * -lastfm_read(struct playlist_provider *_playlist) -{ - struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist; - - return playlist_plugin_read(playlist->xspf); -} - -static const char *const lastfm_schemes[] = { - "lastfm", - NULL -}; - -const struct playlist_plugin lastfm_playlist_plugin = { - .name = "lastfm", - - .init = lastfm_init, - .finish = lastfm_finish, - .open_uri = lastfm_open_uri, - .close = lastfm_close, - .read = lastfm_read, - - .schemes = lastfm_schemes, -}; diff --git a/src/playlist/lastfm_playlist_plugin.h b/src/playlist/lastfm_playlist_plugin.h deleted file mode 100644 index 46a8b0caf..000000000 --- a/src/playlist/lastfm_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin lastfm_playlist_plugin; - -#endif diff --git a/src/playlist/m3u_playlist_plugin.c b/src/playlist/m3u_playlist_plugin.c deleted file mode 100644 index 45b70d2b1..000000000 --- a/src/playlist/m3u_playlist_plugin.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/m3u_playlist_plugin.h" -#include "playlist_plugin.h" -#include "text_input_stream.h" -#include "uri.h" -#include "song.h" - -#include <glib.h> - -struct m3u_playlist { - struct playlist_provider base; - - struct text_input_stream *tis; -}; - -static struct playlist_provider * -m3u_open_stream(struct input_stream *is) -{ - struct m3u_playlist *playlist = g_new(struct m3u_playlist, 1); - - playlist_provider_init(&playlist->base, &m3u_playlist_plugin); - playlist->tis = text_input_stream_new(is); - - return &playlist->base; -} - -static void -m3u_close(struct playlist_provider *_playlist) -{ - struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist; - - text_input_stream_free(playlist->tis); - g_free(playlist); -} - -static struct song * -m3u_read(struct playlist_provider *_playlist) -{ - struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist; - const char *line; - - do { - line = text_input_stream_read(playlist->tis); - if (line == NULL) - return NULL; - - while (*line != 0 && g_ascii_isspace(*line)) - ++line; - } while (line[0] == '#' || *line == 0); - - return song_remote_new(line); -} - -static const char *const m3u_suffixes[] = { - "m3u", - NULL -}; - -static const char *const m3u_mime_types[] = { - "audio/x-mpegurl", - NULL -}; - -const struct playlist_plugin m3u_playlist_plugin = { - .name = "m3u", - - .open_stream = m3u_open_stream, - .close = m3u_close, - .read = m3u_read, - - .suffixes = m3u_suffixes, - .mime_types = m3u_mime_types, -}; diff --git a/src/playlist/m3u_playlist_plugin.h b/src/playlist/m3u_playlist_plugin.h deleted file mode 100644 index 3890a5fc2..000000000 --- a/src/playlist/m3u_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin m3u_playlist_plugin; - -#endif diff --git a/src/playlist/pls_playlist_plugin.c b/src/playlist/pls_playlist_plugin.c deleted file mode 100644 index c4e5492af..000000000 --- a/src/playlist/pls_playlist_plugin.c +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/pls_playlist_plugin.h" -#include "playlist_plugin.h" -#include "input_stream.h" -#include "uri.h" -#include "song.h" -#include "tag.h" -#include <glib.h> - -struct pls_playlist { - struct playlist_provider base; - - GSList *songs; -}; - -static void pls_parser(GKeyFile *keyfile, struct pls_playlist *playlist) -{ - gchar *key; - gchar *value; - int length; - GError *error = NULL; - int num_entries = g_key_file_get_integer(keyfile, "playlist", - "NumberOfEntries", &error); - if (error) { - g_debug("Invalid PLS file: '%s'", error->message); - g_error_free(error); - error = NULL; - - /* Hack to work around shoutcast failure to comform to spec */ - num_entries = g_key_file_get_integer(keyfile, "playlist", - "numberofentries", &error); - if (error) { - g_error_free(error); - error = NULL; - } - } - - while (num_entries > 0) { - struct song *song; - key = g_strdup_printf("File%i", num_entries); - value = g_key_file_get_string(keyfile, "playlist", key, - &error); - if(error) { - g_debug("Invalid PLS entry %s: '%s'",key, error->message); - g_error_free(error); - g_free(key); - return; - } - g_free(key); - - song = song_remote_new(value); - g_free(value); - - key = g_strdup_printf("Title%i", num_entries); - value = g_key_file_get_string(keyfile, "playlist", key, - &error); - g_free(key); - if(error == NULL && value){ - if (song->tag == NULL) - song->tag = tag_new(); - tag_add_item(song->tag,TAG_TITLE, value); - } - /* Ignore errors? Most likely value not present */ - if(error) g_error_free(error); - error = NULL; - g_free(value); - - key = g_strdup_printf("Length%i", num_entries); - length = g_key_file_get_integer(keyfile, "playlist", key, - &error); - g_free(key); - if(error == NULL && length > 0){ - if (song->tag == NULL) - song->tag = tag_new(); - song->tag->time = length; - } - /* Ignore errors? Most likely value not present */ - if(error) g_error_free(error); - error = NULL; - - playlist->songs = g_slist_prepend(playlist->songs, song); - num_entries--; - } - -} - -static struct playlist_provider * -pls_open_stream(struct input_stream *is) -{ - GError *error = NULL; - size_t nbytes; - char buffer[1024]; - bool success; - GKeyFile *keyfile; - struct pls_playlist *playlist; - GString *kf_data = g_string_new(""); - - do { - nbytes = input_stream_lock_read(is, buffer, sizeof(buffer), - &error); - if (nbytes == 0) { - if (error != NULL) { - g_string_free(kf_data, TRUE); - g_warning("%s", error->message); - g_error_free(error); - return NULL; - } - - break; - } - - kf_data = g_string_append_len(kf_data, buffer,nbytes); - /* Limit to 64k */ - } while(kf_data->len < 65536); - - if (kf_data->len == 0) { - g_warning("KeyFile parser failed: No Data"); - g_string_free(kf_data, TRUE); - return NULL; - } - - keyfile = g_key_file_new(); - success = g_key_file_load_from_data(keyfile, - kf_data->str, kf_data->len, - G_KEY_FILE_NONE, &error); - - g_string_free(kf_data, TRUE); - - if (!success) { - g_warning("KeyFile parser failed: %s", error->message); - g_error_free(error); - g_key_file_free(keyfile); - return NULL; - } - - playlist = g_new(struct pls_playlist, 1); - playlist_provider_init(&playlist->base, &pls_playlist_plugin); - playlist->songs = NULL; - - pls_parser(keyfile, playlist); - - g_key_file_free(keyfile); - return &playlist->base; -} - - -static void -song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct song *song = data; - - song_free(song); -} - -static void -pls_close(struct playlist_provider *_playlist) -{ - struct pls_playlist *playlist = (struct pls_playlist *)_playlist; - - g_slist_foreach(playlist->songs, song_free_callback, NULL); - g_slist_free(playlist->songs); - - g_free(playlist); - -} - -static struct song * -pls_read(struct playlist_provider *_playlist) -{ - struct pls_playlist *playlist = (struct pls_playlist *)_playlist; - struct song *song; - - if (playlist->songs == NULL) - return NULL; - - song = playlist->songs->data; - playlist->songs = g_slist_remove(playlist->songs, song); - - return song; -} - -static const char *const pls_suffixes[] = { - "pls", - NULL -}; - -static const char *const pls_mime_types[] = { - "audio/x-scpls", - NULL -}; - -const struct playlist_plugin pls_playlist_plugin = { - .name = "pls", - - .open_stream = pls_open_stream, - .close = pls_close, - .read = pls_read, - - .suffixes = pls_suffixes, - .mime_types = pls_mime_types, -}; diff --git a/src/playlist/pls_playlist_plugin.h b/src/playlist/pls_playlist_plugin.h deleted file mode 100644 index d03435f6d..000000000 --- a/src/playlist/pls_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin pls_playlist_plugin; - -#endif diff --git a/src/playlist/rss_playlist_plugin.c b/src/playlist/rss_playlist_plugin.c deleted file mode 100644 index 6740cba7e..000000000 --- a/src/playlist/rss_playlist_plugin.c +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/rss_playlist_plugin.h" -#include "playlist_plugin.h" -#include "input_stream.h" -#include "song.h" -#include "tag.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "rss" - -/** - * This is the state object for the GLib XML parser. - */ -struct rss_parser { - /** - * The list of songs (in reverse order because that's faster - * while adding). - */ - GSList *songs; - - /** - * The current position in the XML file. - */ - enum { - ROOT, ITEM, - } state; - - /** - * The current tag within the "entry" element. This is only - * valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there - * is no (known) tag. - */ - enum tag_type tag; - - /** - * The current song. It is allocated after the "location" - * element. - */ - struct song *song; -}; - -static const gchar * -get_attribute(const gchar **attribute_names, const gchar **attribute_values, - const gchar *name) -{ - for (unsigned i = 0; attribute_names[i] != NULL; ++i) - if (g_ascii_strcasecmp(attribute_names[i], name) == 0) - return attribute_values[i]; - - return NULL; -} - -static void -rss_start_element(G_GNUC_UNUSED GMarkupParseContext *context, - const gchar *element_name, - const gchar **attribute_names, - const gchar **attribute_values, - gpointer user_data, G_GNUC_UNUSED GError **error) -{ - struct rss_parser *parser = user_data; - - switch (parser->state) { - case ROOT: - if (g_ascii_strcasecmp(element_name, "item") == 0) { - parser->state = ITEM; - parser->song = song_remote_new("rss:"); - parser->tag = TAG_NUM_OF_ITEM_TYPES; - } - - break; - - case ITEM: - if (g_ascii_strcasecmp(element_name, "enclosure") == 0) { - const gchar *href = get_attribute(attribute_names, - attribute_values, - "url"); - if (href != NULL) { - /* create new song object, and copy - the existing tag over; we cannot - replace the existing song's URI, - because that attribute is - immutable */ - struct song *song = song_remote_new(href); - - if (parser->song != NULL) { - song->tag = parser->song->tag; - parser->song->tag = NULL; - song_free(parser->song); - } - - parser->song = song; - } - } else if (g_ascii_strcasecmp(element_name, "title") == 0) - parser->tag = TAG_TITLE; - else if (g_ascii_strcasecmp(element_name, "itunes:author") == 0) - parser->tag = TAG_ARTIST; - - break; - } -} - -static void -rss_end_element(G_GNUC_UNUSED GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, G_GNUC_UNUSED GError **error) -{ - struct rss_parser *parser = user_data; - - switch (parser->state) { - case ROOT: - break; - - case ITEM: - if (g_ascii_strcasecmp(element_name, "item") == 0) { - if (strcmp(parser->song->uri, "rss:") != 0) - parser->songs = g_slist_prepend(parser->songs, - parser->song); - else - song_free(parser->song); - - parser->state = ROOT; - } else - parser->tag = TAG_NUM_OF_ITEM_TYPES; - - break; - } -} - -static void -rss_text(G_GNUC_UNUSED GMarkupParseContext *context, - const gchar *text, gsize text_len, - gpointer user_data, G_GNUC_UNUSED GError **error) -{ - struct rss_parser *parser = user_data; - - switch (parser->state) { - case ROOT: - break; - - case ITEM: - if (parser->tag != TAG_NUM_OF_ITEM_TYPES) { - if (parser->song->tag == NULL) - parser->song->tag = tag_new(); - tag_add_item_n(parser->song->tag, parser->tag, - text, text_len); - } - - break; - } -} - -static const GMarkupParser rss_parser = { - .start_element = rss_start_element, - .end_element = rss_end_element, - .text = rss_text, -}; - -static void -song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct song *song = data; - - song_free(song); -} - -static void -rss_parser_destroy(gpointer data) -{ - struct rss_parser *parser = data; - - if (parser->state >= ITEM) - song_free(parser->song); - - g_slist_foreach(parser->songs, song_free_callback, NULL); - g_slist_free(parser->songs); -} - -/* - * The playlist object - * - */ - -struct rss_playlist { - struct playlist_provider base; - - GSList *songs; -}; - -static struct playlist_provider * -rss_open_stream(struct input_stream *is) -{ - struct rss_parser parser = { - .songs = NULL, - .state = ROOT, - }; - struct rss_playlist *playlist; - GMarkupParseContext *context; - char buffer[1024]; - size_t nbytes; - bool success; - GError *error = NULL; - - /* parse the RSS XML file */ - - context = g_markup_parse_context_new(&rss_parser, - G_MARKUP_TREAT_CDATA_AS_TEXT, - &parser, rss_parser_destroy); - - while (true) { - nbytes = input_stream_lock_read(is, buffer, sizeof(buffer), - &error); - if (nbytes == 0) { - if (error != NULL) { - g_markup_parse_context_free(context); - g_warning("%s", error->message); - g_error_free(error); - return NULL; - } - - break; - } - - success = g_markup_parse_context_parse(context, buffer, nbytes, - &error); - if (!success) { - g_warning("XML parser failed: %s", error->message); - g_error_free(error); - g_markup_parse_context_free(context); - return NULL; - } - } - - success = g_markup_parse_context_end_parse(context, &error); - if (!success) { - g_warning("XML parser failed: %s", error->message); - g_error_free(error); - g_markup_parse_context_free(context); - return NULL; - } - - /* create a #rss_playlist object from the parsed song list */ - - playlist = g_new(struct rss_playlist, 1); - playlist_provider_init(&playlist->base, &rss_playlist_plugin); - playlist->songs = g_slist_reverse(parser.songs); - parser.songs = NULL; - - g_markup_parse_context_free(context); - - return &playlist->base; -} - -static void -rss_close(struct playlist_provider *_playlist) -{ - struct rss_playlist *playlist = (struct rss_playlist *)_playlist; - - g_slist_foreach(playlist->songs, song_free_callback, NULL); - g_slist_free(playlist->songs); - g_free(playlist); -} - -static struct song * -rss_read(struct playlist_provider *_playlist) -{ - struct rss_playlist *playlist = (struct rss_playlist *)_playlist; - struct song *song; - - if (playlist->songs == NULL) - return NULL; - - song = playlist->songs->data; - playlist->songs = g_slist_remove(playlist->songs, song); - - return song; -} - -static const char *const rss_suffixes[] = { - "rss", - NULL -}; - -static const char *const rss_mime_types[] = { - "application/rss+xml", - "text/xml", - NULL -}; - -const struct playlist_plugin rss_playlist_plugin = { - .name = "rss", - - .open_stream = rss_open_stream, - .close = rss_close, - .read = rss_read, - - .suffixes = rss_suffixes, - .mime_types = rss_mime_types, -}; diff --git a/src/playlist/rss_playlist_plugin.h b/src/playlist/rss_playlist_plugin.h deleted file mode 100644 index 3b376de79..000000000 --- a/src/playlist/rss_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_RSS_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_RSS_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin rss_playlist_plugin; - -#endif diff --git a/src/playlist/soundcloud_playlist_plugin.c b/src/playlist/soundcloud_playlist_plugin.c deleted file mode 100644 index 7c79f880a..000000000 --- a/src/playlist/soundcloud_playlist_plugin.c +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/soundcloud_playlist_plugin.h" -#include "conf.h" -#include "input_stream.h" -#include "playlist_plugin.h" -#include "song.h" -#include "tag.h" - -#include <glib.h> -#include <yajl/yajl_parse.h> - -#include <string.h> - -struct soundcloud_playlist { - struct playlist_provider base; - - GSList *songs; -}; - -static struct { - char *apikey; -} soundcloud_config; - -static bool -soundcloud_init(const struct config_param *param) -{ - soundcloud_config.apikey = - config_dup_block_string(param, "apikey", NULL); - if (soundcloud_config.apikey == NULL) { - g_debug("disabling the soundcloud playlist plugin " - "because API key is not set"); - return false; - } - - return true; -} - -static void -soundcloud_finish(void) -{ - g_free(soundcloud_config.apikey); -} - -/** - * Construct a full soundcloud resolver URL from the given fragment. - * @param uri uri of a soundcloud page (or just the path) - * @return Constructed URL. Must be freed with g_free. - */ -static char * -soundcloud_resolve(const char* uri) { - char *u, *ru; - - if (g_str_has_prefix(uri, "http://")) { - u = g_strdup(uri); - } else if (g_str_has_prefix(uri, "soundcloud.com")) { - u = g_strconcat("http://", uri, NULL); - } else { - /* assume it's just a path on soundcloud.com */ - u = g_strconcat("http://soundcloud.com/", uri, NULL); - } - - ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=", - u, "&client_id=", soundcloud_config.apikey, NULL); - g_free(u); - - return ru; -} - -/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */ - -enum key { - Duration, - Title, - Stream_URL, - Other, -}; - -const char* key_str[] = { - "duration", - "title", - "stream_url", - NULL, -}; - -struct parse_data { - int key; - char* stream_url; - long duration; - char* title; - int got_url; /* nesting level of last stream_url */ - GSList* songs; -}; - -static int handle_integer(void *ctx, - long -#ifndef HAVE_YAJL1 - long -#endif - intval) -{ - struct parse_data *data = (struct parse_data *) ctx; - - switch (data->key) { - case Duration: - data->duration = intval; - break; - default: - break; - } - - return 1; -} - -static int handle_string(void *ctx, const unsigned char* stringval, -#ifdef HAVE_YAJL1 - unsigned int -#else - size_t -#endif - stringlen) -{ - struct parse_data *data = (struct parse_data *) ctx; - const char *s = (const char *) stringval; - - switch (data->key) { - case Title: - if (data->title != NULL) - g_free(data->title); - data->title = g_strndup(s, stringlen); - break; - case Stream_URL: - if (data->stream_url != NULL) - g_free(data->stream_url); - data->stream_url = g_strndup(s, stringlen); - data->got_url = 1; - break; - default: - break; - } - - return 1; -} - -static int handle_mapkey(void *ctx, const unsigned char* stringval, -#ifdef HAVE_YAJL1 - unsigned int -#else - size_t -#endif - stringlen) -{ - struct parse_data *data = (struct parse_data *) ctx; - - int i; - data->key = Other; - - for (i = 0; i < Other; ++i) { - if (strncmp((const char *)stringval, key_str[i], stringlen) == 0) { - data->key = i; - break; - } - } - - return 1; -} - -static int handle_start_map(void *ctx) -{ - struct parse_data *data = (struct parse_data *) ctx; - - if (data->got_url > 0) - data->got_url++; - - return 1; -} - -static int handle_end_map(void *ctx) -{ - struct parse_data *data = (struct parse_data *) ctx; - - if (data->got_url > 1) { - data->got_url--; - return 1; - } - - if (data->got_url == 0) - return 1; - - /* got_url == 1, track finished, make it into a song */ - data->got_url = 0; - - struct song *s; - struct tag *t; - char *u; - - u = g_strconcat(data->stream_url, "?client_id=", soundcloud_config.apikey, NULL); - s = song_remote_new(u); - g_free(u); - t = tag_new(); - t->time = data->duration / 1000; - if (data->title != NULL) - tag_add_item(t, TAG_NAME, data->title); - s->tag = t; - - data->songs = g_slist_prepend(data->songs, s); - - return 1; -} - -static yajl_callbacks parse_callbacks = { - NULL, - NULL, - handle_integer, - NULL, - NULL, - handle_string, - handle_start_map, - handle_mapkey, - handle_end_map, - NULL, - NULL, -}; - -/** - * Read JSON data and parse it using the given YAJL parser. - * @param url URL of the JSON data. - * @param hand YAJL parser handle. - * @return -1 on error, 0 on success. - */ -static int -soundcloud_parse_json(const char *url, yajl_handle hand, GMutex* mutex, GCond* cond) -{ - struct input_stream *input_stream; - GError *error = NULL; - char buffer[4096]; - unsigned char *ubuffer = (unsigned char *)buffer; - size_t nbytes; - - input_stream = input_stream_open(url, mutex, cond, &error); - if (input_stream == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - return -1; - } - - g_mutex_lock(mutex); - input_stream_wait_ready(input_stream); - - yajl_status stat; - int done = 0; - - while (!done) { - nbytes = input_stream_read(input_stream, buffer, sizeof(buffer), &error); - if (nbytes == 0) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - if (input_stream_eof(input_stream)) { - done = true; - } else { - g_mutex_unlock(mutex); - input_stream_close(input_stream); - return -1; - } - } - - if (done) { -#ifdef HAVE_YAJL1 - stat = yajl_parse_complete(hand); -#else - stat = yajl_complete_parse(hand); -#endif - } else - stat = yajl_parse(hand, ubuffer, nbytes); - - if (stat != yajl_status_ok -#ifdef HAVE_YAJL1 - && stat != yajl_status_insufficient_data -#endif - ) - { - unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes); - g_warning("%s", str); - yajl_free_error(hand, str); - break; - } - } - - g_mutex_unlock(mutex); - input_stream_close(input_stream); - - return 0; -} - -/** - * Parse a soundcloud:// URL and create a playlist. - * @param uri A soundcloud URL. Accepted forms: - * soundcloud://track/<track-id> - * soundcloud://playlist/<playlist-id> - * soundcloud://url/<url or path of soundcloud page> - */ - -static struct playlist_provider * -soundcloud_open_uri(const char *uri, GMutex *mutex, GCond *cond) -{ - struct soundcloud_playlist *playlist = NULL; - - char *s, *p; - char *scheme, *arg, *rest; - s = g_strdup(uri); - scheme = s; - for (p = s; *p; p++) { - if (*p == ':' && *(p+1) == '/' && *(p+2) == '/') { - *p = 0; - p += 3; - break; - } - } - arg = p; - for (; *p; p++) { - if (*p == '/') { - *p = 0; - p++; - break; - } - } - rest = p; - - if (strcmp(scheme, "soundcloud") != 0) { - g_warning("incompatible scheme for soundcloud plugin: %s", scheme); - g_free(s); - return NULL; - } - - char *u = NULL; - if (strcmp(arg, "track") == 0) { - u = g_strconcat("http://api.soundcloud.com/tracks/", - rest, ".json?client_id=", soundcloud_config.apikey, NULL); - } else if (strcmp(arg, "playlist") == 0) { - u = g_strconcat("http://api.soundcloud.com/playlists/", - rest, ".json?client_id=", soundcloud_config.apikey, NULL); - } else if (strcmp(arg, "url") == 0) { - /* Translate to soundcloud resolver call. libcurl will automatically - follow the redirect to the right resource. */ - u = soundcloud_resolve(rest); - } - g_free(s); - - if (u == NULL) { - g_warning("unknown soundcloud URI"); - return NULL; - } - - yajl_handle hand; - struct parse_data data; - - data.got_url = 0; - data.songs = NULL; - data.title = NULL; - data.stream_url = NULL; -#ifdef HAVE_YAJL1 - hand = yajl_alloc(&parse_callbacks, NULL, NULL, (void *) &data); -#else - hand = yajl_alloc(&parse_callbacks, NULL, (void *) &data); -#endif - - int ret = soundcloud_parse_json(u, hand, mutex, cond); - - g_free(u); - yajl_free(hand); - if (data.title != NULL) - g_free(data.title); - if (data.stream_url != NULL) - g_free(data.stream_url); - - if (ret == -1) - return NULL; - - playlist = g_new(struct soundcloud_playlist, 1); - playlist_provider_init(&playlist->base, &soundcloud_playlist_plugin); - playlist->songs = g_slist_reverse(data.songs); - - return &playlist->base; -} - -static void -soundcloud_close(struct playlist_provider *_playlist) -{ - struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist; - - g_free(playlist); -} - - -static struct song * -soundcloud_read(struct playlist_provider *_playlist) -{ - struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist; - - if (playlist->songs == NULL) - return NULL; - - struct song* s; - s = (struct song *)playlist->songs->data; - playlist->songs = g_slist_remove(playlist->songs, s); - return s; -} - -static const char *const soundcloud_schemes[] = { - "soundcloud", - NULL -}; - -const struct playlist_plugin soundcloud_playlist_plugin = { - .name = "soundcloud", - - .init = soundcloud_init, - .finish = soundcloud_finish, - .open_uri = soundcloud_open_uri, - .close = soundcloud_close, - .read = soundcloud_read, - - .schemes = soundcloud_schemes, -}; - - diff --git a/src/playlist/soundcloud_playlist_plugin.h b/src/playlist/soundcloud_playlist_plugin.h deleted file mode 100644 index e09e2dd46..000000000 --- a/src/playlist/soundcloud_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_SOUNDCLOUD_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_SOUNDCLOUD_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin soundcloud_playlist_plugin; - -#endif diff --git a/src/playlist/xspf_playlist_plugin.c b/src/playlist/xspf_playlist_plugin.c deleted file mode 100644 index 17d9040e2..000000000 --- a/src/playlist/xspf_playlist_plugin.c +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/xspf_playlist_plugin.h" -#include "playlist_plugin.h" -#include "input_stream.h" -#include "uri.h" -#include "song.h" -#include "tag.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "xspf" - -/** - * This is the state object for the GLib XML parser. - */ -struct xspf_parser { - /** - * The list of songs (in reverse order because that's faster - * while adding). - */ - GSList *songs; - - /** - * The current position in the XML file. - */ - enum { - ROOT, PLAYLIST, TRACKLIST, TRACK, - LOCATION, - } state; - - /** - * The current tag within the "track" element. This is only - * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there - * is no (known) tag. - */ - enum tag_type tag; - - /** - * The current song. It is allocated after the "location" - * element. - */ - struct song *song; -}; - -static void -xspf_start_element(G_GNUC_UNUSED GMarkupParseContext *context, - const gchar *element_name, - G_GNUC_UNUSED const gchar **attribute_names, - G_GNUC_UNUSED const gchar **attribute_values, - gpointer user_data, G_GNUC_UNUSED GError **error) -{ - struct xspf_parser *parser = user_data; - - switch (parser->state) { - case ROOT: - if (strcmp(element_name, "playlist") == 0) - parser->state = PLAYLIST; - - break; - - case PLAYLIST: - if (strcmp(element_name, "trackList") == 0) - parser->state = TRACKLIST; - - break; - - case TRACKLIST: - if (strcmp(element_name, "track") == 0) { - parser->state = TRACK; - parser->song = NULL; - parser->tag = TAG_NUM_OF_ITEM_TYPES; - } - - break; - - case TRACK: - if (strcmp(element_name, "location") == 0) - parser->state = LOCATION; - else if (strcmp(element_name, "title") == 0) - parser->tag = TAG_TITLE; - else if (strcmp(element_name, "creator") == 0) - /* TAG_COMPOSER would be more correct - according to the XSPF spec */ - parser->tag = TAG_ARTIST; - else if (strcmp(element_name, "annotation") == 0) - parser->tag = TAG_COMMENT; - else if (strcmp(element_name, "album") == 0) - parser->tag = TAG_ALBUM; - else if (strcmp(element_name, "trackNum") == 0) - parser->tag = TAG_TRACK; - - break; - - case LOCATION: - break; - } -} - -static void -xspf_end_element(G_GNUC_UNUSED GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, G_GNUC_UNUSED GError **error) -{ - struct xspf_parser *parser = user_data; - - switch (parser->state) { - case ROOT: - break; - - case PLAYLIST: - if (strcmp(element_name, "playlist") == 0) - parser->state = ROOT; - - break; - - case TRACKLIST: - if (strcmp(element_name, "tracklist") == 0) - parser->state = PLAYLIST; - - break; - - case TRACK: - if (strcmp(element_name, "track") == 0) { - if (parser->song != NULL) - parser->songs = g_slist_prepend(parser->songs, - parser->song); - - parser->state = TRACKLIST; - } else - parser->tag = TAG_NUM_OF_ITEM_TYPES; - - break; - - case LOCATION: - parser->state = TRACK; - break; - } -} - -static void -xspf_text(G_GNUC_UNUSED GMarkupParseContext *context, - const gchar *text, gsize text_len, - gpointer user_data, G_GNUC_UNUSED GError **error) -{ - struct xspf_parser *parser = user_data; - - switch (parser->state) { - case ROOT: - case PLAYLIST: - case TRACKLIST: - break; - - case TRACK: - if (parser->song != NULL && - parser->tag != TAG_NUM_OF_ITEM_TYPES) { - if (parser->song->tag == NULL) - parser->song->tag = tag_new(); - tag_add_item_n(parser->song->tag, parser->tag, - text, text_len); - } - - break; - - case LOCATION: - if (parser->song == NULL) { - char *uri = g_strndup(text, text_len); - parser->song = song_remote_new(uri); - g_free(uri); - } - - break; - } -} - -static const GMarkupParser xspf_parser = { - .start_element = xspf_start_element, - .end_element = xspf_end_element, - .text = xspf_text, -}; - -static void -song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct song *song = data; - - song_free(song); -} - -static void -xspf_parser_destroy(gpointer data) -{ - struct xspf_parser *parser = data; - - if (parser->state >= TRACK && parser->song != NULL) - song_free(parser->song); - - g_slist_foreach(parser->songs, song_free_callback, NULL); - g_slist_free(parser->songs); -} - -/* - * The playlist object - * - */ - -struct xspf_playlist { - struct playlist_provider base; - - GSList *songs; -}; - -static struct playlist_provider * -xspf_open_stream(struct input_stream *is) -{ - struct xspf_parser parser = { - .songs = NULL, - .state = ROOT, - }; - struct xspf_playlist *playlist; - GMarkupParseContext *context; - char buffer[1024]; - size_t nbytes; - bool success; - GError *error = NULL; - - /* parse the XSPF XML file */ - - context = g_markup_parse_context_new(&xspf_parser, - G_MARKUP_TREAT_CDATA_AS_TEXT, - &parser, xspf_parser_destroy); - - while (true) { - nbytes = input_stream_lock_read(is, buffer, sizeof(buffer), - &error); - if (nbytes == 0) { - if (error != NULL) { - g_markup_parse_context_free(context); - g_warning("%s", error->message); - g_error_free(error); - return NULL; - } - - break; - } - - success = g_markup_parse_context_parse(context, buffer, nbytes, - &error); - if (!success) { - g_warning("XML parser failed: %s", error->message); - g_error_free(error); - g_markup_parse_context_free(context); - return NULL; - } - } - - success = g_markup_parse_context_end_parse(context, &error); - if (!success) { - g_warning("XML parser failed: %s", error->message); - g_error_free(error); - g_markup_parse_context_free(context); - return NULL; - } - - /* create a #xspf_playlist object from the parsed song list */ - - playlist = g_new(struct xspf_playlist, 1); - playlist_provider_init(&playlist->base, &xspf_playlist_plugin); - playlist->songs = g_slist_reverse(parser.songs); - parser.songs = NULL; - - g_markup_parse_context_free(context); - - return &playlist->base; -} - -static void -xspf_close(struct playlist_provider *_playlist) -{ - struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist; - - g_slist_foreach(playlist->songs, song_free_callback, NULL); - g_slist_free(playlist->songs); - g_free(playlist); -} - -static struct song * -xspf_read(struct playlist_provider *_playlist) -{ - struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist; - struct song *song; - - if (playlist->songs == NULL) - return NULL; - - song = playlist->songs->data; - playlist->songs = g_slist_remove(playlist->songs, song); - - return song; -} - -static const char *const xspf_suffixes[] = { - "xspf", - NULL -}; - -static const char *const xspf_mime_types[] = { - "application/xspf+xml", - NULL -}; - -const struct playlist_plugin xspf_playlist_plugin = { - .name = "xspf", - - .open_stream = xspf_open_stream, - .close = xspf_close, - .read = xspf_read, - - .suffixes = xspf_suffixes, - .mime_types = xspf_mime_types, -}; diff --git a/src/playlist/xspf_playlist_plugin.h b/src/playlist/xspf_playlist_plugin.h deleted file mode 100644 index 4636d7e83..000000000 --- a/src/playlist/xspf_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin xspf_playlist_plugin; - -#endif diff --git a/src/playlist_any.c b/src/playlist_any.c deleted file mode 100644 index 450ca5932..000000000 --- a/src/playlist_any.c +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_any.h" -#include "playlist_list.h" -#include "playlist_mapper.h" -#include "uri.h" -#include "input_stream.h" - -#include <assert.h> - -static struct playlist_provider * -playlist_open_remote(const char *uri, GMutex *mutex, GCond *cond, - struct input_stream **is_r) -{ - assert(uri_has_scheme(uri)); - - struct playlist_provider *playlist = - playlist_list_open_uri(uri, mutex, cond); - if (playlist != NULL) { - *is_r = NULL; - return playlist; - } - - GError *error = NULL; - struct input_stream *is = input_stream_open(uri, mutex, cond, &error); - if (is == NULL) { - if (error != NULL) { - g_warning("Failed to open %s: %s", - uri, error->message); - g_error_free(error); - } - - return NULL; - } - - playlist = playlist_list_open_stream(is, uri); - if (playlist == NULL) { - input_stream_close(is); - return NULL; - } - - *is_r = is; - return playlist; -} - -struct playlist_provider * -playlist_open_any(const char *uri, GMutex *mutex, GCond *cond, - struct input_stream **is_r) -{ - return uri_has_scheme(uri) - ? playlist_open_remote(uri, mutex, cond, is_r) - : playlist_mapper_open(uri, mutex, cond, is_r); -} diff --git a/src/playlist_any.h b/src/playlist_any.h deleted file mode 100644 index 310913de9..000000000 --- a/src/playlist_any.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_ANY_H -#define MPD_PLAYLIST_ANY_H - -#include <glib.h> - -struct playlist_provider; -struct input_stream; - -/** - * Opens a playlist from the specified URI, which can be either an - * absolute remote URI (with a scheme) or a relative path to the - * music orplaylist directory. - * - * @param is_r on success, an input_stream object may be returned - * here, which must be closed after the playlist_provider object is - * freed - */ -struct playlist_provider * -playlist_open_any(const char *uri, GMutex *mutex, GCond *cond, - struct input_stream **is_r); - -#endif diff --git a/src/playlist_control.c b/src/playlist_control.c deleted file mode 100644 index 0dea7676a..000000000 --- a/src/playlist_control.c +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Functions for controlling playback on the playlist level. - * - */ - -#include "config.h" -#include "playlist_internal.h" -#include "player_control.h" -#include "idle.h" - -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "playlist" - -void -playlist_stop(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - return; - - assert(playlist->current >= 0); - - g_debug("stop"); - pc_stop(pc); - playlist->queued = -1; - playlist->playing = false; - - if (playlist->queue.random) { - /* shuffle the playlist, so the next playback will - result in a new random order */ - - unsigned current_position = - queue_order_to_position(&playlist->queue, - playlist->current); - - queue_shuffle_order(&playlist->queue); - - /* make sure that "current" stays valid, and the next - "play" command plays the same song again */ - playlist->current = - queue_position_to_order(&playlist->queue, - current_position); - } -} - -enum playlist_result -playlist_play(struct playlist *playlist, struct player_control *pc, - int song) -{ - unsigned i = song; - - pc_clear_error(pc); - - if (song == -1) { - /* play any song ("current" song, or the first song */ - - if (queue_is_empty(&playlist->queue)) - return PLAYLIST_RESULT_SUCCESS; - - if (playlist->playing) { - /* already playing: unpause playback, just in - case it was paused, and return */ - pc_set_pause(pc, false); - return PLAYLIST_RESULT_SUCCESS; - } - - /* select a song: "current" song, or the first one */ - i = playlist->current >= 0 - ? playlist->current - : 0; - } else if (!queue_valid_position(&playlist->queue, song)) - return PLAYLIST_RESULT_BAD_RANGE; - - if (playlist->queue.random) { - if (song >= 0) - /* "i" is currently the song position (which - would be equal to the order number in - no-random mode); convert it to a order - number, because random mode is enabled */ - i = queue_position_to_order(&playlist->queue, song); - - if (!playlist->playing) - playlist->current = 0; - - /* swap the new song with the previous "current" one, - so playback continues as planned */ - queue_swap_order(&playlist->queue, - i, playlist->current); - i = playlist->current; - } - - playlist->stop_on_error = false; - playlist->error_count = 0; - - playlist_play_order(playlist, pc, i); - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_play_id(struct playlist *playlist, struct player_control *pc, - int id) -{ - int song; - - if (id == -1) { - return playlist_play(playlist, pc, id); - } - - song = queue_id_to_position(&playlist->queue, id); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_play(playlist, pc, song); -} - -void -playlist_next(struct playlist *playlist, struct player_control *pc) -{ - int next_order; - int current; - - if (!playlist->playing) - return; - - assert(!queue_is_empty(&playlist->queue)); - assert(queue_valid_order(&playlist->queue, playlist->current)); - - current = playlist->current; - playlist->stop_on_error = false; - - /* determine the next song from the queue's order list */ - - next_order = queue_next_order(&playlist->queue, playlist->current); - if (next_order < 0) { - /* no song after this one: stop playback */ - playlist_stop(playlist, pc); - - /* reset "current song" */ - playlist->current = -1; - } - else - { - if (next_order == 0 && playlist->queue.random) { - /* The queue told us that the next song is the first - song. This means we are in repeat mode. Shuffle - the queue order, so this time, the user hears the - songs in a different than before */ - assert(playlist->queue.repeat); - - queue_shuffle_order(&playlist->queue); - - /* note that playlist->current and playlist->queued are - now invalid, but playlist_play_order() will - discard them anyway */ - } - - playlist_play_order(playlist, pc, next_order); - } - - /* Consume mode removes each played songs. */ - if(playlist->queue.consume) - playlist_delete(playlist, pc, - queue_order_to_position(&playlist->queue, - current)); -} - -void -playlist_previous(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - return; - - assert(queue_length(&playlist->queue) > 0); - - if (playlist->current > 0) { - /* play the preceding song */ - playlist_play_order(playlist, pc, - playlist->current - 1); - } else if (playlist->queue.repeat) { - /* play the last song in "repeat" mode */ - playlist_play_order(playlist, pc, - queue_length(&playlist->queue) - 1); - } else { - /* re-start playing the current song if it's - the first one */ - playlist_play_order(playlist, pc, playlist->current); - } -} - -enum playlist_result -playlist_seek_song(struct playlist *playlist, struct player_control *pc, - unsigned song, float seek_time) -{ - const struct song *queued; - unsigned i; - bool success; - - if (!queue_valid_position(&playlist->queue, song)) - return PLAYLIST_RESULT_BAD_RANGE; - - queued = playlist_get_queued_song(playlist); - - if (playlist->queue.random) - i = queue_position_to_order(&playlist->queue, song); - else - i = song; - - pc_clear_error(pc); - playlist->stop_on_error = true; - playlist->error_count = 0; - - if (!playlist->playing || (unsigned)playlist->current != i) { - /* seeking is not within the current song - prepare - song change */ - - playlist->playing = true; - playlist->current = i; - - queued = NULL; - } - - success = pc_seek(pc, queue_get_order(&playlist->queue, i), seek_time); - if (!success) { - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_NOT_PLAYING; - } - - playlist->queued = -1; - playlist_update_queued_song(playlist, pc, NULL); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, - unsigned id, float seek_time) -{ - int song = queue_id_to_position(&playlist->queue, id); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_seek_song(playlist, pc, song, seek_time); -} - -enum playlist_result -playlist_seek_current(struct playlist *playlist, struct player_control *pc, - float seek_time, bool relative) -{ - if (!playlist->playing) - return PLAYLIST_RESULT_NOT_PLAYING; - - if (relative) { - struct player_status status; - pc_get_status(pc, &status); - - if (status.state != PLAYER_STATE_PLAY && - status.state != PLAYER_STATE_PAUSE) - return PLAYLIST_RESULT_NOT_PLAYING; - - seek_time += (int)status.elapsed_time; - } - - if (seek_time < 0) - seek_time = 0; - - return playlist_seek_song(playlist, pc, playlist->current, seek_time); -} diff --git a/src/playlist_database.c b/src/playlist_database.c deleted file mode 100644 index 6b9d87155..000000000 --- a/src/playlist_database.c +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_database.h" -#include "playlist_vector.h" -#include "text_file.h" -#include "string_util.h" - -#include <string.h> -#include <stdlib.h> - -static GQuark -playlist_database_quark(void) -{ - return g_quark_from_static_string("playlist_database"); -} - -void -playlist_vector_save(FILE *fp, const struct list_head *pv) -{ - struct playlist_metadata *pm; - playlist_vector_for_each(pm, pv) - fprintf(fp, PLAYLIST_META_BEGIN "%s\n" - "mtime: %li\n" - "playlist_end\n", - pm->name, (long)pm->mtime); -} - -bool -playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name, - GString *buffer, GError **error_r) -{ - struct playlist_metadata pm = { - .mtime = 0, - }; - char *line, *colon; - const char *value; - - while ((line = read_text_line(fp, buffer)) != NULL && - strcmp(line, "playlist_end") != 0) { - colon = strchr(line, ':'); - if (colon == NULL || colon == line) { - g_set_error(error_r, playlist_database_quark(), 0, - "unknown line in db: %s", line); - return false; - } - - *colon++ = 0; - value = strchug_fast_c(colon); - - if (strcmp(line, "mtime") == 0) - pm.mtime = strtol(value, NULL, 10); - else { - g_set_error(error_r, playlist_database_quark(), 0, - "unknown line in db: %s", line); - return false; - } - } - - playlist_vector_update_or_add(pv, name, pm.mtime); - return true; -} diff --git a/src/playlist_database.h b/src/playlist_database.h deleted file mode 100644 index 3238fa06b..000000000 --- a/src/playlist_database.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_DATABASE_H -#define MPD_PLAYLIST_DATABASE_H - -#include "check.h" - -#include <stdbool.h> -#include <stdio.h> -#include <glib.h> - -#define PLAYLIST_META_BEGIN "playlist_begin: " - -struct list_head; - -void -playlist_vector_save(FILE *fp, const struct list_head *pv); - -bool -playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name, - GString *buffer, GError **error_r); - -#endif diff --git a/src/playlist_edit.c b/src/playlist_edit.c deleted file mode 100644 index d10f49451..000000000 --- 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_global.c b/src/playlist_global.c deleted file mode 100644 index 650b88bb8..000000000 --- a/src/playlist_global.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * The manager of the global "struct playlist" instance (g_playlist). - * - */ - -#include "config.h" -#include "playlist.h" -#include "playlist_state.h" -#include "event_pipe.h" -#include "main.h" - -struct playlist g_playlist; - -static void -playlist_tag_event(void) -{ - playlist_tag_changed(&g_playlist); -} - -static void -playlist_event(void) -{ - playlist_sync(&g_playlist, global_player_control); -} - -void -playlist_global_init(void) -{ - playlist_init(&g_playlist); - - event_pipe_register(PIPE_EVENT_TAG, playlist_tag_event); - event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event); -} - -void -playlist_global_finish(void) -{ - playlist_finish(&g_playlist); -} diff --git a/src/playlist_internal.h b/src/playlist_internal.h deleted file mode 100644 index 81b175176..000000000 --- a/src/playlist_internal.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Internal header for the components of the playlist code. - * - */ - -#ifndef PLAYLIST_INTERNAL_H -#define PLAYLIST_INTERNAL_H - -#include "playlist.h" - -struct player_control; - -/** - * Returns the song object which is currently queued. Returns none if - * there is none (yet?) or if MPD isn't playing. - */ -const struct song * -playlist_get_queued_song(struct playlist *playlist); - -/** - * Updates the "queued song". Calculates the next song according to - * the current one (if MPD isn't playing, it takes the first song), - * and queues this song. Clears the old queued song if there was one. - * - * @param prev the song which was previously queued, as determined by - * playlist_get_queued_song() - */ -void -playlist_update_queued_song(struct playlist *playlist, - struct player_control *pc, - const struct song *prev); - -void -playlist_play_order(struct playlist *playlist, struct player_control *pc, - int orderNum); - -#endif diff --git a/src/playlist_list.c b/src/playlist_list.c deleted file mode 100644 index 68d24fe49..000000000 --- a/src/playlist_list.c +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_list.h" -#include "playlist_plugin.h" -#include "playlist/extm3u_playlist_plugin.h" -#include "playlist/m3u_playlist_plugin.h" -#include "playlist/xspf_playlist_plugin.h" -#include "playlist/lastfm_playlist_plugin.h" -#include "playlist/despotify_playlist_plugin.h" -#include "playlist/soundcloud_playlist_plugin.h" -#include "playlist/pls_playlist_plugin.h" -#include "playlist/asx_playlist_plugin.h" -#include "playlist/rss_playlist_plugin.h" -#include "playlist/cue_playlist_plugin.h" -#include "playlist/embcue_playlist_plugin.h" -#include "input_stream.h" -#include "uri.h" -#include "string_util.h" -#include "conf.h" -#include "mpd_error.h" - -#include <assert.h> -#include <string.h> -#include <stdio.h> - -const struct playlist_plugin *const playlist_plugins[] = { - &extm3u_playlist_plugin, - &m3u_playlist_plugin, - &xspf_playlist_plugin, - &pls_playlist_plugin, - &asx_playlist_plugin, - &rss_playlist_plugin, -#ifdef ENABLE_DESPOTIFY - &despotify_playlist_plugin, -#endif -#ifdef ENABLE_LASTFM - &lastfm_playlist_plugin, -#endif -#ifdef ENABLE_SOUNDCLOUD - &soundcloud_playlist_plugin, -#endif - &cue_playlist_plugin, - &embcue_playlist_plugin, - NULL -}; - -/** which plugins have been initialized successfully? */ -static bool playlist_plugins_enabled[G_N_ELEMENTS(playlist_plugins)]; - -#define playlist_plugins_for_each_enabled(plugin) \ - playlist_plugins_for_each(plugin) \ - if (playlist_plugins_enabled[playlist_plugin_iterator - playlist_plugins]) - -/** - * Find the "playlist" configuration block for the specified plugin. - * - * @param plugin_name the name of the playlist plugin - * @return the configuration block, or NULL if none was configured - */ -static const struct config_param * -playlist_plugin_config(const char *plugin_name) -{ - const struct config_param *param = NULL; - - assert(plugin_name != NULL); - - while ((param = config_get_next_param(CONF_PLAYLIST_PLUGIN, param)) != NULL) { - const char *name = - config_get_block_string(param, "name", NULL); - if (name == NULL) - MPD_ERROR("playlist configuration without 'plugin' name in line %d", - param->line); - - if (strcmp(name, plugin_name) == 0) - return param; - } - - return NULL; -} - -void -playlist_list_global_init(void) -{ - for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { - const struct playlist_plugin *plugin = playlist_plugins[i]; - const struct config_param *param = - playlist_plugin_config(plugin->name); - - if (!config_get_block_bool(param, "enabled", true)) - /* the plugin is disabled in mpd.conf */ - continue; - - playlist_plugins_enabled[i] = - playlist_plugin_init(playlist_plugins[i], param); - } -} - -void -playlist_list_global_finish(void) -{ - playlist_plugins_for_each_enabled(plugin) - playlist_plugin_finish(plugin); -} - -static struct playlist_provider * -playlist_list_open_uri_scheme(const char *uri, GMutex *mutex, GCond *cond, - bool *tried) -{ - char *scheme; - struct playlist_provider *playlist = NULL; - - assert(uri != NULL); - - scheme = g_uri_parse_scheme(uri); - if (scheme == NULL) - return NULL; - - for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { - const struct playlist_plugin *plugin = playlist_plugins[i]; - - assert(!tried[i]); - - if (playlist_plugins_enabled[i] && plugin->open_uri != NULL && - plugin->schemes != NULL && - string_array_contains(plugin->schemes, scheme)) { - playlist = playlist_plugin_open_uri(plugin, uri, - mutex, cond); - if (playlist != NULL) - break; - - tried[i] = true; - } - } - - g_free(scheme); - return playlist; -} - -static struct playlist_provider * -playlist_list_open_uri_suffix(const char *uri, GMutex *mutex, GCond *cond, - const bool *tried) -{ - const char *suffix; - struct playlist_provider *playlist = NULL; - - assert(uri != NULL); - - suffix = uri_get_suffix(uri); - if (suffix == NULL) - return NULL; - - for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { - const struct playlist_plugin *plugin = playlist_plugins[i]; - - if (playlist_plugins_enabled[i] && !tried[i] && - plugin->open_uri != NULL && plugin->suffixes != NULL && - string_array_contains(plugin->suffixes, suffix)) { - playlist = playlist_plugin_open_uri(plugin, uri, - mutex, cond); - if (playlist != NULL) - break; - } - } - - return playlist; -} - -struct playlist_provider * -playlist_list_open_uri(const char *uri, GMutex *mutex, GCond *cond) -{ - struct playlist_provider *playlist; - /** this array tracks which plugins have already been tried by - playlist_list_open_uri_scheme() */ - bool tried[G_N_ELEMENTS(playlist_plugins) - 1]; - - assert(uri != NULL); - - memset(tried, false, sizeof(tried)); - - playlist = playlist_list_open_uri_scheme(uri, mutex, cond, tried); - if (playlist == NULL) - playlist = playlist_list_open_uri_suffix(uri, mutex, cond, - tried); - - return playlist; -} - -static struct playlist_provider * -playlist_list_open_stream_mime2(struct input_stream *is, const char *mime) -{ - struct playlist_provider *playlist; - - assert(is != NULL); - assert(mime != NULL); - - playlist_plugins_for_each_enabled(plugin) { - if (plugin->open_stream != NULL && - plugin->mime_types != NULL && - string_array_contains(plugin->mime_types, mime)) { - /* rewind the stream, so each plugin gets a - fresh start */ - input_stream_seek(is, 0, SEEK_SET, NULL); - - playlist = playlist_plugin_open_stream(plugin, is); - if (playlist != NULL) - return playlist; - } - } - - return NULL; -} - -static struct playlist_provider * -playlist_list_open_stream_mime(struct input_stream *is) -{ - assert(is->mime != NULL); - - const char *semicolon = strchr(is->mime, ';'); - if (semicolon == NULL) - return playlist_list_open_stream_mime2(is, is->mime); - - if (semicolon == is->mime) - return NULL; - - /* probe only the portion before the semicolon*/ - char *mime = g_strndup(is->mime, semicolon - is->mime); - struct playlist_provider *playlist = - playlist_list_open_stream_mime2(is, mime); - g_free(mime); - return playlist; -} - -static struct playlist_provider * -playlist_list_open_stream_suffix(struct input_stream *is, const char *suffix) -{ - struct playlist_provider *playlist; - - assert(is != NULL); - assert(suffix != NULL); - - playlist_plugins_for_each_enabled(plugin) { - if (plugin->open_stream != NULL && - plugin->suffixes != NULL && - string_array_contains(plugin->suffixes, suffix)) { - /* rewind the stream, so each plugin gets a - fresh start */ - input_stream_seek(is, 0, SEEK_SET, NULL); - - playlist = playlist_plugin_open_stream(plugin, is); - if (playlist != NULL) - return playlist; - } - } - - return NULL; -} - -struct playlist_provider * -playlist_list_open_stream(struct input_stream *is, const char *uri) -{ - const char *suffix; - struct playlist_provider *playlist; - - input_stream_lock_wait_ready(is); - - if (is->mime != NULL) { - playlist = playlist_list_open_stream_mime(is); - if (playlist != NULL) - return playlist; - } - - suffix = uri != NULL ? uri_get_suffix(uri) : NULL; - if (suffix != NULL) { - playlist = playlist_list_open_stream_suffix(is, suffix); - if (playlist != NULL) - return playlist; - } - - return NULL; -} - -bool -playlist_suffix_supported(const char *suffix) -{ - assert(suffix != NULL); - - playlist_plugins_for_each_enabled(plugin) { - if (plugin->suffixes != NULL && - string_array_contains(plugin->suffixes, suffix)) - return true; - } - - return false; -} - -struct playlist_provider * -playlist_list_open_path(const char *path_fs, GMutex *mutex, GCond *cond, - struct input_stream **is_r) -{ - GError *error = NULL; - const char *suffix; - struct input_stream *is; - struct playlist_provider *playlist; - - assert(path_fs != NULL); - - suffix = uri_get_suffix(path_fs); - if (suffix == NULL || !playlist_suffix_supported(suffix)) - return NULL; - - is = input_stream_open(path_fs, mutex, cond, &error); - if (is == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - - return NULL; - } - - input_stream_lock_wait_ready(is); - - playlist = playlist_list_open_stream_suffix(is, suffix); - if (playlist != NULL) - *is_r = is; - else - input_stream_close(is); - - return playlist; -} diff --git a/src/playlist_list.h b/src/playlist_list.h deleted file mode 100644 index c3967d5ae..000000000 --- a/src/playlist_list.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_LIST_H -#define MPD_PLAYLIST_LIST_H - -#include <glib.h> - -#include <stdbool.h> - -struct playlist_provider; -struct input_stream; - -extern const struct playlist_plugin *const playlist_plugins[]; - -#define playlist_plugins_for_each(plugin) \ - for (const struct playlist_plugin *plugin, \ - *const*playlist_plugin_iterator = &playlist_plugins[0]; \ - (plugin = *playlist_plugin_iterator) != NULL; \ - ++playlist_plugin_iterator) - -/** - * Initializes all playlist plugins. - */ -void -playlist_list_global_init(void); - -/** - * Deinitializes all playlist plugins. - */ -void -playlist_list_global_finish(void); - -/** - * Opens a playlist by its URI. - */ -struct playlist_provider * -playlist_list_open_uri(const char *uri, GMutex *mutex, GCond *cond); - -/** - * Opens a playlist from an input stream. - * - * @param is an #input_stream object which is open and ready - * @param uri optional URI which was used to open the stream; may be - * used to select the appropriate playlist plugin - */ -struct playlist_provider * -playlist_list_open_stream(struct input_stream *is, const char *uri); - -/** - * Determines if there is a playlist plugin which can handle the - * specified file name suffix. - */ -bool -playlist_suffix_supported(const char *suffix); - -/** - * Opens a playlist from a local file. - * - * @param path_fs the path of the playlist file - * @param is_r on success, an input_stream object is returned here, - * which must be closed after the playlist_provider object is freed - * @return a playlist, or NULL on error - */ -struct playlist_provider * -playlist_list_open_path(const char *path_fs, GMutex *mutex, GCond *cond, - struct input_stream **is_r); - -#endif diff --git a/src/playlist_mapper.c b/src/playlist_mapper.c deleted file mode 100644 index 13adb80d0..000000000 --- a/src/playlist_mapper.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_mapper.h" -#include "playlist_list.h" -#include "stored_playlist.h" -#include "mapper.h" -#include "uri.h" - -#include <assert.h> - -static struct playlist_provider * -playlist_open_path(const char *path_fs, GMutex *mutex, GCond *cond, - struct input_stream **is_r) -{ - struct playlist_provider *playlist; - - playlist = playlist_list_open_uri(path_fs, mutex, cond); - if (playlist != NULL) - *is_r = NULL; - else - playlist = playlist_list_open_path(path_fs, mutex, cond, is_r); - - return playlist; -} - -/** - * Load a playlist from the configured playlist directory. - */ -static struct playlist_provider * -playlist_open_in_playlist_dir(const char *uri, GMutex *mutex, GCond *cond, - struct input_stream **is_r) -{ - char *path_fs; - - assert(spl_valid_name(uri)); - - const char *playlist_directory_fs = map_spl_path(); - if (playlist_directory_fs == NULL) - return NULL; - - path_fs = g_build_filename(playlist_directory_fs, uri, NULL); - - struct playlist_provider *playlist = - playlist_open_path(path_fs, mutex, cond, is_r); - g_free(path_fs); - - return playlist; -} - -/** - * Load a playlist from the configured music directory. - */ -static struct playlist_provider * -playlist_open_in_music_dir(const char *uri, GMutex *mutex, GCond *cond, - struct input_stream **is_r) -{ - char *path_fs; - - assert(uri_safe_local(uri)); - - path_fs = map_uri_fs(uri); - if (path_fs == NULL) - return NULL; - - struct playlist_provider *playlist = - playlist_open_path(path_fs, mutex, cond, is_r); - g_free(path_fs); - - return playlist; -} - -struct playlist_provider * -playlist_mapper_open(const char *uri, GMutex *mutex, GCond *cond, - struct input_stream **is_r) -{ - struct playlist_provider *playlist; - - if (spl_valid_name(uri)) { - playlist = playlist_open_in_playlist_dir(uri, mutex, cond, - is_r); - if (playlist != NULL) - return playlist; - } - - if (uri_safe_local(uri)) { - playlist = playlist_open_in_music_dir(uri, mutex, cond, is_r); - if (playlist != NULL) - return playlist; - } - - return NULL; -} diff --git a/src/playlist_mapper.h b/src/playlist_mapper.h deleted file mode 100644 index 9a7187d93..000000000 --- a/src/playlist_mapper.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_MAPPER_H -#define MPD_PLAYLIST_MAPPER_H - -#include <glib.h> - -struct input_stream; - -/** - * Opens a playlist from an URI relative to the playlist or music - * directory. - * - * @param is_r on success, an input_stream object may be returned - * here, which must be closed after the playlist_provider object is - * freed - */ -struct playlist_provider * -playlist_mapper_open(const char *uri, GMutex *mutex, GCond *cond, - struct input_stream **is_r); - -#endif diff --git a/src/playlist_plugin.h b/src/playlist_plugin.h deleted file mode 100644 index a27f651c0..000000000 --- a/src/playlist_plugin.h +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_PLUGIN_H - -#include <glib.h> - -#include <stdbool.h> -#include <stddef.h> - -struct config_param; -struct input_stream; -struct tag; - -/** - * An object which provides the contents of a playlist. - */ -struct playlist_provider { - const struct playlist_plugin *plugin; -}; - -static inline void -playlist_provider_init(struct playlist_provider *playlist, - const struct playlist_plugin *plugin) -{ - playlist->plugin = plugin; -} - -struct playlist_plugin { - const char *name; - - /** - * Initialize the plugin. Optional method. - * - * @param param a configuration block for this plugin, or NULL - * if none is configured - * @return true if the plugin was initialized successfully, - * false if the plugin is not available - */ - bool (*init)(const struct config_param *param); - - /** - * Deinitialize a plugin which was initialized successfully. - * Optional method. - */ - void (*finish)(void); - - /** - * Opens the playlist on the specified URI. This URI has - * either matched one of the schemes or one of the suffixes. - */ - struct playlist_provider *(*open_uri)(const char *uri, - GMutex *mutex, GCond *cond); - - /** - * Opens the playlist in the specified input stream. It has - * either matched one of the suffixes or one of the MIME - * types. - */ - struct playlist_provider *(*open_stream)(struct input_stream *is); - - void (*close)(struct playlist_provider *playlist); - - struct song *(*read)(struct playlist_provider *playlist); - - const char *const*schemes; - const char *const*suffixes; - const char *const*mime_types; -}; - -/** - * Initialize a plugin. - * - * @param param a configuration block for this plugin, or NULL if none - * is configured - * @return true if the plugin was initialized successfully, false if - * the plugin is not available - */ -static inline bool -playlist_plugin_init(const struct playlist_plugin *plugin, - const struct config_param *param) -{ - return plugin->init != NULL - ? plugin->init(param) - : true; -} - -/** - * Deinitialize a plugin which was initialized successfully. - */ -static inline void -playlist_plugin_finish(const struct playlist_plugin *plugin) -{ - if (plugin->finish != NULL) - plugin->finish(); -} - -static inline struct playlist_provider * -playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri, - GMutex *mutex, GCond *cond) -{ - return plugin->open_uri(uri, mutex, cond); -} - -static inline struct playlist_provider * -playlist_plugin_open_stream(const struct playlist_plugin *plugin, - struct input_stream *is) -{ - return plugin->open_stream(is); -} - -static inline void -playlist_plugin_close(struct playlist_provider *playlist) -{ - playlist->plugin->close(playlist); -} - -static inline struct song * -playlist_plugin_read(struct playlist_provider *playlist) -{ - return playlist->plugin->read(playlist); -} - -#endif diff --git a/src/playlist_print.c b/src/playlist_print.c deleted file mode 100644 index 59c42f969..000000000 --- a/src/playlist_print.c +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_print.h" -#include "playlist_list.h" -#include "playlist_plugin.h" -#include "playlist_any.h" -#include "playlist_song.h" -#include "playlist.h" -#include "queue_print.h" -#include "stored_playlist.h" -#include "song_print.h" -#include "song.h" -#include "database.h" -#include "client.h" -#include "input_stream.h" - -void -playlist_print_uris(struct client *client, const struct playlist *playlist) -{ - const struct queue *queue = &playlist->queue; - - queue_print_uris(client, queue, 0, queue_length(queue)); -} - -bool -playlist_print_info(struct client *client, const struct playlist *playlist, - unsigned start, unsigned end) -{ - const struct queue *queue = &playlist->queue; - - if (end > queue_length(queue)) - /* correct the "end" offset */ - end = queue_length(queue); - - if (start > end) - /* an invalid "start" offset is fatal */ - return false; - - queue_print_info(client, queue, start, end); - return true; -} - -bool -playlist_print_id(struct client *client, const struct playlist *playlist, - unsigned id) -{ - const struct queue *queue = &playlist->queue; - int position; - - position = queue_id_to_position(queue, id); - if (position < 0) - /* no such song */ - return false; - - return playlist_print_info(client, playlist, position, position + 1); -} - -bool -playlist_print_current(struct client *client, const struct playlist *playlist) -{ - int current_position = playlist_get_current_song(playlist); - - if (current_position < 0) - return false; - - queue_print_info(client, &playlist->queue, - current_position, current_position + 1); - return true; -} - -void -playlist_print_find(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list) -{ - queue_find(client, &playlist->queue, list); -} - -void -playlist_print_search(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list) -{ - queue_search(client, &playlist->queue, list); -} - -void -playlist_print_changes_info(struct client *client, - const struct playlist *playlist, - uint32_t version) -{ - queue_print_changes_info(client, &playlist->queue, version); -} - -void -playlist_print_changes_position(struct client *client, - const struct playlist *playlist, - uint32_t version) -{ - queue_print_changes_position(client, &playlist->queue, version); -} - -bool -spl_print(struct client *client, const char *name_utf8, bool detail, - GError **error_r) -{ - GPtrArray *list; - - list = spl_load(name_utf8, error_r); - if (list == NULL) - return false; - - for (unsigned i = 0; i < list->len; ++i) { - const char *temp = g_ptr_array_index(list, i); - bool wrote = false; - - if (detail) { - struct song *song = db_get_song(temp); - if (song) { - song_print_info(client, song); - wrote = true; - } - } - - if (!wrote) { - client_printf(client, SONG_FILE "%s\n", temp); - } - } - - spl_free(list); - return true; -} - -static void -playlist_provider_print(struct client *client, const char *uri, - struct playlist_provider *playlist, bool detail) -{ - struct song *song; - char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; - - while ((song = playlist_plugin_read(playlist)) != NULL) { - song = playlist_check_translate_song(song, base_uri, false); - if (song == NULL) - continue; - - if (detail) - song_print_info(client, song); - else - song_print_uri(client, song); - - if (!song_in_database(song)) - song_free(song); - } - - g_free(base_uri); -} - -bool -playlist_file_print(struct client *client, const char *uri, bool detail) -{ - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); - - struct input_stream *is; - struct playlist_provider *playlist = - playlist_open_any(uri, mutex, cond, &is); - if (playlist == NULL) { - g_cond_free(cond); - g_mutex_free(mutex); - return false; - } - - playlist_provider_print(client, uri, playlist, detail); - playlist_plugin_close(playlist); - - if (is != NULL) - input_stream_close(is); - - g_cond_free(cond); - g_mutex_free(mutex); - - return true; -} diff --git a/src/playlist_print.h b/src/playlist_print.h deleted file mode 100644 index d4f1911d2..000000000 --- a/src/playlist_print.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PLAYLIST_PRINT_H -#define PLAYLIST_PRINT_H - -#include <glib.h> -#include <stdbool.h> -#include <stdint.h> - -struct client; -struct playlist; -struct locate_item_list; - -/** - * Sends the whole playlist to the client, song URIs only. - */ -void -playlist_print_uris(struct client *client, const struct playlist *playlist); - -/** - * Sends a range of the playlist to the client, including all known - * information about the songs. The "end" offset is decreased - * automatically if it is too large; passing UINT_MAX is allowed. - * This function however fails when the start offset is invalid. - */ -bool -playlist_print_info(struct client *client, const struct playlist *playlist, - unsigned start, unsigned end); - -/** - * Sends the song with the specified id to the client. - * - * @return true on suite, false if there is no such song - */ -bool -playlist_print_id(struct client *client, const struct playlist *playlist, - unsigned id); - -/** - * Sends the current song to the client. - * - * @return true on success, false if there is no current song - */ -bool -playlist_print_current(struct client *client, const struct playlist *playlist); - -/** - * Find songs in the playlist. - */ -void -playlist_print_find(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list); - -/** - * Search for songs in the playlist. - */ -void -playlist_print_search(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list); - -/** - * Print detailed changes since the specified playlist version. - */ -void -playlist_print_changes_info(struct client *client, - const struct playlist *playlist, - uint32_t version); - -/** - * Print changes since the specified playlist version, position only. - */ -void -playlist_print_changes_position(struct client *client, - const struct playlist *playlist, - uint32_t version); - -/** - * Send the stored playlist to the client. - * - * @param client the client which requested the playlist - * @param name_utf8 the name of the stored playlist in UTF-8 encoding - * @param detail true if all details should be printed - * @return true on success, false if the playlist does not exist - */ -bool -spl_print(struct client *client, const char *name_utf8, bool detail, - GError **error_r); - -/** - * Send the playlist file to the client. - * - * @param client the client which requested the playlist - * @param uri the URI of the playlist file in UTF-8 encoding - * @param detail true if all details should be printed - * @return true on success, false if the playlist does not exist - */ -bool -playlist_file_print(struct client *client, const char *uri, bool detail); - -#endif diff --git a/src/playlist_queue.c b/src/playlist_queue.c deleted file mode 100644 index aada94984..000000000 --- a/src/playlist_queue.c +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_queue.h" -#include "playlist_plugin.h" -#include "playlist_any.h" -#include "playlist_song.h" -#include "playlist.h" -#include "song.h" -#include "input_stream.h" - -enum playlist_result -playlist_load_into_queue(const char *uri, struct playlist_provider *source, - unsigned start_index, unsigned end_index, - struct playlist *dest, struct player_control *pc, - bool secure) -{ - enum playlist_result result; - struct song *song; - char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; - - for (unsigned i = 0; - i < end_index && (song = playlist_plugin_read(source)) != NULL; - ++i) { - if (i < start_index) { - /* skip songs before the start index */ - if (!song_in_database(song)) - song_free(song); - continue; - } - - song = playlist_check_translate_song(song, base_uri, secure); - if (song == NULL) - continue; - - result = playlist_append_song(dest, pc, song, NULL); - if (result != PLAYLIST_RESULT_SUCCESS) { - if (!song_in_database(song)) - song_free(song); - g_free(base_uri); - return result; - } - } - - g_free(base_uri); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_open_into_queue(const char *uri, - unsigned start_index, unsigned end_index, - struct playlist *dest, struct player_control *pc, - bool secure) -{ - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); - - struct input_stream *is; - struct playlist_provider *playlist = - playlist_open_any(uri, mutex, cond, &is); - if (playlist == NULL) { - g_cond_free(cond); - g_mutex_free(mutex); - return PLAYLIST_RESULT_NO_SUCH_LIST; - } - - enum playlist_result result = - playlist_load_into_queue(uri, playlist, start_index, end_index, - dest, pc, secure); - playlist_plugin_close(playlist); - - if (is != NULL) - input_stream_close(is); - - g_cond_free(cond); - g_mutex_free(mutex); - - return result; -} diff --git a/src/playlist_queue.h b/src/playlist_queue.h deleted file mode 100644 index 24a851aab..000000000 --- a/src/playlist_queue.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/*! \file - * \brief Glue between playlist plugin and the play queue - */ - -#ifndef MPD_PLAYLIST_QUEUE_H -#define MPD_PLAYLIST_QUEUE_H - -#include "playlist_error.h" - -#include <stdbool.h> - -struct playlist_provider; -struct playlist; -struct player_control; - -/** - * Loads the contents of a playlist and append it to the specified - * play queue. - * - * @param uri the URI of the playlist, used to resolve relative song - * URIs - * @param start_index the index of the first song - * @param end_index the index of the last song (excluding) - */ -enum playlist_result -playlist_load_into_queue(const char *uri, struct playlist_provider *source, - unsigned start_index, unsigned end_index, - struct playlist *dest, struct player_control *pc, - bool secure); - -/** - * Opens a playlist with a playlist plugin and append to the specified - * play queue. - */ -enum playlist_result -playlist_open_into_queue(const char *uri, - unsigned start_index, unsigned end_index, - struct playlist *dest, struct player_control *pc, - bool secure); - -#endif - diff --git a/src/playlist_save.c b/src/playlist_save.c deleted file mode 100644 index 334159e0d..000000000 --- a/src/playlist_save.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_save.h" -#include "playlist.h" -#include "stored_playlist.h" -#include "queue.h" -#include "song.h" -#include "mapper.h" -#include "path.h" -#include "uri.h" -#include "database.h" -#include "idle.h" -#include "glib_compat.h" - -#include <glib.h> - -void -playlist_print_song(FILE *file, const struct song *song) -{ - if (playlist_saveAbsolutePaths && song_in_database(song)) { - char *path = map_song_fs(song); - if (path != NULL) { - fprintf(file, "%s\n", path); - g_free(path); - } - } else { - char *uri = song_get_uri(song), *uri_fs; - - uri_fs = utf8_to_fs_charset(uri); - g_free(uri); - - fprintf(file, "%s\n", uri_fs); - g_free(uri_fs); - } -} - -void -playlist_print_uri(FILE *file, const char *uri) -{ - char *s; - - if (playlist_saveAbsolutePaths && !uri_has_scheme(uri) && - !g_path_is_absolute(uri)) - s = map_uri_fs(uri); - else - s = utf8_to_fs_charset(uri); - - if (s != NULL) { - fprintf(file, "%s\n", s); - g_free(s); - } -} - -enum playlist_result -spl_save_queue(const char *name_utf8, const struct queue *queue) -{ - char *path_fs; - FILE *file; - - if (map_spl_path() == NULL) - return PLAYLIST_RESULT_DISABLED; - - if (!spl_valid_name(name_utf8)) - return PLAYLIST_RESULT_BAD_NAME; - - path_fs = map_spl_utf8_to_fs(name_utf8); - if (path_fs == NULL) - return PLAYLIST_RESULT_BAD_NAME; - - if (g_file_test(path_fs, G_FILE_TEST_EXISTS)) { - g_free(path_fs); - return PLAYLIST_RESULT_LIST_EXISTS; - } - - file = fopen(path_fs, "w"); - g_free(path_fs); - - if (file == NULL) - return PLAYLIST_RESULT_ERRNO; - - for (unsigned i = 0; i < queue_length(queue); i++) - playlist_print_song(file, queue_get(queue, i)); - - fclose(file); - - idle_add(IDLE_STORED_PLAYLIST); - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -spl_save_playlist(const char *name_utf8, const struct playlist *playlist) -{ - return spl_save_queue(name_utf8, &playlist->queue); -} - -bool -playlist_load_spl(struct playlist *playlist, struct player_control *pc, - const char *name_utf8, - unsigned start_index, unsigned end_index, - GError **error_r) -{ - GPtrArray *list; - - list = spl_load(name_utf8, error_r); - if (list == NULL) - return false; - - if (list->len < end_index) - end_index = list->len; - - for (unsigned i = start_index; i < end_index; ++i) { - const char *temp = g_ptr_array_index(list, i); - if ((playlist_append_uri(playlist, pc, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { - /* for windows compatibility, convert slashes */ - char *temp2 = g_strdup(temp); - char *p = temp2; - while (*p) { - if (*p == '\\') - *p = '/'; - p++; - } - if ((playlist_append_uri(playlist, pc, temp2, - NULL)) != PLAYLIST_RESULT_SUCCESS) { - g_warning("can't add file \"%s\"", temp2); - } - g_free(temp2); - } - } - - spl_free(list); - return true; -} diff --git a/src/playlist_save.h b/src/playlist_save.h deleted file mode 100644 index a6c31a9a6..000000000 --- a/src/playlist_save.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_SAVE_H -#define MPD_PLAYLIST_SAVE_H - -#include "playlist_error.h" - -#include <stdbool.h> -#include <stdio.h> - -struct song; -struct queue; -struct playlist; -struct player_control; - -void -playlist_print_song(FILE *fp, const struct song *song); - -void -playlist_print_uri(FILE *fp, const char *uri); - -/** - * Saves a queue object into a stored playlist file. - */ -enum playlist_result -spl_save_queue(const char *name_utf8, const struct queue *queue); - -/** - * Saves a playlist object into a stored playlist file. - */ -enum playlist_result -spl_save_playlist(const char *name_utf8, const struct playlist *playlist); - -/** - * Loads a stored playlist file, and append all songs to the global - * playlist. - */ -bool -playlist_load_spl(struct playlist *playlist, struct player_control *pc, - const char *name_utf8, - unsigned start_index, unsigned end_index, - GError **error_r); - -#endif diff --git a/src/playlist_song.c b/src/playlist_song.c deleted file mode 100644 index a3d9ab4d9..000000000 --- a/src/playlist_song.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_song.h" -#include "database.h" -#include "mapper.h" -#include "song.h" -#include "uri.h" -#include "path.h" -#include "ls.h" -#include "tag.h" - -#include <assert.h> -#include <string.h> - -static void -merge_song_metadata(struct song *dest, const struct song *base, - const struct song *add) -{ - dest->tag = base->tag != NULL - ? (add->tag != NULL - ? tag_merge(base->tag, add->tag) - : tag_dup(base->tag)) - : (add->tag != NULL - ? tag_dup(add->tag) - : NULL); - - dest->mtime = base->mtime; - dest->start_ms = add->start_ms; - dest->end_ms = add->end_ms; -} - -static struct song * -apply_song_metadata(struct song *dest, const struct song *src) -{ - struct song *tmp; - - assert(dest != NULL); - assert(src != NULL); - - if (src->tag == NULL && src->start_ms == 0 && src->end_ms == 0) - return dest; - - if (song_in_database(dest)) { - char *path_fs = map_song_fs(dest); - if (path_fs == NULL) - return dest; - - char *path_utf8 = fs_charset_to_utf8(path_fs); - if (path_utf8 != NULL) - g_free(path_fs); - else - path_utf8 = path_fs; - - tmp = song_file_new(path_utf8, NULL); - g_free(path_utf8); - - merge_song_metadata(tmp, dest, src); - } else { - tmp = song_file_new(dest->uri, NULL); - merge_song_metadata(tmp, dest, src); - } - - if (dest->tag != NULL && dest->tag->time > 0 && - src->start_ms > 0 && src->end_ms == 0 && - src->start_ms / 1000 < (unsigned)dest->tag->time) - /* the range is open-ended, and the playlist plugin - did not know the total length of the song file - (e.g. last track on a CUE file); fix it up here */ - tmp->tag->time = dest->tag->time - src->start_ms / 1000; - - if (!song_in_database(dest)) - song_free(dest); - - return tmp; -} - -static struct song * -playlist_check_load_song(const struct song *song, const char *uri, bool secure) -{ - struct song *dest; - - if (uri_has_scheme(uri)) { - dest = song_remote_new(uri); - } else if (g_path_is_absolute(uri) && secure) { - dest = song_file_load(uri, NULL); - if (dest == NULL) - return NULL; - } else { - dest = db_get_song(uri); - if (dest == NULL) - /* not found in database */ - return NULL; - } - - return apply_song_metadata(dest, song); -} - -struct song * -playlist_check_translate_song(struct song *song, const char *base_uri, - bool secure) -{ - if (song_in_database(song)) - /* already ok */ - return song; - - const char *uri = song->uri; - - if (uri_has_scheme(uri)) { - if (uri_supported_scheme(uri)) - /* valid remote song */ - return song; - else { - /* unsupported remote song */ - song_free(song); - return NULL; - } - } - - if (base_uri != NULL && strcmp(base_uri, ".") == 0) - /* g_path_get_dirname() returns "." when there is no - directory name in the given path; clear that now, - because it would break the database lookup - functions */ - base_uri = NULL; - - if (g_path_is_absolute(uri)) { - /* XXX fs_charset vs utf8? */ - const char *suffix = map_to_relative_path(uri); - assert(suffix != NULL); - - if (suffix != uri) - uri = suffix; - else if (!secure) { - /* local files must be relative to the music - directory when "secure" is enabled */ - song_free(song); - return NULL; - } - - base_uri = NULL; - } - - char *allocated = NULL; - if (base_uri != NULL) - uri = allocated = g_build_filename(base_uri, uri, NULL); - - struct song *dest = playlist_check_load_song(song, uri, secure); - song_free(song); - g_free(allocated); - return dest; -} diff --git a/src/playlist_song.h b/src/playlist_song.h deleted file mode 100644 index ea8786912..000000000 --- a/src/playlist_song.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_SONG_H -#define MPD_PLAYLIST_SONG_H - -#include <stdbool.h> - -/** - * Verifies the song, returns NULL if it is unsafe. Translate the - * song to a new song object within the database, if it is a local - * file. The old song object is freed. - * - * @param secure if true, then local files are only allowed if they - * are relative to base_uri - */ -struct song * -playlist_check_translate_song(struct song *song, const char *base_uri, - bool secure); - -#endif diff --git a/src/playlist_state.c b/src/playlist_state.c deleted file mode 100644 index 4aa2c2c92..000000000 --- a/src/playlist_state.c +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Saving and loading the playlist to/from the state file. - * - */ - -#include "config.h" -#include "playlist_state.h" -#include "playlist.h" -#include "player_control.h" -#include "queue_save.h" -#include "path.h" -#include "text_file.h" -#include "conf.h" - -#include <string.h> -#include <stdlib.h> - -#define PLAYLIST_STATE_FILE_STATE "state: " -#define PLAYLIST_STATE_FILE_RANDOM "random: " -#define PLAYLIST_STATE_FILE_REPEAT "repeat: " -#define PLAYLIST_STATE_FILE_SINGLE "single: " -#define PLAYLIST_STATE_FILE_CONSUME "consume: " -#define PLAYLIST_STATE_FILE_CURRENT "current: " -#define PLAYLIST_STATE_FILE_TIME "time: " -#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: " -#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: " -#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: " -#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin" -#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end" - -#define PLAYLIST_STATE_FILE_STATE_PLAY "play" -#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause" -#define PLAYLIST_STATE_FILE_STATE_STOP "stop" - -#define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX - -void -playlist_state_save(FILE *fp, const struct playlist *playlist, - struct player_control *pc) -{ - struct player_status player_status; - - pc_get_status(pc, &player_status); - - fputs(PLAYLIST_STATE_FILE_STATE, fp); - - if (playlist->playing) { - switch (player_status.state) { - case PLAYER_STATE_PAUSE: - fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp); - break; - default: - fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp); - } - fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", - queue_order_to_position(&playlist->queue, - playlist->current)); - fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n", - (int)player_status.elapsed_time); - } else { - fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp); - - if (playlist->current >= 0) - fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", - queue_order_to_position(&playlist->queue, - playlist->current)); - } - - fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist->queue.random); - fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist->queue.repeat); - fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist->queue.single); - fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n", - playlist->queue.consume); - fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n", - (int)(pc_get_cross_fade(pc))); - fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", - pc_get_mixramp_db(pc)); - fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", - pc_get_mixramp_delay(pc)); - fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp); - queue_save(fp, &playlist->queue); - fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp); -} - -static void -playlist_state_load(FILE *fp, GString *buffer, struct playlist *playlist) -{ - const char *line = read_text_line(fp, buffer); - if (line == NULL) { - g_warning("No playlist in state file"); - return; - } - - while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { - queue_load_song(fp, buffer, line, &playlist->queue); - - line = read_text_line(fp, buffer); - if (line == NULL) { - g_warning("'" PLAYLIST_STATE_FILE_PLAYLIST_END - "' not found in state file"); - break; - } - } - - queue_increment_version(&playlist->queue); -} - -bool -playlist_state_restore(const char *line, FILE *fp, GString *buffer, - struct playlist *playlist, struct player_control *pc) -{ - int current = -1; - int seek_time = 0; - enum player_state state = PLAYER_STATE_STOP; - bool random_mode = false; - - if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE)) - return false; - - line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1; - - if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0) - state = PLAYER_STATE_PLAY; - else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0) - state = PLAYER_STATE_PAUSE; - - while ((line = read_text_line(fp, buffer)) != NULL) { - if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_TIME)) { - seek_time = - atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_REPEAT)) { - if (strcmp - (&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), - "1") == 0) { - playlist_set_repeat(playlist, pc, true); - } else - playlist_set_repeat(playlist, pc, false); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) { - if (strcmp - (&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), - "1") == 0) { - playlist_set_single(playlist, pc, true); - } else - playlist_set_single(playlist, pc, false); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) { - if (strcmp - (&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), - "1") == 0) { - playlist_set_consume(playlist, true); - } else - playlist_set_consume(playlist, false); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CROSSFADE)) { - pc_set_cross_fade(pc, - atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) { - pc_set_mixramp_db(pc, - atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { - pc_set_mixramp_delay(pc, - atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY))); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) { - random_mode = - strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), - "1") == 0; - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CURRENT)) { - current = atoi(&(line - [strlen - (PLAYLIST_STATE_FILE_CURRENT)])); - } else if (g_str_has_prefix(line, - PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { - playlist_state_load(fp, buffer, playlist); - } - } - - playlist_set_random(playlist, pc, random_mode); - - if (!queue_is_empty(&playlist->queue)) { - if (!queue_valid_position(&playlist->queue, current)) - current = 0; - - if (state == PLAYER_STATE_PLAY && - config_get_bool("restore_paused", false)) - /* the user doesn't want MPD to auto-start - playback after startup; fall back to - "pause" */ - state = PLAYER_STATE_PAUSE; - - /* enable all devices for the first time; this must be - called here, after the audio output states were - restored, before playback begins */ - if (state != PLAYER_STATE_STOP) - pc_update_audio(pc); - - if (state == PLAYER_STATE_STOP /* && config_option */) - playlist->current = current; - else if (seek_time == 0) - playlist_play(playlist, pc, current); - else - playlist_seek_song(playlist, pc, current, seek_time); - - if (state == PLAYER_STATE_PAUSE) - pc_pause(pc); - } - - return true; -} - -unsigned -playlist_state_get_hash(const struct playlist *playlist, - struct player_control *pc) -{ - struct player_status player_status; - - pc_get_status(pc, &player_status); - - return playlist->queue.version ^ - (player_status.state != PLAYER_STATE_STOP - ? ((int)player_status.elapsed_time << 8) - : 0) ^ - (playlist->current >= 0 - ? (queue_order_to_position(&playlist->queue, - playlist->current) << 16) - : 0) ^ - ((int)pc_get_cross_fade(pc) << 20) ^ - (player_status.state << 24) ^ - (playlist->queue.random << 27) ^ - (playlist->queue.repeat << 28) ^ - (playlist->queue.single << 29) ^ - (playlist->queue.consume << 30) ^ - (playlist->queue.random << 31); -} diff --git a/src/playlist_state.h b/src/playlist_state.h deleted file mode 100644 index f67d01d2c..000000000 --- a/src/playlist_state.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Saving and loading the playlist to/from the state file. - * - */ - -#ifndef PLAYLIST_STATE_H -#define PLAYLIST_STATE_H - -#include <glib.h> -#include <stdbool.h> -#include <stdio.h> - -struct playlist; -struct player_control; - -void -playlist_state_save(FILE *fp, const struct playlist *playlist, - struct player_control *pc); - -bool -playlist_state_restore(const char *line, FILE *fp, GString *buffer, - struct playlist *playlist, struct player_control *pc); - -/** - * Generates a hash number for the current state of the playlist and - * the playback options. This is used by timer_save_state_file() to - * determine whether the state has changed and the state file should - * be saved. - */ -unsigned -playlist_state_get_hash(const struct playlist *playlist, - struct player_control *pc); - -#endif diff --git a/src/playlist_vector.c b/src/playlist_vector.c deleted file mode 100644 index 74c7bf089..000000000 --- a/src/playlist_vector.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_vector.h" -#include "db_lock.h" - -#include <assert.h> -#include <string.h> -#include <glib.h> - -static struct playlist_metadata * -playlist_metadata_new(const char *name, time_t mtime) -{ - assert(name != NULL); - - struct playlist_metadata *pm = g_slice_new(struct playlist_metadata); - pm->name = g_strdup(name); - pm->mtime = mtime; - return pm; -} - -static void -playlist_metadata_free(struct playlist_metadata *pm) -{ - assert(pm != NULL); - assert(pm->name != NULL); - - g_free(pm->name); - g_slice_free(struct playlist_metadata, pm); -} - -void -playlist_vector_deinit(struct list_head *pv) -{ - assert(pv != NULL); - - struct playlist_metadata *pm, *n; - playlist_vector_for_each_safe(pm, n, pv) - playlist_metadata_free(pm); -} - -struct playlist_metadata * -playlist_vector_find(struct list_head *pv, const char *name) -{ - assert(holding_db_lock()); - assert(pv != NULL); - assert(name != NULL); - - struct playlist_metadata *pm; - playlist_vector_for_each(pm, pv) - if (strcmp(pm->name, name) == 0) - return pm; - - return NULL; -} - -void -playlist_vector_add(struct list_head *pv, - const char *name, time_t mtime) -{ - assert(holding_db_lock()); - - struct playlist_metadata *pm = playlist_metadata_new(name, mtime); - list_add_tail(&pm->siblings, pv); -} - -bool -playlist_vector_update_or_add(struct list_head *pv, - const char *name, time_t mtime) -{ - assert(holding_db_lock()); - - struct playlist_metadata *pm = playlist_vector_find(pv, name); - if (pm != NULL) { - if (mtime == pm->mtime) - return false; - - pm->mtime = mtime; - } else - playlist_vector_add(pv, name, mtime); - - return true; -} - -bool -playlist_vector_remove(struct list_head *pv, const char *name) -{ - assert(holding_db_lock()); - - struct playlist_metadata *pm = playlist_vector_find(pv, name); - if (pm == NULL) - return false; - - list_del(&pm->siblings); - playlist_metadata_free(pm); - return true; -} diff --git a/src/playlist_vector.h b/src/playlist_vector.h deleted file mode 100644 index 0af6df8b4..000000000 --- a/src/playlist_vector.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_VECTOR_H -#define MPD_PLAYLIST_VECTOR_H - -#include "util/list.h" - -#include <stdbool.h> -#include <stddef.h> -#include <sys/time.h> - -#define playlist_vector_for_each(pos, head) \ - list_for_each_entry(pos, head, siblings) - -#define playlist_vector_for_each_safe(pos, n, head) \ - list_for_each_entry_safe(pos, n, head, siblings) - -/** - * A directory entry pointing to a playlist file. - */ -struct playlist_metadata { - struct list_head siblings; - - /** - * The UTF-8 encoded name of the playlist file. - */ - char *name; - - time_t mtime; -}; - -void -playlist_vector_deinit(struct list_head *pv); - -/** - * Caller must lock the #db_mutex. - */ -struct playlist_metadata * -playlist_vector_find(struct list_head *pv, const char *name); - -/** - * Caller must lock the #db_mutex. - */ -void -playlist_vector_add(struct list_head *pv, - const char *name, time_t mtime); - -/** - * Caller must lock the #db_mutex. - * - * @return true if the vector or one of its items was modified - */ -bool -playlist_vector_update_or_add(struct list_head *pv, - const char *name, time_t mtime); - -/** - * Caller must lock the #db_mutex. - */ -bool -playlist_vector_remove(struct list_head *pv, const char *name); - -#endif /* SONGVEC_H */ diff --git a/src/protocol/ArgParser.cxx b/src/protocol/ArgParser.cxx new file mode 100644 index 000000000..6bd53a358 --- /dev/null +++ b/src/protocol/ArgParser.cxx @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ArgParser.hxx" +#include "Result.hxx" + +#include <glib.h> +#include <stdlib.h> + +bool +check_uint32(Client *client, uint32_t *dst, const char *s) +{ + char *test; + + *dst = strtoul(s, &test, 10); + if (test == s || *test != '\0') { + command_error(client, ACK_ERROR_ARG, + "Integer expected: %s", s); + return false; + } + return true; +} + +bool +check_int(Client *client, int *value_r, const char *s) +{ + char *test; + long value; + + value = strtol(s, &test, 10); + if (test == s || *test != '\0') { + command_error(client, ACK_ERROR_ARG, + "Integer expected: %s", s); + return false; + } + +#if G_MAXLONG > G_MAXINT + if (value < G_MININT || value > G_MAXINT) { + command_error(client, ACK_ERROR_ARG, + "Number too large: %s", s); + return false; + } +#endif + + *value_r = (int)value; + return true; +} + +bool +check_range(Client *client, unsigned *value_r1, unsigned *value_r2, + const char *s) +{ + char *test, *test2; + long value; + + value = strtol(s, &test, 10); + if (test == s || (*test != '\0' && *test != ':')) { + command_error(client, ACK_ERROR_ARG, + "Integer or range expected: %s", s); + return false; + } + + if (value == -1 && *test == 0) { + /* compatibility with older MPD versions: specifying + "-1" makes MPD display the whole list */ + *value_r1 = 0; + *value_r2 = G_MAXUINT; + return true; + } + + if (value < 0) { + command_error(client, ACK_ERROR_ARG, + "Number is negative: %s", s); + return false; + } + +#if G_MAXLONG > G_MAXUINT + if (value > G_MAXUINT) { + command_error(client, ACK_ERROR_ARG, + "Number too large: %s", s); + return false; + } +#endif + + *value_r1 = (unsigned)value; + + if (*test == ':') { + value = strtol(++test, &test2, 10); + if (*test2 != '\0') { + command_error(client, ACK_ERROR_ARG, + "Integer or range expected: %s", s); + return false; + } + + if (test == test2) + value = G_MAXUINT; + + if (value < 0) { + command_error(client, ACK_ERROR_ARG, + "Number is negative: %s", s); + return false; + } + +#if G_MAXLONG > G_MAXUINT + if (value > G_MAXUINT) { + command_error(client, ACK_ERROR_ARG, + "Number too large: %s", s); + return false; + } +#endif + *value_r2 = (unsigned)value; + } else { + *value_r2 = (unsigned)value + 1; + } + + return true; +} + +bool +check_unsigned(Client *client, unsigned *value_r, const char *s) +{ + unsigned long value; + char *endptr; + + value = strtoul(s, &endptr, 10); + if (endptr == s || *endptr != 0) { + command_error(client, ACK_ERROR_ARG, + "Integer expected: %s", s); + return false; + } + + if (value > G_MAXUINT) { + command_error(client, ACK_ERROR_ARG, + "Number too large: %s", s); + return false; + } + + *value_r = (unsigned)value; + return true; +} + +bool +check_bool(Client *client, bool *value_r, const char *s) +{ + long value; + char *endptr; + + value = strtol(s, &endptr, 10); + if (endptr == s || *endptr != 0 || (value != 0 && value != 1)) { + command_error(client, ACK_ERROR_ARG, + "Boolean (0/1) expected: %s", s); + return false; + } + + *value_r = !!value; + return true; +} + +bool +check_float(Client *client, float *value_r, const char *s) +{ + float value; + char *endptr; + + value = strtof(s, &endptr); + if (endptr == s || *endptr != 0) { + command_error(client, ACK_ERROR_ARG, + "Float expected: %s", s); + return false; + } + + *value_r = value; + return true; +} diff --git a/src/protocol/ArgParser.hxx b/src/protocol/ArgParser.hxx new file mode 100644 index 000000000..b6feb3e67 --- /dev/null +++ b/src/protocol/ArgParser.hxx @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PROTOCOL_ARGPARSER_HXX +#define MPD_PROTOCOL_ARGPARSER_HXX + +#include "check.h" + +#include <stdbool.h> +#include <stdint.h> + +class Client; + +bool +check_uint32(Client *client, uint32_t *dst, const char *s); + +bool +check_int(Client *client, int *value_r, const char *s); + +bool +check_range(Client *client, unsigned *value_r1, unsigned *value_r2, + const char *s); + +bool +check_unsigned(Client *client, unsigned *value_r, const char *s); + +bool +check_bool(Client *client, bool *value_r, const char *s); + +bool +check_float(Client *client, float *value_r, const char *s); + +#endif diff --git a/src/protocol/Result.cxx b/src/protocol/Result.cxx new file mode 100644 index 000000000..e10a731cc --- /dev/null +++ b/src/protocol/Result.cxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Result.hxx" +#include "Client.hxx" + +#include <assert.h> + +const char *current_command; +int command_list_num; + +void +command_success(Client *client) +{ + client_puts(client, "OK\n"); +} + +void +command_error_v(Client *client, enum ack error, + const char *fmt, va_list args) +{ + assert(client != NULL); + assert(current_command != NULL); + + client_printf(client, "ACK [%i@%i] {%s} ", + (int)error, command_list_num, current_command); + client_vprintf(client, fmt, args); + client_puts(client, "\n"); + + current_command = NULL; +} + +void +command_error(Client *client, enum ack error, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + command_error_v(client, error, fmt, args); + va_end(args); +} diff --git a/src/protocol/Result.hxx b/src/protocol/Result.hxx new file mode 100644 index 000000000..99d9a2fa0 --- /dev/null +++ b/src/protocol/Result.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PROTOCOL_RESULT_HXX +#define MPD_PROTOCOL_RESULT_HXX + +#include "check.h" +#include "gcc.h" +#include "ack.h" + +class Client; + +extern const char *current_command; +extern int command_list_num; + +void +command_success(Client *client); + +void +command_error_v(Client *client, enum ack error, + const char *fmt, va_list args); + +gcc_fprintf_ +void +command_error(Client *client, enum ack error, const char *fmt, ...); + +#endif diff --git a/src/protocol/argparser.c b/src/protocol/argparser.c deleted file mode 100644 index d20437cb7..000000000 --- a/src/protocol/argparser.c +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "argparser.h" -#include "result.h" - -#include <glib.h> -#include <stdlib.h> - -bool -check_uint32(struct client *client, uint32_t *dst, const char *s) -{ - char *test; - - *dst = strtoul(s, &test, 10); - if (test == s || *test != '\0') { - command_error(client, ACK_ERROR_ARG, - "Integer expected: %s", s); - return false; - } - return true; -} - -bool -check_int(struct client *client, int *value_r, const char *s) -{ - char *test; - long value; - - value = strtol(s, &test, 10); - if (test == s || *test != '\0') { - command_error(client, ACK_ERROR_ARG, - "Integer expected: %s", s); - return false; - } - -#if G_MAXLONG > G_MAXINT - if (value < G_MININT || value > G_MAXINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } -#endif - - *value_r = (int)value; - return true; -} - -bool -check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, - const char *s) -{ - char *test, *test2; - long value; - - value = strtol(s, &test, 10); - if (test == s || (*test != '\0' && *test != ':')) { - command_error(client, ACK_ERROR_ARG, - "Integer or range expected: %s", s); - return false; - } - - if (value == -1 && *test == 0) { - /* compatibility with older MPD versions: specifying - "-1" makes MPD display the whole list */ - *value_r1 = 0; - *value_r2 = G_MAXUINT; - return true; - } - - if (value < 0) { - command_error(client, ACK_ERROR_ARG, - "Number is negative: %s", s); - return false; - } - -#if G_MAXLONG > G_MAXUINT - if (value > G_MAXUINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } -#endif - - *value_r1 = (unsigned)value; - - if (*test == ':') { - value = strtol(++test, &test2, 10); - if (*test2 != '\0') { - command_error(client, ACK_ERROR_ARG, - "Integer or range expected: %s", s); - return false; - } - - if (test == test2) - value = G_MAXUINT; - - if (value < 0) { - command_error(client, ACK_ERROR_ARG, - "Number is negative: %s", s); - return false; - } - -#if G_MAXLONG > G_MAXUINT - if (value > G_MAXUINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } -#endif - *value_r2 = (unsigned)value; - } else { - *value_r2 = (unsigned)value + 1; - } - - return true; -} - -bool -check_unsigned(struct client *client, unsigned *value_r, const char *s) -{ - unsigned long value; - char *endptr; - - value = strtoul(s, &endptr, 10); - if (endptr == s || *endptr != 0) { - command_error(client, ACK_ERROR_ARG, - "Integer expected: %s", s); - return false; - } - - if (value > G_MAXUINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } - - *value_r = (unsigned)value; - return true; -} - -bool -check_bool(struct client *client, bool *value_r, const char *s) -{ - long value; - char *endptr; - - value = strtol(s, &endptr, 10); - if (endptr == s || *endptr != 0 || (value != 0 && value != 1)) { - command_error(client, ACK_ERROR_ARG, - "Boolean (0/1) expected: %s", s); - return false; - } - - *value_r = !!value; - return true; -} - -bool -check_float(struct client *client, float *value_r, const char *s) -{ - float value; - char *endptr; - - value = strtof(s, &endptr); - if (endptr == s || *endptr != 0) { - command_error(client, ACK_ERROR_ARG, - "Float expected: %s", s); - return false; - } - - *value_r = value; - return true; -} diff --git a/src/protocol/argparser.h b/src/protocol/argparser.h deleted file mode 100644 index e88aea478..000000000 --- a/src/protocol/argparser.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PROTOCOL_ARGPARSER_H -#define MPD_PROTOCOL_ARGPARSER_H - -#include "check.h" - -#include <stdbool.h> -#include <stdint.h> - -struct client; - -bool -check_uint32(struct client *client, uint32_t *dst, const char *s); - -bool -check_int(struct client *client, int *value_r, const char *s); - -bool -check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, - const char *s); - -bool -check_unsigned(struct client *client, unsigned *value_r, const char *s); - -bool -check_bool(struct client *client, bool *value_r, const char *s); - -bool -check_float(struct client *client, float *value_r, const char *s); - -#endif diff --git a/src/protocol/result.c b/src/protocol/result.c deleted file mode 100644 index 30cd0a266..000000000 --- a/src/protocol/result.c +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "result.h" -#include "client.h" - -#include <assert.h> - -const char *current_command; -int command_list_num; - -void -command_success(struct client *client) -{ - client_puts(client, "OK\n"); -} - -void -command_error_v(struct client *client, enum ack error, - const char *fmt, va_list args) -{ - assert(client != NULL); - assert(current_command != NULL); - - client_printf(client, "ACK [%i@%i] {%s} ", - (int)error, command_list_num, current_command); - client_vprintf(client, fmt, args); - client_puts(client, "\n"); - - current_command = NULL; -} - -void -command_error(struct client *client, enum ack error, const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - command_error_v(client, error, fmt, args); - va_end(args); -} diff --git a/src/protocol/result.h b/src/protocol/result.h deleted file mode 100644 index 8b9e44bfd..000000000 --- a/src/protocol/result.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PROTOCOL_RESULT_H -#define MPD_PROTOCOL_RESULT_H - -#include "check.h" -#include "ack.h" - -#include <glib.h> - -struct client; - -extern const char *current_command; -extern int command_list_num; - -void -command_success(struct client *client); - -void -command_error_v(struct client *client, enum ack error, - const char *fmt, va_list args); - -G_GNUC_PRINTF(3, 4) -void -command_error(struct client *client, enum ack error, const char *fmt, ...); - -#endif diff --git a/src/queue.c b/src/queue.c deleted file mode 100644 index 4fe564a35..000000000 --- a/src/queue.c +++ /dev/null @@ -1,603 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "queue.h" -#include "song.h" - -#include <stdlib.h> - -/** - * Generate a non-existing id number. - */ -static unsigned -queue_generate_id(const struct queue *queue) -{ - static unsigned cur = (unsigned)-1; - - do { - cur++; - - if (cur >= queue->max_length * QUEUE_HASH_MULT) - cur = 0; - } while (queue->id_to_position[cur] != -1); - - return cur; -} - -int -queue_next_order(const struct queue *queue, unsigned order) -{ - assert(order < queue->length); - - if (queue->single && queue->repeat && !queue->consume) - return order; - else if (order + 1 < queue->length) - return order + 1; - else if (queue->repeat && (order > 0 || !queue->consume)) - /* restart at first song */ - return 0; - else - /* end of queue */ - return -1; -} - -void -queue_increment_version(struct queue *queue) -{ - static unsigned long max = ((uint32_t) 1 << 31) - 1; - - queue->version++; - - if (queue->version >= max) { - for (unsigned i = 0; i < queue->length; i++) - queue->items[i].version = 0; - - queue->version = 1; - } -} - -void -queue_modify(struct queue *queue, unsigned order) -{ - unsigned position; - - assert(order < queue->length); - - position = queue->order[order]; - queue->items[position].version = queue->version; - - queue_increment_version(queue); -} - -void -queue_modify_all(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; i++) - queue->items[i].version = queue->version; - - queue_increment_version(queue); -} - -unsigned -queue_append(struct queue *queue, struct song *song, uint8_t priority) -{ - unsigned id = queue_generate_id(queue); - - assert(!queue_is_full(queue)); - - queue->items[queue->length] = (struct queue_item){ - .song = song, - .id = id, - .version = queue->version, - .priority = priority, - }; - - queue->order[queue->length] = queue->length; - queue->id_to_position[id] = queue->length; - - ++queue->length; - - return id; -} - -void -queue_swap(struct queue *queue, unsigned position1, unsigned position2) -{ - struct queue_item tmp; - unsigned id1 = queue->items[position1].id; - unsigned id2 = queue->items[position2].id; - - tmp = queue->items[position1]; - queue->items[position1] = queue->items[position2]; - queue->items[position2] = tmp; - - queue->items[position1].version = queue->version; - queue->items[position2].version = queue->version; - - queue->id_to_position[id1] = position2; - queue->id_to_position[id2] = position1; -} - -static void -queue_move_song_to(struct queue *queue, unsigned from, unsigned to) -{ - unsigned from_id = queue->items[from].id; - - queue->items[to] = queue->items[from]; - queue->items[to].version = queue->version; - queue->id_to_position[from_id] = to; -} - -void -queue_move(struct queue *queue, unsigned from, unsigned to) -{ - struct queue_item item = queue->items[from]; - - /* move songs to one less in from->to */ - - for (unsigned i = from; i < to; i++) - queue_move_song_to(queue, i + 1, i); - - /* move songs to one more in to->from */ - - for (unsigned i = from; i > to; i--) - queue_move_song_to(queue, i - 1, i); - - /* put song at _to_ */ - - queue->id_to_position[item.id] = to; - queue->items[to] = item; - queue->items[to].version = queue->version; - - /* now deal with order */ - - if (queue->random) { - for (unsigned i = 0; i < queue->length; i++) { - if (queue->order[i] > from && queue->order[i] <= to) - queue->order[i]--; - else if (queue->order[i] < from && - queue->order[i] >= to) - queue->order[i]++; - else if (from == queue->order[i]) - queue->order[i] = to; - } - } -} - -void -queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to) -{ - struct queue_item items[end - start]; - // Copy the original block [start,end-1] - for (unsigned i = start; i < end; i++) - items[i - start] = queue->items[i]; - - // If to > start, we need to move to-start items to start, starting from end - for (unsigned i = end; i < end + to - start; i++) - queue_move_song_to(queue, i, start + i - end); - - // If to < start, we need to move start-to items to newend (= end + to - start), starting from to - // This is the same as moving items from start-1 to to (decreasing), with start-1 going to end-1 - // We have to iterate in this order to avoid writing over something we haven't yet moved - for (unsigned i = start - 1; i >= to && i != G_MAXUINT; i--) - queue_move_song_to(queue, i, i + end - start); - - // Copy the original block back in, starting at to. - for (unsigned i = start; i< end; i++) - { - queue->id_to_position[items[i-start].id] = to + i - start; - queue->items[to + i - start] = items[i-start]; - queue->items[to + i - start].version = queue->version; - } - - if (queue->random) { - // Update the positions in the queue. - // Note that the ranges for these cases are the same as the ranges of - // the loops above. - for (unsigned i = 0; i < queue->length; i++) { - if (queue->order[i] >= end && queue->order[i] < to + end - start) - queue->order[i] -= end - start; - else if (queue->order[i] < start && - queue->order[i] >= to) - queue->order[i] += end - start; - else if (start <= queue->order[i] && queue->order[i] < end) - queue->order[i] += to - start; - } - } -} - -/** - * Moves a song to a new position in the "order" list. - */ -static void -queue_move_order(struct queue *queue, unsigned from_order, unsigned to_order) -{ - assert(queue != NULL); - assert(from_order < queue->length); - assert(to_order <= queue->length); - - const unsigned from_position = - queue_order_to_position(queue, from_order); - - if (from_order < to_order) { - for (unsigned i = from_order; i < to_order; ++i) - queue->order[i] = queue->order[i + 1]; - } else { - for (unsigned i = from_order; i > to_order; --i) - queue->order[i] = queue->order[i - 1]; - } - - queue->order[to_order] = from_position; -} - -void -queue_delete(struct queue *queue, unsigned position) -{ - struct song *song; - unsigned id, order; - - assert(position < queue->length); - - song = queue_get(queue, position); - if (!song_in_database(song)) - song_free(song); - - id = queue_position_to_id(queue, position); - order = queue_position_to_order(queue, position); - - --queue->length; - - /* release the song id */ - - queue->id_to_position[id] = -1; - - /* delete song from songs array */ - - for (unsigned i = position; i < queue->length; i++) - queue_move_song_to(queue, i + 1, i); - - /* delete the entry from the order array */ - - for (unsigned i = order; i < queue->length; i++) - queue->order[i] = queue->order[i + 1]; - - /* readjust values in the order array */ - - for (unsigned i = 0; i < queue->length; i++) - if (queue->order[i] > position) - --queue->order[i]; -} - -void -queue_clear(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; i++) { - struct queue_item *item = &queue->items[i]; - - if (!song_in_database(item->song)) - song_free(item->song); - - queue->id_to_position[item->id] = -1; - } - - queue->length = 0; -} - -void -queue_init(struct queue *queue, unsigned max_length) -{ - queue->max_length = max_length; - queue->length = 0; - queue->version = 1; - queue->repeat = false; - queue->random = false; - queue->single = false; - queue->consume = false; - - queue->items = g_new(struct queue_item, max_length); - queue->order = g_malloc(sizeof(queue->order[0]) * - max_length); - queue->id_to_position = g_malloc(sizeof(queue->id_to_position[0]) * - max_length * QUEUE_HASH_MULT); - - for (unsigned i = 0; i < max_length * QUEUE_HASH_MULT; ++i) - queue->id_to_position[i] = -1; - - queue->rand = g_rand_new(); -} - -void -queue_finish(struct queue *queue) -{ - queue_clear(queue); - - g_free(queue->items); - g_free(queue->order); - g_free(queue->id_to_position); - - g_rand_free(queue->rand); -} - -static const struct queue_item * -queue_get_order_item_const(const struct queue *queue, unsigned order) -{ - assert(queue != NULL); - assert(order < queue->length); - - return &queue->items[queue->order[order]]; -} - -static uint8_t -queue_get_order_priority(const struct queue *queue, unsigned order) -{ - return queue_get_order_item_const(queue, order)->priority; -} - -static gint -queue_item_compare_order_priority(gconstpointer av, gconstpointer bv, - gpointer user_data) -{ - const struct queue *queue = user_data; - const unsigned *const ap = av; - const unsigned *const bp = bv; - assert(ap >= queue->order && ap < queue->order + queue->length); - assert(bp >= queue->order && bp < queue->order + queue->length); - uint8_t a = queue->items[*ap].priority; - uint8_t b = queue->items[*bp].priority; - - if (G_LIKELY(a == b)) - return 0; - else if (a > b) - return -1; - else - return 1; -} - -static void -queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - g_qsort_with_data(&queue->order[start], end - start, - sizeof(queue->order[0]), - queue_item_compare_order_priority, - queue); -} - -/** - * Shuffle the order of items in the specified range, ignoring their - * priorities. - */ -static void -queue_shuffle_order_range(struct queue *queue, unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - for (unsigned i = start; i < end; ++i) - queue_swap_order(queue, i, - g_rand_int_range(queue->rand, i, end)); -} - -/** - * Sort the "order" of items by priority, and then shuffle each - * priority group. - */ -void -queue_shuffle_order_range_with_priority(struct queue *queue, - unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - if (start == end) - return; - - /* first group the range by priority */ - queue_sort_order_by_priority(queue, start, end); - - /* now shuffle each priority group */ - unsigned group_start = start; - uint8_t group_priority = queue_get_order_priority(queue, start); - - for (unsigned i = start + 1; i < end; ++i) { - uint8_t priority = queue_get_order_priority(queue, i); - assert(priority <= group_priority); - - if (priority != group_priority) { - /* start of a new group - shuffle the one that - has just ended */ - queue_shuffle_order_range(queue, group_start, i); - group_start = i; - group_priority = priority; - } - } - - /* shuffle the last group */ - queue_shuffle_order_range(queue, group_start, end); -} - -void -queue_shuffle_order(struct queue *queue) -{ - queue_shuffle_order_range_with_priority(queue, 0, queue->length); -} - -static void -queue_shuffle_order_first(struct queue *queue, unsigned start, unsigned end) -{ - queue_swap_order(queue, start, - g_rand_int_range(queue->rand, start, end)); -} - -void -queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end) -{ - queue_swap_order(queue, end - 1, - g_rand_int_range(queue->rand, start, end)); -} - -void -queue_shuffle_range(struct queue *queue, unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue->length); - - for (unsigned i = start; i < end; i++) { - unsigned ri = g_rand_int_range(queue->rand, i, end); - queue_swap(queue, i, ri); - } -} - -/** - * Find the first item that has this specified priority or higher. - */ -G_GNUC_PURE -static unsigned -queue_find_priority_order(const struct queue *queue, unsigned start_order, - uint8_t priority, unsigned exclude_order) -{ - assert(queue != NULL); - assert(queue->random); - assert(start_order <= queue->length); - - for (unsigned order = start_order; order < queue->length; ++order) { - const unsigned position = queue_order_to_position(queue, order); - const struct queue_item *item = &queue->items[position]; - if (item->priority <= priority && order != exclude_order) - return order; - } - - return queue->length; -} - -G_GNUC_PURE -static unsigned -queue_count_same_priority(const struct queue *queue, unsigned start_order, - uint8_t priority) -{ - assert(queue != NULL); - assert(queue->random); - assert(start_order <= queue->length); - - for (unsigned order = start_order; order < queue->length; ++order) { - const unsigned position = queue_order_to_position(queue, order); - const struct queue_item *item = &queue->items[position]; - if (item->priority != priority) - return order - start_order; - } - - return queue->length - start_order; -} - -bool -queue_set_priority(struct queue *queue, unsigned position, uint8_t priority, - int after_order) -{ - assert(queue != NULL); - assert(position < queue->length); - - struct queue_item *item = &queue->items[position]; - uint8_t old_priority = item->priority; - if (old_priority == priority) - return false; - - item->version = queue->version; - item->priority = priority; - - if (!queue->random) - /* don't reorder if not in random mode */ - return true; - - unsigned order = queue_position_to_order(queue, position); - if (after_order >= 0) { - if (order == (unsigned)after_order) - /* don't reorder the current song */ - return true; - - if (order < (unsigned)after_order) { - /* the specified song has been played already - - enqueue it only if its priority has just - become bigger than the current one's */ - - const unsigned after_position = - queue_order_to_position(queue, after_order); - const struct queue_item *after_item = - &queue->items[after_position]; - if (old_priority > after_item->priority || - priority <= after_item->priority) - /* priority hasn't become bigger */ - return true; - } - } - - /* move the item to the beginning of the priority group (or - create a new priority group) */ - - const unsigned before_order = - queue_find_priority_order(queue, after_order + 1, priority, - order); - const unsigned new_order = before_order > order - ? before_order - 1 - : before_order; - queue_move_order(queue, order, new_order); - - /* shuffle the song within that priority group */ - - const unsigned priority_count = - queue_count_same_priority(queue, new_order, priority); - assert(priority_count >= 1); - queue_shuffle_order_first(queue, new_order, - new_order + priority_count); - - return true; -} - -bool -queue_set_priority_range(struct queue *queue, - unsigned start_position, unsigned end_position, - uint8_t priority, int after_order) -{ - assert(queue != NULL); - assert(start_position <= end_position); - assert(end_position <= queue->length); - - bool modified = false; - int after_position = after_order >= 0 - ? (int)queue_order_to_position(queue, after_order) - : -1; - for (unsigned i = start_position; i < end_position; ++i) { - after_order = after_position >= 0 - ? (int)queue_position_to_order(queue, after_position) - : -1; - - modified |= queue_set_priority(queue, i, priority, - after_order); - } - - return modified; -} diff --git a/src/queue.h b/src/queue.h deleted file mode 100644 index e4bfcdffa..000000000 --- a/src/queue.h +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef QUEUE_H -#define QUEUE_H - -#include <glib.h> - -#include <assert.h> -#include <stdbool.h> -#include <stdint.h> - -enum { - /** - * reserve max_length * QUEUE_HASH_MULT elements in the id - * number space - */ - QUEUE_HASH_MULT = 4, -}; - -/** - * One element of the queue: basically a song plus some queue specific - * information attached. - */ -struct queue_item { - struct song *song; - - /** the unique id of this item in the queue */ - unsigned id; - - /** when was this item last changed? */ - uint32_t version; - - /** - * The priority of this item, between 0 and 255. High - * priority value means that this song gets played first in - * "random" mode. - */ - uint8_t priority; -}; - -/** - * A queue of songs. This is the backend of the playlist: it contains - * an ordered list of songs. - * - * Songs can be addressed in three possible ways: - * - * - the position in the queue - * - the unique id (which stays the same, regardless of moves) - * - the order number (which only differs from "position" in random mode) - */ -struct queue { - /** configured maximum length of the queue */ - unsigned max_length; - - /** number of songs in the queue */ - unsigned length; - - /** the current version number */ - uint32_t version; - - /** all songs in "position" order */ - struct queue_item *items; - - /** map order numbers to positions */ - unsigned *order; - - /** map song ids to positions */ - int *id_to_position; - - /** repeat playback when the end of the queue has been - reached? */ - bool repeat; - - /** play only current song. */ - bool single; - - /** remove each played files. */ - bool consume; - - /** play back songs in random order? */ - bool random; - - /** random number generator for shuffle and random mode */ - GRand *rand; -}; - -static inline unsigned -queue_length(const struct queue *queue) -{ - assert(queue->length <= queue->max_length); - - return queue->length; -} - -/** - * Determine if the queue is empty, i.e. there are no songs. - */ -static inline bool -queue_is_empty(const struct queue *queue) -{ - return queue->length == 0; -} - -/** - * Determine if the maximum number of songs has been reached. - */ -static inline bool -queue_is_full(const struct queue *queue) -{ - assert(queue->length <= queue->max_length); - - return queue->length >= queue->max_length; -} - -/** - * Is that a valid position number? - */ -static inline bool -queue_valid_position(const struct queue *queue, unsigned position) -{ - return position < queue->length; -} - -/** - * Is that a valid order number? - */ -static inline bool -queue_valid_order(const struct queue *queue, unsigned order) -{ - return order < queue->length; -} - -static inline int -queue_id_to_position(const struct queue *queue, unsigned id) -{ - if (id >= queue->max_length * QUEUE_HASH_MULT) - return -1; - - assert(queue->id_to_position[id] >= -1); - assert(queue->id_to_position[id] < (int)queue->length); - - return queue->id_to_position[id]; -} - -static inline int -queue_position_to_id(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].id; -} - -static inline unsigned -queue_order_to_position(const struct queue *queue, unsigned order) -{ - assert(order < queue->length); - - return queue->order[order]; -} - -static inline unsigned -queue_position_to_order(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - for (unsigned i = 0;; ++i) { - assert(i < queue->length); - - if (queue->order[i] == position) - return i; - } -} - -G_GNUC_PURE -static inline uint8_t -queue_get_priority_at_position(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].priority; -} - -/** - * Returns the song at the specified position. - */ -static inline struct song * -queue_get(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].song; -} - -/** - * Returns the song at the specified order number. - */ -static inline struct song * -queue_get_order(const struct queue *queue, unsigned order) -{ - return queue_get(queue, queue_order_to_position(queue, order)); -} - -/** - * Is the song at the specified position newer than the specified - * version? - */ -static inline bool -queue_song_newer(const struct queue *queue, unsigned position, - uint32_t version) -{ - assert(position < queue->length); - - return version > queue->version || - queue->items[position].version >= version || - queue->items[position].version == 0; -} - -/** - * Initialize a queue object. - */ -void -queue_init(struct queue *queue, unsigned max_length); - -/** - * Deinitializes a queue object. It does not free the queue pointer - * itself. - */ -void -queue_finish(struct queue *queue); - -/** - * Returns the order number following the specified one. This takes - * end of queue and "repeat" mode into account. - * - * @return the next order number, or -1 to stop playback - */ -int -queue_next_order(const struct queue *queue, unsigned order); - -/** - * Increments the queue's version number. This handles integer - * overflow well. - */ -void -queue_increment_version(struct queue *queue); - -/** - * Marks the specified song as "modified" and increments the version - * number. - */ -void -queue_modify(struct queue *queue, unsigned order); - -/** - * Marks all songs as "modified" and increments the version number. - */ -void -queue_modify_all(struct queue *queue); - -/** - * Appends a song to the queue and returns its position. Prior to - * that, the caller must check if the queue is already full. - * - * If a song is not in the database (determined by - * song_in_database()), it is freed when removed from the queue. - * - * @param priority the priority of this new queue item - */ -unsigned -queue_append(struct queue *queue, struct song *song, uint8_t priority); - -/** - * Swaps two songs, addressed by their position. - */ -void -queue_swap(struct queue *queue, unsigned position1, unsigned position2); - -/** - * Swaps two songs, addressed by their order number. - */ -static inline void -queue_swap_order(struct queue *queue, unsigned order1, unsigned order2) -{ - unsigned tmp = queue->order[order1]; - queue->order[order1] = queue->order[order2]; - queue->order[order2] = tmp; -} - -/** - * Moves a song to a new position. - */ -void -queue_move(struct queue *queue, unsigned from, unsigned to); - -/** - * Moves a range of songs to a new position. - */ -void -queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to); - -/** - * Removes a song from the playlist. - */ -void -queue_delete(struct queue *queue, unsigned position); - -/** - * Removes all songs from the playlist. - */ -void -queue_clear(struct queue *queue); - -/** - * Initializes the "order" array, and restores "normal" order. - */ -static inline void -queue_restore_order(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; ++i) - queue->order[i] = i; -} - -/** - * Shuffle the order of items in the specified range, taking their - * priorities into account. - */ -void -queue_shuffle_order_range_with_priority(struct queue *queue, - unsigned start, unsigned end); - -/** - * Shuffles the virtual order of songs, but does not move them - * physically. This is used in random mode. - */ -void -queue_shuffle_order(struct queue *queue); - -/** - * Shuffles the virtual order of the last song in the specified - * (order) range. This is used in random mode after a song has been - * appended by queue_append(). - */ -void -queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end); - -/** - * Shuffles a (position) range in the queue. The songs are physically - * shuffled, not by using the "order" mapping. - */ -void -queue_shuffle_range(struct queue *queue, unsigned start, unsigned end); - -bool -queue_set_priority(struct queue *queue, unsigned position, - uint8_t priority, int after_order); - -bool -queue_set_priority_range(struct queue *queue, - unsigned start_position, unsigned end_position, - uint8_t priority, int after_order); - -#endif diff --git a/src/queue_print.c b/src/queue_print.c deleted file mode 100644 index d149e8b6f..000000000 --- a/src/queue_print.c +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "queue_print.h" -#include "queue.h" -#include "song.h" -#include "song_print.h" -#include "locate.h" -#include "client.h" -#include "mapper.h" - -/** - * Send detailed information about a range of songs in the queue to a - * client. - * - * @param client the client which has requested information - * @param start the index of the first song (including) - * @param end the index of the last song (excluding) - */ -static void -queue_print_song_info(struct client *client, const struct queue *queue, - unsigned position) -{ - song_print_info(client, queue_get(queue, position)); - client_printf(client, "Pos: %u\nId: %u\n", - position, queue_position_to_id(queue, position)); - - uint8_t priority = queue_get_priority_at_position(queue, position); - if (priority != 0) - client_printf(client, "Prio: %u\n", priority); -} - -void -queue_print_info(struct client *client, const struct queue *queue, - unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue_length(queue)); - - for (unsigned i = start; i < end; ++i) - queue_print_song_info(client, queue, i); -} - -void -queue_print_uris(struct client *client, const struct queue *queue, - unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue_length(queue)); - - for (unsigned i = start; i < end; ++i) { - client_printf(client, "%i:", i); - song_print_uri(client, queue_get(queue, i)); - } -} - -void -queue_print_changes_info(struct client *client, const struct queue *queue, - uint32_t version) -{ - for (unsigned i = 0; i < queue_length(queue); i++) { - if (queue_song_newer(queue, i, version)) - queue_print_song_info(client, queue, i); - } -} - -void -queue_print_changes_position(struct client *client, const struct queue *queue, - uint32_t version) -{ - for (unsigned i = 0; i < queue_length(queue); i++) - if (queue_song_newer(queue, i, version)) - client_printf(client, "cpos: %i\nId: %i\n", - i, queue_position_to_id(queue, i)); -} - -void -queue_search(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria) -{ - unsigned i; - struct locate_item_list *new_list = - locate_item_list_casefold(criteria); - - for (i = 0; i < queue_length(queue); i++) { - const struct song *song = queue_get(queue, i); - - if (locate_song_search(song, new_list)) - queue_print_song_info(client, queue, i); - } - - locate_item_list_free(new_list); -} - -void -queue_find(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria) -{ - for (unsigned i = 0; i < queue_length(queue); i++) { - const struct song *song = queue_get(queue, i); - - if (locate_song_match(song, criteria)) - queue_print_song_info(client, queue, i); - } -} diff --git a/src/queue_print.h b/src/queue_print.h deleted file mode 100644 index 371e20416..000000000 --- a/src/queue_print.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This library sends information about songs in the queue to the - * client. - */ - -#ifndef QUEUE_PRINT_H -#define QUEUE_PRINT_H - -#include <stdint.h> - -struct client; -struct queue; -struct locate_item_list; - -void -queue_print_info(struct client *client, const struct queue *queue, - unsigned start, unsigned end); - -void -queue_print_uris(struct client *client, const struct queue *queue, - unsigned start, unsigned end); - -void -queue_print_changes_info(struct client *client, const struct queue *queue, - uint32_t version); - -void -queue_print_changes_position(struct client *client, const struct queue *queue, - uint32_t version); - -void -queue_search(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria); - -void -queue_find(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria); - -#endif diff --git a/src/queue_save.c b/src/queue_save.c deleted file mode 100644 index 16852d3c1..000000000 --- a/src/queue_save.c +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "queue_save.h" -#include "queue.h" -#include "song.h" -#include "uri.h" -#include "database.h" -#include "song_save.h" -#include "text_file.h" - -#include <stdlib.h> - -#define PRIO_LABEL "Prio: " - -static void -queue_save_database_song(FILE *fp, int idx, const struct song *song) -{ - char *uri = song_get_uri(song); - - fprintf(fp, "%i:%s\n", idx, uri); - g_free(uri); -} - -static void -queue_save_full_song(FILE *fp, const struct song *song) -{ - song_save(fp, song); -} - -static void -queue_save_song(FILE *fp, int idx, const struct song *song) -{ - if (song_in_database(song)) - queue_save_database_song(fp, idx, song); - else - queue_save_full_song(fp, song); -} - -void -queue_save(FILE *fp, const struct queue *queue) -{ - for (unsigned i = 0; i < queue_length(queue); i++) { - uint8_t prio = queue_get_priority_at_position(queue, i); - if (prio != 0) - fprintf(fp, PRIO_LABEL "%u\n", prio); - - queue_save_song(fp, i, queue_get(queue, i)); - } -} - -static struct song * -get_song(const char *uri) -{ - return uri_has_scheme(uri) - ? song_remote_new(uri) - : db_get_song(uri); -} - -void -queue_load_song(FILE *fp, GString *buffer, const char *line, - struct queue *queue) -{ - struct song *song; - - if (queue_is_full(queue)) - return; - - uint8_t priority = 0; - if (g_str_has_prefix(line, PRIO_LABEL)) { - priority = strtoul(line + sizeof(PRIO_LABEL) - 1, NULL, 10); - - line = read_text_line(fp, buffer); - if (line == NULL) - return; - } - - if (g_str_has_prefix(line, SONG_BEGIN)) { - const char *uri = line + sizeof(SONG_BEGIN) - 1; - if (!uri_has_scheme(uri) && !g_path_is_absolute(uri)) - return; - - GError *error = NULL; - song = song_load(fp, NULL, uri, buffer, &error); - if (song == NULL) { - g_warning("%s", error->message); - g_error_free(error); - return; - } - } else { - char *endptr; - long ret = strtol(line, &endptr, 10); - if (ret < 0 || *endptr != ':' || endptr[1] == 0) { - g_warning("Malformed playlist line in state file"); - return; - } - - line = endptr + 1; - - song = get_song(line); - if (song == NULL) - return; - } - - queue_append(queue, song, priority); -} diff --git a/src/queue_save.h b/src/queue_save.h deleted file mode 100644 index 5526d615d..000000000 --- a/src/queue_save.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This library saves the queue into the state file, and also loads it - * back into memory. - */ - -#ifndef QUEUE_SAVE_H -#define QUEUE_SAVE_H - -#include <glib.h> -#include <stdio.h> - -struct queue; - -void -queue_save(FILE *fp, const struct queue *queue); - -/** - * Loads one song from the state file and appends it to the queue. - */ -void -queue_load_song(FILE *fp, GString *buffer, const char *line, - struct queue *queue); - -#endif diff --git a/src/refcount.h b/src/refcount.h deleted file mode 100644 index a882d76b0..000000000 --- a/src/refcount.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** \file - * - * A very simple reference counting library. - */ - -#ifndef MPD_REFCOUNT_H -#define MPD_REFCOUNT_H - -#include <glib.h> -#include <stdbool.h> - -struct refcount { - gint n; -}; - -static inline void -refcount_init(struct refcount *r) -{ - r->n = 1; -} - -static inline void -refcount_inc(struct refcount *r) -{ - g_atomic_int_inc(&r->n); -} - -/** - * @return true if the number of references has been dropped to 0 - */ -static inline bool -refcount_dec(struct refcount *r) -{ - return g_atomic_int_dec_and_test(&r->n); -} - -#endif diff --git a/src/replay_gain_config.c b/src/replay_gain_config.c deleted file mode 100644 index 2181387b7..000000000 --- a/src/replay_gain_config.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "replay_gain_config.h" -#include "playlist.h" -#include "conf.h" -#include "idle.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> -#include <string.h> -#include <math.h> - -static const char *const replay_gain_mode_names[] = { - [REPLAY_GAIN_ALBUM] = "album", - [REPLAY_GAIN_TRACK] = "track", -}; - -enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF; - -const bool DEFAULT_REPLAYGAIN_LIMIT = true; - -float replay_gain_preamp = 1.0; -float replay_gain_missing_preamp = 1.0; -bool replay_gain_limit; - -const char * -replay_gain_get_mode_string(void) -{ - switch (replay_gain_mode) { - case REPLAY_GAIN_AUTO: - return "auto"; - - case REPLAY_GAIN_OFF: - return "off"; - - case REPLAY_GAIN_TRACK: - return "track"; - - case REPLAY_GAIN_ALBUM: - return "album"; - } - - /* unreachable */ - assert(false); - return "off"; -} - -bool -replay_gain_set_mode_string(const char *p) -{ - assert(p != NULL); - - if (strcmp(p, "off") == 0) - replay_gain_mode = REPLAY_GAIN_OFF; - else if (strcmp(p, "track") == 0) - replay_gain_mode = REPLAY_GAIN_TRACK; - else if (strcmp(p, "album") == 0) - replay_gain_mode = REPLAY_GAIN_ALBUM; - else if (strcmp(p, "auto") == 0) - replay_gain_mode = REPLAY_GAIN_AUTO; - else - return false; - - idle_add(IDLE_OPTIONS); - - return true; -} - -void replay_gain_global_init(void) -{ - const struct config_param *param = config_get_param(CONF_REPLAYGAIN); - - if (param != NULL && !replay_gain_set_mode_string(param->value)) { - MPD_ERROR("replaygain value \"%s\" at line %i is invalid\n", - param->value, param->line); - } - - param = config_get_param(CONF_REPLAYGAIN_PREAMP); - - if (param) { - char *test; - float f = strtod(param->value, &test); - - if (*test != '\0') { - MPD_ERROR("Replaygain preamp \"%s\" is not a number at " - "line %i\n", param->value, param->line); - } - - if (f < -15 || f > 15) { - MPD_ERROR("Replaygain preamp \"%s\" is not between -15 and" - "15 at line %i\n", param->value, param->line); - } - - replay_gain_preamp = pow(10, f / 20.0); - } - - param = config_get_param(CONF_REPLAYGAIN_MISSING_PREAMP); - - if (param) { - char *test; - float f = strtod(param->value, &test); - - if (*test != '\0') { - MPD_ERROR("Replaygain missing preamp \"%s\" is not a number at " - "line %i\n", param->value, param->line); - } - - if (f < -15 || f > 15) { - MPD_ERROR("Replaygain missing preamp \"%s\" is not between -15 and" - "15 at line %i\n", param->value, param->line); - } - - replay_gain_missing_preamp = pow(10, f / 20.0); - } - - replay_gain_limit = config_get_bool(CONF_REPLAYGAIN_LIMIT, DEFAULT_REPLAYGAIN_LIMIT); -} - -enum replay_gain_mode replay_gain_get_real_mode(void) -{ - enum replay_gain_mode rgm; - - rgm = replay_gain_mode; - - if (rgm == REPLAY_GAIN_AUTO) - rgm = g_playlist.queue.random ? REPLAY_GAIN_TRACK : REPLAY_GAIN_ALBUM; - - return rgm; -} diff --git a/src/replay_gain_config.h b/src/replay_gain_config.h index 18747cef2..4b59334c0 100644 --- a/src/replay_gain_config.h +++ b/src/replay_gain_config.h @@ -30,6 +30,10 @@ extern float replay_gain_preamp; extern float replay_gain_missing_preamp; extern bool replay_gain_limit; +#ifdef __cplusplus +extern "C" { +#endif + void replay_gain_global_init(void); /** @@ -50,6 +54,10 @@ replay_gain_set_mode_string(const char *p); * Returns the "real" mode according to the "auto" setting" */ enum replay_gain_mode -replay_gain_get_real_mode(void); +replay_gain_get_real_mode(bool random_mode); + +#ifdef __cplusplus +} +#endif #endif diff --git a/src/replay_gain_info.c b/src/replay_gain_info.c deleted file mode 100644 index 1f09e7a1a..000000000 --- a/src/replay_gain_info.c +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "replay_gain_info.h" - -float -replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, float missing_preamp, bool peak_limit) -{ - float scale; - - if (replay_gain_tuple_defined(tuple)) { - scale = pow(10.0, tuple->gain / 20.0); - scale *= preamp; - if (scale > 15.0) - scale = 15.0; - - if (peak_limit && scale * tuple->peak > 1.0) - scale = 1.0 / tuple->peak; - } else - scale = missing_preamp; - - return scale; -} - -void -replay_gain_info_complete(struct replay_gain_info *info) -{ - if (!replay_gain_tuple_defined(&info->tuples[REPLAY_GAIN_ALBUM])) - info->tuples[REPLAY_GAIN_ALBUM] = - info->tuples[REPLAY_GAIN_TRACK]; -} diff --git a/src/replay_gain_info.h b/src/replay_gain_info.h index 9097c3e02..b06dc6cf0 100644 --- a/src/replay_gain_info.h +++ b/src/replay_gain_info.h @@ -22,8 +22,12 @@ #include "check.h" +#ifdef __cplusplus +#include <cmath> +#else #include <stdbool.h> #include <math.h> +#endif enum replay_gain_mode { REPLAY_GAIN_AUTO = -2, @@ -58,9 +62,17 @@ replay_gain_info_init(struct replay_gain_info *info) static inline bool replay_gain_tuple_defined(const struct replay_gain_tuple *tuple) { +#ifdef __cplusplus + return !std::isinf(tuple->gain); +#else return !isinf(tuple->gain); +#endif } +#ifdef __cplusplus +extern "C" { +#endif + float replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, float missing_preamp, bool peak_limit); @@ -71,4 +83,8 @@ replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, flo void replay_gain_info_complete(struct replay_gain_info *info); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/resolver.h b/src/resolver.h index e5ad06754..af14f5f23 100644 --- a/src/resolver.h +++ b/src/resolver.h @@ -20,18 +20,24 @@ #ifndef MPD_RESOLVER_H #define MPD_RESOLVER_H +#include "gcc.h" + #include <glib.h> struct sockaddr; struct addrinfo; -G_GNUC_CONST +gcc_const static inline GQuark resolver_quark(void) { return g_quark_from_static_string("resolver"); } +#ifdef __cplusplus +extern "C" { +#endif + /** * Converts the specified socket address into a string in the form * "IP:PORT". The return value must be freed with g_free() when you @@ -42,7 +48,7 @@ resolver_quark(void) * @param error location to store the error occurring, or NULL to * ignore errors */ -G_GNUC_MALLOC +gcc_malloc char * sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error); @@ -61,4 +67,8 @@ resolve_host_port(const char *host_port, unsigned default_port, int flags, int socktype, GError **error_r); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/server_socket.c b/src/server_socket.c deleted file mode 100644 index 396399596..000000000 --- a/src/server_socket.c +++ /dev/null @@ -1,483 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" - -#ifdef HAVE_STRUCT_UCRED -#define _GNU_SOURCE 1 -#endif - -#include "server_socket.h" -#include "socket_util.h" -#include "resolver.h" -#include "fd_util.h" -#include "glib_socket.h" - -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <string.h> -#include <errno.h> -#include <unistd.h> -#include <stdlib.h> -#include <assert.h> - -#ifdef WIN32 -#include <ws2tcpip.h> -#include <winsock.h> -#else -#include <netinet/in.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <netdb.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "listen" - -#define DEFAULT_PORT 6600 - -struct one_socket { - struct one_socket *next; - struct server_socket *parent; - - unsigned serial; - - int fd; - guint source_id; - - char *path; - - size_t address_length; - struct sockaddr address; -}; - -struct server_socket { - server_socket_callback_t callback; - void *callback_ctx; - - struct one_socket *sockets, **sockets_tail_r; - unsigned next_serial; -}; - -static GQuark -server_socket_quark(void) -{ - return g_quark_from_static_string("server_socket"); -} - -struct server_socket * -server_socket_new(server_socket_callback_t callback, void *callback_ctx) -{ - struct server_socket *ss = g_new(struct server_socket, 1); - ss->callback = callback; - ss->callback_ctx = callback_ctx; - ss->sockets = NULL; - ss->sockets_tail_r = &ss->sockets; - ss->next_serial = 1; - return ss; -} - -void -server_socket_free(struct server_socket *ss) -{ - server_socket_close(ss); - - while (ss->sockets != NULL) { - struct one_socket *s = ss->sockets; - ss->sockets = s->next; - - assert(s->fd < 0); - - g_free(s->path); - g_free(s); - } - - g_free(ss); -} - -/** - * Wraper for sockaddr_to_string() which never fails. - */ -static char * -one_socket_to_string(const struct one_socket *s) -{ - char *p = sockaddr_to_string(&s->address, s->address_length, NULL); - if (p == NULL) - p = g_strdup("[unknown]"); - return p; -} - -static int -get_remote_uid(int fd) -{ -#ifdef HAVE_STRUCT_UCRED - struct ucred cred; - socklen_t len = sizeof (cred); - - if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0) - return 0; - - return cred.uid; -#else -#ifdef HAVE_GETPEEREID - uid_t euid; - gid_t egid; - - if (getpeereid(fd, &euid, &egid) == 0) - return euid; -#else - (void)fd; -#endif - return -1; -#endif -} - -static gboolean -server_socket_in_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - gpointer data) -{ - struct one_socket *s = data; - - struct sockaddr_storage address; - size_t address_length = sizeof(address); - int fd = accept_cloexec_nonblock(s->fd, (struct sockaddr*)&address, - &address_length); - if (fd >= 0) { - if (socket_keepalive(fd)) - g_warning("Could not set TCP keepalive option: %s", - g_strerror(errno)); - s->parent->callback(fd, (const struct sockaddr*)&address, - address_length, get_remote_uid(fd), - s->parent->callback_ctx); - } else { - g_warning("accept() failed: %s", g_strerror(errno)); - } - - return true; -} - -static void -set_fd(struct one_socket *s, int fd) -{ - assert(s != NULL); - assert(s->fd < 0); - assert(fd >= 0); - - s->fd = fd; - - GIOChannel *channel = g_io_channel_new_socket(s->fd); - s->source_id = g_io_add_watch(channel, G_IO_IN, - server_socket_in_event, s); - g_io_channel_unref(channel); -} - -bool -server_socket_open(struct server_socket *ss, GError **error_r) -{ - struct one_socket *good = NULL, *bad = NULL; - GError *last_error = NULL; - - for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) { - assert(s->serial > 0); - assert(good == NULL || s->serial >= good->serial); - assert(s->fd < 0); - - if (bad != NULL && s->serial != bad->serial) { - server_socket_close(ss); - g_propagate_error(error_r, last_error); - return false; - } - - GError *error = NULL; - int fd = socket_bind_listen(s->address.sa_family, - SOCK_STREAM, 0, - &s->address, s->address_length, 5, - &error); - if (fd < 0) { - if (good != NULL && good->serial == s->serial) { - char *address_string = one_socket_to_string(s); - char *good_string = one_socket_to_string(good); - g_warning("bind to '%s' failed: %s " - "(continuing anyway, because " - "binding to '%s' succeeded)", - address_string, error->message, - good_string); - g_free(address_string); - g_free(good_string); - g_error_free(error); - } else if (bad == NULL) { - bad = s; - - char *address_string = one_socket_to_string(s); - g_propagate_prefixed_error(&last_error, error, - "Failed to bind to '%s': ", - address_string); - g_free(address_string); - } else - g_error_free(error); - continue; - } - - /* allow everybody to connect */ - - if (s->path != NULL) - chmod(s->path, 0666); - - /* register in the GLib main loop */ - - set_fd(s, fd); - - /* mark this socket as "good", and clear previous - errors */ - - good = s; - - if (bad != NULL) { - bad = NULL; - g_error_free(last_error); - last_error = NULL; - } - } - - if (bad != NULL) { - server_socket_close(ss); - g_propagate_error(error_r, last_error); - return false; - } - - return true; -} - -void -server_socket_close(struct server_socket *ss) -{ - for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) { - if (s->fd < 0) - continue; - - g_source_remove(s->source_id); - close_socket(s->fd); - s->fd = -1; - } -} - -static struct one_socket * -one_socket_new(unsigned serial, const struct sockaddr *address, - size_t address_length) -{ - assert(address != NULL); - assert(address_length > 0); - - struct one_socket *s = g_malloc(sizeof(*s) - sizeof(s->address) + - address_length); - s->next = NULL; - s->serial = serial; - s->fd = -1; - s->path = NULL; - s->address_length = address_length; - memcpy(&s->address, address, address_length); - - return s; -} - -bool -server_socket_add_fd(struct server_socket *ss, int fd, GError **error_r) -{ - assert(ss != NULL); - assert(ss->sockets_tail_r != NULL); - assert(*ss->sockets_tail_r == NULL); - assert(fd >= 0); - - struct sockaddr_storage address; - socklen_t address_length; - if (getsockname(fd, (struct sockaddr *)&address, - &address_length) < 0) { - g_set_error(error_r, server_socket_quark(), errno, - "Failed to get socket address: %s", - g_strerror(errno)); - return false; - } - - struct one_socket *s = one_socket_new(ss->next_serial, - (struct sockaddr *)&address, - address_length); - s->parent = ss; - *ss->sockets_tail_r = s; - ss->sockets_tail_r = &s->next; - - set_fd(s, fd); - - return true; -} - -static struct one_socket * -server_socket_add_address(struct server_socket *ss, - const struct sockaddr *address, - size_t address_length) -{ - assert(ss != NULL); - assert(ss->sockets_tail_r != NULL); - assert(*ss->sockets_tail_r == NULL); - - struct one_socket *s = one_socket_new(ss->next_serial, - address, address_length); - s->parent = ss; - *ss->sockets_tail_r = s; - ss->sockets_tail_r = &s->next; - - return s; -} - -#ifdef HAVE_TCP - -/** - * Add a listener on a port on all IPv4 interfaces. - * - * @param port the TCP port - */ -static void -server_socket_add_port_ipv4(struct server_socket *ss, unsigned port) -{ - struct sockaddr_in sin; - memset(&sin, 0, sizeof(sin)); - sin.sin_port = htons(port); - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = INADDR_ANY; - - server_socket_add_address(ss, (const struct sockaddr *)&sin, - sizeof(sin)); -} - -#ifdef HAVE_IPV6 -/** - * Add a listener on a port on all IPv6 interfaces. - * - * @param port the TCP port - */ -static void -server_socket_add_port_ipv6(struct server_socket *ss, unsigned port) -{ - struct sockaddr_in6 sin; - memset(&sin, 0, sizeof(sin)); - sin.sin6_port = htons(port); - sin.sin6_family = AF_INET6; - - server_socket_add_address(ss, (const struct sockaddr *)&sin, - sizeof(sin)); -} -#endif /* HAVE_IPV6 */ - -#endif /* HAVE_TCP */ - -bool -server_socket_add_port(struct server_socket *ss, unsigned port, - GError **error_r) -{ -#ifdef HAVE_TCP - if (port == 0 || port > 0xffff) { - g_set_error(error_r, server_socket_quark(), 0, - "Invalid TCP port"); - return false; - } - -#ifdef HAVE_IPV6 - server_socket_add_port_ipv6(ss, port); -#endif - server_socket_add_port_ipv4(ss, port); - - ++ss->next_serial; - - return true; -#else /* HAVE_TCP */ - (void)ss; - (void)port; - - g_set_error(error_r, server_socket_quark(), 0, - "TCP support is disabled"); - return false; -#endif /* HAVE_TCP */ -} - -bool -server_socket_add_host(struct server_socket *ss, const char *hostname, - unsigned port, GError **error_r) -{ -#ifdef HAVE_TCP - struct addrinfo *ai = resolve_host_port(hostname, port, - AI_PASSIVE, SOCK_STREAM, - error_r); - if (ai == NULL) - return false; - - for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next) - server_socket_add_address(ss, i->ai_addr, i->ai_addrlen); - - freeaddrinfo(ai); - - ++ss->next_serial; - - return true; -#else /* HAVE_TCP */ - (void)ss; - (void)hostname; - (void)port; - - g_set_error(error_r, server_socket_quark(), 0, - "TCP support is disabled"); - return false; -#endif /* HAVE_TCP */ -} - -bool -server_socket_add_path(struct server_socket *ss, const char *path, - GError **error_r) -{ -#ifdef HAVE_UN - struct sockaddr_un s_un; - - size_t path_length = strlen(path); - if (path_length >= sizeof(s_un.sun_path)) { - g_set_error(error_r, server_socket_quark(), 0, - "UNIX socket path is too long"); - return false; - } - - unlink(path); - - s_un.sun_family = AF_UNIX; - memcpy(s_un.sun_path, path, path_length + 1); - - struct one_socket *s = - server_socket_add_address(ss, (const struct sockaddr *)&s_un, - sizeof(s_un)); - s->path = g_strdup(path); - - return true; -#else /* !HAVE_UN */ - (void)ss; - (void)path; - - g_set_error(error_r, server_socket_quark(), 0, - "UNIX domain socket support is disabled"); - return false; -#endif /* !HAVE_UN */ -} - diff --git a/src/server_socket.h b/src/server_socket.h deleted file mode 100644 index 7caa4bbf2..000000000 --- a/src/server_socket.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SERVER_SOCKET_H -#define MPD_SERVER_SOCKET_H - -#include <stdbool.h> - -#include <glib.h> - -struct sockaddr; - -typedef void (*server_socket_callback_t)(int fd, - const struct sockaddr *address, - size_t address_length, int uid, - void *ctx); - -struct server_socket * -server_socket_new(server_socket_callback_t callback, void *callback_ctx); - -void -server_socket_free(struct server_socket *ss); - -bool -server_socket_open(struct server_socket *ss, GError **error_r); - -void -server_socket_close(struct server_socket *ss); - -/** - * Add a socket descriptor that is accepting connections. After this - * has been called, don't call server_socket_open(), because the - * socket is already open. - */ -bool -server_socket_add_fd(struct server_socket *ss, int fd, GError **error_r); - -/** - * Add a listener on a port on all interfaces. - * - * @param port the TCP port - * @param error_r location to store the error occurring, or NULL to - * ignore errors - * @return true on success - */ -bool -server_socket_add_port(struct server_socket *ss, unsigned port, - GError **error_r); - -/** - * Resolves a host name, and adds listeners on all addresses in the - * result set. - * - * @param hostname the host name to be resolved - * @param port the TCP port - * @param error_r location to store the error occurring, or NULL to - * ignore errors - * @return true on success - */ -bool -server_socket_add_host(struct server_socket *ss, const char *hostname, - unsigned port, GError **error_r); - -/** - * Add a listener on a Unix domain socket. - * - * @param path the absolute socket path - * @param error_r location to store the error occurring, or NULL to - * ignore errors - * @return true on success - */ -bool -server_socket_add_path(struct server_socket *ss, const char *path, - GError **error_r); - -#endif diff --git a/src/sig_handlers.c b/src/sig_handlers.c deleted file mode 100644 index b23f9e778..000000000 --- a/src/sig_handlers.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "sig_handlers.h" - -#ifndef WIN32 - -#include "log.h" -#include "main.h" -#include "event_pipe.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <signal.h> -#include <errno.h> -#include <string.h> - -static void exit_signal_handler(G_GNUC_UNUSED int signum) -{ - g_main_loop_quit(main_loop); -} - -static void reload_signal_handler(G_GNUC_UNUSED int signum) -{ - event_pipe_emit_fast(PIPE_EVENT_RELOAD); -} - -static void -x_sigaction(int signum, const struct sigaction *act) -{ - if (sigaction(signum, act, NULL) < 0) - MPD_ERROR("sigaction() failed: %s", strerror(errno)); -} - -static void -handle_reload_event(void) -{ - g_debug("got SIGHUP, reopening log files"); - cycle_log_files(); -} - -#endif - -void initSigHandlers(void) -{ -#ifndef WIN32 - struct sigaction sa; - - sa.sa_flags = 0; - sigemptyset(&sa.sa_mask); - sa.sa_handler = SIG_IGN; - x_sigaction(SIGPIPE, &sa); - - sa.sa_handler = exit_signal_handler; - x_sigaction(SIGINT, &sa); - x_sigaction(SIGTERM, &sa); - - event_pipe_register(PIPE_EVENT_RELOAD, handle_reload_event); - sa.sa_handler = reload_signal_handler; - x_sigaction(SIGHUP, &sa); -#endif -} diff --git a/src/sig_handlers.h b/src/sig_handlers.h deleted file mode 100644 index 32e9bad95..000000000 --- a/src/sig_handlers.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SIG_HANDLERS_H -#define MPD_SIG_HANDLERS_H - -void initSigHandlers(void); - -#endif diff --git a/src/socket_util.c b/src/socket_util.c deleted file mode 100644 index a06a0cbd5..000000000 --- a/src/socket_util.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "socket_util.h" -#include "fd_util.h" - -#include <errno.h> -#include <unistd.h> - -#ifndef G_OS_WIN32 -#include <sys/socket.h> -#else /* G_OS_WIN32 */ -#include <ws2tcpip.h> -#include <winsock.h> -#endif /* G_OS_WIN32 */ - -#ifdef HAVE_IPV6 -#include <string.h> -#endif - -static GQuark -listen_quark(void) -{ - return g_quark_from_static_string("listen"); -} - -int -socket_bind_listen(int domain, int type, int protocol, - const struct sockaddr *address, size_t address_length, - int backlog, - GError **error) -{ - int fd, ret; - const int reuse = 1; - - fd = socket_cloexec_nonblock(domain, type, protocol); - if (fd < 0) { - g_set_error(error, listen_quark(), errno, - "Failed to create socket: %s", g_strerror(errno)); - return -1; - } - - ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, - (const char *) &reuse, sizeof(reuse)); - if (ret < 0) { - g_set_error(error, listen_quark(), errno, - "setsockopt() failed: %s", g_strerror(errno)); - close_socket(fd); - return -1; - } - - ret = bind(fd, address, address_length); - if (ret < 0) { - g_set_error(error, listen_quark(), errno, - "%s", g_strerror(errno)); - close_socket(fd); - return -1; - } - - ret = listen(fd, backlog); - if (ret < 0) { - g_set_error(error, listen_quark(), errno, - "listen() failed: %s", g_strerror(errno)); - close_socket(fd); - return -1; - } - -#ifdef HAVE_STRUCT_UCRED - setsockopt(fd, SOL_SOCKET, SO_PASSCRED, - (const char *) &reuse, sizeof(reuse)); -#endif - - return fd; -} - -int -socket_keepalive(int fd) -{ - const int reuse = 1; - - return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, - (const char *)&reuse, sizeof(reuse)); -} diff --git a/src/socket_util.h b/src/socket_util.h deleted file mode 100644 index 93bd27362..000000000 --- a/src/socket_util.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This library provides easy helper functions for working with - * sockets. - * - */ - -#ifndef SOCKET_UTIL_H -#define SOCKET_UTIL_H - -#include <glib.h> - -struct sockaddr; - -/** - * Creates a socket listening on the specified address. This is a - * shortcut for socket(), bind() and listen(). - * - * @param domain the socket domain, e.g. PF_INET6 - * @param type the socket type, e.g. SOCK_STREAM - * @param protocol the protocol, usually 0 to let the kernel choose - * @param address the address to listen on - * @param address_length the size of #address - * @param backlog the backlog parameter for the listen() system call - * @param error location to store the error occurring, or NULL to - * ignore errors - * @return the socket file descriptor or -1 on error - */ -int -socket_bind_listen(int domain, int type, int protocol, - const struct sockaddr *address, size_t address_length, - int backlog, - GError **error); - -int -socket_keepalive(int fd); - -#endif diff --git a/src/song.c b/src/song.c deleted file mode 100644 index f5cc7c35a..000000000 --- a/src/song.c +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "song.h" -#include "uri.h" -#include "directory.h" -#include "tag.h" - -#include <glib.h> - -#include <assert.h> - -static struct song * -song_alloc(const char *uri, struct directory *parent) -{ - size_t uri_length; - struct song *song; - - assert(uri); - uri_length = strlen(uri); - assert(uri_length); - song = g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1); - - song->tag = NULL; - memcpy(song->uri, uri, uri_length + 1); - song->parent = parent; - song->mtime = 0; - song->start_ms = song->end_ms = 0; - - return song; -} - -struct song * -song_remote_new(const char *uri) -{ - return song_alloc(uri, NULL); -} - -struct song * -song_file_new(const char *path, struct directory *parent) -{ - assert((parent == NULL) == (*path == '/')); - - return song_alloc(path, parent); -} - -struct song * -song_replace_uri(struct song *old_song, const char *uri) -{ - struct song *new_song = song_alloc(uri, old_song->parent); - new_song->tag = old_song->tag; - new_song->mtime = old_song->mtime; - new_song->start_ms = old_song->start_ms; - new_song->end_ms = old_song->end_ms; - g_free(old_song); - return new_song; -} - -void -song_free(struct song *song) -{ - if (song->tag) - tag_free(song->tag); - g_free(song); -} - -char * -song_get_uri(const struct song *song) -{ - assert(song != NULL); - assert(*song->uri); - - if (!song_in_database(song) || directory_is_root(song->parent)) - return g_strdup(song->uri); - else - return g_strconcat(directory_get_path(song->parent), - "/", song->uri, NULL); -} - -double -song_get_duration(const struct song *song) -{ - if (song->end_ms > 0) - return (song->end_ms - song->start_ms) / 1000.0; - - if (song->tag == NULL) - return 0; - - return song->tag->time - song->start_ms / 1000.0; -} diff --git a/src/song.h b/src/song.h index 8b97d45d0..4095317f6 100644 --- a/src/song.h +++ b/src/song.h @@ -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/song_print.c b/src/song_print.c deleted file mode 100644 index fb608a8b2..000000000 --- a/src/song_print.c +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "song_print.h" -#include "song.h" -#include "directory.h" -#include "tag_print.h" -#include "client.h" -#include "uri.h" -#include "mapper.h" - -void -song_print_uri(struct client *client, struct song *song) -{ - if (song_in_database(song) && !directory_is_root(song->parent)) { - client_printf(client, "%s%s/%s\n", SONG_FILE, - directory_get_path(song->parent), song->uri); - } else { - char *allocated; - const char *uri; - - uri = allocated = uri_remove_auth(song->uri); - if (uri == NULL) - uri = song->uri; - - client_printf(client, "%s%s\n", SONG_FILE, - map_to_relative_path(uri)); - - g_free(allocated); - } -} - -void -song_print_info(struct client *client, struct song *song) -{ - song_print_uri(client, song); - - if (song->end_ms > 0) - client_printf(client, "Range: %u.%03u-%u.%03u\n", - song->start_ms / 1000, - song->start_ms % 1000, - song->end_ms / 1000, - song->end_ms % 1000); - else if (song->start_ms > 0) - client_printf(client, "Range: %u.%03u-\n", - song->start_ms / 1000, - song->start_ms % 1000); - - if (song->mtime > 0) { -#ifndef G_OS_WIN32 - struct tm tm; -#endif - const struct tm *tm2; - -#ifdef G_OS_WIN32 - tm2 = gmtime(&song->mtime); -#else - tm2 = gmtime_r(&song->mtime, &tm); -#endif - - if (tm2 != NULL) { - char timestamp[32]; - - strftime(timestamp, sizeof(timestamp), -#ifdef G_OS_WIN32 - "%Y-%m-%dT%H:%M:%SZ", -#else - "%FT%TZ", -#endif - tm2); - client_printf(client, "Last-Modified: %s\n", - timestamp); - } - } - - if (song->tag) - tag_print(client, song->tag); -} diff --git a/src/song_print.h b/src/song_print.h deleted file mode 100644 index 8f1f0cc65..000000000 --- a/src/song_print.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SONG_PRINT_H -#define MPD_SONG_PRINT_H - -struct client; -struct song; -struct songvec; - -void -song_print_info(struct client *client, struct song *song); - -void -song_print_uri(struct client *client, struct song *song); - -#endif diff --git a/src/song_save.c b/src/song_save.c deleted file mode 100644 index 4fcb46e22..000000000 --- a/src/song_save.c +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "song_save.h" -#include "song.h" -#include "tag_save.h" -#include "directory.h" -#include "tag.h" -#include "text_file.h" -#include "string_util.h" - -#include <glib.h> - -#include <stdlib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "song" - -#define SONG_MTIME "mtime" -#define SONG_END "song_end" - -static GQuark -song_save_quark(void) -{ - return g_quark_from_static_string("song_save"); -} - -void -song_save(FILE *fp, const struct song *song) -{ - fprintf(fp, SONG_BEGIN "%s\n", song->uri); - - if (song->end_ms > 0) - fprintf(fp, "Range: %u-%u\n", song->start_ms, song->end_ms); - else if (song->start_ms > 0) - fprintf(fp, "Range: %u-\n", song->start_ms); - - if (song->tag != NULL) - tag_save(fp, song->tag); - - fprintf(fp, SONG_MTIME ": %li\n", (long)song->mtime); - fprintf(fp, SONG_END "\n"); -} - -struct song * -song_load(FILE *fp, struct directory *parent, const char *uri, - GString *buffer, GError **error_r) -{ - struct song *song = parent != NULL - ? song_file_new(uri, parent) - : song_remote_new(uri); - char *line, *colon; - enum tag_type type; - const char *value; - - while ((line = read_text_line(fp, buffer)) != NULL && - strcmp(line, SONG_END) != 0) { - colon = strchr(line, ':'); - if (colon == NULL || colon == line) { - if (song->tag != NULL) - tag_end_add(song->tag); - song_free(song); - - g_set_error(error_r, song_save_quark(), 0, - "unknown line in db: %s", line); - return NULL; - } - - *colon++ = 0; - value = strchug_fast_c(colon); - - if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) { - if (!song->tag) { - song->tag = tag_new(); - tag_begin_add(song->tag); - } - - tag_add_item(song->tag, type, value); - } else if (strcmp(line, "Time") == 0) { - if (!song->tag) { - song->tag = tag_new(); - tag_begin_add(song->tag); - } - - song->tag->time = atoi(value); - } else if (strcmp(line, "Playlist") == 0) { - if (!song->tag) { - song->tag = tag_new(); - tag_begin_add(song->tag); - } - - song->tag->has_playlist = strcmp(value, "yes") == 0; - } else if (strcmp(line, SONG_MTIME) == 0) { - song->mtime = atoi(value); - } else if (strcmp(line, "Range") == 0) { - char *endptr; - - song->start_ms = strtoul(value, &endptr, 10); - if (*endptr == '-') - song->end_ms = strtoul(endptr + 1, NULL, 10); - } else { - if (song->tag != NULL) - tag_end_add(song->tag); - song_free(song); - - g_set_error(error_r, song_save_quark(), 0, - "unknown line in db: %s", line); - return NULL; - } - } - - if (song->tag != NULL) - tag_end_add(song->tag); - - return song; -} diff --git a/src/song_save.h b/src/song_save.h deleted file mode 100644 index f6ecbbfeb..000000000 --- a/src/song_save.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SONG_SAVE_H -#define MPD_SONG_SAVE_H - -#include <glib.h> - -#include <stdio.h> - -#define SONG_BEGIN "song_begin: " - -struct song; -struct directory; - -void -song_save(FILE *fp, const struct song *song); - -/** - * Loads a song from the input file. Reading stops after the - * "song_end" line. - * - * @param error_r location to store the error occurring, or NULL to - * ignore errors - * @return true on success, false on error - */ -struct song * -song_load(FILE *fp, struct directory *parent, const char *uri, - GString *buffer, GError **error_r); - -#endif diff --git a/src/song_sticker.c b/src/song_sticker.c deleted file mode 100644 index 78025906e..000000000 --- a/src/song_sticker.c +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "song_sticker.h" -#include "song.h" -#include "directory.h" -#include "sticker.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -char * -sticker_song_get_value(const struct song *song, const char *name) -{ - char *uri, *value; - - assert(song != NULL); - assert(song_in_database(song)); - - uri = song_get_uri(song); - value = sticker_load_value("song", uri, name); - g_free(uri); - - return value; -} - -bool -sticker_song_set_value(const struct song *song, - const char *name, const char *value) -{ - char *uri; - bool ret; - - assert(song != NULL); - assert(song_in_database(song)); - - uri = song_get_uri(song); - ret = sticker_store_value("song", uri, name, value); - g_free(uri); - - return ret; -} - -bool -sticker_song_delete(const struct song *song) -{ - char *uri; - bool ret; - - assert(song != NULL); - assert(song_in_database(song)); - - uri = song_get_uri(song); - ret = sticker_delete("song", uri); - g_free(uri); - - return ret; -} - -bool -sticker_song_delete_value(const struct song *song, const char *name) -{ - char *uri; - bool success; - - assert(song != NULL); - assert(song_in_database(song)); - - uri = song_get_uri(song); - success = sticker_delete_value("song", uri, name); - g_free(uri); - - return success; -} - -struct sticker * -sticker_song_get(const struct song *song) -{ - char *uri; - struct sticker *sticker; - - assert(song != NULL); - assert(song_in_database(song)); - - uri = song_get_uri(song); - sticker = sticker_load("song", uri); - g_free(uri); - - return sticker; -} - -struct sticker_song_find_data { - struct directory *directory; - const char *base_uri; - size_t base_uri_length; - - void (*func)(struct song *song, const char *value, - gpointer user_data); - gpointer user_data; -}; - -static void -sticker_song_find_cb(const char *uri, const char *value, gpointer user_data) -{ - struct sticker_song_find_data *data = user_data; - struct song *song; - - if (memcmp(uri, data->base_uri, data->base_uri_length) != 0) - /* should not happen, ignore silently */ - return; - - song = directory_lookup_song(data->directory, - uri + data->base_uri_length); - if (song != NULL) - data->func(song, value, data->user_data); -} - -bool -sticker_song_find(struct directory *directory, const char *name, - void (*func)(struct song *song, const char *value, - gpointer user_data), - gpointer user_data) -{ - struct sticker_song_find_data data = { - .directory = directory, - .func = func, - .user_data = user_data, - }; - char *allocated; - bool success; - - data.base_uri = directory_get_path(directory); - if (*data.base_uri != 0) - /* append slash to base_uri */ - data.base_uri = allocated = - g_strconcat(data.base_uri, "/", NULL); - else - /* searching in root directory - no trailing slash */ - allocated = NULL; - - data.base_uri_length = strlen(data.base_uri); - - success = sticker_find("song", data.base_uri, name, - sticker_song_find_cb, &data); - g_free(allocated); - - return success; -} diff --git a/src/song_sticker.h b/src/song_sticker.h deleted file mode 100644 index 20ae68ce9..000000000 --- a/src/song_sticker.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef SONG_STICKER_H -#define SONG_STICKER_H - -#include <stdbool.h> -#include <glib.h> - -struct song; -struct directory; -struct sticker; - -/** - * Returns one value from a song's sticker record. The caller must - * free the return value with g_free(). - */ -char * -sticker_song_get_value(const struct song *song, const char *name); - -/** - * Sets a sticker value in the specified song. Overwrites existing - * values. - */ -bool -sticker_song_set_value(const struct song *song, - const char *name, const char *value); - -/** - * Deletes a sticker from the database. All values are deleted. - */ -bool -sticker_song_delete(const struct song *song); - -/** - * Deletes a sticker value. Does nothing if the sticker did not - * exist. - */ -bool -sticker_song_delete_value(const struct song *song, const char *name); - -/** - * Loads the sticker for the specified song. - * - * @param song the song object - * @return a sticker object, or NULL on error or if there is no sticker - */ -struct sticker * -sticker_song_get(const struct song *song); - -/** - * Finds stickers with the specified name below the specified - * directory. - * - * Caller must lock the #db_mutex. - * - * @param directory the base directory to search in - * @param name the name of the sticker - * @return true on success (even if no sticker was found), false on - * failure - */ -bool -sticker_song_find(struct directory *directory, const char *name, - void (*func)(struct song *song, const char *value, - gpointer user_data), - gpointer user_data); - -#endif diff --git a/src/song_update.c b/src/song_update.c deleted file mode 100644 index 37f502a20..000000000 --- a/src/song_update.c +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "song.h" -#include "uri.h" -#include "directory.h" -#include "mapper.h" -#include "decoder_list.h" -#include "decoder_plugin.h" -#include "tag_ape.h" -#include "tag_id3.h" -#include "tag.h" -#include "tag_handler.h" -#include "input_stream.h" - -#include <glib.h> - -#include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <stdio.h> - -struct song * -song_file_load(const char *path, struct directory *parent) -{ - struct song *song; - bool ret; - - assert((parent == NULL) == g_path_is_absolute(path)); - assert(!uri_has_scheme(path)); - assert(strchr(path, '\n') == NULL); - - song = song_file_new(path, parent); - - //in archive ? - if (parent != NULL && parent->device == DEVICE_INARCHIVE) { - ret = song_file_update_inarchive(song); - } else { - ret = song_file_update(song); - } - if (!ret) { - song_free(song); - return NULL; - } - - return song; -} - -/** - * Attempts to load APE or ID3 tags from the specified file. - */ -static bool -tag_scan_fallback(const char *path, - const struct tag_handler *handler, void *handler_ctx) -{ - return tag_ape_scan2(path, handler, handler_ctx) || - tag_id3_scan(path, handler, handler_ctx); -} - -bool -song_file_update(struct song *song) -{ - const char *suffix; - char *path_fs; - const struct decoder_plugin *plugin; - struct stat st; - struct input_stream *is = NULL; - - assert(song_is_file(song)); - - /* check if there's a suffix and a plugin */ - - suffix = uri_get_suffix(song->uri); - if (suffix == NULL) - return false; - - plugin = decoder_plugin_from_suffix(suffix, NULL); - if (plugin == NULL) - return false; - - path_fs = map_song_fs(song); - if (path_fs == NULL) - return false; - - if (song->tag != NULL) { - tag_free(song->tag); - song->tag = NULL; - } - - if (stat(path_fs, &st) < 0 || !S_ISREG(st.st_mode)) { - g_free(path_fs); - return false; - } - - song->mtime = st.st_mtime; - - GMutex *mutex = NULL; - GCond *cond; -#if !GCC_CHECK_VERSION(4, 2) - /* work around "may be used uninitialized in this function" - false positive */ - cond = NULL; -#endif - - do { - /* load file tag */ - song->tag = tag_new(); - if (decoder_plugin_scan_file(plugin, path_fs, - &full_tag_handler, song->tag)) - break; - - tag_free(song->tag); - song->tag = NULL; - - /* fall back to stream tag */ - if (plugin->scan_stream != NULL) { - /* open the input_stream (if not already - open) */ - if (is == NULL) { - mutex = g_mutex_new(); - cond = g_cond_new(); - is = input_stream_open(path_fs, mutex, cond, - NULL); - } - - /* now try the stream_tag() method */ - if (is != NULL) { - song->tag = tag_new(); - if (decoder_plugin_scan_stream(plugin, is, - &full_tag_handler, - song->tag)) - break; - - tag_free(song->tag); - song->tag = NULL; - - input_stream_lock_seek(is, 0, SEEK_SET, NULL); - } - } - - plugin = decoder_plugin_from_suffix(suffix, plugin); - } while (plugin != NULL); - - if (is != NULL) - input_stream_close(is); - - if (mutex != NULL) { - g_cond_free(cond); - g_mutex_free(mutex); - } - - if (song->tag != NULL && tag_is_empty(song->tag)) - tag_scan_fallback(path_fs, &full_tag_handler, song->tag); - - g_free(path_fs); - return song->tag != NULL; -} - -bool -song_file_update_inarchive(struct song *song) -{ - const char *suffix; - const struct decoder_plugin *plugin; - - assert(song_is_file(song)); - - /* check if there's a suffix and a plugin */ - - suffix = uri_get_suffix(song->uri); - if (suffix == NULL) - return false; - - plugin = decoder_plugin_from_suffix(suffix, false); - if (plugin == NULL) - return false; - - if (song->tag != NULL) - tag_free(song->tag); - - //accept every file that has music suffix - //because we don't support tag reading through - //input streams - song->tag = tag_new(); - - return true; -} diff --git a/src/state_file.c b/src/state_file.c deleted file mode 100644 index de0e70538..000000000 --- a/src/state_file.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "state_file.h" -#include "output_state.h" -#include "playlist.h" -#include "playlist_state.h" -#include "volume.h" -#include "text_file.h" - -#include <glib.h> -#include <assert.h> -#include <string.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "state_file" - -static char *state_file_path; - -/** the GLib source id for the save timer */ -static guint save_state_source_id; - -/** - * These version numbers determine whether we need to save the state - * file. If nothing has changed, we won't let the hard drive spin up. - */ -static unsigned prev_volume_version, prev_output_version, - prev_playlist_version; - -static void -state_file_write(struct player_control *pc) -{ - FILE *fp; - - assert(state_file_path != NULL); - - g_debug("Saving state file %s", state_file_path); - - fp = fopen(state_file_path, "w"); - if (G_UNLIKELY(!fp)) { - g_warning("failed to create %s: %s", - state_file_path, g_strerror(errno)); - return; - } - - save_sw_volume_state(fp); - audio_output_state_save(fp); - playlist_state_save(fp, &g_playlist, pc); - - fclose(fp); - - prev_volume_version = sw_volume_state_get_hash(); - prev_output_version = audio_output_state_get_version(); - prev_playlist_version = playlist_state_get_hash(&g_playlist, pc); -} - -static void -state_file_read(struct player_control *pc) -{ - FILE *fp; - bool success; - - assert(state_file_path != NULL); - - g_debug("Loading state file %s", state_file_path); - - fp = fopen(state_file_path, "r"); - if (G_UNLIKELY(!fp)) { - g_warning("failed to open %s: %s", - state_file_path, g_strerror(errno)); - return; - } - - GString *buffer = g_string_sized_new(1024); - const char *line; - while ((line = read_text_line(fp, buffer)) != NULL) { - success = read_sw_volume_state(line) || - audio_output_state_read(line) || - playlist_state_restore(line, fp, buffer, - &g_playlist, pc); - if (!success) - g_warning("Unrecognized line in state file: %s", line); - } - - fclose(fp); - - prev_volume_version = sw_volume_state_get_hash(); - prev_output_version = audio_output_state_get_version(); - prev_playlist_version = playlist_state_get_hash(&g_playlist, pc); - - - g_string_free(buffer, true); -} - -/** - * This function is called every 5 minutes by the GLib main loop, and - * saves the state file. - */ -static gboolean -timer_save_state_file(gpointer data) -{ - struct player_control *pc = data; - - if (prev_volume_version == sw_volume_state_get_hash() && - prev_output_version == audio_output_state_get_version() && - prev_playlist_version == playlist_state_get_hash(&g_playlist, pc)) - /* nothing has changed - don't save the state file, - don't spin up the hard disk */ - return true; - - state_file_write(pc); - return true; -} - -void -state_file_init(const char *path, struct player_control *pc) -{ - assert(state_file_path == NULL); - - if (path == NULL) - return; - - state_file_path = g_strdup(path); - state_file_read(pc); - - save_state_source_id = g_timeout_add_seconds(5 * 60, - timer_save_state_file, - pc); -} - -void -state_file_finish(struct player_control *pc) -{ - if (state_file_path == NULL) - /* no state file configured, no cleanup required */ - return; - - if (save_state_source_id != 0) - g_source_remove(save_state_source_id); - - state_file_write(pc); - - g_free(state_file_path); -} diff --git a/src/state_file.h b/src/state_file.h deleted file mode 100644 index 4c4f881cc..000000000 --- a/src/state_file.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_STATE_FILE_H -#define MPD_STATE_FILE_H - -struct player_control; - -void -state_file_init(const char *path, struct player_control *pc); - -void -state_file_finish(struct player_control *pc); - -void write_state_file(void); - -#endif /* STATE_FILE_H */ diff --git a/src/stats.c b/src/stats.c deleted file mode 100644 index fe6a064a6..000000000 --- a/src/stats.c +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "stats.h" -#include "database.h" -#include "db_visitor.h" -#include "tag.h" -#include "song.h" -#include "client.h" -#include "player_control.h" -#include "strset.h" -#include "client_internal.h" - -struct stats stats; - -void stats_global_init(void) -{ - stats.timer = g_timer_new(); -} - -void stats_global_finish(void) -{ - g_timer_destroy(stats.timer); -} - -struct visit_data { - struct strset *artists; - struct strset *albums; -}; - -static void -visit_tag(struct visit_data *data, const struct tag *tag) -{ - if (tag->time > 0) - stats.song_duration += tag->time; - - for (unsigned i = 0; i < tag->num_items; ++i) { - const struct tag_item *item = tag->items[i]; - - switch (item->type) { - case TAG_ARTIST: - strset_add(data->artists, item->value); - break; - - case TAG_ALBUM: - strset_add(data->albums, item->value); - break; - - default: - break; - } - } -} - -static bool -collect_stats_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct visit_data *data = _data; - - ++stats.song_count; - - if (song->tag != NULL) - visit_tag(data, song->tag); - - return true; -} - -static const struct db_visitor collect_stats_visitor = { - .song = collect_stats_song, -}; - -void stats_update(void) -{ - struct visit_data data; - - stats.song_count = 0; - stats.song_duration = 0; - stats.artist_count = 0; - - data.artists = strset_new(); - data.albums = strset_new(); - - db_walk("", &collect_stats_visitor, &data, NULL); - - stats.artist_count = strset_size(data.artists); - stats.album_count = strset_size(data.albums); - - strset_free(data.artists); - strset_free(data.albums); -} - -int stats_print(struct client *client) -{ - client_printf(client, - "artists: %u\n" - "albums: %u\n" - "songs: %i\n" - "uptime: %li\n" - "playtime: %li\n" - "db_playtime: %li\n" - "db_update: %li\n", - stats.artist_count, - stats.album_count, - stats.song_count, - (long)g_timer_elapsed(stats.timer, NULL), - (long)(pc_get_total_play_time(client->player_control) + 0.5), - stats.song_duration, - (long)db_get_mtime()); - return 0; -} diff --git a/src/stats.h b/src/stats.h index a686477de..eb723bcf3 100644 --- a/src/stats.h +++ b/src/stats.h @@ -22,7 +22,7 @@ #include <glib.h> -struct client; +class Client; struct stats { GTimer *timer; @@ -49,6 +49,7 @@ void stats_global_finish(void); void stats_update(void); -int stats_print(struct client *client); +void +stats_print(Client *client); #endif diff --git a/src/sticker.c b/src/sticker.c deleted file mode 100644 index 346a827a5..000000000 --- a/src/sticker.c +++ /dev/null @@ -1,660 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "sticker.h" -#include "idle.h" - -#include <glib.h> -#include <sqlite3.h> -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "sticker" - -#if SQLITE_VERSION_NUMBER < 3003009 -#define sqlite3_prepare_v2 sqlite3_prepare -#endif - -struct sticker { - GHashTable *table; -}; - -enum sticker_sql { - STICKER_SQL_GET, - STICKER_SQL_LIST, - STICKER_SQL_UPDATE, - STICKER_SQL_INSERT, - STICKER_SQL_DELETE, - STICKER_SQL_DELETE_VALUE, - STICKER_SQL_FIND, -}; - -static const char *const sticker_sql[] = { - [STICKER_SQL_GET] = - "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_LIST] = - "SELECT name,value FROM sticker WHERE type=? AND uri=?", - [STICKER_SQL_UPDATE] = - "UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_INSERT] = - "INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)", - [STICKER_SQL_DELETE] = - "DELETE FROM sticker WHERE type=? AND uri=?", - [STICKER_SQL_DELETE_VALUE] = - "DELETE FROM sticker WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_FIND] = - "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?", -}; - -static const char sticker_sql_create[] = - "CREATE TABLE IF NOT EXISTS sticker(" - " type VARCHAR NOT NULL, " - " uri VARCHAR NOT NULL, " - " name VARCHAR NOT NULL, " - " value VARCHAR NOT NULL" - ");" - "CREATE UNIQUE INDEX IF NOT EXISTS" - " sticker_value ON sticker(type, uri, name);" - ""; - -static sqlite3 *sticker_db; -static sqlite3_stmt *sticker_stmt[G_N_ELEMENTS(sticker_sql)]; - -static GQuark -sticker_quark(void) -{ - return g_quark_from_static_string("sticker"); -} - -static sqlite3_stmt * -sticker_prepare(const char *sql, GError **error_r) -{ - int ret; - sqlite3_stmt *stmt; - - ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, NULL); - if (ret != SQLITE_OK) { - g_set_error(error_r, sticker_quark(), ret, - "sqlite3_prepare_v2() failed: %s", - sqlite3_errmsg(sticker_db)); - return NULL; - } - - return stmt; -} - -bool -sticker_global_init(const char *path, GError **error_r) -{ - int ret; - - if (path == NULL) - /* not configured */ - return true; - - /* open/create the sqlite database */ - - ret = sqlite3_open(path, &sticker_db); - if (ret != SQLITE_OK) { - g_set_error(error_r, sticker_quark(), ret, - "Failed to open sqlite database '%s': %s", - path, sqlite3_errmsg(sticker_db)); - return false; - } - - /* create the table and index */ - - ret = sqlite3_exec(sticker_db, sticker_sql_create, NULL, NULL, NULL); - if (ret != SQLITE_OK) { - g_set_error(error_r, sticker_quark(), ret, - "Failed to create sticker table: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - /* prepare the statements we're going to use */ - - for (unsigned i = 0; i < G_N_ELEMENTS(sticker_sql); ++i) { - assert(sticker_sql[i] != NULL); - - sticker_stmt[i] = sticker_prepare(sticker_sql[i], error_r); - if (sticker_stmt[i] == NULL) - return false; - } - - return true; -} - -void -sticker_global_finish(void) -{ - if (sticker_db == NULL) - /* not configured */ - return; - - for (unsigned i = 0; i < G_N_ELEMENTS(sticker_stmt); ++i) { - assert(sticker_stmt[i] != NULL); - - sqlite3_finalize(sticker_stmt[i]); - } - - sqlite3_close(sticker_db); -} - -bool -sticker_enabled(void) -{ - return sticker_db != NULL; -} - -char * -sticker_load_value(const char *type, const char *uri, const char *name) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET]; - int ret; - char *value; - - assert(sticker_enabled()); - assert(type != NULL); - assert(uri != NULL); - assert(name != NULL); - - if (*name == 0) - return NULL; - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return NULL; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return NULL; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return NULL; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret == SQLITE_ROW) { - /* record found */ - value = g_strdup((const char*)sqlite3_column_text(stmt, 0)); - } else if (ret == SQLITE_DONE) { - /* no record found */ - value = NULL; - } else { - /* error */ - g_warning("sqlite3_step() failed: %s", - sqlite3_errmsg(sticker_db)); - return NULL; - } - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - return value; -} - -static bool -sticker_list_values(GHashTable *hash, const char *type, const char *uri) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_LIST]; - int ret; - char *name, *value; - - assert(hash != NULL); - assert(type != NULL); - assert(uri != NULL); - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - do { - ret = sqlite3_step(stmt); - switch (ret) { - case SQLITE_ROW: - name = g_strdup((const char*)sqlite3_column_text(stmt, 0)); - value = g_strdup((const char*)sqlite3_column_text(stmt, 1)); - g_hash_table_insert(hash, name, value); - break; - case SQLITE_DONE: - break; - case SQLITE_BUSY: - /* no op */ - break; - default: - g_warning("sqlite3_step() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - } while (ret != SQLITE_DONE); - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - return true; -} - -static bool -sticker_update_value(const char *type, const char *uri, - const char *name, const char *value) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_UPDATE]; - int ret; - - assert(type != NULL); - assert(uri != NULL); - assert(name != NULL); - assert(*name != 0); - assert(value != NULL); - - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, value, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, type, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, uri, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 4, name, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - g_warning("sqlite3_step() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_changes(sticker_db); - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - idle_add(IDLE_STICKER); - return ret > 0; -} - -static bool -sticker_insert_value(const char *type, const char *uri, - const char *name, const char *value) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_INSERT]; - int ret; - - assert(type != NULL); - assert(uri != NULL); - assert(name != NULL); - assert(*name != 0); - assert(value != NULL); - - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 4, value, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - g_warning("sqlite3_step() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - - idle_add(IDLE_STICKER); - return true; -} - -bool -sticker_store_value(const char *type, const char *uri, - const char *name, const char *value) -{ - assert(sticker_enabled()); - assert(type != NULL); - assert(uri != NULL); - assert(name != NULL); - assert(value != NULL); - - if (*name == 0) - return false; - - return sticker_update_value(type, uri, name, value) || - sticker_insert_value(type, uri, name, value); -} - -bool -sticker_delete(const char *type, const char *uri) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE]; - int ret; - - assert(sticker_enabled()); - assert(type != NULL); - assert(uri != NULL); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - g_warning("sqlite3_step() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - idle_add(IDLE_STICKER); - return true; -} - -bool -sticker_delete_value(const char *type, const char *uri, const char *name) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE_VALUE]; - int ret; - - assert(sticker_enabled()); - assert(type != NULL); - assert(uri != NULL); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - g_warning("sqlite3_step() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_changes(sticker_db); - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - idle_add(IDLE_STICKER); - return ret > 0; -} - -static struct sticker * -sticker_new(void) -{ - struct sticker *sticker = g_new(struct sticker, 1); - - sticker->table = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_free); - return sticker; -} - -void -sticker_free(struct sticker *sticker) -{ - assert(sticker != NULL); - assert(sticker->table != NULL); - - g_hash_table_destroy(sticker->table); - g_free(sticker); -} - -const char * -sticker_get_value(const struct sticker *sticker, const char *name) -{ - return g_hash_table_lookup(sticker->table, name); -} - -struct sticker_foreach_data { - void (*func)(const char *name, const char *value, - gpointer user_data); - gpointer user_data; -}; - -static void -sticker_foreach_func(gpointer key, gpointer value, gpointer user_data) -{ - struct sticker_foreach_data *data = user_data; - - data->func(key, value, data->user_data); -} - -void -sticker_foreach(const struct sticker *sticker, - void (*func)(const char *name, const char *value, - gpointer user_data), - gpointer user_data) -{ - struct sticker_foreach_data data = { - .func = func, - .user_data = user_data, - }; - - g_hash_table_foreach(sticker->table, sticker_foreach_func, &data); -} - -struct sticker * -sticker_load(const char *type, const char *uri) -{ - struct sticker *sticker = sticker_new(); - bool success; - - success = sticker_list_values(sticker->table, type, uri); - if (!success) { - sticker_free(sticker); - return NULL; - } - - if (g_hash_table_size(sticker->table) == 0) { - /* don't return empty sticker objects */ - sticker_free(sticker); - return NULL; - } - - return sticker; -} - -bool -sticker_find(const char *type, const char *base_uri, const char *name, - void (*func)(const char *uri, const char *value, - gpointer user_data), - gpointer user_data) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND]; - int ret; - - assert(type != NULL); - assert(name != NULL); - assert(func != NULL); - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - if (base_uri == NULL) - base_uri = ""; - - ret = sqlite3_bind_text(stmt, 2, base_uri, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - do { - ret = sqlite3_step(stmt); - switch (ret) { - case SQLITE_ROW: - func((const char*)sqlite3_column_text(stmt, 0), - (const char*)sqlite3_column_text(stmt, 1), - user_data); - break; - case SQLITE_DONE: - break; - case SQLITE_BUSY: - /* no op */ - break; - default: - g_warning("sqlite3_step() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - } while (ret != SQLITE_DONE); - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - return true; -} diff --git a/src/sticker.h b/src/sticker.h deleted file mode 100644 index 5545206a5..000000000 --- a/src/sticker.h +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This is the sticker database library. It is the backend of all the - * sticker code in MPD. - * - * "Stickers" are pieces of information attached to existing MPD - * objects (e.g. song files, directories, albums). Clients can create - * arbitrary name/value pairs. MPD itself does not assume any special - * meaning in them. - * - * The goal is to allow clients to share additional (possibly dynamic) - * information about songs, which is neither stored on the client (not - * available to other clients), nor stored in the song files (MPD has - * no write access). - * - * Client developers should create a standard for common sticker - * names, to ensure interoperability. - * - * Examples: song ratings; statistics; deferred tag writes; lyrics; - * ... - * - */ - -#ifndef STICKER_H -#define STICKER_H - -#include <glib.h> - -#include <stdbool.h> - -struct sticker; - -/** - * Opens the sticker database (if path is not NULL). - * - * @param error_r location to store the error occurring, or NULL to - * ignore errors - * @return true on success, false on error - */ -bool -sticker_global_init(const char *path, GError **error_r); - -/** - * Close the sticker database. - */ -void -sticker_global_finish(void); - -/** - * Returns true if the sticker database is configured and available. - */ -bool -sticker_enabled(void); - -/** - * Returns one value from an object's sticker record. The caller must - * free the return value with g_free(). - */ -char * -sticker_load_value(const char *type, const char *uri, const char *name); - -/** - * Sets a sticker value in the specified object. Overwrites existing - * values. - */ -bool -sticker_store_value(const char *type, const char *uri, - const char *name, const char *value); - -/** - * Deletes a sticker from the database. All sticker values of the - * specified object are deleted. - */ -bool -sticker_delete(const char *type, const char *uri); - -/** - * Deletes a sticker value. Fails if no sticker with this name - * exists. - */ -bool -sticker_delete_value(const char *type, const char *uri, const char *name); - -/** - * Frees resources held by the sticker object. - * - * @param sticker the sticker object to be freed - */ -void -sticker_free(struct sticker *sticker); - -/** - * Determines a single value in a sticker. - * - * @param sticker the sticker object - * @param name the name of the sticker - * @return the sticker value, or NULL if none was found - */ -const char * -sticker_get_value(const struct sticker *sticker, const char *name); - -/** - * Iterates over all sticker items in a sticker. - * - * @param sticker the sticker object - * @param func a callback function - * @param user_data an opaque pointer for the callback function - */ -void -sticker_foreach(const struct sticker *sticker, - void (*func)(const char *name, const char *value, - gpointer user_data), - gpointer user_data); - -/** - * Loads the sticker for the specified resource. - * - * @param type the resource type, e.g. "song" - * @param uri the URI of the resource, e.g. the song path - * @return a sticker object, or NULL on error or if there is no sticker - */ -struct sticker * -sticker_load(const char *type, const char *uri); - -/** - * Finds stickers with the specified name below the specified URI. - * - * @param type the resource type, e.g. "song" - * @param base_uri the URI prefix of the resources, or NULL if all - * resources should be searched - * @param name the name of the sticker - * @return true on success (even if no sticker was found), false on - * failure - */ -bool -sticker_find(const char *type, const char *base_uri, const char *name, - void (*func)(const char *uri, const char *value, - gpointer user_data), - gpointer user_data); - -#endif diff --git a/src/sticker_print.c b/src/sticker_print.c deleted file mode 100644 index 65e79513c..000000000 --- a/src/sticker_print.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "sticker_print.h" -#include "sticker.h" -#include "client.h" - -void -sticker_print_value(struct client *client, - const char *name, const char *value) -{ - client_printf(client, "sticker: %s=%s\n", name, value); -} - -static void -print_sticker_cb(const char *name, const char *value, gpointer data) -{ - struct client *client = data; - - sticker_print_value(client, name, value); -} - -void -sticker_print(struct client *client, const struct sticker *sticker) -{ - sticker_foreach(sticker, print_sticker_cb, client); -} diff --git a/src/sticker_print.h b/src/sticker_print.h deleted file mode 100644 index 7398c8083..000000000 --- a/src/sticker_print.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_STICKER_PRINT_H -#define MPD_STICKER_PRINT_H - -struct sticker; -struct client; - -/** - * Sends one sticker value to the client. - */ -void -sticker_print_value(struct client *client, - const char *name, const char *value); - -/** - * Sends all sticker values to the client. - */ -void -sticker_print(struct client *client, const struct sticker *sticker); - -#endif diff --git a/src/stored_playlist.c b/src/stored_playlist.c deleted file mode 100644 index 39ba2bac1..000000000 --- a/src/stored_playlist.c +++ /dev/null @@ -1,552 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "stored_playlist.h" -#include "playlist_save.h" -#include "text_file.h" -#include "song.h" -#include "mapper.h" -#include "path.h" -#include "uri.h" -#include "database.h" -#include "idle.h" -#include "conf.h" -#include "glib_compat.h" - -#include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <dirent.h> -#include <string.h> -#include <errno.h> - -static const char PLAYLIST_COMMENT = '#'; - -static unsigned playlist_max_length; -bool playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS; - -void -spl_global_init(void) -{ - playlist_max_length = config_get_positive(CONF_MAX_PLAYLIST_LENGTH, - DEFAULT_PLAYLIST_MAX_LENGTH); - - playlist_saveAbsolutePaths = - config_get_bool(CONF_SAVE_ABSOLUTE_PATHS, - DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS); -} - -bool -spl_valid_name(const char *name_utf8) -{ - /* - * Not supporting '/' was done out of laziness, and we should - * really strive to support it in the future. - * - * Not supporting '\r' and '\n' is done out of protocol - * limitations (and arguably laziness), but bending over head - * over heels to modify the protocol (and compatibility with - * all clients) to support idiots who put '\r' and '\n' in - * filenames isn't going to happen, either. - */ - - return strchr(name_utf8, '/') == NULL && - strchr(name_utf8, '\n') == NULL && - strchr(name_utf8, '\r') == NULL; -} - -static const char * -spl_map(GError **error_r) -{ - const char *path_fs = map_spl_path(); - if (path_fs == NULL) - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_DISABLED, - "Stored playlists are disabled"); - - return path_fs; -} - -static bool -spl_check_name(const char *name_utf8, GError **error_r) -{ - if (!spl_valid_name(name_utf8)) { - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_BAD_NAME, - "Bad playlist name"); - return false; - } - - return true; -} - -static char * -spl_map_to_fs(const char *name_utf8, GError **error_r) -{ - if (spl_map(error_r) == NULL || - !spl_check_name(name_utf8, error_r)) - return NULL; - - char *path_fs = map_spl_utf8_to_fs(name_utf8); - if (path_fs == NULL) - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_BAD_NAME, - "Bad playlist name"); - - return path_fs; -} - -/** - * Create a GError for the current errno. - */ -static void -playlist_errno(GError **error_r) -{ - switch (errno) { - case ENOENT: - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_NO_SUCH_LIST, - "No such playlist"); - break; - - default: - g_set_error_literal(error_r, g_file_error_quark(), errno, - g_strerror(errno)); - break; - } -} - -static struct stored_playlist_info * -load_playlist_info(const char *parent_path_fs, const char *name_fs) -{ - size_t name_length = strlen(name_fs); - char *path_fs, *name, *name_utf8; - int ret; - struct stat st; - struct stored_playlist_info *playlist; - - if (name_length < sizeof(PLAYLIST_FILE_SUFFIX) || - memchr(name_fs, '\n', name_length) != NULL) - return NULL; - - if (!g_str_has_suffix(name_fs, PLAYLIST_FILE_SUFFIX)) - return NULL; - - path_fs = g_build_filename(parent_path_fs, name_fs, NULL); - ret = stat(path_fs, &st); - g_free(path_fs); - if (ret < 0 || !S_ISREG(st.st_mode)) - return NULL; - - name = g_strndup(name_fs, - name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX)); - name_utf8 = fs_charset_to_utf8(name); - g_free(name); - if (name_utf8 == NULL) - return NULL; - - playlist = g_new(struct stored_playlist_info, 1); - playlist->name = name_utf8; - playlist->mtime = st.st_mtime; - return playlist; -} - -GPtrArray * -spl_list(GError **error_r) -{ - const char *parent_path_fs = spl_map(error_r); - DIR *dir; - struct dirent *ent; - GPtrArray *list; - struct stored_playlist_info *playlist; - - if (parent_path_fs == NULL) - return NULL; - - dir = opendir(parent_path_fs); - if (dir == NULL) { - g_set_error_literal(error_r, g_file_error_quark(), errno, - g_strerror(errno)); - return NULL; - } - - list = g_ptr_array_new(); - - while ((ent = readdir(dir)) != NULL) { - playlist = load_playlist_info(parent_path_fs, ent->d_name); - if (playlist != NULL) - g_ptr_array_add(list, playlist); - } - - closedir(dir); - return list; -} - -void -spl_list_free(GPtrArray *list) -{ - for (unsigned i = 0; i < list->len; ++i) { - struct stored_playlist_info *playlist = - g_ptr_array_index(list, i); - g_free(playlist->name); - g_free(playlist); - } - - g_ptr_array_free(list, true); -} - -static bool -spl_save(GPtrArray *list, const char *utf8path, GError **error_r) -{ - FILE *file; - - assert(utf8path != NULL); - - if (spl_map(error_r) == NULL) - return false; - - char *path_fs = spl_map_to_fs(utf8path, error_r); - if (path_fs == NULL) - return false; - - file = fopen(path_fs, "w"); - g_free(path_fs); - if (file == NULL) { - playlist_errno(error_r); - return false; - } - - for (unsigned i = 0; i < list->len; ++i) { - const char *uri = g_ptr_array_index(list, i); - playlist_print_uri(file, uri); - } - - fclose(file); - return true; -} - -GPtrArray * -spl_load(const char *utf8path, GError **error_r) -{ - FILE *file; - GPtrArray *list; - char *path_fs; - - if (spl_map(error_r) == NULL) - return NULL; - - path_fs = spl_map_to_fs(utf8path, error_r); - if (path_fs == NULL) - return NULL; - - file = fopen(path_fs, "r"); - g_free(path_fs); - if (file == NULL) { - playlist_errno(error_r); - return NULL; - } - - list = g_ptr_array_new(); - - GString *buffer = g_string_sized_new(1024); - char *s; - while ((s = read_text_line(file, buffer)) != NULL) { - if (*s == 0 || *s == PLAYLIST_COMMENT) - continue; - - if (!uri_has_scheme(s)) { - char *path_utf8; - - path_utf8 = map_fs_to_utf8(s); - if (path_utf8 == NULL) - continue; - - s = path_utf8; - } else - s = g_strdup(s); - - g_ptr_array_add(list, s); - - if (list->len >= playlist_max_length) - break; - } - - fclose(file); - return list; -} - -void -spl_free(GPtrArray *list) -{ - for (unsigned i = 0; i < list->len; ++i) { - char *uri = g_ptr_array_index(list, i); - g_free(uri); - } - - g_ptr_array_free(list, true); -} - -static char * -spl_remove_index_internal(GPtrArray *list, unsigned idx) -{ - char *uri; - - assert(idx < list->len); - - uri = g_ptr_array_remove_index(list, idx); - assert(uri != NULL); - return uri; -} - -static void -spl_insert_index_internal(GPtrArray *list, unsigned idx, char *uri) -{ - assert(idx <= list->len); - - g_ptr_array_add(list, uri); - - memmove(list->pdata + idx + 1, list->pdata + idx, - (list->len - idx - 1) * sizeof(list->pdata[0])); - g_ptr_array_index(list, idx) = uri; -} - -bool -spl_move_index(const char *utf8path, unsigned src, unsigned dest, - GError **error_r) -{ - char *uri; - - if (src == dest) - /* this doesn't check whether the playlist exists, but - what the hell.. */ - return true; - - GPtrArray *list = spl_load(utf8path, error_r); - if (list == NULL) - return false; - - if (src >= list->len || dest >= list->len) { - spl_free(list); - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_BAD_RANGE, - "Bad range"); - return false; - } - - uri = spl_remove_index_internal(list, src); - spl_insert_index_internal(list, dest, uri); - - bool result = spl_save(list, utf8path, error_r); - - spl_free(list); - - idle_add(IDLE_STORED_PLAYLIST); - return result; -} - -bool -spl_clear(const char *utf8path, GError **error_r) -{ - FILE *file; - - if (spl_map(error_r) == NULL) - return false; - - char *path_fs = spl_map_to_fs(utf8path, error_r); - if (path_fs == NULL) - return false; - - file = fopen(path_fs, "w"); - g_free(path_fs); - if (file == NULL) { - playlist_errno(error_r); - return false; - } - - fclose(file); - - idle_add(IDLE_STORED_PLAYLIST); - return true; -} - -bool -spl_delete(const char *name_utf8, GError **error_r) -{ - char *path_fs; - int ret; - - path_fs = spl_map_to_fs(name_utf8, error_r); - if (path_fs == NULL) - return false; - - ret = unlink(path_fs); - g_free(path_fs); - if (ret < 0) { - playlist_errno(error_r); - return false; - } - - idle_add(IDLE_STORED_PLAYLIST); - return true; -} - -bool -spl_remove_index(const char *utf8path, unsigned pos, GError **error_r) -{ - char *uri; - - GPtrArray *list = spl_load(utf8path, error_r); - if (list == NULL) - return false; - - if (pos >= list->len) { - spl_free(list); - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_BAD_RANGE, - "Bad range"); - return false; - } - - uri = spl_remove_index_internal(list, pos); - g_free(uri); - bool result = spl_save(list, utf8path, error_r); - - spl_free(list); - - idle_add(IDLE_STORED_PLAYLIST); - return result; -} - -bool -spl_append_song(const char *utf8path, struct song *song, GError **error_r) -{ - FILE *file; - struct stat st; - - if (spl_map(error_r) == NULL) - return false; - - char *path_fs = spl_map_to_fs(utf8path, error_r); - if (path_fs == NULL) - return false; - - file = fopen(path_fs, "a"); - g_free(path_fs); - if (file == NULL) { - playlist_errno(error_r); - return false; - } - - if (fstat(fileno(file), &st) < 0) { - playlist_errno(error_r); - fclose(file); - return false; - } - - if (st.st_size / (MPD_PATH_MAX + 1) >= (off_t)playlist_max_length) { - fclose(file); - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_TOO_LARGE, - "Stored playlist is too large"); - return false; - } - - playlist_print_song(file, song); - - fclose(file); - - idle_add(IDLE_STORED_PLAYLIST); - return true; -} - -bool -spl_append_uri(const char *url, const char *utf8file, GError **error_r) -{ - struct song *song; - - if (uri_has_scheme(url)) { - song = song_remote_new(url); - bool success = spl_append_song(utf8file, song, error_r); - song_free(song); - return success; - } else { - song = db_get_song(url); - if (song == NULL) { - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_NO_SUCH_SONG, - "No such song"); - return false; - } - - return spl_append_song(utf8file, song, error_r); - } -} - -static bool -spl_rename_internal(const char *from_path_fs, const char *to_path_fs, - GError **error_r) -{ - if (!g_file_test(from_path_fs, G_FILE_TEST_IS_REGULAR)) { - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_NO_SUCH_LIST, - "No such playlist"); - return false; - } - - if (g_file_test(to_path_fs, G_FILE_TEST_EXISTS)) { - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_LIST_EXISTS, - "Playlist exists already"); - return false; - } - - if (rename(from_path_fs, to_path_fs) < 0) { - playlist_errno(error_r); - return false; - } - - idle_add(IDLE_STORED_PLAYLIST); - return true; -} - -bool -spl_rename(const char *utf8from, const char *utf8to, GError **error_r) -{ - if (spl_map(error_r) == NULL) - return false; - - char *from_path_fs = spl_map_to_fs(utf8from, error_r); - if (from_path_fs == NULL) - return false; - - char *to_path_fs = spl_map_to_fs(utf8to, error_r); - if (to_path_fs == NULL) { - g_free(from_path_fs); - return false; - } - - bool success = spl_rename_internal(from_path_fs, to_path_fs, error_r); - - g_free(from_path_fs); - g_free(to_path_fs); - - return success; -} diff --git a/src/stored_playlist.h b/src/stored_playlist.h deleted file mode 100644 index cfe49633c..000000000 --- a/src/stored_playlist.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_STORED_PLAYLIST_H -#define MPD_STORED_PLAYLIST_H - -#include <glib.h> -#include <stdbool.h> -#include <time.h> - -struct song; - -struct stored_playlist_info { - char *name; - - time_t mtime; -}; - -extern bool playlist_saveAbsolutePaths; - -/** - * Perform some global initialization, e.g. load configuration values. - */ -void -spl_global_init(void); - -/** - * Determines whether the specified string is a valid name for a - * stored playlist. - */ -bool -spl_valid_name(const char *name_utf8); - -/** - * Returns a list of stored_playlist_info struct pointers. Returns - * NULL if an error occurred. - */ -GPtrArray * -spl_list(GError **error_r); - -void -spl_list_free(GPtrArray *list); - -GPtrArray * -spl_load(const char *utf8path, GError **error_r); - -void -spl_free(GPtrArray *list); - -bool -spl_move_index(const char *utf8path, unsigned src, unsigned dest, - GError **error_r); - -bool -spl_clear(const char *utf8path, GError **error_r); - -bool -spl_delete(const char *name_utf8, GError **error_r); - -bool -spl_remove_index(const char *utf8path, unsigned pos, GError **error_r); - -bool -spl_append_song(const char *utf8path, struct song *song, GError **error_r); - -bool -spl_append_uri(const char *file, const char *utf8file, GError **error_r); - -bool -spl_rename(const char *utf8from, const char *utf8to, GError **error_r); - -#endif diff --git a/src/string_util.c b/src/string_util.c index 6e5429076..5d9feccf9 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 dc80a46ef..62de53873 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 5862e4075..000000000 --- a/src/strset.c +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "strset.h" - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -#define NUM_SLOTS 16384 - -struct strset_slot { - struct strset_slot *next; - const char *value; -}; - -struct strset { - unsigned size; - - struct strset_slot *current_slot; - unsigned next_slot; - - struct strset_slot slots[NUM_SLOTS]; -}; - -static unsigned calc_hash(const char *p) { - unsigned hash = 5381; - - assert(p != NULL); - - while (*p != 0) - hash = (hash << 5) + hash + *p++; - - return hash; -} - -G_GNUC_MALLOC struct strset *strset_new(void) -{ - struct strset *set = g_new0(struct strset, 1); - return set; -} - -void strset_free(struct strset *set) -{ - unsigned i; - - for (i = 0; i < NUM_SLOTS; ++i) { - struct strset_slot *slot = set->slots[i].next, *next; - - while (slot != NULL) { - next = slot->next; - g_free(slot); - slot = next; - } - } - - g_free(set); -} - -void strset_add(struct strset *set, const char *value) -{ - struct strset_slot *base_slot - = &set->slots[calc_hash(value) % NUM_SLOTS]; - struct strset_slot *slot = base_slot; - - if (base_slot->value == NULL) { - /* empty slot - put into base_slot */ - assert(base_slot->next == NULL); - - base_slot->value = value; - ++set->size; - return; - } - - for (slot = base_slot; slot != NULL; slot = slot->next) - if (strcmp(slot->value, value) == 0) - /* found it - do nothing */ - return; - - /* insert it into the slot chain */ - slot = g_new(struct strset_slot, 1); - slot->next = base_slot->next; - slot->value = value; - base_slot->next = slot; - ++set->size; -} - -int strset_get(const struct strset *set, const char *value) -{ - const struct strset_slot *slot = &set->slots[calc_hash(value)]; - - if (slot->value == NULL) - return 0; - - for (slot = slot->next; slot != NULL; slot = slot->next) - if (strcmp(slot->value, value) == 0) - /* found it - do nothing */ - return 1; - - return 0; -} - -unsigned strset_size(const struct strset *set) -{ - return set->size; -} - -void strset_rewind(struct strset *set) -{ - set->current_slot = NULL; - set->next_slot = 0; -} - -const char *strset_next(struct strset *set) -{ - if (set->current_slot != NULL && set->current_slot->next != NULL) { - set->current_slot = set->current_slot->next; - return set->current_slot->value; - } - - while (set->next_slot < NUM_SLOTS && - set->slots[set->next_slot].value == NULL) - ++set->next_slot; - - if (set->next_slot >= NUM_SLOTS) - return NULL; - - set->current_slot = &set->slots[set->next_slot++]; - return set->current_slot->value; -} - diff --git a/src/strset.h b/src/strset.h deleted file mode 100644 index 5382e59b8..000000000 --- a/src/strset.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * "struct strset" is a hashed string set: you can add strings to this - * library, and it stores them as a set of unique strings. You can - * get the size of the set, and you can enumerate through all values. - * - * It is important to note that the strset does not copy the string - * values - it stores the exact pointers it was given in strset_add(). - */ - -#ifndef MPD_STRSET_H -#define MPD_STRSET_H - -#include <glib.h> - -struct strset; - -G_GNUC_MALLOC struct strset *strset_new(void); - -void strset_free(struct strset *set); - -void strset_add(struct strset *set, const char *value); - -int strset_get(const struct strset *set, const char *value); - -unsigned strset_size(const struct strset *set); - -void strset_rewind(struct strset *set); - -const char *strset_next(struct strset *set); - -#endif diff --git a/src/tag.c b/src/tag.c deleted file mode 100644 index c0faa7ab2..000000000 --- a/src/tag.c +++ /dev/null @@ -1,523 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag.h" -#include "tag_internal.h" -#include "tag_pool.h" -#include "conf.h" -#include "song.h" -#include "mpd_error.h" - -#include <glib.h> -#include <assert.h> -#include <stdio.h> -#include <stdlib.h> - -/** - * Maximum number of items managed in the bulk list; if it is - * exceeded, we switch back to "normal" reallocation. - */ -#define BULK_MAX 64 - -static struct { -#ifndef NDEBUG - bool busy; -#endif - struct tag_item *items[BULK_MAX]; -} bulk; - -const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = { - [TAG_ARTIST] = "Artist", - [TAG_ARTIST_SORT] = "ArtistSort", - [TAG_ALBUM] = "Album", - [TAG_ALBUM_ARTIST] = "AlbumArtist", - [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort", - [TAG_TITLE] = "Title", - [TAG_TRACK] = "Track", - [TAG_NAME] = "Name", - [TAG_GENRE] = "Genre", - [TAG_DATE] = "Date", - [TAG_COMPOSER] = "Composer", - [TAG_PERFORMER] = "Performer", - [TAG_COMMENT] = "Comment", - [TAG_DISC] = "Disc", - - /* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */ - [TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID", - [TAG_MUSICBRAINZ_ALBUMID] = "MUSICBRAINZ_ALBUMID", - [TAG_MUSICBRAINZ_ALBUMARTISTID] = "MUSICBRAINZ_ALBUMARTISTID", - [TAG_MUSICBRAINZ_TRACKID] = "MUSICBRAINZ_TRACKID", -}; - -bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; - -enum tag_type -tag_name_parse(const char *name) -{ - assert(name != NULL); - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - assert(tag_item_names[i] != NULL); - - if (strcmp(name, tag_item_names[i]) == 0) - return (enum tag_type)i; - } - - return TAG_NUM_OF_ITEM_TYPES; -} - -enum tag_type -tag_name_parse_i(const char *name) -{ - assert(name != NULL); - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - assert(tag_item_names[i] != NULL); - - if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0) - return (enum tag_type)i; - } - - return TAG_NUM_OF_ITEM_TYPES; -} - -static size_t items_size(const struct tag *tag) -{ - return tag->num_items * sizeof(struct tag_item *); -} - -void tag_lib_init(void) -{ - const char *value; - int quit = 0; - char *temp; - char *s; - char *c; - enum tag_type type; - - /* parse the "metadata_to_use" config parameter below */ - - /* ignore comments by default */ - ignore_tag_items[TAG_COMMENT] = true; - - value = config_get_string(CONF_METADATA_TO_USE, NULL); - if (value == NULL) - return; - - memset(ignore_tag_items, true, TAG_NUM_OF_ITEM_TYPES); - - if (0 == g_ascii_strcasecmp(value, "none")) - return; - - temp = c = s = g_strdup(value); - while (!quit) { - if (*s == ',' || *s == '\0') { - if (*s == '\0') - quit = 1; - *s = '\0'; - - c = g_strstrip(c); - if (*c == 0) - continue; - - type = tag_name_parse_i(c); - if (type == TAG_NUM_OF_ITEM_TYPES) - MPD_ERROR("error parsing metadata item \"%s\"", - c); - - ignore_tag_items[type] = false; - - s++; - c = s; - } - s++; - } - - g_free(temp); -} - -struct tag *tag_new(void) -{ - struct tag *ret = g_new(struct tag, 1); - ret->items = NULL; - ret->time = -1; - ret->has_playlist = false; - ret->num_items = 0; - return ret; -} - -static void tag_delete_item(struct tag *tag, unsigned idx) -{ - assert(idx < tag->num_items); - tag->num_items--; - - g_mutex_lock(tag_pool_lock); - tag_pool_put_item(tag->items[idx]); - g_mutex_unlock(tag_pool_lock); - - if (tag->num_items - idx > 0) { - memmove(tag->items + idx, tag->items + idx + 1, - (tag->num_items - idx) * sizeof(tag->items[0])); - } - - if (tag->num_items > 0) { - tag->items = g_realloc(tag->items, items_size(tag)); - } else { - g_free(tag->items); - tag->items = NULL; - } -} - -void tag_clear_items_by_type(struct tag *tag, enum tag_type type) -{ - for (unsigned i = 0; i < tag->num_items; i++) { - if (tag->items[i]->type == type) { - tag_delete_item(tag, i); - /* decrement since when just deleted this node */ - i--; - } - } -} - -void tag_free(struct tag *tag) -{ - int i; - - assert(tag != NULL); - - g_mutex_lock(tag_pool_lock); - for (i = tag->num_items; --i >= 0; ) - tag_pool_put_item(tag->items[i]); - g_mutex_unlock(tag_pool_lock); - - if (tag->items == bulk.items) { -#ifndef NDEBUG - assert(bulk.busy); - bulk.busy = false; -#endif - } else - g_free(tag->items); - - g_free(tag); -} - -struct tag *tag_dup(const struct tag *tag) -{ - struct tag *ret; - - if (!tag) - return NULL; - - ret = tag_new(); - ret->time = tag->time; - ret->has_playlist = tag->has_playlist; - ret->num_items = tag->num_items; - ret->items = ret->num_items > 0 ? g_malloc(items_size(tag)) : NULL; - - g_mutex_lock(tag_pool_lock); - for (unsigned i = 0; i < tag->num_items; i++) - ret->items[i] = tag_pool_dup_item(tag->items[i]); - g_mutex_unlock(tag_pool_lock); - - return ret; -} - -struct tag * -tag_merge(const struct tag *base, const struct tag *add) -{ - struct tag *ret; - unsigned n; - - assert(base != NULL); - assert(add != NULL); - - /* allocate new tag object */ - - ret = tag_new(); - ret->time = add->time > 0 ? add->time : base->time; - ret->num_items = base->num_items + add->num_items; - ret->items = ret->num_items > 0 ? g_malloc(items_size(ret)) : NULL; - - g_mutex_lock(tag_pool_lock); - - /* copy all items from "add" */ - - for (unsigned i = 0; i < add->num_items; ++i) - ret->items[i] = tag_pool_dup_item(add->items[i]); - - n = add->num_items; - - /* copy additional items from "base" */ - - for (unsigned i = 0; i < base->num_items; ++i) - if (!tag_has_type(add, base->items[i]->type)) - ret->items[n++] = tag_pool_dup_item(base->items[i]); - - g_mutex_unlock(tag_pool_lock); - - assert(n <= ret->num_items); - - if (n < ret->num_items) { - /* some tags were not copied - shrink ret->items */ - assert(n > 0); - - ret->num_items = n; - ret->items = g_realloc(ret->items, items_size(ret)); - } - - return ret; -} - -struct tag * -tag_merge_replace(struct tag *base, struct tag *add) -{ - if (add == NULL) - return base; - - if (base == NULL) - return add; - - struct tag *tag = tag_merge(base, add); - tag_free(base); - tag_free(add); - - return tag; -} - -const char * -tag_get_value(const struct tag *tag, enum tag_type type) -{ - assert(tag != NULL); - assert(type < TAG_NUM_OF_ITEM_TYPES); - - for (unsigned i = 0; i < tag->num_items; i++) - if (tag->items[i]->type == type) - return tag->items[i]->value; - - return NULL; -} - -bool tag_has_type(const struct tag *tag, enum tag_type type) -{ - return tag_get_value(tag, type) != NULL; -} - -bool tag_equal(const struct tag *tag1, const struct tag *tag2) -{ - if (tag1 == NULL && tag2 == NULL) - return true; - else if (!tag1 || !tag2) - return false; - - if (tag1->time != tag2->time) - return false; - - if (tag1->num_items != tag2->num_items) - return false; - - for (unsigned i = 0; i < tag1->num_items; i++) { - if (tag1->items[i]->type != tag2->items[i]->type) - return false; - if (strcmp(tag1->items[i]->value, tag2->items[i]->value)) { - return false; - } - } - - return true; -} - -/** - * Replace invalid sequences with the question mark. - */ -static char * -patch_utf8(const char *src, size_t length, const gchar *end) -{ - /* duplicate the string, and replace invalid bytes in that - buffer */ - char *dest = g_strdup(src); - - do { - dest[end - src] = '?'; - } while (!g_utf8_validate(end + 1, (src + length) - (end + 1), &end)); - - return dest; -} - -static char * -fix_utf8(const char *str, size_t length) -{ - const gchar *end; - char *temp; - gsize written; - - assert(str != NULL); - - /* check if the string is already valid UTF-8 */ - if (g_utf8_validate(str, length, &end)) - return NULL; - - /* no, it's not - try to import it from ISO-Latin-1 */ - temp = g_convert(str, length, "utf-8", "iso-8859-1", - NULL, &written, NULL); - if (temp != NULL) - /* success! */ - return temp; - - /* no, still broken - there's no medication, just patch - invalid sequences */ - return patch_utf8(str, length, end); -} - -void tag_begin_add(struct tag *tag) -{ - assert(!bulk.busy); - assert(tag != NULL); - assert(tag->items == NULL); - assert(tag->num_items == 0); - -#ifndef NDEBUG - bulk.busy = true; -#endif - tag->items = bulk.items; -} - -void tag_end_add(struct tag *tag) -{ - if (tag->items == bulk.items) { - assert(tag->num_items <= BULK_MAX); - - if (tag->num_items > 0) { - /* copy the tag items from the bulk list over - to a new list (which fits exactly) */ - tag->items = g_malloc(items_size(tag)); - memcpy(tag->items, bulk.items, items_size(tag)); - } else - tag->items = NULL; - } - -#ifndef NDEBUG - bulk.busy = false; -#endif -} - -static bool -char_is_non_printable(unsigned char ch) -{ - return ch < 0x20; -} - -static const char * -find_non_printable(const char *p, size_t length) -{ - for (size_t i = 0; i < length; ++i) - if (char_is_non_printable(p[i])) - return p + i; - - return NULL; -} - -/** - * Clears all non-printable characters, convert them to space. - * Returns NULL if nothing needs to be cleared. - */ -static char * -clear_non_printable(const char *p, size_t length) -{ - const char *first = find_non_printable(p, length); - char *dest; - - if (first == NULL) - return NULL; - - dest = g_strndup(p, length); - - for (size_t i = first - p; i < length; ++i) - if (char_is_non_printable(dest[i])) - dest[i] = ' '; - - return dest; -} - -static char * -fix_tag_value(const char *p, size_t length) -{ - char *utf8, *cleared; - - utf8 = fix_utf8(p, length); - if (utf8 != NULL) { - p = utf8; - length = strlen(p); - } - - cleared = clear_non_printable(p, length); - if (cleared == NULL) - cleared = utf8; - else - g_free(utf8); - - return cleared; -} - -static void -tag_add_item_internal(struct tag *tag, enum tag_type type, - const char *value, size_t len) -{ - unsigned int i = tag->num_items; - char *p; - - p = fix_tag_value(value, len); - if (p != NULL) { - value = p; - len = strlen(value); - } - - tag->num_items++; - - if (tag->items != bulk.items) - /* bulk mode disabled */ - tag->items = g_realloc(tag->items, items_size(tag)); - else if (tag->num_items >= BULK_MAX) { - /* bulk list already full - switch back to non-bulk */ - assert(bulk.busy); - - tag->items = g_malloc(items_size(tag)); - memcpy(tag->items, bulk.items, - items_size(tag) - sizeof(struct tag_item *)); - } - - g_mutex_lock(tag_pool_lock); - tag->items[i] = tag_pool_get_item(type, value, len); - g_mutex_unlock(tag_pool_lock); - - g_free(p); -} - -void tag_add_item_n(struct tag *tag, enum tag_type type, - const char *value, size_t len) -{ - if (ignore_tag_items[type]) - { - return; - } - if (!value || !len) - return; - - tag_add_item_internal(tag, type, value, len); -} @@ -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_file.c b/src/tag_file.c deleted file mode 100644 index 8d8a0f5fb..000000000 --- a/src/tag_file.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag_file.h" -#include "uri.h" -#include "decoder_list.h" -#include "decoder_plugin.h" -#include "input_stream.h" - -#include <assert.h> -#include <unistd.h> /* for SEEK_SET */ - -bool -tag_file_scan(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - assert(path_fs != NULL); - assert(handler != NULL); - - /* check if there's a suffix and a plugin */ - - const char *suffix = uri_get_suffix(path_fs); - if (suffix == NULL) - return false; - - const struct decoder_plugin *plugin = - decoder_plugin_from_suffix(suffix, NULL); - if (plugin == NULL) - return false; - - struct input_stream *is = NULL; - GMutex *mutex = NULL; - GCond *cond = NULL; - - do { - /* load file tag */ - if (decoder_plugin_scan_file(plugin, path_fs, - handler, handler_ctx)) - break; - - /* fall back to stream tag */ - if (plugin->scan_stream != NULL) { - /* open the input_stream (if not already - open) */ - if (is == NULL) { - mutex = g_mutex_new(); - cond = g_cond_new(); - is = input_stream_open(path_fs, mutex, cond, - NULL); - } - - /* now try the stream_tag() method */ - if (is != NULL) { - if (decoder_plugin_scan_stream(plugin, is, - handler, - handler_ctx)) - break; - - input_stream_lock_seek(is, 0, SEEK_SET, NULL); - } - } - - plugin = decoder_plugin_from_suffix(suffix, plugin); - } while (plugin != NULL); - - if (is != NULL) { - input_stream_close(is); - g_cond_free(cond); - g_mutex_free(mutex); - } - - return plugin != NULL; -} diff --git a/src/tag_file.h b/src/tag_file.h deleted file mode 100644 index 8cf1af3cb..000000000 --- a/src/tag_file.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_FILE_H -#define MPD_TAG_FILE_H - -#include "check.h" - -#include <stdbool.h> - -struct tag_handler; - -/** - * Scan the tags of a song file. Invokes matching decoder plugins, - * but does not invoke the special "APE" and "ID3" scanners. - */ -bool -tag_file_scan(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx); - -#endif diff --git a/src/tag_id3.c b/src/tag_id3.c index 0971829f0..5744e0e62 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 049c53ad9..1907c13fc 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_internal.h b/src/tag_internal.h deleted file mode 100644 index af05cc6b6..000000000 --- a/src/tag_internal.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_INTERNAL_H -#define MPD_TAG_INTERNAL_H - -#include "tag.h" - -#include <stdbool.h> - -extern bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; - -#endif diff --git a/src/tag_pool.c b/src/tag_pool.c deleted file mode 100644 index eabf3e369..000000000 --- a/src/tag_pool.c +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag_pool.h" - -#include <assert.h> - -GMutex *tag_pool_lock = NULL; - -#define NUM_SLOTS 4096 - -struct slot { - struct slot *next; - unsigned char ref; - struct tag_item item; -} mpd_packed; - -static struct slot *slots[NUM_SLOTS]; - -static inline unsigned -calc_hash_n(enum tag_type type, const char *p, size_t length) -{ - unsigned hash = 5381; - - assert(p != NULL); - - while (length-- > 0) - hash = (hash << 5) + hash + *p++; - - return hash ^ type; -} - -static inline unsigned -calc_hash(enum tag_type type, const char *p) -{ - unsigned hash = 5381; - - assert(p != NULL); - - while (*p != 0) - hash = (hash << 5) + hash + *p++; - - return hash ^ type; -} - -static inline struct slot * -tag_item_to_slot(struct tag_item *item) -{ - return (struct slot*)(((char*)item) - offsetof(struct slot, item)); -} - -static struct slot *slot_alloc(struct slot *next, - enum tag_type type, - const char *value, int length) -{ - struct slot *slot; - - slot = g_malloc(sizeof(*slot) - sizeof(slot->item.value) + length + 1); - slot->next = next; - slot->ref = 1; - slot->item.type = type; - memcpy(slot->item.value, value, length); - slot->item.value[length] = 0; - return slot; -} - -void tag_pool_init(void) -{ - g_assert(tag_pool_lock == NULL); - tag_pool_lock = g_mutex_new(); -} - -void tag_pool_deinit(void) -{ - g_assert(tag_pool_lock != NULL); - g_mutex_free(tag_pool_lock); - tag_pool_lock = NULL; -} - -struct tag_item * -tag_pool_get_item(enum tag_type type, const char *value, size_t length) -{ - struct slot **slot_p, *slot; - - slot_p = &slots[calc_hash_n(type, value, length) % NUM_SLOTS]; - for (slot = *slot_p; slot != NULL; slot = slot->next) { - if (slot->item.type == type && - length == strlen(slot->item.value) && - memcmp(value, slot->item.value, length) == 0 && - slot->ref < 0xff) { - assert(slot->ref > 0); - ++slot->ref; - return &slot->item; - } - } - - slot = slot_alloc(*slot_p, type, value, length); - *slot_p = slot; - return &slot->item; -} - -struct tag_item *tag_pool_dup_item(struct tag_item *item) -{ - struct slot *slot = tag_item_to_slot(item); - - assert(slot->ref > 0); - - if (slot->ref < 0xff) { - ++slot->ref; - return item; - } else { - /* the reference counter overflows above 0xff; - duplicate the item, and start with 1 */ - size_t length = strlen(item->value); - struct slot **slot_p = - &slots[calc_hash_n(item->type, item->value, - length) % NUM_SLOTS]; - slot = slot_alloc(*slot_p, item->type, - item->value, strlen(item->value)); - *slot_p = slot; - return &slot->item; - } -} - -void tag_pool_put_item(struct tag_item *item) -{ - struct slot **slot_p, *slot; - - slot = tag_item_to_slot(item); - assert(slot->ref > 0); - --slot->ref; - - if (slot->ref > 0) - return; - - for (slot_p = &slots[calc_hash(item->type, item->value) % NUM_SLOTS]; - *slot_p != slot; - slot_p = &(*slot_p)->next) { - assert(*slot_p != NULL); - } - - *slot_p = slot->next; - g_free(slot); -} diff --git a/src/tag_pool.h b/src/tag_pool.h deleted file mode 100644 index a96c00d85..000000000 --- a/src/tag_pool.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_POOL_H -#define MPD_TAG_POOL_H - -#include "tag.h" - -#include <glib.h> - -extern GMutex *tag_pool_lock; - -struct tag_item; - -void tag_pool_init(void); - -void tag_pool_deinit(void); - -struct tag_item * -tag_pool_get_item(enum tag_type type, const char *value, size_t length); - -struct tag_item *tag_pool_dup_item(struct tag_item *item); - -void tag_pool_put_item(struct tag_item *item); - -#endif diff --git a/src/tag_print.c b/src/tag_print.c deleted file mode 100644 index 9a46b247a..000000000 --- a/src/tag_print.c +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag_print.h" -#include "tag.h" -#include "tag_internal.h" -#include "client.h" -#include "song.h" - -void tag_print_types(struct client *client) -{ - int i; - - for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { - if (!ignore_tag_items[i]) - client_printf(client, "tagtype: %s\n", - tag_item_names[i]); - } -} - -void tag_print(struct client *client, const struct tag *tag) -{ - if (tag->time >= 0) - client_printf(client, SONG_TIME "%i\n", tag->time); - - for (unsigned i = 0; i < tag->num_items; i++) { - client_printf(client, "%s: %s\n", - tag_item_names[tag->items[i]->type], - tag->items[i]->value); - } -} diff --git a/src/tag_print.h b/src/tag_print.h deleted file mode 100644 index b9eeeaecf..000000000 --- a/src/tag_print.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_PRINT_H -#define MPD_TAG_PRINT_H - -struct tag; -struct client; - -void tag_print_types(struct client *client); - -void tag_print(struct client *client, const struct tag *tag); - -#endif diff --git a/src/tag_save.c b/src/tag_save.c deleted file mode 100644 index 2fdaef56c..000000000 --- a/src/tag_save.c +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag_save.h" -#include "tag.h" -#include "tag_internal.h" -#include "song.h" - -void tag_save(FILE *file, const struct tag *tag) -{ - if (tag->time >= 0) - fprintf(file, SONG_TIME "%i\n", tag->time); - - if (tag->has_playlist) - fprintf(file, "Playlist: yes\n"); - - for (unsigned i = 0; i < tag->num_items; i++) - fprintf(file, "%s: %s\n", - tag_item_names[tag->items[i]->type], - tag->items[i]->value); -} diff --git a/src/tag_save.h b/src/tag_save.h deleted file mode 100644 index 9f6a580c8..000000000 --- a/src/tag_save.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_SAVE_H -#define MPD_TAG_SAVE_H - -#include <stdio.h> - -struct tag; - -void tag_save(FILE *file, const struct tag *tag); - -#endif diff --git a/src/tag_table.h b/src/tag_table.h index d87d4869a..8e2172c18 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.c b/src/text_file.c deleted file mode 100644 index 3674e5ce2..000000000 --- a/src/text_file.c +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "text_file.h" - -#include <assert.h> -#include <string.h> - -char * -read_text_line(FILE *file, GString *buffer) -{ - enum { - max_length = 512 * 1024, - step = 1024, - }; - - gsize length = 0, i; - char *p; - - assert(file != NULL); - assert(buffer != NULL); - - if (buffer->allocated_len < step) - g_string_set_size(buffer, step); - - while (buffer->len < max_length) { - p = fgets(buffer->str + length, - buffer->allocated_len - length, file); - if (p == NULL) { - if (length == 0 || ferror(file)) - return NULL; - break; - } - - i = strlen(buffer->str + length); - length += i; - if (i < step - 1 || buffer->str[length - 1] == '\n') - break; - - g_string_set_size(buffer, length + step); - } - - /* remove the newline characters */ - if (buffer->str[length - 1] == '\n') - --length; - if (buffer->str[length - 1] == '\r') - --length; - - g_string_set_size(buffer, length); - return buffer->str; -} diff --git a/src/text_file.h b/src/text_file.h deleted file mode 100644 index 9dd810943..000000000 --- a/src/text_file.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TEXT_FILE_H -#define MPD_TEXT_FILE_H - -#include <glib.h> - -#include <stdio.h> - -/** - * Reads a line from the input file, and strips trailing space. There - * is a reasonable maximum line length, only to prevent denial of - * service. - * - * @param file the source file, opened in text mode - * @param buffer an allocator for the buffer - * @return a pointer to the line, or NULL on end-of-file or error - */ -char * -read_text_line(FILE *file, GString *buffer); - -#endif diff --git a/src/text_input_stream.c b/src/text_input_stream.c index 4a858fc85..4a2eeb817 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 000000000..bbaabddc2 --- /dev/null +++ b/src/thread/Cond.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_THREAD_COND_HXX +#define MPD_THREAD_COND_HXX + +#ifdef WIN32 + +/* mingw-w64 4.6.3 lacks a std::cond implementation */ + +#include "WindowsCond.hxx" +typedef WindowsCond Cond; + +#else + +#include "PosixCond.hxx" +typedef PosixCond Cond; + +#endif + +#endif diff --git a/src/thread/CriticalSection.hxx b/src/thread/CriticalSection.hxx new file mode 100644 index 000000000..8bc05b8f5 --- /dev/null +++ b/src/thread/CriticalSection.hxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009-2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_CRITICAL_SECTION_HXX +#define MPD_THREAD_CRITICAL_SECTION_HXX + +#include <windows.h> + +/** + * Wrapper for a CRITICAL_SECTION, backend for the Mutex class. + */ +class CriticalSection { + friend class WindowsCond; + + CRITICAL_SECTION critical_section; + +public: + CriticalSection() { + ::InitializeCriticalSection(&critical_section); + } + + ~CriticalSection() { + ::DeleteCriticalSection(&critical_section); + } + + CriticalSection(const CriticalSection &other) = delete; + CriticalSection &operator=(const CriticalSection &other) = delete; + + void lock() { + ::EnterCriticalSection(&critical_section); + }; + + bool try_lock() { + return ::TryEnterCriticalSection(&critical_section) != 0; + }; + + void unlock() { + ::LeaveCriticalSection(&critical_section); + } +}; + +#endif diff --git a/src/thread/GLibCond.hxx b/src/thread/GLibCond.hxx new file mode 100644 index 000000000..9ab08e9fd --- /dev/null +++ b/src/thread/GLibCond.hxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_GLIB_COND_HXX +#define MPD_THREAD_GLIB_COND_HXX + +#include "GLibMutex.hxx" + +/** + * A wrapper for GCond. + */ +class GLibCond { +#if GLIB_CHECK_VERSION(2,32,0) + GCond cond; +#else + GCond *cond; +#endif + +public: + GLibCond() { +#if GLIB_CHECK_VERSION(2,32,0) + g_cond_init(&cond); +#else + cond = g_cond_new(); +#endif + } + + ~GLibCond() { +#if GLIB_CHECK_VERSION(2,32,0) + g_cond_clear(&cond); +#else + g_cond_free(cond); +#endif + } + + GLibCond(const GLibCond &other) = delete; + GLibCond &operator=(const GLibCond &other) = delete; + +private: + GCond *GetNative() { +#if GLIB_CHECK_VERSION(2,32,0) + return &cond; +#else + return cond; +#endif + } + +public: + void signal() { + g_cond_signal(GetNative()); + } + + void broadcast() { + g_cond_broadcast(GetNative()); + } + + void wait(GLibMutex &mutex) { + g_cond_wait(GetNative(), mutex.GetNative()); + } +}; + +#endif diff --git a/src/thread/GLibMutex.hxx b/src/thread/GLibMutex.hxx new file mode 100644 index 000000000..2c666c1ea --- /dev/null +++ b/src/thread/GLibMutex.hxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_GLIB_MUTEX_HXX +#define MPD_THREAD_GLIB_MUTEX_HXX + +#include <glib.h> + +/** + * A wrapper for GMutex. + */ +class GLibMutex { + friend class GLibCond; + +#if GLIB_CHECK_VERSION(2,32,0) + GMutex mutex; +#else + GMutex *mutex; +#endif + +public: + GLibMutex() { +#if GLIB_CHECK_VERSION(2,32,0) + g_mutex_init(&mutex); +#else + mutex = g_mutex_new(); +#endif + } + + ~GLibMutex() { +#if GLIB_CHECK_VERSION(2,32,0) + g_mutex_clear(&mutex); +#else + g_mutex_free(mutex); +#endif + } + + GLibMutex(const GLibMutex &other) = delete; + GLibMutex &operator=(const GLibMutex &other) = delete; + +private: + GMutex *GetNative() { +#if GLIB_CHECK_VERSION(2,32,0) + return &mutex; +#else + return mutex; +#endif + } + +public: + void lock() { + g_mutex_lock(GetNative()); + } + + bool try_lock() { + return g_mutex_trylock(GetNative()); + } + + void unlock() { + g_mutex_lock(GetNative()); + } +}; + +#endif diff --git a/src/thread/Mutex.hxx b/src/thread/Mutex.hxx new file mode 100644 index 000000000..675af74b1 --- /dev/null +++ b/src/thread/Mutex.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_THREAD_MUTEX_HXX +#define MPD_THREAD_MUTEX_HXX + +#ifdef WIN32 + +/* mingw-w64 4.6.3 lacks a std::mutex implementation */ + +#include "CriticalSection.hxx" +typedef CriticalSection Mutex; + +#else + +#include "PosixMutex.hxx" + +typedef PosixMutex Mutex; + +#endif + +class ScopeLock { + Mutex &mutex; + +public: + ScopeLock(Mutex &_mutex):mutex(_mutex) { + mutex.lock(); + }; + + ~ScopeLock() { + mutex.unlock(); + }; + + ScopeLock(const ScopeLock &other) = delete; + ScopeLock &operator=(const ScopeLock &other) = delete; +}; + +#endif diff --git a/src/thread/PosixCond.hxx b/src/thread/PosixCond.hxx new file mode 100644 index 000000000..acdc05edc --- /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 000000000..d50764af4 --- /dev/null +++ b/src/thread/PosixMutex.hxx @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2009-2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_POSIX_MUTEX_HXX +#define MPD_THREAD_POSIX_MUTEX_HXX + +#include <pthread.h> + +/** + * Low-level wrapper for a pthread_mutex_t. + */ +class PosixMutex { + friend class PosixCond; + + pthread_mutex_t mutex; + +public: + constexpr PosixMutex():mutex(PTHREAD_MUTEX_INITIALIZER) {} + + PosixMutex(const PosixMutex &other) = delete; + PosixMutex &operator=(const PosixMutex &other) = delete; + + void lock() { + pthread_mutex_lock(&mutex); + } + + bool try_lock() { + return pthread_mutex_trylock(&mutex) == 0; + } + + void unlock() { + pthread_mutex_unlock(&mutex); + } +}; + +#endif diff --git a/src/thread/WindowsCond.hxx b/src/thread/WindowsCond.hxx new file mode 100644 index 000000000..f4e909c72 --- /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 184881249..1506c9173 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 bbb34e100..4a98e882f 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 d55eb3ca6..2026e5ad6 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.c b/src/update.c deleted file mode 100644 index 12eec40c4..000000000 --- a/src/update.c +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "update.h" -#include "update_queue.h" -#include "update_walk.h" -#include "update_remove.h" -#include "update.h" -#include "database.h" -#include "mapper.h" -#include "playlist.h" -#include "event_pipe.h" -#include "update.h" -#include "idle.h" -#include "stats.h" -#include "main.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "update" - -static enum update_progress { - UPDATE_PROGRESS_IDLE = 0, - UPDATE_PROGRESS_RUNNING = 1, - UPDATE_PROGRESS_DONE = 2 -} progress; - -static bool modified; - -static GThread *update_thr; - -static const unsigned update_task_id_max = 1 << 15; - -static unsigned update_task_id; - -/* XXX this flag is passed to update_task() */ -static bool discard; - -unsigned -isUpdatingDB(void) -{ - return (progress != UPDATE_PROGRESS_IDLE) ? update_task_id : 0; -} - -static void * update_task(void *_path) -{ - const char *path = _path; - - if (path != NULL && *path != 0) - g_debug("starting: %s", path); - else - g_debug("starting"); - - modified = update_walk(path, discard); - - if (modified || !db_exists()) { - GError *error = NULL; - if (!db_save(&error)) { - g_warning("Failed to save database: %s", - error->message); - g_error_free(error); - } - } - - if (path != NULL && *path != 0) - g_debug("finished: %s", path); - else - g_debug("finished"); - g_free(_path); - - progress = UPDATE_PROGRESS_DONE; - event_pipe_emit(PIPE_EVENT_UPDATE); - return NULL; -} - -static void -spawn_update_task(const char *path) -{ - GError *e = NULL; - - assert(g_thread_self() == main_task); - - progress = UPDATE_PROGRESS_RUNNING; - modified = false; - - update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e); - if (update_thr == NULL) - MPD_ERROR("Failed to spawn update task: %s", e->message); - - if (++update_task_id > update_task_id_max) - update_task_id = 1; - g_debug("spawned thread for update job id %i", update_task_id); -} - -unsigned -update_enqueue(const char *path, bool _discard) -{ - assert(g_thread_self() == main_task); - - if (!mapper_has_music_directory()) - return 0; - - if (progress != UPDATE_PROGRESS_IDLE) { - unsigned next_task_id = - update_queue_push(path, discard, update_task_id); - if (next_task_id == 0) - return 0; - - return next_task_id > update_task_id_max ? 1 : next_task_id; - } - - discard = _discard; - spawn_update_task(path); - - idle_add(IDLE_UPDATE); - - return update_task_id; -} - -/** - * Called in the main thread after the database update is finished. - */ -static void update_finished_event(void) -{ - char *path; - - assert(progress == UPDATE_PROGRESS_DONE); - - g_thread_join(update_thr); - - idle_add(IDLE_UPDATE); - - if (modified) { - /* send "idle" events */ - playlist_increment_version_all(&g_playlist); - idle_add(IDLE_DATABASE); - } - - path = update_queue_shift(&discard); - if (path != NULL) { - /* schedule the next path */ - spawn_update_task(path); - g_free(path); - } else { - progress = UPDATE_PROGRESS_IDLE; - - stats_update(); - } -} - -void update_global_init(void) -{ - event_pipe_register(PIPE_EVENT_UPDATE, update_finished_event); - - update_remove_global_init(); - update_walk_global_init(); -} - -void update_global_finish(void) -{ - update_walk_global_finish(); - update_remove_global_finish(); -} diff --git a/src/update.h b/src/update.h deleted file mode 100644 index 3d586b694..000000000 --- a/src/update.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_H -#define MPD_UPDATE_H - -#include <stdbool.h> - -void update_global_init(void); - -void update_global_finish(void); - -unsigned -isUpdatingDB(void); - -/** - * Add this path to the database update queue. - * - * @param path a path to update; if NULL or an empty string, - * the whole music directory is updated - * @return the job id, or 0 on error - */ -unsigned -update_enqueue(const char *path, bool discard); - -#endif diff --git a/src/update_archive.c b/src/update_archive.c deleted file mode 100644 index 3fb2bc18c..000000000 --- a/src/update_archive.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_archive.h" -#include "update_internal.h" -#include "db_lock.h" -#include "directory.h" -#include "song.h" -#include "mapper.h" -#include "archive_list.h" -#include "archive_plugin.h" - -#include <glib.h> - -#include <string.h> - -static void -update_archive_tree(struct directory *directory, char *name) -{ - char *tmp = strchr(name, '/'); - if (tmp) { - *tmp = 0; - //add dir is not there already - db_lock(); - struct directory *subdir = - directory_make_child(directory, name); - subdir->device = DEVICE_INARCHIVE; - db_unlock(); - //create directories first - update_archive_tree(subdir, tmp+1); - } else { - if (strlen(name) == 0) { - g_warning("archive returned directory only"); - return; - } - - //add file - db_lock(); - struct song *song = directory_get_song(directory, name); - db_unlock(); - if (song == NULL) { - song = song_file_load(name, directory); - if (song != NULL) { - db_lock(); - directory_add_song(directory, song); - db_unlock(); - - modified = true; - g_message("added %s/%s", - directory_get_path(directory), name); - } - } - } -} - -/** - * Updates the file listing from an archive file. - * - * @param parent the parent directory the archive file resides in - * @param name the UTF-8 encoded base name of the archive file - * @param st stat() information on the archive file - * @param plugin the archive plugin which fits this archive type - */ -static void -update_archive_file2(struct directory *parent, const char *name, - const struct stat *st, - const struct archive_plugin *plugin) -{ - db_lock(); - struct directory *directory = directory_get_child(parent, name); - db_unlock(); - - if (directory != NULL && directory->mtime == st->st_mtime && - !walk_discard) - /* MPD has already scanned the archive, and it hasn't - changed since - don't consider updating it */ - return; - - char *path_fs = map_directory_child_fs(parent, name); - - /* open archive */ - GError *error = NULL; - struct archive_file *file = archive_file_open(plugin, path_fs, &error); - if (file == NULL) { - g_free(path_fs); - g_warning("%s", error->message); - g_error_free(error); - return; - } - - g_debug("archive %s opened", path_fs); - g_free(path_fs); - - if (directory == NULL) { - g_debug("creating archive directory: %s", name); - db_lock(); - directory = directory_new_child(parent, name); - /* mark this directory as archive (we use device for - this) */ - directory->device = DEVICE_INARCHIVE; - db_unlock(); - } - - directory->mtime = st->st_mtime; - - archive_file_scan_reset(file); - - char *filepath; - while ((filepath = archive_file_scan_next(file)) != NULL) { - /* split name into directory and file */ - g_debug("adding archive file: %s", filepath); - update_archive_tree(directory, filepath); - } - - archive_file_close(file); -} - -bool -update_archive_file(struct directory *directory, - const char *name, const char *suffix, - const struct stat *st) -{ -#ifdef ENABLE_ARCHIVE - const struct archive_plugin *plugin = - archive_plugin_from_suffix(suffix); - if (plugin == NULL) - return false; - - update_archive_file2(directory, name, st, plugin); - return true; -#else - (void)directory; - (void)name; - (void)suffix; - (void)st; - - return false; -#endif -} diff --git a/src/update_archive.h b/src/update_archive.h deleted file mode 100644 index 838697dd9..000000000 --- a/src/update_archive.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_ARCHIVE_H -#define MPD_UPDATE_ARCHIVE_H - -#include "check.h" - -#include <stdbool.h> -#include <sys/stat.h> - -struct directory; -struct archive_plugin; - -#ifdef ENABLE_ARCHIVE - -bool -update_archive_file(struct directory *directory, - const char *name, const char *suffix, - const struct stat *st); - -#else - -#include <glib.h> - -static inline bool -update_archive_file(G_GNUC_UNUSED struct directory *directory, - G_GNUC_UNUSED const char *name, - G_GNUC_UNUSED const char *suffix, - G_GNUC_UNUSED const struct stat *st) -{ - return false; -} - -#endif - -#endif diff --git a/src/update_container.c b/src/update_container.c deleted file mode 100644 index bda95dabe..000000000 --- a/src/update_container.c +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_container.h" -#include "update_internal.h" -#include "update_db.h" -#include "db_lock.h" -#include "directory.h" -#include "song.h" -#include "mapper.h" -#include "decoder_plugin.h" -#include "tag.h" -#include "tag_handler.h" - -#include <glib.h> - -/** - * Create the specified directory object if it does not exist already - * or if the #stat object indicates that it has been modified since - * the last update. Returns NULL when it exists already and is - * unmodified. - * - * The caller must lock the database. - */ -static struct directory * -make_directory_if_modified(struct directory *parent, const char *name, - const struct stat *st) -{ - struct directory *directory = directory_get_child(parent, name); - - // directory exists already - if (directory != NULL) { - if (directory->mtime == st->st_mtime && !walk_discard) { - /* not modified */ - db_unlock(); - return NULL; - } - - delete_directory(directory); - modified = true; - } - - directory = directory_make_child(parent, name); - directory->mtime = st->st_mtime; - return directory; -} - -bool -update_container_file(struct directory *directory, - const char *name, - const struct stat *st, - const struct decoder_plugin *plugin) -{ - if (plugin->container_scan == NULL) - return false; - - db_lock(); - struct directory *contdir = - make_directory_if_modified(directory, name, st); - if (contdir == NULL) { - /* not modified */ - db_unlock(); - return true; - } - - contdir->device = DEVICE_CONTAINER; - db_unlock(); - - char *const pathname = map_directory_child_fs(directory, name); - - char *vtrack; - unsigned int tnum = 0; - while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL) { - struct song *song = song_file_new(vtrack, contdir); - - // shouldn't be necessary but it's there.. - song->mtime = st->st_mtime; - - char *child_path_fs = map_directory_child_fs(contdir, vtrack); - - song->tag = tag_new(); - decoder_plugin_scan_file(plugin, child_path_fs, - &add_tag_handler, song->tag); - g_free(child_path_fs); - - db_lock(); - directory_add_song(contdir, song); - db_unlock(); - - modified = true; - - g_message("added %s/%s", - directory_get_path(directory), vtrack); - g_free(vtrack); - } - - g_free(pathname); - - if (tnum == 1) { - db_lock(); - delete_directory(contdir); - db_unlock(); - return false; - } else - return true; -} diff --git a/src/update_container.h b/src/update_container.h deleted file mode 100644 index 7f42c80ca..000000000 --- a/src/update_container.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_CONTAINER_H -#define MPD_UPDATE_CONTAINER_H - -#include "check.h" - -#include <stdbool.h> -#include <sys/stat.h> - -struct directory; -struct decoder_plugin; - -bool -update_container_file(struct directory *directory, - const char *name, - const struct stat *st, - const struct decoder_plugin *plugin); - -#endif diff --git a/src/update_db.c b/src/update_db.c deleted file mode 100644 index 8982a53e2..000000000 --- a/src/update_db.c +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_db.h" -#include "update_remove.h" -#include "directory.h" -#include "song.h" -#include "playlist_vector.h" -#include "db_lock.h" - -#include <glib.h> -#include <assert.h> - -void -delete_song(struct directory *dir, struct song *del) -{ - assert(del->parent == dir); - - /* first, prevent traversers in main task from getting this */ - directory_remove_song(dir, del); - - db_unlock(); /* temporary unlock, because update_remove_song() blocks */ - - /* now take it out of the playlist (in the main_task) */ - update_remove_song(del); - - /* finally, all possible references gone, free it */ - song_free(del); - - db_lock(); -} - -/** - * Recursively remove all sub directories and songs from a directory, - * leaving an empty directory. - * - * Caller must lock the #db_mutex. - */ -static void -clear_directory(struct directory *directory) -{ - struct directory *child, *n; - directory_for_each_child_safe(child, n, directory) - delete_directory(child); - - struct song *song, *ns; - directory_for_each_song_safe(song, ns, directory) { - assert(song->parent == directory); - delete_song(directory, song); - } -} - -void -delete_directory(struct directory *directory) -{ - assert(directory->parent != NULL); - - clear_directory(directory); - - directory_delete(directory); -} - -bool -delete_name_in(struct directory *parent, const char *name) -{ - bool modified = false; - - db_lock(); - struct directory *directory = directory_get_child(parent, name); - - if (directory != NULL) { - delete_directory(directory); - modified = true; - } - - struct song *song = directory_get_song(parent, name); - if (song != NULL) { - delete_song(parent, song); - modified = true; - } - - playlist_vector_remove(&parent->playlists, name); - - db_unlock(); - - return modified; -} diff --git a/src/update_db.h b/src/update_db.h deleted file mode 100644 index 0a9e46b05..000000000 --- a/src/update_db.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_DB_H -#define MPD_UPDATE_DB_H - -#include "check.h" - -#include <stdbool.h> - -struct directory; -struct song; - -/** - * Caller must lock the #db_mutex. - */ -void -delete_song(struct directory *parent, struct song *song); - -/** - * Recursively free a directory and all its contents. - * - * Caller must lock the #db_mutex. - */ -void -delete_directory(struct directory *directory); - -/** - * Caller must NOT lock the #db_mutex. - * - * @return true if the database was modified - */ -bool -delete_name_in(struct directory *parent, const char *name); - -#endif diff --git a/src/update_internal.h b/src/update_internal.h deleted file mode 100644 index 76ab7bed3..000000000 --- a/src/update_internal.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_INTERNAL_H -#define MPD_UPDATE_INTERNAL_H - -#include "check.h" - -#include <stdbool.h> - -extern bool walk_discard; -extern bool modified; - -#endif diff --git a/src/update_io.c b/src/update_io.c deleted file mode 100644 index c6a540a0f..000000000 --- a/src/update_io.c +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_io.h" -#include "mapper.h" -#include "directory.h" -#include "glib_compat.h" - -#include <glib.h> - -#include <errno.h> -#include <unistd.h> - -int -stat_directory(const struct directory *directory, struct stat *st) -{ - char *path_fs = map_directory_fs(directory); - if (path_fs == NULL) - return -1; - - int ret = stat(path_fs, st); - if (ret < 0) - g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno)); - - g_free(path_fs); - return ret; -} - -int -stat_directory_child(const struct directory *parent, const char *name, - struct stat *st) -{ - char *path_fs = map_directory_child_fs(parent, name); - if (path_fs == NULL) - return -1; - - int ret = stat(path_fs, st); - if (ret < 0) - g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno)); - - g_free(path_fs); - return ret; -} - -bool -directory_exists(const struct directory *directory) -{ - char *path_fs = map_directory_fs(directory); - if (path_fs == NULL) - /* invalid path: cannot exist */ - return false; - - GFileTest test = directory->device == DEVICE_INARCHIVE || - directory->device == DEVICE_CONTAINER - ? G_FILE_TEST_IS_REGULAR - : G_FILE_TEST_IS_DIR; - - bool exists = g_file_test(path_fs, test); - g_free(path_fs); - - return exists; -} - -bool -directory_child_is_regular(const struct directory *directory, - const char *name_utf8) -{ - char *path_fs = map_directory_child_fs(directory, name_utf8); - if (path_fs == NULL) - return false; - - struct stat st; - bool is_regular = stat(path_fs, &st) == 0 && S_ISREG(st.st_mode); - g_free(path_fs); - - return is_regular; -} - -bool -directory_child_access(const struct directory *directory, - const char *name, int mode) -{ -#ifdef WIN32 - /* access() is useless on WIN32 */ - (void)directory; - (void)name; - (void)mode; - return true; -#else - char *path = map_directory_child_fs(directory, name); - if (path == NULL) - /* something went wrong, but that isn't a permission - problem */ - return true; - - bool success = access(path, mode) == 0 || errno != EACCES; - g_free(path); - return success; -#endif -} diff --git a/src/update_io.h b/src/update_io.h deleted file mode 100644 index 6ff1ccebd..000000000 --- a/src/update_io.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_IO_H -#define MPD_UPDATE_IO_H - -#include "check.h" - -#include <stdbool.h> -#include <sys/stat.h> - -struct directory; - -int -stat_directory(const struct directory *directory, struct stat *st); - -int -stat_directory_child(const struct directory *parent, const char *name, - struct stat *st); - -bool -directory_exists(const struct directory *directory); - -bool -directory_child_is_regular(const struct directory *directory, - const char *name_utf8); - -/** - * Checks if the given permissions on the mapped file are given. - */ -bool -directory_child_access(const struct directory *directory, - const char *name, int mode); - -#endif diff --git a/src/update_queue.c b/src/update_queue.c deleted file mode 100644 index 2150fa4e4..000000000 --- a/src/update_queue.c +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "update_queue.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -/* make this dynamic?, or maybe this is big enough... */ -static struct { - char *path; - bool discard; -} update_queue[32]; - -static size_t update_queue_length; - -unsigned -update_queue_push(const char *path, bool discard, unsigned base) -{ - assert(update_queue_length <= G_N_ELEMENTS(update_queue)); - - if (update_queue_length == G_N_ELEMENTS(update_queue)) - return 0; - - update_queue[update_queue_length].path = g_strdup(path); - update_queue[update_queue_length].discard = discard; - - ++update_queue_length; - - return base + update_queue_length; -} - -char * -update_queue_shift(bool *discard_r) -{ - char *path; - - if (update_queue_length == 0) - return NULL; - - path = update_queue[0].path; - *discard_r = update_queue[0].discard; - - memmove(&update_queue[0], &update_queue[1], - --update_queue_length * sizeof(update_queue[0])); - return path; -} diff --git a/src/update_queue.h b/src/update_queue.h deleted file mode 100644 index 84ba474b0..000000000 --- a/src/update_queue.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_QUEUE_H -#define MPD_UPDATE_QUEUE_H - -#include "check.h" - -#include <stdbool.h> - -unsigned -update_queue_push(const char *path, bool discard, unsigned base); - -char * -update_queue_shift(bool *discard_r); - -#endif diff --git a/src/update_remove.c b/src/update_remove.c deleted file mode 100644 index f443f5eb2..000000000 --- a/src/update_remove.c +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_remove.h" -#include "event_pipe.h" -#include "song.h" -#include "playlist.h" -#include "main.h" - -#ifdef ENABLE_SQLITE -#include "sticker.h" -#include "song_sticker.h" -#endif - -#include <glib.h> - -#include <assert.h> - -static const struct song *removed_song; - -static GMutex *remove_mutex; -static GCond *remove_cond; - -/** - * Safely remove a song from the database. This must be done in the - * main task, to be sure that there is no pointer left to it. - */ -static void -song_remove_event(void) -{ - char *uri; - - assert(removed_song != NULL); - - uri = song_get_uri(removed_song); - g_message("removing %s", uri); - g_free(uri); - -#ifdef ENABLE_SQLITE - /* if the song has a sticker, remove it */ - if (sticker_enabled()) - sticker_song_delete(removed_song); -#endif - - playlist_delete_song(&g_playlist, global_player_control, removed_song); - - /* clear "removed_song" and send signal to update thread */ - g_mutex_lock(remove_mutex); - removed_song = NULL; - g_cond_signal(remove_cond); - g_mutex_unlock(remove_mutex); -} - -void -update_remove_global_init(void) -{ - remove_mutex = g_mutex_new(); - remove_cond = g_cond_new(); - - event_pipe_register(PIPE_EVENT_DELETE, song_remove_event); -} - -void -update_remove_global_finish(void) -{ - g_mutex_free(remove_mutex); - g_cond_free(remove_cond); -} - -void -update_remove_song(const struct song *song) -{ - assert(removed_song == NULL); - - removed_song = song; - - event_pipe_emit(PIPE_EVENT_DELETE); - - g_mutex_lock(remove_mutex); - - while (removed_song != NULL) - g_cond_wait(remove_cond, remove_mutex); - - g_mutex_unlock(remove_mutex); -} diff --git a/src/update_remove.h b/src/update_remove.h deleted file mode 100644 index 479ef83ff..000000000 --- a/src/update_remove.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_REMOVE_H -#define MPD_UPDATE_REMOVE_H - -#include "check.h" - -struct song; - -void -update_remove_global_init(void); - -void -update_remove_global_finish(void); - -/** - * Sends a signal to the main thread which will in turn remove the - * song: from the sticker database and from the playlist. This - * serialized access is implemented to avoid excessive locking. - */ -void -update_remove_song(const struct song *song); - -#endif diff --git a/src/update_song.c b/src/update_song.c deleted file mode 100644 index 1126ad115..000000000 --- a/src/update_song.c +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_song.h" -#include "update_internal.h" -#include "update_io.h" -#include "update_db.h" -#include "update_container.h" -#include "db_lock.h" -#include "directory.h" -#include "song.h" -#include "decoder_list.h" -#include "decoder_plugin.h" - -#include <glib.h> - -#include <unistd.h> - -static void -update_song_file2(struct directory *directory, - const char *name, const struct stat *st, - const struct decoder_plugin *plugin) -{ - db_lock(); - struct song *song = directory_get_song(directory, name); - db_unlock(); - - if (!directory_child_access(directory, name, R_OK)) { - g_warning("no read permissions on %s/%s", - directory_get_path(directory), name); - if (song != NULL) { - db_lock(); - delete_song(directory, song); - db_unlock(); - } - - return; - } - - if (!(song != NULL && st->st_mtime == song->mtime && - !walk_discard) && - update_container_file(directory, name, st, plugin)) { - if (song != NULL) { - db_lock(); - delete_song(directory, song); - db_unlock(); - } - - return; - } - - if (song == NULL) { - g_debug("reading %s/%s", - directory_get_path(directory), name); - song = song_file_load(name, directory); - if (song == NULL) { - g_debug("ignoring unrecognized file %s/%s", - directory_get_path(directory), name); - return; - } - - db_lock(); - directory_add_song(directory, song); - db_unlock(); - - modified = true; - g_message("added %s/%s", - directory_get_path(directory), name); - } else if (st->st_mtime != song->mtime || walk_discard) { - g_message("updating %s/%s", - directory_get_path(directory), name); - if (!song_file_update(song)) { - g_debug("deleting unrecognized file %s/%s", - directory_get_path(directory), name); - db_lock(); - delete_song(directory, song); - db_unlock(); - } - - modified = true; - } -} - -bool -update_song_file(struct directory *directory, - const char *name, const char *suffix, - const struct stat *st) -{ - const struct decoder_plugin *plugin = - decoder_plugin_from_suffix(suffix, false); - if (plugin == NULL) - return false; - - update_song_file2(directory, name, st, plugin); - return true; -} diff --git a/src/update_song.h b/src/update_song.h deleted file mode 100644 index cff63f576..000000000 --- a/src/update_song.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_SONG_H -#define MPD_UPDATE_SONG_H - -#include "check.h" - -#include <stdbool.h> -#include <sys/stat.h> - -struct directory; - -bool -update_song_file(struct directory *directory, - const char *name, const char *suffix, - const struct stat *st); - -#endif diff --git a/src/update_walk.c b/src/update_walk.c deleted file mode 100644 index 8554e8f3c..000000000 --- a/src/update_walk.c +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_walk.h" -#include "update_io.h" -#include "update_db.h" -#include "update_song.h" -#include "update_archive.h" -#include "database.h" -#include "db_lock.h" -#include "exclude.h" -#include "directory.h" -#include "song.h" -#include "playlist_vector.h" -#include "uri.h" -#include "mapper.h" -#include "path.h" -#include "playlist_list.h" -#include "conf.h" - -#include <glib.h> - -#include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <dirent.h> -#include <string.h> -#include <stdlib.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "update" - -bool walk_discard; -bool modified; - -#ifndef WIN32 - -enum { - DEFAULT_FOLLOW_INSIDE_SYMLINKS = true, - DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true, -}; - -static bool follow_inside_symlinks; -static bool follow_outside_symlinks; - -#endif - -void -update_walk_global_init(void) -{ -#ifndef WIN32 - follow_inside_symlinks = - config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS, - DEFAULT_FOLLOW_INSIDE_SYMLINKS); - - follow_outside_symlinks = - config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS, - DEFAULT_FOLLOW_OUTSIDE_SYMLINKS); -#endif -} - -void -update_walk_global_finish(void) -{ -} - -static void -directory_set_stat(struct directory *dir, const struct stat *st) -{ - dir->inode = st->st_ino; - dir->device = st->st_dev; - dir->have_stat = true; -} - -static void -remove_excluded_from_directory(struct directory *directory, - GSList *exclude_list) -{ - db_lock(); - - struct directory *child, *n; - directory_for_each_child_safe(child, n, directory) { - char *name_fs = utf8_to_fs_charset(directory_get_name(child)); - - if (exclude_list_check(exclude_list, name_fs)) { - delete_directory(child); - modified = true; - } - - g_free(name_fs); - } - - struct song *song, *ns; - directory_for_each_song_safe(song, ns, directory) { - assert(song->parent == directory); - - char *name_fs = utf8_to_fs_charset(song->uri); - if (exclude_list_check(exclude_list, name_fs)) { - delete_song(directory, song); - modified = true; - } - - g_free(name_fs); - } - - db_unlock(); -} - -static void -purge_deleted_from_directory(struct directory *directory) -{ - struct directory *child, *n; - directory_for_each_child_safe(child, n, directory) { - if (directory_exists(child)) - continue; - - db_lock(); - delete_directory(child); - db_unlock(); - - modified = true; - } - - struct song *song, *ns; - directory_for_each_song_safe(song, ns, directory) { - char *path; - struct stat st; - if ((path = map_song_fs(song)) == NULL || - stat(path, &st) < 0 || !S_ISREG(st.st_mode)) { - db_lock(); - delete_song(directory, song); - db_unlock(); - - modified = true; - } - - g_free(path); - } - - struct playlist_metadata *pm, *np; - directory_for_each_playlist_safe(pm, np, directory) { - if (!directory_child_is_regular(directory, pm->name)) { - db_lock(); - playlist_vector_remove(&directory->playlists, pm->name); - db_unlock(); - } - } -} - -#ifndef G_OS_WIN32 -static int -update_directory_stat(struct directory *directory) -{ - struct stat st; - if (stat_directory(directory, &st) < 0) - return -1; - - directory_set_stat(directory, &st); - return 0; -} -#endif - -static int -find_inode_ancestor(struct directory *parent, ino_t inode, dev_t device) -{ -#ifndef G_OS_WIN32 - while (parent) { - if (!parent->have_stat && update_directory_stat(parent) < 0) - return -1; - - if (parent->inode == inode && parent->device == device) { - g_debug("recursive directory found"); - return 1; - } - - parent = parent->parent; - } -#else - (void)parent; - (void)inode; - (void)device; -#endif - - return 0; -} - -static bool -update_playlist_file2(struct directory *directory, - const char *name, const char *suffix, - const struct stat *st) -{ - if (!playlist_suffix_supported(suffix)) - return false; - - db_lock(); - if (playlist_vector_update_or_add(&directory->playlists, name, - st->st_mtime)) - modified = true; - db_unlock(); - return true; -} - -static bool -update_regular_file(struct directory *directory, - const char *name, const struct stat *st) -{ - const char *suffix = uri_get_suffix(name); - if (suffix == NULL) - return false; - - return update_song_file(directory, name, suffix, st) || - update_archive_file(directory, name, suffix, st) || - update_playlist_file2(directory, name, suffix, st); -} - -static bool -update_directory(struct directory *directory, const struct stat *st); - -static void -update_directory_child(struct directory *directory, - const char *name, const struct stat *st) -{ - assert(strchr(name, '/') == NULL); - - if (S_ISREG(st->st_mode)) { - update_regular_file(directory, name, st); - } else if (S_ISDIR(st->st_mode)) { - if (find_inode_ancestor(directory, st->st_ino, st->st_dev)) - return; - - db_lock(); - struct directory *subdir = - directory_make_child(directory, name); - db_unlock(); - - assert(directory == subdir->parent); - - if (!update_directory(subdir, st)) { - db_lock(); - delete_directory(subdir); - db_unlock(); - } - } else { - g_debug("update: %s is not a directory, archive or music", name); - } -} - -/* we don't look at "." / ".." nor files with newlines in their name */ -G_GNUC_PURE -static bool skip_path(const char *path) -{ - return (path[0] == '.' && path[1] == 0) || - (path[0] == '.' && path[1] == '.' && path[2] == 0) || - strchr(path, '\n') != NULL; -} - -G_GNUC_PURE -static bool -skip_symlink(const struct directory *directory, const char *utf8_name) -{ -#ifndef WIN32 - char *path_fs = map_directory_child_fs(directory, utf8_name); - if (path_fs == NULL) - return true; - - char buffer[MPD_PATH_MAX]; - ssize_t length = readlink(path_fs, buffer, sizeof(buffer)); - g_free(path_fs); - if (length < 0) - /* don't skip if this is not a symlink */ - return errno != EINVAL; - - if ((size_t)length >= sizeof(buffer)) - /* skip symlinks when the buffer is too small for the - link target */ - return true; - - /* null-terminate the buffer, because readlink() will not */ - buffer[length] = 0; - - if (!follow_inside_symlinks && !follow_outside_symlinks) { - /* ignore all symlinks */ - return true; - } else if (follow_inside_symlinks && follow_outside_symlinks) { - /* consider all symlinks */ - return false; - } - - if (g_path_is_absolute(buffer)) { - /* if the symlink points to an absolute path, see if - that path is inside the music directory */ - const char *relative = map_to_relative_path(buffer); - return relative > buffer - ? !follow_inside_symlinks - : !follow_outside_symlinks; - } - - const char *p = buffer; - while (*p == '.') { - if (p[1] == '.' && G_IS_DIR_SEPARATOR(p[2])) { - /* "../" moves to parent directory */ - directory = directory->parent; - if (directory == NULL) { - /* we have moved outside the music - directory - skip this symlink - if such symlinks are not allowed */ - return !follow_outside_symlinks; - } - p += 3; - } else if (G_IS_DIR_SEPARATOR(p[1])) - /* eliminate "./" */ - p += 2; - else - break; - } - - /* we are still in the music directory, so this symlink points - to a song which is already in the database - skip according - to the follow_inside_symlinks param*/ - return !follow_inside_symlinks; -#else - /* no symlink checking on WIN32 */ - - (void)directory; - (void)utf8_name; - - return false; -#endif -} - -static bool -update_directory(struct directory *directory, const struct stat *st) -{ - assert(S_ISDIR(st->st_mode)); - - directory_set_stat(directory, st); - - char *path_fs = map_directory_fs(directory); - if (path_fs == NULL) - return false; - - DIR *dir = opendir(path_fs); - if (!dir) { - g_warning("Failed to open directory %s: %s", - path_fs, g_strerror(errno)); - g_free(path_fs); - return false; - } - - char *exclude_path_fs = g_build_filename(path_fs, ".mpdignore", NULL); - GSList *exclude_list = exclude_list_load(exclude_path_fs); - g_free(exclude_path_fs); - - g_free(path_fs); - - if (exclude_list != NULL) - remove_excluded_from_directory(directory, exclude_list); - - purge_deleted_from_directory(directory); - - struct dirent *ent; - while ((ent = readdir(dir))) { - char *utf8; - struct stat st2; - - if (skip_path(ent->d_name) || - exclude_list_check(exclude_list, ent->d_name)) - continue; - - utf8 = fs_charset_to_utf8(ent->d_name); - if (utf8 == NULL) - continue; - - if (skip_symlink(directory, utf8)) { - modified |= delete_name_in(directory, utf8); - g_free(utf8); - continue; - } - - if (stat_directory_child(directory, utf8, &st2) == 0) - update_directory_child(directory, utf8, &st2); - else - modified |= delete_name_in(directory, utf8); - - g_free(utf8); - } - - exclude_list_free(exclude_list); - - closedir(dir); - - directory->mtime = st->st_mtime; - - return true; -} - -static struct directory * -directory_make_child_checked(struct directory *parent, const char *name_utf8) -{ - db_lock(); - struct directory *directory = directory_get_child(parent, name_utf8); - db_unlock(); - - if (directory != NULL) - return directory; - - struct stat st; - if (stat_directory_child(parent, name_utf8, &st) < 0 || - find_inode_ancestor(parent, st.st_ino, st.st_dev)) - return NULL; - - if (skip_symlink(parent, name_utf8)) - return NULL; - - /* if we're adding directory paths, make sure to delete filenames - with potentially the same name */ - db_lock(); - struct song *conflicting = directory_get_song(parent, name_utf8); - if (conflicting) - delete_song(parent, conflicting); - - directory = directory_new_child(parent, name_utf8); - db_unlock(); - - directory_set_stat(directory, &st); - return directory; -} - -static struct directory * -directory_make_uri_parent_checked(const char *uri) -{ - struct directory *directory = db_get_root(); - char *duplicated = g_strdup(uri); - char *name_utf8 = duplicated, *slash; - - while ((slash = strchr(name_utf8, '/')) != NULL) { - *slash = 0; - - if (*name_utf8 == 0) - continue; - - directory = directory_make_child_checked(directory, name_utf8); - if (directory == NULL) - break; - - name_utf8 = slash + 1; - } - - g_free(duplicated); - return directory; -} - -static void -update_uri(const char *uri) -{ - struct directory *parent = directory_make_uri_parent_checked(uri); - if (parent == NULL) - return; - - char *name = g_path_get_basename(uri); - - struct stat st; - if (!skip_symlink(parent, name) && - stat_directory_child(parent, name, &st) == 0) - update_directory_child(parent, name, &st); - else - modified |= delete_name_in(parent, name); - - g_free(name); -} - -bool -update_walk(const char *path, bool discard) -{ - walk_discard = discard; - modified = false; - - if (path != NULL && !isRootDirectory(path)) { - update_uri(path); - } else { - struct directory *directory = db_get_root(); - struct stat st; - - if (stat_directory(directory, &st) == 0) - update_directory(directory, &st); - } - - return modified; -} diff --git a/src/update_walk.h b/src/update_walk.h deleted file mode 100644 index ab1e41fb9..000000000 --- a/src/update_walk.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_WALK_H -#define MPD_UPDATE_WALK_H - -#include "check.h" - -#include <stdbool.h> - -void -update_walk_global_init(void); - -void -update_walk_global_finish(void); - -/** - * Returns true if the database was modified. - */ -bool -update_walk(const char *path, bool discard); - -#endif @@ -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 000000000..d1c55c965 --- /dev/null +++ b/src/util/HugeAllocator.cxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "HugeAllocator.hxx" + +#ifdef __linux__ +#include <sys/mman.h> +#include <unistd.h> +#else +#include <stdlib.h> +#endif + +#ifdef __linux__ + +/** + * Round up the parameter, make it page-aligned. + */ +gcc_const +static size_t +AlignToPageSize(size_t size) +{ + static const long page_size = sysconf(_SC_PAGESIZE); + if (page_size > 0) + return size; + + size_t ps(page_size); + return (size + ps - 1) / ps * ps; +} + +void * +HugeAllocate(size_t size) +{ + size = AlignToPageSize(size); + + constexpr int flags = MAP_ANONYMOUS|MAP_PRIVATE|MAP_NORESERVE; + void *p = mmap(nullptr, size, + PROT_READ|PROT_WRITE, flags, + -1, 0); + if (p == (void *)-1) + return nullptr; + +#ifdef MADV_HUGEPAGE + /* allow the Linux kernel to use "Huge Pages", which reduces page + table overhead for this big chunk of data */ + madvise(p, size, MADV_HUGEPAGE); +#endif + +#ifdef MADV_DONTFORK + /* just in case MPD needs to fork, don't copy this allocation + to the child process, to reduce overhead */ + madvise(p, size, MADV_DONTFORK); +#endif + + return p; +} + +void +HugeFree(void *p, size_t size) +{ + munmap(p, AlignToPageSize(size)); +} + +void +HugeDiscard(void *p, size_t size) +{ +#ifdef MADV_DONTNEED + madvise(p, AlignToPageSize(size), MADV_DONTNEED); +#endif +} + +#endif diff --git a/src/util/HugeAllocator.hxx b/src/util/HugeAllocator.hxx new file mode 100644 index 000000000..01c92cd43 --- /dev/null +++ b/src/util/HugeAllocator.hxx @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_HUGE_ALLOCATOR_HXX +#define MPD_HUGE_ALLOCATOR_HXX + +#include "gcc.h" + +#include <stddef.h> + +#ifdef __linux__ + +/** + * Allocate a huge amount of memory. This will be done in a way that + * allows giving the memory back to the kernel as soon as we don't + * need it anymore. On the downside, this call is expensive. + */ +gcc_malloc +void * +HugeAllocate(size_t size); + +/** + * @param p an allocation returned by HugeAllocate() + * @param size the allocation's size as passed to HugeAllocate() + */ +void +HugeFree(void *p, size_t size); + +/** + * Discard any data stored in the allocation and give the memory back + * to the kernel. After returning, the allocation still exists and + * can be reused at any time, but its contents are undefined. + * + * @param p an allocation returned by HugeAllocate() + * @param size the allocation's size as passed to HugeAllocate() + */ +void +HugeDiscard(void *p, size_t size); + +#else + +/* not Linux: fall back to standard C calls */ + +#include <stdlib.h> + +gcc_malloc +static inline void * +HugeAllocate(size_t size) +{ + return malloc(size); +} + +static inline void +HugeFree(void *p, size_t) +{ + free(p); +} + +static inline void +HugeDiscard(void *, size_t) +{ +} + +#endif + +#endif diff --git a/src/util/LazyRandomEngine.cxx b/src/util/LazyRandomEngine.cxx new file mode 100644 index 000000000..0f90ebb2e --- /dev/null +++ b/src/util/LazyRandomEngine.cxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "LazyRandomEngine.hxx" + +void +LazyRandomEngine::AutoCreate() +{ + if (engine != nullptr) + return; + + std::random_device rd; + engine = new std::mt19937(rd()); +} diff --git a/src/util/LazyRandomEngine.hxx b/src/util/LazyRandomEngine.hxx new file mode 100644 index 000000000..8afe1d1c0 --- /dev/null +++ b/src/util/LazyRandomEngine.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_LAZY_RANDOM_ENGINE_HXX +#define MPD_LAZY_RANDOM_ENGINE_HXX + +#include "check.h" + +#include <random> + +#include <assert.h> + +/** + * A random engine that will be created and seeded on demand. + */ +class LazyRandomEngine { + std::mt19937 *engine; + +public: + typedef std::mt19937::result_type result_type; + + LazyRandomEngine():engine(nullptr) {} + ~LazyRandomEngine() { + delete engine; + } + + LazyRandomEngine(const LazyRandomEngine &other) = delete; + LazyRandomEngine &operator=(const LazyRandomEngine &other) = delete; + + /** + * Create and seed the real engine. Call this before any + * other method. + */ + void AutoCreate(); + + result_type min() const { + return engine->min(); + } + + result_type max() const { + return engine->max(); + } + + result_type operator()() { + assert(engine != nullptr); + + return engine->operator()(); + } +}; + +#endif diff --git a/src/util/Manual.hxx b/src/util/Manual.hxx new file mode 100644 index 000000000..ecd2c52b8 --- /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 000000000..a3659b8f4 --- /dev/null +++ b/src/util/PeakBuffer.cxx @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "PeakBuffer.hxx" +#include "HugeAllocator.hxx" +#include "fifo_buffer.h" + +#include <algorithm> + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +PeakBuffer::~PeakBuffer() +{ + if (normal_buffer != nullptr) + fifo_buffer_free(normal_buffer); + + if (peak_buffer != nullptr) + HugeFree(peak_buffer, peak_size); +} + +bool +PeakBuffer::IsEmpty() const +{ + return (normal_buffer == nullptr || + fifo_buffer_is_empty(normal_buffer)) && + (peak_buffer == nullptr || + fifo_buffer_is_empty(peak_buffer)); +} + +const void * +PeakBuffer::Read(size_t *length_r) const +{ + if (normal_buffer != nullptr) { + const void *p = fifo_buffer_read(normal_buffer, length_r); + if (p != nullptr) + return p; + } + + if (peak_buffer != nullptr) { + const void *p = fifo_buffer_read(peak_buffer, length_r); + if (p != nullptr) + return p; + } + + return nullptr; +} + +void +PeakBuffer::Consume(size_t length) +{ + if (normal_buffer != nullptr && !fifo_buffer_is_empty(normal_buffer)) { + fifo_buffer_consume(normal_buffer, length); + return; + } + + if (peak_buffer != nullptr && !fifo_buffer_is_empty(peak_buffer)) { + fifo_buffer_consume(peak_buffer, length); + if (fifo_buffer_is_empty(peak_buffer)) { + HugeFree(peak_buffer, peak_size); + peak_buffer = nullptr; + } + + return; + } +} + +static size_t +AppendTo(fifo_buffer *buffer, const void *data, size_t length) +{ + assert(data != nullptr); + assert(length > 0); + + size_t total = 0; + + do { + size_t max_length; + void *p = fifo_buffer_write(buffer, &max_length); + if (p == nullptr) + break; + + const size_t nbytes = std::min(length, max_length); + memcpy(p, data, nbytes); + fifo_buffer_append(buffer, nbytes); + + data = (const uint8_t *)data + nbytes; + length -= nbytes; + total += nbytes; + } while (length > 0); + + return total; +} + +bool +PeakBuffer::Append(const void *data, size_t length) +{ + if (length == 0) + return true; + + if (peak_buffer != nullptr && !fifo_buffer_is_empty(peak_buffer)) { + size_t nbytes = AppendTo(peak_buffer, data, length); + return nbytes == length; + } + + if (normal_buffer == nullptr) + normal_buffer = fifo_buffer_new(normal_size); + + size_t nbytes = AppendTo(normal_buffer, data, length); + if (nbytes > 0) { + data = (const uint8_t *)data + nbytes; + length -= nbytes; + if (length == 0) + return true; + } + + if (peak_buffer == nullptr && peak_size > 0) { + peak_buffer = (fifo_buffer *)HugeAllocate(peak_size); + if (peak_buffer == nullptr) + return false; + + fifo_buffer_init(peak_buffer, peak_size); + } + + nbytes = AppendTo(peak_buffer, data, length); + return nbytes == length; +} diff --git a/src/util/PeakBuffer.hxx b/src/util/PeakBuffer.hxx new file mode 100644 index 000000000..0fbba8d77 --- /dev/null +++ b/src/util/PeakBuffer.hxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PEAK_BUFFER_HXX +#define MPD_PEAK_BUFFER_HXX + +#include "gcc.h" + +#include <stddef.h> + +struct fifo_buffer; + +/** + * A FIFO-like buffer that will allocate more memory on demand to + * allow large peaks. This second buffer will be given back to the + * kernel when it has been consumed. + */ +class PeakBuffer { + size_t normal_size, peak_size; + + fifo_buffer *normal_buffer, *peak_buffer; + +public: + PeakBuffer(size_t _normal_size, size_t _peak_size) + :normal_size(_normal_size), peak_size(_peak_size), + normal_buffer(nullptr), peak_buffer(nullptr) {} + + PeakBuffer(PeakBuffer &&other) + :normal_size(other.normal_size), peak_size(other.peak_size), + normal_buffer(other.normal_buffer), + peak_buffer(other.peak_buffer) { + other.normal_buffer = nullptr; + other.peak_buffer = nullptr; + } + + ~PeakBuffer(); + + PeakBuffer(const PeakBuffer &) = delete; + PeakBuffer &operator=(const PeakBuffer &) = delete; + + gcc_pure + bool IsEmpty() const; + + const void *Read(size_t *length_r) const; + void Consume(size_t length); + + bool Append(const void *data, size_t length); +}; + +#endif diff --git a/src/util/RefCount.hxx b/src/util/RefCount.hxx new file mode 100644 index 000000000..9a45a585b --- /dev/null +++ b/src/util/RefCount.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * + * A very simple reference counting library. + */ + +#ifndef MPD_REFCOUNT_HXX +#define MPD_REFCOUNT_HXX + +#include <atomic> + +class RefCount { + std::atomic_uint n; + +public: + constexpr RefCount():n(1) {} + + void Increment() { + ++n; + } + + /** + * @return true if the number of references has been dropped to 0 + */ + bool Decrement() { + return --n == 0; + } +}; + +#endif diff --git a/src/util/SliceBuffer.hxx b/src/util/SliceBuffer.hxx new file mode 100644 index 000000000..c61f164f4 --- /dev/null +++ b/src/util/SliceBuffer.hxx @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SLICE_BUFFER_HXX +#define MPD_SLICE_BUFFER_HXX + +#include "HugeAllocator.hxx" +#include "gcc.h" + +#include <utility> +#include <new> + +#include <assert.h> +#include <stddef.h> + +/** + * This class pre-allocates a certain number of objects, and allows + * callers to allocate and free these objects ("slices"). + */ +template<typename T> +class SliceBuffer { + union Slice { + Slice *next; + + T value; + }; + + /** + * The maximum number of slices in this container. + */ + const unsigned n_max; + + /** + * The number of slices that are initialized. This is used to + * avoid page faulting on the new allocation, so the kernel + * does not need to reserve physical memory pages. + */ + unsigned n_initialized; + + /** + * The number of slices currently allocated. + */ + unsigned n_allocated; + + Slice *const data; + + /** + * Pointer to the first free element in the chain. + */ + Slice *available; + + size_t CalcAllocationSize() const { + return n_max * sizeof(Slice); + } + +public: + SliceBuffer(unsigned _count) + :n_max(_count), n_initialized(0), n_allocated(0), + data((Slice *)HugeAllocate(CalcAllocationSize())), + available(nullptr) { + assert(n_max > 0); + } + + ~SliceBuffer() { + /* all slices must be freed explicitly, and this + assertion checks for leaks */ + assert(n_allocated == 0); + + HugeFree(data, CalcAllocationSize()); + } + + SliceBuffer(const SliceBuffer &other) = delete; + SliceBuffer &operator=(const SliceBuffer &other) = delete; + + /** + * @return true if buffer allocation (by the constructor) has failed + */ + bool IsOOM() { + return data == nullptr; + } + + unsigned GetCapacity() const { + return n_max; + } + + bool IsEmpty() const { + return n_allocated == 0; + } + + bool IsFull() const { + return n_allocated == n_max; + } + + template<typename... Args> + T *Allocate(Args&&... args) { + assert(n_initialized <= n_max); + assert(n_allocated <= n_initialized); + + if (available == nullptr) { + if (n_initialized == n_max) { + /* out of (internal) memory, buffer is full */ + assert(n_allocated == n_max); + return nullptr; + } + + available = &data[n_initialized++]; + available->next = nullptr; + } + + /* allocate a slice */ + T *value = &available->value; + available = available->next; + ++n_allocated; + + /* construct the object */ + return ::new((void *)value) T(std::forward<Args>(args)...); + } + + void Free(T *value) { + assert(n_initialized <= n_max); + assert(n_allocated > 0); + assert(n_allocated <= n_initialized); + + Slice *slice = reinterpret_cast<Slice *>(value); + assert(slice >= data && slice < data + n_max); + + /* destruct the object */ + value->~T(); + + /* insert the slice in the "available" linked list */ + slice->next = available; + available = slice; + --n_allocated; + + /* give memory back to the kernel when the last slice + was freed */ + if (n_allocated == 0) { + HugeDiscard(data, CalcAllocationSize()); + n_initialized = 0; + available = nullptr; + } + } +}; + +#endif diff --git a/src/util/bit_reverse.h b/src/util/bit_reverse.h index e44693b1d..54cb789bb 100644 --- a/src/util/bit_reverse.h +++ b/src/util/bit_reverse.h @@ -20,12 +20,13 @@ #ifndef MPD_BIT_REVERSE_H #define MPD_BIT_REVERSE_H -#include <glib.h> +#include "gcc.h" + #include <stdint.h> extern const uint8_t bit_reverse_table[256]; -G_GNUC_CONST +gcc_const static inline uint8_t bit_reverse(uint8_t x) { diff --git a/src/util/fifo_buffer.c b/src/util/fifo_buffer.c new file mode 100644 index 000000000..162ddf946 --- /dev/null +++ b/src/util/fifo_buffer.c @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "fifo_buffer.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +struct fifo_buffer { + size_t size, start, end; + unsigned char buffer[sizeof(size_t)]; +}; + +struct fifo_buffer * +fifo_buffer_new(size_t size) +{ + struct fifo_buffer *buffer; + + assert(size > 0); + + buffer = (struct fifo_buffer *)g_malloc(sizeof(*buffer) - + sizeof(buffer->buffer) + size); + + buffer->size = size; + buffer->start = 0; + buffer->end = 0; + + return buffer; +} + +void +fifo_buffer_init(struct fifo_buffer *buffer, size_t size) +{ + buffer->size = size - (sizeof(*buffer) - sizeof(buffer->buffer)); + buffer->start = 0; + buffer->end = 0; +} + +static void +fifo_buffer_move(struct fifo_buffer *buffer); + +struct fifo_buffer * +fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size) +{ + if (buffer == NULL) + return new_size > 0 + ? fifo_buffer_new(new_size) + : NULL; + + /* existing data must fit in new size */ + assert(new_size >= buffer->end - buffer->start); + + if (new_size == 0) { + fifo_buffer_free(buffer); + return NULL; + } + + /* compress the buffer when we're shrinking and the tail of + the buffer would exceed the new size */ + if (buffer->end > new_size) + fifo_buffer_move(buffer); + + /* existing data must fit in new size: second check */ + assert(buffer->end <= new_size); + + buffer = g_realloc(buffer, sizeof(*buffer) - sizeof(buffer->buffer) + + new_size); + buffer->size = new_size; + return buffer; +} + +void +fifo_buffer_free(struct fifo_buffer *buffer) +{ + assert(buffer != NULL); + + g_free(buffer); +} + +size_t +fifo_buffer_capacity(const struct fifo_buffer *buffer) +{ + assert(buffer != NULL); + + return buffer->size; +} + +size_t +fifo_buffer_available(const struct fifo_buffer *buffer) +{ + assert(buffer != NULL); + + return buffer->end - buffer->start; +} + +void +fifo_buffer_clear(struct fifo_buffer *buffer) +{ + assert(buffer != NULL); + + buffer->start = 0; + buffer->end = 0; +} + +const void * +fifo_buffer_read(const struct fifo_buffer *buffer, size_t *length_r) +{ + assert(buffer != NULL); + assert(buffer->end >= buffer->start); + assert(length_r != NULL); + + if (buffer->start == buffer->end) + /* the buffer is empty */ + return NULL; + + *length_r = buffer->end - buffer->start; + return buffer->buffer + buffer->start; +} + +void +fifo_buffer_consume(struct fifo_buffer *buffer, size_t length) +{ + assert(buffer != NULL); + assert(buffer->end >= buffer->start); + assert(buffer->start + length <= buffer->end); + + buffer->start += length; +} + +/** + * Move data to the beginning of the buffer, to make room at the end. + */ +static void +fifo_buffer_move(struct fifo_buffer *buffer) +{ + if (buffer->start == 0) + return; + + if (buffer->end > buffer->start) + memmove(buffer->buffer, + buffer->buffer + buffer->start, + buffer->end - buffer->start); + + buffer->end -= buffer->start; + buffer->start = 0; +} + +void * +fifo_buffer_write(struct fifo_buffer *buffer, size_t *max_length_r) +{ + assert(buffer != NULL); + assert(buffer->end <= buffer->size); + assert(max_length_r != NULL); + + if (buffer->end == buffer->size) { + fifo_buffer_move(buffer); + if (buffer->end == buffer->size) + return NULL; + } else if (buffer->start > 0 && buffer->start == buffer->end) { + buffer->start = 0; + buffer->end = 0; + } + + *max_length_r = buffer->size - buffer->end; + return buffer->buffer + buffer->end; +} + +void +fifo_buffer_append(struct fifo_buffer *buffer, size_t length) +{ + assert(buffer != NULL); + assert(buffer->end >= buffer->start); + assert(buffer->end + length <= buffer->size); + + buffer->end += length; +} + +bool +fifo_buffer_is_empty(struct fifo_buffer *buffer) +{ + return buffer->start == buffer->end; +} + +bool +fifo_buffer_is_full(struct fifo_buffer *buffer) +{ + return buffer->start == 0 && buffer->end == buffer->size; +} diff --git a/src/util/fifo_buffer.h b/src/util/fifo_buffer.h new file mode 100644 index 000000000..ccea97d86 --- /dev/null +++ b/src/util/fifo_buffer.h @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * + * This is a general purpose FIFO buffer library. You may append data + * at the end, while another instance reads data from the beginning. + * It is optimized for zero-copy usage: you get pointers to the real + * buffer, where you may operate on. + * + * This library is not thread safe. + */ + +#ifndef MPD_FIFO_BUFFER_H +#define MPD_FIFO_BUFFER_H + +#include <stdbool.h> +#include <stddef.h> + +struct fifo_buffer; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Creates a new #fifo_buffer object. Free this object with + * fifo_buffer_free(). + * + * @param size the size of the buffer in bytes + * @return the new #fifo_buffer object + */ +struct fifo_buffer * +fifo_buffer_new(size_t size); + +void +fifo_buffer_init(struct fifo_buffer *buffer, size_t size); + +/** + * Change the capacity of the #fifo_buffer, while preserving existing + * data. + * + * @param buffer the old buffer, may be NULL + * @param new_size the requested new size of the #fifo_buffer; must + * not be smaller than the data which is stored in the old buffer + * @return the new buffer, may be NULL if the requested new size is 0 + */ +struct fifo_buffer * +fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size); + +/** + * Frees the resources consumed by this #fifo_buffer object. + */ +void +fifo_buffer_free(struct fifo_buffer *buffer); + +/** + * Return the capacity of the buffer, i.e. the size that was passed to + * fifo_buffer_new(). + */ +size_t +fifo_buffer_capacity(const struct fifo_buffer *buffer); + +/** + * Return the number of bytes currently stored in the buffer. + */ +size_t +fifo_buffer_available(const struct fifo_buffer *buffer); + +/** + * Clears all data currently in this #fifo_buffer object. This does + * not overwrite the actuall buffer; it just resets the internal + * pointers. + */ +void +fifo_buffer_clear(struct fifo_buffer *buffer); + +/** + * Reads from the beginning of the buffer. To remove consumed data + * from the buffer, call fifo_buffer_consume(). + * + * @param buffer the #fifo_buffer object + * @param length_r the maximum amount to read is returned here + * @return a pointer to the beginning of the buffer, or NULL if the + * buffer is empty + */ +const void * +fifo_buffer_read(const struct fifo_buffer *buffer, size_t *length_r); + +/** + * Marks data at the beginning of the buffer as "consumed". + * + * @param buffer the #fifo_buffer object + * @param length the number of bytes which were consumed + */ +void +fifo_buffer_consume(struct fifo_buffer *buffer, size_t length); + +/** + * Prepares writing to the buffer. This returns a buffer which you + * can write to. To commit the write operation, call + * fifo_buffer_append(). + * + * @param buffer the #fifo_buffer object + * @param max_length_r the maximum amount to write is returned here + * @return a pointer to the end of the buffer, or NULL if the buffer + * is already full + */ +void * +fifo_buffer_write(struct fifo_buffer *buffer, size_t *max_length_r); + +/** + * Commits the write operation initiated by fifo_buffer_write(). + * + * @param buffer the #fifo_buffer object + * @param length the number of bytes which were written + */ +void +fifo_buffer_append(struct fifo_buffer *buffer, size_t length); + +/** + * Checks if the buffer is empty. + */ +bool +fifo_buffer_is_empty(struct fifo_buffer *buffer); + +/** + * Checks if the buffer is full. + */ +bool +fifo_buffer_is_full(struct fifo_buffer *buffer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/growing_fifo.c b/src/util/growing_fifo.c index 88431f60e..88431f60e 100644 --- a/src/growing_fifo.c +++ b/src/util/growing_fifo.c diff --git a/src/growing_fifo.h b/src/util/growing_fifo.h index 723c3b3ff..723c3b3ff 100644 --- a/src/growing_fifo.h +++ b/src/util/growing_fifo.h diff --git a/src/util/list.h b/src/util/list.h index fdab47675..73d99befa 100644 --- a/src/util/list.h +++ b/src/util/list.h @@ -25,8 +25,6 @@ #ifndef _LINUX_LIST_H #define _LINUX_LIST_H -#include <glib.h> - #ifdef __clang__ /* allow typeof() */ #pragma GCC diagnostic ignored "-Wlanguage-extension-token" @@ -40,15 +38,15 @@ * */ #define container_of(ptr, type, member) \ - (&G_STRUCT_MEMBER(type, ptr, -G_STRUCT_OFFSET(type, member))) + ((type *)((uint8_t *)ptr - offsetof(type, member))) /* * These are non-NULL pointers that will result in page faults * under normal circumstances, used to verify that nobody uses * non-initialized list entries. */ -#define LIST_POISON1 ((void *) 0x00100100) -#define LIST_POISON2 ((void *) 0x00200200) +#define LIST_POISON1 ((struct list_head *)(void *) 0x00100100) +#define LIST_POISON2 ((struct list_head *)(void *) 0x00200200) /* * Simple doubly linked list implementation. @@ -82,46 +80,47 @@ static inline void INIT_LIST_HEAD(struct list_head *list) * the prev/next entries already! */ #ifndef CONFIG_DEBUG_LIST -static inline void __list_add(struct list_head *new, +static inline void __list_add(struct list_head *new_item, struct list_head *prev, struct list_head *next) { - next->prev = new; - new->next = next; - new->prev = prev; - prev->next = new; + next->prev = new_item; + new_item->next = next; + new_item->prev = prev; + prev->next = new_item; } #else -extern void __list_add(struct list_head *new, - struct list_head *prev, - struct list_head *next); +extern void __list_add(struct list_head *new_item, + struct list_head *prev, + struct list_head *next); #endif /** * list_add - add a new entry - * @new: new entry to be added + * @new_item: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ -static inline void list_add(struct list_head *new, struct list_head *head) +static inline void list_add(struct list_head *new_item, struct list_head *head) { - __list_add(new, head, head->next); + __list_add(new_item, head, head->next); } /** * list_add_tail - add a new entry - * @new: new entry to be added + * @new_item: new entry to be added * @head: list head to add it before * * Insert a new entry before the specified head. * This is useful for implementing queues. */ -static inline void list_add_tail(struct list_head *new, struct list_head *head) +static inline void +list_add_tail(struct list_head *new_item, struct list_head *head) { - __list_add(new, head->prev, head); + __list_add(new_item, head->prev, head); } /* @@ -163,23 +162,23 @@ extern void list_del(struct list_head *entry); /** * list_replace - replace old entry by new one * @old : the element to be replaced - * @new : the new element to insert + * @new_item : the new element to insert * * If @old was empty, it will be overwritten. */ static inline void list_replace(struct list_head *old, - struct list_head *new) + struct list_head *new_item) { - new->next = old->next; - new->next->prev = new; - new->prev = old->prev; - new->prev->next = new; + new_item->next = old->next; + new_item->next->prev = new_item; + new_item->prev = old->prev; + new_item->prev->next = new_item; } static inline void list_replace_init(struct list_head *old, - struct list_head *new) + struct list_head *new_item) { - list_replace(old, new); + list_replace(old, new_item); INIT_LIST_HEAD(old); } diff --git a/src/utils.c b/src/utils.c index a2de3212e..776813c4b 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 f8d6657f2..059d44fa3 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/src/volume.c b/src/volume.c deleted file mode 100644 index d3ce47dd4..000000000 --- a/src/volume.c +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "volume.h" -#include "conf.h" -#include "idle.h" -#include "pcm_volume.h" -#include "output_all.h" -#include "mixer_control.h" -#include "mixer_all.h" -#include "mixer_type.h" -#include "event_pipe.h" - -#include <glib.h> - -#include <assert.h> -#include <math.h> -#include <string.h> -#include <stdlib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "volume" - -#define SW_VOLUME_STATE "sw_volume: " - -static unsigned volume_software_set = 100; - -/** the cached hardware mixer value; invalid if negative */ -static int last_hardware_volume = -1; -/** the age of #last_hardware_volume */ -static GTimer *hardware_volume_timer; - -/** - * Handler for #PIPE_EVENT_MIXER. - */ -static void -mixer_event_callback(void) -{ - /* flush the hardware volume cache */ - last_hardware_volume = -1; - - /* notify clients */ - idle_add(IDLE_MIXER); -} - -void volume_finish(void) -{ - g_timer_destroy(hardware_volume_timer); -} - -void volume_init(void) -{ - hardware_volume_timer = g_timer_new(); - - event_pipe_register(PIPE_EVENT_MIXER, mixer_event_callback); -} - -int volume_level_get(void) -{ - assert(hardware_volume_timer != NULL); - - if (last_hardware_volume >= 0 && - g_timer_elapsed(hardware_volume_timer, NULL) < 1.0) - /* throttle access to hardware mixers */ - return last_hardware_volume; - - last_hardware_volume = mixer_all_get_volume(); - g_timer_start(hardware_volume_timer); - return last_hardware_volume; -} - -static bool software_volume_change(unsigned volume) -{ - assert(volume <= 100); - - volume_software_set = volume; - mixer_all_set_software_volume(volume); - - return true; -} - -static bool hardware_volume_change(unsigned volume) -{ - /* reset the cache */ - last_hardware_volume = -1; - - return mixer_all_set_volume(volume); -} - -bool volume_level_change(unsigned volume) -{ - assert(volume <= 100); - - volume_software_set = volume; - - idle_add(IDLE_MIXER); - - return hardware_volume_change(volume); -} - -bool -read_sw_volume_state(const char *line) -{ - char *end = NULL; - long int sv; - - if (!g_str_has_prefix(line, SW_VOLUME_STATE)) - return false; - - line += sizeof(SW_VOLUME_STATE) - 1; - sv = strtol(line, &end, 10); - if (*end == 0 && sv >= 0 && sv <= 100) - software_volume_change(sv); - else - g_warning("Can't parse software volume: %s\n", line); - return true; -} - -void save_sw_volume_state(FILE *fp) -{ - fprintf(fp, SW_VOLUME_STATE "%u\n", volume_software_set); -} - -unsigned -sw_volume_state_get_hash(void) -{ - return volume_software_set; -} diff --git a/src/volume.h b/src/volume.h deleted file mode 100644 index b08899a84..000000000 --- a/src/volume.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_VOLUME_H -#define MPD_VOLUME_H - -#include <stdbool.h> -#include <stdio.h> - -void volume_init(void); - -void volume_finish(void); - -int volume_level_get(void); - -bool volume_level_change(unsigned volume); - -bool -read_sw_volume_state(const char *line); - -void save_sw_volume_state(FILE *fp); - -/** - * Generates a hash number for the current state of the software - * volume control. This is used by timer_save_state_file() to - * determine whether the state has changed and the state file should - * be saved. - */ -unsigned -sw_volume_state_get_hash(void); - -#endif diff --git a/src/zeroconf-avahi.c b/src/zeroconf-avahi.c deleted file mode 100644 index f2cc5359b..000000000 --- a/src/zeroconf-avahi.c +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "zeroconf-internal.h" -#include "listen.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <avahi-client/client.h> -#include <avahi-client/publish.h> - -#include <avahi-common/alternative.h> -#include <avahi-common/domain.h> -#include <avahi-common/malloc.h> -#include <avahi-common/error.h> - -#include <avahi-glib/glib-watch.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "avahi" - -static char *avahiName; -static int avahiRunning; -static AvahiGLibPoll *avahi_glib_poll; -static const AvahiPoll *avahi_poll; -static AvahiClient *avahiClient; -static AvahiEntryGroup *avahiGroup; - -static void avahiRegisterService(AvahiClient * c); - -/* Callback when the EntryGroup changes state */ -static void avahiGroupCallback(AvahiEntryGroup * g, - AvahiEntryGroupState state, - G_GNUC_UNUSED void *userdata) -{ - char *n; - assert(g); - - g_debug("Service group changed to state %d", state); - - switch (state) { - case AVAHI_ENTRY_GROUP_ESTABLISHED: - /* The entry group has been established successfully */ - g_message("Service '%s' successfully established.", - avahiName); - break; - - case AVAHI_ENTRY_GROUP_COLLISION: - /* A service name collision happened. Let's pick a new name */ - n = avahi_alternative_service_name(avahiName); - avahi_free(avahiName); - avahiName = n; - - g_message("Service name collision, renaming service to '%s'", - avahiName); - - /* And recreate the services */ - avahiRegisterService(avahi_entry_group_get_client(g)); - break; - - case AVAHI_ENTRY_GROUP_FAILURE: - g_warning("Entry group failure: %s", - avahi_strerror(avahi_client_errno - (avahi_entry_group_get_client(g)))); - /* Some kind of failure happened while we were registering our services */ - avahiRunning = 0; - break; - - case AVAHI_ENTRY_GROUP_UNCOMMITED: - g_debug("Service group is UNCOMMITED"); - break; - case AVAHI_ENTRY_GROUP_REGISTERING: - g_debug("Service group is REGISTERING"); - } -} - -/* Registers a new service with avahi */ -static void avahiRegisterService(AvahiClient * c) -{ - int ret; - assert(c); - g_debug("Registering service %s/%s", SERVICE_TYPE, avahiName); - - /* If this is the first time we're called, - * let's create a new entry group */ - if (!avahiGroup) { - avahiGroup = avahi_entry_group_new(c, avahiGroupCallback, NULL); - if (!avahiGroup) { - g_warning("Failed to create avahi EntryGroup: %s", - avahi_strerror(avahi_client_errno(c))); - goto fail; - } - } - - /* Add the service */ - /* TODO: This currently binds to ALL interfaces. - * We could maybe add a service per actual bound interface, - * if that's better. */ - ret = avahi_entry_group_add_service(avahiGroup, - AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, - 0, avahiName, SERVICE_TYPE, NULL, - NULL, listen_port, NULL); - if (ret < 0) { - g_warning("Failed to add service %s: %s", SERVICE_TYPE, - avahi_strerror(ret)); - goto fail; - } - - /* Tell the server to register the service group */ - ret = avahi_entry_group_commit(avahiGroup); - if (ret < 0) { - g_warning("Failed to commit service group: %s", - avahi_strerror(ret)); - goto fail; - } - return; - -fail: - avahiRunning = 0; -} - -/* Callback when avahi changes state */ -static void avahiClientCallback(AvahiClient * c, AvahiClientState state, - G_GNUC_UNUSED void *userdata) -{ - int reason; - assert(c); - - /* Called whenever the client or server state changes */ - g_debug("Client changed to state %d", state); - - switch (state) { - case AVAHI_CLIENT_S_RUNNING: - g_debug("Client is RUNNING"); - - /* The server has startup successfully and registered its host - * name on the network, so it's time to create our services */ - if (!avahiGroup) - avahiRegisterService(c); - break; - - case AVAHI_CLIENT_FAILURE: - reason = avahi_client_errno(c); - if (reason == AVAHI_ERR_DISCONNECTED) { - g_message("Client Disconnected, will reconnect shortly"); - if (avahiGroup) { - avahi_entry_group_free(avahiGroup); - avahiGroup = NULL; - } - if (avahiClient) - avahi_client_free(avahiClient); - avahiClient = - avahi_client_new(avahi_poll, - AVAHI_CLIENT_NO_FAIL, - avahiClientCallback, NULL, - &reason); - if (!avahiClient) { - g_warning("Could not reconnect: %s", - avahi_strerror(reason)); - avahiRunning = 0; - } - } else { - g_warning("Client failure: %s (terminal)", - avahi_strerror(reason)); - avahiRunning = 0; - } - break; - - case AVAHI_CLIENT_S_COLLISION: - g_debug("Client is COLLISION"); - /* Let's drop our registered services. When the server is back - * in AVAHI_SERVER_RUNNING state we will register them - * again with the new host name. */ - if (avahiGroup) { - g_debug("Resetting group"); - avahi_entry_group_reset(avahiGroup); - } - - case AVAHI_CLIENT_S_REGISTERING: - g_debug("Client is REGISTERING"); - /* The server records are now being established. This - * might be caused by a host name change. We need to wait - * for our own records to register until the host name is - * properly esatblished. */ - - if (avahiGroup) { - g_debug("Resetting group"); - avahi_entry_group_reset(avahiGroup); - } - - break; - - case AVAHI_CLIENT_CONNECTING: - g_debug("Client is CONNECTING"); - } -} - -void init_avahi(const char *serviceName) -{ - int error; - g_debug("Initializing interface"); - - if (!avahi_is_valid_service_name(serviceName)) - MPD_ERROR("Invalid zeroconf_name \"%s\"", serviceName); - - avahiName = avahi_strdup(serviceName); - - avahiRunning = 1; - - avahi_glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT); - avahi_poll = avahi_glib_poll_get(avahi_glib_poll); - - avahiClient = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL, - avahiClientCallback, NULL, &error); - - if (!avahiClient) { - g_warning("Failed to create client: %s", - avahi_strerror(error)); - goto fail; - } - - return; - -fail: - avahi_finish(); -} - -void avahi_finish(void) -{ - g_debug("Shutting down interface"); - - if (avahiGroup) { - avahi_entry_group_free(avahiGroup); - avahiGroup = NULL; - } - - if (avahiClient) { - avahi_client_free(avahiClient); - avahiClient = NULL; - } - - if (avahi_glib_poll != NULL) { - avahi_glib_poll_free(avahi_glib_poll); - avahi_glib_poll = NULL; - } - - avahi_free(avahiName); - avahiName = NULL; -} diff --git a/src/zeroconf-bonjour.c b/src/zeroconf-bonjour.c deleted file mode 100644 index 0f216aade..000000000 --- a/src/zeroconf-bonjour.c +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "zeroconf-internal.h" -#include "listen.h" - -#include <glib.h> - -#include <dns_sd.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "bonjour" - -static DNSServiceRef dnsReference; -static GIOChannel *bonjour_channel; - -static void -dnsRegisterCallback(G_GNUC_UNUSED DNSServiceRef sdRef, - G_GNUC_UNUSED DNSServiceFlags flags, - DNSServiceErrorType errorCode, const char *name, - G_GNUC_UNUSED const char *regtype, - G_GNUC_UNUSED const char *domain, - G_GNUC_UNUSED void *context) -{ - if (errorCode != kDNSServiceErr_NoError) { - g_warning("Failed to register zeroconf service."); - - bonjour_finish(); - } else { - g_debug("Registered zeroconf service with name '%s'", name); - } -} - -static gboolean -bonjour_channel_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - G_GNUC_UNUSED gpointer data) -{ - DNSServiceProcessResult(dnsReference); - - return dnsReference != NULL; -} - -void init_zeroconf_osx(const char *serviceName) -{ - DNSServiceErrorType error = DNSServiceRegister(&dnsReference, - 0, 0, serviceName, - SERVICE_TYPE, NULL, NULL, - g_htons(listen_port), 0, - NULL, - dnsRegisterCallback, - NULL); - - if (error != kDNSServiceErr_NoError) { - g_warning("Failed to register zeroconf service."); - - if (dnsReference) { - DNSServiceRefDeallocate(dnsReference); - dnsReference = NULL; - } - return; - } - - bonjour_channel = g_io_channel_unix_new(DNSServiceRefSockFD(dnsReference)); - g_io_add_watch(bonjour_channel, G_IO_IN, bonjour_channel_event, NULL); -} - -void bonjour_finish(void) -{ - if (bonjour_channel != NULL) { - g_io_channel_unref(bonjour_channel); - bonjour_channel = NULL; - } - - if (dnsReference != NULL) { - DNSServiceRefDeallocate(dnsReference); - dnsReference = NULL; - g_debug("Deregistered Zeroconf service."); - } -} diff --git a/src/zeroconf-internal.h b/src/zeroconf-internal.h deleted file mode 100644 index 983e5c556..000000000 --- a/src/zeroconf-internal.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef ZEROCONF_INTERNAL_H -#define ZEROCONF_INTERNAL_H - -/* The dns-sd service type qualifier to publish */ -#define SERVICE_TYPE "_mpd._tcp" - -void init_avahi(const char *service_name); - -void avahi_finish(void); - -void init_zeroconf_osx(const char *service_name); - -void bonjour_finish(void); - -#endif diff --git a/src/zeroconf.c b/src/zeroconf.c deleted file mode 100644 index 4a399e4a2..000000000 --- a/src/zeroconf.c +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "zeroconf.h" -#include "zeroconf-internal.h" -#include "conf.h" -#include "listen.h" - -#include <glib.h> - -/* The default service name to publish - * (overridden by 'zeroconf_name' config parameter) - */ -#define SERVICE_NAME "Music Player" - -#define DEFAULT_ZEROCONF_ENABLED 1 - -static int zeroconfEnabled; - -void initZeroconf(void) -{ - const char *serviceName; - - zeroconfEnabled = config_get_bool(CONF_ZEROCONF_ENABLED, - DEFAULT_ZEROCONF_ENABLED); - if (!zeroconfEnabled) - return; - - if (listen_port <= 0) { - g_warning("No global port, disabling zeroconf"); - zeroconfEnabled = false; - return; - } - - serviceName = config_get_string(CONF_ZEROCONF_NAME, SERVICE_NAME); - -#ifdef HAVE_AVAHI - init_avahi(serviceName); -#endif - -#ifdef HAVE_BONJOUR - init_zeroconf_osx(serviceName); -#endif -} - -void finishZeroconf(void) -{ - if (!zeroconfEnabled) - return; - -#ifdef HAVE_AVAHI - avahi_finish(); -#endif /* HAVE_AVAHI */ - -#ifdef HAVE_BONJOUR - bonjour_finish(); -#endif -} diff --git a/src/zeroconf.h b/src/zeroconf.h deleted file mode 100644 index 8e33a3d89..000000000 --- a/src/zeroconf.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ZEROCONF_H -#define MPD_ZEROCONF_H - -#include "check.h" - -#ifdef HAVE_ZEROCONF - -void initZeroconf(void); -void finishZeroconf(void); - -#else /* ! HAVE_ZEROCONF */ - -static void initZeroconf(void) { } -static void finishZeroconf(void) { } - -#endif /* ! HAVE_ZEROCONF */ - -#endif diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx new file mode 100644 index 000000000..ba0e74e43 --- /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 000000000..9c2431bf2 --- /dev/null +++ b/test/FakeReplayGainConfig.cxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "replay_gain_config.h" + +float replay_gain_preamp = 1.0; +float replay_gain_missing_preamp = 1.0; +bool replay_gain_limit = true; diff --git a/test/FakeSong.cxx b/test/FakeSong.cxx new file mode 100644 index 000000000..927a07652 --- /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.c deleted file mode 100644 index 84ac69045..000000000 --- a/test/dump_playlist.c +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "io_thread.h" -#include "input_init.h" -#include "input_stream.h" -#include "tag_pool.h" -#include "tag_save.h" -#include "conf.h" -#include "song.h" -#include "decoder_api.h" -#include "decoder_list.h" -#include "playlist_list.h" -#include "playlist_plugin.h" - -#include <glib.h> - -#include <unistd.h> -#include <stdlib.h> - -static void -my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - if (log_domain != NULL) - g_printerr("%s: %s\n", log_domain, message); - else - g_printerr("%s\n", message); -} - -void -decoder_initialized(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED const struct audio_format *audio_format, - G_GNUC_UNUSED bool seekable, - G_GNUC_UNUSED float total_time) -{ -} - -enum decoder_command -decoder_get_command(G_GNUC_UNUSED struct decoder *decoder) -{ - return DECODE_COMMAND_NONE; -} - -void -decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder) -{ -} - -double -decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder) -{ - return 1.0; -} - -void -decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder) -{ -} - -size_t -decoder_read(G_GNUC_UNUSED struct decoder *decoder, - struct input_stream *is, - void *buffer, size_t length) -{ - return input_stream_lock_read(is, buffer, length, NULL); -} - -void -decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED double t) -{ -} - -enum decoder_command -decoder_data(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED struct input_stream *is, - const void *data, size_t datalen, - G_GNUC_UNUSED uint16_t kbit_rate) -{ - G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen); - return DECODE_COMMAND_NONE; -} - -enum decoder_command -decoder_tag(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED const struct tag *tag) -{ - return DECODE_COMMAND_NONE; -} - -float -decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, - const struct replay_gain_info *replay_gain_info) -{ - const struct replay_gain_tuple *tuple = - &replay_gain_info->tuples[REPLAY_GAIN_ALBUM]; - if (replay_gain_tuple_defined(tuple)) - g_printerr("replay_gain[album]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); - - tuple = &replay_gain_info->tuples[REPLAY_GAIN_TRACK]; - if (replay_gain_tuple_defined(tuple)) - g_printerr("replay_gain[track]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); - - return 0.0; -} - -void -decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED float replay_gain_db, - char *mixramp_start, char *mixramp_end) -{ - g_free(mixramp_start); - g_free(mixramp_end); -} - -int main(int argc, char **argv) -{ - const char *uri; - struct input_stream *is = NULL; - bool success; - GError *error = NULL; - struct playlist_provider *playlist; - struct song *song; - - if (argc != 3) { - g_printerr("Usage: dump_playlist CONFIG URI\n"); - return 1; - } - - uri = argv[2]; - - /* initialize GLib */ - - g_thread_init(NULL); - g_log_set_default_handler(my_log_func, NULL); - - /* initialize MPD */ - - tag_pool_init(); - config_global_init(); - success = config_read_file(argv[1], &error); - if (!success) { - g_printerr("%s\n", error->message); - g_error_free(error); - return 1; - } - - io_thread_init(); - if (!io_thread_start(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - if (!input_stream_global_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return 2; - } - - playlist_list_global_init(); - decoder_plugin_init_all(); - - /* open the playlist */ - - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); - - playlist = playlist_list_open_uri(uri, mutex, cond); - if (playlist == NULL) { - /* open the stream and wait until it becomes ready */ - - is = input_stream_open(uri, mutex, cond, &error); - if (is == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } else - g_printerr("input_stream_open() failed\n"); - return 2; - } - - input_stream_lock_wait_ready(is); - - /* open the playlist */ - - playlist = playlist_list_open_stream(is, uri); - if (playlist == NULL) { - input_stream_close(is); - g_printerr("Failed to open playlist\n"); - return 2; - } - } - - /* dump the playlist */ - - while ((song = playlist_plugin_read(playlist)) != NULL) { - g_print("%s\n", song->uri); - - if (song->end_ms > 0) - g_print("range: %u:%02u..%u:%02u\n", - song->start_ms / 60000, - (song->start_ms / 1000) % 60, - song->end_ms / 60000, - (song->end_ms / 1000) % 60); - else if (song->start_ms > 0) - g_print("range: %u:%02u..\n", - song->start_ms / 60000, - (song->start_ms / 1000) % 60); - - if (song->tag != NULL) - tag_save(stdout, song->tag); - - song_free(song); - } - - /* deinitialize everything */ - - playlist_plugin_close(playlist); - if (is != NULL) - input_stream_close(is); - - g_cond_free(cond); - g_mutex_free(mutex); - - decoder_plugin_deinit_all(); - playlist_list_global_finish(); - input_stream_global_finish(); - io_thread_deinit(); - config_global_finish(); - tag_pool_deinit(); - - return 0; -} diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx new file mode 100644 index 000000000..6e1f4858e --- /dev/null +++ b/test/dump_playlist.cxx @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TagSave.hxx" +#include "song.h" +#include "Directory.hxx" +#include "input_stream.h" +#include "conf.h" +#include "decoder_api.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) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +void +decoder_initialized(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED const struct audio_format *audio_format, + G_GNUC_UNUSED bool seekable, + G_GNUC_UNUSED float total_time) +{ +} + +enum decoder_command +decoder_get_command(G_GNUC_UNUSED struct decoder *decoder) +{ + return DECODE_COMMAND_NONE; +} + +void +decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder) +{ +} + +double +decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder) +{ + return 1.0; +} + +void +decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder) +{ +} + +size_t +decoder_read(G_GNUC_UNUSED struct decoder *decoder, + struct input_stream *is, + void *buffer, size_t length) +{ + return input_stream_lock_read(is, buffer, length, NULL); +} + +void +decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED double t) +{ +} + +enum decoder_command +decoder_data(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED struct input_stream *is, + const void *data, size_t datalen, + G_GNUC_UNUSED uint16_t kbit_rate) +{ + G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen); + return DECODE_COMMAND_NONE; +} + +enum decoder_command +decoder_tag(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED struct input_stream *is, + G_GNUC_UNUSED const struct tag *tag) +{ + return DECODE_COMMAND_NONE; +} + +void +decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, + const struct replay_gain_info *replay_gain_info) +{ + const struct replay_gain_tuple *tuple = + &replay_gain_info->tuples[REPLAY_GAIN_ALBUM]; + if (replay_gain_tuple_defined(tuple)) + g_printerr("replay_gain[album]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); + + tuple = &replay_gain_info->tuples[REPLAY_GAIN_TRACK]; + if (replay_gain_tuple_defined(tuple)) + g_printerr("replay_gain[track]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); +} + +void +decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, + char *mixramp_start, char *mixramp_end) +{ + g_free(mixramp_start); + g_free(mixramp_end); +} + +int main(int argc, char **argv) +{ + const char *uri; + struct input_stream *is = NULL; + GError *error = NULL; + struct playlist_provider *playlist; + struct song *song; + + if (argc != 3) { + g_printerr("Usage: dump_playlist CONFIG URI\n"); + return 1; + } + + const Path config_path = Path::FromFS(argv[1]); + uri = argv[2]; + + /* initialize GLib */ + + g_thread_init(NULL); + g_log_set_default_handler(my_log_func, NULL); + + /* initialize MPD */ + + config_global_init(); + if (!ReadConfigFile(config_path, &error)) { + g_printerr("%s\n", error->message); + g_error_free(error); + return 1; + } + + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + if (!input_stream_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + + playlist_list_global_init(); + decoder_plugin_init_all(); + + /* open the playlist */ + + Mutex mutex; + Cond cond; + + playlist = playlist_list_open_uri(uri, mutex, cond); + if (playlist == NULL) { + /* open the stream and wait until it becomes ready */ + + is = input_stream_open(uri, mutex, cond, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } else + g_printerr("input_stream_open() failed\n"); + return 2; + } + + input_stream_lock_wait_ready(is); + + /* open the playlist */ + + playlist = playlist_list_open_stream(is, uri); + if (playlist == NULL) { + input_stream_close(is); + g_printerr("Failed to open playlist\n"); + return 2; + } + } + + /* dump the playlist */ + + while ((song = playlist_plugin_read(playlist)) != NULL) { + g_print("%s\n", song->uri); + + if (song->end_ms > 0) + g_print("range: %u:%02u..%u:%02u\n", + song->start_ms / 60000, + (song->start_ms / 1000) % 60, + song->end_ms / 60000, + (song->end_ms / 1000) % 60); + else if (song->start_ms > 0) + g_print("range: %u:%02u..\n", + song->start_ms / 60000, + (song->start_ms / 1000) % 60); + + if (song->tag != NULL) + tag_save(stdout, song->tag); + + song_free(song); + } + + /* deinitialize everything */ + + playlist_plugin_close(playlist); + if (is != NULL) + input_stream_close(is); + + decoder_plugin_deinit_all(); + playlist_list_global_finish(); + input_stream_global_finish(); + io_thread_deinit(); + config_global_finish(); + + return 0; +} diff --git a/test/dump_rva2.c b/test/dump_rva2.c index 4a726518a..6e978c424 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.c deleted file mode 100644 index f14371441..000000000 --- a/test/dump_text_file.c +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "io_thread.h" -#include "input_init.h" -#include "input_stream.h" -#include "text_input_stream.h" -#include "tag_pool.h" -#include "conf.h" -#include "stdbin.h" - -#ifdef ENABLE_ARCHIVE -#include "archive_list.h" -#endif - -#include <glib.h> - -#include <unistd.h> -#include <stdio.h> -#include <stdlib.h> - -static void -my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - if (log_domain != NULL) - g_printerr("%s: %s\n", log_domain, message); - else - g_printerr("%s\n", message); -} - -static void -dump_text_file(struct text_input_stream *is) -{ - const char *line; - while ((line = text_input_stream_read(is)) != NULL) - printf("'%s'\n", line); -} - -static int -dump_input_stream(struct input_stream *is) -{ - GError *error = NULL; - - input_stream_lock(is); - - /* wait until the stream becomes ready */ - - input_stream_wait_ready(is); - - if (!input_stream_check(is, &error)) { - g_warning("%s", error->message); - g_error_free(error); - input_stream_unlock(is); - return EXIT_FAILURE; - } - - /* read data and tags from the stream */ - - input_stream_unlock(is); - - struct text_input_stream *tis = text_input_stream_new(is); - dump_text_file(tis); - text_input_stream_free(tis); - - input_stream_lock(is); - - if (!input_stream_check(is, &error)) { - g_warning("%s", error->message); - g_error_free(error); - input_stream_unlock(is); - return EXIT_FAILURE; - } - - input_stream_unlock(is); - - return 0; -} - -int main(int argc, char **argv) -{ - GError *error = NULL; - struct input_stream *is; - int ret; - - if (argc != 2) { - g_printerr("Usage: run_input URI\n"); - return 1; - } - - /* initialize GLib */ - - g_thread_init(NULL); - g_log_set_default_handler(my_log_func, NULL); - - /* initialize MPD */ - - tag_pool_init(); - config_global_init(); - - io_thread_init(); - if (!io_thread_start(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - -#ifdef ENABLE_ARCHIVE - archive_plugin_init_all(); -#endif - - if (!input_stream_global_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return 2; - } - - /* open the stream and dump it */ - - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); - - is = input_stream_open(argv[1], mutex, cond, &error); - if (is != NULL) { - ret = dump_input_stream(is); - input_stream_close(is); - } else { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } else - g_printerr("input_stream_open() failed\n"); - ret = 2; - } - - g_cond_free(cond); - g_mutex_free(mutex); - - /* deinitialize everything */ - - input_stream_global_finish(); - -#ifdef ENABLE_ARCHIVE - archive_plugin_deinit_all(); -#endif - - io_thread_deinit(); - - config_global_finish(); - tag_pool_deinit(); - - return ret; -} diff --git a/test/dump_text_file.cxx b/test/dump_text_file.cxx new file mode 100644 index 000000000..93b0d0185 --- /dev/null +++ b/test/dump_text_file.cxx @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "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 "ArchiveList.hxx" +#endif + +#include <glib.h> + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> + +static void +my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, + const gchar *message, G_GNUC_UNUSED gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +static void +dump_text_file(struct text_input_stream *is) +{ + const char *line; + while ((line = text_input_stream_read(is)) != NULL) + printf("'%s'\n", line); +} + +static int +dump_input_stream(struct input_stream *is) +{ + GError *error = NULL; + + input_stream_lock(is); + + /* wait until the stream becomes ready */ + + input_stream_wait_ready(is); + + if (!input_stream_check(is, &error)) { + g_warning("%s", error->message); + g_error_free(error); + input_stream_unlock(is); + return EXIT_FAILURE; + } + + /* read data and tags from the stream */ + + input_stream_unlock(is); + + struct text_input_stream *tis = text_input_stream_new(is); + dump_text_file(tis); + text_input_stream_free(tis); + + input_stream_lock(is); + + if (!input_stream_check(is, &error)) { + g_warning("%s", error->message); + g_error_free(error); + input_stream_unlock(is); + return EXIT_FAILURE; + } + + input_stream_unlock(is); + + return 0; +} + +int main(int argc, char **argv) +{ + GError *error = NULL; + struct input_stream *is; + int ret; + + if (argc != 2) { + g_printerr("Usage: run_input URI\n"); + return 1; + } + + /* initialize GLib */ + + g_thread_init(NULL); + g_log_set_default_handler(my_log_func, NULL); + + /* initialize MPD */ + + config_global_init(); + + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + +#ifdef ENABLE_ARCHIVE + archive_plugin_init_all(); +#endif + + if (!input_stream_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + + /* open the stream and dump it */ + + Mutex mutex; + Cond cond; + + is = input_stream_open(argv[1], mutex, cond, &error); + if (is != NULL) { + ret = dump_input_stream(is); + input_stream_close(is); + } else { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } else + g_printerr("input_stream_open() failed\n"); + ret = 2; + } + + /* deinitialize everything */ + + input_stream_global_finish(); + +#ifdef ENABLE_ARCHIVE + archive_plugin_deinit_all(); +#endif + + io_thread_deinit(); + + config_global_finish(); + + return ret; +} diff --git a/test/read_conf.c b/test/read_conf.c deleted file mode 100644 index 4f6005c6f..000000000 --- a/test/read_conf.c +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "conf.h" - -#include <glib.h> - -#include <assert.h> - -static void -my_log_func(G_GNUC_UNUSED const gchar *log_domain, - GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - if (log_level > G_LOG_LEVEL_WARNING) - return; - - g_printerr("%s\n", message); -} - -int main(int argc, char **argv) -{ - if (argc != 3) { - g_printerr("Usage: read_conf FILE SETTING\n"); - return 1; - } - - const char *path = argv[1]; - const char *name = argv[2]; - - g_log_set_default_handler(my_log_func, NULL); - - config_global_init(); - - GError *error = NULL; - bool success = config_read_file(path, &error); - if (!success) { - g_printerr("%s:", error->message); - g_error_free(error); - return 1; - } - - const char *value = config_get_string(name, NULL); - int ret; - if (value != NULL) { - g_print("%s\n", value); - ret = 0; - } else { - g_printerr("No such setting: %s\n", name); - ret = 2; - } - - config_global_finish(); - return ret; -} diff --git a/test/read_conf.cxx b/test/read_conf.cxx new file mode 100644 index 000000000..8759398da --- /dev/null +++ b/test/read_conf.cxx @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "conf.h" +#include "fs/Path.hxx" + +#include <glib.h> + +#include <assert.h> + +static void +my_log_func(G_GNUC_UNUSED const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, G_GNUC_UNUSED gpointer user_data) +{ + if (log_level > G_LOG_LEVEL_WARNING) + return; + + g_printerr("%s\n", message); +} + +int main(int argc, char **argv) +{ + if (argc != 3) { + g_printerr("Usage: read_conf FILE SETTING\n"); + return 1; + } + + const Path config_path = Path::FromFS(argv[1]); + const char *name = argv[2]; + + g_log_set_default_handler(my_log_func, NULL); + + config_global_init(); + + GError *error = NULL; + if (!ReadConfigFile(config_path, &error)) { + g_printerr("%s:", error->message); + g_error_free(error); + return 1; + } + + ConfigOption option = ParseConfigOptionName(name); + const char *value = option != CONF_MAX + ? config_get_string(option, nullptr) + : nullptr; + int ret; + if (value != NULL) { + g_print("%s\n", value); + ret = 0; + } else { + g_printerr("No such setting: %s\n", name); + ret = 2; + } + + config_global_finish(); + return ret; +} diff --git a/test/read_mixer.c b/test/read_mixer.c deleted file mode 100644 index f6de8177d..000000000 --- a/test/read_mixer.c +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "mixer_control.h" -#include "mixer_list.h" -#include "filter_registry.h" -#include "pcm_volume.h" -#include "event_pipe.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <unistd.h> - -#ifdef HAVE_PULSE -#include "output/pulse_output_plugin.h" - -void -pulse_output_lock(G_GNUC_UNUSED struct pulse_output *po) -{ -} - -void -pulse_output_unlock(G_GNUC_UNUSED struct pulse_output *po) -{ -} - -void -pulse_output_set_mixer(G_GNUC_UNUSED struct pulse_output *po, - G_GNUC_UNUSED struct pulse_mixer *pm) -{ -} - -void -pulse_output_clear_mixer(G_GNUC_UNUSED struct pulse_output *po, - G_GNUC_UNUSED struct pulse_mixer *pm) -{ -} - -bool -pulse_output_set_volume(G_GNUC_UNUSED struct pulse_output *po, - G_GNUC_UNUSED const struct pa_cvolume *volume, - G_GNUC_UNUSED GError **error_r) -{ - return false; -} - -#endif - -#ifdef HAVE_ROAR -#include "output/roar_output_plugin.h" - -int -roar_output_get_volume(G_GNUC_UNUSED struct roar *roar) -{ - return -1; -} - -bool -roar_output_set_volume(G_GNUC_UNUSED struct roar *roar, - G_GNUC_UNUSED unsigned volume) -{ - return true; -} - -#endif - -void -event_pipe_emit(G_GNUC_UNUSED enum pipe_event event) -{ -} - -const struct filter_plugin * -filter_plugin_by_name(G_GNUC_UNUSED const char *name) -{ - assert(false); - return NULL; -} - -bool -pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length, - G_GNUC_UNUSED enum sample_format format, - G_GNUC_UNUSED int volume) -{ - assert(false); - return false; -} - -int main(int argc, G_GNUC_UNUSED char **argv) -{ - GError *error = NULL; - struct mixer *mixer; - bool success; - int volume; - - if (argc != 2) { - g_printerr("Usage: read_mixer PLUGIN\n"); - return 1; - } - - g_thread_init(NULL); - - mixer = mixer_new(&alsa_mixer_plugin, NULL, NULL, &error); - if (mixer == NULL) { - g_printerr("mixer_new() failed: %s\n", error->message); - g_error_free(error); - return 2; - } - - success = mixer_open(mixer, &error); - if (!success) { - mixer_free(mixer); - g_printerr("failed to open the mixer: %s\n", error->message); - g_error_free(error); - return 2; - } - - volume = mixer_get_volume(mixer, &error); - mixer_close(mixer); - mixer_free(mixer); - - assert(volume >= -1 && volume <= 100); - - if (volume < 0) { - if (error != NULL) { - g_printerr("failed to read volume: %s\n", - error->message); - g_error_free(error); - } else - g_printerr("failed to read volume\n"); - return 2; - } - - g_print("%d\n", volume); - return 0; -} diff --git a/test/read_mixer.cxx b/test/read_mixer.cxx new file mode 100644 index 000000000..5cd9b2c52 --- /dev/null +++ b/test/read_mixer.cxx @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +extern "C" { +#include "mixer_control.h" +#include "mixer_list.h" +} + +#include "FilterRegistry.hxx" +#include "PcmVolume.hxx" +#include "GlobalEvents.hxx" +#include "Main.hxx" +#include "event/Loop.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <unistd.h> + +EventLoop *main_loop; + +#ifdef HAVE_PULSE +#include "output/pulse_output_plugin.h" + +void +pulse_output_lock(G_GNUC_UNUSED struct pulse_output *po) +{ +} + +void +pulse_output_unlock(G_GNUC_UNUSED struct pulse_output *po) +{ +} + +void +pulse_output_set_mixer(G_GNUC_UNUSED struct pulse_output *po, + G_GNUC_UNUSED struct pulse_mixer *pm) +{ +} + +void +pulse_output_clear_mixer(G_GNUC_UNUSED struct pulse_output *po, + G_GNUC_UNUSED struct pulse_mixer *pm) +{ +} + +bool +pulse_output_set_volume(G_GNUC_UNUSED struct pulse_output *po, + G_GNUC_UNUSED const struct pa_cvolume *volume, + G_GNUC_UNUSED GError **error_r) +{ + return false; +} + +#endif + +#ifdef HAVE_ROAR +#include "output/RoarOutputPlugin.hxx" + +int +roar_output_get_volume(gcc_unused RoarOutput *roar) +{ + return -1; +} + +bool +roar_output_set_volume(gcc_unused RoarOutput *roar, + G_GNUC_UNUSED unsigned volume) +{ + return true; +} + +#endif + +void +GlobalEvents::Emit(gcc_unused Event event) +{ +} + +const struct filter_plugin * +filter_plugin_by_name(G_GNUC_UNUSED const char *name) +{ + assert(false); + return NULL; +} + +bool +pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length, + G_GNUC_UNUSED enum sample_format format, + G_GNUC_UNUSED int volume) +{ + assert(false); + return false; +} + +int main(int argc, G_GNUC_UNUSED char **argv) +{ + GError *error = NULL; + struct mixer *mixer; + bool success; + int volume; + + if (argc != 2) { + g_printerr("Usage: read_mixer PLUGIN\n"); + return 1; + } + + g_thread_init(NULL); + + 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); + g_error_free(error); + return 2; + } + + success = mixer_open(mixer, &error); + if (!success) { + mixer_free(mixer); + g_printerr("failed to open the mixer: %s\n", error->message); + g_error_free(error); + return 2; + } + + volume = mixer_get_volume(mixer, &error); + mixer_close(mixer); + mixer_free(mixer); + + delete main_loop; + + assert(volume >= -1 && volume <= 100); + + if (volume < 0) { + if (error != NULL) { + g_printerr("failed to read volume: %s\n", + error->message); + g_error_free(error); + } else + g_printerr("failed to read volume\n"); + return 2; + } + + g_print("%d\n", volume); + return 0; +} diff --git a/test/read_tags.c b/test/read_tags.c deleted file mode 100644 index faf9a45c0..000000000 --- a/test/read_tags.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "io_thread.h" -#include "decoder_list.h" -#include "decoder_api.h" -#include "input_init.h" -#include "input_stream.h" -#include "audio_format.h" -#include "pcm_volume.h" -#include "tag_ape.h" -#include "tag_id3.h" -#include "tag_handler.h" -#include "idle.h" - -#include <glib.h> - -#include <assert.h> -#include <unistd.h> -#include <stdlib.h> - -#ifdef HAVE_LOCALE_H -#include <locale.h> -#endif - -/** - * No-op dummy. - */ -void -idle_add(G_GNUC_UNUSED unsigned flags) -{ -} - -/** - * No-op dummy. - */ -bool -pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length, - G_GNUC_UNUSED enum sample_format format, - G_GNUC_UNUSED int volume) -{ - return true; -} - -void -decoder_initialized(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED const struct audio_format *audio_format, - G_GNUC_UNUSED bool seekable, - G_GNUC_UNUSED float total_time) -{ -} - -enum decoder_command -decoder_get_command(G_GNUC_UNUSED struct decoder *decoder) -{ - return DECODE_COMMAND_NONE; -} - -void decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder) -{ -} - -double decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder) -{ - return 1.0; -} - -void decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder) -{ -} - -size_t -decoder_read(G_GNUC_UNUSED struct decoder *decoder, - struct input_stream *is, - void *buffer, size_t length) -{ - return input_stream_lock_read(is, buffer, length, NULL); -} - -void -decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED double t) -{ -} - -enum decoder_command -decoder_data(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED struct input_stream *is, - const void *data, size_t datalen, - G_GNUC_UNUSED uint16_t bit_rate) -{ - G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen); - return DECODE_COMMAND_NONE; -} - -enum decoder_command -decoder_tag(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED const struct tag *tag) -{ - return DECODE_COMMAND_NONE; -} - -float -decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED const struct replay_gain_info *replay_gain_info) -{ - return 0.0; -} - -void -decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED float replay_gain_db, - char *mixramp_start, char *mixramp_end) -{ - g_free(mixramp_start); - g_free(mixramp_end); -} - -static bool empty = true; - -static void -print_duration(unsigned seconds, G_GNUC_UNUSED void *ctx) -{ - g_print("duration=%d\n", seconds); -} - -static void -print_tag(enum tag_type type, const char *value, G_GNUC_UNUSED void *ctx) -{ - g_print("[%s]=%s\n", tag_item_names[type], value); - empty = false; -} - -static void -print_pair(const char *name, const char *value, G_GNUC_UNUSED void *ctx) -{ - g_print("\"%s\"=%s\n", name, value); -} - -static const struct tag_handler print_handler = { - .duration = print_duration, - .tag = print_tag, - .pair = print_pair, -}; - -int main(int argc, char **argv) -{ - GError *error = NULL; - const char *decoder_name, *path; - const struct decoder_plugin *plugin; - -#ifdef HAVE_LOCALE_H - /* initialize locale */ - setlocale(LC_CTYPE,""); -#endif - - if (argc != 3) { - g_printerr("Usage: read_tags DECODER FILE\n"); - return 1; - } - - decoder_name = argv[1]; - path = argv[2]; - - g_thread_init(NULL); - io_thread_init(); - if (!io_thread_start(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - if (!input_stream_global_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return 2; - } - - decoder_plugin_init_all(); - - plugin = decoder_plugin_from_name(decoder_name); - if (plugin == NULL) { - g_printerr("No such decoder: %s\n", decoder_name); - return 1; - } - - bool success = decoder_plugin_scan_file(plugin, path, - &print_handler, NULL); - if (!success && plugin->scan_stream != NULL) { - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); - - struct input_stream *is = - input_stream_open(path, mutex, cond, &error); - - if (is == NULL) { - g_printerr("Failed to open %s: %s\n", - path, error->message); - g_error_free(error); - return 1; - } - - success = decoder_plugin_scan_stream(plugin, is, - &print_handler, NULL); - input_stream_close(is); - - g_cond_free(cond); - g_mutex_free(mutex); - } - - decoder_plugin_deinit_all(); - input_stream_global_finish(); - io_thread_deinit(); - - if (!success) { - g_printerr("Failed to read tags\n"); - return 1; - } - - if (empty) { - tag_ape_scan2(path, &print_handler, NULL); - if (empty) - tag_id3_scan(path, &print_handler, NULL); - } - - return 0; -} diff --git a/test/read_tags.cxx b/test/read_tags.cxx new file mode 100644 index 000000000..1ceddf1d5 --- /dev/null +++ b/test/read_tags.cxx @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "IOThread.hxx" +#include "DecoderList.hxx" +#include "decoder_api.h" +#include "InputInit.hxx" +#include "InputStream.hxx" +#include "audio_format.h" +extern "C" { +#include "tag_ape.h" +#include "tag_id3.h" +} +#include "tag_handler.h" + +#include <glib.h> + +#include <assert.h> +#include <unistd.h> +#include <stdlib.h> + +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif + +void +decoder_initialized(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED const struct audio_format *audio_format, + G_GNUC_UNUSED bool seekable, + G_GNUC_UNUSED float total_time) +{ +} + +enum decoder_command +decoder_get_command(G_GNUC_UNUSED struct decoder *decoder) +{ + return DECODE_COMMAND_NONE; +} + +void decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder) +{ +} + +double decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder) +{ + return 1.0; +} + +void decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder) +{ +} + +size_t +decoder_read(G_GNUC_UNUSED struct decoder *decoder, + struct input_stream *is, + void *buffer, size_t length) +{ + return input_stream_lock_read(is, buffer, length, NULL); +} + +void +decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED double t) +{ +} + +enum decoder_command +decoder_data(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED struct input_stream *is, + const void *data, size_t datalen, + G_GNUC_UNUSED uint16_t bit_rate) +{ + G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen); + return DECODE_COMMAND_NONE; +} + +enum decoder_command +decoder_tag(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED struct input_stream *is, + G_GNUC_UNUSED const struct tag *tag) +{ + return DECODE_COMMAND_NONE; +} + +void +decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED const struct replay_gain_info *replay_gain_info) +{ +} + +void +decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, + char *mixramp_start, char *mixramp_end) +{ + g_free(mixramp_start); + g_free(mixramp_end); +} + +static bool empty = true; + +static void +print_duration(unsigned seconds, G_GNUC_UNUSED void *ctx) +{ + g_print("duration=%d\n", seconds); +} + +static void +print_tag(enum tag_type type, const char *value, G_GNUC_UNUSED void *ctx) +{ + g_print("[%s]=%s\n", tag_item_names[type], value); + empty = false; +} + +static void +print_pair(const char *name, const char *value, G_GNUC_UNUSED void *ctx) +{ + g_print("\"%s\"=%s\n", name, value); +} + +static const struct tag_handler print_handler = { + print_duration, + print_tag, + print_pair, +}; + +int main(int argc, char **argv) +{ + GError *error = NULL; + const char *decoder_name, *path; + const struct decoder_plugin *plugin; + +#ifdef HAVE_LOCALE_H + /* initialize locale */ + setlocale(LC_CTYPE,""); +#endif + + if (argc != 3) { + g_printerr("Usage: read_tags DECODER FILE\n"); + return 1; + } + + decoder_name = argv[1]; + path = argv[2]; + + g_thread_init(NULL); + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + if (!input_stream_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + + decoder_plugin_init_all(); + + plugin = decoder_plugin_from_name(decoder_name); + if (plugin == NULL) { + g_printerr("No such decoder: %s\n", decoder_name); + return 1; + } + + bool success = decoder_plugin_scan_file(plugin, path, + &print_handler, NULL); + if (!success && plugin->scan_stream != NULL) { + Mutex mutex; + Cond cond; + + struct input_stream *is = + input_stream_open(path, mutex, cond, &error); + + if (is == NULL) { + g_printerr("Failed to open %s: %s\n", + path, error->message); + g_error_free(error); + return 1; + } + + mutex.lock(); + + while (!is->ready) { + cond.wait(mutex); + input_stream_update(is); + } + + if (!input_stream_check(is, &error)) { + mutex.unlock(); + + g_printerr("Failed to read %s: %s\n", + path, error->message); + g_error_free(error); + + return EXIT_FAILURE; + } + + mutex.unlock(); + + success = decoder_plugin_scan_stream(plugin, is, + &print_handler, NULL); + input_stream_close(is); + } + + decoder_plugin_deinit_all(); + input_stream_global_finish(); + io_thread_deinit(); + + if (!success) { + g_printerr("Failed to read tags\n"); + return 1; + } + + if (empty) { + tag_ape_scan2(path, &print_handler, NULL); + if (empty) + tag_id3_scan(path, &print_handler, NULL); + } + + return 0; +} diff --git a/test/run_convert.c b/test/run_convert.c deleted file mode 100644 index 4f57400fd..000000000 --- a/test/run_convert.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This program is a command line interface to MPD's PCM conversion - * library (pcm_convert.c). - * - */ - -#include "config.h" -#include "audio_parser.h" -#include "audio_format.h" -#include "pcm_convert.h" -#include "conf.h" -#include "fifo_buffer.h" -#include "stdbin.h" - -#include <glib.h> - -#include <assert.h> -#include <stddef.h> -#include <unistd.h> - -static void -my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - if (log_domain != NULL) - g_printerr("%s: %s\n", log_domain, message); - else - g_printerr("%s\n", message); -} - -const char * -config_get_string(G_GNUC_UNUSED const char *name, const char *default_value) -{ - return default_value; -} - -int main(int argc, char **argv) -{ - GError *error = NULL; - struct audio_format in_audio_format, out_audio_format; - struct pcm_convert_state state; - const void *output; - ssize_t nbytes; - size_t length; - - if (argc != 3) { - g_printerr("Usage: run_convert IN_FORMAT OUT_FORMAT <IN >OUT\n"); - return 1; - } - - g_log_set_default_handler(my_log_func, NULL); - - if (!audio_format_parse(&in_audio_format, argv[1], - false, &error)) { - g_printerr("Failed to parse audio format: %s\n", - error->message); - return 1; - } - - struct audio_format out_audio_format_mask; - if (!audio_format_parse(&out_audio_format_mask, argv[2], - true, &error)) { - g_printerr("Failed to parse audio format: %s\n", - error->message); - return 1; - } - - out_audio_format = in_audio_format; - audio_format_mask_apply(&out_audio_format, &out_audio_format_mask); - - const size_t in_frame_size = audio_format_frame_size(&in_audio_format); - - pcm_convert_init(&state); - - struct fifo_buffer *buffer = fifo_buffer_new(4096); - - while (true) { - void *p = fifo_buffer_write(buffer, &length); - assert(p != NULL); - - nbytes = read(0, p, length); - if (nbytes <= 0) - break; - - fifo_buffer_append(buffer, nbytes); - - const void *src = fifo_buffer_read(buffer, &length); - assert(src != NULL); - - length -= length % in_frame_size; - if (length == 0) - continue; - - fifo_buffer_consume(buffer, length); - - output = pcm_convert(&state, &in_audio_format, src, length, - &out_audio_format, &length, &error); - if (output == NULL) { - g_printerr("Failed to convert: %s\n", error->message); - return 2; - } - - G_GNUC_UNUSED ssize_t ignored = write(1, output, length); - } - - pcm_convert_deinit(&state); -} diff --git a/test/run_convert.cxx b/test/run_convert.cxx new file mode 100644 index 000000000..e66d0be2b --- /dev/null +++ b/test/run_convert.cxx @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This program is a command line interface to MPD's PCM conversion + * library (pcm_convert.c). + * + */ + +#include "config.h" +#include "AudioParser.hxx" +#include "audio_format.h" +#include "PcmConvert.hxx" +#include "conf.h" +#include "util/fifo_buffer.h" +#include "stdbin.h" + +#include <glib.h> + +#include <assert.h> +#include <stddef.h> +#include <unistd.h> + +static void +my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, + const gchar *message, G_GNUC_UNUSED gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +const char * +config_get_string(gcc_unused enum ConfigOption option, + const char *default_value) +{ + return default_value; +} + +int main(int argc, char **argv) +{ + GError *error = NULL; + struct audio_format in_audio_format, out_audio_format; + const void *output; + ssize_t nbytes; + size_t length; + + if (argc != 3) { + g_printerr("Usage: run_convert IN_FORMAT OUT_FORMAT <IN >OUT\n"); + return 1; + } + + g_log_set_default_handler(my_log_func, NULL); + + if (!audio_format_parse(&in_audio_format, argv[1], + false, &error)) { + g_printerr("Failed to parse audio format: %s\n", + error->message); + return 1; + } + + struct audio_format out_audio_format_mask; + if (!audio_format_parse(&out_audio_format_mask, argv[2], + true, &error)) { + g_printerr("Failed to parse audio format: %s\n", + error->message); + return 1; + } + + out_audio_format = in_audio_format; + audio_format_mask_apply(&out_audio_format, &out_audio_format_mask); + + const size_t in_frame_size = audio_format_frame_size(&in_audio_format); + + PcmConvert state; + + struct fifo_buffer *buffer = fifo_buffer_new(4096); + + while (true) { + void *p = fifo_buffer_write(buffer, &length); + assert(p != NULL); + + nbytes = read(0, p, length); + if (nbytes <= 0) + break; + + fifo_buffer_append(buffer, nbytes); + + const void *src = fifo_buffer_read(buffer, &length); + assert(src != NULL); + + length -= length % in_frame_size; + if (length == 0) + continue; + + fifo_buffer_consume(buffer, length); + + output = state.Convert(&in_audio_format, src, length, + &out_audio_format, &length, &error); + if (output == NULL) { + g_printerr("Failed to convert: %s\n", error->message); + return 2; + } + + G_GNUC_UNUSED ssize_t ignored = write(1, output, length); + } + + return EXIT_SUCCESS; +} diff --git a/test/run_decoder.c b/test/run_decoder.c deleted file mode 100644 index e6712c75b..000000000 --- a/test/run_decoder.c +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "io_thread.h" -#include "decoder_list.h" -#include "decoder_api.h" -#include "tag_pool.h" -#include "input_init.h" -#include "input_stream.h" -#include "audio_format.h" -#include "pcm_volume.h" -#include "idle.h" -#include "stdbin.h" - -#include <glib.h> - -#include <assert.h> -#include <unistd.h> -#include <stdlib.h> - -static void -my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - if (log_domain != NULL) - g_printerr("%s: %s\n", log_domain, message); - else - g_printerr("%s\n", message); -} - -/** - * No-op dummy. - */ -void -idle_add(G_GNUC_UNUSED unsigned flags) -{ -} - -/** - * No-op dummy. - */ -bool -pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length, - G_GNUC_UNUSED enum sample_format format, - G_GNUC_UNUSED int volume) -{ - return true; -} - -struct decoder { - const char *uri; - - const struct decoder_plugin *plugin; - - bool initialized; -}; - -void -decoder_initialized(struct decoder *decoder, - const struct audio_format *audio_format, - G_GNUC_UNUSED bool seekable, - G_GNUC_UNUSED float total_time) -{ - struct audio_format_string af_string; - - assert(!decoder->initialized); - assert(audio_format_valid(audio_format)); - - g_printerr("audio_format=%s\n", - audio_format_to_string(audio_format, &af_string)); - - decoder->initialized = true; -} - -enum decoder_command -decoder_get_command(G_GNUC_UNUSED struct decoder *decoder) -{ - return DECODE_COMMAND_NONE; -} - -void decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder) -{ -} - -double decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder) -{ - return 1.0; -} - -void decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder) -{ -} - -size_t -decoder_read(G_GNUC_UNUSED struct decoder *decoder, - struct input_stream *is, - void *buffer, size_t length) -{ - return input_stream_lock_read(is, buffer, length, NULL); -} - -void -decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED double t) -{ -} - -enum decoder_command -decoder_data(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED struct input_stream *is, - const void *data, size_t datalen, - G_GNUC_UNUSED uint16_t kbit_rate) -{ - G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen); - return DECODE_COMMAND_NONE; -} - -enum decoder_command -decoder_tag(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED const struct tag *tag) -{ - return DECODE_COMMAND_NONE; -} - -float -decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, - const struct replay_gain_info *replay_gain_info) -{ - const struct replay_gain_tuple *tuple = - &replay_gain_info->tuples[REPLAY_GAIN_ALBUM]; - if (replay_gain_tuple_defined(tuple)) - g_printerr("replay_gain[album]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); - - tuple = &replay_gain_info->tuples[REPLAY_GAIN_TRACK]; - if (replay_gain_tuple_defined(tuple)) - g_printerr("replay_gain[track]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); - - return 0.0; -} - -void -decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED float replay_gain_db, - char *mixramp_start, char *mixramp_end) -{ - g_free(mixramp_start); - g_free(mixramp_end); -} - -int main(int argc, char **argv) -{ - GError *error = NULL; - const char *decoder_name; - struct decoder decoder; - - if (argc != 3) { - g_printerr("Usage: run_decoder DECODER URI >OUT\n"); - return 1; - } - - decoder_name = argv[1]; - decoder.uri = argv[2]; - - g_thread_init(NULL); - g_log_set_default_handler(my_log_func, NULL); - - io_thread_init(); - if (!io_thread_start(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - tag_pool_init(); - - if (!input_stream_global_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return 2; - } - - decoder_plugin_init_all(); - - decoder.plugin = decoder_plugin_from_name(decoder_name); - if (decoder.plugin == NULL) { - g_printerr("No such decoder: %s\n", decoder_name); - return 1; - } - - decoder.initialized = false; - - if (decoder.plugin->file_decode != NULL) { - decoder_plugin_file_decode(decoder.plugin, &decoder, - decoder.uri); - } else if (decoder.plugin->stream_decode != NULL) { - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); - - struct input_stream *is = - input_stream_open(decoder.uri, mutex, cond, &error); - if (is == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } else - g_printerr("input_stream_open() failed\n"); - - return 1; - } - - decoder_plugin_stream_decode(decoder.plugin, &decoder, is); - - input_stream_close(is); - - g_cond_free(cond); - g_mutex_free(mutex); - } else { - g_printerr("Decoder plugin is not usable\n"); - return 1; - } - - decoder_plugin_deinit_all(); - input_stream_global_finish(); - io_thread_deinit(); - - if (!decoder.initialized) { - g_printerr("Decoding failed\n"); - return 1; - } - - tag_pool_deinit(); - - return 0; -} diff --git a/test/run_decoder.cxx b/test/run_decoder.cxx new file mode 100644 index 000000000..d5c7c3139 --- /dev/null +++ b/test/run_decoder.cxx @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "IOThread.hxx" +#include "DecoderList.hxx" +#include "decoder_api.h" +#include "InputInit.hxx" +#include "input_stream.h" +#include "audio_format.h" +#include "stdbin.h" + +#include <glib.h> + +#include <assert.h> +#include <unistd.h> +#include <stdlib.h> + +static void +my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, + const gchar *message, G_GNUC_UNUSED gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +struct decoder { + const char *uri; + + const struct decoder_plugin *plugin; + + bool initialized; +}; + +void +decoder_initialized(struct decoder *decoder, + const struct audio_format *audio_format, + G_GNUC_UNUSED bool seekable, + G_GNUC_UNUSED float total_time) +{ + struct audio_format_string af_string; + + assert(!decoder->initialized); + assert(audio_format_valid(audio_format)); + + g_printerr("audio_format=%s\n", + audio_format_to_string(audio_format, &af_string)); + + decoder->initialized = true; +} + +enum decoder_command +decoder_get_command(G_GNUC_UNUSED struct decoder *decoder) +{ + return DECODE_COMMAND_NONE; +} + +void decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder) +{ +} + +double decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder) +{ + return 1.0; +} + +void decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder) +{ +} + +size_t +decoder_read(G_GNUC_UNUSED struct decoder *decoder, + struct input_stream *is, + void *buffer, size_t length) +{ + return input_stream_lock_read(is, buffer, length, NULL); +} + +void +decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED double t) +{ +} + +enum decoder_command +decoder_data(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED struct input_stream *is, + const void *data, size_t datalen, + G_GNUC_UNUSED uint16_t kbit_rate) +{ + G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen); + return DECODE_COMMAND_NONE; +} + +enum decoder_command +decoder_tag(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED struct input_stream *is, + G_GNUC_UNUSED const struct tag *tag) +{ + return DECODE_COMMAND_NONE; +} + +void +decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, + const struct replay_gain_info *replay_gain_info) +{ + const struct replay_gain_tuple *tuple = + &replay_gain_info->tuples[REPLAY_GAIN_ALBUM]; + if (replay_gain_tuple_defined(tuple)) + g_printerr("replay_gain[album]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); + + tuple = &replay_gain_info->tuples[REPLAY_GAIN_TRACK]; + if (replay_gain_tuple_defined(tuple)) + g_printerr("replay_gain[track]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); +} + +void +decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, + char *mixramp_start, char *mixramp_end) +{ + g_free(mixramp_start); + g_free(mixramp_end); +} + +int main(int argc, char **argv) +{ + GError *error = NULL; + const char *decoder_name; + struct decoder decoder; + + if (argc != 3) { + g_printerr("Usage: run_decoder DECODER URI >OUT\n"); + return 1; + } + + decoder_name = argv[1]; + decoder.uri = argv[2]; + + g_thread_init(NULL); + g_log_set_default_handler(my_log_func, NULL); + + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + if (!input_stream_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + + decoder_plugin_init_all(); + + decoder.plugin = decoder_plugin_from_name(decoder_name); + if (decoder.plugin == NULL) { + g_printerr("No such decoder: %s\n", decoder_name); + return 1; + } + + decoder.initialized = false; + + if (decoder.plugin->file_decode != NULL) { + decoder_plugin_file_decode(decoder.plugin, &decoder, + decoder.uri); + } else if (decoder.plugin->stream_decode != NULL) { + Mutex mutex; + Cond cond; + + struct input_stream *is = + input_stream_open(decoder.uri, mutex, cond, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } else + g_printerr("input_stream_open() failed\n"); + + return 1; + } + + decoder_plugin_stream_decode(decoder.plugin, &decoder, is); + + input_stream_close(is); + } else { + g_printerr("Decoder plugin is not usable\n"); + return 1; + } + + decoder_plugin_deinit_all(); + input_stream_global_finish(); + io_thread_deinit(); + + if (!decoder.initialized) { + g_printerr("Decoding failed\n"); + return 1; + } + + return 0; +} diff --git a/test/run_encoder.c b/test/run_encoder.c deleted file mode 100644 index db4d3af9b..000000000 --- a/test/run_encoder.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "encoder_list.h" -#include "encoder_plugin.h" -#include "audio_format.h" -#include "audio_parser.h" -#include "conf.h" -#include "stdbin.h" - -#include <glib.h> - -#include <stddef.h> -#include <unistd.h> - -static void -encoder_to_stdout(struct encoder *encoder) -{ - size_t length; - static char buffer[32768]; - - while ((length = encoder_read(encoder, buffer, sizeof(buffer))) > 0) { - G_GNUC_UNUSED ssize_t ignored = write(1, buffer, length); - } -} - -int main(int argc, char **argv) -{ - GError *error = NULL; - struct audio_format audio_format; - bool ret; - const char *encoder_name; - const struct encoder_plugin *plugin; - struct encoder *encoder; - struct config_param *param; - static char buffer[32768]; - ssize_t nbytes; - - /* parse command line */ - - if (argc > 3) { - g_printerr("Usage: run_encoder [ENCODER] [FORMAT] <IN >OUT\n"); - return 1; - } - - if (argc > 1) - encoder_name = argv[1]; - else - encoder_name = "vorbis"; - - audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); - - /* create the encoder */ - - plugin = encoder_plugin_get(encoder_name); - if (plugin == NULL) { - g_printerr("No such encoder: %s\n", encoder_name); - return 1; - } - - param = config_new_param(NULL, -1); - config_add_block_param(param, "quality", "5.0", -1); - - encoder = encoder_init(plugin, param, &error); - if (encoder == NULL) { - g_printerr("Failed to initialize encoder: %s\n", - error->message); - g_error_free(error); - return 1; - } - - /* open the encoder */ - - if (argc > 2) { - ret = audio_format_parse(&audio_format, argv[2], - false, &error); - if (!ret) { - g_printerr("Failed to parse audio format: %s\n", - error->message); - g_error_free(error); - return 1; - } - } - - if (!encoder_open(encoder, &audio_format, &error)) { - g_printerr("Failed to open encoder: %s\n", - error->message); - g_error_free(error); - return 1; - } - - encoder_to_stdout(encoder); - - /* do it */ - - while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { - ret = encoder_write(encoder, buffer, nbytes, &error); - if (!ret) { - g_printerr("encoder_write() failed: %s\n", - error->message); - g_error_free(error); - return 1; - } - - encoder_to_stdout(encoder); - } - - ret = encoder_end(encoder, &error); - if (!ret) { - g_printerr("encoder_flush() failed: %s\n", - error->message); - g_error_free(error); - return 1; - } - - encoder_to_stdout(encoder); -} diff --git a/test/run_encoder.cxx b/test/run_encoder.cxx new file mode 100644 index 000000000..9039f2db5 --- /dev/null +++ b/test/run_encoder.cxx @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "encoder_list.h" +#include "encoder_plugin.h" +#include "audio_format.h" +#include "AudioParser.hxx" +#include "conf.h" +#include "stdbin.h" + +#include <glib.h> + +#include <stddef.h> +#include <unistd.h> + +static void +encoder_to_stdout(struct encoder *encoder) +{ + size_t length; + static char buffer[32768]; + + while ((length = encoder_read(encoder, buffer, sizeof(buffer))) > 0) { + G_GNUC_UNUSED ssize_t ignored = write(1, buffer, length); + } +} + +int main(int argc, char **argv) +{ + GError *error = NULL; + struct audio_format audio_format; + bool ret; + const char *encoder_name; + const struct encoder_plugin *plugin; + struct encoder *encoder; + static char buffer[32768]; + + /* parse command line */ + + if (argc > 3) { + g_printerr("Usage: run_encoder [ENCODER] [FORMAT] <IN >OUT\n"); + return 1; + } + + if (argc > 1) + encoder_name = argv[1]; + else + encoder_name = "vorbis"; + + audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); + + /* create the encoder */ + + plugin = encoder_plugin_get(encoder_name); + if (plugin == NULL) { + g_printerr("No such encoder: %s\n", encoder_name); + return 1; + } + + config_param param; + param.AddBlockParam("quality", "5.0", -1); + + encoder = encoder_init(plugin, ¶m, &error); + if (encoder == NULL) { + g_printerr("Failed to initialize encoder: %s\n", + error->message); + g_error_free(error); + return 1; + } + + /* open the encoder */ + + if (argc > 2) { + ret = audio_format_parse(&audio_format, argv[2], + false, &error); + if (!ret) { + g_printerr("Failed to parse audio format: %s\n", + error->message); + g_error_free(error); + return 1; + } + } + + if (!encoder_open(encoder, &audio_format, &error)) { + g_printerr("Failed to open encoder: %s\n", + error->message); + g_error_free(error); + return 1; + } + + encoder_to_stdout(encoder); + + /* do it */ + + ssize_t nbytes; + while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { + ret = encoder_write(encoder, buffer, nbytes, &error); + if (!ret) { + g_printerr("encoder_write() failed: %s\n", + error->message); + g_error_free(error); + return 1; + } + + encoder_to_stdout(encoder); + } + + ret = encoder_end(encoder, &error); + if (!ret) { + g_printerr("encoder_flush() failed: %s\n", + error->message); + g_error_free(error); + return 1; + } + + encoder_to_stdout(encoder); +} diff --git a/test/run_filter.c b/test/run_filter.c deleted file mode 100644 index 8e793b768..000000000 --- a/test/run_filter.c +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "conf.h" -#include "audio_parser.h" -#include "audio_format.h" -#include "filter_plugin.h" -#include "pcm_volume.h" -#include "idle.h" -#include "mixer_control.h" -#include "playlist.h" -#include "stdbin.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <errno.h> -#include <unistd.h> - -struct playlist g_playlist; - -void -idle_add(G_GNUC_UNUSED unsigned flags) -{ -} - -bool -mixer_set_volume(G_GNUC_UNUSED struct mixer *mixer, - G_GNUC_UNUSED unsigned volume, G_GNUC_UNUSED GError **error_r) -{ - return true; -} - -static void -my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - if (log_domain != NULL) - g_printerr("%s: %s\n", log_domain, message); - else - g_printerr("%s\n", message); -} - -static const struct config_param * -find_named_config_block(const char *block, const char *name) -{ - const struct config_param *param = NULL; - - while ((param = config_get_next_param(block, param)) != NULL) { - const char *current_name = - config_get_block_string(param, "name", NULL); - if (current_name != NULL && strcmp(current_name, name) == 0) - return param; - } - - return NULL; -} - -static struct filter * -load_filter(const char *name) -{ - const struct config_param *param; - struct filter *filter; - GError *error = NULL; - - param = find_named_config_block("filter", name); - if (param == NULL) { - g_printerr("No such configured filter: %s\n", name); - return false; - } - - filter = filter_configured_new(param, &error); - if (filter == NULL) { - g_printerr("Failed to load filter: %s\n", error->message); - g_error_free(error); - return NULL; - } - - return filter; -} - -int main(int argc, char **argv) -{ - struct audio_format audio_format; - struct audio_format_string af_string; - bool success; - GError *error = NULL; - struct filter *filter; - const struct audio_format *out_audio_format; - char buffer[4096]; - - if (argc < 3 || argc > 4) { - g_printerr("Usage: run_filter CONFIG NAME [FORMAT] <IN\n"); - return 1; - } - - audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); - - /* initialize GLib */ - - g_thread_init(NULL); - g_log_set_default_handler(my_log_func, NULL); - - /* read configuration file (mpd.conf) */ - - config_global_init(); - success = config_read_file(argv[1], &error); - if (!success) { - g_printerr("%s:", error->message); - g_error_free(error); - return 1; - } - - /* parse the audio format */ - - if (argc > 3) { - success = audio_format_parse(&audio_format, argv[3], - false, &error); - if (!success) { - g_printerr("Failed to parse audio format: %s\n", - error->message); - g_error_free(error); - return 1; - } - } - - /* initialize the filter */ - - filter = load_filter(argv[2]); - if (filter == NULL) - return 1; - - /* open the filter */ - - out_audio_format = filter_open(filter, &audio_format, &error); - if (out_audio_format == NULL) { - g_printerr("Failed to open filter: %s\n", error->message); - g_error_free(error); - filter_free(filter); - return 1; - } - - g_printerr("audio_format=%s\n", - audio_format_to_string(out_audio_format, &af_string)); - - /* play */ - - while (true) { - ssize_t nbytes; - size_t length; - const void *dest; - - nbytes = read(0, buffer, sizeof(buffer)); - if (nbytes <= 0) - break; - - dest = filter_filter(filter, buffer, (size_t)nbytes, - &length, &error); - if (dest == NULL) { - g_printerr("Filter failed: %s\n", error->message); - filter_close(filter); - filter_free(filter); - return 1; - } - - nbytes = write(1, dest, length); - if (nbytes < 0) { - g_printerr("Failed to write: %s\n", g_strerror(errno)); - filter_close(filter); - filter_free(filter); - return 1; - } - } - - /* cleanup and exit */ - - filter_close(filter); - filter_free(filter); - - config_global_finish(); - - return 0; -} diff --git a/test/run_filter.cxx b/test/run_filter.cxx new file mode 100644 index 000000000..09d755db2 --- /dev/null +++ b/test/run_filter.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 "conf.h" +#include "fs/Path.hxx" +#include "AudioParser.hxx" +#include "audio_format.h" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "PcmVolume.hxx" +#include "mixer_control.h" +#include "stdbin.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +bool +mixer_set_volume(G_GNUC_UNUSED struct mixer *mixer, + G_GNUC_UNUSED unsigned volume, G_GNUC_UNUSED GError **error_r) +{ + return true; +} + +static void +my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, + const gchar *message, G_GNUC_UNUSED gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +static const struct config_param * +find_named_config_block(ConfigOption option, const char *name) +{ + const struct config_param *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) + return param; + } + + return NULL; +} + +static Filter * +load_filter(const char *name) +{ + const struct config_param *param; + GError *error = NULL; + + param = find_named_config_block(CONF_AUDIO_FILTER, name); + if (param == NULL) { + g_printerr("No such configured filter: %s\n", name); + return nullptr; + } + + Filter *filter = filter_configured_new(param, &error); + if (filter == NULL) { + g_printerr("Failed to load filter: %s\n", error->message); + g_error_free(error); + return NULL; + } + + return filter; +} + +int main(int argc, char **argv) +{ + struct audio_format audio_format; + struct audio_format_string af_string; + bool success; + GError *error = NULL; + const struct audio_format *out_audio_format; + char buffer[4096]; + + if (argc < 3 || argc > 4) { + g_printerr("Usage: run_filter CONFIG NAME [FORMAT] <IN\n"); + return 1; + } + + const Path config_path = Path::FromFS(argv[1]); + + audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); + + /* initialize GLib */ + + g_thread_init(NULL); + g_log_set_default_handler(my_log_func, NULL); + + /* read configuration file (mpd.conf) */ + + config_global_init(); + if (!ReadConfigFile(config_path, &error)) { + g_printerr("%s:", error->message); + g_error_free(error); + return 1; + } + + /* parse the audio format */ + + if (argc > 3) { + success = audio_format_parse(&audio_format, argv[3], + false, &error); + if (!success) { + g_printerr("Failed to parse audio format: %s\n", + error->message); + g_error_free(error); + return 1; + } + } + + /* initialize the filter */ + + Filter *filter = load_filter(argv[2]); + if (filter == NULL) + return 1; + + /* open the filter */ + + 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); + delete filter; + return 1; + } + + g_printerr("audio_format=%s\n", + audio_format_to_string(out_audio_format, &af_string)); + + /* play */ + + while (true) { + ssize_t nbytes; + size_t length; + const void *dest; + + nbytes = read(0, buffer, sizeof(buffer)); + if (nbytes <= 0) + break; + + dest = filter->FilterPCM(buffer, (size_t)nbytes, + &length, &error); + if (dest == NULL) { + g_printerr("Filter failed: %s\n", error->message); + filter->Close(); + delete filter; + return 1; + } + + nbytes = write(1, dest, length); + if (nbytes < 0) { + g_printerr("Failed to write: %s\n", g_strerror(errno)); + filter->Close(); + delete filter; + return 1; + } + } + + /* cleanup and exit */ + + filter->Close(); + delete filter; + + config_global_finish(); + + return 0; +} diff --git a/test/run_inotify.c b/test/run_inotify.c deleted file mode 100644 index 3e7c70dba..000000000 --- a/test/run_inotify.c +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "inotify_source.h" - -#include <stdbool.h> -#include <sys/inotify.h> -#include <signal.h> - -static GMainLoop *main_loop; - -static void -exit_signal_handler(G_GNUC_UNUSED int signum) -{ - g_main_loop_quit(main_loop); -} - -enum { - IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF - |IN_MOVE|IN_MOVE_SELF -#ifdef IN_ONLYDIR - |IN_ONLYDIR -#endif -}; - -static void -my_inotify_callback(G_GNUC_UNUSED int wd, unsigned mask, - const char *name, G_GNUC_UNUSED void *ctx) -{ - g_print("mask=0x%x name='%s'\n", mask, name); -} - -int main(int argc, char **argv) -{ - GError *error = NULL; - const char *path; - - if (argc != 2) { - g_printerr("Usage: run_inotify PATH\n"); - return 1; - } - - path = argv[1]; - - struct mpd_inotify_source *source = - mpd_inotify_source_new(my_inotify_callback, NULL, - &error); - if (source == NULL) { - g_warning("%s", error->message); - g_error_free(error); - return 2; - } - - int descriptor = mpd_inotify_source_add(source, path, - IN_MASK, &error); - if (descriptor < 0) { - mpd_inotify_source_free(source); - g_warning("%s", error->message); - g_error_free(error); - return 2; - } - - main_loop = g_main_loop_new(NULL, false); - - struct sigaction sa; - sa.sa_flags = 0; - sigemptyset(&sa.sa_mask); - sa.sa_handler = exit_signal_handler; - sigaction(SIGINT, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); - - g_main_loop_run(main_loop); - g_main_loop_unref(main_loop); - - mpd_inotify_source_free(source); -} diff --git a/test/run_inotify.cxx b/test/run_inotify.cxx new file mode 100644 index 000000000..29fcf70ab --- /dev/null +++ b/test/run_inotify.cxx @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "InotifySource.hxx" +#include "event/Loop.hxx" + +#include <glib.h> + +#include <sys/inotify.h> +#include <signal.h> + +static EventLoop *event_loop; + +static void +exit_signal_handler(G_GNUC_UNUSED int signum) +{ + event_loop->Break(); +} + +enum { + IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF + |IN_MOVE|IN_MOVE_SELF +#ifdef IN_ONLYDIR + |IN_ONLYDIR +#endif +}; + +static void +my_inotify_callback(G_GNUC_UNUSED int wd, unsigned mask, + const char *name, G_GNUC_UNUSED void *ctx) +{ + g_print("mask=0x%x name='%s'\n", mask, name); +} + +int main(int argc, char **argv) +{ + GError *error = NULL; + const char *path; + + if (argc != 2) { + g_printerr("Usage: run_inotify PATH\n"); + return 1; + } + + path = argv[1]; + + event_loop = new EventLoop(EventLoop::Default()); + + InotifySource *source = InotifySource::Create(*event_loop, + my_inotify_callback, + nullptr, &error); + if (source == NULL) { + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + + int descriptor = source->Add(path, IN_MASK, &error); + if (descriptor < 0) { + delete source; + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + + struct sigaction sa; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = exit_signal_handler; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + event_loop->Run(); + + delete source; + delete event_loop; +} diff --git a/test/run_input.c b/test/run_input.c deleted file mode 100644 index 676e4e618..000000000 --- a/test/run_input.c +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "io_thread.h" -#include "input_init.h" -#include "input_stream.h" -#include "tag_pool.h" -#include "tag_save.h" -#include "conf.h" -#include "stdbin.h" - -#ifdef ENABLE_ARCHIVE -#include "archive_list.h" -#endif - -#include <glib.h> - -#include <unistd.h> -#include <stdlib.h> - -static void -my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - if (log_domain != NULL) - g_printerr("%s: %s\n", log_domain, message); - else - g_printerr("%s\n", message); -} - -static int -dump_input_stream(struct input_stream *is) -{ - GError *error = NULL; - char buffer[4096]; - size_t num_read; - ssize_t num_written; - - input_stream_lock(is); - - /* wait until the stream becomes ready */ - - input_stream_wait_ready(is); - - if (!input_stream_check(is, &error)) { - g_warning("%s", error->message); - g_error_free(error); - input_stream_unlock(is); - return EXIT_FAILURE; - } - - /* print meta data */ - - if (is->mime != NULL) - g_printerr("MIME type: %s\n", is->mime); - - /* read data and tags from the stream */ - - while (!input_stream_eof(is)) { - struct tag *tag = input_stream_tag(is); - if (tag != NULL) { - g_printerr("Received a tag:\n"); - tag_save(stderr, tag); - tag_free(tag); - } - - num_read = input_stream_read(is, buffer, sizeof(buffer), - &error); - if (num_read == 0) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - - break; - } - - num_written = write(1, buffer, num_read); - if (num_written <= 0) - break; - } - - if (!input_stream_check(is, &error)) { - g_warning("%s", error->message); - g_error_free(error); - input_stream_unlock(is); - return EXIT_FAILURE; - } - - input_stream_unlock(is); - - return 0; -} - -int main(int argc, char **argv) -{ - GError *error = NULL; - struct input_stream *is; - int ret; - - if (argc != 2) { - g_printerr("Usage: run_input URI\n"); - return 1; - } - - /* initialize GLib */ - - g_thread_init(NULL); - g_log_set_default_handler(my_log_func, NULL); - - /* initialize MPD */ - - tag_pool_init(); - config_global_init(); - - io_thread_init(); - if (!io_thread_start(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - -#ifdef ENABLE_ARCHIVE - archive_plugin_init_all(); -#endif - - if (!input_stream_global_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return 2; - } - - /* open the stream and dump it */ - - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); - - is = input_stream_open(argv[1], mutex, cond, &error); - if (is != NULL) { - ret = dump_input_stream(is); - input_stream_close(is); - } else { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } else - g_printerr("input_stream_open() failed\n"); - ret = 2; - } - - g_cond_free(cond); - g_mutex_free(mutex); - - /* deinitialize everything */ - - input_stream_global_finish(); - -#ifdef ENABLE_ARCHIVE - archive_plugin_deinit_all(); -#endif - - io_thread_deinit(); - - config_global_finish(); - tag_pool_deinit(); - - return ret; -} diff --git a/test/run_input.cxx b/test/run_input.cxx new file mode 100644 index 000000000..157613dbc --- /dev/null +++ b/test/run_input.cxx @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TagSave.hxx" +#include "stdbin.h" +#include "tag.h" +#include "conf.h" +#include "input_stream.h" +#include "InputStream.hxx" +#include "InputInit.hxx" +#include "IOThread.hxx" + +#ifdef ENABLE_ARCHIVE +#include "ArchiveList.hxx" +#endif + +#include <glib.h> + +#include <unistd.h> +#include <stdlib.h> + +static void +my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, + const gchar *message, G_GNUC_UNUSED gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +static int +dump_input_stream(struct input_stream *is) +{ + GError *error = NULL; + char buffer[4096]; + size_t num_read; + ssize_t num_written; + + input_stream_lock(is); + + /* wait until the stream becomes ready */ + + input_stream_wait_ready(is); + + if (!input_stream_check(is, &error)) { + g_warning("%s", error->message); + g_error_free(error); + input_stream_unlock(is); + return EXIT_FAILURE; + } + + /* print meta data */ + + if (!is->mime.empty()) + g_printerr("MIME type: %s\n", is->mime.c_str()); + + /* read data and tags from the stream */ + + while (!input_stream_eof(is)) { + struct tag *tag = input_stream_tag(is); + if (tag != NULL) { + g_printerr("Received a tag:\n"); + tag_save(stderr, tag); + tag_free(tag); + } + + num_read = input_stream_read(is, buffer, sizeof(buffer), + &error); + if (num_read == 0) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + break; + } + + num_written = write(1, buffer, num_read); + if (num_written <= 0) + break; + } + + if (!input_stream_check(is, &error)) { + g_warning("%s", error->message); + g_error_free(error); + input_stream_unlock(is); + return EXIT_FAILURE; + } + + input_stream_unlock(is); + + return 0; +} + +int main(int argc, char **argv) +{ + GError *error = NULL; + struct input_stream *is; + int ret; + + if (argc != 2) { + g_printerr("Usage: run_input URI\n"); + return 1; + } + + /* initialize GLib */ + + g_thread_init(NULL); + g_log_set_default_handler(my_log_func, NULL); + + /* initialize MPD */ + + config_global_init(); + + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + +#ifdef ENABLE_ARCHIVE + archive_plugin_init_all(); +#endif + + if (!input_stream_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + + /* open the stream and dump it */ + + Mutex mutex; + Cond cond; + + is = input_stream_open(argv[1], mutex, cond, &error); + if (is != NULL) { + ret = dump_input_stream(is); + input_stream_close(is); + } else { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } else + g_printerr("input_stream_open() failed\n"); + ret = 2; + } + + /* deinitialize everything */ + + input_stream_global_finish(); + +#ifdef ENABLE_ARCHIVE + archive_plugin_deinit_all(); +#endif + + io_thread_deinit(); + + config_global_finish(); + + return ret; +} diff --git a/test/run_normalize.c b/test/run_normalize.c deleted file mode 100644 index fc26829ed..000000000 --- a/test/run_normalize.c +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This program is a command line interface to MPD's normalize library - * (based on AudioCompress). - * - */ - -#include "config.h" -#include "AudioCompress/compress.h" -#include "audio_parser.h" -#include "audio_format.h" -#include "stdbin.h" - -#include <glib.h> - -#include <stddef.h> -#include <unistd.h> -#include <string.h> - -int main(int argc, char **argv) -{ - GError *error = NULL; - struct audio_format audio_format; - bool ret; - struct Compressor *compressor; - static char buffer[4096]; - ssize_t nbytes; - - if (argc > 2) { - g_printerr("Usage: run_normalize [FORMAT] <IN >OUT\n"); - return 1; - } - - if (argc > 1) { - ret = audio_format_parse(&audio_format, argv[1], - false, &error); - if (!ret) { - g_printerr("Failed to parse audio format: %s\n", - error->message); - return 1; - } - } else - audio_format_init(&audio_format, 48000, 16, 2); - - compressor = Compressor_new(0); - - while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { - Compressor_Process_int16(compressor, - (int16_t *)buffer, nbytes / 2); - - G_GNUC_UNUSED ssize_t ignored = write(1, buffer, nbytes); - } - - Compressor_delete(compressor); -} diff --git a/test/run_normalize.cxx b/test/run_normalize.cxx new file mode 100644 index 000000000..070dbaf5a --- /dev/null +++ b/test/run_normalize.cxx @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This program is a command line interface to MPD's normalize library + * (based on AudioCompress). + * + */ + +#include "config.h" +#include "AudioCompress/compress.h" +#include "AudioParser.hxx" +#include "audio_format.h" +#include "stdbin.h" + +#include <glib.h> + +#include <stddef.h> +#include <unistd.h> +#include <string.h> + +int main(int argc, char **argv) +{ + GError *error = NULL; + struct audio_format audio_format; + bool ret; + struct Compressor *compressor; + static char buffer[4096]; + ssize_t nbytes; + + if (argc > 2) { + g_printerr("Usage: run_normalize [FORMAT] <IN >OUT\n"); + return 1; + } + + if (argc > 1) { + ret = audio_format_parse(&audio_format, argv[1], + false, &error); + if (!ret) { + g_printerr("Failed to parse audio format: %s\n", + error->message); + return 1; + } + } else + audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2); + + compressor = Compressor_new(0); + + while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { + Compressor_Process_int16(compressor, + (int16_t *)buffer, nbytes / 2); + + G_GNUC_UNUSED ssize_t ignored = write(1, buffer, nbytes); + } + + Compressor_delete(compressor); +} diff --git a/test/run_output.c b/test/run_output.c deleted file mode 100644 index bbb1be7d2..000000000 --- a/test/run_output.c +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "io_thread.h" -#include "output_plugin.h" -#include "output_internal.h" -#include "output_control.h" -#include "conf.h" -#include "audio_parser.h" -#include "filter_registry.h" -#include "pcm_convert.h" -#include "event_pipe.h" -#include "idle.h" -#include "playlist.h" -#include "player_control.h" -#include "stdbin.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <unistd.h> -#include <stdlib.h> - -struct playlist g_playlist; - -void -idle_add(G_GNUC_UNUSED unsigned flags) -{ -} - -void -event_pipe_emit(G_GNUC_UNUSED enum pipe_event event) -{ -} - -void pcm_convert_init(G_GNUC_UNUSED struct pcm_convert_state *state) -{ -} - -void pcm_convert_deinit(G_GNUC_UNUSED struct pcm_convert_state *state) -{ -} - -const void * -pcm_convert(G_GNUC_UNUSED struct pcm_convert_state *state, - G_GNUC_UNUSED const struct audio_format *src_format, - G_GNUC_UNUSED const void *src, G_GNUC_UNUSED size_t src_size, - G_GNUC_UNUSED const struct audio_format *dest_format, - G_GNUC_UNUSED size_t *dest_size_r, - GError **error_r) -{ - g_set_error(error_r, pcm_convert_quark(), 0, - "Not implemented"); - return NULL; -} - -const struct filter_plugin * -filter_plugin_by_name(G_GNUC_UNUSED const char *name) -{ - assert(false); - return NULL; -} - -static const struct config_param * -find_named_config_block(const char *block, const char *name) -{ - const struct config_param *param = NULL; - - while ((param = config_get_next_param(block, param)) != NULL) { - const char *current_name = - config_get_block_string(param, "name", NULL); - if (current_name != NULL && strcmp(current_name, name) == 0) - return param; - } - - return NULL; -} - -static struct audio_output * -load_audio_output(const char *name) -{ - const struct config_param *param; - GError *error = NULL; - - param = find_named_config_block(CONF_AUDIO_OUTPUT, name); - if (param == NULL) { - g_printerr("No such configured audio output: %s\n", name); - return false; - } - - static struct player_control dummy_player_control; - - struct audio_output *ao = - audio_output_new(param, &dummy_player_control, &error); - if (ao == NULL) { - g_printerr("%s\n", error->message); - g_error_free(error); - } - - return ao; -} - -static bool -run_output(struct audio_output *ao, struct audio_format *audio_format) -{ - /* open the audio output */ - - GError *error = NULL; - if (!ao_plugin_enable(ao, &error)) { - g_printerr("Failed to enable audio output: %s\n", - error->message); - g_error_free(error); - return false; - } - - if (!ao_plugin_open(ao, audio_format, &error)) { - ao_plugin_disable(ao); - g_printerr("Failed to open audio output: %s\n", - error->message); - g_error_free(error); - return false; - } - - struct audio_format_string af_string; - g_printerr("audio_format=%s\n", - audio_format_to_string(audio_format, &af_string)); - - size_t frame_size = audio_format_frame_size(audio_format); - - /* play */ - - size_t length = 0; - char buffer[4096]; - while (true) { - if (length < sizeof(buffer)) { - ssize_t nbytes = read(0, buffer + length, - sizeof(buffer) - length); - if (nbytes <= 0) - break; - - length += (size_t)nbytes; - } - - size_t play_length = (length / frame_size) * frame_size; - if (play_length > 0) { - size_t consumed = ao_plugin_play(ao, - buffer, play_length, - &error); - if (consumed == 0) { - ao_plugin_close(ao); - ao_plugin_disable(ao); - g_printerr("Failed to play: %s\n", - error->message); - g_error_free(error); - return false; - } - - assert(consumed <= length); - assert(consumed % frame_size == 0); - - length -= consumed; - memmove(buffer, buffer + consumed, length); - } - } - - ao_plugin_close(ao); - ao_plugin_disable(ao); - return true; -} - -int main(int argc, char **argv) -{ - struct audio_format audio_format; - bool success; - GError *error = NULL; - - if (argc < 3 || argc > 4) { - g_printerr("Usage: run_output CONFIG NAME [FORMAT] <IN\n"); - return 1; - } - - audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); - - g_thread_init(NULL); - - /* read configuration file (mpd.conf) */ - - config_global_init(); - success = config_read_file(argv[1], &error); - if (!success) { - g_printerr("%s:", error->message); - g_error_free(error); - return 1; - } - - io_thread_init(); - if (!io_thread_start(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - /* initialize the audio output */ - - struct audio_output *ao = load_audio_output(argv[2]); - if (ao == NULL) - return 1; - - /* parse the audio format */ - - if (argc > 3) { - success = audio_format_parse(&audio_format, argv[3], - false, &error); - if (!success) { - g_printerr("Failed to parse audio format: %s\n", - error->message); - g_error_free(error); - return 1; - } - } - - /* do it */ - - success = run_output(ao, &audio_format); - - /* cleanup and exit */ - - audio_output_free(ao); - - io_thread_deinit(); - - config_global_finish(); - - return success ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/test/run_output.cxx b/test/run_output.cxx new file mode 100644 index 000000000..5b6d54a11 --- /dev/null +++ b/test/run_output.cxx @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "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 "FilterRegistry.hxx" +#include "PlayerControl.hxx" +#include "stdbin.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> + +EventLoop *main_loop; + +void +GlobalEvents::Emit(gcc_unused Event event) +{ +} + +PcmConvert::PcmConvert() {} +PcmConvert::~PcmConvert() {} + +const void * +PcmConvert::Convert(gcc_unused const 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"); + return NULL; +} + +const struct filter_plugin * +filter_plugin_by_name(G_GNUC_UNUSED const char *name) +{ + assert(false); + return NULL; +} + +static const struct config_param * +find_named_config_block(ConfigOption option, const char *name) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(option, param)) != NULL) { + const char *current_name = + config_get_block_string(param, "name", NULL); + if (current_name != NULL && strcmp(current_name, name) == 0) + return param; + } + + return NULL; +} + +player_control::player_control(gcc_unused unsigned _buffer_chunks, + gcc_unused unsigned _buffered_before_play) {} +player_control::~player_control() {} + +static struct audio_output * +load_audio_output(const char *name) +{ + const struct config_param *param; + GError *error = NULL; + + param = find_named_config_block(CONF_AUDIO_OUTPUT, name); + if (param == NULL) { + g_printerr("No such configured audio output: %s\n", name); + return nullptr; + } + + static struct player_control dummy_player_control(32, 4); + + struct audio_output *ao = + audio_output_new(param, &dummy_player_control, &error); + if (ao == NULL) { + g_printerr("%s\n", error->message); + g_error_free(error); + } + + return ao; +} + +static bool +run_output(struct audio_output *ao, struct audio_format *audio_format) +{ + /* open the audio output */ + + GError *error = NULL; + if (!ao_plugin_enable(ao, &error)) { + g_printerr("Failed to enable audio output: %s\n", + error->message); + g_error_free(error); + return false; + } + + if (!ao_plugin_open(ao, audio_format, &error)) { + ao_plugin_disable(ao); + g_printerr("Failed to open audio output: %s\n", + error->message); + g_error_free(error); + return false; + } + + struct audio_format_string af_string; + g_printerr("audio_format=%s\n", + audio_format_to_string(audio_format, &af_string)); + + size_t frame_size = audio_format_frame_size(audio_format); + + /* play */ + + size_t length = 0; + char buffer[4096]; + while (true) { + if (length < sizeof(buffer)) { + ssize_t nbytes = read(0, buffer + length, + sizeof(buffer) - length); + if (nbytes <= 0) + break; + + length += (size_t)nbytes; + } + + size_t play_length = (length / frame_size) * frame_size; + if (play_length > 0) { + size_t consumed = ao_plugin_play(ao, + buffer, play_length, + &error); + if (consumed == 0) { + ao_plugin_close(ao); + ao_plugin_disable(ao); + g_printerr("Failed to play: %s\n", + error->message); + g_error_free(error); + return false; + } + + assert(consumed <= length); + assert(consumed % frame_size == 0); + + length -= consumed; + memmove(buffer, buffer + consumed, length); + } + } + + ao_plugin_close(ao); + ao_plugin_disable(ao); + return true; +} + +int main(int argc, char **argv) +{ + struct audio_format audio_format; + bool success; + GError *error = NULL; + + if (argc < 3 || argc > 4) { + g_printerr("Usage: run_output CONFIG NAME [FORMAT] <IN\n"); + return 1; + } + + const Path config_path = Path::FromFS(argv[1]); + + audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); + + g_thread_init(NULL); + + /* read configuration file (mpd.conf) */ + + config_global_init(); + if (!ReadConfigFile(config_path, &error)) { + g_printerr("%s:", error->message); + g_error_free(error); + return 1; + } + + main_loop = new EventLoop(EventLoop::Default()); + + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + /* initialize the audio output */ + + struct audio_output *ao = load_audio_output(argv[2]); + if (ao == NULL) + return 1; + + /* parse the audio format */ + + if (argc > 3) { + success = audio_format_parse(&audio_format, argv[3], + false, &error); + if (!success) { + g_printerr("Failed to parse audio format: %s\n", + error->message); + g_error_free(error); + return 1; + } + } + + /* do it */ + + success = run_output(ao, &audio_format); + + /* cleanup and exit */ + + audio_output_free(ao); + + io_thread_deinit(); + + delete main_loop; + + config_global_finish(); + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/test/run_tcp_connect.c b/test/run_tcp_connect.c deleted file mode 100644 index bf8d9b82f..000000000 --- a/test/run_tcp_connect.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "resolver.h" -#include "io_thread.h" -#include "tcp_connect.h" -#include "fd_util.h" - -#include <assert.h> -#include <stdlib.h> - -#ifdef WIN32 -#include <ws2tcpip.h> -#include <winsock.h> -#else -#include <sys/socket.h> -#include <netdb.h> -#endif - -static struct tcp_connect *handle; -static GMutex *mutex; -static GCond *cond; -static bool done, success; - -static void -my_tcp_connect_success(int fd, G_GNUC_UNUSED void *ctx) -{ - assert(!done); - assert(!success); - - close_socket(fd); - g_print("success\n"); - - g_mutex_lock(mutex); - done = success = true; - g_cond_signal(cond); - g_mutex_unlock(mutex); -} - -static void -my_tcp_connect_error(GError *error, G_GNUC_UNUSED void *ctx) -{ - assert(!done); - assert(!success); - - g_printerr("error: %s\n", error->message); - g_error_free(error); - - g_mutex_lock(mutex); - done = true; - g_cond_signal(cond); - g_mutex_unlock(mutex); -} - -static void -my_tcp_connect_timeout(G_GNUC_UNUSED void *ctx) -{ - assert(!done); - assert(!success); - - g_printerr("timeout\n"); - - g_mutex_lock(mutex); - done = true; - g_cond_signal(cond); - g_mutex_unlock(mutex); -} - -static void -my_tcp_connect_canceled(G_GNUC_UNUSED void *ctx) -{ - assert(!done); - assert(!success); - - g_printerr("canceled\n"); - - g_mutex_lock(mutex); - done = true; - g_cond_signal(cond); - g_mutex_unlock(mutex); -} - -static const struct tcp_connect_handler my_tcp_connect_handler = { - .success = my_tcp_connect_success, - .error = my_tcp_connect_error, - .timeout = my_tcp_connect_timeout, - .canceled = my_tcp_connect_canceled, -}; - -int main(int argc, char **argv) -{ - if (argc != 2) { - g_printerr("Usage: run_tcp_connect IP:PORT\n"); - return 1; - } - - GError *error = NULL; - struct addrinfo *ai = resolve_host_port(argv[1], 80, 0, SOCK_STREAM, - &error); - if (ai == NULL) { - g_printerr("%s\n", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - /* initialize GLib */ - - g_thread_init(NULL); - - /* initialize MPD */ - - io_thread_init(); - if (!io_thread_start(&error)) { - freeaddrinfo(ai); - g_printerr("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - /* open the connection */ - - mutex = g_mutex_new(); - cond = g_cond_new(); - - tcp_connect_address(ai->ai_addr, ai->ai_addrlen, 5000, - &my_tcp_connect_handler, NULL, - &handle); - freeaddrinfo(ai); - - if (handle != NULL) { - g_mutex_lock(mutex); - while (!done) - g_cond_wait(cond, mutex); - g_mutex_unlock(mutex); - - tcp_connect_free(handle); - } - - g_cond_free(cond); - g_mutex_free(mutex); - - /* deinitialize everything */ - - io_thread_deinit(); - - return EXIT_SUCCESS; -} diff --git a/test/software_volume.c b/test/software_volume.c deleted file mode 100644 index 2357da672..000000000 --- a/test/software_volume.c +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This program is a command line interface to MPD's software volume - * library (pcm_volume.c). - * - */ - -#include "config.h" -#include "pcm_volume.h" -#include "audio_parser.h" -#include "audio_format.h" -#include "stdbin.h" - -#include <glib.h> - -#include <stddef.h> -#include <unistd.h> - -int main(int argc, char **argv) -{ - GError *error = NULL; - struct audio_format audio_format; - bool ret; - static char buffer[4096]; - ssize_t nbytes; - - if (argc > 2) { - g_printerr("Usage: software_volume [FORMAT] <IN >OUT\n"); - return 1; - } - - if (argc > 1) { - ret = audio_format_parse(&audio_format, argv[1], - false, &error); - if (!ret) { - g_printerr("Failed to parse audio format: %s\n", - error->message); - return 1; - } - } else - audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2); - - while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { - if (!pcm_volume(buffer, nbytes, audio_format.format, - PCM_VOLUME_1 / 2)) { - g_printerr("pcm_volume() has failed\n"); - return 2; - } - - G_GNUC_UNUSED ssize_t ignored = write(1, buffer, nbytes); - } -} diff --git a/test/software_volume.cxx b/test/software_volume.cxx new file mode 100644 index 000000000..929932398 --- /dev/null +++ b/test/software_volume.cxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This program is a command line interface to MPD's software volume + * library (pcm_volume.c). + * + */ + +#include "config.h" +#include "PcmVolume.hxx" +#include "AudioParser.hxx" +#include "audio_format.h" +#include "stdbin.h" + +#include <glib.h> + +#include <stddef.h> +#include <unistd.h> + +int main(int argc, char **argv) +{ + GError *error = NULL; + struct audio_format audio_format; + bool ret; + static char buffer[4096]; + ssize_t nbytes; + + if (argc > 2) { + g_printerr("Usage: software_volume [FORMAT] <IN >OUT\n"); + return 1; + } + + if (argc > 1) { + ret = audio_format_parse(&audio_format, argv[1], + false, &error); + if (!ret) { + g_printerr("Failed to parse audio format: %s\n", + error->message); + return 1; + } + } else + audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2); + + while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { + if (!pcm_volume(buffer, nbytes, + sample_format(audio_format.format), + PCM_VOLUME_1 / 2)) { + g_printerr("pcm_volume() has failed\n"); + return 2; + } + + G_GNUC_UNUSED ssize_t ignored = write(1, buffer, nbytes); + } +} diff --git a/test/test_pcm_all.h b/test/test_pcm_all.h deleted file mode 100644 index 493619b55..000000000 --- a/test/test_pcm_all.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TEST_PCM_ALL_H -#define MPD_TEST_PCM_ALL_H - -void -test_pcm_dither_24(void); - -void -test_pcm_dither_32(void); - -void -test_pcm_pack_24(void); - -void -test_pcm_unpack_24(void); - -void -test_pcm_channels_16(void); - -void -test_pcm_channels_32(void); - -void -test_pcm_volume_8(void); - -void -test_pcm_volume_16(void); - -void -test_pcm_volume_24(void); - -void -test_pcm_volume_32(void); - -void -test_pcm_volume_float(void); - -#endif diff --git a/test/test_pcm_all.hxx b/test/test_pcm_all.hxx new file mode 100644 index 000000000..18202454b --- /dev/null +++ b/test/test_pcm_all.hxx @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TEST_PCM_ALL_HXX +#define MPD_TEST_PCM_ALL_HXX + +void +test_pcm_dither_24(); + +void +test_pcm_dither_32(); + +void +test_pcm_pack_24(); + +void +test_pcm_unpack_24(); + +void +test_pcm_channels_16(); + +void +test_pcm_channels_32(); + +void +test_pcm_volume_8(); + +void +test_pcm_volume_16(); + +void +test_pcm_volume_24(); + +void +test_pcm_volume_32(); + +void +test_pcm_volume_float(); + +void +test_pcm_format_8_to_16(); + +void +test_pcm_format_16_to_24(); + +void +test_pcm_format_16_to_32(); + +void +test_pcm_format_float(); + +void +test_pcm_mix_8(); + +void +test_pcm_mix_16(); + +void +test_pcm_mix_24(); + +void +test_pcm_mix_32(); + +#endif diff --git a/test/test_pcm_channels.c b/test/test_pcm_channels.c deleted file mode 100644 index fb3ec5c3e..000000000 --- a/test/test_pcm_channels.c +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "test_pcm_all.h" -#include "pcm_channels.h" -#include "pcm_buffer.h" - -#include <glib.h> - -void -test_pcm_channels_16(void) -{ - enum { N = 256 }; - int16_t src[N * 2]; - - for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i) - src[i] = g_random_int(); - - struct pcm_buffer buffer; - pcm_buffer_init(&buffer); - - /* stereo to mono */ - - size_t dest_size; - const int16_t *dest = - pcm_convert_channels_16(&buffer, 1, 2, src, sizeof(src), - &dest_size); - g_assert(dest != NULL); - g_assert_cmpint(dest_size, ==, sizeof(src) / 2); - for (unsigned i = 0; i < N; ++i) - g_assert_cmpint(dest[i], ==, - (src[i * 2] + src[i * 2 + 1]) / 2); - - /* mono to stereo */ - - dest = pcm_convert_channels_16(&buffer, 2, 1, src, sizeof(src), - &dest_size); - g_assert(dest != NULL); - g_assert_cmpint(dest_size, ==, sizeof(src) * 2); - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i * 2], ==, src[i]); - g_assert_cmpint(dest[i * 2 + 1], ==, src[i]); - } - - pcm_buffer_deinit(&buffer); -} - -void -test_pcm_channels_32(void) -{ - enum { N = 256 }; - int32_t src[N * 2]; - - for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i) - src[i] = g_random_int(); - - struct pcm_buffer buffer; - pcm_buffer_init(&buffer); - - /* stereo to mono */ - - size_t dest_size; - const int32_t *dest = - pcm_convert_channels_32(&buffer, 1, 2, src, sizeof(src), - &dest_size); - g_assert(dest != NULL); - g_assert_cmpint(dest_size, ==, sizeof(src) / 2); - for (unsigned i = 0; i < N; ++i) - g_assert_cmpint(dest[i], ==, - ((int64_t)src[i * 2] + (int64_t)src[i * 2 + 1]) / 2); - - /* mono to stereo */ - - dest = pcm_convert_channels_32(&buffer, 2, 1, src, sizeof(src), - &dest_size); - g_assert(dest != NULL); - g_assert_cmpint(dest_size, ==, sizeof(src) * 2); - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i * 2], ==, src[i]); - g_assert_cmpint(dest[i * 2 + 1], ==, src[i]); - } - - pcm_buffer_deinit(&buffer); -} diff --git a/test/test_pcm_channels.cxx b/test/test_pcm_channels.cxx new file mode 100644 index 000000000..8e013d5af --- /dev/null +++ b/test/test_pcm_channels.cxx @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "test_pcm_all.hxx" +#include "test_pcm_util.hxx" +#include "PcmChannels.hxx" +#include "pcm_buffer.h" + +#include <glib.h> + +void +test_pcm_channels_16() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int16_t, N * 2>(); + + struct pcm_buffer buffer; + pcm_buffer_init(&buffer); + + /* stereo to mono */ + + size_t dest_size; + const int16_t *dest = + pcm_convert_channels_16(&buffer, 1, 2, src, sizeof(src), + &dest_size); + g_assert(dest != NULL); + g_assert_cmpint(dest_size, ==, sizeof(src) / 2); + for (unsigned i = 0; i < N; ++i) + g_assert_cmpint(dest[i], ==, + (src[i * 2] + src[i * 2 + 1]) / 2); + + /* mono to stereo */ + + dest = pcm_convert_channels_16(&buffer, 2, 1, src, sizeof(src), + &dest_size); + g_assert(dest != NULL); + g_assert_cmpint(dest_size, ==, sizeof(src) * 2); + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i * 2], ==, src[i]); + g_assert_cmpint(dest[i * 2 + 1], ==, src[i]); + } + + pcm_buffer_deinit(&buffer); +} + +void +test_pcm_channels_32() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int32_t, N * 2>(); + + struct pcm_buffer buffer; + pcm_buffer_init(&buffer); + + /* stereo to mono */ + + size_t dest_size; + const int32_t *dest = + pcm_convert_channels_32(&buffer, 1, 2, src, sizeof(src), + &dest_size); + g_assert(dest != NULL); + g_assert_cmpint(dest_size, ==, sizeof(src) / 2); + for (unsigned i = 0; i < N; ++i) + g_assert_cmpint(dest[i], ==, + ((int64_t)src[i * 2] + (int64_t)src[i * 2 + 1]) / 2); + + /* mono to stereo */ + + dest = pcm_convert_channels_32(&buffer, 2, 1, src, sizeof(src), + &dest_size); + g_assert(dest != NULL); + g_assert_cmpint(dest_size, ==, sizeof(src) * 2); + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i * 2], ==, src[i]); + g_assert_cmpint(dest[i * 2 + 1], ==, src[i]); + } + + pcm_buffer_deinit(&buffer); +} diff --git a/test/test_pcm_dither.c b/test/test_pcm_dither.c deleted file mode 100644 index 44d105207..000000000 --- a/test/test_pcm_dither.c +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "test_pcm_all.h" -#include "pcm_dither.h" - -#include <glib.h> - -/** - * Generate a random 24 bit PCM sample. - */ -static int32_t -random24(void) -{ - int32_t x = g_random_int() & 0xffffff; - if (x & 0x800000) - x |= 0xff000000; - return x; -} - -void -test_pcm_dither_24(void) -{ - struct pcm_dither dither; - - pcm_dither_24_init(&dither); - - enum { N = 256 }; - int32_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = random24(); - - int16_t dest[N]; - - pcm_dither_24_to_16(&dither, dest, src, src + N); - - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i], >=, (src[i] >> 8) - 8); - g_assert_cmpint(dest[i], <, (src[i] >> 8) + 8); - } -} - -void -test_pcm_dither_32(void) -{ - struct pcm_dither dither; - - pcm_dither_24_init(&dither); - - enum { N = 256 }; - int32_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_int(); - - int16_t dest[N]; - - pcm_dither_32_to_16(&dither, dest, src, src + N); - - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i], >=, (src[i] >> 16) - 8); - g_assert_cmpint(dest[i], <, (src[i] >> 16) + 8); - } -} diff --git a/test/test_pcm_dither.cxx b/test/test_pcm_dither.cxx new file mode 100644 index 000000000..2fb976db5 --- /dev/null +++ b/test/test_pcm_dither.cxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "test_pcm_all.hxx" +#include "test_pcm_util.hxx" +#include "PcmDither.hxx" + +#include <glib.h> + +void +test_pcm_dither_24() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int32_t, N>(GlibRandomInt24()); + + int16_t dest[N]; + PcmDither dither; + dither.Dither24To16(dest, src.begin(), src.end()); + + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i], >=, (src[i] >> 8) - 8); + g_assert_cmpint(dest[i], <, (src[i] >> 8) + 8); + } +} + +void +test_pcm_dither_32() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int32_t, N>(); + + int16_t dest[N]; + PcmDither dither; + dither.Dither32To16(dest, src.begin(), src.end()); + + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i], >=, (src[i] >> 16) - 8); + g_assert_cmpint(dest[i], <, (src[i] >> 16) + 8); + } +} diff --git a/test/test_pcm_format.cxx b/test/test_pcm_format.cxx new file mode 100644 index 000000000..f175a850c --- /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.c deleted file mode 100644 index a483baaab..000000000 --- a/test/test_pcm_main.c +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "test_pcm_all.h" - -#include <glib.h> - -int -main(int argc, char **argv) -{ - g_test_init (&argc, &argv, NULL); - g_test_add_func("/pcm/dither/24", test_pcm_dither_24); - g_test_add_func("/pcm/dither/32", test_pcm_dither_32); - g_test_add_func("/pcm/pack/pack24", test_pcm_pack_24); - g_test_add_func("/pcm/pack/unpack24", test_pcm_unpack_24); - g_test_add_func("/pcm/channels/16", test_pcm_channels_16); - g_test_add_func("/pcm/channels/32", test_pcm_channels_32); - - g_test_add_func("/pcm/volume/8", test_pcm_volume_8); - g_test_add_func("/pcm/volume/16", test_pcm_volume_16); - g_test_add_func("/pcm/volume/24", test_pcm_volume_24); - g_test_add_func("/pcm/volume/32", test_pcm_volume_32); - g_test_add_func("/pcm/volume/float", test_pcm_volume_float); - - g_test_run(); -} diff --git a/test/test_pcm_main.cxx b/test/test_pcm_main.cxx new file mode 100644 index 000000000..a221b26af --- /dev/null +++ b/test/test_pcm_main.cxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "test_pcm_all.hxx" + +#include <glib.h> + +int +main(int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + g_test_add_func("/pcm/dither/24", test_pcm_dither_24); + g_test_add_func("/pcm/dither/32", test_pcm_dither_32); + g_test_add_func("/pcm/pack/pack24", test_pcm_pack_24); + g_test_add_func("/pcm/pack/unpack24", test_pcm_unpack_24); + g_test_add_func("/pcm/channels/16", test_pcm_channels_16); + g_test_add_func("/pcm/channels/32", test_pcm_channels_32); + + g_test_add_func("/pcm/volume/8", test_pcm_volume_8); + g_test_add_func("/pcm/volume/16", test_pcm_volume_16); + g_test_add_func("/pcm/volume/24", test_pcm_volume_24); + g_test_add_func("/pcm/volume/32", test_pcm_volume_32); + g_test_add_func("/pcm/volume/float", test_pcm_volume_float); + + g_test_add_func("/pcm/format/8_to_16", test_pcm_format_8_to_16); + g_test_add_func("/pcm/format/16_to_24", test_pcm_format_16_to_24); + g_test_add_func("/pcm/format/16_to_32", test_pcm_format_16_to_32); + g_test_add_func("/pcm/format/float", test_pcm_format_float); + + g_test_add_func("/pcm/mix/8", test_pcm_mix_8); + g_test_add_func("/pcm/mix/16", test_pcm_mix_16); + g_test_add_func("/pcm/mix/24", test_pcm_mix_24); + g_test_add_func("/pcm/mix/32", test_pcm_mix_32); + + g_test_run(); +} diff --git a/test/test_pcm_mix.cxx b/test/test_pcm_mix.cxx new file mode 100644 index 000000000..a6e01d20f --- /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.c deleted file mode 100644 index 5127536fb..000000000 --- a/test/test_pcm_pack.c +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "test_pcm_all.h" -#include "pcm_pack.h" - -#include <glib.h> - -/** - * Generate a random 24 bit PCM sample. - */ -static int32_t -random24(void) -{ - int32_t x = g_random_int() & 0xffffff; - if (x & 0x800000) - x |= 0xff000000; - return x; -} - -void -test_pcm_pack_24(void) -{ - enum { N = 256 }; - int32_t src[N * 3]; - for (unsigned i = 0; i < N; ++i) - src[i] = random24(); - - uint8_t dest[N * 3]; - - pcm_pack_24(dest, src, src + N); - - for (unsigned i = 0; i < N; ++i) { - int32_t d; - if (G_BYTE_ORDER == G_BIG_ENDIAN) - d = (dest[i * 3] << 16) | (dest[i * 3 + 1] << 8) - | dest[i * 3 + 2]; - else - d = (dest[i * 3 + 2] << 16) | (dest[i * 3 + 1] << 8) - | dest[i * 3]; - if (d & 0x800000) - d |= 0xff000000; - - g_assert_cmpint(d, ==, src[i]); - } -} - -void -test_pcm_unpack_24(void) -{ - enum { N = 256 }; - uint8_t src[N * 3]; - for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i) - src[i] = g_random_int_range(0, 256); - - int32_t dest[N]; - - pcm_unpack_24(dest, src, src + G_N_ELEMENTS(src)); - - for (unsigned i = 0; i < N; ++i) { - int32_t s; - if (G_BYTE_ORDER == G_BIG_ENDIAN) - s = (src[i * 3] << 16) | (src[i * 3 + 1] << 8) - | src[i * 3 + 2]; - else - s = (src[i * 3 + 2] << 16) | (src[i * 3 + 1] << 8) - | src[i * 3]; - if (s & 0x800000) - s |= 0xff000000; - - g_assert_cmpint(s, ==, dest[i]); - } -} diff --git a/test/test_pcm_pack.cxx b/test/test_pcm_pack.cxx new file mode 100644 index 000000000..e23acad58 --- /dev/null +++ b/test/test_pcm_pack.cxx @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "test_pcm_all.hxx" +#include "test_pcm_util.hxx" + +extern "C" { +#include "pcm_pack.h" +} + +#include <glib.h> + +void +test_pcm_pack_24() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int32_t, N>(GlibRandomInt24()); + + uint8_t dest[N * 3]; + pcm_pack_24(dest, src.begin(), src.end()); + + for (unsigned i = 0; i < N; ++i) { + int32_t d; + if (G_BYTE_ORDER == G_BIG_ENDIAN) + d = (dest[i * 3] << 16) | (dest[i * 3 + 1] << 8) + | dest[i * 3 + 2]; + else + d = (dest[i * 3 + 2] << 16) | (dest[i * 3 + 1] << 8) + | dest[i * 3]; + if (d & 0x800000) + d |= 0xff000000; + + g_assert_cmpint(d, ==, src[i]); + } +} + +void +test_pcm_unpack_24() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<uint8_t, N * 3>(); + + int32_t dest[N]; + pcm_unpack_24(dest, src.begin(), src.end()); + + for (unsigned i = 0; i < N; ++i) { + int32_t s; + if (G_BYTE_ORDER == G_BIG_ENDIAN) + s = (src[i * 3] << 16) | (src[i * 3 + 1] << 8) + | src[i * 3 + 2]; + else + s = (src[i * 3 + 2] << 16) | (src[i * 3 + 1] << 8) + | src[i * 3]; + if (s & 0x800000) + s |= 0xff000000; + + g_assert_cmpint(s, ==, dest[i]); + } +} diff --git a/test/test_pcm_util.hxx b/test/test_pcm_util.hxx new file mode 100644 index 000000000..84ba074fd --- /dev/null +++ b/test/test_pcm_util.hxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <glib.h> + +#include <array> + +#include <stddef.h> +#include <stdint.h> + +template<typename T> +struct GlibRandomInt { + T operator()() const { + return T(g_random_int()); + } +}; + +struct GlibRandomInt24 : GlibRandomInt<int32_t> { + int32_t operator()() const { + auto t = GlibRandomInt::operator()(); + t &= 0xffffff; + if (t & 0x800000) + t |= 0xff000000; + return t; + } +}; + +struct GlibRandomFloat { + float operator()() const { + return g_random_double_range(-1.0, 1.0); + } +}; + +template<typename T, size_t N> +class TestDataBuffer : std::array<T, N> { +public: + using typename std::array<T, N>::const_pointer; + using std::array<T, N>::size; + using std::array<T, N>::begin; + using std::array<T, N>::end; + using std::array<T, N>::operator[]; + + template<typename G=GlibRandomInt<T>> + TestDataBuffer(G g=G()):std::array<T, N>() { + for (auto &i : *this) + i = g(); + + } + + operator typename std::array<T, N>::const_pointer() const { + return begin(); + } +}; + +template<typename T> +bool +AssertEqualWithTolerance(const T &a, const T &b, unsigned tolerance) +{ + g_assert_cmpint(a.size(), ==, b.size()); + + for (unsigned i = 0; i < a.size(); ++i) { + int64_t x = a[i], y = b[i]; + + g_assert_cmpint(x, >=, y - int64_t(tolerance)); + g_assert_cmpint(x, <=, y + int64_t(tolerance)); + } + + return true; +} diff --git a/test/test_pcm_volume.c b/test/test_pcm_volume.c deleted file mode 100644 index 713645cf1..000000000 --- a/test/test_pcm_volume.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "test_pcm_all.h" -#include "pcm_volume.h" - -#include <glib.h> - -#include <string.h> - -void -test_pcm_volume_8(void) -{ - enum { N = 256 }; - static const int8_t zero[N]; - int8_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_int(); - - int8_t dest[N]; - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8, - 0), ==, true); - g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8, - PCM_VOLUME_1), ==, true); - g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8, - PCM_VOLUME_1 / 2), ==, true); - - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); - g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); - } -} - -void -test_pcm_volume_16(void) -{ - enum { N = 256 }; - static const int16_t zero[N]; - int16_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_int(); - - int16_t dest[N]; - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16, - 0), ==, true); - g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16, - PCM_VOLUME_1), ==, true); - g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16, - PCM_VOLUME_1 / 2), ==, true); - - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); - g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); - } -} - -/** - * Generate a random 24 bit PCM sample. - */ -static int32_t -random24(void) -{ - int32_t x = g_random_int() & 0xffffff; - if (x & 0x800000) - x |= 0xff000000; - return x; -} - -void -test_pcm_volume_24(void) -{ - enum { N = 256 }; - static const int32_t zero[N]; - int32_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = random24(); - - int32_t dest[N]; - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32, - 0), ==, true); - g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32, - PCM_VOLUME_1), ==, true); - g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32, - PCM_VOLUME_1 / 2), ==, true); - - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); - g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); - } -} - -void -test_pcm_volume_32(void) -{ - enum { N = 256 }; - static const int32_t zero[N]; - int32_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_int(); - - int32_t dest[N]; - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32, - 0), ==, true); - g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32, - PCM_VOLUME_1), ==, true); - g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32, - PCM_VOLUME_1 / 2), ==, true); - - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); - g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); - } -} - -void -test_pcm_volume_float(void) -{ - enum { N = 256 }; - static const float zero[N]; - float src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_double_range(-1.0, 1.0); - - float dest[N]; - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT, - 0), ==, true); - g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT, - PCM_VOLUME_1), ==, true); - g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT, - PCM_VOLUME_1 / 2), ==, true); - - for (unsigned i = 0; i < N; ++i) - g_assert_cmpfloat(dest[i], ==, src[i] / 2); -} diff --git a/test/test_pcm_volume.cxx b/test/test_pcm_volume.cxx new file mode 100644 index 000000000..1ab590490 --- /dev/null +++ b/test/test_pcm_volume.cxx @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "test_pcm_all.hxx" +#include "PcmVolume.hxx" +#include "test_pcm_util.hxx" + +#include <glib.h> + +#include <algorithm> + +#include <string.h> + +void +test_pcm_volume_8() +{ + constexpr unsigned N = 256; + static int8_t zero[N]; + const auto src = TestDataBuffer<int8_t, N>(); + + int8_t dest[N]; + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8, + 0), ==, true); + g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8, + PCM_VOLUME_1), ==, true); + g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8, + PCM_VOLUME_1 / 2), ==, true); + + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); + g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); + } +} + +void +test_pcm_volume_16() +{ + constexpr unsigned N = 256; + static int16_t zero[N]; + const auto src = TestDataBuffer<int16_t, N>(); + + int16_t dest[N]; + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16, + 0), ==, true); + g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16, + PCM_VOLUME_1), ==, true); + g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16, + PCM_VOLUME_1 / 2), ==, true); + + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); + g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); + } +} + +void +test_pcm_volume_24() +{ + constexpr unsigned N = 256; + static int32_t zero[N]; + const auto src = TestDataBuffer<int32_t, N>(GlibRandomInt24()); + + int32_t dest[N]; + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32, + 0), ==, true); + g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32, + PCM_VOLUME_1), ==, true); + g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32, + PCM_VOLUME_1 / 2), ==, true); + + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); + g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); + } +} + +void +test_pcm_volume_32() +{ + constexpr unsigned N = 256; + static int32_t zero[N]; + const auto src = TestDataBuffer<int32_t, N>(); + + int32_t dest[N]; + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32, + 0), ==, true); + g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32, + PCM_VOLUME_1), ==, true); + g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32, + PCM_VOLUME_1 / 2), ==, true); + + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); + g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); + } +} + +void +test_pcm_volume_float() +{ + constexpr unsigned N = 256; + static float zero[N]; + const auto src = TestDataBuffer<float, N>(GlibRandomFloat()); + + float dest[N]; + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT, + 0), ==, true); + g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT, + PCM_VOLUME_1), ==, true); + g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT, + PCM_VOLUME_1 / 2), ==, true); + + for (unsigned i = 0; i < N; ++i) + g_assert_cmpfloat(dest[i], ==, src[i] / 2); +} diff --git a/test/test_queue_priority.c b/test/test_queue_priority.c deleted file mode 100644 index 5543edbba..000000000 --- a/test/test_queue_priority.c +++ /dev/null @@ -1,175 +0,0 @@ -#include "queue.h" -#include "song.h" - -void -song_free(G_GNUC_UNUSED struct song *song) -{ -} - -G_GNUC_UNUSED -static void -dump_order(const struct queue *queue) -{ - g_printerr("queue length=%u, order:\n", queue_length(queue)); - for (unsigned i = 0; i < queue_length(queue); ++i) - g_printerr(" [%u] -> %u (prio=%u)\n", i, queue->order[i], - queue->items[queue->order[i]].priority); -} - -static void -check_descending_priority(G_GNUC_UNUSED const struct queue *queue, - unsigned start_order) -{ - assert(start_order < queue_length(queue)); - - uint8_t last_priority = 0xff; - for (unsigned order = start_order; order < queue_length(queue); ++order) { - unsigned position = queue_order_to_position(queue, order); - uint8_t priority = queue->items[position].priority; - assert(priority <= last_priority); - (void)last_priority; - last_priority = priority; - } -} - -int -main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) -{ - struct song songs[16]; - - struct queue queue; - queue_init(&queue, 32); - - for (unsigned i = 0; i < G_N_ELEMENTS(songs); ++i) - queue_append(&queue, &songs[i], 0); - - assert(queue_length(&queue) == G_N_ELEMENTS(songs)); - - /* priority=10 for 4 items */ - - queue_set_priority_range(&queue, 4, 8, 10, -1); - - queue.random = true; - queue_shuffle_order(&queue); - check_descending_priority(&queue, 0); - - for (unsigned i = 0; i < 4; ++i) { - assert(queue_position_to_order(&queue, i) >= 4); - } - - for (unsigned i = 4; i < 8; ++i) { - assert(queue_position_to_order(&queue, i) < 4); - } - - for (unsigned i = 8; i < G_N_ELEMENTS(songs); ++i) { - assert(queue_position_to_order(&queue, i) >= 4); - } - - /* priority=50 one more item */ - - queue_set_priority_range(&queue, 15, 16, 50, -1); - check_descending_priority(&queue, 0); - - assert(queue_position_to_order(&queue, 15) == 0); - - for (unsigned i = 0; i < 4; ++i) { - assert(queue_position_to_order(&queue, i) >= 4); - } - - for (unsigned i = 4; i < 8; ++i) { - assert(queue_position_to_order(&queue, i) >= 1 && - queue_position_to_order(&queue, i) < 5); - } - - for (unsigned i = 8; i < 15; ++i) { - assert(queue_position_to_order(&queue, i) >= 5); - } - - /* priority=20 for one of the 4 priority=10 items */ - - queue_set_priority_range(&queue, 3, 4, 20, -1); - check_descending_priority(&queue, 0); - - assert(queue_position_to_order(&queue, 3) == 1); - assert(queue_position_to_order(&queue, 15) == 0); - - for (unsigned i = 0; i < 3; ++i) { - assert(queue_position_to_order(&queue, i) >= 5); - } - - for (unsigned i = 4; i < 8; ++i) { - assert(queue_position_to_order(&queue, i) >= 2 && - queue_position_to_order(&queue, i) < 6); - } - - for (unsigned i = 8; i < 15; ++i) { - assert(queue_position_to_order(&queue, i) >= 6); - } - - /* priority=20 for another one of the 4 priority=10 items; - pass "after_order" (with priority=10) and see if it's moved - after that one */ - - unsigned current_order = 4; - unsigned current_position = - queue_order_to_position(&queue, current_order); - - unsigned a_order = 3; - unsigned a_position = queue_order_to_position(&queue, a_order); - assert(queue.items[a_position].priority == 10); - queue_set_priority(&queue, a_position, 20, current_order); - - current_order = queue_position_to_order(&queue, current_position); - assert(current_order == 3); - - a_order = queue_position_to_order(&queue, a_position); - assert(a_order == 4); - - check_descending_priority(&queue, current_order + 1); - - /* priority=70 for one of the last items; must be inserted - right after the current song, before the priority=20 one we - just created */ - - unsigned b_order = 10; - unsigned b_position = queue_order_to_position(&queue, b_order); - assert(queue.items[b_position].priority == 0); - queue_set_priority(&queue, b_position, 70, current_order); - - current_order = queue_position_to_order(&queue, current_position); - assert(current_order == 3); - - b_order = queue_position_to_order(&queue, b_position); - assert(b_order == 4); - - check_descending_priority(&queue, current_order + 1); - - /* priority=60 for the old prio50 item; must not be moved, - because it's before the current song, and it's status - hasn't changed (it was already higher before) */ - - unsigned c_order = 0; - unsigned c_position = queue_order_to_position(&queue, c_order); - assert(queue.items[c_position].priority == 50); - queue_set_priority(&queue, c_position, 60, current_order); - - current_order = queue_position_to_order(&queue, current_position); - assert(current_order == 3); - - c_order = queue_position_to_order(&queue, c_position); - assert(c_order == 0); - - /* move the prio=20 item back */ - - a_order = queue_position_to_order(&queue, a_position); - assert(a_order == 5); - assert(queue.items[a_position].priority == 20); - queue_set_priority(&queue, a_position, 5, current_order); - - - current_order = queue_position_to_order(&queue, current_position); - assert(current_order == 3); - - a_order = queue_position_to_order(&queue, a_position); - assert(a_order == 6); -} diff --git a/test/test_queue_priority.cxx b/test/test_queue_priority.cxx new file mode 100644 index 000000000..8dd1c3f5f --- /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.c deleted file mode 100644 index 619399159..000000000 --- a/test/test_vorbis_encoder.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "encoder_list.h" -#include "encoder_plugin.h" -#include "audio_format.h" -#include "conf.h" -#include "stdbin.h" -#include "tag.h" - -#include <glib.h> - -#include <stddef.h> -#include <unistd.h> - -static uint8_t zero[256]; - -static void -encoder_to_stdout(struct encoder *encoder) -{ - size_t length; - static char buffer[32768]; - - while ((length = encoder_read(encoder, buffer, sizeof(buffer))) > 0) { - G_GNUC_UNUSED ssize_t ignored = write(1, buffer, length); - } -} - -int -main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) -{ - G_GNUC_UNUSED bool success; - - /* create the encoder */ - - const struct encoder_plugin *plugin = encoder_plugin_get("vorbis"); - assert(plugin != NULL); - - struct config_param *param = config_new_param(NULL, -1); - config_add_block_param(param, "quality", "5.0", -1); - - struct encoder *encoder = encoder_init(plugin, param, NULL); - assert(encoder != NULL); - - /* open the encoder */ - - struct audio_format audio_format; - - audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); - success = encoder_open(encoder, &audio_format, NULL); - assert(success); - - encoder_to_stdout(encoder); - - /* write a block of data */ - - success = encoder_write(encoder, zero, sizeof(zero), NULL); - assert(success); - - encoder_to_stdout(encoder); - - /* write a tag */ - - success = encoder_pre_tag(encoder, NULL); - assert(success); - - encoder_to_stdout(encoder); - - struct tag *tag = tag_new(); - tag_add_item(tag, TAG_ARTIST, "Foo"); - tag_add_item(tag, TAG_TITLE, "Bar"); - - success = encoder_tag(encoder, tag, NULL); - assert(success); - - tag_free(tag); - - encoder_to_stdout(encoder); - - /* write another block of data */ - - success = encoder_write(encoder, zero, sizeof(zero), NULL); - assert(success); - - /* finish */ - - success = encoder_end(encoder, NULL); - assert(success); - - encoder_to_stdout(encoder); - - encoder_close(encoder); - encoder_finish(encoder); -} diff --git a/test/test_vorbis_encoder.cxx b/test/test_vorbis_encoder.cxx new file mode 100644 index 000000000..0e502b154 --- /dev/null +++ b/test/test_vorbis_encoder.cxx @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "encoder_list.h" +#include "encoder_plugin.h" +#include "audio_format.h" +#include "conf.h" +#include "stdbin.h" +#include "tag.h" + +#include <glib.h> + +#include <stddef.h> +#include <unistd.h> + +static uint8_t zero[256]; + +static void +encoder_to_stdout(struct encoder *encoder) +{ + size_t length; + static char buffer[32768]; + + while ((length = encoder_read(encoder, buffer, sizeof(buffer))) > 0) { + G_GNUC_UNUSED ssize_t ignored = write(1, buffer, length); + } +} + +int +main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) +{ + G_GNUC_UNUSED bool success; + + /* create the encoder */ + + const struct encoder_plugin *plugin = encoder_plugin_get("vorbis"); + assert(plugin != NULL); + + config_param param; + param.AddBlockParam("quality", "5.0", -1); + + struct encoder *encoder = encoder_init(plugin, ¶m, NULL); + assert(encoder != NULL); + + /* open the encoder */ + + struct audio_format audio_format; + + audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); + success = encoder_open(encoder, &audio_format, NULL); + assert(success); + + encoder_to_stdout(encoder); + + /* write a block of data */ + + success = encoder_write(encoder, zero, sizeof(zero), NULL); + assert(success); + + encoder_to_stdout(encoder); + + /* write a tag */ + + success = encoder_pre_tag(encoder, NULL); + assert(success); + + encoder_to_stdout(encoder); + + struct tag *tag = tag_new(); + tag_add_item(tag, TAG_ARTIST, "Foo"); + tag_add_item(tag, TAG_TITLE, "Bar"); + + success = encoder_tag(encoder, tag, NULL); + assert(success); + + tag_free(tag); + + encoder_to_stdout(encoder); + + /* write another block of data */ + + success = encoder_write(encoder, zero, sizeof(zero), NULL); + assert(success); + + /* finish */ + + success = encoder_end(encoder, NULL); + assert(success); + + encoder_to_stdout(encoder); + + encoder_close(encoder); + encoder_finish(encoder); +} diff --git a/test/visit_archive.cxx b/test/visit_archive.cxx new file mode 100644 index 000000000..6faf4f3ae --- /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 2cce34186..56bb8e62f 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 |