diff options
-rw-r--r-- | INSTALL | 8 | ||||
-rw-r--r-- | Makefile.am | 371 | ||||
-rw-r--r-- | NEWS | 14 | ||||
-rw-r--r-- | configure.ac | 122 | ||||
-rw-r--r-- | doc/user.xml | 131 | ||||
-rw-r--r-- | m4/ax_append_link_flags.m4 | 61 | ||||
-rw-r--r-- | m4/ax_check_link_flag.m4 | 71 | ||||
-rw-r--r-- | m4/ax_cxx_compile_stdcxx_0x.m4 | 107 | ||||
-rw-r--r-- | src/AllCommands.cxx | 387 | ||||
-rw-r--r-- | src/AllCommands.hxx | 34 | ||||
-rw-r--r-- | src/Client.cxx (renamed from src/client.c) | 12 | ||||
-rw-r--r-- | src/Client.hxx (renamed from src/client.h) | 31 | ||||
-rw-r--r-- | src/ClientEvent.cxx (renamed from src/client_event.c) | 15 | ||||
-rw-r--r-- | src/ClientExpire.cxx (renamed from src/client_expire.c) | 8 | ||||
-rw-r--r-- | src/ClientFile.cxx (renamed from src/client_file.c) | 12 | ||||
-rw-r--r-- | src/ClientFile.hxx (renamed from src/client_file.h) | 13 | ||||
-rw-r--r-- | src/ClientGlobal.cxx (renamed from src/client_global.c) | 9 | ||||
-rw-r--r-- | src/ClientIdle.cxx (renamed from src/client_idle.c) | 17 | ||||
-rw-r--r-- | src/ClientIdle.hxx (renamed from src/client_idle.h) | 14 | ||||
-rw-r--r-- | src/ClientInternal.hxx (renamed from src/client_internal.h) | 54 | ||||
-rw-r--r-- | src/ClientList.cxx (renamed from src/client_list.c) | 12 | ||||
-rw-r--r-- | src/ClientMessage.cxx (renamed from src/db/simple_db_plugin.h) | 36 | ||||
-rw-r--r-- | src/ClientMessage.hxx (renamed from src/exclude.h) | 45 | ||||
-rw-r--r-- | src/ClientNew.cxx (renamed from src/client_new.c) | 23 | ||||
-rw-r--r-- | src/ClientProcess.cxx (renamed from src/client_process.c) | 12 | ||||
-rw-r--r-- | src/ClientRead.cxx (renamed from src/client_read.c) | 27 | ||||
-rw-r--r-- | src/ClientSubscribe.cxx | 95 | ||||
-rw-r--r-- | src/ClientSubscribe.hxx (renamed from src/client_subscribe.h) | 26 | ||||
-rw-r--r-- | src/ClientWrite.cxx (renamed from src/client_write.c) | 37 | ||||
-rw-r--r-- | src/CommandError.cxx | 134 | ||||
-rw-r--r-- | src/CommandError.hxx | 39 | ||||
-rw-r--r-- | src/CommandLine.cxx (renamed from src/cmdline.c) | 12 | ||||
-rw-r--r-- | src/CommandLine.hxx (renamed from src/cmdline.h) | 8 | ||||
-rw-r--r-- | src/DatabaseCommands.cxx | 219 | ||||
-rw-r--r-- | src/DatabaseCommands.hxx | 57 | ||||
-rw-r--r-- | src/DatabaseGlue.cxx (renamed from src/database.c) | 126 | ||||
-rw-r--r-- | src/DatabaseGlue.hxx | 59 | ||||
-rw-r--r-- | src/DatabaseHelpers.cxx | 134 | ||||
-rw-r--r-- | src/DatabaseHelpers.hxx | 41 | ||||
-rw-r--r-- | src/DatabaseLock.cxx (renamed from src/db_lock.c) | 4 | ||||
-rw-r--r-- | src/DatabaseLock.hxx (renamed from src/db_lock.h) | 22 | ||||
-rw-r--r-- | src/DatabasePlaylist.cxx | 50 | ||||
-rw-r--r-- | src/DatabasePlaylist.hxx (renamed from src/db_internal.h) | 21 | ||||
-rw-r--r-- | src/DatabasePlugin.hxx | 148 | ||||
-rw-r--r-- | src/DatabasePrint.cxx | 231 | ||||
-rw-r--r-- | src/DatabasePrint.hxx (renamed from src/db_print.h) | 42 | ||||
-rw-r--r-- | src/DatabaseQueue.cxx | 60 | ||||
-rw-r--r-- | src/DatabaseQueue.hxx (renamed from src/db_save.h) | 19 | ||||
-rw-r--r-- | src/DatabaseRegistry.cxx | 43 | ||||
-rw-r--r-- | src/DatabaseRegistry.hxx | 37 | ||||
-rw-r--r-- | src/DatabaseSave.cxx (renamed from src/db_save.c) | 30 | ||||
-rw-r--r-- | src/DatabaseSave.hxx | 36 | ||||
-rw-r--r-- | src/DatabaseSelection.cxx | 27 | ||||
-rw-r--r-- | src/DatabaseSelection.hxx (renamed from src/db_selection.h) | 32 | ||||
-rw-r--r-- | src/DatabaseSimple.hxx (renamed from src/database.h) | 61 | ||||
-rw-r--r-- | src/DatabaseVisitor.hxx (renamed from src/song_print.h) | 21 | ||||
-rw-r--r-- | src/DecoderPrint.cxx (renamed from src/decoder_print.c) | 10 | ||||
-rw-r--r-- | src/DecoderPrint.hxx | 28 | ||||
-rw-r--r-- | src/DecoderThread.cxx (renamed from src/decoder_thread.c) | 53 | ||||
-rw-r--r-- | src/DecoderThread.hxx (renamed from src/decoder_thread.h) | 6 | ||||
-rw-r--r-- | src/Directory.cxx | 336 | ||||
-rw-r--r-- | src/Directory.hxx | 264 | ||||
-rw-r--r-- | src/DirectorySave.cxx (renamed from src/directory_save.c) | 77 | ||||
-rw-r--r-- | src/DirectorySave.hxx (renamed from src/directory_save.h) | 15 | ||||
-rw-r--r-- | src/ExcludeList.cxx (renamed from src/exclude.c) | 41 | ||||
-rw-r--r-- | src/ExcludeList.hxx | 78 | ||||
-rw-r--r-- | src/InotifyQueue.cxx (renamed from src/inotify_queue.c) | 54 | ||||
-rw-r--r-- | src/InotifyQueue.hxx (renamed from src/inotify_queue.h) | 8 | ||||
-rw-r--r-- | src/InotifySource.cxx (renamed from src/inotify_source.c) | 18 | ||||
-rw-r--r-- | src/InotifySource.hxx (renamed from src/inotify_source.h) | 8 | ||||
-rw-r--r-- | src/InotifyUpdate.cxx (renamed from src/inotify_update.c) | 29 | ||||
-rw-r--r-- | src/InotifyUpdate.hxx (renamed from src/inotify_update.h) | 6 | ||||
-rw-r--r-- | src/Listen.cxx (renamed from src/listen.c) | 11 | ||||
-rw-r--r-- | src/Listen.hxx (renamed from src/listen.h) | 8 | ||||
-rw-r--r-- | src/Main.cxx (renamed from src/main.c) | 76 | ||||
-rw-r--r-- | src/Main.hxx (renamed from src/main.h) | 6 | ||||
-rw-r--r-- | src/Mapper.cxx (renamed from src/mapper.c) | 38 | ||||
-rw-r--r-- | src/Mapper.hxx (renamed from src/mapper.h) | 50 | ||||
-rw-r--r-- | src/MessageCommands.cxx | 168 | ||||
-rw-r--r-- | src/MessageCommands.hxx | 42 | ||||
-rw-r--r-- | src/OtherCommands.cxx | 311 | ||||
-rw-r--r-- | src/OtherCommands.hxx | 69 | ||||
-rw-r--r-- | src/OutputCommands.cxx | 77 | ||||
-rw-r--r-- | src/OutputCommands.hxx | 36 | ||||
-rw-r--r-- | src/OutputPrint.cxx (renamed from src/output_print.c) | 13 | ||||
-rw-r--r-- | src/OutputPrint.hxx (renamed from src/output_print.h) | 10 | ||||
-rw-r--r-- | src/OutputState.cxx (renamed from src/output_state.c) | 7 | ||||
-rw-r--r-- | src/OutputState.hxx (renamed from src/output_state.h) | 7 | ||||
-rw-r--r-- | src/Permission.cxx (renamed from src/permission.c) | 37 | ||||
-rw-r--r-- | src/Permission.hxx (renamed from src/permission.h) | 8 | ||||
-rw-r--r-- | src/PlayerCommands.cxx | 390 | ||||
-rw-r--r-- | src/PlayerCommands.hxx | 90 | ||||
-rw-r--r-- | src/PlayerThread.cxx (renamed from src/player_thread.c) | 117 | ||||
-rw-r--r-- | src/PlayerThread.hxx (renamed from src/player_thread.h) | 6 | ||||
-rw-r--r-- | src/PlaylistAny.cxx (renamed from src/playlist_any.c) | 9 | ||||
-rw-r--r-- | src/PlaylistAny.hxx (renamed from src/playlist_any.h) | 6 | ||||
-rw-r--r-- | src/PlaylistCommands.cxx | 223 | ||||
-rw-r--r-- | src/PlaylistCommands.hxx | 60 | ||||
-rw-r--r-- | src/PlaylistDatabase.cxx (renamed from src/playlist_database.c) | 31 | ||||
-rw-r--r-- | src/PlaylistDatabase.hxx (renamed from src/playlist_database.h) | 16 | ||||
-rw-r--r-- | src/PlaylistEdit.cxx (renamed from src/playlist_edit.c) | 59 | ||||
-rw-r--r-- | src/PlaylistFile.cxx (renamed from src/stored_playlist.c) | 249 | ||||
-rw-r--r-- | src/PlaylistFile.hxx (renamed from src/stored_playlist.h) | 37 | ||||
-rw-r--r-- | src/PlaylistInfo.hxx | 64 | ||||
-rw-r--r-- | src/PlaylistMapper.cxx (renamed from src/playlist_mapper.c) | 11 | ||||
-rw-r--r-- | src/PlaylistMapper.hxx (renamed from src/playlist_mapper.h) | 6 | ||||
-rw-r--r-- | src/PlaylistPrint.cxx (renamed from src/playlist_print.c) | 101 | ||||
-rw-r--r-- | src/PlaylistPrint.hxx (renamed from src/playlist_print.h) | 38 | ||||
-rw-r--r-- | src/PlaylistQueue.cxx (renamed from src/playlist_queue.c) | 17 | ||||
-rw-r--r-- | src/PlaylistQueue.hxx (renamed from src/playlist_queue.h) | 8 | ||||
-rw-r--r-- | src/PlaylistSave.cxx (renamed from src/playlist_save.c) | 41 | ||||
-rw-r--r-- | src/PlaylistSave.hxx (renamed from src/playlist_save.h) | 3 | ||||
-rw-r--r-- | src/PlaylistSong.cxx (renamed from src/playlist_song.c) | 29 | ||||
-rw-r--r-- | src/PlaylistSong.hxx (renamed from src/playlist_song.h) | 8 | ||||
-rw-r--r-- | src/PlaylistState.cxx (renamed from src/playlist_state.c) | 24 | ||||
-rw-r--r-- | src/PlaylistState.hxx (renamed from src/playlist_state.h) | 10 | ||||
-rw-r--r-- | src/PlaylistVector.cxx | 68 | ||||
-rw-r--r-- | src/PlaylistVector.hxx | 55 | ||||
-rw-r--r-- | src/QueueCommands.cxx | 393 | ||||
-rw-r--r-- | src/QueueCommands.hxx | 84 | ||||
-rw-r--r-- | src/QueuePrint.cxx (renamed from src/queue_print.c) | 49 | ||||
-rw-r--r-- | src/QueuePrint.hxx (renamed from src/queue_print.h) | 26 | ||||
-rw-r--r-- | src/QueueSave.cxx (renamed from src/queue_save.c) | 55 | ||||
-rw-r--r-- | src/QueueSave.hxx (renamed from src/queue_save.h) | 10 | ||||
-rw-r--r-- | src/Song.cxx (renamed from src/song.c) | 102 | ||||
-rw-r--r-- | src/SongFilter.cxx | 170 | ||||
-rw-r--r-- | src/SongFilter.hxx | 107 | ||||
-rw-r--r-- | src/SongPrint.cxx (renamed from src/song_print.c) | 52 | ||||
-rw-r--r-- | src/SongPrint.hxx | 32 | ||||
-rw-r--r-- | src/SongSave.cxx (renamed from src/song_save.c) | 19 | ||||
-rw-r--r-- | src/SongSave.hxx (renamed from src/song_save.h) | 13 | ||||
-rw-r--r-- | src/SongSticker.cxx (renamed from src/song_sticker.c) | 37 | ||||
-rw-r--r-- | src/SongSticker.hxx (renamed from src/song_sticker.h) | 11 | ||||
-rw-r--r-- | src/SongUpdate.cxx (renamed from src/song_update.c) | 17 | ||||
-rw-r--r-- | src/StateFile.cxx (renamed from src/state_file.c) | 31 | ||||
-rw-r--r-- | src/StateFile.hxx (renamed from src/state_file.h) | 6 | ||||
-rw-r--r-- | src/Stats.cxx | 89 | ||||
-rw-r--r-- | src/StickerCommands.cxx | 176 | ||||
-rw-r--r-- | src/StickerCommands.hxx | 30 | ||||
-rw-r--r-- | src/StickerDatabase.cxx (renamed from src/sticker.c) | 96 | ||||
-rw-r--r-- | src/StickerDatabase.hxx (renamed from src/sticker.h) | 18 | ||||
-rw-r--r-- | src/StickerPrint.cxx (renamed from src/sticker_print.c) | 16 | ||||
-rw-r--r-- | src/StickerPrint.hxx (renamed from src/sticker_print.h) | 13 | ||||
-rw-r--r-- | src/TagPrint.cxx (renamed from src/tag_print.c) | 10 | ||||
-rw-r--r-- | src/TagPrint.hxx (renamed from src/tag_print.h) | 12 | ||||
-rw-r--r-- | src/TagSave.cxx (renamed from src/tag_save.c) | 4 | ||||
-rw-r--r-- | src/TagSave.hxx (renamed from src/tag_save.h) | 6 | ||||
-rw-r--r-- | src/TextFile.cxx (renamed from src/text_file.c) | 15 | ||||
-rw-r--r-- | src/TextFile.hxx | 66 | ||||
-rw-r--r-- | src/TimePrint.cxx | 47 | ||||
-rw-r--r-- | src/TimePrint.hxx | 33 | ||||
-rw-r--r-- | src/UpdateArchive.cxx (renamed from src/update_archive.c) | 35 | ||||
-rw-r--r-- | src/UpdateArchive.hxx (renamed from src/update_archive.h) | 20 | ||||
-rw-r--r-- | src/UpdateContainer.cxx (renamed from src/update_container.c) | 35 | ||||
-rw-r--r-- | src/UpdateContainer.hxx (renamed from src/update_container.h) | 11 | ||||
-rw-r--r-- | src/UpdateDatabase.cxx (renamed from src/update_db.c) | 30 | ||||
-rw-r--r-- | src/UpdateDatabase.hxx (renamed from src/update_db.h) | 16 | ||||
-rw-r--r-- | src/UpdateGlue.cxx (renamed from src/update.c) | 24 | ||||
-rw-r--r-- | src/UpdateGlue.hxx (renamed from src/update.h) | 8 | ||||
-rw-r--r-- | src/UpdateIO.cxx (renamed from src/update_io.c) | 16 | ||||
-rw-r--r-- | src/UpdateIO.hxx (renamed from src/update_io.h) | 19 | ||||
-rw-r--r-- | src/UpdateInternal.hxx (renamed from src/update_internal.h) | 2 | ||||
-rw-r--r-- | src/UpdateQueue.cxx (renamed from src/update_queue.c) | 4 | ||||
-rw-r--r-- | src/UpdateQueue.hxx (renamed from src/update_queue.h) | 8 | ||||
-rw-r--r-- | src/UpdateRemove.cxx (renamed from src/update_remove.c) | 14 | ||||
-rw-r--r-- | src/UpdateRemove.hxx (renamed from src/update_remove.h) | 6 | ||||
-rw-r--r-- | src/UpdateSong.cxx (renamed from src/update_song.c) | 42 | ||||
-rw-r--r-- | src/UpdateSong.hxx (renamed from src/update_song.h) | 11 | ||||
-rw-r--r-- | src/UpdateWalk.cxx (renamed from src/update_walk.c) | 109 | ||||
-rw-r--r-- | src/UpdateWalk.hxx (renamed from src/update_walk.h) | 8 | ||||
-rw-r--r-- | src/Win32Main.cxx (renamed from src/main_win32.c) | 5 | ||||
-rw-r--r-- | src/audio_check.h | 2 | ||||
-rw-r--r-- | src/audio_format.h | 19 | ||||
-rw-r--r-- | src/audio_parser.h | 2 | ||||
-rw-r--r-- | src/client_message.c | 96 | ||||
-rw-r--r-- | src/client_message.h | 72 | ||||
-rw-r--r-- | src/client_subscribe.c | 123 | ||||
-rw-r--r-- | src/clock.h | 6 | ||||
-rw-r--r-- | src/command.c | 2298 | ||||
-rw-r--r-- | src/command.h | 49 | ||||
-rw-r--r-- | src/conf.c | 1 | ||||
-rw-r--r-- | src/db/ProxyDatabasePlugin.cxx | 476 | ||||
-rw-r--r-- | src/db/ProxyDatabasePlugin.hxx | 27 | ||||
-rw-r--r-- | src/db/SimpleDatabasePlugin.cxx | 347 | ||||
-rw-r--r-- | src/db/SimpleDatabasePlugin.hxx | 98 | ||||
-rw-r--r-- | src/db/simple_db_plugin.c | 357 | ||||
-rw-r--r-- | src/dbUtils.c | 209 | ||||
-rw-r--r-- | src/dbUtils.h | 56 | ||||
-rw-r--r-- | src/db_plugin.h | 156 | ||||
-rw-r--r-- | src/db_print.c | 393 | ||||
-rw-r--r-- | src/db_visitor.h | 54 | ||||
-rw-r--r-- | src/decoder/AdPlugDecoderPlugin.cxx | 148 | ||||
-rw-r--r-- | src/decoder/AdPlugDecoderPlugin.h | 25 | ||||
-rw-r--r-- | src/decoder/FLACCommon.cxx (renamed from src/decoder/_flac_common.c) | 62 | ||||
-rw-r--r-- | src/decoder/FLACCommon.hxx (renamed from src/decoder/_flac_common.h) | 30 | ||||
-rw-r--r-- | src/decoder/FLACDecoderPlugin.cxx (renamed from src/decoder/flac_decoder_plugin.c) | 292 | ||||
-rw-r--r-- | src/decoder/FLACDecoderPlugin.h | 26 | ||||
-rw-r--r-- | src/decoder/FLACIOHandle.cxx | 114 | ||||
-rw-r--r-- | src/decoder/FLACIOHandle.hxx | 48 | ||||
-rw-r--r-- | src/decoder/FLACInput.cxx | 152 | ||||
-rw-r--r-- | src/decoder/FLACInput.hxx | 72 | ||||
-rw-r--r-- | src/decoder/FLACMetaData.cxx (renamed from src/decoder/flac_metadata.c) | 134 | ||||
-rw-r--r-- | src/decoder/FLACMetaData.hxx | 140 | ||||
-rw-r--r-- | src/decoder/FLAC_PCM.cxx (renamed from src/decoder/flac_pcm.c) | 4 | ||||
-rw-r--r-- | src/decoder/FLAC_PCM.hxx (renamed from src/decoder/flac_pcm.h) | 6 | ||||
-rw-r--r-- | src/decoder/OggUtil.cxx | 56 | ||||
-rw-r--r-- | src/decoder/OggUtil.hxx | 48 | ||||
-rw-r--r-- | src/decoder/OpusDecoderPlugin.cxx | 366 | ||||
-rw-r--r-- | src/decoder/OpusDecoderPlugin.h | 25 | ||||
-rw-r--r-- | src/decoder/OpusHead.cxx | 44 | ||||
-rw-r--r-- | src/decoder/OpusHead.hxx | 30 | ||||
-rw-r--r-- | src/decoder/OpusReader.hxx | 97 | ||||
-rw-r--r-- | src/decoder/OpusTags.cxx | 77 | ||||
-rw-r--r-- | src/decoder/OpusTags.hxx | 31 | ||||
-rw-r--r-- | src/decoder/XiphTags.c | 28 | ||||
-rw-r--r-- | src/decoder/XiphTags.h | 28 | ||||
-rw-r--r-- | src/decoder/dsdiff_decoder_plugin.c | 138 | ||||
-rw-r--r-- | src/decoder/dsdlib.c | 56 | ||||
-rw-r--r-- | src/decoder/dsdlib.h | 5 | ||||
-rw-r--r-- | src/decoder/dsf_decoder_plugin.c | 29 | ||||
-rw-r--r-- | src/decoder/flac_compat.h | 114 | ||||
-rw-r--r-- | src/decoder/flac_metadata.h | 64 | ||||
-rw-r--r-- | src/decoder/mad_decoder_plugin.c | 4 | ||||
-rw-r--r-- | src/decoder/ogg_codec.c (renamed from src/decoder/_ogg_common.c) | 18 | ||||
-rw-r--r-- | src/decoder/ogg_codec.h (renamed from src/decoder/_ogg_common.h) | 14 | ||||
-rw-r--r-- | src/decoder/sidplay_decoder_plugin.cxx | 8 | ||||
-rw-r--r-- | src/decoder/vorbis_comments.c | 10 | ||||
-rw-r--r-- | src/decoder/vorbis_decoder_plugin.c | 115 | ||||
-rw-r--r-- | src/decoder_control.c | 33 | ||||
-rw-r--r-- | src/decoder_control.h | 94 | ||||
-rw-r--r-- | src/decoder_error.h | 35 | ||||
-rw-r--r-- | src/decoder_internal.h | 7 | ||||
-rw-r--r-- | src/decoder_list.c | 11 | ||||
-rw-r--r-- | src/directory.c | 314 | ||||
-rw-r--r-- | src/directory.h | 262 | ||||
-rw-r--r-- | src/dsd2pcm/dsd2pcm.hpp | 6 | ||||
-rw-r--r-- | src/dsd2pcm/noiseshape.hpp | 9 | ||||
-rw-r--r-- | src/encoder/OggStream.hxx | 128 | ||||
-rw-r--r-- | src/encoder/OpusEncoderPlugin.cxx | 432 | ||||
-rw-r--r-- | src/encoder/OpusEncoderPlugin.hxx | 25 | ||||
-rw-r--r-- | src/encoder/VorbisEncoderPlugin.cxx (renamed from src/encoder/vorbis_encoder.c) | 123 | ||||
-rw-r--r-- | src/encoder/VorbisEncoderPlugin.hxx | 25 | ||||
-rw-r--r-- | src/encoder/flac_encoder.c | 31 | ||||
-rw-r--r-- | src/encoder_list.c | 8 | ||||
-rw-r--r-- | src/encoder_plugin.h | 2 | ||||
-rw-r--r-- | src/event_pipe.h | 2 | ||||
-rw-r--r-- | src/fd_util.h | 8 | ||||
-rw-r--r-- | src/filter/null_filter_plugin.c | 1 | ||||
-rw-r--r-- | src/filter_plugin.h | 2 | ||||
-rw-r--r-- | src/gcc.h | 32 | ||||
-rw-r--r-- | src/gerror.h (renamed from src/decoder_print.h) | 9 | ||||
-rw-r--r-- | src/input/file_input_plugin.c | 17 | ||||
-rw-r--r-- | src/input_init.h | 3 | ||||
-rw-r--r-- | src/io_error.h | 44 | ||||
-rw-r--r-- | src/locate.c | 239 | ||||
-rw-r--r-- | src/locate.h | 92 | ||||
-rw-r--r-- | src/ls.cxx (renamed from src/ls.c) | 14 | ||||
-rw-r--r-- | src/ls.hxx (renamed from src/ls.h) | 11 | ||||
-rw-r--r-- | src/mixer_control.h | 2 | ||||
-rw-r--r-- | src/mixer_plugin.h | 2 | ||||
-rw-r--r-- | src/output/httpd_client.h | 3 | ||||
-rw-r--r-- | src/output/pulse_output_plugin.h | 4 | ||||
-rw-r--r-- | src/output/shout_output_plugin.c | 71 | ||||
-rw-r--r-- | src/output_all.c | 19 | ||||
-rw-r--r-- | src/output_all.h | 7 | ||||
-rw-r--r-- | src/output_error.h | 35 | ||||
-rw-r--r-- | src/output_init.c | 1 | ||||
-rw-r--r-- | src/output_internal.h | 7 | ||||
-rw-r--r-- | src/output_plugin.h | 7 | ||||
-rw-r--r-- | src/output_thread.c | 2 | ||||
-rw-r--r-- | src/pcm_channels.c | 71 | ||||
-rw-r--r-- | src/pcm_channels.h | 17 | ||||
-rw-r--r-- | src/pcm_convert.c | 79 | ||||
-rw-r--r-- | src/pcm_mix.h | 3 | ||||
-rw-r--r-- | src/pcm_volume.h | 1 | ||||
-rw-r--r-- | src/pipe.h | 11 | ||||
-rw-r--r-- | src/player_control.c | 111 | ||||
-rw-r--r-- | src/player_control.h | 100 | ||||
-rw-r--r-- | src/playlist.c | 21 | ||||
-rw-r--r-- | src/playlist_control.c | 5 | ||||
-rw-r--r-- | src/playlist_global.c | 3 | ||||
-rw-r--r-- | src/playlist_vector.c | 114 | ||||
-rw-r--r-- | src/playlist_vector.h | 80 | ||||
-rw-r--r-- | src/protocol/ArgParser.cxx (renamed from src/protocol/argparser.c) | 18 | ||||
-rw-r--r-- | src/protocol/ArgParser.hxx (renamed from src/protocol/argparser.h) | 20 | ||||
-rw-r--r-- | src/protocol/Result.cxx (renamed from src/protocol/result.c) | 12 | ||||
-rw-r--r-- | src/protocol/Result.hxx (renamed from src/protocol/result.h) | 19 | ||||
-rw-r--r-- | src/queue.c | 11 | ||||
-rw-r--r-- | src/resolver.h | 6 | ||||
-rw-r--r-- | src/server_socket.h | 5 | ||||
-rw-r--r-- | src/sig_handlers.c | 2 | ||||
-rw-r--r-- | src/socket_util.c | 2 | ||||
-rw-r--r-- | src/socket_util.h | 4 | ||||
-rw-r--r-- | src/song.h | 71 | ||||
-rw-r--r-- | src/stats.c | 128 | ||||
-rw-r--r-- | src/stats.h | 5 | ||||
-rw-r--r-- | src/string_util.h | 12 | ||||
-rw-r--r-- | src/strset.c | 148 | ||||
-rw-r--r-- | src/strset.h | 50 | ||||
-rw-r--r-- | src/tag.c | 20 | ||||
-rw-r--r-- | src/tag_id3.c | 5 | ||||
-rw-r--r-- | src/tag_id3.h | 18 | ||||
-rw-r--r-- | src/tag_pool.c | 25 | ||||
-rw-r--r-- | src/tag_pool.h | 6 | ||||
-rw-r--r-- | src/text_file.h | 39 | ||||
-rw-r--r-- | src/tokenizer.c | 2 | ||||
-rw-r--r-- | src/tokenizer.h | 2 | ||||
-rw-r--r-- | src/uri.h | 10 | ||||
-rw-r--r-- | src/util/bit_reverse.h | 5 | ||||
-rw-r--r-- | src/util/list.h | 49 | ||||
-rw-r--r-- | src/utils.h | 3 | ||||
-rw-r--r-- | src/zeroconf-avahi.c | 2 | ||||
-rw-r--r-- | src/zeroconf.c | 2 | ||||
-rw-r--r-- | test/DumpDatabase.cxx | 151 | ||||
-rw-r--r-- | test/DumpPlaylist.cxx (renamed from test/dump_playlist.c) | 14 | ||||
-rw-r--r-- | test/FakeSong.cxx | 33 | ||||
-rw-r--r-- | test/RunInotify.cxx (renamed from test/run_inotify.c) | 5 | ||||
-rw-r--r-- | test/RunInput.cxx (renamed from test/run_input.c) | 13 | ||||
-rw-r--r-- | test/TestQueuePriority.cxx (renamed from test/test_queue_priority.c) | 15 | ||||
-rw-r--r-- | test/dump_text_file.c | 3 | ||||
-rw-r--r-- | test/read_tags.c | 19 | ||||
-rw-r--r-- | test/run_decoder.c | 5 |
322 files changed, 12517 insertions, 8302 deletions
@@ -81,8 +81,11 @@ 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. @@ -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 89b643530..e37b6369e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,6 +12,7 @@ noinst_LIBRARIES = \ libpcm.a \ libtag.a \ libinput.a \ + libdb_plugins.a \ libplaylist_plugins.a \ libdecoder_plugins.a \ libfilter_plugins.a \ @@ -19,10 +20,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) \ @@ -53,8 +56,6 @@ mpd_headers = \ 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 \ @@ -67,11 +68,8 @@ mpd_headers = \ 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 \ @@ -79,27 +77,14 @@ mpd_headers = \ 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 \ @@ -113,17 +98,12 @@ mpd_headers = \ 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 \ @@ -140,28 +120,16 @@ mpd_headers = \ 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_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 \ @@ -175,23 +143,16 @@ mpd_headers = \ 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 \ @@ -199,17 +160,12 @@ mpd_headers = \ 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 \ @@ -233,71 +189,79 @@ src_mpd_SOURCES = \ 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/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.c \ - src/cmdline.c \ + src/CommandLine.cxx src/CommandLine.hxx \ src/conf.c \ src/crossfade.c \ src/cue/cue_parser.c src/cue/cue_parser.h \ - src/dbUtils.c \ - src/decoder_thread.c \ + src/decoder_error.h \ + src/DecoderThread.cxx src/DecoderThread.hxx \ 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/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/UpdateGlue.cxx src/UpdateGlue.hxx \ + src/UpdateQueue.cxx src/UpdateQueue.hxx \ + src/UpdateIO.cxx src/UpdateIO.hxx \ + src/UpdateDatabase.cxx src/UpdateDatabase.hxx \ + src/UpdateWalk.cxx src/UpdateWalk.hxx \ + src/UpdateSong.cxx src/UpdateSong.hxx \ + src/UpdateContainer.cxx src/UpdateContainer.hxx \ + src/UpdateInternal.hxx \ + src/UpdateRemove.cxx src/UpdateRemove.hxx \ + src/Client.cxx src/Client.hxx \ + src/ClientInternal.hxx \ + src/ClientEvent.cxx \ + src/ClientExpire.cxx \ + src/ClientGlobal.cxx \ + src/ClientIdle.cxx src/ClientIdle.hxx \ + src/ClientList.cxx \ + 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/server_socket.c \ - src/listen.c \ + src/Listen.cxx src/Listen.hxx \ src/log.c \ - src/ls.c \ + src/ls.cxx \ + src/io_error.h \ src/io_thread.c src/io_thread.h \ - src/main.c \ - src/main_win32.c \ + src/Main.cxx src/Main.hxx \ + src/Win32Main.cxx \ src/event_pipe.c \ src/daemon.c \ src/AudioCompress/compress.c \ @@ -305,54 +269,54 @@ src_mpd_SOURCES = \ src/pipe.c \ src/chunk.c \ src/path.c \ - src/mapper.c \ + src/Mapper.cxx src/Mapper.hxx \ src/page.c \ - src/permission.c \ - src/player_thread.c \ + src/Permission.cxx src/Permission.hxx \ + src/PlayerThread.cxx src/PlayerThread.hxx \ 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/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/queue.c \ - src/queue_print.c \ - src/queue_save.c \ + src/QueuePrint.cxx src/QueuePrint.hxx \ + src/QueueSave.cxx src/QueueSave.hxx \ 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/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/StateFile.cxx src/StateFile.hxx \ + src/Stats.cxx \ src/tag.c \ src/tag_pool.c \ - src/tag_print.c \ - src/tag_save.c \ + 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/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/SongFilter.cxx src/SongFilter.hxx \ + src/PlaylistFile.cxx src/PlaylistFile.hxx \ src/timer.c # @@ -376,16 +340,17 @@ 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 @@ -427,6 +392,22 @@ if HAVE_LIBSAMPLERATE libpcm_a_SOURCES += src/pcm_resample_libsamplerate.c endif +# 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,7 +415,7 @@ 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 \ @@ -515,8 +496,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 +515,11 @@ DECODER_LIBS = \ $(WAVPACK_LIBS) \ $(MAD_LIBS) \ $(MPG123_LIBS) \ + $(OPUS_LIBS) \ $(MP4FF_LIBS) \ $(FFMPEG_LIBS) \ $(MPCDEC_LIBS) \ + $(ADPLUG_LIBS) \ $(FAAD_LIBS) DECODER_SRC = @@ -551,10 +536,29 @@ if HAVE_MPCDEC libdecoder_plugins_a_SOURCES += src/decoder/mpcdec_decoder_plugin.c endif +if HAVE_OPUS +libdecoder_plugins_a_SOURCES += \ + src/decoder/OggUtil.cxx \ + src/decoder/OggUtil.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_WAVPACK libdecoder_plugins_a_SOURCES += src/decoder/wavpack_decoder_plugin.c endif +if HAVE_ADPLUG +libdecoder_plugins_a_SOURCES += \ + src/decoder/AdPlugDecoderPlugin.cxx \ + src/decoder/AdPlugDecoderPlugin.h +endif + if HAVE_FAAD libdecoder_plugins_a_SOURCES += src/decoder/faad_decoder_plugin.c endif @@ -563,15 +567,10 @@ if HAVE_MP4 libdecoder_plugins_a_SOURCES += src/decoder/mp4ff_decoder_plugin.c endif -if HAVE_OGG_COMMON -libdecoder_plugins_a_SOURCES += src/decoder/_ogg_common.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/ogg_codec.c src/decoder/ogg_codec.h endif if ENABLE_VORBIS_DECODER @@ -582,7 +581,14 @@ libdecoder_plugins_a_SOURCES += \ 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 @@ -639,6 +645,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 +653,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 @@ -773,9 +789,10 @@ OUTPUT_API_SRC = \ src/output_list.c \ src/output_all.c \ src/output_thread.c \ + src/output_error.h \ src/output_control.c \ - src/output_state.c \ - src/output_print.c \ + src/OutputState.cxx src/OutputState.hxx \ + src/OutputPrint.cxx src/OutputPrint.hxx \ src/output_command.c \ src/output_plugin.c src/output_plugin.h \ src/output_finish.c \ @@ -990,7 +1007,7 @@ if ENABLE_TEST C_TESTS = \ test/test_byte_reverse \ test/test_pcm \ - test/test_queue_priority + test/TestQueuePriority TESTS = $(C_TESTS) @@ -998,9 +1015,10 @@ noinst_PROGRAMS = \ $(C_TESTS) \ test/read_conf \ test/run_resolver \ - test/run_input \ + test/DumpDatabase \ + test/RunInput \ test/dump_text_file \ - test/dump_playlist \ + test/DumpPlaylist \ test/run_decoder \ test/read_tags \ test/run_filter \ @@ -1028,15 +1046,32 @@ test_run_resolver_LDADD = \ test_run_resolver_SOURCES = test/run_resolver.c \ src/resolver.c -test_run_input_LDADD = \ +test_DumpDatabase_LDADD = \ + $(DB_LIBS) \ + libutil.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.c src/tag_pool.c src/TagSave.cxx \ + src/path.c \ + src/SongFilter.cxx \ + src/TextFile.cxx \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.c + +test_RunInput_LDADD = \ $(INPUT_LIBS) \ $(ARCHIVE_LIBS) \ $(GLIB_LIBS) -test_run_input_SOURCES = test/run_input.c \ +test_RunInput_SOURCES = test/RunInput.c \ 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/tag.c src/tag_pool.c src/TagSave.cxx \ src/fd_util.c test_dump_text_file_LDADD = \ @@ -1051,7 +1086,7 @@ test_dump_text_file_SOURCES = test/dump_text_file.c \ src/text_input_stream.c src/fifo_buffer.c \ src/fd_util.c -test_dump_playlist_LDADD = \ +test_DumpPlaylist_LDADD = \ $(PLAYLIST_LIBS) \ $(FLAC_LIBS) \ $(INPUT_LIBS) \ @@ -1060,12 +1095,12 @@ test_dump_playlist_LDADD = \ $(TAG_LIBS) \ libutil.a \ $(GLIB_LIBS) -test_dump_playlist_SOURCES = test/dump_playlist.c \ +test_DumpPlaylist_SOURCES = test/DumpPlaylist.cxx \ $(DECODER_SRC) \ src/io_thread.c src/io_thread.h \ src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ src/uri.c \ - src/song.c src/tag.c src/tag_pool.c src/tag_save.c \ + src/Song.cxx src/tag.c src/tag_pool.c src/TagSave.cxx \ src/tag_handler.c src/tag_file.c \ src/audio_check.c src/pcm_buffer.c \ src/text_input_stream.c src/fifo_buffer.c \ @@ -1073,9 +1108,9 @@ test_dump_playlist_SOURCES = test/dump_playlist.c \ src/fd_util.c if HAVE_FLAC -test_dump_playlist_SOURCES += \ +test_DumpPlaylist_SOURCES += \ src/replay_gain_info.c \ - src/decoder/flac_metadata.c + src/decoder/FLACMetaData.cxx endif test_run_decoder_LDADD = \ @@ -1148,11 +1183,11 @@ test_run_filter_SOURCES = test/run_filter.c \ if ENABLE_DESPOTIFY test_read_tags_SOURCES += \ src/despotify_utils.c -test_run_input_SOURCES += \ +test_RunInput_SOURCES += \ src/despotify_utils.c test_dump_text_file_SOURCES += \ src/despotify_utils.c -test_dump_playlist_SOURCES += \ +test_DumpPlaylist_SOURCES += \ src/despotify_utils.c test_run_decoder_SOURCES += \ src/despotify_utils.c @@ -1282,12 +1317,12 @@ TESTS += test/test_archive_iso9660.sh endif if ENABLE_INOTIFY -noinst_PROGRAMS += test/run_inotify -test_run_inotify_SOURCES = test/run_inotify.c \ +noinst_PROGRAMS += test/RunInotify +test_RunInotify_SOURCES = test/RunInotify.c \ src/fd_util.c \ src/fifo_buffer.c \ - src/inotify_source.c -test_run_inotify_LDADD = $(GLIB_LIBS) + src/InotifySource.cxx +test_RunInotify_LDADD = $(GLIB_LIBS) endif test_test_byte_reverse_SOURCES = \ @@ -1308,13 +1343,12 @@ test_test_pcm_LDADD = \ libutil.a \ $(GLIB_LIBS) -test_test_queue_priority_SOURCES = \ +test_TestQueuePriority_SOURCES = \ src/queue.c \ - test/test_queue_priority.c -test_test_queue_priority_LDADD = \ + test/TestQueuePriority.cxx +test_TestQueuePriority_LDADD = \ $(GLIB_LIBS) -if HAVE_CXX noinst_PROGRAMS += src/dsd2pcm/dsd2pcm src_dsd2pcm_dsd2pcm_SOURCES = \ @@ -1322,7 +1356,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,17 @@ +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 +* 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 +* improved decoder/output error reporting + ver 0.17.3 (2012/??/??) * output: - osx: fix pops during playback diff --git a/configure.ac b/configure.ac index ed99cee5a..9ade4bdf2 100644 --- a/configure.ac +++ b/configure.ac @@ -1,13 +1,13 @@ AC_PREREQ(2.60) -AC_INIT(mpd, 0.17.3~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 @@ -126,6 +110,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) @@ -147,6 +144,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 +334,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)]),, @@ -537,6 +550,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) @@ -806,6 +828,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]) @@ -832,7 +862,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 +929,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 +937,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 +1050,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 @@ -1095,6 +1127,7 @@ if 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 +1137,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 +1226,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 +1530,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 +1560,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 --------------------------- @@ -1559,6 +1617,7 @@ results(mpg123, [MPG123]) results(mp4, [MP4]) results(mpc, [Musepack]) printf '\n\t' +results(opus, [OPUS]) results(tremor, [OggTremor]) results(vorbis, [OggVorbis]) results(audiofile, [WAVE]) @@ -1602,6 +1661,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/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/client.c b/src/Client.cxx index 3fa2c9be4..efb4125ac 100644 --- a/src/client.c +++ b/src/Client.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,24 +18,24 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" -bool client_is_expired(const struct client *client) +bool client_is_expired(const Client *client) { return client->channel == NULL; } -int client_get_uid(const struct client *client) +int client_get_uid(const Client *client) { return client->uid; } -unsigned client_get_permission(const struct client *client) +unsigned client_get_permission(const Client *client) { return client->permission; } -void client_set_permission(struct client *client, unsigned permission) +void client_set_permission(Client *client, unsigned permission) { client->permission = permission; } diff --git a/src/client.h b/src/Client.hxx index 0302a2e0a..622815a56 100644 --- a/src/client.h +++ b/src/Client.hxx @@ -20,14 +20,15 @@ #ifndef MPD_CLIENT_H #define MPD_CLIENT_H -#include <glib.h> +#include "gcc.h" + #include <stdbool.h> #include <stddef.h> #include <stdarg.h> -struct client; struct sockaddr; struct player_control; +class Client; void client_manager_init(void); void client_manager_deinit(void); @@ -35,45 +36,47 @@ 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); +gcc_pure +bool client_is_expired(const 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); +gcc_pure +int client_get_uid(const Client *client); /** * Is this client running on the same machine, connected with a local * (UNIX domain) socket? */ -G_GNUC_PURE +gcc_pure static inline bool -client_is_local(const struct client *client) +client_is_local(const Client *client) { return client_get_uid(client) > 0; } -G_GNUC_PURE -unsigned client_get_permission(const struct client *client); +gcc_pure +unsigned client_get_permission(const Client *client); -void client_set_permission(struct client *client, unsigned permission); +void client_set_permission(Client *client, unsigned permission); /** * Write a C string to the client. */ -void client_puts(struct client *client, const char *s); +void client_puts(Client *client, const char *s); /** * Write a printf-like formatted string to the client. */ -void client_vprintf(struct client *client, const char *fmt, va_list args); +void client_vprintf(Client *client, const char *fmt, va_list args); /** * Write a printf-like formatted string to the client. */ -G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...); +gcc_fprintf +void +client_printf(Client *client, const char *fmt, ...); #endif diff --git a/src/client_event.c b/src/ClientEvent.cxx index 4f54ae0a7..aacd01cfe 100644 --- a/src/client_event.c +++ b/src/ClientEvent.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,8 @@ */ #include "config.h" -#include "client_internal.h" -#include "main.h" +#include "ClientInternal.hxx" +#include "Main.hxx" #include <assert.h> @@ -27,7 +27,7 @@ static gboolean client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, gpointer data) { - struct client *client = data; + Client *client = (Client *)data; assert(!client_is_expired(client)); @@ -49,7 +49,7 @@ client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, /* 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, + GIOCondition(G_IO_IN|G_IO_ERR|G_IO_HUP), client_in_event, client); return false; } @@ -62,7 +62,7 @@ gboolean client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, gpointer data) { - struct client *client = data; + Client *client = (Client *)data; enum command_return ret; assert(!client_is_expired(client)); @@ -77,6 +77,7 @@ client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, ret = client_read(client); switch (ret) { case COMMAND_RETURN_OK: + case COMMAND_RETURN_IDLE: case COMMAND_RETURN_ERROR: break; @@ -98,7 +99,7 @@ client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, 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, + GIOCondition(G_IO_OUT|G_IO_ERR|G_IO_HUP), client_out_event, client); return false; } diff --git a/src/client_expire.c b/src/ClientExpire.cxx index 1ca32ebcc..e24770ea1 100644 --- a/src/client_expire.c +++ b/src/ClientExpire.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,12 +18,12 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" static guint expire_source_id; void -client_set_expired(struct client *client) +client_set_expired(Client *client) { if (!client_is_expired(client)) client_schedule_expire(); @@ -42,7 +42,7 @@ client_set_expired(struct client *client) static void client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) { - struct client *client = data; + Client *client = (Client *)data; if (client_is_expired(client)) { g_debug("[%u] expired", client->num); diff --git a/src/client_file.c b/src/ClientFile.cxx index 2ee433308..ca5acb229 100644 --- a/src/client_file.c +++ b/src/ClientFile.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,9 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "client_file.h" -#include "client.h" +#include "ClientFile.hxx" +#include "Client.hxx" #include "ack.h" +#include "io_error.h" #include <sys/stat.h> #include <sys/types.h> @@ -27,7 +28,7 @@ #include <unistd.h> bool -client_allow_file(const struct client *client, const char *path_fs, +client_allow_file(const Client *client, const char *path_fs, GError **error_r) { #ifdef WIN32 @@ -53,8 +54,7 @@ client_allow_file(const struct client *client, const char *path_fs, struct stat st; if (stat(path_fs, &st) < 0) { - g_set_error(error_r, g_file_error_quark(), errno, - "%s", g_strerror(errno)); + set_error_errno(error_r); return false; } diff --git a/src/client_file.h b/src/ClientFile.hxx index bc64bd041..48e00c44f 100644 --- a/src/client_file.h +++ b/src/ClientFile.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_CLIENT_FILE_H -#define MPD_CLIENT_FILE_H +#ifndef MPD_CLIENT_FILE_HXX +#define MPD_CLIENT_FILE_HXX + +#include "gerror.h" -#include <glib.h> #include <stdbool.h> -struct client; +class Client; /** * Is this client allowed to use the specified local file? @@ -36,7 +37,7 @@ struct client; * @return true if access is allowed */ bool -client_allow_file(const struct client *client, const char *path_fs, +client_allow_file(const Client *client, const char *path_fs, GError **error_r); #endif diff --git a/src/client_global.c b/src/ClientGlobal.cxx index adf3b2f9e..10c78f1f1 100644 --- a/src/client_global.c +++ b/src/ClientGlobal.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,11 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" + +extern "C" { #include "conf.h" +} #include <assert.h> @@ -55,7 +58,7 @@ void client_manager_init(void) static void client_close_all(void) { while (!client_list_is_empty()) { - struct client *client = client_list_get_first(); + Client *client = client_list_get_first(); client_close(client); } diff --git a/src/client_idle.c b/src/ClientIdle.cxx index 930911d6e..c2fa5dde5 100644 --- a/src/client_idle.c +++ b/src/ClientIdle.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,12 @@ */ #include "config.h" -#include "client_idle.h" -#include "client_internal.h" +#include "ClientIdle.hxx" +#include "ClientInternal.hxx" + +extern "C" { #include "idle.h" +} #include <assert.h> @@ -28,7 +31,7 @@ * Send "idle" response to this client. */ static void -client_idle_notify(struct client *client) +client_idle_notify(Client *client) { unsigned flags, i; const char *const* idle_names; @@ -52,7 +55,7 @@ client_idle_notify(struct client *client) } void -client_idle_add(struct client *client, unsigned flags) +client_idle_add(Client *client, unsigned flags) { if (client_is_expired(client)) return; @@ -68,7 +71,7 @@ client_idle_add(struct client *client, unsigned flags) static void client_idle_callback(gpointer data, gpointer user_data) { - struct client *client = data; + Client *client = (Client *)data; unsigned flags = GPOINTER_TO_UINT(user_data); client_idle_add(client, flags); @@ -81,7 +84,7 @@ void client_manager_idle_add(unsigned flags) client_list_foreach(client_idle_callback, GUINT_TO_POINTER(flags)); } -bool client_idle_wait(struct client *client, unsigned flags) +bool client_idle_wait(Client *client, unsigned flags) { assert(!client->idle_waiting); diff --git a/src/client_idle.h b/src/ClientIdle.hxx index c56fd014c..a8c08b7aa 100644 --- a/src/client_idle.h +++ b/src/ClientIdle.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,15 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_CLIENT_IDLE_H -#define MPD_CLIENT_IDLE_H +#ifndef MPD_CLIENT_IDLE_HXX +#define MPD_CLIENT_IDLE_HXX -#include <stdbool.h> - -struct client; +class Client; void -client_idle_add(struct client *client, unsigned flags); +client_idle_add(Client *client, unsigned flags); /** * Adds the specified idle flags to all clients and immediately sends @@ -40,6 +38,6 @@ client_manager_idle_add(unsigned flags); * client into waiting mode and returns false. */ bool -client_idle_wait(struct client *client, unsigned flags); +client_idle_wait(Client *client, unsigned flags); #endif diff --git a/src/client_internal.h b/src/ClientInternal.hxx index ba97e4b8f..5ad24068c 100644 --- a/src/client_internal.h +++ b/src/ClientInternal.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_CLIENT_INTERNAL_H -#define MPD_CLIENT_INTERNAL_H +#ifndef MPD_CLIENT_INTERNAL_HXX +#define MPD_CLIENT_INTERNAL_HXX -#include "client.h" -#include "client_message.h" +#include "Client.hxx" +#include "ClientMessage.hxx" #include "command.h" +#include <set> +#include <string> +#include <deque> + +#include <glib.h> + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "client" @@ -37,7 +43,8 @@ struct deferred_buffer { char data[sizeof(long)]; }; -struct client { +class Client { +public: struct player_control *player_control; GIOChannel *channel; @@ -79,7 +86,7 @@ struct client { /** * A list of channel names this client is subscribed to. */ - GSList *subscriptions; + std::set<std::string> subscriptions; /** * The number of subscriptions in #subscriptions. Used to @@ -88,15 +95,14 @@ struct client { unsigned num_subscriptions; /** - * A list of messages this client has received in reverse - * order (latest first). + * A list of messages this client has received. */ - GSList *messages; + std::deque<ClientMessage> messages; - /** - * The number of messages in #messages. - */ - unsigned num_messages; + gcc_pure + bool IsSubscribed(const char *channel_name) const { + return subscriptions.find(channel_name) != subscriptions.end(); + } }; extern unsigned int client_max_connections; @@ -110,23 +116,23 @@ client_list_is_empty(void); bool client_list_is_full(void); -struct client * +Client * client_list_get_first(void); void -client_list_add(struct client *client); +client_list_add(Client *client); void client_list_foreach(GFunc func, gpointer user_data); void -client_list_remove(struct client *client); +client_list_remove(Client *client); void -client_close(struct client *client); +client_close(Client *client); static inline void -new_cmd_list_ptr(struct client *client, const char *s) +new_cmd_list_ptr(Client *client, const char *s) { client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s)); } @@ -141,7 +147,7 @@ free_cmd_list(GSList *list) } void -client_set_expired(struct client *client); +client_set_expired(Client *client); /** * Schedule an "expired" check for all clients: permanently delete @@ -157,16 +163,16 @@ void client_deinit_expire(void); enum command_return -client_read(struct client *client); +client_read(Client *client); enum command_return -client_process_line(struct client *client, char *line); +client_process_line(Client *client, char *line); void -client_write_deferred(struct client *client); +client_write_deferred(Client *client); void -client_write_output(struct client *client); +client_write_output(Client *client); gboolean client_in_event(GIOChannel *source, GIOCondition condition, diff --git a/src/client_list.c b/src/ClientList.cxx index 2c7f37aff..e0fa533d9 100644 --- a/src/client_list.c +++ b/src/ClientList.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" #include <assert.h> @@ -37,16 +37,16 @@ client_list_is_full(void) return num_clients >= client_max_connections; } -struct client * +Client * client_list_get_first(void) { assert(clients != NULL); - return clients->data; + return (Client *)clients->data; } void -client_list_add(struct client *client) +client_list_add(Client *client) { clients = g_list_prepend(clients, client); ++num_clients; @@ -59,7 +59,7 @@ client_list_foreach(GFunc func, gpointer user_data) } void -client_list_remove(struct client *client) +client_list_remove(Client *client) { assert(num_clients > 0); assert(clients != NULL); diff --git a/src/db/simple_db_plugin.h b/src/ClientMessage.cxx index 511505846..6fbcf3371 100644 --- a/src/db/simple_db_plugin.h +++ b/src/ClientMessage.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,26 +17,26 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_SIMPLE_DB_PLUGIN_H -#define MPD_SIMPLE_DB_PLUGIN_H +#include "ClientMessage.hxx" +#include <assert.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); +static bool +valid_channel_char(const char ch) +{ + return g_ascii_isalnum(ch) || + ch == '_' || ch == '-' || ch == '.' || ch == ':'; +} bool -simple_db_save(struct db *db, GError **error_r); - -G_GNUC_PURE -time_t -simple_db_get_mtime(const struct db *db); - -#endif +client_message_valid_channel_name(const char *name) +{ + do { + if (!valid_channel_char(*name)) + return false; + } while (*++name != 0); + + return true; +} diff --git a/src/exclude.h b/src/ClientMessage.hxx index 5b1229e29..2a929d445 100644 --- a/src/exclude.h +++ b/src/ClientMessage.hxx @@ -17,35 +17,36 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -/* - * The .mpdignore backend code. - * - */ +#ifndef MPD_CLIENT_MESSAGE_H +#define MPD_CLIENT_MESSAGE_H -#ifndef MPD_EXCLUDE_H -#define MPD_EXCLUDE_H +#include "gcc.h" -#include <glib.h> - -#include <stdbool.h> +#include <string> /** - * Loads and parses a .mpdignore file. + * A client-to-client message. */ -GSList * -exclude_list_load(const char *path_fs); +class ClientMessage { + std::string channel, message; -/** - * Frees a list returned by exclude_list_load(). - */ -void -exclude_list_free(GSList *list); +public: + template<typename T, typename U> + ClientMessage(T &&_channel, U &&_message) + :channel(std::forward<T>(_channel)), + message(std::forward<U>(_message)) {} -/** - * Checks whether one of the patterns in the .mpdignore file matches - * the specified file name. - */ + const char *GetChannel() const { + return channel.c_str(); + } + + const char *GetMessage() const { + return message.c_str(); + } +}; + +gcc_pure bool -exclude_list_check(GSList *list, const char *name_fs); +client_message_valid_channel_name(const char *name); #endif diff --git a/src/client_new.c b/src/ClientNew.cxx index cf28c43c5..eab78c690 100644 --- a/src/client_new.c +++ b/src/ClientNew.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,13 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" #include "fd_util.h" +extern "C" { #include "fifo_buffer.h" #include "resolver.h" -#include "permission.h" +} +#include "Permission.hxx" #include "glib_socket.h" #include <assert.h> @@ -48,7 +50,6 @@ 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); @@ -85,7 +86,7 @@ client_new(struct player_control *player_control, return; } - client = g_new0(struct client, 1); + Client *client = new Client(); client->player_control = player_control; client->channel = g_io_channel_new_socket(fd); @@ -99,7 +100,7 @@ client_new(struct player_control *player_control, 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, + GIOCondition(G_IO_IN|G_IO_ERR|G_IO_HUP), client_in_event, client); client->input = fifo_buffer_new(4096); @@ -119,10 +120,6 @@ client_new(struct player_control *player_control, 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); @@ -136,12 +133,12 @@ client_new(struct player_control *player_control, static void deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data) { - struct deferred_buffer *buffer = data; + struct deferred_buffer *buffer = (struct deferred_buffer *)data; g_free(buffer); } void -client_close(struct client *client) +client_close(Client *client) { client_list_remove(client); @@ -161,5 +158,5 @@ client_close(struct client *client) g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, "[%u] closed", client->num); - g_free(client); + delete client; } diff --git a/src/client_process.c b/src/ClientProcess.cxx index 57a8a7824..bbc19321e 100644 --- a/src/client_process.c +++ b/src/ClientProcess.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,9 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" +#include "protocol/Result.hxx" +#include "AllCommands.hxx" #include <string.h> @@ -27,13 +29,13 @@ #define CLIENT_LIST_MODE_END "command_list_end" static enum command_return -client_process_command_list(struct client *client, bool list_ok, GSList *list) +client_process_command_list(Client *client, bool list_ok, 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; + char *cmd = (char *)cur->data; g_debug("command_process_list: process command \"%s\"", cmd); @@ -49,7 +51,7 @@ client_process_command_list(struct client *client, bool list_ok, GSList *list) } enum command_return -client_process_line(struct client *client, char *line) +client_process_line(Client *client, char *line) { enum command_return ret; diff --git a/src/client_read.c b/src/ClientRead.cxx index 26ade264e..aab4cae33 100644 --- a/src/client_read.c +++ b/src/ClientRead.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,35 +18,35 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" + +extern "C" { #include "fifo_buffer.h" +} #include <assert.h> #include <string.h> static char * -client_read_line(struct client *client) +client_read_line(Client *client) { - const char *p, *newline; size_t length; - char *line; - - p = fifo_buffer_read(client->input, &length); + const char *p = (const char *)fifo_buffer_read(client->input, &length); if (p == NULL) return NULL; - newline = memchr(p, '\n', length); + const char *newline = (const char *)memchr(p, '\n', length); if (newline == NULL) return NULL; - line = g_strndup(p, newline - p); + char *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) +client_input_received(Client *client, size_t bytesRead) { char *line; @@ -69,10 +69,8 @@ client_input_received(struct client *client, size_t bytesRead) } enum command_return -client_read(struct client *client) +client_read(Client *client) { - char *p; - size_t max_length; GError *error = NULL; GIOStatus status; gsize bytes_read; @@ -80,7 +78,8 @@ client_read(struct client *client) assert(client != NULL); assert(client->channel != NULL); - p = fifo_buffer_write(client->input, &max_length); + size_t max_length; + char *p = (char *)fifo_buffer_write(client->input, &max_length); if (p == NULL) { g_warning("[%u] buffer overflow", client->num); return COMMAND_RETURN_CLOSE; diff --git a/src/ClientSubscribe.cxx b/src/ClientSubscribe.cxx new file mode 100644 index 000000000..a1ad6e68e --- /dev/null +++ b/src/ClientSubscribe.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 "ClientSubscribe.hxx" +#include "ClientIdle.hxx" +#include "ClientInternal.hxx" + +extern "C" { +#include "idle.h" +} + +#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_idle_add(client, IDLE_MESSAGE); + + client->messages.push_back(msg); + return true; +} diff --git a/src/client_subscribe.h b/src/ClientSubscribe.hxx index 09f864417..e9d3b8a68 100644 --- a/src/client_subscribe.h +++ b/src/ClientSubscribe.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,14 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_CLIENT_SUBSCRIBE_H -#define MPD_CLIENT_SUBSCRIBE_H +#ifndef MPD_CLIENT_SUBSCRIBE_HXX +#define MPD_CLIENT_SUBSCRIBE_HXX -#include <stdbool.h> -#include <glib.h> +#include "gcc.h" -struct client; -struct client_message; +typedef struct _GSList GSList; +class Client; +class ClientMessage; enum client_subscribe_result { /** success */ @@ -41,19 +41,15 @@ enum client_subscribe_result { }; enum client_subscribe_result -client_subscribe(struct client *client, const char *channel); +client_subscribe(Client *client, const char *channel); bool -client_unsubscribe(struct client *client, const char *channel); +client_unsubscribe(Client *client, const char *channel); void -client_unsubscribe_all(struct client *client); +client_unsubscribe_all(Client *client); bool -client_push_message(struct client *client, const struct client_message *msg); - -G_GNUC_MALLOC -GSList * -client_read_messages(struct client *client); +client_push_message(Client *client, const ClientMessage &msg); #endif diff --git a/src/client_write.c b/src/ClientWrite.cxx index 78cfca8a1..825029fb9 100644 --- a/src/client_write.c +++ b/src/ClientWrite.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,14 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" #include <assert.h> #include <string.h> #include <stdio.h> static size_t -client_write_deferred_buffer(struct client *client, +client_write_deferred_buffer(Client *client, const struct deferred_buffer *buffer) { GError *error = NULL; @@ -67,12 +67,13 @@ client_write_deferred_buffer(struct client *client, } void -client_write_deferred(struct client *client) +client_write_deferred(Client *client) { size_t ret; while (!g_queue_is_empty(client->deferred_send)) { struct deferred_buffer *buf = + (struct deferred_buffer *) g_queue_peek_head(client->deferred_send); assert(buf->size > 0); @@ -108,8 +109,8 @@ client_write_deferred(struct client *client) } } -static void client_defer_output(struct client *client, - const void *data, size_t length) +static void +client_defer_output(Client *client, const void *data, size_t length) { size_t alloc; struct deferred_buffer *buf; @@ -129,15 +130,15 @@ static void client_defer_output(struct client *client, return; } - buf = g_malloc(alloc); + buf = (struct deferred_buffer *)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) +static void +client_write_direct(Client *client, const char *data, size_t length) { GError *error = NULL; GIOStatus status; @@ -181,7 +182,7 @@ static void client_write_direct(struct client *client, } void -client_write_output(struct client *client) +client_write_output(Client *client) { if (client_is_expired(client) || !client->send_buf_used) return; @@ -211,7 +212,8 @@ client_write_output(struct client *client) /** * Write a block of data to the client. */ -static void client_write(struct client *client, const char *buffer, size_t buflen) +static void +client_write(Client *client, const char *buffer, size_t buflen) { /* if the client is going to be closed, do nothing */ if (client_is_expired(client)) @@ -236,17 +238,18 @@ static void client_write(struct client *client, const char *buffer, size_t bufle } } -void client_puts(struct client *client, const char *s) +void +client_puts(Client *client, const char *s) { client_write(client, s, strlen(s)); } -void client_vprintf(struct client *client, const char *fmt, va_list args) +void +client_vprintf(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); @@ -256,7 +259,7 @@ void client_vprintf(struct client *client, const char *fmt, va_list args) /* wtf.. */ return; - buffer = g_malloc(length + 1); + char *buffer = (char *)g_malloc(length + 1); vsnprintf(buffer, length + 1, fmt, args); client_write(client, buffer, length); g_free(buffer); @@ -274,7 +277,9 @@ void client_vprintf(struct client *client, const char *fmt, va_list args) #endif } -G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...) +G_GNUC_PRINTF(2, 3) +void +client_printf(Client *client, const char *fmt, ...) { va_list 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..739e982e9 --- /dev/null +++ b/src/CommandError.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 <glib.h> + +class Client; + +enum command_return +print_playlist_result(Client *client, enum playlist_result result); + +/** + * Send the GError to the client and free the GError. + */ +enum command_return +print_error(Client *client, GError *error); + +#endif diff --git a/src/cmdline.c b/src/CommandLine.cxx index cb7eff36a..a588ad5b2 100644 --- a/src/cmdline.c +++ b/src/CommandLine.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,15 @@ */ #include "config.h" -#include "cmdline.h" +#include "CommandLine.hxx" #include "path.h" +#include "ls.hxx" + +extern "C" { #include "log.h" #include "conf.h" +} + #include "decoder_list.h" #include "decoder_plugin.h" #include "output_list.h" @@ -30,7 +35,6 @@ #include "input_plugin.h" #include "playlist_list.h" #include "playlist_plugin.h" -#include "ls.h" #include "mpd_error.h" #include "glib_compat.h" @@ -159,7 +163,7 @@ parse_cmdline(int argc, char **argv, struct options *options, "verbose logging", NULL }, { "version", 'V', 0, G_OPTION_ARG_NONE, &option_version, "print version number", NULL }, - { .long_name = NULL } + { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } }; options->kill = false; diff --git a/src/cmdline.h b/src/CommandLine.hxx index 68f625a6c..7a8731f82 100644 --- a/src/cmdline.h +++ b/src/CommandLine.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef CMDLINE_H -#define CMDLINE_H +#ifndef MPD_COMMAND_LINE_HXX +#define MPD_COMMAND_LINE_HXX #include <glib.h> -#include <stdbool.h> - struct options { gboolean kill; gboolean daemon; diff --git a/src/DatabaseCommands.cxx b/src/DatabaseCommands.cxx new file mode 100644 index 000000000..4e636a33f --- /dev/null +++ b/src/DatabaseCommands.cxx @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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; + } + + GError *error = NULL; + return findAddIn(client->player_control, "", &filter, &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/database.c b/src/DatabaseGlue.cxx index 8c903bb45..283ef9943 100644 --- a/src/database.c +++ b/src/DatabaseGlue.cxx @@ -18,17 +18,21 @@ */ #include "config.h" -#include "database.h" +#include "DatabaseGlue.hxx" +#include "DatabaseSimple.hxx" +#include "DatabaseRegistry.hxx" +#include "DatabaseSave.hxx" +#include "Directory.hxx" + +extern "C" { #include "db_error.h" -#include "db_save.h" -#include "db_selection.h" -#include "db_visitor.h" -#include "db_plugin.h" -#include "db/simple_db_plugin.h" -#include "directory.h" #include "stats.h" #include "conf.h" #include "glib_compat.h" +} + +#include "DatabasePlugin.hxx" +#include "db/SimpleDatabasePlugin.hxx" #include <glib.h> @@ -42,97 +46,89 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "database" -static struct db *db; +static Database *db; static bool db_is_open; +static bool is_simple; bool -db_init(const struct config_param *path, GError **error_r) +DatabaseGlobalInit(const config_param *param, GError **error_r) { assert(db == NULL); assert(!db_is_open); - if (path == NULL) - return true; - - 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); + const char *plugin_name = + config_get_block_string(param, "plugin", "simple"); + is_simple = strcmp(plugin_name, "simple") == 0; - config_param_free(param); + const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name); + if (plugin == NULL) { + g_set_error(error_r, db_quark(), 0, + "No such database plugin: %s", plugin_name); + return false; + } + db = plugin->create(param, error_r); return db != NULL; } void -db_finish(void) +DatabaseGlobalDeinit(void) { if (db_is_open) - db_plugin_close(db); + db->Close(); if (db != NULL) - db_plugin_free(db); + delete db; } -struct directory * -db_get_root(void) +const Database * +GetDatabase() { - assert(db != NULL); + assert(db == NULL || db_is_open); - return simple_db_get_root(db); + return db; } -struct directory * -db_get_directory(const char *name) +const Database * +GetDatabase(GError **error_r) { - if (db == NULL) - return NULL; + assert(db == nullptr || db_is_open); - struct directory *music_root = db_get_root(); - if (name == NULL) - return music_root; + if (db == nullptr) + g_set_error_literal(error_r, db_quark(), DB_DISABLED, + "No database"); - struct directory *directory = - directory_lookup_directory(music_root, name); - return directory; + return db; } -struct song * -db_get_song(const char *file) +bool +db_is_simple(void) { - assert(file != NULL); - - g_debug("get song: %s", file); + assert(db == NULL || db_is_open); - if (db == NULL) - return NULL; - - return db_plugin_get_song(db, file, NULL); + return is_simple; } -bool -db_visit(const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r) +Directory * +db_get_root(void) { - if (db == NULL) { - g_set_error_literal(error_r, db_quark(), DB_DISABLED, - "No database"); - return false; - } + assert(db != NULL); + assert(db_is_simple()); - return db_plugin_visit(db, selection, visitor, ctx, error_r); + return ((SimpleDatabase *)db)->GetRoot(); } -bool -db_walk(const char *uri, - const struct db_visitor *visitor, void *ctx, - GError **error_r) +Directory * +db_get_directory(const char *name) { - struct db_selection selection; - db_selection_init(&selection, uri, true); + if (db == NULL) + return NULL; + + Directory *music_root = db_get_root(); + if (name == NULL) + return music_root; - return db_visit(&selection, visitor, ctx, error_r); + return music_root->LookupDirectory(name); } bool @@ -140,17 +136,18 @@ db_save(GError **error_r) { assert(db != NULL); assert(db_is_open); + assert(db_is_simple()); - return simple_db_save(db, error_r); + return ((SimpleDatabase *)db)->Save(error_r); } bool -db_load(GError **error) +DatabaseGlobalOpen(GError **error) { assert(db != NULL); assert(!db_is_open); - if (!db_plugin_open(db, error)) + if (!db->Open(error)) return false; db_is_open = true; @@ -165,6 +162,7 @@ db_get_mtime(void) { assert(db != NULL); assert(db_is_open); + assert(db_is_simple()); - return simple_db_get_mtime(db); + return ((SimpleDatabase *)db)->GetLastModified(); } diff --git a/src/DatabaseGlue.hxx b/src/DatabaseGlue.hxx new file mode 100644 index 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/db_lock.c b/src/DatabaseLock.cxx index 53759673d..872b9bfd3 100644 --- a/src/db_lock.c +++ b/src/DatabaseLock.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "db_lock.h" +#include "DatabaseLock.hxx" #include "gcc.h" #if GCC_CHECK_VERSION(4, 2) diff --git a/src/db_lock.h b/src/DatabaseLock.hxx index 4640502f3..6646fb43d 100644 --- a/src/db_lock.h +++ b/src/DatabaseLock.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,14 +23,13 @@ * multi-threading. */ -#ifndef MPD_DB_LOCK_H -#define MPD_DB_LOCK_H +#ifndef MPD_DB_LOCK_HXX +#define MPD_DB_LOCK_HXX #include "check.h" #include <glib.h> #include <assert.h> -#include <stdbool.h> extern GStaticMutex db_mutex; @@ -81,4 +80,19 @@ db_unlock(void) g_static_mutex_unlock(&db_mutex); } +#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/db_internal.h b/src/DatabasePlaylist.hxx index a33351524..7c6952ffa 100644 --- a/src/db_internal.h +++ b/src/DatabasePlaylist.hxx @@ -17,19 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DB_INTERNAL_H -#define MPD_DB_INTERNAL_H +#ifndef MPD_DATABASE_PLAYLIST_HXX +#define MPD_DATABASE_PLAYLIST_HXX -#include "db_plugin.h" +#include "gcc.h" +#include "gerror.h" -#include <assert.h> +class SongFilter; -static inline void -db_base_init(struct db *db, const struct db_plugin *plugin) -{ - assert(plugin != NULL); - - db->plugin = plugin; -} +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..2c3fcc9f8 --- /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" + +extern "C" { +#include "song.h" +#include "tag.h" +} + +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +#include <functional> + +static bool +PrintDirectory(Client *client, const Directory &directory) +{ + if (!directory.IsRoot()) + client_printf(client, "directory: %s\n", directory.GetPath()); + + return true; +} + +static void +print_playlist_in_directory(Client *client, + const Directory &directory, + const char *name_utf8) +{ + if (directory.IsRoot()) + client_printf(client, "playlist: %s\n", name_utf8); + else + client_printf(client, "playlist: %s/%s\n", + directory.GetPath(), name_utf8); +} + +static bool +PrintSongBrief(Client *client, song &song) +{ + assert(song.parent != NULL); + + song_print_uri(client, &song); + + if (song.tag != NULL && song.tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, *song.parent, song.uri); + + return true; +} + +static bool +PrintSongFull(Client *client, song &song) +{ + assert(song.parent != NULL); + + song_print_info(client, &song); + + if (song.tag != NULL && song.tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, *song.parent, song.uri); + + return true; +} + +static bool +PrintPlaylistBrief(Client *client, + const PlaylistInfo &playlist, + const Directory &directory) +{ + print_playlist_in_directory(client, directory, playlist.name.c_str()); + return true; +} + +static bool +PrintPlaylistFull(Client *client, + const PlaylistInfo &playlist, + const Directory &directory) +{ + print_playlist_in_directory(client, directory, playlist.name.c_str()); + + if (playlist.mtime > 0) + time_print(client, "Last-Modified", playlist.mtime); + + return true; +} + +bool +db_selection_print(Client *client, const DatabaseSelection &selection, + bool full, GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + using namespace std::placeholders; + const auto d = selection.filter == nullptr + ? std::bind(PrintDirectory, client, _1) + : VisitDirectory(); + const auto s = std::bind(full ? PrintSongFull : PrintSongBrief, + client, _1); + const auto p = selection.filter == nullptr + ? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief, + client, _1, _2) + : VisitPlaylist(); + + return db->Visit(selection, d, s, p, error_r); +} + +struct SearchStats { + int numberOfSongs; + unsigned long playTime; +}; + +static void printSearchStats(Client *client, SearchStats *stats) +{ + client_printf(client, "songs: %i\n", stats->numberOfSongs); + client_printf(client, "playtime: %li\n", stats->playTime); +} + +static bool +stats_visitor_song(SearchStats &stats, song &song) +{ + stats.numberOfSongs++; + stats.playTime += song_get_duration(&song); + + return true; +} + +bool +searchStatsForSongsIn(Client *client, const char *name, + const SongFilter *filter, + GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + const DatabaseSelection selection(name, true, filter); + + SearchStats stats; + stats.numberOfSongs = 0; + stats.playTime = 0; + + using namespace std::placeholders; + const auto f = std::bind(stats_visitor_song, std::ref(stats), + _1); + if (!db->Visit(selection, f, error_r)) + return false; + + printSearchStats(client, &stats); + return true; +} + +bool +printAllIn(Client *client, const char *uri_utf8, GError **error_r) +{ + const DatabaseSelection selection(uri_utf8, true); + return db_selection_print(client, selection, false, error_r); +} + +bool +printInfoForAllIn(Client *client, const char *uri_utf8, + GError **error_r) +{ + const DatabaseSelection selection(uri_utf8, true); + return db_selection_print(client, selection, true, error_r); +} + +static bool +PrintSongURIVisitor(Client *client, song &song) +{ + song_print_uri(client, &song); + + return true; +} + +static bool +PrintUniqueTag(Client *client, enum tag_type tag_type, + const char *value) +{ + client_printf(client, "%s: %s\n", tag_item_names[tag_type], value); + return true; +} + +bool +listAllUniqueTags(Client *client, int type, + const SongFilter *filter, + GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + const DatabaseSelection selection("", true, filter); + + if (type == LOCATE_TAG_FILE_TYPE) { + using namespace std::placeholders; + const auto f = std::bind(PrintSongURIVisitor, client, _1); + return db->Visit(selection, f, error_r); + } else { + using namespace std::placeholders; + const auto f = std::bind(PrintUniqueTag, client, + (enum tag_type)type, _1); + return db->VisitUniqueTags(selection, (enum tag_type)type, + f, error_r); + } +} diff --git a/src/db_print.h b/src/DatabasePrint.hxx index 1b957da18..68551b63c 100644 --- a/src/db_print.h +++ b/src/DatabasePrint.hxx @@ -21,51 +21,37 @@ #define MPD_DB_PRINT_H #include "gcc.h" +#include "gerror.h" -#include <glib.h> -#include <stdbool.h> - -struct client; -struct locate_item_list; -struct db_selection; +class SongFilter; +struct DatabaseSelection; struct db_visitor; +class Client; -gcc_nonnull(1,2) +gcc_nonnull(1) bool -db_selection_print(struct client *client, const struct db_selection *selection, +db_selection_print(Client *client, const DatabaseSelection &selection, bool full, GError **error_r); gcc_nonnull(1,2) bool -printAllIn(struct client *client, const char *uri_utf8, GError **error_r); +printAllIn(Client *client, const char *uri_utf8, GError **error_r); gcc_nonnull(1,2) bool -printInfoForAllIn(struct client *client, const char *uri_utf8, +printInfoForAllIn(Client *client, const char *uri_utf8, GError **error_r); -gcc_nonnull(1,2,3) -bool -searchForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r); - -gcc_nonnull(1,2,3) -bool -findSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r); - -gcc_nonnull(1,2,3) +gcc_nonnull(1,2) bool -searchStatsForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, +searchStatsForSongsIn(Client *client, const char *name, + const SongFilter *filter, GError **error_r); -gcc_nonnull(1,3) +gcc_nonnull(1) bool -listAllUniqueTags(struct client *client, int type, - const struct locate_item_list *criteria, +listAllUniqueTags(Client *client, int type, + const SongFilter *filter, GError **error_r); #endif diff --git a/src/DatabaseQueue.cxx b/src/DatabaseQueue.cxx new file mode 100644 index 000000000..325748d02 --- /dev/null +++ b/src/DatabaseQueue.cxx @@ -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. + */ + +#include "config.h" +#include "DatabaseQueue.hxx" +#include "DatabaseSelection.hxx" + +extern "C" { +#include "playlist.h" +} + +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +#include <functional> + +static bool +AddToQueue(struct player_control *pc, song &song, GError **error_r) +{ + 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; +} + +bool +findAddIn(struct player_control *pc, const char *uri, + 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(AddToQueue, pc, _1, _2); + return db->Visit(selection, f, error_r); +} diff --git a/src/db_save.h b/src/DatabaseQueue.hxx index e760ec881..21ffe0fb0 100644 --- a/src/db_save.h +++ b/src/DatabaseQueue.hxx @@ -17,19 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DB_SAVE_H -#define MPD_DB_SAVE_H +#ifndef MPD_DATABASE_QUEUE_HXX +#define MPD_DATABASE_QUEUE_HXX -#include <glib.h> -#include <stdbool.h> -#include <stdio.h> +#include "gcc.h" +#include "gerror.h" -struct directory; - -void -db_save_internal(FILE *file, const struct directory *root); +class SongFilter; +struct player_control; +gcc_nonnull(1,2) bool -db_load_internal(FILE *file, struct directory *root, GError **error); +findAddIn(struct player_control *pc, const char *name, + const SongFilter *filter, 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/db_save.c b/src/DatabaseSave.cxx index 4af9d58b8..78a2c4939 100644 --- a/src/db_save.c +++ b/src/DatabaseSave.cxx @@ -18,15 +18,18 @@ */ #include "config.h" -#include "db_save.h" -#include "db_lock.h" -#include "directory.h" -#include "directory_save.h" +#include "DatabaseSave.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" +#include "DirectorySave.hxx" #include "song.h" +#include "TextFile.hxx" + +extern "C" { #include "path.h" -#include "text_file.h" #include "tag.h" #include "tag_internal.h" +} #include <glib.h> @@ -55,7 +58,7 @@ db_quark(void) } void -db_save_internal(FILE *fp, const struct directory *music_root) +db_save_internal(FILE *fp, const Directory *music_root) { assert(music_root != NULL); @@ -74,9 +77,8 @@ db_save_internal(FILE *fp, const struct directory *music_root) } bool -db_load_internal(FILE *fp, struct directory *music_root, GError **error) +db_load_internal(TextFile &file, Directory *music_root, GError **error) { - GString *buffer = g_string_sized_new(1024); char *line; int format = 0; bool found_charset = false, found_version = false; @@ -86,16 +88,15 @@ db_load_internal(FILE *fp, struct directory *music_root, GError **error) assert(music_root != NULL); /* get initial info */ - line = read_text_line(fp, buffer); + line = file.ReadLine(); if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) { g_set_error(error, db_quark(), 0, "Database corrupted"); - g_string_free(buffer, true); return false; } memset(tags, false, sizeof(tags)); - while ((line = read_text_line(fp, buffer)) != NULL && + while ((line = file.ReadLine()) != NULL && strcmp(line, DIRECTORY_INFO_END) != 0) { if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) { format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1); @@ -103,7 +104,6 @@ db_load_internal(FILE *fp, struct directory *music_root, GError **error) if (found_version) { g_set_error(error, db_quark(), 0, "Duplicate version line"); - g_string_free(buffer, true); return false; } @@ -114,7 +114,6 @@ db_load_internal(FILE *fp, struct directory *music_root, GError **error) if (found_charset) { g_set_error(error, db_quark(), 0, "Duplicate charset line"); - g_string_free(buffer, true); return false; } @@ -129,7 +128,6 @@ db_load_internal(FILE *fp, struct directory *music_root, GError **error) "\"%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)) { @@ -147,7 +145,6 @@ db_load_internal(FILE *fp, struct directory *music_root, GError **error) } else { g_set_error(error, db_quark(), 0, "Malformed line: %s", line); - g_string_free(buffer, true); return false; } } @@ -171,9 +168,8 @@ db_load_internal(FILE *fp, struct directory *music_root, GError **error) g_debug("reading DB"); db_lock(); - success = directory_load(fp, music_root, buffer, error); + success = directory_load(file, music_root, error); db_unlock(); - g_string_free(buffer, true); return success; } diff --git a/src/DatabaseSave.hxx b/src/DatabaseSave.hxx new file mode 100644 index 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/db_selection.h b/src/DatabaseSelection.hxx index 2cebb4907..3a81c01ec 100644 --- a/src/db_selection.h +++ b/src/DatabaseSelection.hxx @@ -17,17 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DB_SELECTION_H -#define MPD_DB_SELECTION_H +#ifndef MPD_DATABASE_SELECTION_HXX +#define MPD_DATABASE_SELECTION_HXX #include "gcc.h" #include <assert.h> +#include <stddef.h> -struct directory; +class SongFilter; struct song; -struct db_selection { +struct DatabaseSelection { /** * The base URI of the search (UTF-8). Must not begin or end * with a slash. NULL or an empty string searches the whole @@ -39,18 +40,17 @@ struct db_selection { * Recursively search all sub directories? */ bool recursive; -}; -gcc_nonnull(1,2) -static inline void -db_selection_init(struct db_selection *selection, - const char *uri, bool recursive) -{ - assert(selection != NULL); - assert(uri != NULL); - - selection->uri = uri; - selection->recursive = recursive; -} + const SongFilter *filter; + + DatabaseSelection(const char *_uri, bool _recursive, + const SongFilter *_filter=nullptr) + :uri(_uri), recursive(_recursive), filter(_filter) { + assert(uri != NULL); + } + + gcc_pure + bool Match(const song &song) const; +}; #endif diff --git a/src/database.h b/src/DatabaseSimple.hxx index f877b74d3..e57ae41bf 100644 --- a/src/database.h +++ b/src/DatabaseSimple.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DATABASE_H -#define MPD_DATABASE_H +#ifndef MPD_DATABASE_SIMPLE_HXX +#define MPD_DATABASE_SIMPLE_HXX #include "gcc.h" @@ -28,68 +28,55 @@ #include <stdbool.h> struct config_param; -struct directory; +struct Directory; struct db_selection; struct db_visitor; /** - * Initialize the database library. - * - * @param path the absolute path of the database file + * Check whether the default #SimpleDatabasePlugin is used. This + * allows using db_get_root(), db_save(), db_get_mtime() and + * db_exists(). */ bool -db_init(const struct config_param *path, GError **error_r); - -void -db_finish(void); +db_is_simple(void); /** * Returns the root directory object. Returns NULL if there is no * configured music directory. + * + * May only be used if db_is_simple() returns true. */ -G_GNUC_PURE -struct directory * +gcc_pure +Directory * db_get_root(void); /** * Caller must lock the #db_mutex. */ gcc_nonnull(1) -G_GNUC_PURE -struct directory * +gcc_pure +Directory * db_get_directory(const char *name); -gcc_nonnull(1) -G_GNUC_PURE -struct song * -db_get_song(const char *file); - -gcc_nonnull(1,2) -bool -db_visit(const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r); - -gcc_nonnull(1,2) -bool -db_walk(const char *uri, - const struct db_visitor *visitor, void *ctx, - GError **error_r); - +/** + * May only be used if db_is_simple() returns true. + */ bool db_save(GError **error_r); -bool -db_load(GError **error); - -G_GNUC_PURE +/** + * May only be used if db_is_simple() returns true. + */ +gcc_pure time_t db_get_mtime(void); /** * Returns true if there is a valid database file on the disk. + * + * May only be used if db_is_simple() returns true. */ -G_GNUC_PURE +gcc_pure static inline bool db_exists(void) { diff --git a/src/song_print.h b/src/DatabaseVisitor.hxx index 8f1f0cc65..c90441415 100644 --- a/src/song_print.h +++ b/src/DatabaseVisitor.hxx @@ -17,17 +17,22 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_SONG_PRINT_H -#define MPD_SONG_PRINT_H +#ifndef MPD_DATABASE_VISITOR_HXX +#define MPD_DATABASE_VISITOR_HXX -struct client; +#include "gerror.h" + +#include <functional> + +struct Directory; struct song; -struct songvec; +struct PlaylistInfo; -void -song_print_info(struct client *client, struct song *song); +typedef std::function<bool(const Directory &, GError **)> VisitDirectory; +typedef std::function<bool(struct song &, GError **)> VisitSong; +typedef std::function<bool(const PlaylistInfo &, const Directory &, + GError **)> VisitPlaylist; -void -song_print_uri(struct client *client, struct song *song); +typedef std::function<bool(const char *, GError **)> VisitString; #endif diff --git a/src/decoder_print.c b/src/DecoderPrint.cxx index e14477ed8..70c713e9c 100644 --- a/src/decoder_print.c +++ b/src/DecoderPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,15 +18,15 @@ */ #include "config.h" -#include "decoder_print.h" +#include "DecoderPrint.hxx" #include "decoder_list.h" #include "decoder_plugin.h" -#include "client.h" +#include "Client.hxx" #include <assert.h> static void -decoder_plugin_print(struct client *client, +decoder_plugin_print(Client *client, const struct decoder_plugin *plugin) { const char *const*p; @@ -46,7 +46,7 @@ decoder_plugin_print(struct client *client, } void -decoder_list_print(struct client *client) +decoder_list_print(Client *client) { decoder_plugins_for_each_enabled(plugin) decoder_plugin_print(client, plugin); diff --git a/src/DecoderPrint.hxx b/src/DecoderPrint.hxx new file mode 100644 index 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/decoder_thread.c b/src/DecoderThread.cxx index af80ed45b..cbdb19188 100644 --- a/src/decoder_thread.c +++ b/src/DecoderThread.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,21 +18,23 @@ */ #include "config.h" -#include "decoder_thread.h" +#include "DecoderThread.hxx" +#include "decoder_error.h" +#include "decoder_plugin.h" +#include "song.h" +#include "mpd_error.h" +#include "Mapper.hxx" + +extern "C" { #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> @@ -182,12 +184,7 @@ decoder_file_decode(const struct decoder_plugin *plugin, static inline gpointer deconst_plugin(const struct decoder_plugin *plugin) { - union { - const struct decoder_plugin *in; - gpointer out; - } u = { .in = plugin }; - - return u.out; + return const_cast<struct decoder_plugin *>(plugin); } /** @@ -383,11 +380,7 @@ static void decoder_run_song(struct decoder_control *dc, const struct song *song, const char *uri) { - struct decoder decoder = { - .dc = dc, - .initial_seek_pending = dc->start_ms > 0, - .initial_seek_running = false, - }; + decoder decoder(dc, dc->start_ms > 0); int ret; decoder.timestamp = 0.0; @@ -428,12 +421,27 @@ decoder_run_song(struct decoder_control *dc, decoder_lock(dc); - dc->state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR; + 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); + } } static void decoder_run(struct decoder_control *dc) { + dc_clear_error(dc); + const struct song *song = dc->song; char *uri; @@ -446,6 +454,9 @@ decoder_run(struct decoder_control *dc) if (uri == NULL) { dc->state = DECODE_STATE_ERROR; + dc->error = g_error_new(decoder_quark(), 0, + "Failed to map song"); + decoder_command_finished_locked(dc); return; } @@ -458,7 +469,7 @@ decoder_run(struct decoder_control *dc) static gpointer decoder_task(gpointer arg) { - struct decoder_control *dc = arg; + struct decoder_control *dc = (struct decoder_control *)arg; decoder_lock(dc); diff --git a/src/decoder_thread.h b/src/DecoderThread.hxx index 78f12a54a..8efaa2fca 100644 --- a/src/decoder_thread.h +++ b/src/DecoderThread.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DECODER_THREAD_H -#define MPD_DECODER_THREAD_H +#ifndef MPD_DECODER_THREAD_HXX +#define MPD_DECODER_THREAD_HXX struct decoder_control; diff --git a/src/Directory.cxx b/src/Directory.cxx new file mode 100644 index 000000000..f27b3d474 --- /dev/null +++ b/src/Directory.cxx @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "path.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..710f47921 --- /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 <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) + +struct song; +struct db_visitor; +class SongFilter; + +struct Directory { + /** + * Pointers to the siblings of this directory within the + * parent directory. It is unused (undefined) in the root + * directory. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + struct list_head siblings; + + /** + * A doubly linked list of child directories. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + struct list_head children; + + /** + * A doubly linked list of songs within this directory. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + struct list_head songs; + + PlaylistVector playlists; + + Directory *parent; + time_t mtime; + ino_t inode; + dev_t device; + bool have_stat; /* not needed if ino_t == dev_t == 0 is impossible */ + char path[sizeof(long)]; + +protected: + Directory(const char *path); + + gcc_malloc gcc_nonnull_all + static Directory *Allocate(const char *path); + +public: + /** + * Default constructor, needed for #detached_root. + */ + Directory(); + ~Directory(); + + /** + * Generic constructor for #Directory object. + */ + gcc_malloc + static Directory *NewGeneric(const char *path_utf8, Directory *parent); + + /** + * Create a new root #Directory object. + */ + gcc_malloc + static Directory *NewRoot() { + return NewGeneric("", nullptr); + } + + /** + * Free this #Directory object (and the whole object tree within it), + * assuming it was already removed from the parent. + */ + void Free(); + + /** + * Remove this #Directory object from its parent and free it. This + * must not be called with the root Directory. + * + * Caller must lock the #db_mutex. + */ + void Delete(); + + /** + * Create a new #Directory object as a child of the given one. + * + * Caller must lock the #db_mutex. + * + * @param name_utf8 the UTF-8 encoded name of the new sub directory + */ + gcc_malloc + Directory *CreateChild(const char *name_utf8); + + /** + * Caller must lock the #db_mutex. + */ + gcc_pure + const Directory *FindChild(const char *name) const; + + gcc_pure + Directory *FindChild(const char *name) { + const Directory *cthis = this; + return const_cast<Directory *>(cthis->FindChild(name)); + } + + /** + * Look up a sub directory, and create the object if it does not + * exist. + * + * Caller must lock the #db_mutex. + */ + Directory *MakeChild(const char *name_utf8) { + Directory *child = FindChild(name_utf8); + if (child == nullptr) + child = CreateChild(name_utf8); + return child; + } + + /** + * Looks up a directory by its relative URI. + * + * @param uri the relative URI + * @return the Directory, or NULL if none was found + */ + gcc_pure + Directory *LookupDirectory(const char *uri); + + gcc_pure + bool IsEmpty() const { + return list_empty(&children) && + list_empty(&songs) && + playlists.empty(); + } + + gcc_pure + const char *GetPath() const { + return path; + } + + /** + * Returns the base name of the directory. + */ + gcc_pure + const char *GetName() const; + + /** + * Is this the root directory of the music database? + */ + gcc_pure + bool IsRoot() const { + return parent == NULL; + } + + /** + * Look up a song in this directory by its name. + * + * Caller must lock the #db_mutex. + */ + gcc_pure + const song *FindSong(const char *name_utf8) const; + + gcc_pure + song *FindSong(const char *name_utf8) { + const Directory *cthis = this; + return const_cast<song *>(cthis->FindSong(name_utf8)); + } + + /** + * Looks up a song by its relative URI. + * + * Caller must lock the #db_mutex. + * + * @param uri the relative URI + * @return the song, or NULL if none was found + */ + gcc_pure + song *LookupSong(const char *uri); + + /** + * Add a song object to this directory. Its "parent" attribute must + * be set already. + */ + void AddSong(song *song); + + /** + * Remove a song object from this directory (which effectively + * invalidates the song object, because the "parent" attribute becomes + * stale), but does not free it. + */ + void RemoveSong(song *song); + + /** + * Caller must lock the #db_mutex. + */ + void PruneEmpty(); + + /** + * Sort all directory entries recursively. + * + * Caller must lock the #db_mutex. + */ + void Sort(); + + /** + * Caller must lock #db_mutex. + */ + bool Walk(bool recursive, const SongFilter *match, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const; +}; + +static inline bool +isRootDirectory(const char *name) +{ + return name[0] == 0 || (name[0] == '/' && name[1] == 0); +} + +#endif diff --git a/src/directory_save.c b/src/DirectorySave.cxx index de1df050a..6a5efb058 100644 --- a/src/directory_save.c +++ b/src/DirectorySave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,12 +18,12 @@ */ #include "config.h" -#include "directory_save.h" -#include "directory.h" +#include "DirectorySave.hxx" +#include "Directory.hxx" #include "song.h" -#include "text_file.h" -#include "song_save.h" -#include "playlist_database.h" +#include "SongSave.hxx" +#include "PlaylistDatabase.hxx" +#include "TextFile.hxx" #include <assert.h> #include <string.h> @@ -43,17 +43,16 @@ directory_quark(void) } void -directory_save(FILE *fp, const struct directory *directory) +directory_save(FILE *fp, const Directory *directory) { - if (!directory_is_root(directory)) { + if (!directory->IsRoot()) { fprintf(fp, DIRECTORY_MTIME "%lu\n", (unsigned long)directory->mtime); - fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, - directory_get_path(directory)); + fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, directory->GetPath()); } - struct directory *cur; + Directory *cur; directory_for_each_child(cur, directory) { char *base = g_path_get_basename(cur->path); @@ -70,33 +69,31 @@ directory_save(FILE *fp, const struct directory *directory) directory_for_each_song(song, directory) song_save(fp, song); - playlist_vector_save(fp, &directory->playlists); + playlist_vector_save(fp, directory->playlists); - if (!directory_is_root(directory)) - fprintf(fp, DIRECTORY_END "%s\n", - directory_get_path(directory)); + if (!directory->IsRoot()) + fprintf(fp, DIRECTORY_END "%s\n", directory->GetPath()); } -static struct directory * -directory_load_subdir(FILE *fp, struct directory *parent, const char *name, - GString *buffer, GError **error_r) +static Directory * +directory_load_subdir(TextFile &file, Directory *parent, const char *name, + GError **error_r) { - const char *line; bool success; - if (directory_get_child(parent, name) != NULL) { + if (parent->FindChild(name) != nullptr) { g_set_error(error_r, directory_quark(), 0, "Duplicate subdirectory '%s'", name); return NULL; } - struct directory *directory = directory_new_child(parent, name); + Directory *directory = parent->CreateChild(name); - line = read_text_line(fp, buffer); + const char *line = file.ReadLine(); if (line == NULL) { g_set_error(error_r, directory_quark(), 0, "Unexpected end of file"); - directory_delete(directory); + directory->Delete(); return NULL; } @@ -105,11 +102,11 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name, g_ascii_strtoull(line + sizeof(DIRECTORY_MTIME) - 1, NULL, 10); - line = read_text_line(fp, buffer); + line = file.ReadLine(); if (line == NULL) { g_set_error(error_r, directory_quark(), 0, "Unexpected end of file"); - directory_delete(directory); + directory->Delete(); return NULL; } } @@ -117,13 +114,13 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name, if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) { g_set_error(error_r, directory_quark(), 0, "Malformed line: %s", line); - directory_delete(directory); + directory->Delete(); return NULL; } - success = directory_load(fp, directory, buffer, error_r); + success = directory_load(file, directory, error_r); if (!success) { - directory_delete(directory); + directory->Delete(); return NULL; } @@ -131,44 +128,42 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name, } bool -directory_load(FILE *fp, struct directory *directory, - GString *buffer, GError **error) +directory_load(TextFile &file, Directory *directory, GError **error) { const char *line; - while ((line = read_text_line(fp, buffer)) != NULL && + while ((line = file.ReadLine()) != NULL && !g_str_has_prefix(line, DIRECTORY_END)) { if (g_str_has_prefix(line, DIRECTORY_DIR)) { - struct directory *subdir = - directory_load_subdir(fp, directory, + Directory *subdir = + directory_load_subdir(file, directory, line + sizeof(DIRECTORY_DIR) - 1, - buffer, error); + error); if (subdir == NULL) return false; } else if (g_str_has_prefix(line, SONG_BEGIN)) { const char *name = line + sizeof(SONG_BEGIN) - 1; struct song *song; - if (directory_get_song(directory, name) != NULL) { + if (directory->FindSong(name) != nullptr) { g_set_error(error, directory_quark(), 0, "Duplicate song '%s'", name); - return NULL; + return false; } - song = song_load(fp, directory, name, - buffer, error); + song = song_load(file, directory, name, error); if (song == NULL) return false; - directory_add_song(directory, song); + directory->AddSong(song); } else if (g_str_has_prefix(line, PLAYLIST_META_BEGIN)) { /* duplicate the name, because playlist_metadata_load() will overwrite the buffer */ char *name = g_strdup(line + sizeof(PLAYLIST_META_BEGIN) - 1); - if (!playlist_metadata_load(fp, &directory->playlists, - name, buffer, error)) { + if (!playlist_metadata_load(file, directory->playlists, + name, error)) { g_free(name); return false; } diff --git a/src/directory_save.h b/src/DirectorySave.hxx index 2d0056830..a7f3034a7 100644 --- a/src/directory_save.h +++ b/src/DirectorySave.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,21 +17,20 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DIRECTORY_SAVE_H -#define MPD_DIRECTORY_SAVE_H +#ifndef MPD_DIRECTORY_SAVE_HXX +#define MPD_DIRECTORY_SAVE_HXX #include <glib.h> -#include <stdbool.h> #include <stdio.h> -struct directory; +struct Directory; +class TextFile; void -directory_save(FILE *fp, const struct directory *directory); +directory_save(FILE *fp, const Directory *directory); bool -directory_load(FILE *fp, struct directory *directory, - GString *buffer, GError **error); +directory_load(TextFile &file, Directory *directory, GError **error); #endif diff --git a/src/exclude.c b/src/ExcludeList.cxx index 438039d30..4beb7afc3 100644 --- a/src/exclude.c +++ b/src/ExcludeList.cxx @@ -23,24 +23,23 @@ */ #include "config.h" -#include "exclude.h" +#include "ExcludeList.hxx" + +extern "C" { #include "path.h" +} #include <assert.h> #include <string.h> #include <stdio.h> #include <errno.h> -GSList * -exclude_list_load(const char *path_fs) +bool +ExcludeList::LoadFile(const char *path_fs) { - FILE *file; - char line[1024]; - GSList *list = NULL; - assert(path_fs != NULL); - file = fopen(path_fs, "r"); + FILE *file = fopen(path_fs, "r"); if (file == NULL) { if (errno != ENOENT) { char *path_utf8 = fs_charset_to_utf8(path_fs); @@ -49,9 +48,10 @@ exclude_list_load(const char *path_fs) g_free(path_utf8); } - return NULL; + return false; } + char line[1024]; while (fgets(line, sizeof(line), file) != NULL) { char *p = strchr(line, '#'); if (p != NULL) @@ -59,37 +59,24 @@ exclude_list_load(const char *path_fs) p = g_strstrip(line); if (*p != 0) - list = g_slist_prepend(list, g_pattern_spec_new(p)); + patterns.emplace_front(p); } fclose(file); - return list; -} - -void -exclude_list_free(GSList *list) -{ - while (list != NULL) { - GPatternSpec *pattern = list->data; - g_pattern_spec_free(pattern); - list = g_slist_remove(list, list->data); - } + return true; } bool -exclude_list_check(GSList *list, const char *name_fs) +ExcludeList::Check(const char *name_fs) const { assert(name_fs != NULL); /* XXX include full path name in check */ - for (; list != NULL; list = list->next) { - GPatternSpec *pattern = list->data; - - if (g_pattern_match_string(pattern, name_fs)) + for (const auto &i : patterns) + if (i.Check(name_fs)) return true; - } return false; } diff --git a/src/ExcludeList.hxx b/src/ExcludeList.hxx new file mode 100644 index 000000000..4d678b085 --- /dev/null +++ b/src/ExcludeList.hxx @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 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 char *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/inotify_queue.c b/src/InotifyQueue.cxx index d5e2228c3..a2e21272f 100644 --- a/src/inotify_queue.c +++ b/src/InotifyQueue.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,11 @@ */ #include "config.h" -#include "inotify_queue.h" -#include "update.h" +#include "InotifyQueue.hxx" +#include "UpdateGlue.hxx" + +#include <deque> +#include <string> #include <glib.h> @@ -37,7 +40,7 @@ enum { INOTIFY_UPDATE_DELAY_S = 5, }; -static GSList *inotify_queue; +static std::deque<std::string> inotify_queue; static guint queue_source_id; void @@ -45,20 +48,11 @@ 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 @@ -66,8 +60,8 @@ mpd_inotify_run_update(G_GNUC_UNUSED gpointer data) { unsigned id; - while (inotify_queue != NULL) { - char *uri_utf8 = inotify_queue->data; + while (!inotify_queue.empty()) { + const char *uri_utf8 = inotify_queue.front().c_str(); id = update_enqueue(uri_utf8, false); if (id == 0) @@ -76,9 +70,7 @@ mpd_inotify_run_update(G_GNUC_UNUSED gpointer data) g_debug("updating '%s' job=%u", uri_utf8, id); - g_free(uri_utf8); - inotify_queue = g_slist_delete_link(inotify_queue, - inotify_queue); + inotify_queue.pop_front(); } /* done, remove the timer event by returning false */ @@ -97,39 +89,29 @@ path_in(const char *path, const char *possible_parent) } void -mpd_inotify_enqueue(char *uri_utf8) +mpd_inotify_enqueue(const char *uri_utf8) { - GSList *old_queue = inotify_queue; - if (queue_source_id != 0) g_source_remove(queue_source_id); queue_source_id = g_timeout_add_seconds(INOTIFY_UPDATE_DELAY_S, mpd_inotify_run_update, NULL); - inotify_queue = NULL; - while (old_queue != NULL) { - char *current_uri = old_queue->data; + for (auto i = inotify_queue.begin(), end = inotify_queue.end(); + i != end;) { + const char *current_uri = i->c_str(); - if (path_in(uri_utf8, current_uri)) { + if (path_in(uri_utf8, current_uri)) /* already enqueued */ - g_free(uri_utf8); - inotify_queue = g_slist_concat(inotify_queue, - old_queue); return; - } - - old_queue = g_slist_delete_link(old_queue, old_queue); if (path_in(current_uri, uri_utf8)) /* existing path is a sub-path of the new path; we can dequeue the existing path and update the new path instead */ - g_free(current_uri); + i = inotify_queue.erase(i); else - /* move the existing path to the new queue */ - inotify_queue = g_slist_prepend(inotify_queue, - current_uri); + ++i; } - inotify_queue = g_slist_prepend(inotify_queue, uri_utf8); + inotify_queue.emplace_back(uri_utf8); } diff --git a/src/inotify_queue.h b/src/InotifyQueue.hxx index cfc28ebfe..158a5dbb5 100644 --- a/src/inotify_queue.h +++ b/src/InotifyQueue.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INOTIFY_QUEUE_H -#define MPD_INOTIFY_QUEUE_H +#ifndef MPD_INOTIFY_QUEUE_HXX +#define MPD_INOTIFY_QUEUE_HXX void mpd_inotify_queue_init(void); @@ -27,6 +27,6 @@ void mpd_inotify_queue_finish(void); void -mpd_inotify_enqueue(char *uri_utf8); +mpd_inotify_enqueue(const char *uri_utf8); #endif diff --git a/src/inotify_source.c b/src/InotifySource.cxx index e415f5e72..16fd62511 100644 --- a/src/inotify_source.c +++ b/src/InotifySource.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,15 +18,20 @@ */ #include "config.h" -#include "inotify_source.h" +#include "InotifySource.hxx" + +extern "C" { #include "fifo_buffer.h" +} + #include "fd_util.h" #include "mpd_error.h" +#include <glib.h> + #include <sys/inotify.h> #include <unistd.h> #include <errno.h> -#include <stdbool.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "inotify" @@ -61,11 +66,10 @@ mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source, G_GNUC_UNUSED GIOCondition condition, gpointer data) { - struct mpd_inotify_source *source = data; + struct mpd_inotify_source *source = (struct mpd_inotify_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) @@ -83,7 +87,9 @@ mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source, while (true) { const char *name; - event = fifo_buffer_read(source->buffer, &length); + const struct inotify_event *event = + (const struct inotify_event *) + fifo_buffer_read(source->buffer, &length); if (event == NULL || length < sizeof(*event) || length < sizeof(*event) + event->len) break; diff --git a/src/inotify_source.h b/src/InotifySource.hxx index f92e18e39..cde2bc269 100644 --- a/src/inotify_source.h +++ b/src/InotifySource.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INOTIFY_SOURCE_H -#define MPD_INOTIFY_SOURCE_H +#ifndef MPD_INOTIFY_SOURCE_HXX +#define MPD_INOTIFY_SOURCE_HXX -#include <glib.h> +#include "gerror.h" typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask, const char *name, void *ctx); diff --git a/src/inotify_update.c b/src/InotifyUpdate.cxx index 3f4a8c0c4..b7bb3af2b 100644 --- a/src/inotify_update.c +++ b/src/InotifyUpdate.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,17 +18,19 @@ */ #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 "InotifyUpdate.hxx" +#include "InotifySource.hxx" +#include "InotifyQueue.hxx" +#include "Mapper.hxx" + +extern "C" { #include "path.h" +} +#include <glib.h> #include <assert.h> #include <sys/inotify.h> #include <sys/stat.h> -#include <stdbool.h> #include <string.h> #include <dirent.h> #include <errno.h> @@ -90,7 +92,8 @@ tree_remove_watch_directory(struct watch_directory *directory) static struct watch_directory * tree_find_watch_directory(int wd) { - return g_tree_lookup(inotify_directories, GINT_TO_POINTER(wd)); + return (struct watch_directory *) + g_tree_lookup(inotify_directories, GINT_TO_POINTER(wd)); } static void @@ -109,7 +112,7 @@ remove_watch_directory(struct watch_directory *directory) tree_remove_watch_directory(directory); while (directory->children != NULL) - remove_watch_directory(directory->children->data); + remove_watch_directory((struct watch_directory *)directory->children->data); directory->parent->children = g_list_remove(directory->parent->children, directory); @@ -292,10 +295,10 @@ mpd_inotify_callback(int wd, unsigned mask, ? fs_charset_to_utf8(uri_fs) : g_strdup(""); - if (uri_utf8 != NULL) - /* this function will take care of freeing - uri_utf8 */ + if (uri_utf8 != NULL) { mpd_inotify_enqueue(uri_utf8); + g_free(uri_utf8); + } } g_free(uri_fs); @@ -349,7 +352,7 @@ static gboolean free_watch_directory(G_GNUC_UNUSED gpointer key, gpointer value, G_GNUC_UNUSED gpointer data) { - struct watch_directory *directory = value; + struct watch_directory *directory = (struct watch_directory *)value; g_free(directory->name); g_list_free(directory->children); diff --git a/src/inotify_update.h b/src/InotifyUpdate.hxx index ca75c0f45..ceb421553 100644 --- a/src/inotify_update.h +++ b/src/InotifyUpdate.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INOTIFY_UPDATE_H -#define MPD_INOTIFY_UPDATE_H +#ifndef MPD_INOTIFY_UPDATE_HXX +#define MPD_INOTIFY_UPDATE_HXX #include "check.h" diff --git a/src/listen.c b/src/Listen.cxx index 90e13b9c1..954de08f1 100644 --- a/src/listen.c +++ b/src/Listen.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,14 @@ */ #include "config.h" -#include "listen.h" +#include "Listen.hxx" +#include "Main.hxx" +#include "Client.hxx" + +extern "C" { #include "server_socket.h" -#include "client.h" #include "conf.h" -#include "main.h" +} #include <string.h> #include <assert.h> diff --git a/src/listen.h b/src/Listen.hxx index 246e83706..fd553477b 100644 --- a/src/listen.h +++ b/src/Listen.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_LISTEN_H -#define MPD_LISTEN_H +#ifndef MPD_LISTEN_HXX +#define MPD_LISTEN_HXX -#include <glib.h> +#include "gerror.h" #include <stdbool.h> diff --git a/src/main.c b/src/Main.cxx index 12f8d86f6..1974a6103 100644 --- a/src/main.c +++ b/src/Main.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,24 +18,29 @@ */ #include "config.h" -#include "main.h" +#include "Main.hxx" +#include "CommandLine.hxx" +#include "PlaylistFile.hxx" +#include "UpdateGlue.hxx" +#include "chunk.h" +#include "StateFile.hxx" +#include "PlayerThread.hxx" +#include "Mapper.hxx" +#include "DatabaseGlue.hxx" +#include "DatabaseSimple.hxx" +#include "Permission.hxx" +#include "Listen.hxx" +#include "ClientIdle.hxx" +#include "Client.hxx" +#include "AllCommands.hxx" + +extern "C" { #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" @@ -43,31 +48,31 @@ #include "output_all.h" #include "volume.h" #include "log.h" -#include "permission.h" #include "pcm_resample.h" #include "replay_gain_config.h" #include "decoder_list.h" #include "input_init.h" #include "playlist_list.h" -#include "state_file.h" #include "tag.h" -#include "dbUtils.h" #include "zeroconf.h" #include "event_pipe.h" -#include "tag_pool.h" +} + #include "mpd_error.h" #ifdef ENABLE_INOTIFY -#include "inotify_update.h" +#include "InotifyUpdate.hxx" #endif #ifdef ENABLE_SQLITE -#include "sticker.h" +#include "StickerDatabase.hxx" #endif +extern "C" { #ifdef ENABLE_ARCHIVE #include "archive_list.h" #endif +} #include <glib.h> @@ -153,31 +158,47 @@ glue_mapper_init(GError **error_r) static bool glue_db_init_and_load(void) { + const struct config_param *param = config_get_param("database"); const struct config_param *path = config_get_param(CONF_DB_FILE); + if (param != NULL && path != NULL) + g_message("Found both 'database' and '" CONF_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 " + CONF_MUSIC_DIR " - disabling database"); 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"); + struct config_param *allocated = NULL; - if (!db_init(path, &error)) + if (param == NULL && path != NULL) { + allocated = config_new_param("database", path->line); + config_add_block_param(allocated, "path", + path->value, path->line); + param = allocated; + } + + if (!DatabaseGlobalInit(param, &error)) MPD_ERROR("%s", error->message); - ret = db_load(&error); + if (allocated != NULL) + config_param_free(allocated); + + ret = DatabaseGlobalOpen(&error); if (!ret) MPD_ERROR("%s", error->message); /* run database update after daemonization? */ - return db_exists(); + return !db_is_simple() || db_exists(); } /** @@ -342,7 +363,6 @@ int mpd_main(int argc, char *argv[]) io_thread_init(); winsock_init(); idle_init(); - tag_pool_init(); config_global_init(); success = parse_cmdline(argc, argv, &options, &error); @@ -501,7 +521,7 @@ int mpd_main(int argc, char *argv[]) playlist_global_finish(); start = clock(); - db_finish(); + DatabaseGlobalDeinit(); g_debug("db_finish took %f seconds", ((float)(clock()-start))/CLOCKS_PER_SEC); @@ -518,7 +538,6 @@ int mpd_main(int argc, char *argv[]) volume_finish(); mapper_finish(); path_global_finish(); - finishPermissions(); pc_free(global_player_control); command_finish(); update_global_finish(); @@ -527,7 +546,6 @@ int mpd_main(int argc, char *argv[]) archive_plugin_deinit_all(); #endif config_global_finish(); - tag_pool_deinit(); idle_deinit(); stats_global_finish(); io_thread_deinit(); diff --git a/src/main.h b/src/Main.hxx index 2a7d75910..54916ff4b 100644 --- a/src/main.h +++ b/src/Main.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MAIN_H -#define MAIN_H +#ifndef MPD_MAIN_HXX +#define MPD_MAIN_HXX #include <glib.h> diff --git a/src/mapper.c b/src/Mapper.cxx index 7db74b1af..4d863418b 100644 --- a/src/mapper.c +++ b/src/Mapper.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,10 +22,13 @@ */ #include "config.h" -#include "mapper.h" -#include "directory.h" +#include "Mapper.hxx" +#include "Directory.hxx" #include "song.h" + +extern "C" { #include "path.h" +} #include <glib.h> @@ -178,19 +181,19 @@ map_uri_fs(const char *uri) } char * -map_directory_fs(const struct directory *directory) +map_directory_fs(const Directory *directory) { assert(music_dir_utf8 != NULL); assert(music_dir_fs != NULL); - if (directory_is_root(directory)) + if (directory->IsRoot()) return g_strdup(music_dir_fs); - return map_uri_fs(directory_get_path(directory)); + return map_uri_fs(directory->GetPath()); } char * -map_directory_child_fs(const struct directory *directory, const char *name) +map_directory_child_fs(const Directory *directory, const char *name) { assert(music_dir_utf8 != NULL); assert(music_dir_fs != NULL); @@ -219,13 +222,32 @@ map_directory_child_fs(const struct directory *directory, const char *name) return path; } +/** + * 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 char * +map_detached_song_fs(const char *uri_utf8) +{ + char *uri_fs = utf8_to_fs_charset(uri_utf8); + if (uri_fs == NULL) + return NULL; + + char *path = g_build_filename(music_dir_fs, uri_fs, NULL); + g_free(uri_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); + return song_is_detached(song) + ? map_detached_song_fs(song->uri) + : map_directory_child_fs(song->parent, song->uri); else return utf8_to_fs_charset(song->uri); } diff --git a/src/mapper.h b/src/Mapper.hxx index d6184a175..2ced38a10 100644 --- a/src/mapper.h +++ b/src/Mapper.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,15 +21,15 @@ * Maps directory and song objects to file system paths. */ -#ifndef MPD_MAPPER_H -#define MPD_MAPPER_H +#ifndef MPD_MAPPER_HXX +#define MPD_MAPPER_HXX -#include <glib.h> -#include <stdbool.h> +#include "gcc.h" +#include "gerror.h" #define PLAYLIST_FILE_SUFFIX ".m3u" -struct directory; +struct Directory; struct song; void mapper_init(const char *_music_dir, const char *_playlist_dir); @@ -39,7 +39,7 @@ void mapper_finish(void); /** * Return the absolute path of the music directory encoded in UTF-8. */ -G_GNUC_CONST +gcc_const const char * mapper_get_music_directory_utf8(void); @@ -47,18 +47,18 @@ mapper_get_music_directory_utf8(void); * Return the absolute path of the music directory encoded in the * filesystem character set. */ -G_GNUC_CONST +gcc_const const char * mapper_get_music_directory_fs(void); /** * Returns true if a music directory was configured. */ -G_GNUC_CONST +gcc_const static inline bool mapper_has_music_directory(void) { - return mapper_get_music_directory_utf8() != NULL; + return mapper_get_music_directory_utf8() != nullptr; } /** @@ -66,7 +66,7 @@ mapper_has_music_directory(void) * this function converts it to a relative path. If not, it returns * the unmodified string pointer. */ -G_GNUC_PURE +gcc_pure const char * map_to_relative_path(const char *path_utf8); @@ -75,7 +75,7 @@ map_to_relative_path(const char *path_utf8); * is basically done by converting the URI to the file system charset * and prepending the music directory. */ -G_GNUC_MALLOC +gcc_malloc char * map_uri_fs(const char *uri); @@ -83,11 +83,11 @@ map_uri_fs(const char *uri); * Determines the file system path of a directory object. * * @param directory the directory object - * @return the path in file system encoding, or NULL if mapping failed + * @return the path in file system encoding, or nullptr if mapping failed */ -G_GNUC_MALLOC +gcc_malloc char * -map_directory_fs(const struct directory *directory); +map_directory_fs(const Directory *directory); /** * Determines the file system path of a directory's child (may be a @@ -95,20 +95,20 @@ map_directory_fs(const struct directory *directory); * * @param directory the parent directory object * @param name the child's name in UTF-8 - * @return the path in file system encoding, or NULL if mapping failed + * @return the path in file system encoding, or nullptr if mapping failed */ -G_GNUC_MALLOC +gcc_malloc char * -map_directory_child_fs(const struct directory *directory, const char *name); +map_directory_child_fs(const Directory *directory, const char *name); /** * Determines the file system path of a song. This must not be a * remote song. * * @param song the song object - * @return the path in file system encoding, or NULL if mapping failed + * @return the path in file system encoding, or nullptr if mapping failed */ -G_GNUC_MALLOC +gcc_malloc char * map_song_fs(const struct song *song); @@ -117,16 +117,16 @@ map_song_fs(const struct song *song); * absolute) to a relative path in UTF-8 encoding. * * @param path_fs a path in file system encoding - * @return the relative path in UTF-8, or NULL if mapping failed + * @return the relative path in UTF-8, or nullptr if mapping failed */ -G_GNUC_MALLOC +gcc_malloc char * map_fs_to_utf8(const char *path_fs); /** * Returns the playlist directory. */ -G_GNUC_CONST +gcc_const const char * map_spl_path(void); @@ -135,9 +135,9 @@ map_spl_path(void); * path. The return value is allocated on the heap and must be freed * with g_free(). * - * @return the path in file system encoding, or NULL if mapping failed + * @return the path in file system encoding, or nullptr if mapping failed */ -G_GNUC_PURE +gcc_pure char * map_spl_utf8_to_fs(const char *name); diff --git a/src/MessageCommands.cxx b/src/MessageCommands.cxx new file mode 100644 index 000000000..c5f4d79b0 --- /dev/null +++ b/src/MessageCommands.cxx @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MessageCommands.hxx" +#include "ClientSubscribe.hxx" +#include "ClientInternal.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; + } +} + +struct channels_context { + std::set<std::string> channels; +}; + +static void +collect_channels(gpointer data, gpointer user_data) +{ + struct channels_context *context = + (struct channels_context *)user_data; + const Client *client = (const Client *)data; + + context->channels.insert(client->subscriptions.begin(), + client->subscriptions.end()); +} + +enum command_return +handle_channels(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 1); + + struct channels_context context; + + client_list_foreach(collect_channels, &context); + + for (const auto &channel : context.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; +} + +struct send_message_context { + ClientMessage msg; + + bool sent; + + template<typename T, typename U> + send_message_context(T &&_channel, U &&_message) + :msg(std::forward<T>(_channel), std::forward<U>(_message)), + sent(false) {} +}; + +static void +send_message(gpointer data, gpointer user_data) +{ + struct send_message_context *context = + (struct send_message_context *)user_data; + Client *client = (Client *)data; + + if (client_push_message(client, context->msg)) + context->sent = true; +} + +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; + } + + struct send_message_context context(argv[1], argv[2]); + + client_list_foreach(send_message, &context); + + 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; + } +} 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/OtherCommands.cxx b/src/OtherCommands.cxx new file mode 100644 index 000000000..32d51c42e --- /dev/null +++ b/src/OtherCommands.cxx @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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" + +extern "C" { +#include "uri.h" +#include "volume.h" +#include "stats.h" +} + +#include "Permission.hxx" +#include "PlaylistFile.hxx" +#include "ClientIdle.hxx" +#include "ClientFile.hxx" +#include "Client.hxx" + +extern "C" { +#include "idle.h" +} + +#ifdef ENABLE_SQLITE +#include "StickerDatabase.hxx" +#endif + +#include <assert.h> +#include <string.h> + +static void +print_spl_list(Client *client, const PlaylistFileList &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 = 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; + } + + 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_idle_wait(client, 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/OutputCommands.cxx b/src/OutputCommands.cxx new file mode 100644 index 000000000..4dd689c4e --- /dev/null +++ b/src/OutputCommands.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 "OutputCommands.hxx" +#include "OutputPrint.hxx" +#include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" + +extern "C" { +#include "output_command.h" +} + +#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/output_print.c b/src/OutputPrint.cxx index 483648ca2..ed4391547 100644 --- a/src/output_print.c +++ b/src/OutputPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,15 +23,18 @@ */ #include "config.h" -#include "output_print.h" +#include "OutputPrint.hxx" #include "output_internal.h" +#include "Client.hxx" + +extern "C" { #include "output_all.h" -#include "client.h" +} void -printAudioDevices(struct client *client) +printAudioDevices(Client *client) { - unsigned n = audio_output_count(); + const unsigned n = audio_output_count(); for (unsigned i = 0; i < n; ++i) { const struct audio_output *ao = audio_output_get(i); diff --git a/src/output_print.h b/src/OutputPrint.hxx index e02f4e9f5..78717d0af 100644 --- a/src/output_print.h +++ b/src/OutputPrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,12 +22,12 @@ * */ -#ifndef OUTPUT_PRINT_H -#define OUTPUT_PRINT_H +#ifndef MPD_OUTPUT_PRINT_HXX +#define MPD_OUTPUT_PRINT_HXX -struct client; +class Client; void -printAudioDevices(struct client *client); +printAudioDevices(Client *client); #endif diff --git a/src/output_state.c b/src/OutputState.cxx index 7bcafb36b..95aeacbca 100644 --- a/src/output_state.c +++ b/src/OutputState.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,9 +23,12 @@ */ #include "config.h" -#include "output_state.h" +#include "OutputState.hxx" #include "output_internal.h" + +extern "C" { #include "output_all.h" +} #include <glib.h> diff --git a/src/output_state.h b/src/OutputState.hxx index 320a3520a..5ab765ba8 100644 --- a/src/output_state.h +++ b/src/OutputState.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,10 +22,9 @@ * */ -#ifndef OUTPUT_STATE_H -#define OUTPUT_STATE_H +#ifndef MPD_OUTPUT_STATE_HXX +#define MPD_OUTPUT_STATE_HXX -#include <stdbool.h> #include <stdio.h> bool diff --git a/src/permission.c b/src/Permission.cxx index cd52b9c86..0b76e3364 100644 --- a/src/permission.c +++ b/src/Permission.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,16 @@ */ #include "config.h" -#include "permission.h" -#include "conf.h" +#include "Permission.hxx" #include "mpd_error.h" +extern "C" { +#include "conf.h" +} + +#include <map> +#include <string> + #include <glib.h> #include <stdbool.h> @@ -35,7 +41,7 @@ #define PERMISSION_CONTROL_STRING "control" #define PERMISSION_ADMIN_STRING "admin" -static GHashTable *permission_passwords; +static std::map<std::string, unsigned> permission_passwords; static unsigned permission_default; @@ -75,9 +81,6 @@ void initPermissions(void) unsigned permission; const struct config_param *param; - permission_passwords = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, NULL); - permission_default = PERMISSION_READ | PERMISSION_ADD | PERMISSION_CONTROL | PERMISSION_ADMIN; @@ -101,9 +104,8 @@ void initPermissions(void) permission = parsePermissions(separator + 1); - g_hash_table_replace(permission_passwords, - password, - GINT_TO_POINTER(permission)); + permission_passwords.insert(std::make_pair(password, + permission)); } while ((param = config_get_next_param(CONF_PASSWORD, param))); } @@ -115,23 +117,14 @@ void initPermissions(void) int getPermissionFromPassword(char const* password, unsigned* permission) { - bool found; - gpointer key, value; - - found = g_hash_table_lookup_extended(permission_passwords, - password, &key, &value); - if (!found) + auto i = permission_passwords.find(password); + if (i == permission_passwords.end()) return -1; - *permission = GPOINTER_TO_INT(value); + *permission = i->second; return 0; } -void finishPermissions(void) -{ - g_hash_table_destroy(permission_passwords); -} - unsigned getDefaultPermissions(void) { return permission_default; diff --git a/src/permission.h b/src/Permission.hxx index 6c3771362..4ff3850e0 100644 --- a/src/permission.h +++ b/src/Permission.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PERMISSION_H -#define MPD_PERMISSION_H +#ifndef MPD_PERMISSION_HXX +#define MPD_PERMISSION_HXX #define PERMISSION_NONE 0 #define PERMISSION_READ 1 @@ -29,8 +29,6 @@ int getPermissionFromPassword(char const* password, unsigned* permission); -void finishPermissions(void); - unsigned getDefaultPermissions(void); void initPermissions(void); diff --git a/src/PlayerCommands.cxx b/src/PlayerCommands.cxx new file mode 100644 index 000000000..e4b5c145f --- /dev/null +++ b/src/PlayerCommands.cxx @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "PlaylistPrint.hxx" +#include "UpdateGlue.hxx" +#include "ClientInternal.hxx" +#include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" + +extern "C" { +#include "player_control.h" +#include "playlist.h" +#include "volume.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; + 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); +} + +enum command_return +handle_playid(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); +} + +enum command_return +handle_stop(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist_stop(&g_playlist, client->player_control); + 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, &g_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; + + pc_set_pause(client->player_control, pause_flag); + } else + pc_pause(client->player_control); + + 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; + 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; +} + +enum command_return +handle_next(G_GNUC_UNUSED 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; +} + +enum command_return +handle_previous(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist_previous(&g_playlist, client->player_control); + 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; + + playlist_set_repeat(&g_playlist, client->player_control, 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; + + playlist_set_single(&g_playlist, client->player_control, 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; + + playlist_set_consume(&g_playlist, 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; + + playlist_set_random(&g_playlist, client->player_control, status); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_clearerror(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + pc_clear_error(client->player_control); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_seek(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); +} + +enum command_return +handle_seekid(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); +} + +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 = + playlist_seek_current(&g_playlist, client->player_control, + 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; + pc_set_cross_fade(client->player_control, 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; + pc_set_mixramp_db(client->player_control, 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; + pc_set_mixramp_delay(client->player_control, 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; + } + + 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/player_thread.c b/src/PlayerThread.cxx index 707fb27ae..ad7a008db 100644 --- a/src/player_thread.c +++ b/src/PlayerThread.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,23 +18,24 @@ */ #include "config.h" -#include "player_thread.h" +#include "PlayerThread.hxx" +#include "DecoderThread.hxx" +#include "song.h" +#include "Main.hxx" +#include "mpd_error.h" + +extern "C" { #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> @@ -123,6 +124,20 @@ struct player { * precisely. */ float elapsed_time; + + player(player_control *_pc, decoder_control *_dc) + :pc(_pc), dc(_dc), + buffering(false), + decoder_starting(false), + paused(false), + queued(true), + output_open(false), + song(NULL), + xfade(XFADE_UNKNOWN), + cross_fading(false), + cross_fade_chunks(0), + cross_fade_tag(NULL), + elapsed_time(0.0) {} }; static struct music_buffer *player_buffer; @@ -162,7 +177,7 @@ player_dc_start(struct player *player, struct music_pipe *pipe) if (pc->command == PLAYER_COMMAND_SEEK) start_ms += (unsigned)(pc->seek_where * 1000); - dc_start(dc, pc->next_song, + dc_start(dc, song_dup_detached(pc->next_song), start_ms, pc->next_song->end_ms, player_buffer, pipe); } @@ -235,16 +250,22 @@ player_wait_for_decoder(struct player *player) player->queued = false; - if (decoder_lock_has_failed(dc)) { + GError *error = dc_lock_get_error(dc); + if (error != NULL) { player_lock(pc); - pc->errored_song = dc->song; - pc->error = PLAYER_ERROR_FILE; + pc_set_error(pc, PLAYER_ERROR_DECODER, error); + + song_free(pc->next_song); pc->next_song = NULL; + player_unlock(pc); return false; } + if (player->song != NULL) + song_free(player->song); + player->song = pc->next_song; player->elapsed_time = 0.0; @@ -305,7 +326,9 @@ player_open_output(struct player *player) assert(pc->state == PLAYER_STATE_PLAY || pc->state == PLAYER_STATE_PAUSE); - if (audio_output_all_open(&player->play_audio_format, player_buffer)) { + GError *error = NULL; + if (audio_output_all_open(&player->play_audio_format, player_buffer, + &error)) { player->output_open = true; player->paused = false; @@ -315,6 +338,8 @@ player_open_output(struct player *player) return true; } else { + g_warning("%s", error->message); + player->output_open = false; /* pause: the user may resume playback as soon as an @@ -322,7 +347,7 @@ player_open_output(struct player *player) player->paused = true; player_lock(pc); - pc->error = PLAYER_ERROR_AUDIO; + pc_set_error(pc, PLAYER_ERROR_OUTPUT, error); pc->state = PLAYER_STATE_PAUSE; player_unlock(pc); @@ -347,13 +372,13 @@ player_check_decoder_startup(struct player *player) decoder_lock(dc); - if (decoder_has_failed(dc)) { + GError *error = dc_get_error(dc); + if (error != NULL) { /* the decoder failed */ decoder_unlock(dc); player_lock(pc); - pc->errored_song = dc->song; - pc->error = PLAYER_ERROR_FILE; + pc_set_error(pc, PLAYER_ERROR_DECODER, error); player_unlock(pc); return false; @@ -429,7 +454,11 @@ player_send_silence(struct player *player) chunk->length = num_frames * frame_size; memset(chunk->data, 0, chunk->length); - if (!audio_output_all_play(chunk)) { + GError *error = NULL; + if (!audio_output_all_play(chunk, &error)) { + g_warning("%s", error->message); + g_error_free(error); + music_buffer_return(player_buffer, chunk); return false; } @@ -452,7 +481,7 @@ static bool player_seek_decoder(struct player *player) const unsigned start_ms = song->start_ms; - if (decoder_current_song(dc) != song) { + if (!decoder_lock_is_current_song(dc, song)) { /* the decoder is already decoding the "next" song - stop it and start the previous song again */ @@ -478,6 +507,7 @@ static bool player_seek_decoder(struct player *player) player->pipe = dc->pipe; } + song_free(pc->next_song); pc->next_song = NULL; player->queued = false; } @@ -598,6 +628,7 @@ static void player_process_command(struct player *player) player_lock(pc); } + song_free(pc->next_song); pc->next_song = NULL; player->queued = false; player_command_finished_locked(pc); @@ -652,7 +683,8 @@ update_song_tag(struct song *song, const struct tag *new_tag) static bool play_chunk(struct player_control *pc, struct song *song, struct music_chunk *chunk, - const struct audio_format *format) + const struct audio_format *format, + GError **error_r) { assert(music_chunk_check_format(chunk, format)); @@ -670,7 +702,7 @@ play_chunk(struct player_control *pc, /* send the chunk to the audio outputs */ - if (!audio_output_all_play(chunk)) + if (!audio_output_all_play(chunk, error_r)) return false; pc->total_play_time += (double)chunk->length / @@ -785,13 +817,16 @@ play_next_chunk(struct player *player) /* play the current chunk */ + GError *error = NULL; if (!play_chunk(player->pc, player->song, chunk, - &player->play_audio_format)) { + &player->play_audio_format, &error)) { + g_warning("%s", error->message); + music_buffer_return(player_buffer, chunk); player_lock(pc); - pc->error = PLAYER_ERROR_AUDIO; + pc_set_error(pc, PLAYER_ERROR_OUTPUT, error); /* pause: the user may resume playback as soon as an audio output becomes available */ @@ -862,21 +897,7 @@ player_song_border(struct player *player) */ static void do_play(struct player_control *pc, struct decoder_control *dc) { - struct player player = { - .pc = pc, - .dc = dc, - .buffering = true, - .decoder_starting = false, - .paused = false, - .queued = true, - .output_open = false, - .song = NULL, - .xfade = XFADE_UNKNOWN, - .cross_fading = false, - .cross_fade_chunks = 0, - .cross_fade_tag = NULL, - .elapsed_time = 0.0, - }; + player player(pc, dc); player_unlock(pc); @@ -884,6 +905,8 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) 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); @@ -1049,10 +1072,14 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) if (player.cross_fade_tag != NULL) tag_free(player.cross_fade_tag); + if (player.song != NULL) + song_free(player.song); + player_lock(pc); if (player.queued) { assert(pc->next_song != NULL); + song_free(pc->next_song); pc->next_song = NULL; } @@ -1068,7 +1095,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) static gpointer player_task(gpointer arg) { - struct player_control *pc = arg; + struct player_control *pc = (struct player_control *)arg; struct decoder_control *dc = dc_new(pc->cond); decoder_thread_start(dc); @@ -1094,7 +1121,11 @@ player_task(gpointer arg) /* fall through */ case PLAYER_COMMAND_PAUSE: - pc->next_song = NULL; + if (pc->next_song != NULL) { + song_free(pc->next_song); + pc->next_song = NULL; + } + player_command_finished_locked(pc); break; @@ -1135,7 +1166,11 @@ player_task(gpointer arg) return NULL; case PLAYER_COMMAND_CANCEL: - pc->next_song = NULL; + if (pc->next_song != NULL) { + song_free(pc->next_song); + pc->next_song = NULL; + } + player_command_finished_locked(pc); break; diff --git a/src/player_thread.h b/src/PlayerThread.hxx index 7373eb438..197669cd6 100644 --- a/src/player_thread.h +++ b/src/PlayerThread.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -34,8 +34,8 @@ * #music_chunk instances around in #music_pipe objects. */ -#ifndef MPD_PLAYER_THREAD_H -#define MPD_PLAYER_THREAD_H +#ifndef MPD_PLAYER_THREAD_HXX +#define MPD_PLAYER_THREAD_HXX struct player_control; diff --git a/src/playlist_any.c b/src/PlaylistAny.cxx index 450ca5932..efa975a1f 100644 --- a/src/playlist_any.c +++ b/src/PlaylistAny.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,14 @@ */ #include "config.h" -#include "playlist_any.h" +#include "PlaylistAny.hxx" +#include "PlaylistMapper.hxx" + +extern "C" { #include "playlist_list.h" -#include "playlist_mapper.h" #include "uri.h" #include "input_stream.h" +} #include <assert.h> diff --git a/src/playlist_any.h b/src/PlaylistAny.hxx index 310913de9..fbc325420 100644 --- a/src/playlist_any.h +++ b/src/PlaylistAny.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_ANY_H -#define MPD_PLAYLIST_ANY_H +#ifndef MPD_PLAYLIST_ANY_HXX +#define MPD_PLAYLIST_ANY_HXX #include <glib.h> diff --git a/src/PlaylistCommands.cxx b/src/PlaylistCommands.cxx new file mode 100644 index 000000000..a684e4a3b --- /dev/null +++ b/src/PlaylistCommands.cxx @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlaylistCommands.hxx" +#include "DatabasePlaylist.hxx" +#include "CommandError.hxx" +#include "PlaylistPrint.hxx" +#include "PlaylistSave.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistQueue.hxx" +#include "TimePrint.hxx" +#include "ClientInternal.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "ls.hxx" + +extern "C" { +#include "playlist.h" +#include "uri.h" +} + +#include <assert.h> +#include <stdlib.h> + +static void +print_spl_list(Client *client, const PlaylistFileList &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], &g_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, + &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); +} + +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/playlist_database.c b/src/PlaylistDatabase.cxx index 6b9d87155..edc6a2815 100644 --- a/src/playlist_database.c +++ b/src/PlaylistDatabase.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,13 @@ */ #include "config.h" -#include "playlist_database.h" -#include "playlist_vector.h" -#include "text_file.h" +#include "PlaylistDatabase.hxx" +#include "PlaylistVector.hxx" +#include "TextFile.hxx" + +extern "C" { #include "string_util.h" +} #include <string.h> #include <stdlib.h> @@ -33,27 +36,25 @@ playlist_database_quark(void) } void -playlist_vector_save(FILE *fp, const struct list_head *pv) +playlist_vector_save(FILE *fp, const PlaylistVector &pv) { - struct playlist_metadata *pm; - playlist_vector_for_each(pm, pv) + for (const PlaylistInfo &pi : pv) fprintf(fp, PLAYLIST_META_BEGIN "%s\n" "mtime: %li\n" "playlist_end\n", - pm->name, (long)pm->mtime); + pi.name.c_str(), (long)pi.mtime); } bool -playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name, - GString *buffer, GError **error_r) +playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name, + GError **error_r) { - struct playlist_metadata pm = { - .mtime = 0, - }; + PlaylistInfo pm(name, 0); + char *line, *colon; const char *value; - while ((line = read_text_line(fp, buffer)) != NULL && + while ((line = file.ReadLine()) != NULL && strcmp(line, "playlist_end") != 0) { colon = strchr(line, ':'); if (colon == NULL || colon == line) { @@ -74,6 +75,6 @@ playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name, } } - playlist_vector_update_or_add(pv, name, pm.mtime); + pv.UpdateOrInsert(std::move(pm)); return true; } diff --git a/src/playlist_database.h b/src/PlaylistDatabase.hxx index 3238fa06b..f5f039267 100644 --- a/src/playlist_database.h +++ b/src/PlaylistDatabase.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,24 +17,24 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_DATABASE_H -#define MPD_PLAYLIST_DATABASE_H +#ifndef MPD_PLAYLIST_DATABASE_HXX +#define MPD_PLAYLIST_DATABASE_HXX #include "check.h" -#include <stdbool.h> #include <stdio.h> #include <glib.h> #define PLAYLIST_META_BEGIN "playlist_begin: " -struct list_head; +class PlaylistVector; +class TextFile; void -playlist_vector_save(FILE *fp, const struct list_head *pv); +playlist_vector_save(FILE *fp, const PlaylistVector &pv); bool -playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name, - GString *buffer, GError **error_r); +playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name, + GError **error_r); #endif diff --git a/src/playlist_edit.c b/src/PlaylistEdit.cxx index d10f49451..d44de0581 100644 --- a/src/playlist_edit.c +++ b/src/PlaylistEdit.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,12 +24,16 @@ */ #include "config.h" +extern "C" { #include "playlist_internal.h" #include "player_control.h" -#include "database.h" #include "uri.h" #include "song.h" #include "idle.h" +} + +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" #include <stdlib.h> @@ -45,14 +49,6 @@ 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; @@ -109,34 +105,32 @@ playlist_append_song(struct playlist *playlist, struct player_control *pc, 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) { + 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; - g_debug("add to playlist: %s", uri); + song = db->GetSong(uri, nullptr); + if (song == nullptr) + return PLAYLIST_RESULT_NO_SUCH_SONG; + } - song = song_by_uri(uri); - if (song == NULL) - return PLAYLIST_RESULT_NO_SUCH_SONG; + enum playlist_result result = + playlist_append_song(playlist, pc, song, added_id); + if (db != nullptr) + db->ReturnSong(song); - return playlist_append_song(playlist, pc, song, added_id); + return result; } enum playlist_result @@ -287,9 +281,6 @@ playlist_delete_internal(struct playlist *playlist, struct player_control *pc, /* 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 */ @@ -363,8 +354,6 @@ playlist_delete_song(struct playlist *playlist, struct player_control *pc, 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 diff --git a/src/stored_playlist.c b/src/PlaylistFile.cxx index 39ba2bac1..67ffdff4f 100644 --- a/src/stored_playlist.c +++ b/src/PlaylistFile.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,16 +18,22 @@ */ #include "config.h" -#include "stored_playlist.h" -#include "playlist_save.h" -#include "text_file.h" +#include "PlaylistFile.hxx" +#include "PlaylistSave.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" #include "song.h" -#include "mapper.h" +#include "io_error.h" +#include "Mapper.hxx" +#include "TextFile.hxx" + +extern "C" { #include "path.h" #include "uri.h" -#include "database.h" #include "idle.h" #include "conf.h" +} + #include "glib_compat.h" #include <assert.h> @@ -128,96 +134,73 @@ playlist_errno(GError **error_r) break; default: - g_set_error_literal(error_r, g_file_error_quark(), errno, - g_strerror(errno)); + set_error_errno(error_r); break; } } -static struct stored_playlist_info * -load_playlist_info(const char *parent_path_fs, const char *name_fs) +static bool +LoadPlaylistFileInfo(PlaylistFileInfo &info, + const char *parent_path_fs, const char *name_fs) { size_t name_length = strlen(name_fs); - char *path_fs, *name, *name_utf8; - int ret; - struct stat st; - struct stored_playlist_info *playlist; if (name_length < sizeof(PLAYLIST_FILE_SUFFIX) || memchr(name_fs, '\n', name_length) != NULL) - return NULL; + return false; if (!g_str_has_suffix(name_fs, PLAYLIST_FILE_SUFFIX)) - return NULL; + return false; - path_fs = g_build_filename(parent_path_fs, name_fs, NULL); - ret = stat(path_fs, &st); + char *path_fs = g_build_filename(parent_path_fs, name_fs, NULL); + struct stat st; + int ret = stat(path_fs, &st); g_free(path_fs); if (ret < 0 || !S_ISREG(st.st_mode)) - return NULL; + return false; - name = g_strndup(name_fs, - name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX)); - name_utf8 = fs_charset_to_utf8(name); + char *name = g_strndup(name_fs, + name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX)); + char *name_utf8 = fs_charset_to_utf8(name); g_free(name); if (name_utf8 == NULL) - return NULL; + return false; - playlist = g_new(struct stored_playlist_info, 1); - playlist->name = name_utf8; - playlist->mtime = st.st_mtime; - return playlist; + info.name = name_utf8; + info.mtime = st.st_mtime; + return true; } -GPtrArray * -spl_list(GError **error_r) +PlaylistFileList +ListPlaylistFiles(GError **error_r) { - const char *parent_path_fs = spl_map(error_r); - DIR *dir; - struct dirent *ent; - GPtrArray *list; - struct stored_playlist_info *playlist; + PlaylistFileList list; + const char *parent_path_fs = spl_map(error_r); if (parent_path_fs == NULL) - return NULL; + return list; - dir = opendir(parent_path_fs); + DIR *dir = opendir(parent_path_fs); if (dir == NULL) { - g_set_error_literal(error_r, g_file_error_quark(), errno, - g_strerror(errno)); - return NULL; + set_error_errno(error_r); + return list; } - list = g_ptr_array_new(); - + PlaylistFileInfo info; + struct dirent *ent; while ((ent = readdir(dir)) != NULL) { - playlist = load_playlist_info(parent_path_fs, ent->d_name); - if (playlist != NULL) - g_ptr_array_add(list, playlist); + if (LoadPlaylistFileInfo(info, parent_path_fs, ent->d_name)) + list.push_back(std::move(info)); } closedir(dir); return list; } -void -spl_list_free(GPtrArray *list) -{ - for (unsigned i = 0; i < list->len; ++i) { - struct stored_playlist_info *playlist = - g_ptr_array_index(list, i); - g_free(playlist->name); - g_free(playlist); - } - - g_ptr_array_free(list, true); -} - static bool -spl_save(GPtrArray *list, const char *utf8path, GError **error_r) +SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path, + GError **error_r) { - FILE *file; - assert(utf8path != NULL); if (spl_map(error_r) == NULL) @@ -227,48 +210,40 @@ spl_save(GPtrArray *list, const char *utf8path, GError **error_r) if (path_fs == NULL) return false; - file = fopen(path_fs, "w"); + FILE *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); - } + for (const auto &uri_utf8 : contents) + playlist_print_uri(file, uri_utf8.c_str()); fclose(file); return true; } -GPtrArray * -spl_load(const char *utf8path, GError **error_r) +PlaylistFileContents +LoadPlaylistFile(const char *utf8path, GError **error_r) { - FILE *file; - GPtrArray *list; - char *path_fs; + PlaylistFileContents contents; if (spl_map(error_r) == NULL) - return NULL; + return contents; - path_fs = spl_map_to_fs(utf8path, error_r); + char *path_fs = spl_map_to_fs(utf8path, error_r); if (path_fs == NULL) - return NULL; + return contents; - file = fopen(path_fs, "r"); - g_free(path_fs); - if (file == NULL) { + TextFile file(path_fs); + if (file.HasFailed()) { playlist_errno(error_r); - return NULL; + return contents; } - list = g_ptr_array_new(); - - GString *buffer = g_string_sized_new(1024); char *s; - while ((s = read_text_line(file, buffer)) != NULL) { + while ((s = file.ReadLine()) != NULL) { if (*s == 0 || *s == PLAYLIST_COMMENT) continue; @@ -283,80 +258,45 @@ spl_load(const char *utf8path, GError **error_r) } else s = g_strdup(s); - g_ptr_array_add(list, s); - - if (list->len >= playlist_max_length) + contents.emplace_back(s); + if (contents.size() >= playlist_max_length) break; } - fclose(file); - return list; -} - -void -spl_free(GPtrArray *list) -{ - for (unsigned i = 0; i < list->len; ++i) { - char *uri = g_ptr_array_index(list, i); - g_free(uri); - } - - g_ptr_array_free(list, true); -} - -static char * -spl_remove_index_internal(GPtrArray *list, unsigned idx) -{ - char *uri; - - assert(idx < list->len); - - uri = g_ptr_array_remove_index(list, idx); - assert(uri != NULL); - return uri; -} - -static void -spl_insert_index_internal(GPtrArray *list, unsigned idx, char *uri) -{ - assert(idx <= list->len); - - g_ptr_array_add(list, uri); - - memmove(list->pdata + idx + 1, list->pdata + idx, - (list->len - idx - 1) * sizeof(list->pdata[0])); - g_ptr_array_index(list, idx) = uri; + return contents; } bool spl_move_index(const char *utf8path, unsigned src, unsigned dest, GError **error_r) { - char *uri; - if (src == dest) /* this doesn't check whether the playlist exists, but what the hell.. */ return true; - GPtrArray *list = spl_load(utf8path, error_r); - if (list == NULL) + GError *error = nullptr; + auto contents = LoadPlaylistFile(utf8path, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); return false; + } - if (src >= list->len || dest >= list->len) { - spl_free(list); + if (src >= contents.size() || dest >= contents.size()) { g_set_error_literal(error_r, playlist_quark(), PLAYLIST_RESULT_BAD_RANGE, "Bad range"); return false; } - uri = spl_remove_index_internal(list, src); - spl_insert_index_internal(list, dest, uri); + const auto src_i = std::next(contents.begin(), src); + auto value = std::move(*src_i); + contents.erase(src_i); - bool result = spl_save(list, utf8path, error_r); + const auto dest_i = std::next(contents.begin(), dest); + contents.insert(dest_i, std::move(value)); - spl_free(list); + bool result = SavePlaylistFile(contents, utf8path, error_r); idle_add(IDLE_STORED_PLAYLIST); return result; @@ -390,14 +330,11 @@ spl_clear(const char *utf8path, GError **error_r) bool spl_delete(const char *name_utf8, GError **error_r) { - char *path_fs; - int ret; - - path_fs = spl_map_to_fs(name_utf8, error_r); + char *path_fs = spl_map_to_fs(name_utf8, error_r); if (path_fs == NULL) return false; - ret = unlink(path_fs); + int ret = unlink(path_fs); g_free(path_fs); if (ret < 0) { playlist_errno(error_r); @@ -411,25 +348,23 @@ spl_delete(const char *name_utf8, GError **error_r) bool spl_remove_index(const char *utf8path, unsigned pos, GError **error_r) { - char *uri; - - GPtrArray *list = spl_load(utf8path, error_r); - if (list == NULL) + GError *error = nullptr; + auto contents = LoadPlaylistFile(utf8path, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); return false; + } - if (pos >= list->len) { - spl_free(list); + if (pos >= contents.size()) { g_set_error_literal(error_r, playlist_quark(), PLAYLIST_RESULT_BAD_RANGE, "Bad range"); return false; } - uri = spl_remove_index_internal(list, pos); - g_free(uri); - bool result = spl_save(list, utf8path, error_r); + contents.erase(std::next(contents.begin(), pos)); - spl_free(list); + bool result = SavePlaylistFile(contents, utf8path, error_r); idle_add(IDLE_STORED_PLAYLIST); return result; @@ -439,7 +374,6 @@ 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; @@ -455,6 +389,7 @@ spl_append_song(const char *utf8path, struct song *song, GError **error_r) return false; } + struct stat st; if (fstat(fileno(file), &st) < 0) { playlist_errno(error_r); fclose(file); @@ -480,23 +415,23 @@ spl_append_song(const char *utf8path, struct song *song, GError **error_r) bool spl_append_uri(const char *url, const char *utf8file, GError **error_r) { - struct song *song; - if (uri_has_scheme(url)) { - song = song_remote_new(url); + struct song *song = song_remote_new(url); bool success = spl_append_song(utf8file, song, error_r); song_free(song); return success; } else { - song = db_get_song(url); - if (song == NULL) { - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_NO_SUCH_SONG, - "No such song"); + const Database *db = GetDatabase(error_r); + if (db == nullptr) return false; - } - return spl_append_song(utf8file, song, error_r); + 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; } } diff --git a/src/stored_playlist.h b/src/PlaylistFile.hxx index cfe49633c..6df5a46d3 100644 --- a/src/stored_playlist.h +++ b/src/PlaylistFile.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,21 +17,28 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_STORED_PLAYLIST_H -#define MPD_STORED_PLAYLIST_H +#ifndef MPD_PLAYLIST_FILE_HXX +#define MPD_PLAYLIST_FILE_HXX + +#include <list> +#include <vector> +#include <string> #include <glib.h> -#include <stdbool.h> #include <time.h> struct song; -struct stored_playlist_info { - char *name; +struct PlaylistFileInfo { + std::string name; time_t mtime; }; +typedef std::list<PlaylistFileInfo> PlaylistFileList; + +typedef std::vector<std::string> PlaylistFileContents; + extern bool playlist_saveAbsolutePaths; /** @@ -40,6 +47,8 @@ extern bool playlist_saveAbsolutePaths; void spl_global_init(void); +#ifdef __cplusplus + /** * Determines whether the specified string is a valid name for a * stored playlist. @@ -51,17 +60,11 @@ spl_valid_name(const char *name_utf8); * Returns a list of stored_playlist_info struct pointers. Returns * NULL if an error occurred. */ -GPtrArray * -spl_list(GError **error_r); - -void -spl_list_free(GPtrArray *list); - -GPtrArray * -spl_load(const char *utf8path, GError **error_r); +PlaylistFileList +ListPlaylistFiles(GError **error_r); -void -spl_free(GPtrArray *list); +PlaylistFileContents +LoadPlaylistFile(const char *utf8path, GError **error_r); bool spl_move_index(const char *utf8path, unsigned src, unsigned dest, @@ -86,3 +89,5 @@ bool spl_rename(const char *utf8from, const char *utf8to, GError **error_r); #endif + +#endif diff --git a/src/PlaylistInfo.hxx b/src/PlaylistInfo.hxx new file mode 100644 index 000000000..e4cf0f4ef --- /dev/null +++ b/src/PlaylistInfo.hxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_INFO_HXX +#define MPD_PLAYLIST_INFO_HXX + +#include "check.h" +#include "util/list.h" +#include "gcc.h" + +#include <string> + +#include <sys/time.h> + +/** + * A directory entry pointing to a playlist file. + */ +struct PlaylistInfo { + struct list_head siblings; + + /** + * 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; + } + }; + + template<typename N> + PlaylistInfo(N &&_name, time_t _mtime) + :name(std::forward<N>(_name)), mtime(_mtime) {} + + PlaylistInfo(const PlaylistInfo &other) = delete; + PlaylistInfo(PlaylistInfo &&) = default; +}; + +#endif diff --git a/src/playlist_mapper.c b/src/PlaylistMapper.cxx index 13adb80d0..01b8f7dd8 100644 --- a/src/playlist_mapper.c +++ b/src/PlaylistMapper.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,14 @@ */ #include "config.h" -#include "playlist_mapper.h" +#include "PlaylistMapper.hxx" +#include "PlaylistFile.hxx" +#include "Mapper.hxx" + +extern "C" { #include "playlist_list.h" -#include "stored_playlist.h" -#include "mapper.h" #include "uri.h" +} #include <assert.h> diff --git a/src/playlist_mapper.h b/src/PlaylistMapper.hxx index 9a7187d93..dc4e5cce8 100644 --- a/src/playlist_mapper.h +++ b/src/PlaylistMapper.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_MAPPER_H -#define MPD_PLAYLIST_MAPPER_H +#ifndef MPD_PLAYLIST_MAPPER_HXX +#define MPD_PLAYLIST_MAPPER_HXX #include <glib.h> diff --git a/src/playlist_print.c b/src/PlaylistPrint.cxx index 59c42f969..de17ac2c8 100644 --- a/src/playlist_print.c +++ b/src/PlaylistPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,22 +18,26 @@ */ #include "config.h" -#include "playlist_print.h" +#include "PlaylistPrint.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistAny.hxx" +#include "PlaylistSong.hxx" +#include "QueuePrint.hxx" +#include "SongPrint.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "Client.hxx" + +extern "C" { #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) +playlist_print_uris(Client *client, const struct playlist *playlist) { const struct queue *queue = &playlist->queue; @@ -41,7 +45,7 @@ playlist_print_uris(struct client *client, const struct playlist *playlist) } bool -playlist_print_info(struct client *client, const struct playlist *playlist, +playlist_print_info(Client *client, const struct playlist *playlist, unsigned start, unsigned end) { const struct queue *queue = &playlist->queue; @@ -59,7 +63,7 @@ playlist_print_info(struct client *client, const struct playlist *playlist, } bool -playlist_print_id(struct client *client, const struct playlist *playlist, +playlist_print_id(Client *client, const struct playlist *playlist, unsigned id) { const struct queue *queue = &playlist->queue; @@ -74,7 +78,7 @@ playlist_print_id(struct client *client, const struct playlist *playlist, } bool -playlist_print_current(struct client *client, const struct playlist *playlist) +playlist_print_current(Client *client, const struct playlist *playlist) { int current_position = playlist_get_current_song(playlist); @@ -87,21 +91,14 @@ playlist_print_current(struct client *client, const struct playlist *playlist) } void -playlist_print_find(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list) -{ - queue_find(client, &playlist->queue, list); -} - -void -playlist_print_search(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list) +playlist_print_find(Client *client, const struct playlist *playlist, + const SongFilter &filter) { - queue_search(client, &playlist->queue, list); + queue_find(client, &playlist->queue, filter); } void -playlist_print_changes_info(struct client *client, +playlist_print_changes_info(Client *client, const struct playlist *playlist, uint32_t version) { @@ -109,46 +106,51 @@ playlist_print_changes_info(struct client *client, } void -playlist_print_changes_position(struct client *client, +playlist_print_changes_position(Client *client, const struct playlist *playlist, uint32_t version) { queue_print_changes_position(client, &playlist->queue, version); } +static bool +PrintSongDetails(Client *client, const char *uri_utf8) +{ + const Database *db = GetDatabase(nullptr); + if (db == nullptr) + return false; + + song *song = db->GetSong(uri_utf8, nullptr); + if (song == nullptr) + return false; + + song_print_info(client, song); + db->ReturnSong(song); + return true; +} + bool -spl_print(struct client *client, const char *name_utf8, bool detail, +spl_print(Client *client, const char *name_utf8, bool detail, GError **error_r) { - GPtrArray *list; - - list = spl_load(name_utf8, error_r); - if (list == NULL) + GError *error = NULL; + PlaylistFileContents contents = LoadPlaylistFile(name_utf8, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); return false; + } - for (unsigned i = 0; i < list->len; ++i) { - const char *temp = g_ptr_array_index(list, i); - bool wrote = false; - - if (detail) { - struct song *song = db_get_song(temp); - if (song) { - song_print_info(client, song); - wrote = true; - } - } - - if (!wrote) { - client_printf(client, SONG_FILE "%s\n", temp); - } + for (const auto &uri_utf8 : contents) { + if (!detail || !PrintSongDetails(client, uri_utf8.c_str())) + client_printf(client, SONG_FILE "%s\n", + uri_utf8.c_str()); } - spl_free(list); return true; } static void -playlist_provider_print(struct client *client, const char *uri, +playlist_provider_print(Client *client, const char *uri, struct playlist_provider *playlist, bool detail) { struct song *song; @@ -164,15 +166,14 @@ playlist_provider_print(struct client *client, const char *uri, else song_print_uri(client, song); - if (!song_in_database(song)) - song_free(song); + song_free(song); } g_free(base_uri); } bool -playlist_file_print(struct client *client, const char *uri, bool detail) +playlist_file_print(Client *client, const char *uri, bool detail) { GMutex *mutex = g_mutex_new(); GCond *cond = g_cond_new(); diff --git a/src/playlist_print.h b/src/PlaylistPrint.hxx index d4f1911d2..588aeca85 100644 --- a/src/playlist_print.h +++ b/src/PlaylistPrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,22 +17,21 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef PLAYLIST_PRINT_H -#define PLAYLIST_PRINT_H +#ifndef MPD_PLAYLIST_PRINT_HXX +#define MPD_PLAYLIST_PRINT_HXX #include <glib.h> -#include <stdbool.h> #include <stdint.h> -struct client; struct playlist; -struct locate_item_list; +class SongFilter; +class Client; /** * Sends the whole playlist to the client, song URIs only. */ void -playlist_print_uris(struct client *client, const struct playlist *playlist); +playlist_print_uris(Client *client, const struct playlist *playlist); /** * Sends a range of the playlist to the client, including all known @@ -41,7 +40,7 @@ playlist_print_uris(struct client *client, const struct playlist *playlist); * This function however fails when the start offset is invalid. */ bool -playlist_print_info(struct client *client, const struct playlist *playlist, +playlist_print_info(Client *client, const struct playlist *playlist, unsigned start, unsigned end); /** @@ -50,7 +49,7 @@ playlist_print_info(struct client *client, const struct playlist *playlist, * @return true on suite, false if there is no such song */ bool -playlist_print_id(struct client *client, const struct playlist *playlist, +playlist_print_id(Client *client, const struct playlist *playlist, unsigned id); /** @@ -59,27 +58,20 @@ playlist_print_id(struct client *client, const struct playlist *playlist, * @return true on success, false if there is no current song */ bool -playlist_print_current(struct client *client, const struct playlist *playlist); +playlist_print_current(Client *client, const struct playlist *playlist); /** * Find songs in the playlist. */ void -playlist_print_find(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list); - -/** - * Search for songs in the playlist. - */ -void -playlist_print_search(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list); +playlist_print_find(Client *client, const struct playlist *playlist, + const SongFilter &filter); /** * Print detailed changes since the specified playlist version. */ void -playlist_print_changes_info(struct client *client, +playlist_print_changes_info(Client *client, const struct playlist *playlist, uint32_t version); @@ -87,7 +79,7 @@ playlist_print_changes_info(struct client *client, * Print changes since the specified playlist version, position only. */ void -playlist_print_changes_position(struct client *client, +playlist_print_changes_position(Client *client, const struct playlist *playlist, uint32_t version); @@ -100,7 +92,7 @@ playlist_print_changes_position(struct client *client, * @return true on success, false if the playlist does not exist */ bool -spl_print(struct client *client, const char *name_utf8, bool detail, +spl_print(Client *client, const char *name_utf8, bool detail, GError **error_r); /** @@ -112,6 +104,6 @@ spl_print(struct client *client, const char *name_utf8, bool detail, * @return true on success, false if the playlist does not exist */ bool -playlist_file_print(struct client *client, const char *uri, bool detail); +playlist_file_print(Client *client, const char *uri, bool detail); #endif diff --git a/src/playlist_queue.c b/src/PlaylistQueue.cxx index aada94984..ad9739449 100644 --- a/src/playlist_queue.c +++ b/src/PlaylistQueue.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,16 @@ */ #include "config.h" -#include "playlist_queue.h" +#include "PlaylistQueue.hxx" #include "playlist_plugin.h" -#include "playlist_any.h" -#include "playlist_song.h" +#include "PlaylistAny.hxx" +#include "PlaylistSong.hxx" + +extern "C" { #include "playlist.h" #include "song.h" #include "input_stream.h" +} enum playlist_result playlist_load_into_queue(const char *uri, struct playlist_provider *source, @@ -41,8 +44,7 @@ playlist_load_into_queue(const char *uri, struct playlist_provider *source, ++i) { if (i < start_index) { /* skip songs before the start index */ - if (!song_in_database(song)) - song_free(song); + song_free(song); continue; } @@ -51,9 +53,8 @@ playlist_load_into_queue(const char *uri, struct playlist_provider *source, continue; result = playlist_append_song(dest, pc, song, NULL); + song_free(song); if (result != PLAYLIST_RESULT_SUCCESS) { - if (!song_in_database(song)) - song_free(song); g_free(base_uri); return result; } diff --git a/src/playlist_queue.h b/src/PlaylistQueue.hxx index 24a851aab..cda77c818 100644 --- a/src/playlist_queue.h +++ b/src/PlaylistQueue.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,13 +21,11 @@ * \brief Glue between playlist plugin and the play queue */ -#ifndef MPD_PLAYLIST_QUEUE_H -#define MPD_PLAYLIST_QUEUE_H +#ifndef MPD_PLAYLIST_QUEUE_HXX +#define MPD_PLAYLIST_QUEUE_HXX #include "playlist_error.h" -#include <stdbool.h> - struct playlist_provider; struct playlist; struct player_control; diff --git a/src/playlist_save.c b/src/PlaylistSave.cxx index 334159e0d..722ae9c83 100644 --- a/src/playlist_save.c +++ b/src/PlaylistSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,16 +18,18 @@ */ #include "config.h" -#include "playlist_save.h" -#include "playlist.h" -#include "stored_playlist.h" -#include "queue.h" +#include "PlaylistSave.hxx" +#include "PlaylistFile.hxx" #include "song.h" -#include "mapper.h" +#include "Mapper.hxx" + +extern "C" { +#include "playlist.h" #include "path.h" #include "uri.h" -#include "database.h" #include "idle.h" +} + #include "glib_compat.h" #include <glib.h> @@ -117,34 +119,35 @@ playlist_load_spl(struct playlist *playlist, struct player_control *pc, unsigned start_index, unsigned end_index, GError **error_r) { - GPtrArray *list; - - list = spl_load(name_utf8, error_r); - if (list == NULL) + GError *error = NULL; + PlaylistFileContents contents = LoadPlaylistFile(name_utf8, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); return false; + } - if (list->len < end_index) - end_index = list->len; + if (end_index > contents.size()) + end_index = contents.size(); for (unsigned i = start_index; i < end_index; ++i) { - const char *temp = g_ptr_array_index(list, i); - if ((playlist_append_uri(playlist, pc, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { + const auto &uri_utf8 = contents[i]; + + if ((playlist_append_uri(playlist, pc, uri_utf8.c_str(), + nullptr)) != PLAYLIST_RESULT_SUCCESS) { /* for windows compatibility, convert slashes */ - char *temp2 = g_strdup(temp); + char *temp2 = g_strdup(uri_utf8.c_str()); char *p = temp2; while (*p) { if (*p == '\\') *p = '/'; p++; } - if ((playlist_append_uri(playlist, pc, temp2, - NULL)) != PLAYLIST_RESULT_SUCCESS) { + if ((playlist_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/PlaylistSave.hxx index a6c31a9a6..ff5f0c494 100644 --- a/src/playlist_save.h +++ b/src/PlaylistSave.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,7 +22,6 @@ #include "playlist_error.h" -#include <stdbool.h> #include <stdio.h> struct song; diff --git a/src/playlist_song.c b/src/PlaylistSong.cxx index a3d9ab4d9..4bfcc5969 100644 --- a/src/playlist_song.c +++ b/src/PlaylistSong.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,18 @@ */ #include "config.h" -#include "playlist_song.h" -#include "database.h" -#include "mapper.h" +#include "PlaylistSong.hxx" +#include "Mapper.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" +#include "ls.hxx" + +extern "C" { #include "song.h" #include "uri.h" #include "path.h" -#include "ls.h" #include "tag.h" +} #include <assert.h> #include <string.h> @@ -86,9 +90,7 @@ apply_song_metadata(struct song *dest, const struct song *src) (e.g. last track on a CUE file); fix it up here */ tmp->tag->time = dest->tag->time - src->start_ms / 1000; - if (!song_in_database(dest)) - song_free(dest); - + song_free(dest); return tmp; } @@ -104,10 +106,17 @@ playlist_check_load_song(const struct song *song, const char *uri, bool secure) if (dest == NULL) return NULL; } else { - dest = db_get_song(uri); - if (dest == NULL) + const Database *db = GetDatabase(nullptr); + if (db == nullptr) + return nullptr; + + struct song *tmp = db->GetSong(uri, nullptr); + if (tmp == NULL) /* not found in database */ return NULL; + + dest = song_dup_detached(tmp); + db->ReturnSong(tmp); } return apply_song_metadata(dest, song); diff --git a/src/playlist_song.h b/src/PlaylistSong.hxx index ea8786912..117ee1338 100644 --- a/src/playlist_song.h +++ b/src/PlaylistSong.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PLAYLIST_SONG_H -#define MPD_PLAYLIST_SONG_H - -#include <stdbool.h> +#ifndef MPD_PLAYLIST_SONG_HXX +#define MPD_PLAYLIST_SONG_HXX /** * Verifies the song, returns NULL if it is unsafe. Translate the diff --git a/src/playlist_state.c b/src/PlaylistState.cxx index 4aa2c2c92..1bb18250f 100644 --- a/src/playlist_state.c +++ b/src/PlaylistState.cxx @@ -23,13 +23,15 @@ */ #include "config.h" -#include "playlist_state.h" +#include "PlaylistState.hxx" +#include "QueueSave.hxx" +#include "TextFile.hxx" + +extern "C" { #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> @@ -102,18 +104,18 @@ playlist_state_save(FILE *fp, const struct playlist *playlist, } static void -playlist_state_load(FILE *fp, GString *buffer, struct playlist *playlist) +playlist_state_load(TextFile &file, struct playlist *playlist) { - const char *line = read_text_line(fp, buffer); + const char *line = file.ReadLine(); if (line == NULL) { g_warning("No playlist in state file"); return; } while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { - queue_load_song(fp, buffer, line, &playlist->queue); + queue_load_song(file, line, &playlist->queue); - line = read_text_line(fp, buffer); + line = file.ReadLine(); if (line == NULL) { g_warning("'" PLAYLIST_STATE_FILE_PLAYLIST_END "' not found in state file"); @@ -125,7 +127,7 @@ playlist_state_load(FILE *fp, GString *buffer, struct playlist *playlist) } bool -playlist_state_restore(const char *line, FILE *fp, GString *buffer, +playlist_state_restore(const char *line, TextFile &file, struct playlist *playlist, struct player_control *pc) { int current = -1; @@ -143,7 +145,7 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0) state = PLAYER_STATE_PAUSE; - while ((line = read_text_line(fp, buffer)) != NULL) { + while ((line = file.ReadLine()) != NULL) { if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_TIME)) { seek_time = atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); @@ -187,7 +189,7 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, (PLAYLIST_STATE_FILE_CURRENT)])); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { - playlist_state_load(fp, buffer, playlist); + playlist_state_load(file, playlist); } } diff --git a/src/playlist_state.h b/src/PlaylistState.hxx index f67d01d2c..572f6fb4a 100644 --- a/src/playlist_state.h +++ b/src/PlaylistState.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,22 +22,22 @@ * */ -#ifndef PLAYLIST_STATE_H -#define PLAYLIST_STATE_H +#ifndef MPD_PLAYLIST_STATE_HXX +#define MPD_PLAYLIST_STATE_HXX #include <glib.h> -#include <stdbool.h> #include <stdio.h> struct playlist; struct player_control; +class TextFile; void playlist_state_save(FILE *fp, const struct playlist *playlist, struct player_control *pc); bool -playlist_state_restore(const char *line, FILE *fp, GString *buffer, +playlist_state_restore(const char *line, TextFile &file, struct playlist *playlist, struct player_control *pc); /** diff --git a/src/PlaylistVector.cxx b/src/PlaylistVector.cxx new file mode 100644 index 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..0a4cd95b3 --- /dev/null +++ b/src/PlaylistVector.hxx @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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>::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/QueueCommands.cxx b/src/QueueCommands.cxx new file mode 100644 index 000000000..8a13075a5 --- /dev/null +++ b/src/QueueCommands.cxx @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "PlaylistPrint.hxx" +#include "ClientFile.hxx" +#include "ClientInternal.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "ls.hxx" + +extern "C" { +#include "playlist.h" +#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 = 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 findAddIn(client->player_control, uri, nullptr, &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 = 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; +} + +enum command_return +handle_delete(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); +} + +enum command_return +handle_deleteid(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); +} + +enum command_return +handle_playlist(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist_print_uris(client, &g_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 = 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; +} + +enum command_return +handle_clear(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist_clear(&g_playlist, client->player_control); + 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, &g_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, &g_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, &g_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, &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_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, &g_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 = + 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; +} + +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 = + 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; +} + +enum command_return +handle_move(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); +} + +enum command_return +handle_moveid(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); +} + +enum command_return +handle_swap(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); +} + +enum command_return +handle_swapid(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); +} 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/queue_print.c b/src/QueuePrint.cxx index d149e8b6f..bd49f92ec 100644 --- a/src/queue_print.c +++ b/src/QueuePrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,16 @@ */ #include "config.h" -#include "queue_print.h" +#include "QueuePrint.hxx" +#include "SongFilter.hxx" +#include "SongPrint.hxx" +#include "Mapper.hxx" +#include "Client.hxx" + +extern "C" { #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 @@ -35,7 +38,7 @@ * @param end the index of the last song (excluding) */ static void -queue_print_song_info(struct client *client, const struct queue *queue, +queue_print_song_info(Client *client, const struct queue *queue, unsigned position) { song_print_info(client, queue_get(queue, position)); @@ -48,7 +51,7 @@ queue_print_song_info(struct client *client, const struct queue *queue, } void -queue_print_info(struct client *client, const struct queue *queue, +queue_print_info(Client *client, const struct queue *queue, unsigned start, unsigned end) { assert(start <= end); @@ -59,7 +62,7 @@ queue_print_info(struct client *client, const struct queue *queue, } void -queue_print_uris(struct client *client, const struct queue *queue, +queue_print_uris(Client *client, const struct queue *queue, unsigned start, unsigned end) { assert(start <= end); @@ -72,7 +75,7 @@ queue_print_uris(struct client *client, const struct queue *queue, } void -queue_print_changes_info(struct client *client, const struct queue *queue, +queue_print_changes_info(Client *client, const struct queue *queue, uint32_t version) { for (unsigned i = 0; i < queue_length(queue); i++) { @@ -82,7 +85,7 @@ queue_print_changes_info(struct client *client, const struct queue *queue, } void -queue_print_changes_position(struct client *client, const struct queue *queue, +queue_print_changes_position(Client *client, const struct queue *queue, uint32_t version) { for (unsigned i = 0; i < queue_length(queue); i++) @@ -92,31 +95,13 @@ queue_print_changes_position(struct client *client, const struct queue *queue, } 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) +queue_find(Client *client, const struct queue *queue, + const SongFilter &filter) { for (unsigned i = 0; i < queue_length(queue); i++) { const struct song *song = queue_get(queue, i); - if (locate_song_match(song, criteria)) + if (filter.Match(*song)) queue_print_song_info(client, queue, i); } } diff --git a/src/queue_print.h b/src/QueuePrint.hxx index 371e20416..6b3a29fb6 100644 --- a/src/queue_print.h +++ b/src/QueuePrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,37 +22,33 @@ * client. */ -#ifndef QUEUE_PRINT_H -#define QUEUE_PRINT_H +#ifndef MPD_QUEUE_PRINT_HXX +#define MPD_QUEUE_PRINT_HXX #include <stdint.h> -struct client; struct queue; -struct locate_item_list; +class SongFilter; +class Client; void -queue_print_info(struct client *client, const struct queue *queue, +queue_print_info(Client *client, const struct queue *queue, unsigned start, unsigned end); void -queue_print_uris(struct client *client, const struct queue *queue, +queue_print_uris(Client *client, const struct queue *queue, unsigned start, unsigned end); void -queue_print_changes_info(struct client *client, const struct queue *queue, +queue_print_changes_info(Client *client, const struct queue *queue, uint32_t version); void -queue_print_changes_position(struct client *client, const struct queue *queue, +queue_print_changes_position(Client *client, const struct queue *queue, uint32_t version); void -queue_search(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria); - -void -queue_find(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria); +queue_find(Client *client, const struct queue *queue, + const SongFilter &filter); #endif diff --git a/src/queue_save.c b/src/QueueSave.cxx index 16852d3c1..6ba48f336 100644 --- a/src/queue_save.c +++ b/src/QueueSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,17 @@ */ #include "config.h" -#include "queue_save.h" -#include "queue.h" +#include "QueueSave.hxx" #include "song.h" +#include "SongSave.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" +#include "TextFile.hxx" + +extern "C" { +#include "queue.h" #include "uri.h" -#include "database.h" -#include "song_save.h" -#include "text_file.h" +} #include <stdlib.h> @@ -66,20 +70,9 @@ queue_save(FILE *fp, const struct queue *queue) } } -static struct song * -get_song(const char *uri) -{ - return uri_has_scheme(uri) - ? song_remote_new(uri) - : db_get_song(uri); -} - void -queue_load_song(FILE *fp, GString *buffer, const char *line, - struct queue *queue) +queue_load_song(TextFile &file, const char *line, queue *queue) { - struct song *song; - if (queue_is_full(queue)) return; @@ -87,18 +80,21 @@ queue_load_song(FILE *fp, GString *buffer, const char *line, if (g_str_has_prefix(line, PRIO_LABEL)) { priority = strtoul(line + sizeof(PRIO_LABEL) - 1, NULL, 10); - line = read_text_line(fp, buffer); + line = file.ReadLine(); if (line == NULL) return; } + const Database *db = nullptr; + struct song *song; + if (g_str_has_prefix(line, SONG_BEGIN)) { const char *uri = line + sizeof(SONG_BEGIN) - 1; if (!uri_has_scheme(uri) && !g_path_is_absolute(uri)) return; GError *error = NULL; - song = song_load(fp, NULL, uri, buffer, &error); + song = song_load(file, NULL, uri, &error); if (song == NULL) { g_warning("%s", error->message); g_error_free(error); @@ -112,12 +108,23 @@ queue_load_song(FILE *fp, GString *buffer, const char *line, return; } - line = endptr + 1; + const char *uri = endptr + 1; - song = get_song(line); - if (song == NULL) - return; + if (uri_has_scheme(uri)) { + song = song_remote_new(uri); + } else { + db = GetDatabase(nullptr); + if (db == nullptr) + return; + + song = db->GetSong(uri, nullptr); + if (song == nullptr) + return; + } } queue_append(queue, song, priority); + + if (db != nullptr) + db->ReturnSong(song); } diff --git a/src/queue_save.h b/src/QueueSave.hxx index 5526d615d..dc4a764a9 100644 --- a/src/queue_save.h +++ b/src/QueueSave.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,13 +22,14 @@ * back into memory. */ -#ifndef QUEUE_SAVE_H -#define QUEUE_SAVE_H +#ifndef MPD_QUEUE_SAVE_HXX +#define MPD_QUEUE_SAVE_HXX #include <glib.h> #include <stdio.h> struct queue; +class TextFile; void queue_save(FILE *fp, const struct queue *queue); @@ -37,7 +38,6 @@ queue_save(FILE *fp, const struct queue *queue); * Loads one song from the state file and appends it to the queue. */ void -queue_load_song(FILE *fp, GString *buffer, const char *line, - struct queue *queue); +queue_load_song(TextFile &file, const char *line, queue *queue); #endif diff --git a/src/song.c b/src/Song.cxx index f5cc7c35a..c845384ae 100644 --- a/src/song.c +++ b/src/Song.cxx @@ -19,26 +19,31 @@ #include "config.h" #include "song.h" -#include "uri.h" -#include "directory.h" +#include "Directory.hxx" + +extern "C" { #include "tag.h" +} #include <glib.h> #include <assert.h> +Directory detached_root; + static struct song * -song_alloc(const char *uri, struct directory *parent) +song_alloc(const char *uri, Directory *parent) { size_t uri_length; - struct song *song; assert(uri); uri_length = strlen(uri); assert(uri_length); - song = g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1); - song->tag = NULL; + struct song *song = (struct song *) + g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1); + + song->tag = nullptr; memcpy(song->uri, uri, uri_length + 1); song->parent = parent; song->mtime = 0; @@ -50,13 +55,13 @@ song_alloc(const char *uri, struct directory *parent) struct song * song_remote_new(const char *uri) { - return song_alloc(uri, NULL); + return song_alloc(uri, nullptr); } struct song * -song_file_new(const char *path, struct directory *parent) +song_file_new(const char *path, Directory *parent) { - assert((parent == NULL) == (*path == '/')); + assert((parent == nullptr) == (*path == '/')); return song_alloc(path, parent); } @@ -73,6 +78,35 @@ song_replace_uri(struct song *old_song, const char *uri) return new_song; } +struct song * +song_detached_new(const char *uri) +{ + assert(uri != nullptr); + + return song_alloc(uri, &detached_root); +} + +struct song * +song_dup_detached(const struct song *src) +{ + assert(src != nullptr); + + struct song *song; + if (song_in_database(src)) { + char *uri = song_get_uri(src); + song = song_detached_new(uri); + g_free(uri); + } else + song = song_alloc(src->uri, nullptr); + + song->tag = tag_dup(src->tag); + song->mtime = src->mtime; + song->start_ms = src->start_ms; + song->end_ms = src->end_ms; + + return song; +} + void song_free(struct song *song) { @@ -81,17 +115,57 @@ song_free(struct song *song) g_free(song); } +gcc_pure +static inline bool +directory_equals(const Directory &a, const Directory &b) +{ + return strcmp(a.path, b.path) == 0; +} + +gcc_pure +static inline bool +directory_is_same(const Directory *a, const Directory *b) +{ + return a == b || + (a != nullptr && b != nullptr && + directory_equals(*a, *b)); + +} + +bool +song_equals(const struct song *a, const struct song *b) +{ + assert(a != nullptr); + assert(b != nullptr); + + if (a->parent != nullptr && b->parent != nullptr && + !directory_equals(*a->parent, *b->parent) && + (a->parent == &detached_root || b->parent == &detached_root)) { + /* must compare the full URI if one of the objects is + "detached" */ + char *au = song_get_uri(a); + char *bu = song_get_uri(b); + const bool result = strcmp(au, bu) == 0; + g_free(bu); + g_free(au); + return result; + } + + return directory_is_same(a->parent, b->parent) && + strcmp(a->uri, b->uri) == 0; +} + char * song_get_uri(const struct song *song) { - assert(song != NULL); + assert(song != nullptr); assert(*song->uri); - if (!song_in_database(song) || directory_is_root(song->parent)) + if (!song_in_database(song) || song->parent->IsRoot()) return g_strdup(song->uri); else - return g_strconcat(directory_get_path(song->parent), - "/", song->uri, NULL); + return g_strconcat(song->parent->GetPath(), + "/", song->uri, nullptr); } double @@ -100,7 +174,7 @@ song_get_duration(const struct song *song) if (song->end_ms > 0) return (song->end_ms - song->start_ms) / 1000.0; - if (song->tag == NULL) + if (song->tag == nullptr) return 0; return song->tag->time - song->start_ms / 1000.0; diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx new file mode 100644 index 000000000..6803b453e --- /dev/null +++ b/src/SongFilter.cxx @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "path.h" +#include "song.h" + +extern "C" { +#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/song_print.c b/src/SongPrint.cxx index fb608a8b2..c56e54d8b 100644 --- a/src/song_print.c +++ b/src/SongPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,20 +18,24 @@ */ #include "config.h" -#include "song_print.h" +#include "SongPrint.hxx" #include "song.h" -#include "directory.h" -#include "tag_print.h" -#include "client.h" +#include "Directory.hxx" +#include "TimePrint.hxx" +#include "TagPrint.hxx" +#include "Mapper.hxx" +#include "Client.hxx" + +extern "C" { #include "uri.h" -#include "mapper.h" +} void -song_print_uri(struct client *client, struct song *song) +song_print_uri(Client *client, struct song *song) { - if (song_in_database(song) && !directory_is_root(song->parent)) { + if (song_in_database(song) && !song->parent->IsRoot()) { client_printf(client, "%s%s/%s\n", SONG_FILE, - directory_get_path(song->parent), song->uri); + song->parent->GetPath(), song->uri); } else { char *allocated; const char *uri; @@ -48,7 +52,7 @@ song_print_uri(struct client *client, struct song *song) } void -song_print_info(struct client *client, struct song *song) +song_print_info(Client *client, struct song *song) { song_print_uri(client, song); @@ -63,32 +67,8 @@ song_print_info(struct client *client, struct song *song) song->start_ms / 1000, song->start_ms % 1000); - if (song->mtime > 0) { -#ifndef G_OS_WIN32 - struct tm tm; -#endif - const struct tm *tm2; - -#ifdef G_OS_WIN32 - tm2 = gmtime(&song->mtime); -#else - tm2 = gmtime_r(&song->mtime, &tm); -#endif - - if (tm2 != NULL) { - char timestamp[32]; - - strftime(timestamp, sizeof(timestamp), -#ifdef G_OS_WIN32 - "%Y-%m-%dT%H:%M:%SZ", -#else - "%FT%TZ", -#endif - tm2); - client_printf(client, "Last-Modified: %s\n", - timestamp); - } - } + if (song->mtime > 0) + time_print(client, "Last-Modified", song->mtime); if (song->tag) tag_print(client, song->tag); diff --git a/src/SongPrint.hxx b/src/SongPrint.hxx new file mode 100644 index 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/song_save.c b/src/SongSave.cxx index 4fcb46e22..a32b7fecc 100644 --- a/src/song_save.c +++ b/src/SongSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,16 @@ */ #include "config.h" -#include "song_save.h" +#include "SongSave.hxx" #include "song.h" -#include "tag_save.h" -#include "directory.h" +#include "TagSave.hxx" +#include "Directory.hxx" +#include "TextFile.hxx" + +extern "C" { #include "tag.h" -#include "text_file.h" #include "string_util.h" +} #include <glib.h> @@ -60,8 +63,8 @@ song_save(FILE *fp, const struct song *song) } struct song * -song_load(FILE *fp, struct directory *parent, const char *uri, - GString *buffer, GError **error_r) +song_load(TextFile &file, Directory *parent, const char *uri, + GError **error_r) { struct song *song = parent != NULL ? song_file_new(uri, parent) @@ -70,7 +73,7 @@ song_load(FILE *fp, struct directory *parent, const char *uri, enum tag_type type; const char *value; - while ((line = read_text_line(fp, buffer)) != NULL && + while ((line = file.ReadLine()) != NULL && strcmp(line, SONG_END) != 0) { colon = strchr(line, ':'); if (colon == NULL || colon == line) { diff --git a/src/song_save.h b/src/SongSave.hxx index f6ecbbfeb..3214c545f 100644 --- a/src/song_save.h +++ b/src/SongSave.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_SONG_SAVE_H -#define MPD_SONG_SAVE_H +#ifndef MPD_SONG_SAVE_HXX +#define MPD_SONG_SAVE_HXX #include <glib.h> @@ -27,7 +27,8 @@ #define SONG_BEGIN "song_begin: " struct song; -struct directory; +struct Directory; +class TextFile; void song_save(FILE *fp, const struct song *song); @@ -41,7 +42,7 @@ song_save(FILE *fp, const struct song *song); * @return true on success, false on error */ struct song * -song_load(FILE *fp, struct directory *parent, const char *uri, - GString *buffer, GError **error_r); +song_load(TextFile &file, Directory *parent, const char *uri, + GError **error_r); #endif diff --git a/src/song_sticker.c b/src/SongSticker.cxx index 78025906e..40af50b31 100644 --- a/src/song_sticker.c +++ b/src/SongSticker.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,10 @@ */ #include "config.h" -#include "song_sticker.h" +#include "SongSticker.hxx" +#include "StickerDatabase.hxx" #include "song.h" -#include "directory.h" -#include "sticker.h" +#include "Directory.hxx" #include <glib.h> @@ -109,7 +109,7 @@ sticker_song_get(const struct song *song) } struct sticker_song_find_data { - struct directory *directory; + Directory *directory; const char *base_uri; size_t base_uri_length; @@ -121,34 +121,31 @@ struct sticker_song_find_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; + struct sticker_song_find_data *data = + (struct sticker_song_find_data *)user_data; if (memcmp(uri, data->base_uri, data->base_uri_length) != 0) /* should not happen, ignore silently */ return; - song = directory_lookup_song(data->directory, - uri + data->base_uri_length); + song *song = data->directory->LookupSong(uri + data->base_uri_length); if (song != NULL) data->func(song, value, data->user_data); } bool -sticker_song_find(struct directory *directory, const char *name, +sticker_song_find(Directory *directory, const char *name, void (*func)(struct song *song, const char *value, gpointer user_data), gpointer user_data) { - struct sticker_song_find_data data = { - .directory = directory, - .func = func, - .user_data = user_data, - }; - char *allocated; - bool success; + struct sticker_song_find_data data; + data.directory = directory; + data.func = func; + data.user_data = user_data; - data.base_uri = directory_get_path(directory); + char *allocated; + data.base_uri = directory->GetPath(); if (*data.base_uri != 0) /* append slash to base_uri */ data.base_uri = allocated = @@ -159,8 +156,8 @@ sticker_song_find(struct directory *directory, const char *name, data.base_uri_length = strlen(data.base_uri); - success = sticker_find("song", data.base_uri, name, - sticker_song_find_cb, &data); + bool success = sticker_find("song", data.base_uri, name, + sticker_song_find_cb, &data); g_free(allocated); return success; diff --git a/src/song_sticker.h b/src/SongSticker.hxx index 20ae68ce9..fd25af4c8 100644 --- a/src/song_sticker.h +++ b/src/SongSticker.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,14 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef SONG_STICKER_H -#define SONG_STICKER_H +#ifndef MPD_SONG_STICKER_HXX +#define MPD_SONG_STICKER_HXX -#include <stdbool.h> #include <glib.h> struct song; -struct directory; +struct Directory; struct sticker; /** @@ -76,7 +75,7 @@ sticker_song_get(const struct song *song); * failure */ bool -sticker_song_find(struct directory *directory, const char *name, +sticker_song_find(Directory *directory, const char *name, void (*func)(struct song *song, const char *value, gpointer user_data), gpointer user_data); diff --git a/src/song_update.c b/src/SongUpdate.cxx index 37f502a20..2d22f27c4 100644 --- a/src/song_update.c +++ b/src/SongUpdate.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,16 @@ */ #include "config.h" /* must be first for large file support */ + +extern "C" { #include "song.h" #include "uri.h" -#include "directory.h" -#include "mapper.h" +} + +#include "Directory.hxx" +#include "Mapper.hxx" + +extern "C" { #include "decoder_list.h" #include "decoder_plugin.h" #include "tag_ape.h" @@ -29,6 +35,7 @@ #include "tag.h" #include "tag_handler.h" #include "input_stream.h" +} #include <glib.h> @@ -38,7 +45,7 @@ #include <stdio.h> struct song * -song_file_load(const char *path, struct directory *parent) +song_file_load(const char *path, Directory *parent) { struct song *song; bool ret; @@ -187,7 +194,7 @@ song_file_update_inarchive(struct song *song) if (suffix == NULL) return false; - plugin = decoder_plugin_from_suffix(suffix, false); + plugin = decoder_plugin_from_suffix(suffix, nullptr); if (plugin == NULL) return false; diff --git a/src/state_file.c b/src/StateFile.cxx index de0e70538..cd344eb0e 100644 --- a/src/state_file.c +++ b/src/StateFile.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,12 +18,15 @@ */ #include "config.h" -#include "state_file.h" -#include "output_state.h" +#include "StateFile.hxx" +#include "OutputState.hxx" #include "playlist.h" -#include "playlist_state.h" +#include "PlaylistState.hxx" +#include "TextFile.hxx" + +extern "C" { #include "volume.h" -#include "text_file.h" +} #include <glib.h> #include <assert.h> @@ -75,39 +78,31 @@ state_file_write(struct player_control *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)) { + TextFile file(state_file_path); + if (file.HasFailed()) { 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) { + while ((line = file.ReadLine()) != NULL) { success = read_sw_volume_state(line) || audio_output_state_read(line) || - playlist_state_restore(line, fp, buffer, - &g_playlist, pc); + playlist_state_restore(line, file, &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); } /** @@ -117,7 +112,7 @@ state_file_read(struct player_control *pc) static gboolean timer_save_state_file(gpointer data) { - struct player_control *pc = data; + struct player_control *pc = (struct player_control *)data; if (prev_volume_version == sw_volume_state_get_hash() && prev_output_version == audio_output_state_get_version() && diff --git a/src/state_file.h b/src/StateFile.hxx index 4c4f881cc..77629aba9 100644 --- a/src/state_file.h +++ b/src/StateFile.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_STATE_FILE_H -#define MPD_STATE_FILE_H +#ifndef MPD_STATE_FILE_HXX +#define MPD_STATE_FILE_HXX struct player_control; diff --git a/src/Stats.cxx b/src/Stats.cxx new file mode 100644 index 000000000..f96bcf177 --- /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 "player_control.h" +} + +#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)(pc_get_total_play_time(client->player_control) + 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/sticker.c b/src/StickerDatabase.cxx index 346a827a5..3d6e212d1 100644 --- a/src/sticker.c +++ b/src/StickerDatabase.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,14 @@ */ #include "config.h" -#include "sticker.h" +#include "StickerDatabase.hxx" + +extern "C" { #include "idle.h" +} + +#include <string> +#include <map> #include <glib.h> #include <sqlite3.h> @@ -33,7 +39,7 @@ #endif struct sticker { - GHashTable *table; + std::map<std::string, std::string> table; }; enum sticker_sql { @@ -47,19 +53,19 @@ enum sticker_sql { }; static const char *const sticker_sql[] = { - [STICKER_SQL_GET] = + //[STICKER_SQL_GET] = "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_LIST] = + //[STICKER_SQL_LIST] = "SELECT name,value FROM sticker WHERE type=? AND uri=?", - [STICKER_SQL_UPDATE] = + //[STICKER_SQL_UPDATE] = "UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_INSERT] = + //[STICKER_SQL_INSERT] = "INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)", - [STICKER_SQL_DELETE] = + //[STICKER_SQL_DELETE] = "DELETE FROM sticker WHERE type=? AND uri=?", - [STICKER_SQL_DELETE_VALUE] = + //[STICKER_SQL_DELETE_VALUE] = "DELETE FROM sticker WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_FIND] = + //[STICKER_SQL_FIND] = "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?", }; @@ -226,13 +232,12 @@ sticker_load_value(const char *type, const char *uri, const char *name) } static bool -sticker_list_values(GHashTable *hash, const char *type, const char *uri) +sticker_list_values(std::map<std::string, std::string> &table, + const char *type, const char *uri) { sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_LIST]; int ret; - char *name, *value; - assert(hash != NULL); assert(type != NULL); assert(uri != NULL); assert(sticker_enabled()); @@ -256,10 +261,13 @@ sticker_list_values(GHashTable *hash, const char *type, const char *uri) do { ret = sqlite3_step(stmt); switch (ret) { + const char *name, *value; + case SQLITE_ROW: - name = g_strdup((const char*)sqlite3_column_text(stmt, 0)); - value = g_strdup((const char*)sqlite3_column_text(stmt, 1)); - g_hash_table_insert(hash, name, value); + name = (const char*)sqlite3_column_text(stmt, 0); + value = (const char*)sqlite3_column_text(stmt, 1); + + table.insert(std::make_pair(name, value)); break; case SQLITE_DONE: break; @@ -518,44 +526,20 @@ sticker_delete_value(const char *type, const char *uri, const char *name) return ret > 0; } -static struct sticker * -sticker_new(void) -{ - struct sticker *sticker = g_new(struct sticker, 1); - - sticker->table = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_free); - return sticker; -} - void sticker_free(struct sticker *sticker) { - assert(sticker != NULL); - assert(sticker->table != NULL); - - g_hash_table_destroy(sticker->table); - g_free(sticker); + delete sticker; } const char * sticker_get_value(const struct sticker *sticker, const char *name) { - return g_hash_table_lookup(sticker->table, name); -} + auto i = sticker->table.find(name); + if (i == sticker->table.end()) + return nullptr; -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); + return i->second.c_str(); } void @@ -564,33 +548,23 @@ sticker_foreach(const struct sticker *sticker, gpointer user_data), gpointer user_data) { - struct sticker_foreach_data data = { - .func = func, - .user_data = user_data, - }; - - g_hash_table_foreach(sticker->table, sticker_foreach_func, &data); + for (const auto &i : sticker->table) + func(i.first.c_str(), i.second.c_str(), user_data); } struct sticker * sticker_load(const char *type, const char *uri) { - struct sticker *sticker = sticker_new(); - bool success; + sticker s; - success = sticker_list_values(sticker->table, type, uri); - if (!success) { - sticker_free(sticker); + if (!sticker_list_values(s.table, type, uri)) return NULL; - } - if (g_hash_table_size(sticker->table) == 0) { + if (s.table.empty()) /* don't return empty sticker objects */ - sticker_free(sticker); return NULL; - } - return sticker; + return new sticker(std::move(s)); } bool diff --git a/src/sticker.h b/src/StickerDatabase.hxx index 5545206a5..90ff9b066 100644 --- a/src/sticker.h +++ b/src/StickerDatabase.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -39,12 +39,10 @@ * */ -#ifndef STICKER_H -#define STICKER_H +#ifndef MPD_STICKER_DATABASE_HXX +#define MPD_STICKER_DATABASE_HXX -#include <glib.h> - -#include <stdbool.h> +#include "gerror.h" struct sticker; @@ -127,8 +125,8 @@ sticker_get_value(const struct sticker *sticker, const char *name); void sticker_foreach(const struct sticker *sticker, void (*func)(const char *name, const char *value, - gpointer user_data), - gpointer user_data); + void *user_data), + void *user_data); /** * Loads the sticker for the specified resource. @@ -153,7 +151,7 @@ sticker_load(const char *type, const char *uri); bool sticker_find(const char *type, const char *base_uri, const char *name, void (*func)(const char *uri, const char *value, - gpointer user_data), - gpointer user_data); + void *user_data), + void *user_data); #endif diff --git a/src/sticker_print.c b/src/StickerPrint.cxx index 65e79513c..1708ee4f0 100644 --- a/src/sticker_print.c +++ b/src/StickerPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,27 +18,27 @@ */ #include "config.h" -#include "sticker_print.h" -#include "sticker.h" -#include "client.h" +#include "StickerPrint.hxx" +#include "StickerDatabase.hxx" +#include "Client.hxx" void -sticker_print_value(struct client *client, +sticker_print_value(Client *client, const char *name, const char *value) { client_printf(client, "sticker: %s=%s\n", name, value); } static void -print_sticker_cb(const char *name, const char *value, gpointer data) +print_sticker_cb(const char *name, const char *value, void *data) { - struct client *client = data; + Client *client = (Client *)data; sticker_print_value(client, name, value); } void -sticker_print(struct client *client, const struct sticker *sticker) +sticker_print(Client *client, const struct sticker *sticker) { sticker_foreach(sticker, print_sticker_cb, client); } diff --git a/src/sticker_print.h b/src/StickerPrint.hxx index 7398c8083..27225a494 100644 --- a/src/sticker_print.h +++ b/src/StickerPrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,23 +17,22 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_STICKER_PRINT_H -#define MPD_STICKER_PRINT_H +#ifndef MPD_STICKER_PRINT_HXX +#define MPD_STICKER_PRINT_HXX struct sticker; -struct client; +class Client; /** * Sends one sticker value to the client. */ void -sticker_print_value(struct client *client, - const char *name, const char *value); +sticker_print_value(Client *client, const char *name, const char *value); /** * Sends all sticker values to the client. */ void -sticker_print(struct client *client, const struct sticker *sticker); +sticker_print(Client *client, const struct sticker *sticker); #endif diff --git a/src/tag_print.c b/src/TagPrint.cxx index 9a46b247a..5a5dbbeff 100644 --- a/src/tag_print.c +++ b/src/TagPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,13 @@ */ #include "config.h" -#include "tag_print.h" +#include "TagPrint.hxx" #include "tag.h" #include "tag_internal.h" -#include "client.h" #include "song.h" +#include "Client.hxx" -void tag_print_types(struct client *client) +void tag_print_types(Client *client) { int i; @@ -35,7 +35,7 @@ void tag_print_types(struct client *client) } } -void tag_print(struct client *client, const struct tag *tag) +void tag_print(Client *client, const struct tag *tag) { if (tag->time >= 0) client_printf(client, SONG_TIME "%i\n", tag->time); diff --git a/src/tag_print.h b/src/TagPrint.hxx index b9eeeaecf..99e1d0850 100644 --- a/src/tag_print.h +++ b/src/TagPrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,14 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_TAG_PRINT_H -#define MPD_TAG_PRINT_H +#ifndef MPD_TAG_PRINT_HXX +#define MPD_TAG_PRINT_HXX struct tag; -struct client; +class Client; -void tag_print_types(struct client *client); +void tag_print_types(Client *client); -void tag_print(struct client *client, const struct tag *tag); +void tag_print(Client *client, const struct tag *tag); #endif diff --git a/src/tag_save.c b/src/TagSave.cxx index 2fdaef56c..639dc2d97 100644 --- a/src/tag_save.c +++ b/src/TagSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "tag_save.h" +#include "TagSave.hxx" #include "tag.h" #include "tag_internal.h" #include "song.h" diff --git a/src/tag_save.h b/src/TagSave.hxx index 9f6a580c8..a7ccfa613 100644 --- a/src/tag_save.h +++ b/src/TagSave.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_TAG_SAVE_H -#define MPD_TAG_SAVE_H +#ifndef MPD_TAG_SAVE_HXX +#define MPD_TAG_SAVE_HXX #include <stdio.h> diff --git a/src/text_file.c b/src/TextFile.cxx index 3674e5ce2..4ad59ee4a 100644 --- a/src/text_file.c +++ b/src/TextFile.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,27 +18,20 @@ */ #include "config.h" -#include "text_file.h" +#include "TextFile.hxx" #include <assert.h> #include <string.h> char * -read_text_line(FILE *file, GString *buffer) +TextFile::ReadLine() { - enum { - max_length = 512 * 1024, - step = 1024, - }; - gsize length = 0, i; char *p; assert(file != NULL); assert(buffer != NULL); - - if (buffer->allocated_len < step) - g_string_set_size(buffer, step); + assert(buffer->allocated_len >= step); while (buffer->len < max_length) { p = fgets(buffer->str + length, diff --git a/src/TextFile.hxx b/src/TextFile.hxx new file mode 100644 index 000000000..25f6ea7a8 --- /dev/null +++ b/src/TextFile.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_TEXT_FILE_HXX +#define MPD_TEXT_FILE_HXX + +#include "gcc.h" + +#include <glib.h> + +#include <stdio.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 char *path_fs) + :file(fopen(path_fs, "r")), 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/update_archive.c b/src/UpdateArchive.cxx index 3fb2bc18c..72f7aaf19 100644 --- a/src/update_archive.c +++ b/src/UpdateArchive.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,29 +18,32 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_archive.h" -#include "update_internal.h" -#include "db_lock.h" -#include "directory.h" +#include "UpdateArchive.hxx" +#include "UpdateInternal.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" #include "song.h" -#include "mapper.h" +#include "Mapper.hxx" + +extern "C" { #include "archive_list.h" #include "archive_plugin.h" +} #include <glib.h> #include <string.h> static void -update_archive_tree(struct directory *directory, char *name) +update_archive_tree(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); + Directory *subdir = + directory->MakeChild(name); subdir->device = DEVICE_INARCHIVE; db_unlock(); //create directories first @@ -53,18 +56,18 @@ update_archive_tree(struct directory *directory, char *name) //add file db_lock(); - struct song *song = directory_get_song(directory, name); + struct song *song = directory->FindSong(name); db_unlock(); if (song == NULL) { song = song_file_load(name, directory); if (song != NULL) { db_lock(); - directory_add_song(directory, song); + directory->AddSong(song); db_unlock(); modified = true; g_message("added %s/%s", - directory_get_path(directory), name); + directory->GetPath(), name); } } } @@ -79,12 +82,12 @@ update_archive_tree(struct directory *directory, char *name) * @param plugin the archive plugin which fits this archive type */ static void -update_archive_file2(struct directory *parent, const char *name, +update_archive_file2(Directory *parent, const char *name, const struct stat *st, const struct archive_plugin *plugin) { db_lock(); - struct directory *directory = directory_get_child(parent, name); + Directory *directory = parent->FindChild(name); db_unlock(); if (directory != NULL && directory->mtime == st->st_mtime && @@ -111,7 +114,7 @@ update_archive_file2(struct directory *parent, const char *name, if (directory == NULL) { g_debug("creating archive directory: %s", name); db_lock(); - directory = directory_new_child(parent, name); + directory = parent->CreateChild(name); /* mark this directory as archive (we use device for this) */ directory->device = DEVICE_INARCHIVE; @@ -133,7 +136,7 @@ update_archive_file2(struct directory *parent, const char *name, } bool -update_archive_file(struct directory *directory, +update_archive_file(Directory *directory, const char *name, const char *suffix, const struct stat *st) { diff --git a/src/update_archive.h b/src/UpdateArchive.hxx index 838697dd9..aa9882ae0 100644 --- a/src/update_archive.h +++ b/src/UpdateArchive.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,21 +17,21 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_ARCHIVE_H -#define MPD_UPDATE_ARCHIVE_H +#ifndef MPD_UPDATE_ARCHIVE_HXX +#define MPD_UPDATE_ARCHIVE_HXX #include "check.h" +#include "gcc.h" -#include <stdbool.h> #include <sys/stat.h> -struct directory; +struct Directory; struct archive_plugin; #ifdef ENABLE_ARCHIVE bool -update_archive_file(struct directory *directory, +update_archive_file(Directory *directory, const char *name, const char *suffix, const struct stat *st); @@ -40,10 +40,10 @@ update_archive_file(struct directory *directory, #include <glib.h> static inline bool -update_archive_file(G_GNUC_UNUSED struct directory *directory, - G_GNUC_UNUSED const char *name, - G_GNUC_UNUSED const char *suffix, - G_GNUC_UNUSED const struct stat *st) +update_archive_file(gcc_unused Directory *directory, + gcc_unused const char *name, + gcc_unused const char *suffix, + gcc_unused const struct stat *st) { return false; } diff --git a/src/update_container.c b/src/UpdateContainer.cxx index bda95dabe..07bf0cee1 100644 --- a/src/update_container.c +++ b/src/UpdateContainer.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,16 +18,19 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_container.h" -#include "update_internal.h" -#include "update_db.h" -#include "db_lock.h" -#include "directory.h" +#include "UpdateContainer.hxx" +#include "UpdateInternal.hxx" +#include "UpdateDatabase.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" #include "song.h" -#include "mapper.h" #include "decoder_plugin.h" +#include "Mapper.hxx" + +extern "C" { #include "tag.h" #include "tag_handler.h" +} #include <glib.h> @@ -39,11 +42,11 @@ * * The caller must lock the database. */ -static struct directory * -make_directory_if_modified(struct directory *parent, const char *name, +static Directory * +make_directory_if_modified(Directory *parent, const char *name, const struct stat *st) { - struct directory *directory = directory_get_child(parent, name); + Directory *directory = parent->FindChild(name); // directory exists already if (directory != NULL) { @@ -57,13 +60,13 @@ make_directory_if_modified(struct directory *parent, const char *name, modified = true; } - directory = directory_make_child(parent, name); + directory = parent->MakeChild(name); directory->mtime = st->st_mtime; return directory; } bool -update_container_file(struct directory *directory, +update_container_file(Directory *directory, const char *name, const struct stat *st, const struct decoder_plugin *plugin) @@ -72,8 +75,7 @@ update_container_file(struct directory *directory, return false; db_lock(); - struct directory *contdir = - make_directory_if_modified(directory, name, st); + Directory *contdir = make_directory_if_modified(directory, name, st); if (contdir == NULL) { /* not modified */ db_unlock(); @@ -101,13 +103,12 @@ update_container_file(struct directory *directory, g_free(child_path_fs); db_lock(); - directory_add_song(contdir, song); + contdir->AddSong(song); db_unlock(); modified = true; - g_message("added %s/%s", - directory_get_path(directory), vtrack); + g_message("added %s/%s", directory->GetPath(), vtrack); g_free(vtrack); } diff --git a/src/update_container.h b/src/UpdateContainer.hxx index 7f42c80ca..0078730d6 100644 --- a/src/update_container.h +++ b/src/UpdateContainer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,19 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_CONTAINER_H -#define MPD_UPDATE_CONTAINER_H +#ifndef MPD_UPDATE_CONTAINER_HXX +#define MPD_UPDATE_CONTAINER_HXX #include "check.h" -#include <stdbool.h> #include <sys/stat.h> -struct directory; +struct Directory; struct decoder_plugin; bool -update_container_file(struct directory *directory, +update_container_file(Directory *directory, const char *name, const struct stat *st, const struct decoder_plugin *plugin); diff --git a/src/update_db.c b/src/UpdateDatabase.cxx index 8982a53e2..984fb1be8 100644 --- a/src/update_db.c +++ b/src/UpdateDatabase.cxx @@ -18,23 +18,23 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_db.h" -#include "update_remove.h" -#include "directory.h" +#include "UpdateDatabase.hxx" +#include "UpdateRemove.hxx" +#include "PlaylistVector.hxx" +#include "Directory.hxx" #include "song.h" -#include "playlist_vector.h" -#include "db_lock.h" +#include "DatabaseLock.hxx" #include <glib.h> #include <assert.h> void -delete_song(struct directory *dir, struct song *del) +delete_song(Directory *dir, struct song *del) { assert(del->parent == dir); /* first, prevent traversers in main task from getting this */ - directory_remove_song(dir, del); + dir->RemoveSong(del); db_unlock(); /* temporary unlock, because update_remove_song() blocks */ @@ -54,9 +54,9 @@ delete_song(struct directory *dir, struct song *del) * Caller must lock the #db_mutex. */ static void -clear_directory(struct directory *directory) +clear_directory(Directory *directory) { - struct directory *child, *n; + Directory *child, *n; directory_for_each_child_safe(child, n, directory) delete_directory(child); @@ -68,35 +68,35 @@ clear_directory(struct directory *directory) } void -delete_directory(struct directory *directory) +delete_directory(Directory *directory) { assert(directory->parent != NULL); clear_directory(directory); - directory_delete(directory); + directory->Delete(); } bool -delete_name_in(struct directory *parent, const char *name) +delete_name_in(Directory *parent, const char *name) { bool modified = false; db_lock(); - struct directory *directory = directory_get_child(parent, name); + Directory *directory = parent->FindChild(name); if (directory != NULL) { delete_directory(directory); modified = true; } - struct song *song = directory_get_song(parent, name); + struct song *song = parent->FindSong(name); if (song != NULL) { delete_song(parent, song); modified = true; } - playlist_vector_remove(&parent->playlists, name); + parent->playlists.erase(name); db_unlock(); diff --git a/src/update_db.h b/src/UpdateDatabase.hxx index 0a9e46b05..7b55ce95d 100644 --- a/src/update_db.h +++ b/src/UpdateDatabase.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,21 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_DB_H -#define MPD_UPDATE_DB_H +#ifndef MPD_UPDATE_DATABASE_HXX +#define MPD_UPDATE_DATABASE_HXX #include "check.h" -#include <stdbool.h> - -struct directory; +struct Directory; struct song; /** * Caller must lock the #db_mutex. */ void -delete_song(struct directory *parent, struct song *song); +delete_song(Directory *parent, struct song *song); /** * Recursively free a directory and all its contents. @@ -39,7 +37,7 @@ delete_song(struct directory *parent, struct song *song); * Caller must lock the #db_mutex. */ void -delete_directory(struct directory *directory); +delete_directory(Directory *directory); /** * Caller must NOT lock the #db_mutex. @@ -47,6 +45,6 @@ delete_directory(struct directory *directory); * @return true if the database was modified */ bool -delete_name_in(struct directory *parent, const char *name); +delete_name_in(Directory *parent, const char *name); #endif diff --git a/src/update.c b/src/UpdateGlue.cxx index 12eec40c4..f5664893b 100644 --- a/src/update.c +++ b/src/UpdateGlue.cxx @@ -18,19 +18,21 @@ */ #include "config.h" -#include "update.h" -#include "update_queue.h" -#include "update_walk.h" -#include "update_remove.h" -#include "update.h" -#include "database.h" -#include "mapper.h" +#include "UpdateGlue.hxx" +#include "UpdateQueue.hxx" +#include "UpdateWalk.hxx" +#include "UpdateRemove.hxx" +#include "Mapper.hxx" +#include "DatabaseSimple.hxx" + +extern "C" { #include "playlist.h" #include "event_pipe.h" -#include "update.h" #include "idle.h" #include "stats.h" -#include "main.h" +} + +#include "Main.hxx" #include "mpd_error.h" #include <glib.h> @@ -65,7 +67,7 @@ isUpdatingDB(void) static void * update_task(void *_path) { - const char *path = _path; + const char *path = (const char *)_path; if (path != NULL && *path != 0) g_debug("starting: %s", path); @@ -118,7 +120,7 @@ update_enqueue(const char *path, bool _discard) { assert(g_thread_self() == main_task); - if (!mapper_has_music_directory()) + if (!db_is_simple() || !mapper_has_music_directory()) return 0; if (progress != UPDATE_PROGRESS_IDLE) { diff --git a/src/update.h b/src/UpdateGlue.hxx index 3d586b694..9d546a2a3 100644 --- a/src/update.h +++ b/src/UpdateGlue.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_H -#define MPD_UPDATE_H - -#include <stdbool.h> +#ifndef MPD_UPDATE_GLUE_HXX +#define MPD_UPDATE_GLUE_HXX void update_global_init(void); diff --git a/src/update_io.c b/src/UpdateIO.cxx index c6a540a0f..2aee56514 100644 --- a/src/update_io.c +++ b/src/UpdateIO.cxx @@ -18,9 +18,9 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_io.h" -#include "mapper.h" -#include "directory.h" +#include "UpdateIO.hxx" +#include "Directory.hxx" +#include "Mapper.hxx" #include "glib_compat.h" #include <glib.h> @@ -29,7 +29,7 @@ #include <unistd.h> int -stat_directory(const struct directory *directory, struct stat *st) +stat_directory(const Directory *directory, struct stat *st) { char *path_fs = map_directory_fs(directory); if (path_fs == NULL) @@ -44,7 +44,7 @@ stat_directory(const struct directory *directory, struct stat *st) } int -stat_directory_child(const struct directory *parent, const char *name, +stat_directory_child(const Directory *parent, const char *name, struct stat *st) { char *path_fs = map_directory_child_fs(parent, name); @@ -60,7 +60,7 @@ stat_directory_child(const struct directory *parent, const char *name, } bool -directory_exists(const struct directory *directory) +directory_exists(const Directory *directory) { char *path_fs = map_directory_fs(directory); if (path_fs == NULL) @@ -79,7 +79,7 @@ directory_exists(const struct directory *directory) } bool -directory_child_is_regular(const struct directory *directory, +directory_child_is_regular(const Directory *directory, const char *name_utf8) { char *path_fs = map_directory_child_fs(directory, name_utf8); @@ -94,7 +94,7 @@ directory_child_is_regular(const struct directory *directory, } bool -directory_child_access(const struct directory *directory, +directory_child_access(const Directory *directory, const char *name, int mode) { #ifdef WIN32 diff --git a/src/update_io.h b/src/UpdateIO.hxx index 6ff1ccebd..ee47b2682 100644 --- a/src/update_io.h +++ b/src/UpdateIO.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,35 +17,34 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_IO_H -#define MPD_UPDATE_IO_H +#ifndef MPD_UPDATE_IO_HXX +#define MPD_UPDATE_IO_HXX #include "check.h" -#include <stdbool.h> #include <sys/stat.h> -struct directory; +struct Directory; int -stat_directory(const struct directory *directory, struct stat *st); +stat_directory(const Directory *directory, struct stat *st); int -stat_directory_child(const struct directory *parent, const char *name, +stat_directory_child(const Directory *parent, const char *name, struct stat *st); bool -directory_exists(const struct directory *directory); +directory_exists(const Directory *directory); bool -directory_child_is_regular(const struct directory *directory, +directory_child_is_regular(const Directory *directory, const char *name_utf8); /** * Checks if the given permissions on the mapped file are given. */ bool -directory_child_access(const struct directory *directory, +directory_child_access(const Directory *directory, const char *name, int mode); #endif diff --git a/src/update_internal.h b/src/UpdateInternal.hxx index 76ab7bed3..50443c086 100644 --- a/src/update_internal.h +++ b/src/UpdateInternal.hxx @@ -22,8 +22,6 @@ #include "check.h" -#include <stdbool.h> - extern bool walk_discard; extern bool modified; diff --git a/src/update_queue.c b/src/UpdateQueue.cxx index 2150fa4e4..85eb22358 100644 --- a/src/update_queue.c +++ b/src/UpdateQueue.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "update_queue.h" +#include "UpdateQueue.hxx" #include <glib.h> diff --git a/src/update_queue.h b/src/UpdateQueue.hxx index 84ba474b0..7de06964f 100644 --- a/src/update_queue.h +++ b/src/UpdateQueue.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_QUEUE_H -#define MPD_UPDATE_QUEUE_H +#ifndef MPD_UPDATE_QUEUE_HXX +#define MPD_UPDATE_QUEUE_HXX #include "check.h" -#include <stdbool.h> - unsigned update_queue_push(const char *path, bool discard, unsigned base); diff --git a/src/update_remove.c b/src/UpdateRemove.cxx index f443f5eb2..c88eec42a 100644 --- a/src/update_remove.c +++ b/src/UpdateRemove.cxx @@ -18,15 +18,19 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_remove.h" +#include "UpdateRemove.hxx" + +extern "C" { #include "event_pipe.h" -#include "song.h" #include "playlist.h" -#include "main.h" +} + +#include "song.h" +#include "Main.hxx" #ifdef ENABLE_SQLITE -#include "sticker.h" -#include "song_sticker.h" +#include "StickerDatabase.hxx" +#include "SongSticker.hxx" #endif #include <glib.h> diff --git a/src/update_remove.h b/src/UpdateRemove.hxx index 479ef83ff..dc15622fc 100644 --- a/src/update_remove.h +++ b/src/UpdateRemove.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_REMOVE_H -#define MPD_UPDATE_REMOVE_H +#ifndef MPD_UPDATE_REMOVE_HXX +#define MPD_UPDATE_REMOVE_HXX #include "check.h" diff --git a/src/update_song.c b/src/UpdateSong.cxx index 1126ad115..7994ea754 100644 --- a/src/update_song.c +++ b/src/UpdateSong.cxx @@ -18,33 +18,36 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_song.h" -#include "update_internal.h" -#include "update_io.h" -#include "update_db.h" -#include "update_container.h" -#include "db_lock.h" -#include "directory.h" +#include "UpdateSong.hxx" +#include "UpdateInternal.hxx" +#include "UpdateIO.hxx" +#include "UpdateDatabase.hxx" +#include "UpdateContainer.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" #include "song.h" -#include "decoder_list.h" #include "decoder_plugin.h" +extern "C" { +#include "decoder_list.h" +} + #include <glib.h> #include <unistd.h> static void -update_song_file2(struct directory *directory, +update_song_file2(Directory *directory, const char *name, const struct stat *st, const struct decoder_plugin *plugin) { db_lock(); - struct song *song = directory_get_song(directory, name); + struct song *song = directory->FindSong(name); db_unlock(); if (!directory_child_access(directory, name, R_OK)) { g_warning("no read permissions on %s/%s", - directory_get_path(directory), name); + directory->GetPath(), name); if (song != NULL) { db_lock(); delete_song(directory, song); @@ -67,28 +70,27 @@ update_song_file2(struct directory *directory, } if (song == NULL) { - g_debug("reading %s/%s", - directory_get_path(directory), name); + g_debug("reading %s/%s", directory->GetPath(), name); song = song_file_load(name, directory); if (song == NULL) { g_debug("ignoring unrecognized file %s/%s", - directory_get_path(directory), name); + directory->GetPath(), name); return; } db_lock(); - directory_add_song(directory, song); + directory->AddSong(song); db_unlock(); modified = true; g_message("added %s/%s", - directory_get_path(directory), name); + directory->GetPath(), name); } else if (st->st_mtime != song->mtime || walk_discard) { g_message("updating %s/%s", - directory_get_path(directory), name); + directory->GetPath(), name); if (!song_file_update(song)) { g_debug("deleting unrecognized file %s/%s", - directory_get_path(directory), name); + directory->GetPath(), name); db_lock(); delete_song(directory, song); db_unlock(); @@ -99,12 +101,12 @@ update_song_file2(struct directory *directory, } bool -update_song_file(struct directory *directory, +update_song_file(Directory *directory, const char *name, const char *suffix, const struct stat *st) { const struct decoder_plugin *plugin = - decoder_plugin_from_suffix(suffix, false); + decoder_plugin_from_suffix(suffix, nullptr); if (plugin == NULL) return false; diff --git a/src/update_song.h b/src/UpdateSong.hxx index cff63f576..60a532e3a 100644 --- a/src/update_song.h +++ b/src/UpdateSong.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,18 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_SONG_H -#define MPD_UPDATE_SONG_H +#ifndef MPD_UPDATE_SONG_HXX +#define MPD_UPDATE_SONG_HXX #include "check.h" -#include <stdbool.h> #include <sys/stat.h> -struct directory; +struct Directory; bool -update_song_file(struct directory *directory, +update_song_file(Directory *directory, const char *name, const char *suffix, const struct stat *st); diff --git a/src/update_walk.c b/src/UpdateWalk.cxx index 8554e8f3c..30e2ca989 100644 --- a/src/update_walk.c +++ b/src/UpdateWalk.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,22 +18,25 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_walk.h" -#include "update_io.h" -#include "update_db.h" -#include "update_song.h" -#include "update_archive.h" -#include "database.h" -#include "db_lock.h" -#include "exclude.h" -#include "directory.h" +#include "UpdateWalk.hxx" +#include "UpdateIO.hxx" +#include "UpdateDatabase.hxx" +#include "UpdateSong.hxx" +#include "UpdateArchive.hxx" +#include "DatabaseLock.hxx" +#include "DatabaseSimple.hxx" +#include "Directory.hxx" #include "song.h" -#include "playlist_vector.h" +#include "PlaylistVector.hxx" +#include "Mapper.hxx" +#include "ExcludeList.hxx" + +extern "C" { #include "uri.h" -#include "mapper.h" #include "path.h" #include "playlist_list.h" #include "conf.h" +} #include <glib.h> @@ -84,7 +87,7 @@ update_walk_global_finish(void) } static void -directory_set_stat(struct directory *dir, const struct stat *st) +directory_set_stat(Directory *dir, const struct stat *st) { dir->inode = st->st_ino; dir->device = st->st_dev; @@ -92,16 +95,16 @@ directory_set_stat(struct directory *dir, const struct stat *st) } static void -remove_excluded_from_directory(struct directory *directory, - GSList *exclude_list) +remove_excluded_from_directory(Directory *directory, + const ExcludeList &exclude_list) { db_lock(); - struct directory *child, *n; + Directory *child, *n; directory_for_each_child_safe(child, n, directory) { - char *name_fs = utf8_to_fs_charset(directory_get_name(child)); + char *name_fs = utf8_to_fs_charset(child->GetName()); - if (exclude_list_check(exclude_list, name_fs)) { + if (exclude_list.Check(name_fs)) { delete_directory(child); modified = true; } @@ -114,7 +117,7 @@ remove_excluded_from_directory(struct directory *directory, assert(song->parent == directory); char *name_fs = utf8_to_fs_charset(song->uri); - if (exclude_list_check(exclude_list, name_fs)) { + if (exclude_list.Check(name_fs)) { delete_song(directory, song); modified = true; } @@ -126,9 +129,9 @@ remove_excluded_from_directory(struct directory *directory, } static void -purge_deleted_from_directory(struct directory *directory) +purge_deleted_from_directory(Directory *directory) { - struct directory *child, *n; + Directory *child, *n; directory_for_each_child_safe(child, n, directory) { if (directory_exists(child)) continue; @@ -156,19 +159,21 @@ purge_deleted_from_directory(struct directory *directory) g_free(path); } - struct playlist_metadata *pm, *np; - directory_for_each_playlist_safe(pm, np, directory) { - if (!directory_child_is_regular(directory, pm->name)) { + for (auto i = directory->playlists.begin(), + end = directory->playlists.end(); + i != end;) { + if (!directory_child_is_regular(directory, i->name.c_str())) { db_lock(); - playlist_vector_remove(&directory->playlists, pm->name); + i = directory->playlists.erase(i); db_unlock(); - } + } else + ++i; } } #ifndef G_OS_WIN32 static int -update_directory_stat(struct directory *directory) +update_directory_stat(Directory *directory) { struct stat st; if (stat_directory(directory, &st) < 0) @@ -180,7 +185,7 @@ update_directory_stat(struct directory *directory) #endif static int -find_inode_ancestor(struct directory *parent, ino_t inode, dev_t device) +find_inode_ancestor(Directory *parent, ino_t inode, dev_t device) { #ifndef G_OS_WIN32 while (parent) { @@ -204,23 +209,24 @@ find_inode_ancestor(struct directory *parent, ino_t inode, dev_t device) } static bool -update_playlist_file2(struct directory *directory, +update_playlist_file2(Directory *directory, const char *name, const char *suffix, const struct stat *st) { if (!playlist_suffix_supported(suffix)) return false; + PlaylistInfo pi(name, st->st_mtime); + db_lock(); - if (playlist_vector_update_or_add(&directory->playlists, name, - st->st_mtime)) + if (directory->playlists.UpdateOrInsert(std::move(pi))) modified = true; db_unlock(); return true; } static bool -update_regular_file(struct directory *directory, +update_regular_file(Directory *directory, const char *name, const struct stat *st) { const char *suffix = uri_get_suffix(name); @@ -233,10 +239,10 @@ update_regular_file(struct directory *directory, } static bool -update_directory(struct directory *directory, const struct stat *st); +update_directory(Directory *directory, const struct stat *st); static void -update_directory_child(struct directory *directory, +update_directory_child(Directory *directory, const char *name, const struct stat *st) { assert(strchr(name, '/') == NULL); @@ -248,8 +254,7 @@ update_directory_child(struct directory *directory, return; db_lock(); - struct directory *subdir = - directory_make_child(directory, name); + Directory *subdir = directory->MakeChild(name); db_unlock(); assert(directory == subdir->parent); @@ -275,7 +280,7 @@ static bool skip_path(const char *path) G_GNUC_PURE static bool -skip_symlink(const struct directory *directory, const char *utf8_name) +skip_symlink(const Directory *directory, const char *utf8_name) { #ifndef WIN32 char *path_fs = map_directory_child_fs(directory, utf8_name); @@ -348,7 +353,7 @@ skip_symlink(const struct directory *directory, const char *utf8_name) } static bool -update_directory(struct directory *directory, const struct stat *st) +update_directory(Directory *directory, const struct stat *st) { assert(S_ISDIR(st->st_mode)); @@ -367,12 +372,13 @@ update_directory(struct directory *directory, const struct stat *st) } char *exclude_path_fs = g_build_filename(path_fs, ".mpdignore", NULL); - GSList *exclude_list = exclude_list_load(exclude_path_fs); + ExcludeList exclude_list; + exclude_list.LoadFile(exclude_path_fs); g_free(exclude_path_fs); g_free(path_fs); - if (exclude_list != NULL) + if (!exclude_list.IsEmpty()) remove_excluded_from_directory(directory, exclude_list); purge_deleted_from_directory(directory); @@ -382,8 +388,7 @@ update_directory(struct directory *directory, const struct stat *st) char *utf8; struct stat st2; - if (skip_path(ent->d_name) || - exclude_list_check(exclude_list, ent->d_name)) + if (skip_path(ent->d_name) || exclude_list.Check(ent->d_name)) continue; utf8 = fs_charset_to_utf8(ent->d_name); @@ -404,8 +409,6 @@ update_directory(struct directory *directory, const struct stat *st) g_free(utf8); } - exclude_list_free(exclude_list); - closedir(dir); directory->mtime = st->st_mtime; @@ -413,11 +416,11 @@ update_directory(struct directory *directory, const struct stat *st) return true; } -static struct directory * -directory_make_child_checked(struct directory *parent, const char *name_utf8) +static Directory * +directory_make_child_checked(Directory *parent, const char *name_utf8) { db_lock(); - struct directory *directory = directory_get_child(parent, name_utf8); + Directory *directory = parent->FindChild(name_utf8); db_unlock(); if (directory != NULL) @@ -434,21 +437,21 @@ directory_make_child_checked(struct directory *parent, const char *name_utf8) /* if we're adding directory paths, make sure to delete filenames with potentially the same name */ db_lock(); - struct song *conflicting = directory_get_song(parent, name_utf8); + struct song *conflicting = parent->FindSong(name_utf8); if (conflicting) delete_song(parent, conflicting); - directory = directory_new_child(parent, name_utf8); + directory = parent->CreateChild(name_utf8); db_unlock(); directory_set_stat(directory, &st); return directory; } -static struct directory * +static Directory * directory_make_uri_parent_checked(const char *uri) { - struct directory *directory = db_get_root(); + Directory *directory = db_get_root(); char *duplicated = g_strdup(uri); char *name_utf8 = duplicated, *slash; @@ -472,7 +475,7 @@ directory_make_uri_parent_checked(const char *uri) static void update_uri(const char *uri) { - struct directory *parent = directory_make_uri_parent_checked(uri); + Directory *parent = directory_make_uri_parent_checked(uri); if (parent == NULL) return; @@ -497,7 +500,7 @@ update_walk(const char *path, bool discard) if (path != NULL && !isRootDirectory(path)) { update_uri(path); } else { - struct directory *directory = db_get_root(); + Directory *directory = db_get_root(); struct stat st; if (stat_directory(directory, &st) == 0) diff --git a/src/update_walk.h b/src/UpdateWalk.hxx index ab1e41fb9..62c0d0a8e 100644 --- a/src/update_walk.h +++ b/src/UpdateWalk.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_UPDATE_WALK_H -#define MPD_UPDATE_WALK_H +#ifndef MPD_UPDATE_WALK_HXX +#define MPD_UPDATE_WALK_HXX #include "check.h" -#include <stdbool.h> - void update_walk_global_init(void); diff --git a/src/main_win32.c b/src/Win32Main.cxx index aac7ad886..8543ea108 100644 --- a/src/main_win32.c +++ b/src/Win32Main.cxx @@ -18,12 +18,15 @@ */ #include "config.h" -#include "main.h" +#include "Main.hxx" #ifdef WIN32 #include "mpd_error.h" + +extern "C" { #include "event_pipe.h" +} #include <glib.h> diff --git a/src/audio_check.h b/src/audio_check.h index 9f71cf9c0..e2302126f 100644 --- a/src/audio_check.h +++ b/src/audio_check.h @@ -28,7 +28,7 @@ /** * The GLib quark used for errors reported by this library. */ -G_GNUC_CONST +gcc_const static inline GQuark audio_format_quark(void) { diff --git a/src/audio_format.h b/src/audio_format.h index bf77add3b..d86ade5ee 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 || @@ -223,7 +224,7 @@ 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 +255,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 +264,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 +275,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 +288,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,7 +300,7 @@ 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); diff --git a/src/audio_parser.h b/src/audio_parser.h index a963eb467..49926999e 100644 --- a/src/audio_parser.h +++ b/src/audio_parser.h @@ -25,7 +25,7 @@ #ifndef AUDIO_PARSER_H #define AUDIO_PARSER_H -#include <glib.h> +#include "gerror.h" #include <stdbool.h> 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_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/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/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 index 167f2da92..5f12d84d9 100644 --- a/src/conf.c +++ b/src/conf.c @@ -102,6 +102,7 @@ static struct config_entry config_entries[] = { { .name = CONF_DESPOTIFY_PASSWORD, false, false}, { .name = CONF_DESPOTIFY_HIGH_BITRATE, false, false }, { .name = "filter", true, true }, + { .name = "database", false, true }, }; static bool diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx new file mode 100644 index 000000000..e41ee8819 --- /dev/null +++ b/src/db/ProxyDatabasePlugin.cxx @@ -0,0 +1,476 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ProxyDatabasePlugin.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseSelection.hxx" +#include "PlaylistVector.hxx" +#include "Directory.hxx" +#include "gcc.h" + +extern "C" { +#include "db_error.h" +#include "conf.h" +#include "song.h" +#include "tag.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; 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 (auto i = tag_table; 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..84e4e7cee --- /dev/null +++ b/src/db/SimpleDatabasePlugin.cxx @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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" + +extern "C" { +#include "conf.h" +} + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.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; + free(_path); + + return true; +} + +bool +SimpleDatabase::Check(GError **error_r) const +{ + assert(!path.empty()); + + /* Check if the file exists */ + if (access(path.c_str(), 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(path.c_str()); + + /* 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", + path.c_str(), 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", + path.c_str()); + 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(path.c_str(), &st) < 0) { + g_set_error(error_r, simple_db_quark(), errno, + "Couldn't stat db file \"%s\": %s", + path.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.c_str()); + return false; + } + + /* And check that we can write to it */ + if (access(path.c_str(), R_OK | W_OK)) { + g_set_error(error_r, simple_db_quark(), errno, + "Can't open db file \"%s\" for reading/writing: %s", + path.c_str(), g_strerror(errno)); + return false; + } + + return true; +} + +bool +SimpleDatabase::Load(GError **error_r) +{ + assert(!path.empty()); + assert(root != NULL); + + TextFile file(path.c_str()); + if (file.HasFailed()) { + g_set_error(error_r, simple_db_quark(), errno, + "Failed to open database file \"%s\": %s", + path.c_str(), g_strerror(errno)); + return false; + } + + if (!db_load_internal(file, root, error_r)) + return false; + + struct stat st; + if (stat(path.c_str(), &st) == 0) + 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.c_str(), "w"); + if (!fp) { + g_set_error(error_r, simple_db_quark(), errno, + "unable to write to db file \"%s\": %s", + path.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 (stat(path.c_str(), &st) == 0) + 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..789dcdae9 --- /dev/null +++ b/src/db/SimpleDatabasePlugin.hxx @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "gcc.h" + +#include <cassert> +#include <string> + +#include <time.h> + +struct Directory; + +class SimpleDatabase : public Database { + std::string path; + + Directory *root; + + time_t mtime; + +#ifndef NDEBUG + unsigned borrowed_song_count; +#endif + +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/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_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_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..b3b7f1d73 --- /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" + +extern "C" { +#include "decoder_api.h" +#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/_flac_common.c b/src/decoder/FLACCommon.cxx index d7f0c4a8a..97743f332 100644 --- a/src/decoder/_flac_common.c +++ b/src/decoder/FLACCommon.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,40 +22,35 @@ */ #include "config.h" -#include "_flac_common.h" -#include "flac_metadata.h" -#include "flac_pcm.h" +#include "FLACCommon.hxx" +#include "FLACMetaData.hxx" +#include "FLAC_PCM.hxx" + +extern "C" { #include "audio_check.h" +} #include <glib.h> #include <assert.h> -void -flac_data_init(struct flac_data *data, struct decoder * decoder, - struct input_stream *input_stream) +flac_data::flac_data(struct decoder *_decoder, + struct input_stream *_input_stream) + :FLACInput(_input_stream, _decoder), + initialized(false), unsupported(false), + total_frames(0), first_frame(0), next_frame(0), position(0), + decoder(_decoder), input_stream(_input_stream), + tag(nullptr) { - pcm_buffer_init(&data->buffer); - - data->unsupported = false; - data->initialized = false; - data->total_frames = 0; - data->first_frame = 0; - data->next_frame = 0; - - data->position = 0; - data->decoder = decoder; - data->input_stream = input_stream; - data->tag = NULL; + pcm_buffer_init(&buffer); } -void -flac_data_deinit(struct flac_data *data) +flac_data::~flac_data() { - pcm_buffer_deinit(&data->buffer); + pcm_buffer_deinit(&buffer); - if (data->tag != NULL) - tag_free(data->tag); + if (tag != nullptr) + tag_free(tag); } static enum sample_format @@ -86,7 +81,7 @@ flac_got_stream_info(struct flac_data *data, if (data->initialized || data->unsupported) return; - GError *error = NULL; + GError *error = nullptr; if (!audio_format_init_checked(&data->audio_format, stream_info->sample_rate, flac_sample_format(stream_info->bits_per_sample), @@ -129,8 +124,8 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, decoder_mixramp(data->decoder, replay_gain_db, mixramp_start, mixramp_end); - if (data->tag != NULL) - flac_vorbis_comments_to_tag(data->tag, NULL, + if (data->tag != nullptr) + flac_vorbis_comments_to_tag(data->tag, &block->data.vorbis_comment); default: @@ -138,15 +133,6 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, } } -void flac_error_common_cb(const FLAC__StreamDecoderErrorStatus status, - struct flac_data *data) -{ - if (decoder_get_command(data->decoder) == DECODE_COMMAND_STOP) - return; - - g_warning("%s", FLAC__StreamDecoderErrorStatusString[status]); -} - /** * This function attempts to call decoder_initialized() in case there * was no STREAMINFO block. This is allowed for nonseekable streams, @@ -160,7 +146,7 @@ flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) if (data->unsupported) return false; - GError *error = NULL; + GError *error = nullptr; if (!audio_format_init_checked(&data->audio_format, header->sample_rate, flac_sample_format(header->bits_per_sample), @@ -199,7 +185,7 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame, buffer = pcm_buffer_get(&data->buffer, buffer_size); flac_convert(buffer, frame->header.channels, - data->audio_format.format, buf, + (enum sample_format)data->audio_format.format, buf, 0, frame->header.blocksize); if (nbytes > 0) diff --git a/src/decoder/_flac_common.h b/src/decoder/FLACCommon.hxx index 0d90ba656..e9b45976d 100644 --- a/src/decoder/_flac_common.h +++ b/src/decoder/FLACCommon.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,13 +21,15 @@ * Common data structures and functions used by FLAC and OggFLAC */ -#ifndef MPD_FLAC_COMMON_H -#define MPD_FLAC_COMMON_H +#ifndef MPD_FLAC_COMMON_HXX +#define MPD_FLAC_COMMON_HXX +#include "FLACInput.hxx" + +extern "C" { #include "decoder_api.h" #include "pcm_buffer.h" - -#include <glib.h> +} #include <FLAC/stream_decoder.h> #include <FLAC/metadata.h> @@ -35,7 +37,7 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "flac" -struct flac_data { +struct flac_data : public FLACInput { struct pcm_buffer buffer; /** @@ -78,25 +80,19 @@ struct flac_data { FLAC__uint64 next_frame; FLAC__uint64 position; + struct decoder *decoder; struct input_stream *input_stream; - struct tag *tag; -}; -/* initializes a given FlacData struct */ -void -flac_data_init(struct flac_data *data, struct decoder * decoder, - struct input_stream *input_stream); + struct tag *tag; -void -flac_data_deinit(struct flac_data *data); + flac_data(struct decoder *decoder, struct input_stream *input_stream); + ~flac_data(); +}; void flac_metadata_common_cb(const FLAC__StreamMetadata * block, struct flac_data *data); -void flac_error_common_cb(FLAC__StreamDecoderErrorStatus status, - struct flac_data *data); - FLAC__StreamDecoderWriteStatus flac_common_write(struct flac_data *data, const FLAC__Frame * frame, const FLAC__int32 *const buf[], diff --git a/src/decoder/flac_decoder_plugin.c b/src/decoder/FLACDecoderPlugin.cxx index fb0b3502d..dbe7f207f 100644 --- a/src/decoder/flac_decoder_plugin.c +++ b/src/decoder/FLACDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,13 @@ */ #include "config.h" /* must be first for large file support */ -#include "_flac_common.h" -#include "flac_compat.h" -#include "flac_metadata.h" +#include "FLACDecoderPlugin.h" +#include "FLACCommon.hxx" +#include "FLACMetaData.hxx" -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 -#include "_ogg_common.h" -#endif +extern "C" { +#include "ogg_codec.h" +} #include <glib.h> @@ -34,114 +34,10 @@ #include <sys/stat.h> #include <sys/types.h> -/* this code was based on flac123, from flac-tools */ - -static FLAC__StreamDecoderReadStatus -flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__byte buf[], flac_read_status_size_t *bytes, - void *fdata) -{ - struct flac_data *data = fdata; - size_t r; - - r = decoder_read(data->decoder, data->input_stream, - (void *)buf, *bytes); - *bytes = r; - - if (r == 0) { - if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || - input_stream_lock_eof(data->input_stream)) - return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; - else - return FLAC__STREAM_DECODER_READ_STATUS_ABORT; - } - - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; -} - -static FLAC__StreamDecoderSeekStatus -flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (!data->input_stream->seekable) - return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; - - if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET, - NULL)) - return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; - - return FLAC__STREAM_DECODER_SEEK_STATUS_OK; -} - -static FLAC__StreamDecoderTellStatus -flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 * offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (!data->input_stream->seekable) - return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; - - *offset = (long)(data->input_stream->offset); - - return FLAC__STREAM_DECODER_TELL_STATUS_OK; -} - -static FLAC__StreamDecoderLengthStatus -flac_length_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 * length, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (data->input_stream->size < 0) - return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; - - *length = (size_t) (data->input_stream->size); - - return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; -} - -static FLAC__bool -flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && - decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || - input_stream_lock_eof(data->input_stream); -} - -static void -flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__StreamDecoderErrorStatus status, void *fdata) -{ - flac_error_common_cb(status, (struct flac_data *) fdata); -} - #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) -{ - switch (state) { - case FLAC__SEEKABLE_STREAM_DECODER_OK: - case FLAC__SEEKABLE_STREAM_DECODER_SEEKING: - case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: - return; - - case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: - case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: - case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: - break; - } +#error libFLAC is too old +#endif - g_warning("%s\n", FLAC__SeekableStreamDecoderStateString[state]); -} -#else /* FLAC_API_VERSION_CURRENT >= 7 */ static void flacPrintErroredState(FLAC__StreamDecoderState state) { switch (state) { @@ -162,7 +58,6 @@ static void flacPrintErroredState(FLAC__StreamDecoderState state) g_warning("%s\n", FLAC__StreamDecoderStateString[state]); } -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec, const FLAC__StreamMetadata * block, void *vdata) @@ -195,7 +90,30 @@ static bool flac_scan_file(const char *file, const struct tag_handler *handler, void *handler_ctx) { - return flac_scan_file2(file, NULL, handler, handler_ctx); + FLACMetadataChain chain; + if (!chain.Read(file)) { + g_debug("Failed to read FLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static bool +flac_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + FLACMetadataChain chain; + if (!chain.Read(is)) { + g_debug("Failed to read FLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; } /** @@ -205,15 +123,13 @@ static FLAC__StreamDecoder * flac_decoder_new(void) { FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); - if (sd == NULL) { + if (sd == nullptr) { g_warning("FLAC__stream_decoder_new() failed"); - return NULL; + return nullptr; } -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) g_debug("FLAC__stream_decoder_set_metadata_respond() has failed"); -#endif return sd; } @@ -259,7 +175,7 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, data->first_frame = t_start; while (true) { - if (data->tag != NULL && !tag_is_empty(data->tag)) { + if (data->tag != nullptr && !tag_is_empty(data->tag)) { cmd = decoder_tag(data->decoder, data->input_stream, data->tag); tag_free(data->tag); @@ -300,34 +216,30 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, static FLAC__StreamDecoderInitStatus stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) { -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 return FLAC__stream_decoder_init_ogg_stream(flac_dec, - flac_read_cb, - flac_seek_cb, - flac_tell_cb, - flac_length_cb, - flac_eof_cb, + FLACInput::Read, + FLACInput::Seek, + FLACInput::Tell, + FLACInput::Length, + FLACInput::Eof, flac_write_cb, flacMetadata, - flac_error_cb, + FLACInput::Error, data); -#else - (void)flac_dec; - (void)data; - - return FLAC__STREAM_DECODER_INIT_STATUS_ERROR; -#endif } static FLAC__StreamDecoderInitStatus stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) { return FLAC__stream_decoder_init_stream(flac_dec, - flac_read_cb, flac_seek_cb, - flac_tell_cb, flac_length_cb, - flac_eof_cb, flac_write_cb, + FLACInput::Read, + FLACInput::Seek, + FLACInput::Tell, + FLACInput::Length, + FLACInput::Eof, + flac_write_cb, flacMetadata, - flac_error_cb, + FLACInput::Error, data); } @@ -345,28 +257,23 @@ flac_decode_internal(struct decoder * decoder, bool is_ogg) { FLAC__StreamDecoder *flac_dec; - struct flac_data data; flac_dec = flac_decoder_new(); - if (flac_dec == NULL) + if (flac_dec == nullptr) return; - flac_data_init(&data, decoder, input_stream); + struct flac_data data(decoder, input_stream); data.tag = tag_new(); FLAC__StreamDecoderInitStatus status = stream_init(flac_dec, &data, is_ogg); if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { - flac_data_deinit(&data); FLAC__stream_decoder_delete(flac_dec); -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 g_warning("%s", FLAC__StreamDecoderInitStatusString[status]); -#endif return; } if (!flac_decoder_initialize(&data, flac_dec, 0)) { - flac_data_deinit(&data); FLAC__stream_decoder_finish(flac_dec); FLAC__stream_decoder_delete(flac_dec); return; @@ -374,8 +281,6 @@ flac_decode_internal(struct decoder * decoder, flac_decoder_loop(&data, flac_dec, 0, 0); - flac_data_deinit(&data); - FLAC__stream_decoder_finish(flac_dec); FLAC__stream_decoder_delete(flac_dec); } @@ -386,101 +291,96 @@ flac_decode(struct decoder * decoder, struct input_stream *input_stream) flac_decode_internal(decoder, input_stream, false); } -#ifndef HAVE_OGGFLAC - static bool oggflac_init(G_GNUC_UNUSED const struct config_param *param) { -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 return !!FLAC_API_SUPPORTS_OGG_FLAC; -#else - /* disable oggflac when libflac is too old */ - return false; -#endif } -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - static bool oggflac_scan_file(const char *file, const struct tag_handler *handler, void *handler_ctx) { - FLAC__Metadata_Iterator *it; - FLAC__StreamMetadata *block; - FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); - - if (!(FLAC__metadata_chain_read_ogg(chain, file))) { - FLAC__metadata_chain_delete(chain); + FLACMetadataChain chain; + if (!chain.ReadOgg(file)) { + g_debug("Failed to read OggFLAC tags: %s", + chain.GetStatusString()); return false; } - it = FLAC__metadata_iterator_new(); - FLAC__metadata_iterator_init(it, chain); - - do { - if (!(block = FLAC__metadata_iterator_get_block(it))) - break; + chain.Scan(handler, handler_ctx); + return true; +} - flac_scan_metadata(NULL, block, - handler, handler_ctx); - } while (FLAC__metadata_iterator_next(it)); - FLAC__metadata_iterator_delete(it); +static bool +oggflac_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + FLACMetadataChain chain; + if (!chain.ReadOgg(is)) { + g_debug("Failed to read OggFLAC tags: %s", + chain.GetStatusString()); + return false; + } - FLAC__metadata_chain_delete(chain); + chain.Scan(handler, handler_ctx); return true; } static void oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) { - if (ogg_stream_type_detect(input_stream) != FLAC) + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_FLAC) return; - /* rewind the stream, because ogg_stream_type_detect() has + /* rewind the stream, because ogg_codec_detect() has moved it */ - input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); + input_stream_lock_seek(input_stream, 0, SEEK_SET, nullptr); flac_decode_internal(decoder, input_stream, true); } -static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; +static const char *const oggflac_suffixes[] = { "ogg", "oga", nullptr }; static const char *const oggflac_mime_types[] = { "application/ogg", "application/x-ogg", "audio/ogg", "audio/x-flac+ogg", "audio/x-ogg", - NULL + nullptr }; -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - const struct decoder_plugin oggflac_decoder_plugin = { - .name = "oggflac", - .init = oggflac_init, -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - .stream_decode = oggflac_decode, - .scan_file = oggflac_scan_file, - .suffixes = oggflac_suffixes, - .mime_types = oggflac_mime_types -#endif + "oggflac", + oggflac_init, + nullptr, + oggflac_decode, + nullptr, + oggflac_scan_file, + oggflac_scan_stream, + nullptr, + oggflac_suffixes, + oggflac_mime_types, }; -#endif /* HAVE_OGGFLAC */ - -static const char *const flac_suffixes[] = { "flac", NULL }; +static const char *const flac_suffixes[] = { "flac", nullptr }; static const char *const flac_mime_types[] = { "application/flac", "application/x-flac", "audio/flac", "audio/x-flac", - NULL + nullptr }; const struct decoder_plugin flac_decoder_plugin = { - .name = "flac", - .stream_decode = flac_decode, - .scan_file = flac_scan_file, - .suffixes = flac_suffixes, - .mime_types = flac_mime_types, + "flac", + nullptr, + nullptr, + flac_decode, + nullptr, + flac_scan_file, + flac_scan_stream, + nullptr, + flac_suffixes, + flac_mime_types, }; diff --git a/src/decoder/FLACDecoderPlugin.h b/src/decoder/FLACDecoderPlugin.h new file mode 100644 index 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..193a15ef5 --- /dev/null +++ b/src/decoder/FLACIOHandle.hxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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" + +extern "C" { +#include "input_stream.h" +} + +#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..0383ac4ff --- /dev/null +++ b/src/decoder/FLACInput.cxx @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "gcc.h" + +extern "C" { +#include "input_stream.h" +#include "decoder_api.h" +} + +FLAC__StreamDecoderReadStatus +FLACInput::Read(FLAC__byte buffer[], size_t *bytes) +{ + size_t r = decoder_read(decoder, input_stream, (void *)buffer, *bytes); + *bytes = r; + + if (r == 0) { + if (input_stream_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/flac_metadata.c b/src/decoder/FLACMetaData.cxx index bd1eaf323..561c1591f 100644 --- a/src/decoder/flac_metadata.c +++ b/src/decoder/FLACMetaData.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,16 +18,20 @@ */ #include "config.h" -#include "flac_metadata.h" +#include "FLACMetaData.hxx" + +extern "C" { +#include "XiphTags.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 @@ -91,7 +95,7 @@ flac_find_string_comment(const FLAC__StreamMetadata *block, int len; const unsigned char *p; - *str = NULL; + *str = nullptr; offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, cmnt); if (offset < 0) @@ -128,36 +132,21 @@ flac_parse_mixramp(char **mixramp_start, char **mixramp_end, */ static const char * flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, const char *char_tnum, size_t *length_r) + const char *name, size_t *length_r) { size_t name_length = strlen(name); - size_t char_tnum_length = 0; const char *comment = (const char*)entry->entry; if (entry->length <= name_length || g_ascii_strncasecmp(comment, name, name_length) != 0) - return NULL; - - if (char_tnum != NULL) { - char_tnum_length = strlen(char_tnum); - if (entry->length > name_length + char_tnum_length + 2 && - comment[name_length] == '[' && - g_ascii_strncasecmp(comment + name_length + 1, - char_tnum, char_tnum_length) == 0 && - comment[name_length + char_tnum_length + 1] == ']') - name_length = name_length + char_tnum_length + 2; - else if (entry->length > name_length + char_tnum_length && - g_ascii_strncasecmp(comment + name_length, - char_tnum, char_tnum_length) == 0) - name_length = name_length + char_tnum_length; - } + return nullptr; if (comment[name_length] == '=') { *length_r = entry->length - name_length - 1; return comment + name_length + 1; } - return NULL; + return nullptr; } /** @@ -167,14 +156,13 @@ flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, static bool flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, const char *name, enum tag_type tag_type, - const char *char_tnum, const struct tag_handler *handler, void *handler_ctx) { const char *value; size_t value_length; - value = flac_comment_value(entry, name, char_tnum, &value_length); - if (value != NULL) { + value = flac_comment_value(entry, name, &value_length); + if (value != nullptr) { char *p = g_strndup(value, value_length); tag_handler_invoke_tag(handler, handler_ctx, tag_type, p); g_free(p); @@ -184,23 +172,15 @@ flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, return false; } -static const struct tag_table flac_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - static void -flac_scan_comment(const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment_Entry *entry, +flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, const struct tag_handler *handler, void *handler_ctx) { - if (handler->pair != NULL) { + if (handler->pair != nullptr) { char *name = g_strdup((const char*)entry->entry); char *value = strchr(name, '='); - if (value != NULL && value > name) { + if (value != nullptr && value > name) { *value++ = 0; tag_handler_invoke_pair(handler, handler_ctx, name, value); @@ -209,36 +189,34 @@ flac_scan_comment(const char *char_tnum, g_free(name); } - for (const struct tag_table *i = flac_tags; i->name != NULL; ++i) - if (flac_copy_comment(entry, i->name, i->type, char_tnum, + for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) + if (flac_copy_comment(entry, i->name, i->type, handler, handler_ctx)) return; for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) if (flac_copy_comment(entry, - tag_item_names[i], i, char_tnum, + tag_item_names[i], (enum tag_type)i, handler, handler_ctx)) return; } static void -flac_scan_comments(const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment *comment, +flac_scan_comments(const FLAC__StreamMetadata_VorbisComment *comment, const struct tag_handler *handler, void *handler_ctx) { for (unsigned i = 0; i < comment->num_comments; ++i) - flac_scan_comment(char_tnum, &comment->comments[i], + flac_scan_comment(&comment->comments[i], handler, handler_ctx); } void -flac_scan_metadata(const char *track, - const FLAC__StreamMetadata *block, +flac_scan_metadata(const FLAC__StreamMetadata *block, const struct tag_handler *handler, void *handler_ctx) { switch (block->type) { case FLAC__METADATA_TYPE_VORBIS_COMMENT: - flac_scan_comments(track, &block->data.vorbis_comment, + flac_scan_comments(&block->data.vorbis_comment, handler, handler_ctx); break; @@ -254,70 +232,22 @@ flac_scan_metadata(const char *track, } void -flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, +flac_vorbis_comments_to_tag(struct tag *tag, const FLAC__StreamMetadata_VorbisComment *comment) { - flac_scan_comments(char_tnum, comment, - &add_tag_handler, tag); + flac_scan_comments(comment, &add_tag_handler, tag); } -bool -flac_scan_file2(const char *file, const char *char_tnum, - const struct tag_handler *handler, void *handler_ctx) +void +FLACMetadataChain::Scan(const struct tag_handler *handler, void *handler_ctx) { - FLAC__Metadata_SimpleIterator *it; - FLAC__StreamMetadata *block = NULL; - - it = FLAC__metadata_simple_iterator_new(); - if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) { - const char *err; - FLAC_API FLAC__Metadata_SimpleIteratorStatus s; - - s = FLAC__metadata_simple_iterator_status(it); - - switch (s) { /* slightly more human-friendly messages: */ - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT: - err = "illegal input"; - break; - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE: - err = "error opening file"; - break; - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE: - err = "not a FLAC file"; - break; - default: - err = FLAC__Metadata_SimpleIteratorStatusString[s]; - } - g_debug("Reading '%s' metadata gave the following error: %s\n", - file, err); - FLAC__metadata_simple_iterator_delete(it); - return false; - } + FLACMetadataIterator iterator(*this); do { - block = FLAC__metadata_simple_iterator_get_block(it); - if (!block) + FLAC__StreamMetadata *block = iterator.GetBlock(); + if (block == nullptr) break; - flac_scan_metadata(char_tnum, block, handler, handler_ctx); - FLAC__metadata_object_delete(block); - } while (FLAC__metadata_simple_iterator_next(it)); - - FLAC__metadata_simple_iterator_delete(it); - - return true; -} - -struct tag * -flac_tag_load(const char *file, const char *char_tnum) -{ - struct tag *tag = tag_new(); - - if (!flac_scan_file2(file, char_tnum, &add_tag_handler, tag) || - tag_is_empty(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; + flac_scan_metadata(block, handler, handler_ctx); + } while (iterator.Next()); } diff --git a/src/decoder/FLACMetaData.hxx b/src/decoder/FLACMetaData.hxx new file mode 100644 index 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.c b/src/decoder/FLAC_PCM.cxx index 6964d8ac6..303530aa7 100644 --- a/src/decoder/flac_pcm.c +++ b/src/decoder/FLAC_PCM.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "flac_pcm.h" +#include "FLAC_PCM.hxx" #include <assert.h> diff --git a/src/decoder/flac_pcm.h b/src/decoder/FLAC_PCM.hxx index a931998c1..97d214c17 100644 --- a/src/decoder/flac_pcm.h +++ b/src/decoder/FLAC_PCM.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_FLAC_PCM_H -#define MPD_FLAC_PCM_H +#ifndef MPD_FLAC_PCM_HXX +#define MPD_FLAC_PCM_HXX #include "audio_format.h" diff --git a/src/decoder/OggUtil.cxx b/src/decoder/OggUtil.cxx new file mode 100644 index 000000000..99f73d48e --- /dev/null +++ b/src/decoder/OggUtil.cxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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" + +extern "C" { +#include "decoder_api.h" +} + +bool +OggFeed(ogg_sync_state &oy, struct decoder *decoder, + struct 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, + struct decoder *decoder, struct 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; + } +} diff --git a/src/decoder/OggUtil.hxx b/src/decoder/OggUtil.hxx new file mode 100644 index 000000000..95bf6472f --- /dev/null +++ b/src/decoder/OggUtil.hxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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> + +/** + * 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, + struct input_stream *input_stream, 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, + struct decoder *decoder, struct input_stream *input_stream); + +#endif diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx new file mode 100644 index 000000000..35e368ca9 --- /dev/null +++ b/src/decoder/OpusDecoderPlugin.cxx @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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" + +extern "C" { +#include "ogg_codec.h" +#include "decoder_api.h" +} + +#include "audio_check.h" +#include "tag_handler.h" + +#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 = nullptr; + opus_int16 *output_buffer = nullptr; + unsigned output_size = 0; + + bool os_initialized = false; + bool found_opus = false; + + int opus_serialno; + + size_t frame_size; + +public: + MPDOpusDecoder(struct decoder *_decoder, + struct input_stream *_input_stream) + :decoder(_decoder), input_stream(_input_stream) {} + ~MPDOpusDecoder(); + + enum decoder_command HandlePage(ogg_page &page); + 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); +} + +enum decoder_command +MPDOpusDecoder::HandlePage(ogg_page &page) +{ + const auto page_serialno = ogg_page_serialno(&page); + if (!os_initialized) { + os_initialized = true; + ogg_stream_init(&os, page_serialno); + } else if (page_serialno != os.serialno) + ogg_stream_reset_serialno(&os, page_serialno); + + ogg_stream_pagein(&os, &page); + + 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; +} + +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); +} + +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); +} + +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; +} + +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); + + ogg_sync_state oy; + ogg_sync_init(&oy); + + while (true) { + if (!OggFeed(oy, decoder, input_stream, 1024)) + break; + + ogg_page page; + while (ogg_sync_pageout(&oy, &page) == 1) { + enum decoder_command cmd = d.HandlePage(page); + if (cmd != DECODE_COMMAND_NONE) + break; + } + } + + ogg_sync_clear(&oy); +} + +static bool +mpd_opus_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + ogg_sync_state oy; + ogg_sync_init(&oy); + + ogg_page page; + if (!OggExpectPage(oy, page, nullptr, is)) { + ogg_sync_clear(&oy); + return false; + } + + /* read at most two more pages */ + unsigned remaining_pages = 2; + + bool result = false; + + ogg_stream_state os; + ogg_stream_init(&os, ogg_page_serialno(&page)); + ogg_stream_pagein(&os, &page); + + 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 (!OggExpectPage(oy, page, nullptr, is)) { + result = false; + break; + } + + ogg_stream_pagein(&os, &page); + 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; + } + } + + ogg_stream_clear(&os); + ogg_sync_clear(&oy); + + 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..2cfc14118 --- /dev/null +++ b/src/decoder/OpusReader.hxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_READER_HXX +#define MPD_OPUS_READER_HXX + +#include "check.h" + +#include <stdint.h> +#include <string.h> + +class OpusReader { + const uint8_t *p, *const end; + +public: + OpusReader(const void *_p, size_t size) + :p((const uint8_t *)_p), end(p + size) {} + + bool Skip(size_t length) { + p += length; + return p <= end; + } + + const void *Read(size_t length) { + const uint8_t *result = p; + return Skip(length) + ? result + : nullptr; + } + + bool Expect(const void *value, size_t length) { + const void *data = Read(length); + return data != nullptr && memcmp(value, data, length) == 0; + } + + bool ReadByte(uint8_t &value_r) { + if (p >= end) + return false; + + value_r = *p++; + return true; + } + + bool ReadShort(uint16_t &value_r) { + const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); + if (value == nullptr) + return false; + + value_r = value[0] | (value[1] << 8); + return true; + } + + bool ReadWord(uint32_t &value_r) { + const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); + if (value == nullptr) + return false; + + value_r = value[0] | (value[1] << 8) + | (value[2] << 16) | (value[3] << 24); + return true; + } + + bool SkipString() { + uint32_t length; + return ReadWord(length) && Skip(length); + } + + char *ReadString() { + uint32_t length; + if (!ReadWord(length)) + return nullptr; + + const char *src = (const char *)Read(length); + if (src == nullptr) + return nullptr; + + 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/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/dsdiff_decoder_plugin.c b/src/decoder/dsdiff_decoder_plugin.c index 84471fb3a..4e21e91de 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; @@ -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 */ + + while ( is->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 = is->offset; + } + + /* 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 = is->offset; + } +#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 = is->offset; + metadata->id3_size = chunk_size; + } +#endif + if (chunk_size != 0) { + if (!dsdlib_skip(decoder, is, chunk_size)) + break; + } + + if ( is->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 @@ -374,6 +508,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..c788184e2 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) { @@ -110,3 +116,53 @@ 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 */ + if (is->offset >= is->size) + return; + + count = is->size - is->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..6700f739a 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,19 @@ 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 */ + if (data_size >= (unsigned) is->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 >= (unsigned) is->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 +306,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 +337,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/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_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/mad_decoder_plugin.c b/src/decoder/mad_decoder_plugin.c index 62c371642..c1a0c4b0f 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 */ diff --git a/src/decoder/_ogg_common.c b/src/decoder/ogg_codec.c index 09d2712da..7416f27da 100644 --- a/src/decoder/_ogg_common.c +++ b/src/decoder/ogg_codec.c @@ -22,25 +22,27 @@ */ #include "config.h" -#include "_ogg_common.h" +#include "ogg_codec.h" -ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream) +enum ogg_codec +ogg_codec_detect(struct decoder *decoder, struct input_stream *is) { /* oggflac detection based on code in ogg123 and this post * http://lists.xiph.org/pipermail/flac/2004-December/000393.html * ogg123 trunk still doesn't have this patch as of June 2005 */ unsigned char buf[41]; - size_t r; - - r = decoder_read(NULL, inStream, buf, sizeof(buf)); + size_t r = decoder_read(decoder, is, buf, sizeof(buf)); if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0) - return VORBIS; + return OGG_CODEC_UNKNOWN; if ((memcmp(buf + 29, "FLAC", 4) == 0 && memcmp(buf + 37, "fLaC", 4) == 0) || memcmp(buf + 28, "FLAC", 4) == 0 || memcmp(buf + 28, "fLaC", 4) == 0) - return FLAC; + return OGG_CODEC_FLAC; + + if (memcmp(buf + 28, "Opus", 4) == 0) + return OGG_CODEC_OPUS; - return VORBIS; + return OGG_CODEC_VORBIS; } diff --git a/src/decoder/_ogg_common.h b/src/decoder/ogg_codec.h index 85e4ebba6..fd1fecfbb 100644 --- a/src/decoder/_ogg_common.h +++ b/src/decoder/ogg_codec.h @@ -21,13 +21,19 @@ * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) */ -#ifndef MPD_OGG_COMMON_H -#define MPD_OGG_COMMON_H +#ifndef MPD_OGG_CODEC_H +#define MPD_OGG_CODEC_H #include "decoder_api.h" -typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type; +enum ogg_codec { + OGG_CODEC_UNKNOWN, + OGG_CODEC_VORBIS, + OGG_CODEC_FLAC, + OGG_CODEC_OPUS, +}; -ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream); +enum ogg_codec +ogg_codec_detect(struct decoder *decoder, struct input_stream *is); #endif /* _OGG_COMMON_H */ diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx index 5d162f179..de2e599e9 100644 --- a/src/decoder/sidplay_decoder_plugin.cxx +++ b/src/decoder/sidplay_decoder_plugin.cxx @@ -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/vorbis_comments.c b/src/decoder/vorbis_comments.c index 6c2d57b72..84f7c5014 100644 --- a/src/decoder/vorbis_comments.c +++ b/src/decoder/vorbis_comments.c @@ -19,6 +19,7 @@ #include "config.h" #include "vorbis_comments.h" +#include "XiphTags.h" #include "tag.h" #include "tag_table.h" #include "tag_handler.h" @@ -95,13 +96,6 @@ vorbis_copy_comment(const char *comment, return false; } -static const struct tag_table vorbis_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - static void vorbis_scan_comment(const char *comment, const struct tag_handler *handler, void *handler_ctx) @@ -119,7 +113,7 @@ vorbis_scan_comment(const char *comment, g_free(name); } - for (const struct tag_table *i = vorbis_tags; i->name != NULL; ++i) + for (const struct tag_table *i = xiph_tags; i->name != NULL; ++i) if (vorbis_copy_comment(comment, i->name, i->type, handler, handler_ctx)) return; diff --git a/src/decoder/vorbis_decoder_plugin.c b/src/decoder/vorbis_decoder_plugin.c index 15cdc0ca9..f180a6559 100644 --- a/src/decoder/vorbis_decoder_plugin.c +++ b/src/decoder/vorbis_decoder_plugin.c @@ -19,7 +19,7 @@ #include "config.h" #include "vorbis_comments.h" -#include "_ogg_common.h" +#include "ogg_codec.h" #include "audio_check.h" #include "uri.h" #include "tag_handler.h" @@ -48,12 +48,11 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "vorbis" -#define OGG_CHUNK_SIZE 4096 #if G_BYTE_ORDER == G_BIG_ENDIAN -#define OGG_DECODE_USE_BIGENDIAN 1 +#define VORBIS_BIG_ENDIAN true #else -#define OGG_DECODE_USE_BIGENDIAN 0 +#define VORBIS_BIG_ENDIAN false #endif struct vorbis_input_stream { @@ -66,9 +65,8 @@ struct vorbis_input_stream { static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) { struct vorbis_input_stream *vis = data; - size_t ret; - - ret = decoder_read(vis->decoder, vis->input_stream, ptr, size * nmemb); + size_t ret = decoder_read(vis->decoder, vis->input_stream, + ptr, size * nmemb); errno = 0; @@ -155,9 +153,7 @@ static void vorbis_send_comments(struct decoder *decoder, struct input_stream *is, char **comments) { - struct tag *tag; - - tag = vorbis_comments_to_tag(comments); + struct tag *tag = vorbis_comments_to_tag(comments); if (!tag) return; @@ -165,55 +161,79 @@ vorbis_send_comments(struct decoder *decoder, struct input_stream *is, tag_free(tag); } +#ifndef HAVE_TREMOR +static void +vorbis_interleave(float *dest, const float *const*src, + unsigned nframes, unsigned channels) +{ + for (const float *const*src_end = src + channels; + src != src_end; ++src, ++dest) { + float *d = dest; + for (const float *s = *src, *s_end = s + nframes; + s != s_end; ++s, d += channels) + *d = *s; + } +} +#endif + /* public */ static void vorbis_stream_decode(struct decoder *decoder, struct input_stream *input_stream) { GError *error = NULL; - OggVorbis_File vf; - struct vorbis_input_stream vis; - struct audio_format audio_format; - float total_time; - int current_section; - int prev_section = -1; - long ret; - char chunk[OGG_CHUNK_SIZE]; - long bitRate = 0; - long test; - const vorbis_info *vi; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - if (ogg_stream_type_detect(input_stream) != VORBIS) + + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_VORBIS) return; - /* rewind the stream, because ogg_stream_type_detect() has + /* rewind the stream, because ogg_codec_detect() has moved it */ input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); + struct vorbis_input_stream vis; + OggVorbis_File vf; if (!vorbis_is_open(&vis, &vf, decoder, input_stream)) return; - vi = ov_info(&vf, -1); + const vorbis_info *vi = ov_info(&vf, -1); if (vi == NULL) { g_warning("ov_info() has failed"); return; } + struct audio_format audio_format; if (!audio_format_init_checked(&audio_format, vi->rate, +#ifdef HAVE_TREMOR SAMPLE_FORMAT_S16, +#else + SAMPLE_FORMAT_FLOAT, +#endif vi->channels, &error)) { g_warning("%s", error->message); g_error_free(error); return; } - total_time = ov_time_total(&vf, -1); + float total_time = ov_time_total(&vf, -1); if (total_time < 0) total_time = 0; decoder_initialized(decoder, &audio_format, vis.seekable, total_time); + enum decoder_command cmd = decoder_get_command(decoder); + +#ifdef HAVE_TREMOR + char buffer[4096]; +#else + float buffer[2048]; + const int frames_per_buffer = + G_N_ELEMENTS(buffer) / audio_format.channels; + const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels; +#endif + + int prev_section = -1; + unsigned kbit_rate = 0; + do { if (cmd == DECODE_COMMAND_SEEK) { double seek_where = decoder_seek_where(decoder); @@ -223,17 +243,33 @@ vorbis_stream_decode(struct decoder *decoder, decoder_seek_error(decoder); } - ret = ov_read(&vf, chunk, sizeof(chunk), - OGG_DECODE_USE_BIGENDIAN, 2, 1, ¤t_section); - if (ret == OV_HOLE) /* bad packet */ - ret = 0; - else if (ret <= 0) + int current_section; + +#ifdef HAVE_TREMOR + long nbytes = ov_read(&vf, buffer, sizeof(buffer), + VORBIS_BIG_ENDIAN, 2, 1, + ¤t_section); +#else + float **per_channel; + long nframes = ov_read_float(&vf, &per_channel, + frames_per_buffer, + ¤t_section); + long nbytes = nframes; + if (nframes > 0) { + vorbis_interleave(buffer, + (const float*const*)per_channel, + nframes, audio_format.channels); + nbytes *= frame_size; + } +#endif + + if (nbytes == OV_HOLE) /* bad packet */ + nbytes = 0; + else if (nbytes <= 0) /* break on EOF or other error */ break; if (current_section != prev_section) { - char **comments; - vi = ov_info(&vf, -1); if (vi == NULL) { g_warning("ov_info() has failed"); @@ -248,7 +284,7 @@ vorbis_stream_decode(struct decoder *decoder, break; } - comments = ov_comment(&vf, -1)->user_comments; + char **comments = ov_comment(&vf, -1)->user_comments; vorbis_send_comments(decoder, input_stream, comments); struct replay_gain_info rgi; @@ -258,12 +294,13 @@ vorbis_stream_decode(struct decoder *decoder, prev_section = current_section; } - if ((test = ov_bitrate_instant(&vf)) > 0) - bitRate = test / 1000; + long test = ov_bitrate_instant(&vf); + if (test > 0) + kbit_rate = test / 1000; cmd = decoder_data(decoder, input_stream, - chunk, ret, - bitRate); + buffer, nbytes, + kbit_rate); } while (cmd != DECODE_COMMAND_STOP); ov_clear(&vf); diff --git a/src/decoder_control.c b/src/decoder_control.c index 2ce03b666..33d4e4d44 100644 --- a/src/decoder_control.c +++ b/src/decoder_control.c @@ -20,6 +20,7 @@ #include "config.h" #include "decoder_control.h" #include "pipe.h" +#include "song.h" #include <assert.h> @@ -40,6 +41,8 @@ dc_new(GCond *client_cond) dc->state = DECODE_STATE_STOP; dc->command = DECODE_COMMAND_NONE; + dc->song = NULL; + dc->replay_gain_db = 0; dc->replay_gain_prev_db = 0; dc->mixramp_start = NULL; @@ -52,6 +55,11 @@ dc_new(GCond *client_cond) void dc_free(struct decoder_control *dc) { + dc_clear_error(dc); + + if (dc->song != NULL) + song_free(dc->song); + g_cond_free(dc->cond); g_mutex_free(dc->mutex); g_free(dc->mixramp_start); @@ -79,6 +87,7 @@ static void dc_command(struct decoder_control *dc, enum decoder_command cmd) { decoder_lock(dc); + dc_clear_error(dc); dc_command_locked(dc, cmd); decoder_unlock(dc); } @@ -94,6 +103,27 @@ dc_command_async(struct decoder_control *dc, enum decoder_command cmd) decoder_unlock(dc); } +bool +decoder_is_current_song(const struct decoder_control *dc, + const struct song *song) +{ + assert(dc != NULL); + assert(song != NULL); + + switch (dc->state) { + case DECODE_STATE_STOP: + case DECODE_STATE_ERROR: + return false; + + case DECODE_STATE_START: + case DECODE_STATE_DECODE: + return song_equals(dc->song, song); + } + + assert(false); + return false; +} + void dc_start(struct decoder_control *dc, struct song *song, unsigned start_ms, unsigned end_ms, @@ -104,6 +134,9 @@ dc_start(struct decoder_control *dc, struct song *song, assert(pipe != NULL); assert(music_pipe_empty(pipe)); + if (dc->song != NULL) + song_free(dc->song); + dc->song = song; dc->start_ms = start_ms; dc->end_ms = end_ms; diff --git a/src/decoder_control.h b/src/decoder_control.h index 566b153ee..9ecbde73e 100644 --- a/src/decoder_control.h +++ b/src/decoder_control.h @@ -67,6 +67,14 @@ struct decoder_control { 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; @@ -82,8 +90,11 @@ struct decoder_control { * 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. */ - const struct song *song; + struct song *song; /** * The initial seek position (in milliseconds), e.g. to the @@ -188,6 +199,50 @@ decoder_has_failed(const struct decoder_control *dc) return dc->state == DECODE_STATE_ERROR; } +/** + * Checks whether an error has occurred, and if so, returns a newly + * allocated copy of the #GError object. + * + * Caller must lock the object. + */ +static inline GError * +dc_get_error(const struct decoder_control *dc) +{ + assert(dc != NULL); + assert(dc->command == DECODE_COMMAND_NONE); + assert(dc->state != DECODE_STATE_ERROR || dc->error != NULL); + + return dc->state == DECODE_STATE_ERROR + ? g_error_copy(dc->error) + : NULL; +} + +/** + * Like dc_get_error(), but locks and unlocks the object. + */ +static inline GError * +dc_lock_get_error(struct decoder_control *dc) +{ + decoder_lock(dc); + GError *error = dc_get_error(dc); + decoder_unlock(dc); + return error; +} + +/** + * Clear the error condition and free the #GError object (if any). + * + * Caller must lock the object. + */ +static inline void +dc_clear_error(struct decoder_control *dc) +{ + if (dc->state == DECODE_STATE_ERROR) { + g_error_free(dc->error); + dc->state = DECODE_STATE_STOP; + } +} + static inline bool decoder_lock_is_idle(struct decoder_control *dc) { @@ -224,28 +279,35 @@ decoder_lock_has_failed(struct decoder_control *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; - } +/** + * 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 +decoder_is_current_song(const struct decoder_control *dc, + const struct song *song); - assert(false); - return NULL; +gcc_pure +static inline bool +decoder_lock_is_current_song(struct decoder_control *dc, + const struct song *song) +{ + decoder_lock(dc); + const bool result = decoder_is_current_song(dc, song); + decoder_unlock(dc); + return result; } /** * Start the decoder. * * @param the decoder - * @param song the song to be decoded + * @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 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.h b/src/decoder_internal.h index d89e68cfc..5bc7e216d 100644 --- a/src/decoder_internal.h +++ b/src/decoder_internal.h @@ -80,6 +80,13 @@ struct decoder { * has changed since the last check. */ unsigned replay_gain_serial; + +#ifdef __cplusplus + decoder(decoder_control *_dc, bool _initial_seek_pending) + :dc(_dc), + initial_seek_pending(_initial_seek_pending), + initial_seek_running(false) {} +#endif }; /** diff --git a/src/decoder_list.c b/src/decoder_list.c index 177b632ad..575346c98 100644 --- a/src/decoder_list.c +++ b/src/decoder_list.c @@ -26,6 +26,9 @@ #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/AdPlugDecoderPlugin.h" #include <glib.h> @@ -34,8 +37,6 @@ 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; @@ -66,6 +67,9 @@ const struct decoder_plugin *const decoder_plugins[] = { #ifdef HAVE_FLAC &flac_decoder_plugin, #endif +#ifdef HAVE_OPUS + &opus_decoder_plugin, +#endif #ifdef ENABLE_SNDFILE &sndfile_decoder_plugin, #endif @@ -101,6 +105,9 @@ const struct decoder_plugin *const decoder_plugins[] = { #ifdef ENABLE_FLUIDSYNTH &fluidsynth_decoder_plugin, #endif +#ifdef HAVE_ADPLUG + &adplug_decoder_plugin, +#endif #ifdef HAVE_FFMPEG &ffmpeg_decoder_plugin, #endif diff --git a/src/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/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/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..9f46e8681 --- /dev/null +++ b/src/encoder/OpusEncoderPlugin.cxx @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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" + +extern "C" { +#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; +}; + +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) +{ + struct opus_encoder *encoder; + + encoder = g_new(struct opus_encoder, 1); + encoder_struct_init(&encoder->encoder, &opus_encoder_plugin); + + /* load configuration from "param" */ + if (!opus_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + g_free(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 */ + g_free(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/vorbis_encoder.c b/src/encoder/VorbisEncoderPlugin.cxx index 468cf38ee..bc0f47fd0 100644 --- a/src/encoder/vorbis_encoder.c +++ b/src/encoder/VorbisEncoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,13 @@ */ #include "config.h" +#include "VorbisEncoderPlugin.hxx" +#include "OggStream.hxx" + +extern "C" { #include "encoder_api.h" +} + #include "encoder_plugin.h" #include "tag.h" #include "audio_format.h" @@ -44,17 +50,13 @@ struct vorbis_encoder { struct audio_format audio_format; - ogg_stream_state os; - vorbis_dsp_state vd; vorbis_block vb; vorbis_info vi; - bool flush; + OggStream stream; }; -extern const struct encoder_plugin vorbis_encoder_plugin; - static inline GQuark vorbis_encoder_quark(void) { @@ -65,8 +67,8 @@ static bool vorbis_encoder_configure(struct vorbis_encoder *encoder, const struct config_param *param, GError **error) { - const char *value = config_get_block_string(param, "quality", NULL); - if (value != NULL) { + const char *value = config_get_block_string(param, "quality", nullptr); + if (value != nullptr) { /* a quality was configured (VBR) */ char *endptr; @@ -81,7 +83,7 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder, return false; } - if (config_get_block_string(param, "bitrate", NULL) != NULL) { + if (config_get_block_string(param, "bitrate", nullptr) != nullptr) { g_set_error(error, vorbis_encoder_quark(), 0, "quality and bitrate are " "both defined (line %i)", @@ -91,8 +93,8 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder, } else { /* a bit rate was configured */ - value = config_get_block_string(param, "bitrate", NULL); - if (value == NULL) { + value = config_get_block_string(param, "bitrate", nullptr); + if (value == nullptr) { g_set_error(error, vorbis_encoder_quark(), 0, "neither bitrate nor quality defined " "at line %i", @@ -125,7 +127,7 @@ vorbis_encoder_init(const struct config_param *param, GError **error) if (!vorbis_encoder_configure(encoder, param, error)) { /* configuration has failed, roll back and return error */ g_free(encoder); - return NULL; + return nullptr; } return &encoder->encoder; @@ -174,7 +176,7 @@ vorbis_encoder_reinit(struct vorbis_encoder *encoder, GError **error) vorbis_analysis_init(&encoder->vd, &encoder->vi); vorbis_block_init(&encoder->vd, &encoder->vb); - ogg_stream_init(&encoder->os, g_random_int()); + encoder->stream.Initialize(g_random_int()); return true; } @@ -187,9 +189,9 @@ vorbis_encoder_headerout(struct vorbis_encoder *encoder, vorbis_comment *vc) vorbis_analysis_headerout(&encoder->vd, vc, &packet, &comments, &codebooks); - ogg_stream_packetin(&encoder->os, &packet); - ogg_stream_packetin(&encoder->os, &comments); - ogg_stream_packetin(&encoder->os, &codebooks); + encoder->stream.PacketIn(packet); + encoder->stream.PacketIn(comments); + encoder->stream.PacketIn(codebooks); } static void @@ -209,7 +211,7 @@ vorbis_encoder_open(struct encoder *_encoder, { struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - audio_format->format = SAMPLE_FORMAT_S16; + audio_format->format = SAMPLE_FORMAT_FLOAT; encoder->audio_format = *audio_format; @@ -218,17 +220,13 @@ vorbis_encoder_open(struct encoder *_encoder, vorbis_encoder_send_header(encoder); - /* set "flush" to true, so the caller gets the full headers on - the first read() */ - encoder->flush = true; - return true; } static void vorbis_encoder_clear(struct vorbis_encoder *encoder) { - ogg_stream_clear(&encoder->os); + encoder->stream.Deinitialize(); vorbis_block_clear(&encoder->vb); vorbis_dsp_clear(&encoder->vd); vorbis_info_clear(&encoder->vi); @@ -246,12 +244,12 @@ static void vorbis_encoder_blockout(struct vorbis_encoder *encoder) { while (vorbis_analysis_blockout(&encoder->vd, &encoder->vb) == 1) { - vorbis_analysis(&encoder->vb, NULL); + vorbis_analysis(&encoder->vb, nullptr); vorbis_bitrate_addblock(&encoder->vb); ogg_packet packet; while (vorbis_bitrate_flushpacket(&encoder->vd, &packet)) - ogg_stream_packetin(&encoder->os, &packet); + encoder->stream.PacketIn(packet); } } @@ -260,7 +258,7 @@ vorbis_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) { struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - encoder->flush = true; + encoder->stream.Flush(); return true; } @@ -279,7 +277,7 @@ vorbis_encoder_pre_tag(struct encoder *_encoder, G_GNUC_UNUSED GError **error) vorbis_analysis_init(&encoder->vd, &encoder->vi); vorbis_block_init(&encoder->vd, &encoder->vb); - encoder->flush = true; + encoder->stream.Flush(); return true; } @@ -308,28 +306,23 @@ vorbis_encoder_tag(struct encoder *_encoder, const struct tag *tag, /* reset ogg_stream_state and begin a new stream */ - ogg_stream_reset_serialno(&encoder->os, g_random_int()); + encoder->stream.Reinitialize(g_random_int()); /* send that vorbis_comment to the ogg_stream_state */ vorbis_encoder_headerout(encoder, &comment); vorbis_comment_clear(&comment); - /* the next vorbis_encoder_read() call should flush the - ogg_stream_state */ - - encoder->flush = true; - return true; } static void -pcm16_to_vorbis_buffer(float **dest, const int16_t *src, - unsigned num_frames, unsigned num_channels) +interleaved_to_vorbis_buffer(float **dest, const float *src, + unsigned num_frames, unsigned num_channels) { for (unsigned i = 0; i < num_frames; i++) for (unsigned j = 0; j < num_channels; j++) - dest[j][i] = *src++ / 32768.0; + dest[j][i] = *src++; } static bool @@ -344,10 +337,11 @@ vorbis_encoder_write(struct encoder *_encoder, /* this is for only 16-bit audio */ - pcm16_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd, - num_frames), - (const int16_t *)data, - num_frames, encoder->audio_format.channels); + interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd, + num_frames), + (const float *)data, + num_frames, + encoder->audio_format.channels); vorbis_analysis_wrote(&encoder->vd, num_frames); vorbis_encoder_blockout(encoder); @@ -355,34 +349,11 @@ vorbis_encoder_write(struct encoder *_encoder, } static size_t -vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length) +vorbis_encoder_read(struct encoder *_encoder, void *dest, size_t length) { struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - unsigned char *dest = _dest; - - ogg_page page; - int ret = ogg_stream_pageout(&encoder->os, &page); - if (ret == 0 && encoder->flush) { - encoder->flush = false; - ret = ogg_stream_flush(&encoder->os, &page); - - } - - if (ret == 0) - return 0; - - assert(page.header_len > 0 || page.body_len > 0); - - size_t nbytes = (size_t)page.header_len + (size_t)page.body_len; - - if (nbytes > length) - /* XXX better error handling */ - MPD_ERROR("buffer too small"); - - memcpy(dest, page.header, page.header_len); - memcpy(dest + page.header_len, page.body, page.body_len); - return nbytes; + return encoder->stream.PageOut(dest, length); } static const char * @@ -392,16 +363,16 @@ vorbis_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) } const struct encoder_plugin vorbis_encoder_plugin = { - .name = "vorbis", - .init = vorbis_encoder_init, - .finish = vorbis_encoder_finish, - .open = vorbis_encoder_open, - .close = vorbis_encoder_close, - .end = vorbis_encoder_pre_tag, - .flush = vorbis_encoder_flush, - .pre_tag = vorbis_encoder_pre_tag, - .tag = vorbis_encoder_tag, - .write = vorbis_encoder_write, - .read = vorbis_encoder_read, - .get_mime_type = vorbis_encoder_get_mime_type, + "vorbis", + vorbis_encoder_init, + vorbis_encoder_finish, + vorbis_encoder_open, + vorbis_encoder_close, + vorbis_encoder_pre_tag, + vorbis_encoder_flush, + vorbis_encoder_pre_tag, + vorbis_encoder_tag, + vorbis_encoder_write, + vorbis_encoder_read, + vorbis_encoder_get_mime_type, }; diff --git a/src/encoder/VorbisEncoderPlugin.hxx b/src/encoder/VorbisEncoderPlugin.hxx new file mode 100644 index 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..6b09bcd66 100644 --- a/src/encoder/flac_encoder.c +++ b/src/encoder/flac_encoder.c @@ -30,6 +30,10 @@ #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_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_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_pipe.h b/src/event_pipe.h index 3734bb86c..3749ccf79 100644 --- a/src/event_pipe.h +++ b/src/event_pipe.h @@ -20,8 +20,6 @@ #ifndef EVENT_PIPE_H #define EVENT_PIPE_H -#include <glib.h> - enum pipe_event { /** database update was finished */ PIPE_EVENT_UPDATE, diff --git a/src/fd_util.h b/src/fd_util.h index dd4df7a13..c8a17c7ef 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. @@ -146,4 +150,8 @@ inotify_init_cloexec(void); int close_socket(int fd); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif diff --git a/src/filter/null_filter_plugin.c b/src/filter/null_filter_plugin.c index e7c998827..7728c55bf 100644 --- a/src/filter/null_filter_plugin.c +++ b/src/filter/null_filter_plugin.c @@ -29,6 +29,7 @@ #include "filter_internal.h" #include "filter_registry.h" +#include <glib.h> #include <assert.h> struct null_filter { diff --git a/src/filter_plugin.h b/src/filter_plugin.h index 58e34dfb2..d45faee1f 100644 --- a/src/filter_plugin.h +++ b/src/filter_plugin.h @@ -26,7 +26,7 @@ #ifndef MPD_FILTER_PLUGIN_H #define MPD_FILTER_PLUGIN_H -#include <glib.h> +#include "gerror.h" #include <stdbool.h> #include <stddef.h> @@ -32,6 +32,9 @@ */ #if GCC_CHECK_VERSION(3,0) +# define gcc_const __attribute__((const)) +# define gcc_pure __attribute__((pure)) +# define gcc_malloc __attribute__((malloc)) # define gcc_must_check __attribute__ ((warn_unused_result)) # define gcc_packed __attribute__ ((packed)) /* these are very useful for type checking */ @@ -41,11 +44,21 @@ # define gcc_fprintf__ __attribute__ ((format(printf,4,5))) # define gcc_scanf __attribute__ ((format(scanf,1,2))) # define gcc_used __attribute__ ((used)) +# define gcc_unused __attribute__((unused)) +# define gcc_warn_unused_result __attribute__((warn_unused_result)) /* # define inline inline __attribute__ ((always_inline)) */ # define gcc_noinline __attribute__ ((noinline)) # define gcc_nonnull(...) __attribute__((nonnull(__VA_ARGS__))) # define gcc_nonnull_all __attribute__((nonnull)) + +# define gcc_likely(x) __builtin_expect (!!(x), 1) +# define gcc_unlikely(x) __builtin_expect (!!(x), 0) + #else +# define gcc_unused +# define gcc_const +# define gcc_pure +# define gcc_malloc # define gcc_must_check # define gcc_packed # define gcc_printf @@ -54,10 +67,29 @@ # 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 + +#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 +#endif + #endif #endif /* MPD_GCC_H */ diff --git a/src/decoder_print.h b/src/gerror.h index 31713d5d8..fe4c54da9 100644 --- a/src/decoder_print.h +++ b/src/gerror.h @@ -17,12 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DECODER_PRINT_H -#define MPD_DECODER_PRINT_H +#ifndef MPD_GERROR_H +#define MPD_GERROR_H -struct client; - -void -decoder_list_print(struct client *client); +typedef struct _GError GError; #endif diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c index 5ee3f200b..e130230a7 100644 --- a/src/input/file_input_plugin.c +++ b/src/input/file_input_plugin.c @@ -23,6 +23,7 @@ #include "input_plugin.h" #include "fd_util.h" #include "open.h" +#include "io_error.h" #include <sys/stat.h> #include <unistd.h> @@ -39,12 +40,6 @@ struct file_input_stream { 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, @@ -60,7 +55,7 @@ input_file_open(const char *filename, fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0); if (fd < 0) { if (errno != ENOENT && errno != ENOTDIR) - g_set_error(error_r, file_quark(), errno, + g_set_error(error_r, errno_quark(), errno, "Failed to open \"%s\": %s", filename, g_strerror(errno)); return NULL; @@ -68,7 +63,7 @@ input_file_open(const char *filename, ret = fstat(fd, &st); if (ret < 0) { - g_set_error(error_r, file_quark(), errno, + g_set_error(error_r, errno_quark(), errno, "Failed to stat \"%s\": %s", filename, g_strerror(errno)); close(fd); @@ -76,7 +71,7 @@ input_file_open(const char *filename, } if (!S_ISREG(st.st_mode)) { - g_set_error(error_r, file_quark(), 0, + g_set_error(error_r, errno_quark(), 0, "Not a regular file: %s", filename); close(fd); return NULL; @@ -107,7 +102,7 @@ input_file_seek(struct input_stream *is, goffset offset, int whence, offset = (goffset)lseek(fis->fd, (off_t)offset, whence); if (offset < 0) { - g_set_error(error_r, file_quark(), errno, + g_set_error(error_r, errno_quark(), errno, "Failed to seek: %s", g_strerror(errno)); return false; } @@ -125,7 +120,7 @@ input_file_read(struct input_stream *is, void *ptr, size_t size, nbytes = read(fis->fd, ptr, size); if (nbytes < 0) { - g_set_error(error_r, file_quark(), errno, + g_set_error(error_r, errno_quark(), errno, "Failed to read: %s", g_strerror(errno)); return 0; } diff --git a/src/input_init.h b/src/input_init.h index ad92cda08..1a73e5ef9 100644 --- a/src/input_init.h +++ b/src/input_init.h @@ -20,9 +20,8 @@ #ifndef MPD_INPUT_INIT_H #define MPD_INPUT_INIT_H -#include "check.h" +#include "gerror.h" -#include <glib.h> #include <stdbool.h> /** diff --git a/src/io_error.h b/src/io_error.h new file mode 100644 index 000000000..c89748360 --- /dev/null +++ b/src/io_error.h @@ -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. + */ + +#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)); +} + +#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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,15 @@ */ #include "config.h" -#include "ls.h" +#include "ls.hxx" + +extern "C" { #include "uri.h" -#include "client.h" +} + +#include "Client.hxx" + +#include <glib.h> #include <assert.h> #include <string.h> @@ -72,7 +78,7 @@ void print_supported_uri_schemes_to_fp(FILE *fp) fprintf(fp,"\n"); } -void print_supported_uri_schemes(struct client *client) +void print_supported_uri_schemes(Client *client) { const char **prefixes = remoteUrlPrefixes; @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_LS_H -#define MPD_LS_H +#ifndef MPD_LS_HXX +#define MPD_LS_HXX -#include <stdbool.h> #include <stdio.h> -struct client; +class Client; /** * Checks whether the scheme of the specified URI is supported by MPD. @@ -36,7 +35,7 @@ bool uri_supported_scheme(const char *url); * Send a list of supported URI schemes to the client. This is the * response to the "urlhandlers" command. */ -void print_supported_uri_schemes(struct client *client); +void print_supported_uri_schemes(Client *client); /** * Send a list of supported URI schemes to a file pointer. diff --git a/src/mixer_control.h b/src/mixer_control.h index 6c3468aca..307298e47 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> 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/output/httpd_client.h b/src/output/httpd_client.h index 739163f42..f0df829db 100644 --- a/src/output/httpd_client.h +++ b/src/output/httpd_client.h @@ -20,9 +20,8 @@ #ifndef MPD_OUTPUT_HTTPD_CLIENT_H #define MPD_OUTPUT_HTTPD_CLIENT_H -#include <glib.h> - #include <stdbool.h> +#include <stddef.h> struct httpd_client; struct httpd_output; diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h index 02a51f27b..b285b5e4d 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; diff --git a/src/output/shout_output_plugin.c b/src/output/shout_output_plugin.c index 56456a0ea..8f9df2ccf 100644 --- a/src/output/shout_output_plugin.c +++ b/src/output/shout_output_plugin.c @@ -105,15 +105,10 @@ static void free_shout_data(struct shout_data *sd) } \ } -static struct audio_output * -my_shout_init_driver(const struct config_param *param, - GError **error) +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,11 +120,6 @@ 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; @@ -141,7 +131,7 @@ my_shout_init_driver(const struct config_param *param, if (port == 0) { g_set_error(error, shout_output_quark(), 0, "shout port must be configured"); - goto failure; + return false; } check_block_param("password"); @@ -164,21 +154,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 +177,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 +189,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 +210,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 +222,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 +241,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 +252,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 +291,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_all.c b/src/output_all.c index f56cd04ee..b2ef1561f 100644 --- a/src/output_all.c +++ b/src/output_all.c @@ -19,6 +19,7 @@ #include "config.h" #include "output_all.h" +#include "output_error.h" #include "output_internal.h" #include "output_control.h" #include "chunk.h" @@ -270,7 +271,7 @@ audio_output_all_update(void) } bool -audio_output_all_play(struct music_chunk *chunk) +audio_output_all_play(struct music_chunk *chunk, GError **error_r) { bool ret; unsigned int i; @@ -281,8 +282,12 @@ audio_output_all_play(struct music_chunk *chunk) assert(music_chunk_check_format(chunk, &input_audio_format)); ret = audio_output_all_update(); - if (!ret) + if (!ret) { + /* TODO: obtain real error */ + g_set_error(error_r, output_quark(), 0, + "Failed to open audio output"); return false; + } music_pipe_push(g_mp, chunk); @@ -294,7 +299,8 @@ audio_output_all_play(struct music_chunk *chunk) bool audio_output_all_open(const struct audio_format *audio_format, - struct music_buffer *buffer) + struct music_buffer *buffer, + GError **error_r) { bool ret = false, enabled = false; unsigned int i; @@ -334,7 +340,12 @@ audio_output_all_open(const struct audio_format *audio_format, } if (!enabled) - g_warning("All audio outputs are disabled"); + g_set_error(error_r, output_quark(), 0, + "All audio outputs are disabled"); + else if (!ret) + /* TODO: obtain real error */ + g_set_error(error_r, output_quark(), 0, + "Failed to open audio output"); if (!ret) /* close all devices if there was an error */ diff --git a/src/output_all.h b/src/output_all.h index 4eeb94f13..00864c9ba 100644 --- a/src/output_all.h +++ b/src/output_all.h @@ -26,6 +26,8 @@ #ifndef OUTPUT_ALL_H #define OUTPUT_ALL_H +#include "gerror.h" + #include <stdbool.h> #include <stddef.h> @@ -84,7 +86,8 @@ audio_output_all_enable_disable(void); */ bool audio_output_all_open(const struct audio_format *audio_format, - struct music_buffer *buffer); + struct music_buffer *buffer, + GError **error_r); /** * Closes all audio outputs. @@ -108,7 +111,7 @@ audio_output_all_release(void); * (all closed then) */ bool -audio_output_all_play(struct music_chunk *chunk); +audio_output_all_play(struct music_chunk *chunk, GError **error_r); /** * Checks if the output devices have drained their music pipe, and diff --git a/src/output_error.h b/src/output_error.h new file mode 100644 index 000000000..ccc784f89 --- /dev/null +++ b/src/output_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_OUTPUT_ERROR_H +#define MPD_OUTPUT_ERROR_H + +#include <glib.h> + +/** + * Quark for GError.domain. + */ +G_GNUC_CONST +static inline GQuark +output_quark(void) +{ + return g_quark_from_static_string("output"); +} + +#endif diff --git a/src/output_init.c b/src/output_init.c index c3b808e94..a6d191920 100644 --- a/src/output_init.c +++ b/src/output_init.c @@ -165,6 +165,7 @@ ao_base_init(struct audio_output *ao, } ao->plugin = plugin; + ao->tags = config_get_block_bool(param, "tags", true); ao->always_on = config_get_block_bool(param, "always_on", false); ao->enabled = config_get_block_bool(param, "enabled", true); ao->really_enabled = false; diff --git a/src/output_internal.h b/src/output_internal.h index 9d975d789..1a3e8aa1f 100644 --- a/src/output_internal.h +++ b/src/output_internal.h @@ -73,6 +73,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? */ diff --git a/src/output_plugin.h b/src/output_plugin.h index 209ca6221..a47296566 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,7 @@ ao_plugin_test_default_device(const struct audio_output_plugin *plugin) : false; } -G_GNUC_MALLOC +gcc_malloc struct audio_output * ao_plugin_init(const struct audio_output_plugin *plugin, const struct config_param *param, @@ -187,7 +188,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); diff --git a/src/output_thread.c b/src/output_thread.c index 4eef2ccdd..cd1a8a878 100644 --- a/src/output_thread.c +++ b/src/output_thread.c @@ -435,7 +435,7 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) assert(ao != NULL); assert(ao->filter != NULL); - if (chunk->tag != NULL) { + if (ao->tags && gcc_unlikely(chunk->tag != NULL)) { g_mutex_unlock(ao->mutex); ao_plugin_send_tag(ao, chunk->tag); g_mutex_lock(ao->mutex); diff --git a/src/pcm_channels.c b/src/pcm_channels.c index ec2bd69a5..9d166a437 100644 --- a/src/pcm_channels.c +++ b/src/pcm_channels.c @@ -244,3 +244,74 @@ pcm_convert_channels_32(struct pcm_buffer *buffer, return dest; } + +static void +pcm_convert_channels_float_1_to_2(float *dest, const float *src, + const float *src_end) +{ + pcm_convert_channels_24_1_to_2((int32_t *)dest, + (const int32_t *)src, + (const int32_t *)src_end); +} + +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 = pcm_buffer_get(buffer, dest_size); + const float *src_end = pcm_end_pointer(src, src_size); + + if (src_channels == 1 && dest_channels == 2) + pcm_convert_channels_float_1_to_2(dest, src, src_end); + else if (src_channels == 2 && dest_channels == 1) + pcm_convert_channels_float_2_to_1(dest, src, src_end); + else if (dest_channels == 2) + pcm_convert_channels_float_n_to_2(dest, src_channels, src, + src_end); + else + return NULL; + + return dest; +} diff --git a/src/pcm_channels.h b/src/pcm_channels.h index 1e4a0991f..6da00316d 100644 --- a/src/pcm_channels.h +++ b/src/pcm_channels.h @@ -77,4 +77,21 @@ pcm_convert_channels_32(struct pcm_buffer *buffer, unsigned src_channels, const int32_t *src, size_t src_size, size_t *dest_size_r); +/** + * Changes the number of channels in 32 bit float PCM data. + * + * @param buffer the destination pcm_buffer object + * @param dest_channels the number of channels requested + * @param src_channels the number of channels in the source buffer + * @param src the source PCM buffer + * @param src_size the number of bytes in #src + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const float * +pcm_convert_channels_float(struct pcm_buffer *buffer, + unsigned dest_channels, + unsigned src_channels, const float *src, + size_t src_size, size_t *dest_size_r); + #endif diff --git a/src/pcm_convert.c b/src/pcm_convert.c index 63f9a1b98..32425143a 100644 --- a/src/pcm_convert.c +++ b/src/pcm_convert.c @@ -61,55 +61,6 @@ pcm_convert_reset(struct pcm_convert_state *state) 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, @@ -273,19 +224,6 @@ pcm_convert_float(struct pcm_convert_state *state, 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, @@ -298,6 +236,23 @@ pcm_convert_float(struct pcm_convert_state *state, return NULL; } + /* convert channels */ + + if (src_format->channels != dest_format->channels) { + buffer = pcm_convert_channels_float(&state->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 */ diff --git a/src/pcm_mix.h b/src/pcm_mix.h index 0e58d01ee..0cf557680 100644 --- a/src/pcm_mix.h +++ b/src/pcm_mix.h @@ -21,6 +21,7 @@ #define PCM_MIX_H #include "audio_format.h" +#include "gcc.h" #include <stdbool.h> #include <stddef.h> @@ -41,7 +42,7 @@ * * @return true on success, false if the format is not supported */ -G_GNUC_WARN_UNUSED_RESULT +gcc_warn_unused_result bool pcm_mix(void *buffer1, const void *buffer2, size_t size, enum sample_format format, float portion1); diff --git a/src/pcm_volume.h b/src/pcm_volume.h index 64e3c7641..4a4a4e45a 100644 --- a/src/pcm_volume.h +++ b/src/pcm_volume.h @@ -25,6 +25,7 @@ #include <stdint.h> #include <stdbool.h> +#include <stddef.h> enum { /** this value means "100% volume" */ diff --git a/src/pipe.h b/src/pipe.h index 84b9869e0..6b5bbf0c7 100644 --- a/src/pipe.h +++ b/src/pipe.h @@ -20,7 +20,8 @@ #ifndef MPD_PIPE_H #define MPD_PIPE_H -#include <glib.h> +#include "gcc.h" + #include <stdbool.h> #ifndef NDEBUG @@ -39,7 +40,7 @@ struct music_pipe; /** * Creates a new #music_pipe object. It is empty. */ -G_GNUC_MALLOC +gcc_malloc struct music_pipe * music_pipe_new(void); @@ -72,7 +73,7 @@ music_pipe_contains(const struct music_pipe *mp, * Returns the first #music_chunk from the pipe. Returns NULL if the * pipe is empty. */ -G_GNUC_PURE +gcc_pure const struct music_chunk * music_pipe_peek(const struct music_pipe *mp); @@ -99,11 +100,11 @@ music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk); /** * Returns the number of chunks currently in this pipe. */ -G_GNUC_PURE +gcc_pure unsigned music_pipe_size(const struct music_pipe *mp); -G_GNUC_PURE +gcc_pure static inline bool music_pipe_empty(const struct music_pipe *mp) { diff --git a/src/player_control.c b/src/player_control.c index 90f616d77..5a16520c2 100644 --- a/src/player_control.c +++ b/src/player_control.c @@ -26,7 +26,7 @@ #include "song.h" #include "idle.h" #include "pcm_volume.h" -#include "main.h" +#include "Main.hxx" #include <assert.h> #include <stdio.h> @@ -47,7 +47,7 @@ pc_new(unsigned buffer_chunks, unsigned int buffered_before_play) pc->cond = g_cond_new(); pc->command = PLAYER_COMMAND_NONE; - pc->error = PLAYER_ERROR_NOERROR; + pc->error_type = PLAYER_ERROR_NONE; pc->state = PLAYER_STATE_STOP; pc->cross_fade_seconds = 0; pc->mixramp_db = 0; @@ -59,6 +59,9 @@ pc_new(unsigned buffer_chunks, unsigned int buffered_before_play) void pc_free(struct player_control *pc) { + if (pc->next_song != NULL) + song_free(pc->next_song); + g_cond_free(pc->cond); g_mutex_free(pc->mutex); g_free(pc); @@ -76,15 +79,6 @@ player_wait_decoder(struct player_control *pc, struct decoder_control *dc) 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) { @@ -236,70 +230,43 @@ pc_get_status(struct player_control *pc, struct player_status *status) player_unlock(pc); } -enum player_state -pc_get_state(struct player_control *pc) +void +pc_set_error(struct player_control *pc, enum player_error type, + GError *error) { - return pc->state; + assert(pc != NULL); + assert(type != PLAYER_ERROR_NONE); + assert(error != NULL); + + if (pc->error_type != PLAYER_ERROR_NONE) + g_error_free(pc->error); + + pc->error_type = type; + pc->error = error; } 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; -} + if (pc->error_type != PLAYER_ERROR_NONE) { + pc->error_type = PLAYER_ERROR_NONE; + g_error_free(pc->error); + } -static char * -pc_errored_song_uri(struct player_control *pc) -{ - return song_get_uri(pc->errored_song); + player_unlock(pc); } 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; + player_lock(pc); + char *message = pc->error_type != PLAYER_ERROR_NONE + ? g_strdup(pc->error->message) + : NULL; + player_unlock(pc); + return message; } static void @@ -328,6 +295,10 @@ pc_seek(struct player_control *pc, struct song *song, float seek_time) assert(song != NULL); player_lock(pc); + + if (pc->next_song != NULL) + song_free(pc->next_song); + pc->next_song = song; pc->seek_where = seek_time; player_command_locked(pc, PLAYER_COMMAND_SEEK); @@ -356,12 +327,6 @@ pc_set_cross_fade(struct player_control *pc, float 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) { @@ -370,12 +335,6 @@ pc_set_mixramp_db(struct player_control *pc, float 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) { @@ -383,9 +342,3 @@ pc_set_mixramp_delay(struct player_control *pc, float 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 index a77d31ec5..3b536b8ba 100644 --- a/src/player_control.h +++ b/src/player_control.h @@ -66,12 +66,17 @@ enum player_command { }; enum player_error { - PLAYER_ERROR_NOERROR = 0, - PLAYER_ERROR_FILE, - PLAYER_ERROR_AUDIO, - PLAYER_ERROR_SYSTEM, - PLAYER_ERROR_UNKTYPE, - PLAYER_ERROR_FILENOTFOUND, + 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 { @@ -103,13 +108,30 @@ struct player_control { enum player_command command; enum player_state state; - enum player_error error; + + 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; - const struct song *errored_song; + double seek_where; float cross_fade_seconds; float mixramp_db; @@ -194,14 +216,10 @@ player_lock_signal(struct player_control *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. + * @param song the song to be queued; the given instance will be owned + * and freed by the player */ void -pc_song_deleted(struct player_control *pc, const struct song *song); - -void pc_play(struct player_control *pc, struct song *song); /** @@ -228,8 +246,24 @@ 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); +static inline enum player_state +pc_get_state(struct player_control *pc) +{ + return pc->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 +pc_set_error(struct player_control *pc, enum player_error type, + GError *error); void pc_clear_error(struct player_control *pc); @@ -242,8 +276,11 @@ pc_clear_error(struct player_control *pc); char * pc_get_error_message(struct player_control *pc); -enum player_error -pc_get_error(struct player_control *pc); +static inline enum player_error +pc_get_error_type(struct player_control *pc) +{ + return pc->error_type; +} void pc_stop(struct player_control *pc); @@ -251,12 +288,18 @@ pc_stop(struct player_control *pc); void pc_update_audio(struct player_control *pc); +/** + * @param song the song to be queued; the given instance will be owned + * and freed by the player + */ void pc_enqueue_song(struct player_control *pc, 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) */ @@ -272,16 +315,25 @@ 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); +static inline float +pc_get_mixramp_db(const struct player_control *pc) +{ + return pc->mixramp_db; +} void pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds); -float -pc_get_mixramp_delay(const struct player_control *pc); +static inline float +pc_get_mixramp_delay(const struct player_control *pc) +{ + return pc->mixramp_delay_seconds; +} -double -pc_get_total_play_time(const struct player_control *pc); +static inline double +pc_get_total_play_time(const struct player_control *pc) +{ + return pc->total_play_time; +} #endif diff --git a/src/playlist.c b/src/playlist.c index dc6d8c340..2532d9d46 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -19,13 +19,9 @@ #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> @@ -78,14 +74,15 @@ 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); + struct song *song = + song_dup_detached(queue_get_order(&playlist->queue, order)); + uri = song_get_uri(song); g_debug("queue song %i:\"%s\"", playlist->queued, uri); g_free(uri); @@ -186,13 +183,13 @@ 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); + struct song *song = + song_dup_detached(queue_get_order(&playlist->queue, orderNum)); uri = song_get_uri(song); g_debug("play %i:\"%s\"", orderNum, uri); @@ -257,14 +254,14 @@ playlist_resume_playback(struct playlist *playlist, struct player_control *pc) assert(playlist->playing); assert(pc_get_state(pc) == PLAYER_STATE_STOP); - error = pc_get_error(pc); - if (error == PLAYER_ERROR_NOERROR) + error = pc_get_error_type(pc); + if (error == PLAYER_ERROR_NONE) 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 || + if ((playlist->stop_on_error && error != PLAYER_ERROR_NONE) || + error == PLAYER_ERROR_OUTPUT || playlist->error_count >= queue_length(&playlist->queue)) /* too many errors, or critical error: stop playback */ diff --git a/src/playlist_control.c b/src/playlist_control.c index 0dea7676a..57cc428fe 100644 --- a/src/playlist_control.c +++ b/src/playlist_control.c @@ -25,6 +25,7 @@ #include "config.h" #include "playlist_internal.h" #include "player_control.h" +#include "song.h" #include "idle.h" #include <glib.h> @@ -239,7 +240,9 @@ playlist_seek_song(struct playlist *playlist, struct player_control *pc, queued = NULL; } - success = pc_seek(pc, queue_get_order(&playlist->queue, i), seek_time); + struct song *the_song = + song_dup_detached(queue_get_order(&playlist->queue, i)); + success = pc_seek(pc, the_song, seek_time); if (!success) { playlist_update_queued_song(playlist, pc, queued); diff --git a/src/playlist_global.c b/src/playlist_global.c index 650b88bb8..e66febce0 100644 --- a/src/playlist_global.c +++ b/src/playlist_global.c @@ -24,9 +24,8 @@ #include "config.h" #include "playlist.h" -#include "playlist_state.h" #include "event_pipe.h" -#include "main.h" +#include "Main.hxx" struct playlist g_playlist; 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.c b/src/protocol/ArgParser.cxx index b21d4c53c..0ab19f7d4 100644 --- a/src/protocol/argparser.c +++ b/src/protocol/ArgParser.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,14 @@ */ #include "config.h" -#include "argparser.h" -#include "result.h" +#include "ArgParser.hxx" +#include "Result.hxx" #include <glib.h> #include <stdlib.h> bool -check_uint32(struct client *client, uint32_t *dst, const char *s) +check_uint32(Client *client, uint32_t *dst, const char *s) { char *test; @@ -39,7 +39,7 @@ check_uint32(struct client *client, uint32_t *dst, const char *s) } bool -check_int(struct client *client, int *value_r, const char *s) +check_int(Client *client, int *value_r, const char *s) { char *test; long value; @@ -64,7 +64,7 @@ check_int(struct client *client, int *value_r, const char *s) } bool -check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, +check_range(Client *client, unsigned *value_r1, unsigned *value_r2, const char *s) { char *test, *test2; @@ -134,7 +134,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, } bool -check_unsigned(struct client *client, unsigned *value_r, const char *s) +check_unsigned(Client *client, unsigned *value_r, const char *s) { unsigned long value; char *endptr; @@ -157,7 +157,7 @@ check_unsigned(struct client *client, unsigned *value_r, const char *s) } bool -check_bool(struct client *client, bool *value_r, const char *s) +check_bool(Client *client, bool *value_r, const char *s) { long value; char *endptr; @@ -174,7 +174,7 @@ check_bool(struct client *client, bool *value_r, const char *s) } bool -check_float(struct client *client, float *value_r, const char *s) +check_float(Client *client, float *value_r, const char *s) { float value; char *endptr; diff --git a/src/protocol/argparser.h b/src/protocol/ArgParser.hxx index e88aea478..b6feb3e67 100644 --- a/src/protocol/argparser.h +++ b/src/protocol/ArgParser.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,33 +17,33 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PROTOCOL_ARGPARSER_H -#define MPD_PROTOCOL_ARGPARSER_H +#ifndef MPD_PROTOCOL_ARGPARSER_HXX +#define MPD_PROTOCOL_ARGPARSER_HXX #include "check.h" #include <stdbool.h> #include <stdint.h> -struct client; +class Client; bool -check_uint32(struct client *client, uint32_t *dst, const char *s); +check_uint32(Client *client, uint32_t *dst, const char *s); bool -check_int(struct client *client, int *value_r, const char *s); +check_int(Client *client, int *value_r, const char *s); bool -check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, +check_range(Client *client, unsigned *value_r1, unsigned *value_r2, const char *s); bool -check_unsigned(struct client *client, unsigned *value_r, const char *s); +check_unsigned(Client *client, unsigned *value_r, const char *s); bool -check_bool(struct client *client, bool *value_r, const char *s); +check_bool(Client *client, bool *value_r, const char *s); bool -check_float(struct client *client, float *value_r, const char *s); +check_float(Client *client, float *value_r, const char *s); #endif diff --git a/src/protocol/result.c b/src/protocol/Result.cxx index 30cd0a266..e10a731cc 100644 --- a/src/protocol/result.c +++ b/src/protocol/Result.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,8 @@ */ #include "config.h" -#include "result.h" -#include "client.h" +#include "Result.hxx" +#include "Client.hxx" #include <assert.h> @@ -27,13 +27,13 @@ const char *current_command; int command_list_num; void -command_success(struct client *client) +command_success(Client *client) { client_puts(client, "OK\n"); } void -command_error_v(struct client *client, enum ack error, +command_error_v(Client *client, enum ack error, const char *fmt, va_list args) { assert(client != NULL); @@ -48,7 +48,7 @@ command_error_v(struct client *client, enum ack error, } void -command_error(struct client *client, enum ack error, const char *fmt, ...) +command_error(Client *client, enum ack error, const char *fmt, ...) { va_list args; va_start(args, fmt); diff --git a/src/protocol/result.h b/src/protocol/Result.hxx index 8b9e44bfd..99d9a2fa0 100644 --- a/src/protocol/result.h +++ b/src/protocol/Result.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,28 +17,27 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PROTOCOL_RESULT_H -#define MPD_PROTOCOL_RESULT_H +#ifndef MPD_PROTOCOL_RESULT_HXX +#define MPD_PROTOCOL_RESULT_HXX #include "check.h" +#include "gcc.h" #include "ack.h" -#include <glib.h> - -struct client; +class Client; extern const char *current_command; extern int command_list_num; void -command_success(struct client *client); +command_success(Client *client); void -command_error_v(struct client *client, enum ack error, +command_error_v(Client *client, enum ack error, const char *fmt, va_list args); -G_GNUC_PRINTF(3, 4) +gcc_fprintf_ void -command_error(struct client *client, enum ack error, const char *fmt, ...); +command_error(Client *client, enum ack error, const char *fmt, ...); #endif diff --git a/src/queue.c b/src/queue.c index 4fe564a35..098cbcce9 100644 --- a/src/queue.c +++ b/src/queue.c @@ -103,7 +103,7 @@ queue_append(struct queue *queue, struct song *song, uint8_t priority) assert(!queue_is_full(queue)); queue->items[queue->length] = (struct queue_item){ - .song = song, + .song = song_dup_detached(song), .id = id, .version = queue->version, .priority = priority, @@ -256,8 +256,8 @@ queue_delete(struct queue *queue, unsigned position) assert(position < queue->length); song = queue_get(queue, position); - if (!song_in_database(song)) - song_free(song); + assert(!song_in_database(song) || song_is_detached(song)); + song_free(song); id = queue_position_to_id(queue, position); order = queue_position_to_order(queue, position); @@ -291,8 +291,9 @@ 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); + assert(!song_in_database(item->song) || + song_is_detached(item->song)); + song_free(item->song); queue->id_to_position[item->id] = -1; } diff --git a/src/resolver.h b/src/resolver.h index e5ad06754..666b6d5b2 100644 --- a/src/resolver.h +++ b/src/resolver.h @@ -20,12 +20,14 @@ #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) { @@ -42,7 +44,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); diff --git a/src/server_socket.h b/src/server_socket.h index 7caa4bbf2..f7e9aa4cf 100644 --- a/src/server_socket.h +++ b/src/server_socket.h @@ -20,9 +20,10 @@ #ifndef MPD_SERVER_SOCKET_H #define MPD_SERVER_SOCKET_H -#include <stdbool.h> +#include "gerror.h" -#include <glib.h> +#include <stdbool.h> +#include <stddef.h> struct sockaddr; diff --git a/src/sig_handlers.c b/src/sig_handlers.c index b23f9e778..eabca1997 100644 --- a/src/sig_handlers.c +++ b/src/sig_handlers.c @@ -23,7 +23,7 @@ #ifndef WIN32 #include "log.h" -#include "main.h" +#include "Main.hxx" #include "event_pipe.h" #include "mpd_error.h" diff --git a/src/socket_util.c b/src/socket_util.c index a06a0cbd5..ee8bf7e1a 100644 --- a/src/socket_util.c +++ b/src/socket_util.c @@ -21,6 +21,8 @@ #include "socket_util.h" #include "fd_util.h" +#include <glib.h> + #include <errno.h> #include <unistd.h> diff --git a/src/socket_util.h b/src/socket_util.h index 93bd27362..4cf845d8e 100644 --- a/src/socket_util.h +++ b/src/socket_util.h @@ -26,7 +26,9 @@ #ifndef SOCKET_UTIL_H #define SOCKET_UTIL_H -#include <glib.h> +#include "gerror.h" + +#include <stddef.h> struct sockaddr; diff --git a/src/song.h b/src/song.h index 8b97d45d0..119d89ccd 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,21 @@ struct song { char uri[sizeof(int)]; }; +/** + * A dummy #directory instance that is used for "detached" song + * copies. + */ +extern struct Directory detached_root; + +G_BEGIN_DECLS + /** 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, struct Directory *parent); /** * allocate a new song structure with a local file name and attempt to @@ -72,7 +82,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, struct Directory *parent); /** * Replaces the URI of a song object. The given song object is @@ -83,9 +93,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 +158,6 @@ 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] == '/'; -} +G_END_DECLS #endif 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/string_util.h b/src/string_util.h index dc80a46ef..c1d316f0c 100644 --- a/src/string_util.h +++ b/src/string_util.h @@ -20,7 +20,7 @@ #ifndef MPD_STRING_UTIL_H #define MPD_STRING_UTIL_H -#include <glib.h> +#include "gcc.h" #include <stdbool.h> @@ -28,10 +28,13 @@ * 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 +43,7 @@ deconst_string(const char *p) }; return u.out; +#endif } /** @@ -49,14 +53,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) { 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 @@ -168,9 +168,9 @@ static void tag_delete_item(struct tag *tag, unsigned idx) assert(idx < tag->num_items); tag->num_items--; - g_mutex_lock(tag_pool_lock); + g_static_mutex_lock(&tag_pool_lock); tag_pool_put_item(tag->items[idx]); - g_mutex_unlock(tag_pool_lock); + g_static_mutex_unlock(&tag_pool_lock); if (tag->num_items - idx > 0) { memmove(tag->items + idx, tag->items + idx + 1, @@ -202,10 +202,10 @@ void tag_free(struct tag *tag) assert(tag != NULL); - g_mutex_lock(tag_pool_lock); + g_static_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); + g_static_mutex_unlock(&tag_pool_lock); if (tag->items == bulk.items) { #ifndef NDEBUG @@ -231,10 +231,10 @@ struct tag *tag_dup(const struct tag *tag) ret->num_items = tag->num_items; ret->items = ret->num_items > 0 ? g_malloc(items_size(tag)) : NULL; - g_mutex_lock(tag_pool_lock); + g_static_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); + g_static_mutex_unlock(&tag_pool_lock); return ret; } @@ -255,7 +255,7 @@ tag_merge(const struct tag *base, const struct tag *add) 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); + g_static_mutex_lock(&tag_pool_lock); /* copy all items from "add" */ @@ -270,7 +270,7 @@ tag_merge(const struct tag *base, const struct tag *add) if (!tag_has_type(add, base->items[i]->type)) ret->items[n++] = tag_pool_dup_item(base->items[i]); - g_mutex_unlock(tag_pool_lock); + g_static_mutex_unlock(&tag_pool_lock); assert(n <= ret->num_items); @@ -502,9 +502,9 @@ tag_add_item_internal(struct tag *tag, enum tag_type type, items_size(tag) - sizeof(struct tag_item *)); } - g_mutex_lock(tag_pool_lock); + g_static_mutex_lock(&tag_pool_lock); tag->items[i] = tag_pool_get_item(type, value, len); - g_mutex_unlock(tag_pool_lock); + g_static_mutex_unlock(&tag_pool_lock); g_free(p); } 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_pool.c b/src/tag_pool.c index eabf3e369..2f9b39486 100644 --- a/src/tag_pool.c +++ b/src/tag_pool.c @@ -22,7 +22,17 @@ #include <assert.h> -GMutex *tag_pool_lock = NULL; +#if GCC_CHECK_VERSION(4, 2) +/* workaround for a warning caused by G_STATIC_MUTEX_INIT */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +GStaticMutex tag_pool_lock = G_STATIC_MUTEX_INIT; + +#if GCC_CHECK_VERSION(4, 2) +#pragma GCC diagnostic pop +#endif #define NUM_SLOTS 4096 @@ -81,19 +91,6 @@ static struct slot *slot_alloc(struct slot *next, return slot; } -void tag_pool_init(void) -{ - g_assert(tag_pool_lock == NULL); - tag_pool_lock = g_mutex_new(); -} - -void tag_pool_deinit(void) -{ - g_assert(tag_pool_lock != NULL); - g_mutex_free(tag_pool_lock); - tag_pool_lock = NULL; -} - struct tag_item * tag_pool_get_item(enum tag_type type, const char *value, size_t length) { diff --git a/src/tag_pool.h b/src/tag_pool.h index a96c00d85..a717f704d 100644 --- a/src/tag_pool.h +++ b/src/tag_pool.h @@ -24,14 +24,10 @@ #include <glib.h> -extern GMutex *tag_pool_lock; +extern GStaticMutex tag_pool_lock; struct tag_item; -void tag_pool_init(void); - -void tag_pool_deinit(void); - struct tag_item * tag_pool_get_item(enum tag_type type, const char *value, size_t length); diff --git a/src/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/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 @@ -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/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/list.h b/src/util/list.h index fdab47675..28c70954f 100644 --- a/src/util/list.h +++ b/src/util/list.h @@ -47,8 +47,8 @@ * 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 +82,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 +164,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.h b/src/utils.h index f8d6657f2..ba7307ded 100644 --- a/src/utils.h +++ b/src/utils.h @@ -20,7 +20,8 @@ #ifndef MPD_UTILS_H #define MPD_UTILS_H -#include <glib.h> +#include "gerror.h" + #include <stdbool.h> #ifndef assert_static diff --git a/src/zeroconf-avahi.c b/src/zeroconf-avahi.c index f2cc5359b..1f93ddb72 100644 --- a/src/zeroconf-avahi.c +++ b/src/zeroconf-avahi.c @@ -19,7 +19,7 @@ #include "config.h" #include "zeroconf-internal.h" -#include "listen.h" +#include "Listen.hxx" #include "mpd_error.h" #include <glib.h> diff --git a/src/zeroconf.c b/src/zeroconf.c index 4a399e4a2..a4611b67e 100644 --- a/src/zeroconf.c +++ b/src/zeroconf.c @@ -21,7 +21,7 @@ #include "zeroconf.h" #include "zeroconf-internal.h" #include "conf.h" -#include "listen.h" +#include "Listen.hxx" #include <glib.h> diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx new file mode 100644 index 000000000..0678d7076 --- /dev/null +++ b/test/DumpDatabase.cxx @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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" + +extern "C" { +#include "conf.h" +#include "tag.h" +} + +#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 char *const config_path = 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 (!config_read_file(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); + struct config_param *param = config_new_param("database", path->line); + if (path != nullptr) + config_add_block_param(param, "path", path->value, path->line); + + Database *db = plugin->create(param, &error); + + config_param_free(param); + + 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/dump_playlist.c b/test/DumpPlaylist.cxx index 84ac69045..427052b6c 100644 --- a/test/dump_playlist.c +++ b/test/DumpPlaylist.cxx @@ -18,23 +18,29 @@ */ #include "config.h" +#include "TagSave.hxx" +#include "song.h" +#include "Directory.hxx" + +extern "C" { #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> +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) @@ -157,7 +163,6 @@ int main(int argc, char **argv) /* initialize MPD */ - tag_pool_init(); config_global_init(); success = config_read_file(argv[1], &error); if (!success) { @@ -249,7 +254,6 @@ int main(int argc, char **argv) input_stream_global_finish(); io_thread_deinit(); config_global_finish(); - tag_pool_deinit(); return 0; } 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/run_inotify.c b/test/RunInotify.cxx index 3e7c70dba..2b77130d8 100644 --- a/test/run_inotify.c +++ b/test/RunInotify.cxx @@ -18,9 +18,10 @@ */ #include "config.h" -#include "inotify_source.h" +#include "InotifySource.hxx" + +#include <glib.h> -#include <stdbool.h> #include <sys/inotify.h> #include <signal.h> diff --git a/test/run_input.c b/test/RunInput.cxx index 676e4e618..52b53db5a 100644 --- a/test/run_input.c +++ b/test/RunInput.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,17 +18,20 @@ */ #include "config.h" +#include "TagSave.hxx" +#include "stdbin.h" + +extern "C" { #include "io_thread.h" #include "input_init.h" #include "input_stream.h" -#include "tag_pool.h" -#include "tag_save.h" +#include "tag.h" #include "conf.h" -#include "stdbin.h" #ifdef ENABLE_ARCHIVE #include "archive_list.h" #endif +} #include <glib.h> @@ -127,7 +130,6 @@ int main(int argc, char **argv) /* initialize MPD */ - tag_pool_init(); config_global_init(); io_thread_init(); @@ -179,7 +181,6 @@ int main(int argc, char **argv) io_thread_deinit(); config_global_finish(); - tag_pool_deinit(); return ret; } diff --git a/test/test_queue_priority.c b/test/TestQueuePriority.cxx index 5543edbba..6d994176f 100644 --- a/test/test_queue_priority.c +++ b/test/TestQueuePriority.cxx @@ -1,5 +1,20 @@ +#include "config.h" +extern "C" { #include "queue.h" +} #include "song.h" +#include "Directory.hxx" + +Directory detached_root; + +Directory::Directory() {} +Directory::~Directory() {} + +struct song * +song_dup_detached(const struct song *src) +{ + return const_cast<song *>(src); +} void song_free(G_GNUC_UNUSED struct song *song) diff --git a/test/dump_text_file.c b/test/dump_text_file.c index f14371441..f13e648b2 100644 --- a/test/dump_text_file.c +++ b/test/dump_text_file.c @@ -22,7 +22,6 @@ #include "input_init.h" #include "input_stream.h" #include "text_input_stream.h" -#include "tag_pool.h" #include "conf.h" #include "stdbin.h" @@ -112,7 +111,6 @@ int main(int argc, char **argv) /* initialize MPD */ - tag_pool_init(); config_global_init(); io_thread_init(); @@ -164,7 +162,6 @@ int main(int argc, char **argv) io_thread_deinit(); config_global_finish(); - tag_pool_deinit(); return ret; } diff --git a/test/read_tags.c b/test/read_tags.c index faf9a45c0..09032efc7 100644 --- a/test/read_tags.c +++ b/test/read_tags.c @@ -218,6 +218,25 @@ int main(int argc, char **argv) return 1; } + g_mutex_lock(mutex); + + while (!is->ready) { + g_cond_wait(cond, mutex); + input_stream_update(is); + } + + if (!input_stream_check(is, &error)) { + g_mutex_unlock(mutex); + + g_printerr("Failed to read %s: %s\n", + path, error->message); + g_error_free(error); + + return EXIT_FAILURE; + } + + g_mutex_unlock(mutex); + success = decoder_plugin_scan_stream(plugin, is, &print_handler, NULL); input_stream_close(is); diff --git a/test/run_decoder.c b/test/run_decoder.c index e6712c75b..c25e727ba 100644 --- a/test/run_decoder.c +++ b/test/run_decoder.c @@ -21,7 +21,6 @@ #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" @@ -191,8 +190,6 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - tag_pool_init(); - if (!input_stream_global_init(&error)) { g_warning("%s", error->message); g_error_free(error); @@ -248,7 +245,5 @@ int main(int argc, char **argv) return 1; } - tag_pool_deinit(); - return 0; } |