diff options
Diffstat (limited to '')
-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) | 15 | ||||
-rw-r--r-- | src/Client.hxx (renamed from src/client.h) | 39 | ||||
-rw-r--r-- | src/ClientEvent.cxx (renamed from src/song_print.h) | 25 | ||||
-rw-r--r-- | src/ClientExpire.cxx (renamed from src/client_expire.c) | 32 | ||||
-rw-r--r-- | src/ClientFile.cxx (renamed from src/client_file.c) | 12 | ||||
-rw-r--r-- | src/ClientFile.hxx (renamed from src/client_file.h) | 13 | ||||
-rw-r--r-- | src/ClientGlobal.cxx (renamed from src/client_global.c) | 9 | ||||
-rw-r--r-- | src/ClientIdle.cxx (renamed from src/client_idle.c) | 24 | ||||
-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) | 131 | ||||
-rw-r--r-- | src/ClientList.cxx (renamed from src/client_list.c) | 33 | ||||
-rw-r--r-- | src/ClientList.hxx | 43 | ||||
-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) | 103 | ||||
-rw-r--r-- | src/ClientProcess.cxx (renamed from src/client_process.c) | 53 | ||||
-rw-r--r-- | src/ClientRead.cxx | 66 | ||||
-rw-r--r-- | src/ClientSubscribe.cxx | 93 | ||||
-rw-r--r-- | src/ClientSubscribe.hxx (renamed from src/client_subscribe.h) | 26 | ||||
-rw-r--r-- | src/ClientWrite.cxx | 87 | ||||
-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) | 14 | ||||
-rw-r--r-- | src/CommandLine.hxx (renamed from src/cmdline.h) | 8 | ||||
-rw-r--r-- | src/CommandListBuilder.cxx | 43 | ||||
-rw-r--r-- | src/CommandListBuilder.hxx | 109 | ||||
-rw-r--r-- | src/ConfigFile.cxx (renamed from src/conf.c) | 135 | ||||
-rw-r--r-- | src/CrossFade.cxx (renamed from src/crossfade.c) | 16 | ||||
-rw-r--r-- | src/CrossFade.hxx (renamed from src/crossfade.h) | 6 | ||||
-rw-r--r-- | src/DatabaseCommands.cxx | 220 | ||||
-rw-r--r-- | src/DatabaseCommands.hxx | 57 | ||||
-rw-r--r-- | src/DatabaseGlue.cxx (renamed from src/database.c) | 128 | ||||
-rw-r--r-- | src/DatabaseGlue.hxx (renamed from src/notify.h) | 48 | ||||
-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) | 6 | ||||
-rw-r--r-- | src/DatabaseLock.hxx (renamed from src/db_lock.h) | 29 | ||||
-rw-r--r-- | src/DatabasePlaylist.cxx | 50 | ||||
-rw-r--r-- | src/DatabasePlaylist.hxx (renamed from src/db_save.h) | 19 | ||||
-rw-r--r-- | src/DatabasePlugin.hxx | 148 | ||||
-rw-r--r-- | src/DatabasePrint.cxx | 231 | ||||
-rw-r--r-- | src/DatabasePrint.hxx (renamed from src/db_print.h) | 42 | ||||
-rw-r--r-- | src/DatabaseQueue.cxx | 54 | ||||
-rw-r--r-- | src/DatabaseQueue.hxx (renamed from src/db_internal.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) | 34 | ||||
-rw-r--r-- | src/DatabaseSave.hxx | 36 | ||||
-rw-r--r-- | src/DatabaseSelection.cxx (renamed from src/decoder_print.h) | 15 | ||||
-rw-r--r-- | src/DatabaseSelection.hxx (renamed from src/db_selection.h) | 32 | ||||
-rw-r--r-- | src/DatabaseSimple.hxx (renamed from src/database.h) | 61 | ||||
-rw-r--r-- | src/DatabaseVisitor.hxx | 38 | ||||
-rw-r--r-- | src/DecoderAPI.cxx (renamed from src/decoder_api.c) | 50 | ||||
-rw-r--r-- | src/DecoderControl.cxx (renamed from src/decoder_control.c) | 44 | ||||
-rw-r--r-- | src/DecoderControl.hxx (renamed from src/decoder_control.h) | 102 | ||||
-rw-r--r-- | src/DecoderInternal.cxx (renamed from src/decoder_internal.c) | 32 | ||||
-rw-r--r-- | src/DecoderInternal.hxx (renamed from src/decoder_internal.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) | 82 | ||||
-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/GlobalEvents.cxx | 114 | ||||
-rw-r--r-- | src/GlobalEvents.hxx | 71 | ||||
-rw-r--r-- | src/IOThread.cxx (renamed from src/io_thread.c) | 104 | ||||
-rw-r--r-- | src/IOThread.hxx (renamed from src/io_thread.h) | 31 | ||||
-rw-r--r-- | src/IcyMetaDataParser.cxx (renamed from src/icy_metadata.c) | 93 | ||||
-rw-r--r-- | src/IcyMetaDataParser.hxx | 83 | ||||
-rw-r--r-- | src/IdTable.hxx | 91 | ||||
-rw-r--r-- | src/Idle.cxx (renamed from src/idle.c) | 8 | ||||
-rw-r--r-- | src/Idle.hxx (renamed from src/idle.h) | 6 | ||||
-rw-r--r-- | src/InotifyQueue.cxx (renamed from src/inotify_queue.c) | 74 | ||||
-rw-r--r-- | src/InotifyQueue.hxx | 41 | ||||
-rw-r--r-- | src/InotifySource.cxx (renamed from src/inotify_source.c) | 106 | ||||
-rw-r--r-- | src/InotifySource.hxx | 74 | ||||
-rw-r--r-- | src/InotifyUpdate.cxx (renamed from src/inotify_update.c) | 56 | ||||
-rw-r--r-- | src/InotifyUpdate.hxx (renamed from src/inotify_update.h) | 6 | ||||
-rw-r--r-- | src/InputInit.cxx (renamed from src/input_init.c) | 6 | ||||
-rw-r--r-- | src/InputInit.hxx (renamed from src/input_init.h) | 11 | ||||
-rw-r--r-- | src/InputRegistry.cxx (renamed from src/input_registry.c) | 8 | ||||
-rw-r--r-- | src/InputRegistry.hxx (renamed from src/input_registry.h) | 8 | ||||
-rw-r--r-- | src/InputStream.cxx (renamed from src/input_stream.c) | 14 | ||||
-rw-r--r-- | src/Listen.cxx (renamed from src/listen.c) | 17 | ||||
-rw-r--r-- | src/Listen.hxx (renamed from src/listen.h) | 8 | ||||
-rw-r--r-- | src/Log.cxx (renamed from src/log.c) | 5 | ||||
-rw-r--r-- | src/Log.hxx (renamed from src/log.h) | 6 | ||||
-rw-r--r-- | src/Main.cxx (renamed from src/main.c) | 176 | ||||
-rw-r--r-- | src/Main.hxx (renamed from src/main.h) | 16 | ||||
-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 | 167 | ||||
-rw-r--r-- | src/MessageCommands.hxx | 42 | ||||
-rw-r--r-- | src/MixerAll.cxx (renamed from src/mixer_all.c) | 13 | ||||
-rw-r--r-- | src/MixerAll.hxx (renamed from src/mixer_all.h) | 8 | ||||
-rw-r--r-- | src/MusicBuffer.cxx | 79 | ||||
-rw-r--r-- | src/MusicBuffer.hxx (renamed from src/buffer.h) | 6 | ||||
-rw-r--r-- | src/MusicChunk.cxx | 84 | ||||
-rw-r--r-- | src/MusicChunk.hxx (renamed from src/chunk.h) | 104 | ||||
-rw-r--r-- | src/MusicPipe.cxx (renamed from src/pipe.c) | 77 | ||||
-rw-r--r-- | src/MusicPipe.hxx (renamed from src/pipe.h) | 11 | ||||
-rw-r--r-- | src/OtherCommands.cxx | 308 | ||||
-rw-r--r-- | src/OtherCommands.hxx | 69 | ||||
-rw-r--r-- | src/OutputAll.cxx (renamed from src/output_all.c) | 58 | ||||
-rw-r--r-- | src/OutputAll.hxx (renamed from src/output_all.h) | 11 | ||||
-rw-r--r-- | src/OutputCommand.cxx (renamed from src/output_command.c) | 11 | ||||
-rw-r--r-- | src/OutputCommand.hxx (renamed from src/output_command.h) | 8 | ||||
-rw-r--r-- | src/OutputCommands.cxx | 74 | ||||
-rw-r--r-- | src/OutputCommands.hxx | 36 | ||||
-rw-r--r-- | src/OutputControl.cxx (renamed from src/output_control.c) | 23 | ||||
-rw-r--r-- | src/OutputControl.hxx (renamed from src/output_control.h) | 13 | ||||
-rw-r--r-- | src/OutputError.hxx | 35 | ||||
-rw-r--r-- | src/OutputFinish.cxx (renamed from src/output_finish.c) | 5 | ||||
-rw-r--r-- | src/OutputInit.cxx (renamed from src/output_init.c) | 19 | ||||
-rw-r--r-- | src/OutputList.cxx (renamed from src/output_list.c) | 6 | ||||
-rw-r--r-- | src/OutputList.hxx (renamed from src/output_list.h) | 6 | ||||
-rw-r--r-- | src/OutputPlugin.cxx (renamed from src/output_plugin.c) | 6 | ||||
-rw-r--r-- | src/OutputPrint.cxx (renamed from src/output_print.c) | 12 | ||||
-rw-r--r-- | src/OutputPrint.hxx (renamed from src/output_print.h) | 10 | ||||
-rw-r--r-- | src/OutputState.cxx (renamed from src/output_state.c) | 6 | ||||
-rw-r--r-- | src/OutputState.hxx (renamed from src/output_state.h) | 7 | ||||
-rw-r--r-- | src/OutputThread.cxx (renamed from src/output_thread.c) | 48 | ||||
-rw-r--r-- | src/OutputThread.hxx (renamed from src/output_thread.h) | 6 | ||||
-rw-r--r-- | src/Partition.hxx | 165 | ||||
-rw-r--r-- | src/Permission.cxx (renamed from src/permission.c) | 34 | ||||
-rw-r--r-- | src/Permission.hxx (renamed from src/permission.h) | 8 | ||||
-rw-r--r-- | src/PlayerCommands.cxx | 394 | ||||
-rw-r--r-- | src/PlayerCommands.hxx | 90 | ||||
-rw-r--r-- | src/PlayerControl.cxx (renamed from src/player_control.c) | 170 | ||||
-rw-r--r-- | src/PlayerControl.hxx (renamed from src/player_control.h) | 124 | ||||
-rw-r--r-- | src/PlayerThread.cxx (renamed from src/player_thread.c) | 150 | ||||
-rw-r--r-- | src/PlayerThread.hxx (renamed from src/player_thread.h) | 6 | ||||
-rw-r--r-- | src/Playlist.cxx | 346 | ||||
-rw-r--r-- | src/Playlist.hxx | 256 | ||||
-rw-r--r-- | src/PlaylistAny.cxx (renamed from src/playlist_any.c) | 11 | ||||
-rw-r--r-- | src/PlaylistAny.hxx (renamed from src/playlist_any.h) | 6 | ||||
-rw-r--r-- | src/PlaylistCommands.cxx | 224 | ||||
-rw-r--r-- | src/PlaylistCommands.hxx | 60 | ||||
-rw-r--r-- | src/PlaylistControl.cxx | 265 | ||||
-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 | 423 | ||||
-rw-r--r-- | src/PlaylistFile.cxx (renamed from src/stored_playlist.c) | 256 | ||||
-rw-r--r-- | src/PlaylistFile.hxx (renamed from src/stored_playlist.h) | 36 | ||||
-rw-r--r-- | src/PlaylistGlobal.cxx (renamed from src/playlist_global.c) | 31 | ||||
-rw-r--r-- | src/PlaylistGlobal.hxx | 26 | ||||
-rw-r--r-- | src/PlaylistInfo.hxx | 63 | ||||
-rw-r--r-- | src/PlaylistMapper.cxx (renamed from src/playlist_mapper.c) | 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) | 116 | ||||
-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) | 23 | ||||
-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) | 49 | ||||
-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) | 33 | ||||
-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) | 74 | ||||
-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 | 56 | ||||
-rw-r--r-- | src/Queue.cxx | 499 | ||||
-rw-r--r-- | src/Queue.hxx | 368 | ||||
-rw-r--r-- | src/QueueCommands.cxx | 375 | ||||
-rw-r--r-- | src/QueueCommands.hxx | 84 | ||||
-rw-r--r-- | src/QueuePrint.cxx | 107 | ||||
-rw-r--r-- | src/QueuePrint.hxx (renamed from src/queue_print.h) | 26 | ||||
-rw-r--r-- | src/QueueSave.cxx (renamed from src/queue_save.c) | 65 | ||||
-rw-r--r-- | src/QueueSave.hxx (renamed from src/queue_save.h) | 10 | ||||
-rw-r--r-- | src/ReplayGainConfig.cxx (renamed from src/replay_gain_config.c) | 21 | ||||
-rw-r--r-- | src/ReplayGainInfo.cxx (renamed from src/replay_gain_info.c) | 2 | ||||
-rw-r--r-- | src/ServerSocket.cxx (renamed from src/server_socket.c) | 323 | ||||
-rw-r--r-- | src/ServerSocket.hxx (renamed from src/server_socket.h) | 14 | ||||
-rw-r--r-- | src/SignalHandlers.cxx (renamed from src/sig_handlers.c) | 15 | ||||
-rw-r--r-- | src/SignalHandlers.hxx | 25 | ||||
-rw-r--r-- | src/SocketError.hxx | 156 | ||||
-rw-r--r-- | src/SocketUtil.cxx (renamed from src/socket_util.c) | 31 | ||||
-rw-r--r-- | src/SocketUtil.hxx (renamed from src/socket_util.h) | 10 | ||||
-rw-r--r-- | src/Song.cxx (renamed from src/song.c) | 99 | ||||
-rw-r--r-- | src/SongFilter.cxx | 167 | ||||
-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) | 21 | ||||
-rw-r--r-- | src/StateFile.cxx | 122 | ||||
-rw-r--r-- | src/StateFile.hxx | 53 | ||||
-rw-r--r-- | src/Stats.cxx | 89 | ||||
-rw-r--r-- | src/StickerCommands.cxx | 176 | ||||
-rw-r--r-- | src/StickerCommands.hxx | 30 | ||||
-rw-r--r-- | src/StickerDatabase.cxx (renamed from src/sticker.c) | 95 | ||||
-rw-r--r-- | src/StickerDatabase.hxx (renamed from src/sticker.h) | 18 | ||||
-rw-r--r-- | src/StickerPrint.cxx (renamed from src/sticker_print.c) | 16 | ||||
-rw-r--r-- | src/StickerPrint.hxx (renamed from src/sticker_print.h) | 13 | ||||
-rw-r--r-- | src/Tag.cxx (renamed from src/tag.c) | 135 | ||||
-rw-r--r-- | src/TagInternal.hxx (renamed from src/tag_internal.h) | 8 | ||||
-rw-r--r-- | src/TagNames.c | 44 | ||||
-rw-r--r-- | src/TagPool.cxx (renamed from src/tag_pool.c) | 30 | ||||
-rw-r--r-- | src/TagPool.hxx (renamed from src/tag_pool.h) | 13 | ||||
-rw-r--r-- | src/TagPrint.cxx (renamed from src/tag_print.c) | 12 | ||||
-rw-r--r-- | src/TagPrint.hxx (renamed from src/tag_print.h) | 12 | ||||
-rw-r--r-- | src/TagSave.cxx (renamed from src/tag_save.c) | 6 | ||||
-rw-r--r-- | src/TagSave.hxx (renamed from src/tag_save.h) | 6 | ||||
-rw-r--r-- | src/TextFile.cxx (renamed from src/text_file.c) | 15 | ||||
-rw-r--r-- | src/TextFile.hxx | 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) | 36 | ||||
-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) | 38 | ||||
-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) | 20 | ||||
-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) | 111 | ||||
-rw-r--r-- | src/UpdateWalk.hxx (renamed from src/update_walk.h) | 8 | ||||
-rw-r--r-- | src/Volume.cxx (renamed from src/volume.c) | 21 | ||||
-rw-r--r-- | src/Volume.hxx (renamed from src/volume.h) | 7 | ||||
-rw-r--r-- | src/Win32Main.cxx (renamed from src/main_win32.c) | 35 | ||||
-rw-r--r-- | src/audio_check.h | 2 | ||||
-rw-r--r-- | src/audio_config.c | 1 | ||||
-rw-r--r-- | src/audio_format.h | 27 | ||||
-rw-r--r-- | src/audio_parser.h | 2 | ||||
-rw-r--r-- | src/buffer.c | 137 | ||||
-rw-r--r-- | src/chunk.c | 102 | ||||
-rw-r--r-- | src/client_event.c | 108 | ||||
-rw-r--r-- | src/client_message.c | 96 | ||||
-rw-r--r-- | src/client_message.h | 72 | ||||
-rw-r--r-- | src/client_read.c | 113 | ||||
-rw-r--r-- | src/client_subscribe.c | 123 | ||||
-rw-r--r-- | src/client_write.c | 284 | ||||
-rw-r--r-- | src/clock.h | 6 | ||||
-rw-r--r-- | src/command.c | 2298 | ||||
-rw-r--r-- | src/command.h | 49 | ||||
-rw-r--r-- | src/conf.h | 4 | ||||
-rw-r--r-- | src/cue/cue_parser.c | 2 | ||||
-rw-r--r-- | src/db/ProxyDatabasePlugin.cxx | 475 | ||||
-rw-r--r-- | src/db/ProxyDatabasePlugin.hxx | 27 | ||||
-rw-r--r-- | src/db/SimpleDatabasePlugin.cxx | 344 | ||||
-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) | 67 | ||||
-rw-r--r-- | src/decoder/FLACCommon.hxx (renamed from src/decoder/_flac_common.h) | 30 | ||||
-rw-r--r-- | src/decoder/FLACDecoderPlugin.cxx (renamed from src/decoder/flac_decoder_plugin.c) | 291 | ||||
-rw-r--r-- | src/decoder/FLACDecoderPlugin.h | 26 | ||||
-rw-r--r-- | src/decoder/FLACIOHandle.cxx | 114 | ||||
-rw-r--r-- | src/decoder/FLACIOHandle.hxx | 45 | ||||
-rw-r--r-- | src/decoder/FLACInput.cxx | 149 | ||||
-rw-r--r-- | src/decoder/FLACInput.hxx | 72 | ||||
-rw-r--r-- | src/decoder/FLACMetaData.cxx (renamed from src/decoder/flac_metadata.c) | 136 | ||||
-rw-r--r-- | src/decoder/FLACMetaData.hxx | 140 | ||||
-rw-r--r-- | src/decoder/FLAC_PCM.cxx (renamed from src/decoder/flac_pcm.c) | 4 | ||||
-rw-r--r-- | src/decoder/FLAC_PCM.hxx (renamed from src/decoder/flac_pcm.h) | 6 | ||||
-rw-r--r-- | src/decoder/OggCodec.cxx (renamed from src/decoder/_ogg_common.c) | 20 | ||||
-rw-r--r-- | src/decoder/OggCodec.hxx (renamed from src/decoder/_ogg_common.h) | 16 | ||||
-rw-r--r-- | src/decoder/OggFind.cxx | 37 | ||||
-rw-r--r-- | src/decoder/OggFind.hxx | 38 | ||||
-rw-r--r-- | src/decoder/OggSyncState.hxx | 78 | ||||
-rw-r--r-- | src/decoder/OggUtil.cxx | 118 | ||||
-rw-r--r-- | src/decoder/OggUtil.hxx | 87 | ||||
-rw-r--r-- | src/decoder/OpusDecoderPlugin.cxx | 396 | ||||
-rw-r--r-- | src/decoder/OpusDecoderPlugin.h | 25 | ||||
-rw-r--r-- | src/decoder/OpusHead.cxx | 44 | ||||
-rw-r--r-- | src/decoder/OpusHead.hxx | 30 | ||||
-rw-r--r-- | src/decoder/OpusReader.hxx | 98 | ||||
-rw-r--r-- | src/decoder/OpusTags.cxx | 77 | ||||
-rw-r--r-- | src/decoder/OpusTags.hxx | 31 | ||||
-rw-r--r-- | src/decoder/VorbisComments.cxx (renamed from src/decoder/vorbis_comments.c) | 16 | ||||
-rw-r--r-- | src/decoder/VorbisComments.hxx (renamed from src/decoder/vorbis_comments.h) | 6 | ||||
-rw-r--r-- | src/decoder/VorbisDecoderPlugin.cxx (renamed from src/decoder/vorbis_decoder_plugin.c) | 156 | ||||
-rw-r--r-- | src/decoder/VorbisDecoderPlugin.h | 25 | ||||
-rw-r--r-- | src/decoder/WavpackDecoderPlugin.cxx (renamed from src/decoder/wavpack_decoder_plugin.c) | 59 | ||||
-rw-r--r-- | src/decoder/WavpackDecoderPlugin.hxx | 25 | ||||
-rw-r--r-- | src/decoder/XiphTags.c | 28 | ||||
-rw-r--r-- | src/decoder/XiphTags.h | 28 | ||||
-rw-r--r-- | src/decoder/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 | 9 | ||||
-rw-r--r-- | src/decoder/sidplay_decoder_plugin.cxx | 10 | ||||
-rw-r--r-- | src/decoder_api.h | 14 | ||||
-rw-r--r-- | src/decoder_error.h | 35 | ||||
-rw-r--r-- | src/decoder_list.c | 16 | ||||
-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 | 433 | ||||
-rw-r--r-- | src/encoder/OpusEncoderPlugin.hxx | 25 | ||||
-rw-r--r-- | src/encoder/VorbisEncoderPlugin.cxx (renamed from src/encoder/vorbis_encoder.c) | 134 | ||||
-rw-r--r-- | src/encoder/VorbisEncoderPlugin.hxx | 25 | ||||
-rw-r--r-- | src/encoder/flac_encoder.c | 35 | ||||
-rw-r--r-- | src/encoder/null_encoder.c | 4 | ||||
-rw-r--r-- | src/encoder/wave_encoder.c | 4 | ||||
-rw-r--r-- | src/encoder_list.c | 8 | ||||
-rw-r--r-- | src/encoder_list.h | 8 | ||||
-rw-r--r-- | src/encoder_plugin.h | 2 | ||||
-rw-r--r-- | src/event/BufferedSocket.cxx | 256 | ||||
-rw-r--r-- | src/event/BufferedSocket.hxx | 125 | ||||
-rw-r--r-- | src/event/Loop.hxx | 84 | ||||
-rw-r--r-- | src/event/MultiSocketMonitor.cxx | 107 | ||||
-rw-r--r-- | src/event/MultiSocketMonitor.hxx | 124 | ||||
-rw-r--r-- | src/event/SocketMonitor.cxx | 121 | ||||
-rw-r--r-- | src/event/SocketMonitor.hxx | 123 | ||||
-rw-r--r-- | src/event/TimeoutMonitor.cxx | 65 | ||||
-rw-r--r-- | src/event/TimeoutMonitor.hxx | 60 | ||||
-rw-r--r-- | src/event/WakeFD.cxx | 225 | ||||
-rw-r--r-- | src/event/WakeFD.hxx | 80 | ||||
-rw-r--r-- | src/event_pipe.c | 164 | ||||
-rw-r--r-- | src/event_pipe.h | 71 | ||||
-rw-r--r-- | src/fd_util.c | 14 | ||||
-rw-r--r-- | src/fd_util.h | 19 | ||||
-rw-r--r-- | src/filter/ReplayGainFilterPlugin.cxx (renamed from src/filter/replay_gain_filter_plugin.c) | 54 | ||||
-rw-r--r-- | src/filter/ReplayGainFilterPlugin.hxx (renamed from src/filter/replay_gain_filter_plugin.h) | 9 | ||||
-rw-r--r-- | src/filter/null_filter_plugin.c | 1 | ||||
-rw-r--r-- | src/filter_plugin.h | 2 | ||||
-rw-r--r-- | src/gcc.h | 33 | ||||
-rw-r--r-- | src/gerror.h (renamed from src/sig_handlers.h) | 6 | ||||
-rw-r--r-- | src/icy_metadata.h | 99 | ||||
-rw-r--r-- | src/icy_server.h | 8 | ||||
-rw-r--r-- | src/inotify_source.h | 61 | ||||
-rw-r--r-- | src/input/CurlInputPlugin.cxx (renamed from src/input/curl_input_plugin.c) | 556 | ||||
-rw-r--r-- | src/input/CurlInputPlugin.hxx (renamed from src/input/curl_input_plugin.h) | 6 | ||||
-rw-r--r-- | src/input/SoupInputPlugin.cxx (renamed from src/input/soup_input_plugin.c) | 58 | ||||
-rw-r--r-- | src/input/SoupInputPlugin.hxx (renamed from src/input/soup_input_plugin.h) | 6 | ||||
-rw-r--r-- | src/input/file_input_plugin.c | 17 | ||||
-rw-r--r-- | src/input_stream.h | 15 | ||||
-rw-r--r-- | src/io_error.h | 51 | ||||
-rw-r--r-- | src/locate.c | 239 | ||||
-rw-r--r-- | src/locate.h | 92 | ||||
-rw-r--r-- | src/ls.cxx (renamed from src/ls.c) | 14 | ||||
-rw-r--r-- | src/ls.hxx (renamed from src/ls.h) | 11 | ||||
-rw-r--r-- | src/mixer/AlsaMixerPlugin.cxx (renamed from src/mixer/alsa_mixer_plugin.c) | 189 | ||||
-rw-r--r-- | src/mixer/PulseMixerPlugin.cxx (renamed from src/mixer/pulse_mixer_plugin.c) | 30 | ||||
-rw-r--r-- | src/mixer/PulseMixerPlugin.h (renamed from src/mixer/pulse_mixer_plugin.h) | 10 | ||||
-rw-r--r-- | src/mixer/winmm_mixer_plugin.c | 2 | ||||
-rw-r--r-- | src/mixer_api.h | 8 | ||||
-rw-r--r-- | src/mixer_control.h | 2 | ||||
-rw-r--r-- | src/mixer_plugin.h | 2 | ||||
-rw-r--r-- | src/mpd_error.h | 1 | ||||
-rw-r--r-- | src/notify.c | 58 | ||||
-rw-r--r-- | src/notify.cxx (renamed from src/state_file.h) | 32 | ||||
-rw-r--r-- | src/notify.hxx | 53 | ||||
-rw-r--r-- | src/output/HttpdClient.cxx | 592 | ||||
-rw-r--r-- | src/output/HttpdClient.hxx | 215 | ||||
-rw-r--r-- | src/output/HttpdInternal.hxx (renamed from src/output/httpd_internal.h) | 14 | ||||
-rw-r--r-- | src/output/HttpdOutputPlugin.cxx (renamed from src/output/httpd_output_plugin.c) | 241 | ||||
-rw-r--r-- | src/output/HttpdOutputPlugin.hxx (renamed from src/output/httpd_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/fifo_output_plugin.c | 1 | ||||
-rw-r--r-- | src/output/httpd_client.c | 764 | ||||
-rw-r--r-- | src/output/httpd_client.h | 71 | ||||
-rw-r--r-- | src/output/osx_output_plugin.c | 2 | ||||
-rw-r--r-- | src/output/pulse_output_plugin.c | 2 | ||||
-rw-r--r-- | src/output/pulse_output_plugin.h | 12 | ||||
-rw-r--r-- | src/output/shout_output_plugin.c | 104 | ||||
-rw-r--r-- | src/output/winmm_output_plugin.c | 1 | ||||
-rw-r--r-- | src/output/winmm_output_plugin.h | 1 | ||||
-rw-r--r-- | src/output_internal.h | 15 | ||||
-rw-r--r-- | src/output_plugin.h | 7 | ||||
-rw-r--r-- | src/page.h | 8 | ||||
-rw-r--r-- | src/path.c | 23 | ||||
-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_convert.h | 8 | ||||
-rw-r--r-- | src/pcm_mix.h | 3 | ||||
-rw-r--r-- | src/pcm_volume.h | 1 | ||||
-rw-r--r-- | src/playlist.c | 452 | ||||
-rw-r--r-- | src/playlist.h | 256 | ||||
-rw-r--r-- | src/playlist_control.c | 288 | ||||
-rw-r--r-- | src/playlist_edit.c | 487 | ||||
-rw-r--r-- | src/playlist_internal.h | 56 | ||||
-rw-r--r-- | src/playlist_vector.c | 114 | ||||
-rw-r--r-- | src/playlist_vector.h | 80 | ||||
-rw-r--r-- | src/protocol/ArgParser.cxx (renamed from src/protocol/argparser.c) | 18 | ||||
-rw-r--r-- | src/protocol/ArgParser.hxx (renamed from src/protocol/argparser.h) | 20 | ||||
-rw-r--r-- | src/protocol/Result.cxx (renamed from src/protocol/result.c) | 12 | ||||
-rw-r--r-- | src/protocol/Result.hxx (renamed from src/protocol/result.h) | 19 | ||||
-rw-r--r-- | src/queue.c | 603 | ||||
-rw-r--r-- | src/queue.h | 379 | ||||
-rw-r--r-- | src/queue_print.c | 122 | ||||
-rw-r--r-- | src/replay_gain_config.h | 10 | ||||
-rw-r--r-- | src/replay_gain_info.h | 16 | ||||
-rw-r--r-- | src/resolver.h | 14 | ||||
-rw-r--r-- | src/song.h | 73 | ||||
-rw-r--r-- | src/state_file.c | 162 | ||||
-rw-r--r-- | src/stats.c | 128 | ||||
-rw-r--r-- | src/stats.h | 5 | ||||
-rw-r--r-- | src/string_util.c | 21 | ||||
-rw-r--r-- | src/string_util.h | 38 | ||||
-rw-r--r-- | src/strset.c | 148 | ||||
-rw-r--r-- | src/strset.h | 50 | ||||
-rw-r--r-- | src/tag.h | 10 | ||||
-rw-r--r-- | src/tag_id3.c | 5 | ||||
-rw-r--r-- | src/tag_id3.h | 18 | ||||
-rw-r--r-- | src/text_file.h | 39 | ||||
-rw-r--r-- | src/text_input_stream.c | 2 | ||||
-rw-r--r-- | src/thread/Cond.hxx | 37 | ||||
-rw-r--r-- | src/thread/CriticalSection.hxx | 68 | ||||
-rw-r--r-- | src/thread/GLibCond.hxx | 88 | ||||
-rw-r--r-- | src/thread/GLibMutex.hxx | 90 | ||||
-rw-r--r-- | src/thread/Mutex.hxx | 54 | ||||
-rw-r--r-- | src/thread/PosixCond.hxx | 60 | ||||
-rw-r--r-- | src/thread/PosixMutex.hxx | 62 | ||||
-rw-r--r-- | src/thread/WindowsCond.hxx | 63 | ||||
-rw-r--r-- | src/timer.h | 8 | ||||
-rw-r--r-- | src/tokenizer.c | 2 | ||||
-rw-r--r-- | src/tokenizer.h | 2 | ||||
-rw-r--r-- | src/uri.h | 10 | ||||
-rw-r--r-- | src/util/HugeAllocator.cxx | 87 | ||||
-rw-r--r-- | src/util/HugeAllocator.hxx | 82 | ||||
-rw-r--r-- | src/util/LazyRandomEngine.cxx (renamed from src/inotify_queue.h) | 21 | ||||
-rw-r--r-- | src/util/LazyRandomEngine.hxx | 67 | ||||
-rw-r--r-- | src/util/PeakBuffer.cxx | 143 | ||||
-rw-r--r-- | src/util/PeakBuffer.hxx | 66 | ||||
-rw-r--r-- | src/util/SliceBuffer.hxx | 161 | ||||
-rw-r--r-- | src/util/bit_reverse.h | 5 | ||||
-rw-r--r-- | src/util/fifo_buffer.c (renamed from src/fifo_buffer.c) | 8 | ||||
-rw-r--r-- | src/util/fifo_buffer.h (renamed from src/fifo_buffer.h) | 11 | ||||
-rw-r--r-- | src/util/growing_fifo.c (renamed from src/growing_fifo.c) | 0 | ||||
-rw-r--r-- | src/util/growing_fifo.h (renamed from src/growing_fifo.h) | 0 | ||||
-rw-r--r-- | src/util/list.h | 53 | ||||
-rw-r--r-- | src/utils.h | 12 | ||||
-rw-r--r-- | src/zeroconf-avahi.c | 9 | ||||
-rw-r--r-- | src/zeroconf-bonjour.c | 2 | ||||
-rw-r--r-- | src/zeroconf.c | 2 |
456 files changed, 20921 insertions, 14938 deletions
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..be121dfe8 100644 --- a/src/client.c +++ b/src/Client.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,24 +18,19 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" -bool client_is_expired(const struct client *client) -{ - return client->channel == NULL; -} - -int client_get_uid(const struct client *client) +int client_get_uid(const Client *client) { return client->uid; } -unsigned client_get_permission(const struct client *client) +unsigned client_get_permission(const Client *client) { return client->permission; } -void client_set_permission(struct client *client, unsigned permission) +void client_set_permission(Client *client, unsigned permission) { client->permission = permission; } diff --git a/src/client.h b/src/Client.hxx index 0302a2e0a..bf2a2521f 100644 --- a/src/client.h +++ b/src/Client.hxx @@ -20,60 +20,61 @@ #ifndef MPD_CLIENT_H #define MPD_CLIENT_H -#include <glib.h> -#include <stdbool.h> +#include "gcc.h" + #include <stddef.h> #include <stdarg.h> -struct client; struct sockaddr; -struct player_control; +class EventLoop; +struct Partition; +class Client; void client_manager_init(void); void client_manager_deinit(void); -void client_new(struct player_control *player_control, - int fd, const struct sockaddr *sa, size_t sa_length, int uid); - -G_GNUC_PURE -bool client_is_expired(const struct client *client); +void +client_new(EventLoop &loop, Partition &partition, + int fd, const struct sockaddr *sa, size_t sa_length, int uid); /** * returns the uid of the client process, or a negative value if the * uid is unknown */ -G_GNUC_PURE -int client_get_uid(const struct client *client); +gcc_pure +int client_get_uid(const Client *client); /** * Is this client running on the same machine, connected with a local * (UNIX domain) socket? */ -G_GNUC_PURE +gcc_pure static inline bool -client_is_local(const struct client *client) +client_is_local(const Client *client) { return client_get_uid(client) > 0; } -G_GNUC_PURE -unsigned client_get_permission(const struct client *client); +gcc_pure +unsigned client_get_permission(const Client *client); -void client_set_permission(struct client *client, unsigned permission); +void client_set_permission(Client *client, unsigned permission); /** * Write a C string to the client. */ -void client_puts(struct client *client, const char *s); +void client_puts(Client *client, const char *s); /** * Write a printf-like formatted string to the client. */ -void client_vprintf(struct client *client, const char *fmt, va_list args); +void client_vprintf(Client *client, const char *fmt, va_list args); /** * Write a printf-like formatted string to the client. */ -G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...); +gcc_fprintf +void +client_printf(Client *client, const char *fmt, ...); #endif diff --git a/src/song_print.h b/src/ClientEvent.cxx index 8f1f0cc65..905cf0c0a 100644 --- a/src/song_print.h +++ b/src/ClientEvent.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,17 +17,20 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_SONG_PRINT_H -#define MPD_SONG_PRINT_H - -struct client; -struct song; -struct songvec; +#include "config.h" +#include "ClientInternal.hxx" void -song_print_info(struct client *client, struct song *song); +Client::OnSocketError(GError *error) +{ + g_warning("error on client %d: %s", num, error->message); + g_error_free(error); -void -song_print_uri(struct client *client, struct song *song); + SetExpired(); +} -#endif +void +Client::OnSocketClosed() +{ + SetExpired(); +} diff --git a/src/client_expire.c b/src/ClientExpire.cxx index 1ca32ebcc..56b003df8 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,41 +18,33 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" +#include "ClientList.hxx" static guint expire_source_id; void -client_set_expired(struct client *client) +Client::SetExpired() { - if (!client_is_expired(client)) - client_schedule_expire(); + if (IsExpired()) + return; - if (client->source_id != 0) { - g_source_remove(client->source_id); - client->source_id = 0; - } - - if (client->channel != NULL) { - g_io_channel_unref(client->channel); - client->channel = NULL; - } + client_schedule_expire(); + BufferedSocket::Close(); } static void -client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +client_check_expired_callback(Client *client, G_GNUC_UNUSED gpointer user_data) { - struct client *client = data; - - if (client_is_expired(client)) { + if (client->IsExpired()) { g_debug("[%u] expired", client->num); - client_close(client); + client->Close(); } else if (!client->idle_waiting && /* idle clients never expire */ (int)g_timer_elapsed(client->last_activity, NULL) > client_timeout) { g_debug("[%u] timeout", client->num); - client_close(client); + client->Close(); } } 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..1aa82b435 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,7 +18,8 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" +#include "ClientList.hxx" #include "conf.h" #include <assert.h> @@ -55,9 +56,9 @@ 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); + client->Close(); } assert(client_list_is_empty()); diff --git a/src/client_idle.c b/src/ClientIdle.cxx index 930911d6e..f9818b278 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,10 @@ */ #include "config.h" -#include "client_idle.h" -#include "client_internal.h" -#include "idle.h" +#include "ClientIdle.hxx" +#include "ClientInternal.hxx" +#include "ClientList.hxx" +#include "Idle.hxx" #include <assert.h> @@ -28,7 +29,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,23 +53,20 @@ 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)) + if (client->IsExpired()) return; client->idle_flags |= flags; if (client->idle_waiting - && (client->idle_flags & client->idle_subscriptions)) { + && (client->idle_flags & client->idle_subscriptions)) client_idle_notify(client); - client_write_output(client); - } } static void -client_idle_callback(gpointer data, gpointer user_data) +client_idle_callback(Client *client, gpointer user_data) { - struct client *client = data; unsigned flags = GPOINTER_TO_UINT(user_data); client_idle_add(client, flags); @@ -81,7 +79,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..dee5d9c71 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,22 @@ * 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 "check.h" +#include "Client.hxx" +#include "ClientMessage.hxx" +#include "CommandListBuilder.hxx" +#include "event/BufferedSocket.hxx" #include "command.h" +#include <set> +#include <string> +#include <list> + +#include <glib.h> + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "client" @@ -32,20 +41,14 @@ enum { CLIENT_MAX_MESSAGES = 64, }; -struct deferred_buffer { - size_t size; - char data[sizeof(long)]; -}; +struct Partition; -struct client { +class Client final : private BufferedSocket { +public: + Partition &partition; + struct playlist &playlist; struct player_control *player_control; - GIOChannel *channel; - guint source_id; - - /** the buffer for reading lines from the #channel */ - struct fifo_buffer *input; - unsigned permission; /** the uid of the client process, or -1 if unknown */ @@ -56,15 +59,9 @@ struct client { */ GTimer *last_activity; - GSList *cmd_list; /* for when in list mode */ - int cmd_list_OK; /* print OK after each command execution */ - size_t cmd_list_size; /* mem cmd_list consumes */ - GQueue *deferred_send; /* for output if client is slow */ - size_t deferred_bytes; /* mem deferred_send consumes */ - unsigned int num; /* client number */ + CommandListBuilder cmd_list; - char send_buf[16384]; - size_t send_buf_used; /* bytes used this instance */ + unsigned int num; /* client number */ /** is this client waiting for an "idle" response? */ bool idle_waiting; @@ -79,7 +76,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,60 +85,45 @@ 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::list<ClientMessage> messages; - /** - * The number of messages in #messages. - */ - unsigned num_messages; -}; + Client(EventLoop &loop, Partition &partition, + int fd, int uid, int num); + ~Client(); -extern unsigned int client_max_connections; -extern int client_timeout; -extern size_t client_max_command_list_size; -extern size_t client_max_output_buffer_size; + bool IsConnected() const { + return BufferedSocket::IsDefined(); + } -bool -client_list_is_empty(void); + gcc_pure + bool IsSubscribed(const char *channel_name) const { + return subscriptions.find(channel_name) != subscriptions.end(); + } -bool -client_list_is_full(void); + gcc_pure + bool IsExpired() const { + return !BufferedSocket::IsDefined(); + } -struct client * -client_list_get_first(void); + void Close(); + void SetExpired(); -void -client_list_add(struct client *client); + using BufferedSocket::Write; -void -client_list_foreach(GFunc func, gpointer user_data); - -void -client_list_remove(struct client *client); - -void -client_close(struct client *client); - -static inline void -new_cmd_list_ptr(struct client *client, const char *s) -{ - client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s)); -} - -static inline void -free_cmd_list(GSList *list) -{ - for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp)) - g_free(tmp->data); - - g_slist_free(list); -} +private: + /* virtual methods from class BufferedSocket */ + virtual InputResult OnSocketInput(const void *data, + size_t length) override; + virtual void OnSocketError(GError *error) override; + virtual void OnSocketClosed() override; +}; -void -client_set_expired(struct client *client); +extern unsigned int client_max_connections; +extern int client_timeout; +extern size_t client_max_command_list_size; +extern size_t client_max_output_buffer_size; /** * Schedule an "expired" check for all clients: permanently delete @@ -157,16 +139,13 @@ 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); - -void -client_write_deferred(struct client *client); +client_process_line(Client *client, char *line); 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..a1248c8ec 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,11 +18,15 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientList.hxx" +#include "ClientInternal.hxx" + +#include <list> +#include <algorithm> #include <assert.h> -static GList *clients; +static std::list<Client *> clients; static unsigned num_clients; bool @@ -37,33 +41,36 @@ client_list_is_full(void) return num_clients >= client_max_connections; } -struct client * +Client * client_list_get_first(void) { - assert(clients != NULL); + assert(!clients.empty()); - return clients->data; + return clients.front(); } void -client_list_add(struct client *client) +client_list_add(Client *client) { - clients = g_list_prepend(clients, client); + clients.push_front(client); ++num_clients; } void -client_list_foreach(GFunc func, gpointer user_data) +client_list_foreach(void (*callback)(Client *client, void *ctx), void *ctx) { - g_list_foreach(clients, func, user_data); + for (auto client = clients.begin(); client != clients.end();) + callback(*(client++), ctx); } void -client_list_remove(struct client *client) +client_list_remove(Client *client) { assert(num_clients > 0); - assert(clients != NULL); + assert(!clients.empty()); - clients = g_list_remove(clients, client); + auto i = std::find(clients.begin(), clients.end(), client); + assert(i != clients.end()); + clients.erase(i); --num_clients; } diff --git a/src/ClientList.hxx b/src/ClientList.hxx new file mode 100644 index 000000000..fccc01d81 --- /dev/null +++ b/src/ClientList.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_LIST_HXX +#define MPD_CLIENT_LIST_HXX + +class Client; + +bool +client_list_is_empty(void); + +bool +client_list_is_full(void); + +Client * +client_list_get_first(void); + +void +client_list_add(Client *client); + +void +client_list_foreach(void (*callback)(Client *client, void *ctx), void *ctx); + +void +client_list_remove(Client *client); + +#endif 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..42cc3470d 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,14 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" +#include "ClientList.hxx" +#include "Partition.hxx" #include "fd_util.h" -#include "fifo_buffer.h" +extern "C" { #include "resolver.h" -#include "permission.h" +} +#include "Permission.hxx" #include "glib_socket.h" #include <assert.h> @@ -43,15 +46,32 @@ static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n"; +Client::Client(EventLoop &_loop, Partition &_partition, + int _fd, int _uid, int _num) + :BufferedSocket(_fd, _loop, 16384, client_max_output_buffer_size), + partition(_partition), + playlist(partition.playlist), player_control(&partition.pc), + permission(getDefaultPermissions()), + uid(_uid), + last_activity(g_timer_new()), + num(_num), + idle_waiting(false), idle_flags(0), + num_subscriptions(0) +{ +} + +Client::~Client() +{ + g_timer_destroy(last_activity); +} + void -client_new(struct player_control *player_control, +client_new(EventLoop &loop, Partition &partition, int fd, const struct sockaddr *sa, size_t sa_length, int uid) { static unsigned int next_client_num; - struct client *client; char *remote; - assert(player_control != NULL); assert(fd >= 0); #ifdef HAVE_LIBWRAP @@ -85,43 +105,8 @@ client_new(struct player_control *player_control, return; } - client = g_new0(struct client, 1); - client->player_control = player_control; - - client->channel = g_io_channel_new_socket(fd); - /* GLib is responsible for closing the file descriptor */ - g_io_channel_set_close_on_unref(client->channel, true); - /* NULL encoding means the stream is binary safe; the MPD - protocol is UTF-8 only, but we are doing this call anyway - to prevent GLib from messing around with the stream */ - g_io_channel_set_encoding(client->channel, NULL, NULL); - /* we prefer to do buffering */ - g_io_channel_set_buffered(client->channel, false); - - client->source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - client_in_event, client); - - client->input = fifo_buffer_new(4096); - - client->permission = getDefaultPermissions(); - client->uid = uid; - - client->last_activity = g_timer_new(); - - client->cmd_list = NULL; - client->cmd_list_OK = -1; - client->cmd_list_size = 0; - - client->deferred_send = g_queue_new(); - client->deferred_bytes = 0; - client->num = next_client_num++; - - client->send_buf_used = 0; - - client->subscriptions = NULL; - client->messages = NULL; - client->num_messages = 0; + Client *client = new Client(loop, partition, fd, uid, + next_client_num++); (void)send(fd, GREETING, sizeof(GREETING) - 1, 0); @@ -133,33 +118,13 @@ client_new(struct player_control *player_control, g_free(remote); } -static void -deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct deferred_buffer *buffer = data; - g_free(buffer); -} - void -client_close(struct client *client) +Client::Close() { - client_list_remove(client); + client_list_remove(this); - client_set_expired(client); + SetExpired(); - g_timer_destroy(client->last_activity); - - if (client->cmd_list) { - free_cmd_list(client->cmd_list); - client->cmd_list = NULL; - } - - g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL); - g_queue_free(client->deferred_send); - - fifo_buffer_free(client->input); - - g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, - "[%u] closed", client->num); - g_free(client); + g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, "[%u] closed", num); + delete this; } diff --git a/src/client_process.c b/src/ClientProcess.cxx index 57a8a7824..bcd20d1b7 100644 --- a/src/client_process.c +++ b/src/ClientProcess.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,9 @@ */ #include "config.h" -#include "client_internal.h" +#include "ClientInternal.hxx" +#include "protocol/Result.hxx" +#include "AllCommands.hxx" #include <string.h> @@ -27,19 +29,20 @@ #define CLIENT_LIST_MODE_END "command_list_end" static enum command_return -client_process_command_list(struct client *client, bool list_ok, GSList *list) +client_process_command_list(Client *client, bool list_ok, + std::list<std::string> &&list) { enum command_return ret = COMMAND_RETURN_OK; unsigned num = 0; - for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) { - char *cmd = cur->data; + for (auto &&i : list) { + char *cmd = &*i.begin(); g_debug("command_process_list: process command \"%s\"", cmd); ret = command_process(client, num++, cmd); g_debug("command_process_list: command returned %i", ret); - if (ret != COMMAND_RETURN_OK || client_is_expired(client)) + if (ret != COMMAND_RETURN_OK || client->IsExpired()) break; else if (list_ok) client_puts(client, "list_OK\n"); @@ -49,7 +52,7 @@ client_process_command_list(struct client *client, bool list_ok, GSList *list) } enum command_return -client_process_line(struct client *client, char *line) +client_process_line(Client *client, char *line) { enum command_return ret; @@ -58,7 +61,6 @@ client_process_line(struct client *client, char *line) /* send empty idle response and leave idle mode */ client->idle_waiting = false; command_success(client); - client_write_output(client); } /* do nothing if the client wasn't idling: the client @@ -74,55 +76,44 @@ client_process_line(struct client *client, char *line) return COMMAND_RETURN_CLOSE; } - if (client->cmd_list_OK >= 0) { + if (client->cmd_list.IsActive()) { if (strcmp(line, CLIENT_LIST_MODE_END) == 0) { g_debug("[%u] process command list", client->num); - /* for scalability reasons, we have prepended - each new command; now we have to reverse it - to restore the correct order */ - client->cmd_list = g_slist_reverse(client->cmd_list); + auto &&cmd_list = client->cmd_list.Commit(); ret = client_process_command_list(client, - client->cmd_list_OK, - client->cmd_list); + client->cmd_list.IsOKMode(), + std::move(cmd_list)); g_debug("[%u] process command " "list returned %i", client->num, ret); if (ret == COMMAND_RETURN_CLOSE || - client_is_expired(client)) + client->IsExpired()) return COMMAND_RETURN_CLOSE; if (ret == COMMAND_RETURN_OK) command_success(client); - client_write_output(client); - free_cmd_list(client->cmd_list); - client->cmd_list = NULL; - client->cmd_list_OK = -1; + client->cmd_list.Reset(); } else { - size_t len = strlen(line) + 1; - client->cmd_list_size += len; - if (client->cmd_list_size > - client_max_command_list_size) { - g_warning("[%u] command list size (%lu) " + if (!client->cmd_list.Add(line)) { + g_warning("[%u] command list size " "is larger than the max (%lu)", client->num, - (unsigned long)client->cmd_list_size, (unsigned long)client_max_command_list_size); return COMMAND_RETURN_CLOSE; } - new_cmd_list_ptr(client, line); ret = COMMAND_RETURN_OK; } } else { if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) { - client->cmd_list_OK = 0; + client->cmd_list.Begin(false); ret = COMMAND_RETURN_OK; } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) { - client->cmd_list_OK = 1; + client->cmd_list.Begin(true); ret = COMMAND_RETURN_OK; } else { g_debug("[%u] process command \"%s\"", @@ -132,13 +123,11 @@ client_process_line(struct client *client, char *line) client->num, ret); if (ret == COMMAND_RETURN_CLOSE || - client_is_expired(client)) + client->IsExpired()) return COMMAND_RETURN_CLOSE; if (ret == COMMAND_RETURN_OK) command_success(client); - - client_write_output(client); } } diff --git a/src/ClientRead.cxx b/src/ClientRead.cxx new file mode 100644 index 000000000..363363d1f --- /dev/null +++ b/src/ClientRead.cxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" +#include "Main.hxx" +#include "event/Loop.hxx" + +#include <assert.h> +#include <string.h> + +BufferedSocket::InputResult +Client::OnSocketInput(const void *data, size_t length) +{ + g_timer_start(last_activity); + + const char *p = (const char *)data; + const char *newline = (const char *)memchr(p, '\n', length); + if (newline == NULL) + return InputResult::MORE; + + char *line = g_strndup(p, newline - p); + BufferedSocket::ConsumeInput(newline + 1 - p); + + enum command_return result = client_process_line(this, line); + g_free(line); + + switch (result) { + case COMMAND_RETURN_OK: + case COMMAND_RETURN_IDLE: + case COMMAND_RETURN_ERROR: + break; + + case COMMAND_RETURN_KILL: + Close(); + main_loop->Break(); + return InputResult::CLOSED; + + case COMMAND_RETURN_CLOSE: + Close(); + return InputResult::CLOSED; + } + + if (IsExpired()) { + Close(); + return InputResult::CLOSED; + } + + return InputResult::AGAIN; +} diff --git a/src/ClientSubscribe.cxx b/src/ClientSubscribe.cxx new file mode 100644 index 000000000..d8420266c --- /dev/null +++ b/src/ClientSubscribe.cxx @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientSubscribe.hxx" +#include "ClientIdle.hxx" +#include "ClientInternal.hxx" +#include "Idle.hxx" + +#include <assert.h> +#include <string.h> + +enum client_subscribe_result +client_subscribe(Client *client, const char *channel) +{ + assert(client != NULL); + assert(channel != NULL); + + if (!client_message_valid_channel_name(channel)) + return CLIENT_SUBSCRIBE_INVALID; + + if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) + return CLIENT_SUBSCRIBE_FULL; + + auto r = client->subscriptions.insert(channel); + if (!r.second) + return CLIENT_SUBSCRIBE_ALREADY; + + ++client->num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + return CLIENT_SUBSCRIBE_OK; +} + +bool +client_unsubscribe(Client *client, const char *channel) +{ + const auto i = client->subscriptions.find(channel); + if (i == client->subscriptions.end()) + return false; + + assert(client->num_subscriptions > 0); + + client->subscriptions.erase(i); + --client->num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + assert((client->num_subscriptions == 0) == + client->subscriptions.empty()); + + return true; +} + +void +client_unsubscribe_all(Client *client) +{ + client->subscriptions.clear(); + client->num_subscriptions = 0; +} + +bool +client_push_message(Client *client, const ClientMessage &msg) +{ + assert(client != NULL); + + if (client->messages.size() >= CLIENT_MAX_MESSAGES || + !client->IsSubscribed(msg.GetChannel())) + return false; + + if (client->messages.empty()) + client_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/ClientWrite.cxx b/src/ClientWrite.cxx new file mode 100644 index 000000000..23b515a3d --- /dev/null +++ b/src/ClientWrite.cxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" + +#include <string.h> +#include <stdio.h> + +/** + * Write a block of data to the client. + */ +static void +client_write(Client *client, const char *data, size_t length) +{ + /* if the client is going to be closed, do nothing */ + if (client->IsExpired() || length == 0) + return; + + client->Write(data, length); +} + +void +client_puts(Client *client, const char *s) +{ + client_write(client, s, strlen(s)); +} + +void +client_vprintf(Client *client, const char *fmt, va_list args) +{ +#ifndef G_OS_WIN32 + va_list tmp; + int length; + + va_copy(tmp, args); + length = vsnprintf(NULL, 0, fmt, tmp); + va_end(tmp); + + if (length <= 0) + /* wtf.. */ + return; + + char *buffer = (char *)g_malloc(length + 1); + vsnprintf(buffer, length + 1, fmt, args); + client_write(client, buffer, length); + g_free(buffer); +#else + /* On mingw32, snprintf() expects a 64 bit integer instead of + a "long int" for "%li". This is not consistent with our + expectation, so we're using plain sprintf() here, hoping + the static buffer is large enough. Sorry for this hack, + but WIN32 development is so painful, I'm not in the mood to + do it properly now. */ + + static char buffer[4096]; + vsprintf(buffer, fmt, args); + client_write(client, buffer, strlen(buffer)); +#endif +} + +G_GNUC_PRINTF(2, 3) +void +client_printf(Client *client, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + client_vprintf(client, fmt, args); + va_end(args); +} diff --git a/src/CommandError.cxx b/src/CommandError.cxx new file mode 100644 index 000000000..7e777d82a --- /dev/null +++ b/src/CommandError.cxx @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CommandError.hxx" +#include "db_error.h" +#include "io_error.h" +#include "protocol/Result.hxx" + +#include <assert.h> +#include <errno.h> + +enum command_return +print_playlist_result(Client *client, enum playlist_result result) +{ + switch (result) { + case PLAYLIST_RESULT_SUCCESS: + return COMMAND_RETURN_OK; + + case PLAYLIST_RESULT_ERRNO: + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(errno)); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_DENIED: + command_error(client, ACK_ERROR_PERMISSION, "Access denied"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NO_SUCH_SONG: + command_error(client, ACK_ERROR_NO_EXIST, "No such song"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NO_SUCH_LIST: + command_error(client, ACK_ERROR_NO_EXIST, "No such playlist"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_LIST_EXISTS: + command_error(client, ACK_ERROR_EXIST, + "Playlist already exists"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_BAD_NAME: + command_error(client, ACK_ERROR_ARG, + "playlist name is invalid: " + "playlist names may not contain slashes," + " newlines or carriage returns"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_BAD_RANGE: + command_error(client, ACK_ERROR_ARG, "Bad song index"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NOT_PLAYING: + command_error(client, ACK_ERROR_PLAYER_SYNC, "Not playing"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_TOO_LARGE: + command_error(client, ACK_ERROR_PLAYLIST_MAX, + "playlist is at the max size"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_DISABLED: + command_error(client, ACK_ERROR_UNKNOWN, + "stored playlist support is disabled"); + return COMMAND_RETURN_ERROR; + } + + assert(0); + return COMMAND_RETURN_ERROR; +} + +/** + * Send the GError to the client and free the GError. + */ +enum command_return +print_error(Client *client, GError *error) +{ + assert(client != NULL); + assert(error != NULL); + + g_warning("%s", error->message); + + if (error->domain == playlist_quark()) { + enum playlist_result result = (playlist_result)error->code; + g_error_free(error); + return print_playlist_result(client, result); + } else if (error->domain == ack_quark()) { + command_error(client, (ack)error->code, "%s", error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } else if (error->domain == db_quark()) { + switch ((enum db_error)error->code) { + case DB_DISABLED: + command_error(client, ACK_ERROR_NO_EXIST, "%s", + error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + + case DB_NOT_FOUND: + g_error_free(error); + command_error(client, ACK_ERROR_NO_EXIST, "Not found"); + return COMMAND_RETURN_ERROR; + } + } else if (error->domain == errno_quark()) { + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(error->code)); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } else if (error->domain == g_file_error_quark()) { + command_error(client, ACK_ERROR_SYSTEM, "%s", error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } + + g_error_free(error); + command_error(client, ACK_ERROR_UNKNOWN, "error"); + return COMMAND_RETURN_ERROR; +} diff --git a/src/CommandError.hxx b/src/CommandError.hxx new file mode 100644 index 000000000..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..a8608af9c 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,19 +18,19 @@ */ #include "config.h" -#include "cmdline.h" +#include "CommandLine.hxx" #include "path.h" -#include "log.h" +#include "ls.hxx" +#include "Log.hxx" #include "conf.h" #include "decoder_list.h" #include "decoder_plugin.h" -#include "output_list.h" +#include "OutputList.hxx" #include "output_plugin.h" -#include "input_registry.h" +#include "InputRegistry.hxx" #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 +159,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/CommandListBuilder.cxx b/src/CommandListBuilder.cxx new file mode 100644 index 000000000..cc10f7205 --- /dev/null +++ b/src/CommandListBuilder.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CommandListBuilder.hxx" +#include "ClientInternal.hxx" + +#include <string.h> + +void +CommandListBuilder::Reset() +{ + list.clear(); + mode = Mode::DISABLED; +} + +bool +CommandListBuilder::Add(const char *cmd) +{ + size_t len = strlen(cmd) + 1; + size += len; + if (size > client_max_command_list_size) + return false; + + list.emplace_back(cmd); + return true; +} diff --git a/src/CommandListBuilder.hxx b/src/CommandListBuilder.hxx new file mode 100644 index 000000000..a112ac33b --- /dev/null +++ b/src/CommandListBuilder.hxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_COMMAND_LIST_BUILDER_HXX +#define MPD_COMMAND_LIST_BUILDER_HXX + +#include <list> +#include <string> + +#include <assert.h> + +class CommandListBuilder { + /** + * print OK after each command execution + */ + enum class Mode { + /** + * Not active. + */ + DISABLED = -1, + + /** + * Enabled in normal list mode. + */ + ENABLED = false, + + /** + * Enabled in "list_OK" mode. + */ + OK = true, + } mode; + + /** + * for when in list mode + */ + std::list<std::string> list; + + /** + * Memory consumed by the list. + */ + size_t size; + +public: + CommandListBuilder() + :mode(Mode::DISABLED), size(0) {} + + /** + * Is a command list currently being built? + */ + bool IsActive() const { + return mode != Mode::DISABLED; + } + + /** + * Is the object in "list_OK" mode? + */ + bool IsOKMode() const { + assert(IsActive()); + + return (bool)mode; + } + + /** + * Reset the object: delete the list and clear the mode. + */ + void Reset(); + + /** + * Begin building a command list. + */ + void Begin(bool ok) { + assert(list.empty()); + assert(mode == Mode::DISABLED); + + mode = (Mode)ok; + } + + /** + * @return false if the list is full + */ + bool Add(const char *cmd); + + /** + * Finishes the list and returns it. + */ + std::list<std::string> &&Commit() { + assert(IsActive()); + + return std::move(list); + } +}; + +#endif diff --git a/src/conf.c b/src/ConfigFile.cxx index 167f2da92..fd08f52b1 100644 --- a/src/conf.c +++ b/src/ConfigFile.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,9 +19,13 @@ #include "config.h" #include "conf.h" + +extern "C" { #include "utils.h" #include "string_util.h" #include "tokenizer.h" +} + #include "path.h" #include "mpd_error.h" @@ -46,62 +50,68 @@ struct config_entry { const bool block; GSList *params; + + constexpr config_entry(const char *_name, + bool _repeatable, bool _block) + :name(_name), repeatable(_repeatable), block(_block), + params(nullptr) {} }; static struct config_entry config_entries[] = { - { .name = CONF_MUSIC_DIR, false, false }, - { .name = CONF_PLAYLIST_DIR, false, false }, - { .name = CONF_FOLLOW_INSIDE_SYMLINKS, false, false }, - { .name = CONF_FOLLOW_OUTSIDE_SYMLINKS, false, false }, - { .name = CONF_DB_FILE, false, false }, - { .name = CONF_STICKER_FILE, false, false }, - { .name = CONF_LOG_FILE, false, false }, - { .name = CONF_PID_FILE, false, false }, - { .name = CONF_STATE_FILE, false, false }, - { .name = "restore_paused", false, false }, - { .name = CONF_USER, false, false }, - { .name = CONF_GROUP, false, false }, - { .name = CONF_BIND_TO_ADDRESS, true, false }, - { .name = CONF_PORT, false, false }, - { .name = CONF_LOG_LEVEL, false, false }, - { .name = CONF_ZEROCONF_NAME, false, false }, - { .name = CONF_ZEROCONF_ENABLED, false, false }, - { .name = CONF_PASSWORD, true, false }, - { .name = CONF_DEFAULT_PERMS, false, false }, - { .name = CONF_AUDIO_OUTPUT, true, true }, - { .name = CONF_AUDIO_OUTPUT_FORMAT, false, false }, - { .name = CONF_MIXER_TYPE, false, false }, - { .name = CONF_REPLAYGAIN, false, false }, - { .name = CONF_REPLAYGAIN_PREAMP, false, false }, - { .name = CONF_REPLAYGAIN_MISSING_PREAMP, false, false }, - { .name = CONF_REPLAYGAIN_LIMIT, false, false }, - { .name = CONF_VOLUME_NORMALIZATION, false, false }, - { .name = CONF_SAMPLERATE_CONVERTER, false, false }, - { .name = CONF_AUDIO_BUFFER_SIZE, false, false }, - { .name = CONF_BUFFER_BEFORE_PLAY, false, false }, - { .name = CONF_HTTP_PROXY_HOST, false, false }, - { .name = CONF_HTTP_PROXY_PORT, false, false }, - { .name = CONF_HTTP_PROXY_USER, false, false }, - { .name = CONF_HTTP_PROXY_PASSWORD, false, false }, - { .name = CONF_CONN_TIMEOUT, false, false }, - { .name = CONF_MAX_CONN, false, false }, - { .name = CONF_MAX_PLAYLIST_LENGTH, false, false }, - { .name = CONF_MAX_COMMAND_LIST_SIZE, false, false }, - { .name = CONF_MAX_OUTPUT_BUFFER_SIZE, false, false }, - { .name = CONF_FS_CHARSET, false, false }, - { .name = CONF_ID3V1_ENCODING, false, false }, - { .name = CONF_METADATA_TO_USE, false, false }, - { .name = CONF_SAVE_ABSOLUTE_PATHS, false, false }, - { .name = CONF_DECODER, true, true }, - { .name = CONF_INPUT, true, true }, - { .name = CONF_GAPLESS_MP3_PLAYBACK, false, false }, - { .name = CONF_PLAYLIST_PLUGIN, true, true }, - { .name = CONF_AUTO_UPDATE, false, false }, - { .name = CONF_AUTO_UPDATE_DEPTH, false, false }, - { .name = CONF_DESPOTIFY_USER, false, false }, - { .name = CONF_DESPOTIFY_PASSWORD, false, false}, - { .name = CONF_DESPOTIFY_HIGH_BITRATE, false, false }, - { .name = "filter", true, true }, + { CONF_MUSIC_DIR, false, false }, + { CONF_PLAYLIST_DIR, false, false }, + { CONF_FOLLOW_INSIDE_SYMLINKS, false, false }, + { CONF_FOLLOW_OUTSIDE_SYMLINKS, false, false }, + { CONF_DB_FILE, false, false }, + { CONF_STICKER_FILE, false, false }, + { CONF_LOG_FILE, false, false }, + { CONF_PID_FILE, false, false }, + { CONF_STATE_FILE, false, false }, + { "restore_paused", false, false }, + { CONF_USER, false, false }, + { CONF_GROUP, false, false }, + { CONF_BIND_TO_ADDRESS, true, false }, + { CONF_PORT, false, false }, + { CONF_LOG_LEVEL, false, false }, + { CONF_ZEROCONF_NAME, false, false }, + { CONF_ZEROCONF_ENABLED, false, false }, + { CONF_PASSWORD, true, false }, + { CONF_DEFAULT_PERMS, false, false }, + { CONF_AUDIO_OUTPUT, true, true }, + { CONF_AUDIO_OUTPUT_FORMAT, false, false }, + { CONF_MIXER_TYPE, false, false }, + { CONF_REPLAYGAIN, false, false }, + { CONF_REPLAYGAIN_PREAMP, false, false }, + { CONF_REPLAYGAIN_MISSING_PREAMP, false, false }, + { CONF_REPLAYGAIN_LIMIT, false, false }, + { CONF_VOLUME_NORMALIZATION, false, false }, + { CONF_SAMPLERATE_CONVERTER, false, false }, + { CONF_AUDIO_BUFFER_SIZE, false, false }, + { CONF_BUFFER_BEFORE_PLAY, false, false }, + { CONF_HTTP_PROXY_HOST, false, false }, + { CONF_HTTP_PROXY_PORT, false, false }, + { CONF_HTTP_PROXY_USER, false, false }, + { CONF_HTTP_PROXY_PASSWORD, false, false }, + { CONF_CONN_TIMEOUT, false, false }, + { CONF_MAX_CONN, false, false }, + { CONF_MAX_PLAYLIST_LENGTH, false, false }, + { CONF_MAX_COMMAND_LIST_SIZE, false, false }, + { CONF_MAX_OUTPUT_BUFFER_SIZE, false, false }, + { CONF_FS_CHARSET, false, false }, + { CONF_ID3V1_ENCODING, false, false }, + { CONF_METADATA_TO_USE, false, false }, + { CONF_SAVE_ABSOLUTE_PATHS, false, false }, + { CONF_DECODER, true, true }, + { CONF_INPUT, true, true }, + { CONF_GAPLESS_MP3_PLAYBACK, false, false }, + { CONF_PLAYLIST_PLUGIN, true, true }, + { CONF_AUTO_UPDATE, false, false }, + { CONF_AUTO_UPDATE_DEPTH, false, false }, + { CONF_DESPOTIFY_USER, false, false }, + { CONF_DESPOTIFY_PASSWORD, false, false}, + { CONF_DESPOTIFY_HIGH_BITRATE, false, false }, + { "filter", true, true }, + { "database", false, true }, }; static bool @@ -161,7 +171,7 @@ config_param_free(struct config_param *param) static void config_param_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) { - struct config_param *param = data; + struct config_param *param = (struct config_param *)data; config_param_free(param); } @@ -196,7 +206,7 @@ void config_global_init(void) static void config_param_check(gpointer data, G_GNUC_UNUSED gpointer user_data) { - struct config_param *param = data; + struct config_param *param = (struct config_param *)data; if (!param->used) /* this whole config_param was not queried at all - @@ -232,9 +242,10 @@ config_add_block_param(struct config_param * param, const char *name, param->num_block_params++; - param->block_params = g_realloc(param->block_params, - param->num_block_params * - sizeof(param->block_params[0])); + param->block_params = (struct block_param *) + g_realloc(param->block_params, + param->num_block_params * + sizeof(param->block_params[0])); bp = ¶m->block_params[param->num_block_params - 1]; @@ -317,7 +328,7 @@ config_read_block(FILE *fp, int *count, char *string, GError **error_r) g_set_error(error_r, config_quark(), 0, "line %i: Unknown tokens after '}'", *count); - return false; + return nullptr; } return ret; @@ -389,7 +400,7 @@ config_read_file(const char *file, GError **error_r) } if (entry->params != NULL && !entry->repeatable) { - param = entry->params->data; + param = (struct config_param *)entry->params->data; g_set_error(error_r, config_quark(), 0, "config parameter \"%s\" is first defined " "on line %i and redefined on line %i\n", @@ -486,7 +497,7 @@ config_get_next_param(const char *name, const struct config_param * last) if (node == NULL) return NULL; - param = node->data; + param = (struct config_param *)node->data; param->used = true; return param; } diff --git a/src/crossfade.c b/src/CrossFade.cxx index 46a0dff30..0bdcc43d6 100644 --- a/src/crossfade.c +++ b/src/CrossFade.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,13 @@ */ #include "config.h" -#include "crossfade.h" -#include "chunk.h" +#include "CrossFade.hxx" +#include "MusicChunk.hxx" #include "audio_format.h" #include "tag.h" +#include <cmath> + #include <assert.h> #include <string.h> #include <stdlib.h> @@ -82,9 +84,8 @@ static float mixramp_interpolate(char *ramp_list, float required_db) } /* If required db < any stored value, use the least. */ - if (isnan(last_db)) { + if (std::isnan(last_db)) return secs; - } /* Finally, interpolate linearly. */ secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db); @@ -114,13 +115,14 @@ unsigned cross_fade_calc(float duration, float total_time, chunks_f = (float)audio_format_time_to_size(af) / (float)CHUNK_SIZE; - if (isnan(mixramp_delay) || !(mixramp_start) || !(mixramp_prev_end)) { + if (std::isnan(mixramp_delay) || !mixramp_start || !mixramp_prev_end) { chunks = (chunks_f * duration + 0.5); } else { /* Calculate mixramp overlap. */ mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db - replay_gain_db) + mixramp_interpolate(mixramp_prev_end, mixramp_db - replay_gain_prev_db); - if (!isnan(mixramp_overlap) && (mixramp_delay <= mixramp_overlap)) { + if (!std::isnan(mixramp_overlap) && + mixramp_delay <= mixramp_overlap) { chunks = (chunks_f * (mixramp_overlap - mixramp_delay)); g_debug("will overlap %d chunks, %fs", chunks, mixramp_overlap - mixramp_delay); diff --git a/src/crossfade.h b/src/CrossFade.hxx index d581dbfe0..1c4670758 100644 --- a/src/crossfade.h +++ b/src/CrossFade.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_CROSSFADE_H -#define MPD_CROSSFADE_H +#ifndef MPD_CROSSFADE_HXX +#define MPD_CROSSFADE_HXX struct audio_format; struct music_chunk; diff --git a/src/DatabaseCommands.cxx b/src/DatabaseCommands.cxx new file mode 100644 index 000000000..bd5a48b35 --- /dev/null +++ b/src/DatabaseCommands.cxx @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseCommands.hxx" +#include "DatabaseQueue.hxx" +#include "DatabasePlaylist.hxx" +#include "DatabasePrint.hxx" +#include "DatabaseSelection.hxx" +#include "CommandError.hxx" +#include "ClientInternal.hxx" +#include "tag.h" +#include "uri.h" +#include "SongFilter.hxx" +#include "protocol/Result.hxx" + +#include <assert.h> +#include <string.h> + +enum command_return +handle_lsinfo2(Client *client, int argc, char *argv[]) +{ + const char *uri; + + if (argc == 2) + uri = argv[1]; + else + /* default is root directory */ + uri = ""; + + const DatabaseSelection selection(uri, false); + + GError *error = NULL; + if (!db_selection_print(client, selection, true, &error)) + return print_error(client, error); + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_match(Client *client, int argc, char *argv[], bool fold_case) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + const DatabaseSelection selection("", true, &filter); + + GError *error = NULL; + return db_selection_print(client, selection, true, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_find(Client *client, int argc, char *argv[]) +{ + return handle_match(client, argc, argv, false); +} + +enum command_return +handle_search(Client *client, int argc, char *argv[]) +{ + return handle_match(client, argc, argv, true); +} + +static enum command_return +handle_match_add(Client *client, int argc, char *argv[], bool fold_case) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + const DatabaseSelection selection("", true, &filter); + GError *error = NULL; + return AddFromDatabase(client->partition, selection, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_findadd(Client *client, int argc, char *argv[]) +{ + return handle_match_add(client, argc, argv, false); +} + +enum command_return +handle_searchadd(Client *client, int argc, char *argv[]) +{ + return handle_match_add(client, argc, argv, true); +} + +enum command_return +handle_searchaddpl(Client *client, int argc, char *argv[]) +{ + const char *playlist = argv[1]; + + SongFilter filter; + if (!filter.Parse(argc - 2, argv + 2, true)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + GError *error = NULL; + return search_add_to_playlist("", playlist, &filter, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_count(Client *client, int argc, char *argv[]) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, false)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + GError *error = NULL; + return searchStatsForSongsIn(client, "", &filter, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_listall(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *directory = ""; + + if (argc == 2) + directory = argv[1]; + + GError *error = NULL; + return printAllIn(client, directory, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_list(Client *client, int argc, char *argv[]) +{ + unsigned tagType = locate_parse_type(argv[1]); + + if (tagType == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]); + return COMMAND_RETURN_ERROR; + } + + if (tagType == LOCATE_TAG_ANY_TYPE) { + command_error(client, ACK_ERROR_ARG, + "\"any\" is not a valid return tag type"); + return COMMAND_RETURN_ERROR; + } + + /* for compatibility with < 0.12.0 */ + SongFilter *filter; + if (argc == 3) { + if (tagType != TAG_ALBUM) { + command_error(client, ACK_ERROR_ARG, + "should be \"%s\" for 3 arguments", + tag_item_names[TAG_ALBUM]); + return COMMAND_RETURN_ERROR; + } + + filter = new SongFilter((unsigned)TAG_ARTIST, argv[2]); + } else if (argc > 2) { + filter = new SongFilter(); + if (!filter->Parse(argc - 2, argv + 2, false)) { + delete filter; + command_error(client, ACK_ERROR_ARG, + "not able to parse args"); + return COMMAND_RETURN_ERROR; + } + } else + filter = nullptr; + + GError *error = NULL; + enum command_return ret = + listAllUniqueTags(client, tagType, filter, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); + + delete filter; + + return ret; +} + +enum command_return +handle_listallinfo(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *directory = ""; + + if (argc == 2) + directory = argv[1]; + + GError *error = NULL; + return printInfoForAllIn(client, directory, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} diff --git a/src/DatabaseCommands.hxx b/src/DatabaseCommands.hxx new file mode 100644 index 000000000..335adc4d6 --- /dev/null +++ b/src/DatabaseCommands.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_COMMANDS_HXX +#define MPD_DATABASE_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_lsinfo2(Client *client, int argc, char *argv[]); + +enum command_return +handle_find(Client *client, int argc, char *argv[]); + +enum command_return +handle_findadd(Client *client, int argc, char *argv[]); + +enum command_return +handle_search(Client *client, int argc, char *argv[]); + +enum command_return +handle_searchadd(Client *client, int argc, char *argv[]); + +enum command_return +handle_searchaddpl(Client *client, int argc, char *argv[]); + +enum command_return +handle_count(Client *client, int argc, char *argv[]); + +enum command_return +handle_listall(Client *client, int argc, char *argv[]); + +enum command_return +handle_list(Client *client, int argc, char *argv[]); + +enum command_return +handle_listallinfo(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/database.c b/src/DatabaseGlue.cxx index 8c903bb45..853e67b07 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" +#include "conf.h" + +extern "C" { #include "db_error.h" -#include "db_save.h" -#include "db_selection.h" -#include "db_visitor.h" -#include "db_plugin.h" -#include "db/simple_db_plugin.h" -#include "directory.h" #include "stats.h" -#include "conf.h" #include "glib_compat.h" +} + +#include "DatabasePlugin.hxx" +#include "db/SimpleDatabasePlugin.hxx" #include <glib.h> @@ -42,97 +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; + const char *plugin_name = + config_get_block_string(param, "plugin", "simple"); + is_simple = strcmp(plugin_name, "simple") == 0; - struct config_param *param = config_new_param("database", path->line); - config_add_block_param(param, "path", path->value, path->line); - - db = db_plugin_new(&simple_db_plugin, param, error_r); - - config_param_free(param); + const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name); + if (plugin == NULL) { + g_set_error(error_r, db_quark(), 0, + "No such database plugin: %s", plugin_name); + return false; + } + db = plugin->create(param, error_r); return db != NULL; } void -db_finish(void) +DatabaseGlobalDeinit(void) { if (db_is_open) - db_plugin_close(db); + db->Close(); if (db != NULL) - db_plugin_free(db); + delete db; } -struct directory * -db_get_root(void) +const Database * +GetDatabase() { - assert(db != NULL); + assert(db == NULL || db_is_open); - return simple_db_get_root(db); + return db; } -struct directory * -db_get_directory(const char *name) +const Database * +GetDatabase(GError **error_r) { - if (db == NULL) - return NULL; + assert(db == nullptr || db_is_open); - struct directory *music_root = db_get_root(); - if (name == NULL) - return music_root; + if (db == nullptr) + g_set_error_literal(error_r, db_quark(), DB_DISABLED, + "No database"); - struct directory *directory = - directory_lookup_directory(music_root, name); - return directory; + return db; } -struct song * -db_get_song(const char *file) +bool +db_is_simple(void) { - assert(file != NULL); + assert(db == NULL || db_is_open); - g_debug("get song: %s", file); - - if (db == NULL) - return NULL; - - return db_plugin_get_song(db, file, NULL); + return is_simple; } -bool -db_visit(const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r) +Directory * +db_get_root(void) { - if (db == NULL) { - g_set_error_literal(error_r, db_quark(), DB_DISABLED, - "No database"); - return false; - } + assert(db != NULL); + assert(db_is_simple()); - return db_plugin_visit(db, selection, visitor, ctx, error_r); + return ((SimpleDatabase *)db)->GetRoot(); } -bool -db_walk(const char *uri, - const struct db_visitor *visitor, void *ctx, - GError **error_r) +Directory * +db_get_directory(const char *name) { - struct db_selection selection; - db_selection_init(&selection, uri, true); + if (db == NULL) + return NULL; + + Directory *music_root = db_get_root(); + if (name == NULL) + return music_root; - return db_visit(&selection, visitor, ctx, error_r); + return music_root->LookupDirectory(name); } bool @@ -140,17 +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/notify.h b/src/DatabaseGlue.hxx index 40821690c..ea26f3242 100644 --- a/src/notify.h +++ b/src/DatabaseGlue.hxx @@ -17,37 +17,43 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_NOTIFY_H -#define MPD_NOTIFY_H +#ifndef MPD_DATABASE_GLUE_HXX +#define MPD_DATABASE_GLUE_HXX -#include <glib.h> +#include "gcc.h" +#include "gerror.h" -#include <stdbool.h> - -struct notify { - GMutex *mutex; - GCond *cond; - bool pending; -}; - -void notify_init(struct notify *notify); - -void notify_deinit(struct notify *notify); +struct config_param; +class Database; /** - * Wait for a notification. Return immediately if we have already - * been notified since we last returned from notify_wait(). + * Initialize the database library. + * + * @param param the database configuration block */ -void notify_wait(struct notify *notify); +bool +DatabaseGlobalInit(const config_param *param, GError **error_r); + +void +DatabaseGlobalDeinit(void); + +bool +DatabaseGlobalOpen(GError **error); /** - * Notify the thread. This function never blocks. + * Returns the global #Database instance. May return NULL if this MPD + * configuration has no database (no music_directory was configured). */ -void notify_signal(struct notify *notify); +gcc_pure +const Database * +GetDatabase(); /** - * Clears a pending notification. + * Returns the global #Database instance. May return NULL if this MPD + * configuration has no database (no music_directory was configured). */ -void notify_clear(struct notify *notify); +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..d0327d794 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) @@ -26,7 +26,7 @@ #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif -GStaticMutex db_mutex = G_STATIC_MUTEX_INIT; +Mutex db_mutex; #ifndef NDEBUG GThread *db_mutex_holder; diff --git a/src/db_lock.h b/src/DatabaseLock.hxx index 4640502f3..371a7d7b2 100644 --- a/src/db_lock.h +++ b/src/DatabaseLock.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,16 +23,16 @@ * multi-threading. */ -#ifndef MPD_DB_LOCK_H -#define MPD_DB_LOCK_H +#ifndef MPD_DB_LOCK_HXX +#define MPD_DB_LOCK_HXX #include "check.h" +#include "thread/Mutex.hxx" #include <glib.h> #include <assert.h> -#include <stdbool.h> -extern GStaticMutex db_mutex; +extern Mutex db_mutex; #ifndef NDEBUG @@ -59,7 +59,7 @@ db_lock(void) { assert(!holding_db_lock()); - g_static_mutex_lock(&db_mutex); + db_mutex.lock(); assert(db_mutex_holder == NULL); #ifndef NDEBUG @@ -78,7 +78,22 @@ db_unlock(void) db_mutex_holder = NULL; #endif - g_static_mutex_unlock(&db_mutex); + db_mutex.unlock(); } +#ifdef __cplusplus + +class ScopeDatabaseLock { +public: + ScopeDatabaseLock() { + db_lock(); + } + + ~ScopeDatabaseLock() { + db_unlock(); + } +}; + +#endif + #endif diff --git a/src/DatabasePlaylist.cxx b/src/DatabasePlaylist.cxx new file mode 100644 index 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_save.h b/src/DatabasePlaylist.hxx index e760ec881..7c6952ffa 100644 --- a/src/db_save.h +++ b/src/DatabasePlaylist.hxx @@ -17,19 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DB_SAVE_H -#define MPD_DB_SAVE_H +#ifndef MPD_DATABASE_PLAYLIST_HXX +#define MPD_DATABASE_PLAYLIST_HXX -#include <glib.h> -#include <stdbool.h> -#include <stdio.h> +#include "gcc.h" +#include "gerror.h" -struct directory; - -void -db_save_internal(FILE *file, const struct directory *root); +class SongFilter; +gcc_nonnull(1,2) bool -db_load_internal(FILE *file, struct directory *root, GError **error); +search_add_to_playlist(const char *uri, const char *path_utf8, + const SongFilter *filter, + GError **error_r); #endif diff --git a/src/DatabasePlugin.hxx b/src/DatabasePlugin.hxx new file mode 100644 index 000000000..a175b3cd9 --- /dev/null +++ b/src/DatabasePlugin.hxx @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This header declares the db_plugin class. It describes a + * plugin API for databases of song metadata. + */ + +#ifndef MPD_DATABASE_PLUGIN_HXX +#define MPD_DATABASE_PLUGIN_HXX + +#include "DatabaseVisitor.hxx" +#include "gcc.h" + +extern "C" { +#include "tag.h" +} + +struct config_param; +struct DatabaseSelection; +struct db_visitor; + +struct DatabaseStats { + /** + * Number of songs. + */ + unsigned song_count; + + /** + * Total duration of all songs (in seconds). + */ + unsigned long total_duration; + + /** + * Number of distinct artist names. + */ + unsigned artist_count; + + /** + * Number of distinct album names. + */ + unsigned album_count; + + void Clear() { + song_count = 0; + total_duration = 0; + artist_count = album_count = 0; + } +}; + +class Database { +public: + /** + * Free instance data. + */ + virtual ~Database() {} + + /** + * Open the database. Read it into memory if applicable. + */ + virtual bool Open(gcc_unused GError **error_r) { + return true; + } + + /** + * Close the database, free allocated memory. + */ + virtual void Close() {} + + /** + * Look up a song (including tag data) in the database. When + * you don't need this anymore, call ReturnSong(). + * + * @param uri_utf8 the URI of the song within the music + * directory (UTF-8) + */ + virtual struct song *GetSong(const char *uri_utf8, + GError **error_r) const = 0; + + /** + * Mark the song object as "unused". Call this on objects + * returned by GetSong(). + */ + virtual void ReturnSong(struct song *song) const = 0; + + /** + * Visit the selected entities. + */ + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const = 0; + + bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + GError **error_r) const { + return Visit(selection, visit_directory, visit_song, + VisitPlaylist(), error_r); + } + + bool Visit(const DatabaseSelection &selection, VisitSong visit_song, + GError **error_r) const { + return Visit(selection, VisitDirectory(), visit_song, error_r); + } + + /** + * Visit all unique tag values. + */ + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r) const = 0; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + GError **error_r) const = 0; +}; + +struct DatabasePlugin { + const char *name; + + /** + * Allocates and configures a database. + */ + Database *(*create)(const struct config_param *param, + GError **error_r); +}; + +#endif diff --git a/src/DatabasePrint.cxx b/src/DatabasePrint.cxx new file mode 100644 index 000000000..2384d5c14 --- /dev/null +++ b/src/DatabasePrint.cxx @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabasePrint.hxx" +#include "DatabaseSelection.hxx" +#include "SongFilter.hxx" +#include "PlaylistVector.hxx" +#include "SongPrint.hxx" +#include "TimePrint.hxx" +#include "Directory.hxx" +#include "Client.hxx" +#include "tag.h" + +extern "C" { +#include "song.h" +} + +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +#include <functional> + +static bool +PrintDirectory(Client *client, const Directory &directory) +{ + if (!directory.IsRoot()) + client_printf(client, "directory: %s\n", directory.GetPath()); + + return true; +} + +static void +print_playlist_in_directory(Client *client, + const Directory &directory, + const char *name_utf8) +{ + if (directory.IsRoot()) + client_printf(client, "playlist: %s\n", name_utf8); + else + client_printf(client, "playlist: %s/%s\n", + directory.GetPath(), name_utf8); +} + +static bool +PrintSongBrief(Client *client, song &song) +{ + assert(song.parent != NULL); + + song_print_uri(client, &song); + + if (song.tag != NULL && song.tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, *song.parent, song.uri); + + return true; +} + +static bool +PrintSongFull(Client *client, song &song) +{ + assert(song.parent != NULL); + + song_print_info(client, &song); + + if (song.tag != NULL && song.tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, *song.parent, song.uri); + + return true; +} + +static bool +PrintPlaylistBrief(Client *client, + const PlaylistInfo &playlist, + const Directory &directory) +{ + print_playlist_in_directory(client, directory, playlist.name.c_str()); + return true; +} + +static bool +PrintPlaylistFull(Client *client, + const PlaylistInfo &playlist, + const Directory &directory) +{ + print_playlist_in_directory(client, directory, playlist.name.c_str()); + + if (playlist.mtime > 0) + time_print(client, "Last-Modified", playlist.mtime); + + return true; +} + +bool +db_selection_print(Client *client, const DatabaseSelection &selection, + bool full, GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + using namespace std::placeholders; + const auto d = selection.filter == nullptr + ? std::bind(PrintDirectory, client, _1) + : VisitDirectory(); + const auto s = std::bind(full ? PrintSongFull : PrintSongBrief, + client, _1); + const auto p = selection.filter == nullptr + ? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief, + client, _1, _2) + : VisitPlaylist(); + + return db->Visit(selection, d, s, p, error_r); +} + +struct SearchStats { + int numberOfSongs; + unsigned long playTime; +}; + +static void printSearchStats(Client *client, SearchStats *stats) +{ + client_printf(client, "songs: %i\n", stats->numberOfSongs); + client_printf(client, "playtime: %li\n", stats->playTime); +} + +static bool +stats_visitor_song(SearchStats &stats, song &song) +{ + stats.numberOfSongs++; + stats.playTime += song_get_duration(&song); + + return true; +} + +bool +searchStatsForSongsIn(Client *client, const char *name, + const SongFilter *filter, + GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + const DatabaseSelection selection(name, true, filter); + + SearchStats stats; + stats.numberOfSongs = 0; + stats.playTime = 0; + + using namespace std::placeholders; + const auto f = std::bind(stats_visitor_song, std::ref(stats), + _1); + if (!db->Visit(selection, f, error_r)) + return false; + + printSearchStats(client, &stats); + return true; +} + +bool +printAllIn(Client *client, const char *uri_utf8, GError **error_r) +{ + const DatabaseSelection selection(uri_utf8, true); + return db_selection_print(client, selection, false, error_r); +} + +bool +printInfoForAllIn(Client *client, const char *uri_utf8, + GError **error_r) +{ + const DatabaseSelection selection(uri_utf8, true); + return db_selection_print(client, selection, true, error_r); +} + +static bool +PrintSongURIVisitor(Client *client, song &song) +{ + song_print_uri(client, &song); + + return true; +} + +static bool +PrintUniqueTag(Client *client, enum tag_type tag_type, + const char *value) +{ + client_printf(client, "%s: %s\n", tag_item_names[tag_type], value); + return true; +} + +bool +listAllUniqueTags(Client *client, int type, + const SongFilter *filter, + GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + const DatabaseSelection selection("", true, filter); + + if (type == LOCATE_TAG_FILE_TYPE) { + using namespace std::placeholders; + const auto f = std::bind(PrintSongURIVisitor, client, _1); + return db->Visit(selection, f, error_r); + } else { + using namespace std::placeholders; + const auto f = std::bind(PrintUniqueTag, client, + (enum tag_type)type, _1); + return db->VisitUniqueTags(selection, (enum tag_type)type, + f, error_r); + } +} diff --git a/src/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..e22144c07 --- /dev/null +++ b/src/DatabaseQueue.cxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseQueue.hxx" +#include "DatabaseSelection.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "Partition.hxx" + +#include <functional> + +static bool +AddToQueue(Partition &partition, song &song, GError **error_r) +{ + enum playlist_result result = + partition.playlist.AppendSong(partition.pc, &song, NULL); + if (result != PLAYLIST_RESULT_SUCCESS) { + g_set_error(error_r, playlist_quark(), result, + "Playlist error"); + return false; + } + + return true; +} + +bool +AddFromDatabase(Partition &partition, const DatabaseSelection &selection, + GError **error_r) +{ + const Database *db = GetDatabase(error_r); + if (db == nullptr) + return false; + + using namespace std::placeholders; + const auto f = std::bind(AddToQueue, std::ref(partition), _1, _2); + return db->Visit(selection, f, error_r); +} diff --git a/src/db_internal.h b/src/DatabaseQueue.hxx index a33351524..bae5b1f05 100644 --- a/src/db_internal.h +++ b/src/DatabaseQueue.hxx @@ -17,19 +17,16 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DB_INTERNAL_H -#define MPD_DB_INTERNAL_H +#ifndef MPD_DATABASE_QUEUE_HXX +#define MPD_DATABASE_QUEUE_HXX -#include "db_plugin.h" +#include "gerror.h" -#include <assert.h> +struct Partition; +struct DatabaseSelection; -static inline void -db_base_init(struct db *db, const struct db_plugin *plugin) -{ - assert(plugin != NULL); - - db->plugin = plugin; -} +bool +AddFromDatabase(Partition &partition, const DatabaseSelection &selection, + GError **error_r); #endif diff --git a/src/DatabaseRegistry.cxx b/src/DatabaseRegistry.cxx new file mode 100644 index 000000000..cf01decdd --- /dev/null +++ b/src/DatabaseRegistry.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseRegistry.hxx" +#include "db/SimpleDatabasePlugin.hxx" +#include "db/ProxyDatabasePlugin.hxx" + +#include <string.h> + +const DatabasePlugin *const database_plugins[] = { + &simple_db_plugin, +#ifdef HAVE_LIBMPDCLIENT + &proxy_db_plugin, +#endif + NULL +}; + +const DatabasePlugin * +GetDatabasePluginByName(const char *name) +{ + for (auto i = database_plugins; *i != nullptr; ++i) + if (strcmp((*i)->name, name) == 0) + return *i; + + return nullptr; +} diff --git a/src/DatabaseRegistry.hxx b/src/DatabaseRegistry.hxx new file mode 100644 index 000000000..4be581573 --- /dev/null +++ b/src/DatabaseRegistry.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_REGISTRY_HXX +#define MPD_DATABASE_REGISTRY_HXX + +#include "gcc.h" + +struct DatabasePlugin; + +/** + * NULL terminated list of all database plugins which were enabled at + * compile time. + */ +extern const DatabasePlugin *const database_plugins[]; + +gcc_pure +const DatabasePlugin * +GetDatabasePluginByName(const char *name); + +#endif diff --git a/src/db_save.c b/src/DatabaseSave.cxx index 4af9d58b8..5100c2a0b 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 "path.h" -#include "text_file.h" +#include "TextFile.hxx" +#include "TagInternal.hxx" #include "tag.h" -#include "tag_internal.h" + +extern "C" { +#include "path.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/decoder_print.h b/src/DatabaseSelection.cxx index 31713d5d8..bd756f5f9 100644 --- a/src/decoder_print.h +++ b/src/DatabaseSelection.cxx @@ -17,12 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DECODER_PRINT_H -#define MPD_DECODER_PRINT_H +#include "DatabaseSelection.hxx" +#include "SongFilter.hxx" -struct client; - -void -decoder_list_print(struct client *client); - -#endif +bool +DatabaseSelection::Match(const song &song) const +{ + return filter == nullptr || filter->Match(song); +} diff --git a/src/db_selection.h b/src/DatabaseSelection.hxx index 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/DatabaseVisitor.hxx b/src/DatabaseVisitor.hxx new file mode 100644 index 000000000..c90441415 --- /dev/null +++ b/src/DatabaseVisitor.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_VISITOR_HXX +#define MPD_DATABASE_VISITOR_HXX + +#include "gerror.h" + +#include <functional> + +struct Directory; +struct song; +struct PlaylistInfo; + +typedef std::function<bool(const Directory &, GError **)> VisitDirectory; +typedef std::function<bool(struct song &, GError **)> VisitSong; +typedef std::function<bool(const PlaylistInfo &, const Directory &, + GError **)> VisitPlaylist; + +typedef std::function<bool(const char *, GError **)> VisitString; + +#endif diff --git a/src/decoder_api.c b/src/DecoderAPI.cxx index a45d0f1e6..49ebacd49 100644 --- a/src/decoder_api.c +++ b/src/DecoderAPI.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,14 +19,18 @@ #include "config.h" #include "decoder_api.h" -#include "decoder_internal.h" -#include "decoder_control.h" + +extern "C" { #include "audio_config.h" -#include "song.h" -#include "buffer.h" -#include "pipe.h" -#include "chunk.h" +} + #include "replay_gain_config.h" +#include "MusicChunk.hxx" +#include "MusicBuffer.hxx" +#include "MusicPipe.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "song.h" #include <glib.h> @@ -362,11 +366,10 @@ update_stream_tag(struct decoder *decoder, struct input_stream *is) enum decoder_command decoder_data(struct decoder *decoder, struct input_stream *is, - const void *_data, size_t length, + const void *data, size_t length, uint16_t kbit_rate) { struct decoder_control *dc = decoder->dc; - const char *data = _data; GError *error = NULL; enum decoder_command cmd; @@ -417,7 +420,6 @@ decoder_data(struct decoder *decoder, while (length > 0) { struct music_chunk *chunk; - char *dest; size_t nbytes; bool full; @@ -427,10 +429,10 @@ decoder_data(struct decoder *decoder, return dc->command; } - dest = music_chunk_write(chunk, &dc->out_audio_format, - decoder->timestamp - - dc->song->start_ms / 1000.0, - kbit_rate, &nbytes); + void *dest = chunk->Write(dc->out_audio_format, + decoder->timestamp - + dc->song->start_ms / 1000.0, + kbit_rate, &nbytes); if (dest == NULL) { /* the chunk is full, flush it */ decoder_flush_chunk(decoder); @@ -449,14 +451,14 @@ decoder_data(struct decoder *decoder, /* expand the music pipe chunk */ - full = music_chunk_expand(chunk, &dc->out_audio_format, nbytes); + full = chunk->Expand(dc->out_audio_format, nbytes); if (full) { /* the chunk is full, flush it */ decoder_flush_chunk(decoder); g_cond_signal(dc->client_cond); } - data += nbytes; + data = (const uint8_t *)data + nbytes; length -= nbytes; decoder->timestamp += (double)nbytes / @@ -517,11 +519,10 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, return cmd; } -float +void decoder_replay_gain(struct decoder *decoder, const struct replay_gain_info *replay_gain_info) { - float return_db = 0; assert(decoder != NULL); if (replay_gain_info != NULL) { @@ -530,9 +531,13 @@ decoder_replay_gain(struct decoder *decoder, serial = 1; if (REPLAY_GAIN_OFF != replay_gain_mode) { - return_db = 20.0 * log10f( + enum replay_gain_mode rgm = replay_gain_mode; + if (rgm != REPLAY_GAIN_ALBUM) + rgm = REPLAY_GAIN_TRACK; + + decoder->dc->replay_gain_db = 20.0 * log10f( replay_gain_tuple_scale( - &replay_gain_info->tuples[replay_gain_get_real_mode()], + &replay_gain_info->tuples[rgm], replay_gain_preamp, replay_gain_missing_preamp, replay_gain_limit)); } @@ -549,19 +554,16 @@ decoder_replay_gain(struct decoder *decoder, } } else decoder->replay_gain_serial = 0; - - return return_db; } void -decoder_mixramp(struct decoder *decoder, float replay_gain_db, +decoder_mixramp(struct decoder *decoder, char *mixramp_start, char *mixramp_end) { assert(decoder != NULL); struct decoder_control *dc = decoder->dc; assert(dc != NULL); - dc->replay_gain_db = replay_gain_db; dc_mixramp_start(dc, mixramp_start); dc_mixramp_end(dc, mixramp_end); } diff --git a/src/decoder_control.c b/src/DecoderControl.cxx index 2ce03b666..2ffaf116f 100644 --- a/src/decoder_control.c +++ b/src/DecoderControl.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,9 @@ */ #include "config.h" -#include "decoder_control.h" -#include "pipe.h" +#include "DecoderControl.hxx" +#include "MusicPipe.hxx" +#include "song.h" #include <assert.h> @@ -27,7 +28,7 @@ #define G_LOG_DOMAIN "decoder_control" struct decoder_control * -dc_new(GCond *client_cond) +dc_new() { struct decoder_control *dc = g_new(struct decoder_control, 1); @@ -35,11 +36,13 @@ dc_new(GCond *client_cond) dc->mutex = g_mutex_new(); dc->cond = g_cond_new(); - dc->client_cond = client_cond; + dc->client_cond = g_cond_new(); 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,12 @@ 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->client_cond); g_cond_free(dc->cond); g_mutex_free(dc->mutex); g_free(dc->mixramp_start); @@ -79,6 +88,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 +104,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 +135,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/DecoderControl.hxx index 566b153ee..42c28d785 100644 --- a/src/decoder_control.h +++ b/src/DecoderControl.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_CONTROL_H -#define MPD_DECODER_CONTROL_H +#ifndef MPD_DECODER_CONTROL_HXX +#define MPD_DECODER_CONTROL_HXX #include "decoder_command.h" #include "audio_format.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 @@ -122,7 +133,7 @@ struct decoder_control { G_GNUC_MALLOC struct decoder_control * -dc_new(GCond *client_cond); +dc_new(); void dc_free(struct decoder_control *dc); @@ -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_internal.c b/src/DecoderInternal.cxx index bc349f2ff..6970a220f 100644 --- a/src/decoder_internal.c +++ b/src/DecoderInternal.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,15 +18,33 @@ */ #include "config.h" -#include "decoder_internal.h" -#include "decoder_control.h" -#include "pipe.h" +#include "DecoderInternal.hxx" +#include "DecoderControl.hxx" +#include "MusicPipe.hxx" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "tag.h" #include "input_stream.h" -#include "buffer.h" -#include "chunk.h" #include <assert.h> +decoder::~decoder() +{ + /* caller must flush the chunk */ + assert(chunk == nullptr); + + if (song_tag != nullptr) + tag_free(song_tag); + + if (stream_tag != nullptr) + tag_free(stream_tag); + + if (decoder_tag != nullptr) + tag_free(decoder_tag); + + pcm_convert_deinit(&conv_state); +} + /** * All chunks are full of decoded data; wait for the player to free * one. @@ -87,7 +105,7 @@ decoder_flush_chunk(struct decoder *decoder) assert(decoder != NULL); assert(decoder->chunk != NULL); - if (music_chunk_is_empty(decoder->chunk)) + if (decoder->chunk->IsEmpty()) music_buffer_return(dc->buffer, decoder->chunk); else music_pipe_push(dc->pipe, decoder->chunk); diff --git a/src/decoder_internal.h b/src/DecoderInternal.hxx index d89e68cfc..ae50a62e2 100644 --- a/src/decoder_internal.h +++ b/src/DecoderInternal.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DECODER_INTERNAL_H -#define MPD_DECODER_INTERNAL_H +#ifndef MPD_DECODER_INTERNAL_HXX +#define MPD_DECODER_INTERNAL_HXX #include "decoder_command.h" #include "pcm_convert.h" @@ -80,6 +80,21 @@ struct decoder { * has changed since the last check. */ unsigned replay_gain_serial; + + decoder(decoder_control *_dc, bool _initial_seek_pending, + struct tag *_tag) + :dc(_dc), + timestamp(0), + initial_seek_pending(_initial_seek_pending), + initial_seek_running(false), + seeking(false), + song_tag(_tag), stream_tag(nullptr), decoder_tag(nullptr), + chunk(nullptr), + replay_gain_serial(0) { + pcm_convert_init(&conv_state); + } + + ~decoder(); }; /** 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..21653830b 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 "decoder_control.h" -#include "decoder_internal.h" -#include "decoder_list.h" +#include "DecoderThread.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "decoder_error.h" #include "decoder_plugin.h" -#include "decoder_api.h" -#include "replay_gain_ape.h" -#include "input_stream.h" -#include "pipe.h" #include "song.h" +#include "mpd_error.h" +#include "Mapper.hxx" +#include "decoder_api.h" #include "tag.h" -#include "mapper.h" -#include "path.h" +#include "input_stream.h" + +extern "C" { +#include "decoder_list.h" +#include "replay_gain_ape.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,57 +380,51 @@ static void decoder_run_song(struct decoder_control *dc, const struct song *song, const char *uri) { - struct decoder decoder = { - .dc = dc, - .initial_seek_pending = dc->start_ms > 0, - .initial_seek_running = false, - }; + decoder decoder(dc, dc->start_ms > 0, + song->tag != NULL && song_is_file(song) + ? tag_dup(song->tag) : nullptr); int ret; - decoder.timestamp = 0.0; - decoder.seeking = false; - decoder.song_tag = song->tag != NULL && song_is_file(song) - ? tag_dup(song->tag) : NULL; - decoder.stream_tag = NULL; - decoder.decoder_tag = NULL; - decoder.chunk = NULL; - dc->state = DECODE_STATE_START; decoder_command_finished_locked(dc); - pcm_convert_init(&decoder.conv_state); - ret = song_is_file(song) ? decoder_run_file(&decoder, uri) : decoder_run_stream(&decoder, uri); decoder_unlock(dc); - pcm_convert_deinit(&decoder.conv_state); - /* flush the last chunk */ if (decoder.chunk != NULL) decoder_flush_chunk(&decoder); - if (decoder.song_tag != NULL) - tag_free(decoder.song_tag); + decoder_lock(dc); - if (decoder.stream_tag != NULL) - tag_free(decoder.stream_tag); + if (ret) + dc->state = DECODE_STATE_STOP; + else { + dc->state = DECODE_STATE_ERROR; - if (decoder.decoder_tag != NULL) - tag_free(decoder.decoder_tag); + const char *error_uri = song->uri; + char *allocated = uri_remove_auth(error_uri); + if (allocated != NULL) + error_uri = allocated; - decoder_lock(dc); + dc->error = g_error_new(decoder_quark(), 0, + "Failed to decode %s", error_uri); + g_free(allocated); + } - dc->state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR; + g_cond_signal(dc->client_cond); } static void decoder_run(struct decoder_control *dc) { + dc_clear_error(dc); + const struct song *song = dc->song; char *uri; @@ -446,6 +437,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 +452,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/GlobalEvents.cxx b/src/GlobalEvents.cxx new file mode 100644 index 000000000..e4f335c9e --- /dev/null +++ b/src/GlobalEvents.cxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "GlobalEvents.hxx" +#include "event/WakeFD.hxx" +#include "mpd_error.h" + +#include <atomic> + +#include <assert.h> +#include <glib.h> +#include <string.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "global_events" + +namespace GlobalEvents { + static WakeFD wake_fd; + static guint source_id; + static std::atomic_uint flags; + static Handler handlers[MAX]; +} + +/** + * Invoke the callback for a certain event. + */ +static void +InvokeGlobalEvent(GlobalEvents::Event event) +{ + assert((unsigned)event < GlobalEvents::MAX); + assert(GlobalEvents::handlers[event] != NULL); + + GlobalEvents::handlers[event](); +} + +static gboolean +GlobalEventCallback(G_GNUC_UNUSED GIOChannel *source, + G_GNUC_UNUSED GIOCondition condition, + G_GNUC_UNUSED gpointer data) +{ + if (!GlobalEvents::wake_fd.Read()) + return true; + + const unsigned flags = GlobalEvents::flags.fetch_and(0); + + for (unsigned i = 0; i < GlobalEvents::MAX; ++i) + if (flags & (1u << i)) + /* invoke the event handler */ + InvokeGlobalEvent(GlobalEvents::Event(i)); + + return true; +} + +void +GlobalEvents::Initialize() +{ + if (!wake_fd.Create()) + MPD_ERROR("Couldn't open pipe: %s", strerror(errno)); + +#ifndef G_OS_WIN32 + GIOChannel *channel = g_io_channel_unix_new(wake_fd.Get()); +#else + GIOChannel *channel = g_io_channel_win32_new_socket(wake_fd.Get()); +#endif + + source_id = g_io_add_watch(channel, G_IO_IN, + GlobalEventCallback, NULL); + g_io_channel_unref(channel); +} + +void +GlobalEvents::Deinitialize() +{ + g_source_remove(source_id); + + wake_fd.Destroy(); +} + +void +GlobalEvents::Register(Event event, Handler callback) +{ + assert((unsigned)event < MAX); + assert(handlers[event] == NULL); + + handlers[event] = callback; +} + +void +GlobalEvents::Emit(Event event) +{ + assert((unsigned)event < MAX); + + const unsigned mask = 1u << unsigned(event); + if ((GlobalEvents::flags.fetch_or(mask) & mask) == 0) + wake_fd.Write(); +} diff --git a/src/GlobalEvents.hxx b/src/GlobalEvents.hxx new file mode 100644 index 000000000..2e549305b --- /dev/null +++ b/src/GlobalEvents.hxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_GLOBAL_EVENTS_HXX +#define MPD_GLOBAL_EVENTS_HXX + +#ifdef WIN32 +/* DELETE is a WIN32 macro that poisons our namespace; this is a + kludge to allow us to use it anyway */ +#ifdef DELETE +#undef DELETE +#endif +#endif + +namespace GlobalEvents { + enum Event { + /** database update was finished */ + UPDATE, + + /** during database update, a song was deleted */ + DELETE, + + /** an idle event was emitted */ + IDLE, + + /** must call playlist_sync() */ + PLAYLIST, + + /** the current song's tag has changed */ + TAG, + + /** SIGHUP received: reload configuration, roll log file */ + RELOAD, + + /** a hardware mixer plugin has detected a change */ + MIXER, + + /** shutdown requested */ + SHUTDOWN, + + MAX + }; + + typedef void (*Handler)(); + + void Initialize(); + + void Deinitialize(); + + void Register(Event event, Handler handler); + + void Emit(Event event); +} + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/io_thread.c b/src/IOThread.cxx index 7c080adcb..192d4cc49 100644 --- a/src/io_thread.c +++ b/src/IOThread.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,16 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "io_thread.h" +#include "config.h" +#include "IOThread.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "event/Loop.hxx" #include <assert.h> static struct { - GMutex *mutex; - GCond *cond; + Mutex mutex; + Cond cond; - GMainContext *context; - GMainLoop *loop; + EventLoop *loop; GThread *thread; } io; @@ -34,10 +37,9 @@ void io_thread_run(void) { assert(io_thread_inside()); - assert(io.context != NULL); assert(io.loop != NULL); - g_main_loop_run(io.loop); + io.loop->Run(); } static gpointer @@ -45,8 +47,8 @@ io_thread_func(G_GNUC_UNUSED gpointer arg) { /* lock+unlock to synchronize with io_thread_start(), to be sure that io.thread is set */ - g_mutex_lock(io.mutex); - g_mutex_unlock(io.mutex); + io.mutex.lock(); + io.mutex.unlock(); io_thread_run(); return NULL; @@ -55,26 +57,21 @@ io_thread_func(G_GNUC_UNUSED gpointer arg) void io_thread_init(void) { - assert(io.context == NULL); assert(io.loop == NULL); assert(io.thread == NULL); - io.mutex = g_mutex_new(); - io.cond = g_cond_new(); - io.context = g_main_context_new(); - io.loop = g_main_loop_new(io.context, false); + io.loop = new EventLoop(); } bool io_thread_start(GError **error_r) { - assert(io.context != NULL); assert(io.loop != NULL); assert(io.thread == NULL); - g_mutex_lock(io.mutex); + io.mutex.lock(); io.thread = g_thread_create(io_thread_func, NULL, true, error_r); - g_mutex_unlock(io.mutex); + io.mutex.unlock(); if (io.thread == NULL) return false; @@ -86,7 +83,7 @@ io_thread_quit(void) { assert(io.loop != NULL); - g_main_loop_quit(io.loop); + io.loop->Break(); } void @@ -98,20 +95,15 @@ io_thread_deinit(void) g_thread_join(io.thread); } - if (io.loop != NULL) - g_main_loop_unref(io.loop); - - if (io.context != NULL) - g_main_context_unref(io.context); - - g_cond_free(io.cond); - g_mutex_free(io.mutex); + delete io.loop; } -GMainContext * -io_thread_context(void) +EventLoop & +io_thread_get() { - return io.context; + assert(io.loop != nullptr); + + return *io.loop; } bool @@ -120,35 +112,6 @@ io_thread_inside(void) return io.thread != NULL && g_thread_self() == io.thread; } -guint -io_thread_idle_add(GSourceFunc function, gpointer data) -{ - GSource *source = g_idle_source_new(); - g_source_set_callback(source, function, data, NULL); - guint id = g_source_attach(source, io.context); - g_source_unref(source); - return id; -} - -GSource * -io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data) -{ - GSource *source = g_timeout_source_new(interval_ms); - g_source_set_callback(source, function, data, NULL); - g_source_attach(source, io.context); - return source; -} - -GSource * -io_thread_timeout_add_seconds(guint interval, - GSourceFunc function, gpointer data) -{ - GSource *source = g_timeout_source_new_seconds(interval); - g_source_set_callback(source, function, data, NULL); - g_source_attach(source, io.context); - return source; -} - struct call_data { GThreadFunc function; gpointer data; @@ -159,15 +122,15 @@ struct call_data { static gboolean io_thread_call_func(gpointer _data) { - struct call_data *data = _data; + struct call_data *data = (struct call_data *)_data; gpointer result = data->function(data->data); - g_mutex_lock(io.mutex); + io.mutex.lock(); data->done = true; data->result = result; - g_cond_broadcast(io.cond); - g_mutex_unlock(io.mutex); + io.cond.broadcast(); + io.mutex.unlock(); return false; } @@ -183,17 +146,18 @@ io_thread_call(GThreadFunc function, gpointer _data) return function(_data); struct call_data data = { - .function = function, - .data = _data, - .done = false, + function, + _data, + false, + nullptr, }; - io_thread_idle_add(io_thread_call_func, &data); + io.loop->AddIdle(io_thread_call_func, &data); - g_mutex_lock(io.mutex); + io.mutex.lock(); while (!data.done) - g_cond_wait(io.cond, io.mutex); - g_mutex_unlock(io.mutex); + io.cond.wait(io.mutex); + io.mutex.unlock(); return data.result; } diff --git a/src/io_thread.h b/src/IOThread.hxx index 8ff5a71e5..a9401dc7f 100644 --- a/src/io_thread.h +++ b/src/IOThread.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_IO_THREAD_H -#define MPD_IO_THREAD_H +#ifndef MPD_IO_THREAD_HXX +#define MPD_IO_THREAD_HXX + +#include "gcc.h" #include <glib.h> -#include <stdbool.h> + +class EventLoop; void io_thread_init(void); @@ -48,29 +51,17 @@ io_thread_quit(void); void io_thread_deinit(void); -G_GNUC_PURE -GMainContext * -io_thread_context(void); +gcc_pure +EventLoop & +io_thread_get(); /** * Is the current thread the I/O thread? */ -G_GNUC_PURE +gcc_pure bool io_thread_inside(void); -guint -io_thread_idle_add(GSourceFunc function, gpointer data); - -G_GNUC_MALLOC -GSource * -io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data); - -G_GNUC_MALLOC -GSource * -io_thread_timeout_add_seconds(guint interval, - GSourceFunc function, gpointer data); - /** * Call a function synchronously in the I/O thread. */ diff --git a/src/icy_metadata.c b/src/IcyMetaDataParser.cxx index 32953e69f..cda63da44 100644 --- a/src/icy_metadata.c +++ b/src/IcyMetaDataParser.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "icy_metadata.h" +#include "IcyMetaDataParser.hxx" #include "tag.h" #include <glib.h> @@ -30,46 +30,37 @@ #define G_LOG_DOMAIN "icy_metadata" void -icy_deinit(struct icy_metadata *im) +IcyMetaDataParser::Reset() { - if (!icy_defined(im)) + if (!IsDefined()) return; - if (im->data_rest == 0 && im->meta_size > 0) - g_free(im->meta_data); + if (data_rest == 0 && meta_size > 0) + g_free(meta_data); - if (im->tag != NULL) - tag_free(im->tag); -} - -void -icy_reset(struct icy_metadata *im) -{ - if (!icy_defined(im)) - return; - - icy_deinit(im); + if (tag != nullptr) + tag_free(tag); - im->data_rest = im->data_size; - im->meta_size = 0; + data_rest = data_size; + meta_size = 0; } size_t -icy_data(struct icy_metadata *im, size_t length) +IcyMetaDataParser::Data(size_t length) { assert(length > 0); - if (!icy_defined(im)) + if (!IsDefined()) return length; - if (im->data_rest == 0) + if (data_rest == 0) return 0; - if (length >= im->data_rest) { - length = im->data_rest; - im->data_rest = 0; + if (length >= data_rest) { + length = data_rest; + data_rest = 0; } else - im->data_rest -= length; + data_rest -= length; return length; } @@ -94,7 +85,7 @@ icy_parse_tag_item(struct tag *tag, const char *item) { gchar **p = g_strsplit(item, "=", 0); - if (p[0] != NULL && p[1] != NULL) { + if (p[0] != nullptr && p[1] != nullptr) { if (strcmp(p[0], "StreamTitle") == 0) icy_add_item(tag, TAG_TITLE, p[1]); else @@ -110,7 +101,7 @@ icy_parse_tag(const char *p) struct tag *tag = tag_new(); gchar **items = g_strsplit(p, ";", 0); - for (unsigned i = 0; items[i] != NULL; ++i) + for (unsigned i = 0; items[i] != nullptr; ++i) icy_parse_tag_item(tag, items[i]); g_strfreev(items); @@ -119,21 +110,21 @@ icy_parse_tag(const char *p) } size_t -icy_meta(struct icy_metadata *im, const void *data, size_t length) +IcyMetaDataParser::Meta(const void *data, size_t length) { - const unsigned char *p = data; + const unsigned char *p = (const unsigned char *)data; - assert(icy_defined(im)); - assert(im->data_rest == 0); + assert(IsDefined()); + assert(data_rest == 0); assert(length > 0); - if (im->meta_size == 0) { + if (meta_size == 0) { /* read meta_size from the first byte of a meta block */ - im->meta_size = *p++ * 16; - if (im->meta_size == 0) { + meta_size = *p++ * 16; + if (meta_size == 0) { /* special case: no metadata */ - im->data_rest = im->data_size; + data_rest = data_size; return 1; } @@ -143,39 +134,39 @@ icy_meta(struct icy_metadata *im, const void *data, size_t length) /* initialize metadata reader, allocate enough memory (+1 for the null terminator) */ - im->meta_position = 0; - im->meta_data = g_malloc(im->meta_size + 1); + meta_position = 0; + meta_data = (char *)g_malloc(meta_size + 1); } - assert(im->meta_position < im->meta_size); + assert(meta_position < meta_size); - if (length > im->meta_size - im->meta_position) - length = im->meta_size - im->meta_position; + if (length > meta_size - meta_position) + length = meta_size - meta_position; - memcpy(im->meta_data + im->meta_position, p, length); - im->meta_position += length; + memcpy(meta_data + meta_position, p, length); + meta_position += length; if (p != data) /* re-add the first byte (which contained meta_size) */ ++length; - if (im->meta_position == im->meta_size) { + if (meta_position == meta_size) { /* null-terminate the string */ - im->meta_data[im->meta_size] = 0; + meta_data[meta_size] = 0; /* parse */ - if (im->tag != NULL) - tag_free(im->tag); + if (tag != nullptr) + tag_free(tag); - im->tag = icy_parse_tag(im->meta_data); - g_free(im->meta_data); + tag = icy_parse_tag(meta_data); + g_free(meta_data); /* change back to normal data mode */ - im->meta_size = 0; - im->data_rest = im->data_size; + meta_size = 0; + data_rest = data_size; } return length; diff --git a/src/IcyMetaDataParser.hxx b/src/IcyMetaDataParser.hxx new file mode 100644 index 000000000..6ccc73f52 --- /dev/null +++ b/src/IcyMetaDataParser.hxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ICY_META_DATA_PARSER_HXX +#define MPD_ICY_META_DATA_PARSER_HXX + +#include <stddef.h> + +class IcyMetaDataParser { + size_t data_size, data_rest; + + size_t meta_size, meta_position; + char *meta_data; + + struct tag *tag; + +public: + IcyMetaDataParser():data_size(0) {} + ~IcyMetaDataParser() { + Reset(); + } + + /** + * Initialize an enabled icy_metadata object with the specified + * data_size (from the icy-metaint HTTP response header). + */ + void Start(size_t _data_size) { + data_size = data_rest = _data_size; + meta_size = 0; + tag = nullptr; + } + + /** + * Resets the icy_metadata. Call this after rewinding the stream. + */ + void Reset(); + + /** + * Checks whether the icy_metadata object is enabled. + */ + bool IsDefined() const { + return data_size > 0; + } + + /** + * Evaluates data. Returns the number of bytes of normal data which + * can be read by the caller, but not more than "length". If the + * return value is smaller than "length", the caller should invoke + * icy_meta(). + */ + size_t Data(size_t length); + + /** + * Reads metadata from the stream. Returns the number of bytes + * consumed. If the return value is smaller than "length", the caller + * should invoke icy_data(). + */ + size_t Meta(const void *data, size_t length); + + struct tag *ReadTag() { + struct tag *result = tag; + tag = nullptr; + return result; + } +}; + +#endif diff --git a/src/IdTable.hxx b/src/IdTable.hxx new file mode 100644 index 000000000..8925fe8ab --- /dev/null +++ b/src/IdTable.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ID_TABLE_HXX +#define MPD_ID_TABLE_HXX + +#include "gcc.h" + +#include <algorithm> + +#include <assert.h> + +/** + * A table that maps id numbers to position numbers. + */ +class IdTable { + unsigned size; + + unsigned next; + + int *data; + +public: + IdTable(unsigned _size):size(_size), next(1), data(new int[size]) { + std::fill(data, data + size, -1); + } + + ~IdTable() { + delete[] data; + } + + int IdToPosition(unsigned id) const { + return id < size + ? data[id] + : -1; + } + + unsigned GenerateId() { + assert(next > 0); + assert(next < size); + + while (true) { + unsigned id = next; + + ++next; + if (next == size) + next = 1; + + if (data[id] < 0) + return id; + } + } + + unsigned Insert(unsigned position) { + unsigned id = GenerateId(); + data[id] = position; + return id; + } + + void Move(unsigned id, unsigned position) { + assert(id < size); + assert(data[id] >= 0); + + data[id] = position; + } + + void Erase(unsigned id) { + assert(id < size); + assert(data[id] >= 0); + + data[id] = -1; + } +}; + +#endif diff --git a/src/idle.c b/src/Idle.cxx index 2d174d78a..fce0cb7da 100644 --- a/src/idle.c +++ b/src/Idle.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,8 +23,8 @@ */ #include "config.h" -#include "idle.h" -#include "event_pipe.h" +#include "Idle.hxx" +#include "GlobalEvents.hxx" #include <assert.h> #include <glib.h> @@ -71,7 +71,7 @@ idle_add(unsigned flags) idle_flags |= flags; g_mutex_unlock(idle_mutex); - event_pipe_emit(PIPE_EVENT_IDLE); + GlobalEvents::Emit(GlobalEvents::IDLE); } unsigned diff --git a/src/idle.h b/src/Idle.hxx index 0156933c0..e77061300 100644 --- a/src/idle.h +++ b/src/Idle.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,8 +22,8 @@ * */ -#ifndef MPD_IDLE_H -#define MPD_IDLE_H +#ifndef MPD_IDLE_HXX +#define MPD_IDLE_HXX enum { /** song database has been updated*/ diff --git a/src/inotify_queue.c b/src/InotifyQueue.cxx index d5e2228c3..3212f95f9 100644 --- a/src/inotify_queue.c +++ b/src/InotifyQueue.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,9 @@ */ #include "config.h" -#include "inotify_queue.h" -#include "update.h" +#include "InotifyQueue.hxx" +#include "UpdateGlue.hxx" +#include "event/Loop.hxx" #include <glib.h> @@ -37,37 +38,13 @@ enum { INOTIFY_UPDATE_DELAY_S = 5, }; -static GSList *inotify_queue; -static guint queue_source_id; - -void -mpd_inotify_queue_init(void) -{ -} - -static void -free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - g_free(data); -} - -void -mpd_inotify_queue_finish(void) -{ - if (queue_source_id != 0) - g_source_remove(queue_source_id); - - g_slist_foreach(inotify_queue, free_callback, NULL); - g_slist_free(inotify_queue); -} - -static gboolean -mpd_inotify_run_update(G_GNUC_UNUSED gpointer data) +bool +InotifyQueue::OnTimeout() { unsigned id; - while (inotify_queue != NULL) { - char *uri_utf8 = inotify_queue->data; + while (!queue.empty()) { + const char *uri_utf8 = queue.front().c_str(); id = update_enqueue(uri_utf8, false); if (id == 0) @@ -76,13 +53,10 @@ mpd_inotify_run_update(G_GNUC_UNUSED gpointer data) g_debug("updating '%s' job=%u", uri_utf8, id); - g_free(uri_utf8); - inotify_queue = g_slist_delete_link(inotify_queue, - inotify_queue); + queue.pop_front(); } /* done, remove the timer event by returning false */ - queue_source_id = 0; return false; } @@ -97,39 +71,25 @@ path_in(const char *path, const char *possible_parent) } void -mpd_inotify_enqueue(char *uri_utf8) +InotifyQueue::Enqueue(const char *uri_utf8) { - GSList *old_queue = inotify_queue; - - if (queue_source_id != 0) - g_source_remove(queue_source_id); - queue_source_id = g_timeout_add_seconds(INOTIFY_UPDATE_DELAY_S, - mpd_inotify_run_update, NULL); + ScheduleSeconds(INOTIFY_UPDATE_DELAY_S); - inotify_queue = NULL; - while (old_queue != NULL) { - char *current_uri = old_queue->data; + for (auto i = queue.begin(), end = queue.end(); i != end;) { + const char *current_uri = i->c_str(); - if (path_in(uri_utf8, current_uri)) { + if (path_in(uri_utf8, current_uri)) /* already enqueued */ - g_free(uri_utf8); - inotify_queue = g_slist_concat(inotify_queue, - old_queue); return; - } - - old_queue = g_slist_delete_link(old_queue, old_queue); if (path_in(current_uri, uri_utf8)) /* existing path is a sub-path of the new path; we can dequeue the existing path and update the new path instead */ - g_free(current_uri); + i = queue.erase(i); else - /* move the existing path to the new queue */ - inotify_queue = g_slist_prepend(inotify_queue, - current_uri); + ++i; } - inotify_queue = g_slist_prepend(inotify_queue, uri_utf8); + queue.emplace_back(uri_utf8); } diff --git a/src/InotifyQueue.hxx b/src/InotifyQueue.hxx new file mode 100644 index 000000000..761df574a --- /dev/null +++ b/src/InotifyQueue.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INOTIFY_QUEUE_HXX +#define MPD_INOTIFY_QUEUE_HXX + +#include "event/TimeoutMonitor.hxx" +#include "gcc.h" + +#include <list> +#include <string> + +class InotifyQueue final : private TimeoutMonitor { + std::list<std::string> queue; + +public: + InotifyQueue(EventLoop &_loop):TimeoutMonitor(_loop) {} + + void Enqueue(const char *uri_utf8); + +private: + virtual bool OnTimeout() override; +}; + +#endif diff --git a/src/inotify_source.c b/src/InotifySource.cxx index e415f5e72..5da32c9bd 100644 --- a/src/inotify_source.c +++ b/src/InotifySource.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,35 +18,20 @@ */ #include "config.h" -#include "inotify_source.h" -#include "fifo_buffer.h" +#include "InotifySource.hxx" +#include "util/fifo_buffer.h" #include "fd_util.h" #include "mpd_error.h" +#include <glib.h> + #include <sys/inotify.h> #include <unistd.h> #include <errno.h> -#include <stdbool.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "inotify" -struct mpd_inotify_source { - int fd; - - GIOChannel *channel; - - /** - * The channel's source id in the GLib main loop. - */ - guint id; - - struct fifo_buffer *buffer; - - mpd_inotify_callback_t callback; - void *callback_ctx; -}; - /** * A GQuark for GError instances. */ @@ -56,34 +41,32 @@ mpd_inotify_quark(void) return g_quark_from_static_string("inotify"); } -static gboolean -mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source, - G_GNUC_UNUSED GIOCondition condition, - gpointer data) +void +InotifySource::OnSocketReady(gcc_unused unsigned flags) { - struct mpd_inotify_source *source = data; void *dest; size_t length; ssize_t nbytes; - const struct inotify_event *event; - dest = fifo_buffer_write(source->buffer, &length); + dest = fifo_buffer_write(buffer, &length); if (dest == NULL) MPD_ERROR("buffer full"); - nbytes = read(source->fd, dest, length); + nbytes = read(Get(), dest, length); if (nbytes < 0) MPD_ERROR("failed to read from inotify: %s", g_strerror(errno)); if (nbytes == 0) MPD_ERROR("end of file from inotify"); - fifo_buffer_append(source->buffer, nbytes); + fifo_buffer_append(buffer, nbytes); while (true) { const char *name; - event = fifo_buffer_read(source->buffer, &length); + const struct inotify_event *event = + (const struct inotify_event *) + fifo_buffer_read(buffer, &length); if (event == NULL || length < sizeof(*event) || length < sizeof(*event) + event->len) break; @@ -93,59 +76,48 @@ mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source, else name = NULL; - source->callback(event->wd, event->mask, name, - source->callback_ctx); - fifo_buffer_consume(source->buffer, - sizeof(*event) + event->len); + callback(event->wd, event->mask, name, callback_ctx); + fifo_buffer_consume(buffer, sizeof(*event) + event->len); } - - return true; } -struct mpd_inotify_source * -mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx, - GError **error_r) +inline +InotifySource::InotifySource(EventLoop &_loop, + mpd_inotify_callback_t _callback, void *_ctx, + int _fd) + :SocketMonitor(_fd, _loop), + callback(_callback), callback_ctx(_ctx), + buffer(fifo_buffer_new(4096)) { - struct mpd_inotify_source *source = - g_new(struct mpd_inotify_source, 1); + ScheduleRead(); - source->fd = inotify_init_cloexec(); - if (source->fd < 0) { +} + +InotifySource * +InotifySource::Create(EventLoop &loop, + mpd_inotify_callback_t callback, void *callback_ctx, + GError **error_r) +{ + int fd = inotify_init_cloexec(); + if (fd < 0) { g_set_error(error_r, mpd_inotify_quark(), errno, "inotify_init() has failed: %s", g_strerror(errno)); - g_free(source); return NULL; } - source->buffer = fifo_buffer_new(4096); - - source->channel = g_io_channel_unix_new(source->fd); - source->id = g_io_add_watch(source->channel, G_IO_IN, - mpd_inotify_in_event, source); - - source->callback = callback; - source->callback_ctx = callback_ctx; - - return source; + return new InotifySource(loop, callback, callback_ctx, fd); } -void -mpd_inotify_source_free(struct mpd_inotify_source *source) +InotifySource::~InotifySource() { - g_source_remove(source->id); - g_io_channel_unref(source->channel); - fifo_buffer_free(source->buffer); - close(source->fd); - g_free(source); + fifo_buffer_free(buffer); } int -mpd_inotify_source_add(struct mpd_inotify_source *source, - const char *path_fs, unsigned mask, - GError **error_r) +InotifySource::Add(const char *path_fs, unsigned mask, GError **error_r) { - int wd = inotify_add_watch(source->fd, path_fs, mask); + int wd = inotify_add_watch(Get(), path_fs, mask); if (wd < 0) g_set_error(error_r, mpd_inotify_quark(), errno, "inotify_add_watch() has failed: %s", @@ -155,9 +127,9 @@ mpd_inotify_source_add(struct mpd_inotify_source *source, } void -mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd) +InotifySource::Remove(unsigned wd) { - int ret = inotify_rm_watch(source->fd, wd); + int ret = inotify_rm_watch(Get(), wd); if (ret < 0 && errno != EINVAL) g_warning("inotify_rm_watch() has failed: %s", g_strerror(errno)); diff --git a/src/InotifySource.hxx b/src/InotifySource.hxx new file mode 100644 index 000000000..0f29ae754 --- /dev/null +++ b/src/InotifySource.hxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INOTIFY_SOURCE_HXX +#define MPD_INOTIFY_SOURCE_HXX + +#include "event/SocketMonitor.hxx" +#include "gerror.h" +#include "gcc.h" + +#include <glib.h> + +typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask, + const char *name, void *ctx); + +class InotifySource final : private SocketMonitor { + mpd_inotify_callback_t callback; + void *callback_ctx; + + struct fifo_buffer *buffer; + + InotifySource(EventLoop &_loop, + mpd_inotify_callback_t callback, void *ctx, int fd); + +public: + /** + * Creates a new inotify source and registers it in the GLib main + * loop. + * + * @param a callback invoked for events received from the kernel + */ + static InotifySource *Create(EventLoop &_loop, + mpd_inotify_callback_t callback, + void *ctx, + GError **error_r); + + ~InotifySource(); + + + /** + * Adds a path to the notify list. + * + * @return a watch descriptor or -1 on error + */ + int Add(const char *path_fs, unsigned mask, GError **error_r); + + /** + * Removes a path from the notify list. + * + * @param wd the watch descriptor returned by mpd_inotify_source_add() + */ + void Remove(unsigned wd); + +private: + virtual void OnSocketReady(unsigned flags) override; +}; + +#endif diff --git a/src/inotify_update.c b/src/InotifyUpdate.cxx index 3f4a8c0c4..7ed0e84ba 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,20 @@ */ #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" +#include "Main.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> @@ -54,7 +57,8 @@ struct watch_directory { GList *children; }; -static struct mpd_inotify_source *inotify_source; +static InotifySource *inotify_source; +static InotifyQueue *inotify_queue; static unsigned inotify_max_depth; static struct watch_directory inotify_root; @@ -90,7 +94,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,12 +114,12 @@ 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); - mpd_inotify_source_rm(inotify_source, directory->descriptor); + inotify_source->Remove(directory->descriptor); g_free(directory->name); g_slice_free(struct watch_directory, directory); } @@ -192,8 +197,7 @@ recursive_watch_subdirectories(struct watch_directory *directory, continue; } - ret = mpd_inotify_source_add(inotify_source, child_path_fs, - IN_MASK, &error); + ret = inotify_source->Add(child_path_fs, IN_MASK, &error); if (ret < 0) { g_warning("Failed to register %s: %s", child_path_fs, error->message); @@ -292,10 +296,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 */ - mpd_inotify_enqueue(uri_utf8); + if (uri_utf8 != NULL) { + inotify_queue->Enqueue(uri_utf8); + g_free(uri_utf8); + } } g_free(uri_fs); @@ -314,8 +318,9 @@ mpd_inotify_init(unsigned max_depth) return; } - inotify_source = mpd_inotify_source_new(mpd_inotify_callback, NULL, - &error); + inotify_source = InotifySource::Create(*main_loop, + mpd_inotify_callback, nullptr, + &error); if (inotify_source == NULL) { g_warning("%s", error->message); g_error_free(error); @@ -325,12 +330,11 @@ mpd_inotify_init(unsigned max_depth) inotify_max_depth = max_depth; inotify_root.name = g_strdup(path); - inotify_root.descriptor = mpd_inotify_source_add(inotify_source, path, - IN_MASK, &error); + inotify_root.descriptor = inotify_source->Add(path, IN_MASK, &error); if (inotify_root.descriptor < 0) { g_warning("%s", error->message); g_error_free(error); - mpd_inotify_source_free(inotify_source); + delete inotify_source; inotify_source = NULL; return; } @@ -340,7 +344,7 @@ mpd_inotify_init(unsigned max_depth) recursive_watch_subdirectories(&inotify_root, path, 0); - mpd_inotify_queue_init(); + inotify_queue = new InotifyQueue(*main_loop); g_debug("watching music directory"); } @@ -349,7 +353,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); @@ -366,8 +370,8 @@ mpd_inotify_finish(void) if (inotify_source == NULL) return; - mpd_inotify_queue_finish(); - mpd_inotify_source_free(inotify_source); + delete inotify_queue; + delete inotify_source; g_tree_foreach(inotify_directories, free_watch_directory, NULL); g_tree_destroy(inotify_directories); diff --git a/src/inotify_update.h b/src/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/input_init.c b/src/InputInit.cxx index 771d648d1..6714cc727 100644 --- a/src/input_init.c +++ b/src/InputInit.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,9 @@ */ #include "config.h" -#include "input_init.h" +#include "InputInit.hxx" +#include "InputRegistry.hxx" #include "input_plugin.h" -#include "input_registry.h" #include "conf.h" #include <assert.h> diff --git a/src/input_init.h b/src/InputInit.hxx index ad92cda08..9d503e5a8 100644 --- a/src/input_init.h +++ b/src/InputInit.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INPUT_INIT_H -#define MPD_INPUT_INIT_H +#ifndef MPD_INPUT_INIT_HXX +#define MPD_INPUT_INIT_HXX -#include "check.h" - -#include <glib.h> -#include <stdbool.h> +#include "gerror.h" /** * Initializes this library and all input_stream implementations. diff --git a/src/input_registry.c b/src/InputRegistry.cxx index 5987d5da2..3dd825915 100644 --- a/src/input_registry.c +++ b/src/InputRegistry.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "input_registry.h" +#include "InputRegistry.hxx" #include "input/file_input_plugin.h" #ifdef ENABLE_ARCHIVE @@ -26,11 +26,11 @@ #endif #ifdef ENABLE_CURL -#include "input/curl_input_plugin.h" +#include "input/CurlInputPlugin.hxx" #endif #ifdef ENABLE_SOUP -#include "input/soup_input_plugin.h" +#include "input/SoupInputPlugin.hxx" #endif #ifdef HAVE_FFMPEG diff --git a/src/input_registry.h b/src/InputRegistry.hxx index 4f5fff8da..a080d108b 100644 --- a/src/input_registry.h +++ b/src/InputRegistry.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INPUT_REGISTRY_H -#define MPD_INPUT_REGISTRY_H +#ifndef MPD_INPUT_REGISTRY_HXX +#define MPD_INPUT_REGISTRY_HXX #include "check.h" -#include <stdbool.h> - /** * NULL terminated list of all input plugins which were enabled at * compile time. diff --git a/src/input_stream.c b/src/InputStream.cxx index e445dca6c..64f347555 100644 --- a/src/input_stream.c +++ b/src/InputStream.cxx @@ -19,9 +19,13 @@ #include "config.h" #include "input_stream.h" -#include "input_registry.h" +#include "InputRegistry.hxx" #include "input_plugin.h" + +extern "C" { #include "input/rewind_input_plugin.h" +#include "uri.h" +} #include <glib.h> #include <assert.h> @@ -115,6 +119,12 @@ input_stream_lock_wait_ready(struct input_stream *is) } bool +input_stream_cheap_seeking(const struct input_stream *is) +{ + return is->seekable && (is->uri == NULL || !uri_has_scheme(is->uri)); +} + +bool input_stream_seek(struct input_stream *is, goffset offset, int whence, GError **error_r) { @@ -165,7 +175,7 @@ input_stream_lock_tag(struct input_stream *is) assert(is->plugin != NULL); if (is->plugin->tag == NULL) - return false; + return nullptr; if (is->mutex == NULL) /* no locking */ diff --git a/src/listen.c b/src/Listen.cxx index 90e13b9c1..7bc10dc18 100644 --- a/src/listen.c +++ b/src/Listen.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,11 @@ */ #include "config.h" -#include "listen.h" -#include "server_socket.h" -#include "client.h" +#include "Listen.hxx" +#include "Main.hxx" +#include "Client.hxx" #include "conf.h" -#include "main.h" +#include "ServerSocket.hxx" #include <string.h> #include <assert.h> @@ -43,7 +43,8 @@ static void listen_callback(int fd, const struct sockaddr *address, size_t address_length, int uid, G_GNUC_UNUSED void *ctx) { - client_new(global_player_control, fd, address, address_length, uid); + client_new(*main_loop, *global_partition, + fd, address, address_length, uid); } static bool @@ -91,13 +92,15 @@ listen_systemd_activation(GError **error_r) bool listen_global_init(GError **error_r) { + assert(main_loop != nullptr); + int port = config_get_positive(CONF_PORT, DEFAULT_PORT); const struct config_param *param = config_get_next_param(CONF_BIND_TO_ADDRESS, NULL); bool success; GError *error = NULL; - listen_socket = server_socket_new(listen_callback, NULL); + listen_socket = server_socket_new(*main_loop, listen_callback, NULL); if (listen_systemd_activation(&error)) return true; 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/log.c b/src/Log.cxx index 2d3c7cafd..76b365778 100644 --- a/src/log.c +++ b/src/Log.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,8 @@ */ #include "config.h" -#include "log.h" +#include "Log.hxx" #include "conf.h" -#include "utils.h" #include "fd_util.h" #include "mpd_error.h" diff --git a/src/log.h b/src/Log.hxx index 683ff3e9f..cfcece104 100644 --- a/src/log.h +++ b/src/Log.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_LOG_H -#define MPD_LOG_H +#ifndef MPD_LOG_HXX +#define MPD_LOG_HXX #include <glib.h> #include <stdbool.h> diff --git a/src/main.c b/src/Main.cxx index 12f8d86f6..917d89457 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,56 +18,62 @@ */ #include "config.h" -#include "main.h" -#include "daemon.h" -#include "io_thread.h" -#include "client.h" -#include "client_idle.h" -#include "idle.h" -#include "command.h" -#include "playlist.h" -#include "stored_playlist.h" -#include "database.h" -#include "update.h" -#include "player_thread.h" -#include "listen.h" -#include "cmdline.h" +#include "Main.hxx" +#include "CommandLine.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistGlobal.hxx" +#include "UpdateGlue.hxx" +#include "MusicChunk.hxx" +#include "StateFile.hxx" +#include "PlayerThread.hxx" +#include "Mapper.hxx" +#include "DatabaseGlue.hxx" +#include "DatabaseSimple.hxx" +#include "Permission.hxx" +#include "Listen.hxx" +#include "ClientIdle.hxx" +#include "Client.hxx" +#include "AllCommands.hxx" +#include "Partition.hxx" +#include "Volume.hxx" +#include "OutputAll.hxx" +#include "tag.h" #include "conf.h" +#include "replay_gain_config.h" +#include "Idle.hxx" +#include "SignalHandlers.hxx" +#include "Log.hxx" +#include "GlobalEvents.hxx" +#include "InputInit.hxx" +#include "event/Loop.hxx" +#include "IOThread.hxx" + +extern "C" { +#include "daemon.h" #include "path.h" -#include "mapper.h" -#include "chunk.h" -#include "player_control.h" #include "stats.h" -#include "sig_handlers.h" #include "audio_config.h" -#include "output_all.h" -#include "volume.h" -#include "log.h" -#include "permission.h" #include "pcm_resample.h" -#include "replay_gain_config.h" #include "decoder_list.h" -#include "input_init.h" #include "playlist_list.h" -#include "state_file.h" -#include "tag.h" -#include "dbUtils.h" #include "zeroconf.h" -#include "event_pipe.h" -#include "tag_pool.h" +} + #include "mpd_error.h" #ifdef ENABLE_INOTIFY -#include "inotify_update.h" +#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> @@ -91,11 +97,11 @@ enum { }; GThread *main_task; -GMainLoop *main_loop; +EventLoop *main_loop; -GCond *main_cond; +Partition *global_partition; -struct player_control *global_player_control; +static StateFile *state_file; static bool glue_daemonize_init(const struct options *options, GError **error_r) @@ -153,31 +159,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(); } /** @@ -205,14 +227,18 @@ glue_state_file_init(GError **error_r) GError *error = NULL; char *path = config_dup_path(CONF_STATE_FILE, &error); - if (path == NULL && error != NULL) { - g_propagate_error(error_r, error); - return false; + if (path == nullptr) { + if (error != nullptr) { + g_propagate_error(error_r, error); + return false; + } + + return true; } - state_file_init(path, global_player_control); + state_file = new StateFile(path, *global_partition, *main_loop); g_free(path); - + state_file->Read(); return true; } @@ -285,11 +311,17 @@ initialize_decoder_and_player(void) if (buffered_before_play > buffered_chunks) buffered_before_play = buffered_chunks; - global_player_control = pc_new(buffered_chunks, buffered_before_play); + const unsigned max_length = + config_get_positive(CONF_MAX_PLAYLIST_LENGTH, + DEFAULT_PLAYLIST_MAX_LENGTH); + + global_partition = new Partition(max_length, + buffered_chunks, + buffered_before_play); } /** - * event_pipe callback function for PIPE_EVENT_IDLE + * Handler for GlobalEvents::IDLE. */ static void idle_event_emitted(void) @@ -302,12 +334,12 @@ idle_event_emitted(void) } /** - * event_pipe callback function for PIPE_EVENT_SHUTDOWN + * Handler for GlobalEvents::SHUTDOWN. */ static void shutdown_event_emitted(void) { - g_main_loop_quit(main_loop); + main_loop->Break(); } int main(int argc, char *argv[]) @@ -342,7 +374,6 @@ int mpd_main(int argc, char *argv[]) io_thread_init(); winsock_init(); idle_init(); - tag_pool_init(); config_global_init(); success = parse_cmdline(argc, argv, &options, &error); @@ -367,6 +398,9 @@ int mpd_main(int argc, char *argv[]) return EXIT_FAILURE; } + main_task = g_thread_self(); + main_loop = new EventLoop(EventLoop::Default()); + success = listen_global_init(&error); if (!success) { g_warning("%s", error->message); @@ -376,13 +410,9 @@ int mpd_main(int argc, char *argv[]) daemonize_set_user(); - main_task = g_thread_self(); - main_loop = g_main_loop_new(NULL, FALSE); - main_cond = g_cond_new(); - - event_pipe_init(); - event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted); - event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_event_emitted); + GlobalEvents::Initialize(); + GlobalEvents::Register(GlobalEvents::IDLE, idle_event_emitted); + GlobalEvents::Register(GlobalEvents::SHUTDOWN, shutdown_event_emitted); path_global_init(); @@ -416,7 +446,7 @@ int mpd_main(int argc, char *argv[]) initialize_decoder_and_player(); volume_init(); initAudioConfig(); - audio_output_all_init(global_player_control); + audio_output_all_init(&global_partition->pc); client_manager_init(); replay_gain_global_init(); @@ -442,7 +472,7 @@ int mpd_main(int argc, char *argv[]) initZeroconf(); - player_create(global_player_control); + player_create(&global_partition->pc); if (create_db) { /* the database failed to load: recreate the @@ -458,6 +488,8 @@ int mpd_main(int argc, char *argv[]) return EXIT_FAILURE; } + audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(global_partition->playlist.queue.random)); + success = config_get_bool(CONF_AUTO_UPDATE, false); #ifdef ENABLE_INOTIFY if (success && mapper_has_music_directory()) @@ -472,14 +504,14 @@ int mpd_main(int argc, char *argv[]) /* enable all audio outputs (if not already done by playlist_state_restore() */ - pc_update_audio(global_player_control); + pc_update_audio(&global_partition->pc); #ifdef WIN32 win32_app_started(); #endif /* run the main loop */ - g_main_loop_run(main_loop); + main_loop->Run(); #ifdef WIN32 win32_app_stopping(); @@ -487,21 +519,24 @@ int mpd_main(int argc, char *argv[]) /* cleanup */ - g_main_loop_unref(main_loop); + delete main_loop; #ifdef ENABLE_INOTIFY mpd_inotify_finish(); #endif - state_file_finish(global_player_control); - pc_kill(global_player_control); + if (state_file != nullptr) { + state_file->Write(); + delete state_file; + } + + pc_kill(&global_partition->pc); finishZeroconf(); client_manager_deinit(); listen_global_finish(); - playlist_global_finish(); start = clock(); - db_finish(); + DatabaseGlobalDeinit(); g_debug("db_finish took %f seconds", ((float)(clock()-start))/CLOCKS_PER_SEC); @@ -509,8 +544,7 @@ int mpd_main(int argc, char *argv[]) sticker_global_finish(); #endif - g_cond_free(main_cond); - event_pipe_deinit(); + GlobalEvents::Deinitialize(); playlist_list_global_finish(); input_stream_global_finish(); @@ -518,8 +552,7 @@ int mpd_main(int argc, char *argv[]) volume_finish(); mapper_finish(); path_global_finish(); - finishPermissions(); - pc_free(global_player_control); + delete global_partition; command_finish(); update_global_finish(); decoder_plugin_deinit_all(); @@ -527,7 +560,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..6ce079434 100644 --- a/src/main.h +++ b/src/Main.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,18 +17,18 @@ * 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> -extern GThread *main_task; +class EventLoop; -extern GMainLoop *main_loop; +extern GThread *main_task; -extern GCond *main_cond; +extern EventLoop *main_loop; -extern struct player_control *global_player_control; +extern struct Partition *global_partition; /** * A entry point for application. @@ -52,7 +52,7 @@ win32_main(int argc, char *argv[]); * When running as a service reports to service control manager * that our service is started. * When running as a console application enables console handler that will - * trigger PIPE_EVENT_SHUTDOWN when user closes console window + * trigger GlobalEvents::SHUTDOWN when user closes console window * or presses Ctrl+C. * This function should be called just before entering main loop. */ diff --git a/src/mapper.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..6a967f95b --- /dev/null +++ b/src/MessageCommands.cxx @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MessageCommands.hxx" +#include "ClientSubscribe.hxx" +#include "ClientInternal.hxx" +#include "ClientList.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(Client *client, gpointer user_data) +{ + struct channels_context *context = + (struct channels_context *)user_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(Client *client, gpointer user_data) +{ + struct send_message_context *context = + (struct send_message_context *)user_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/mixer_all.c b/src/MixerAll.cxx index 95ba90793..a214c1e24 100644 --- a/src/mixer_all.c +++ b/src/MixerAll.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,15 @@ */ #include "config.h" -#include "mixer_all.h" +#include "MixerAll.hxx" +#include "pcm_volume.h" +#include "OutputAll.hxx" + +extern "C" { #include "mixer_control.h" -#include "output_all.h" -#include "output_plugin.h" #include "output_internal.h" -#include "pcm_volume.h" #include "mixer_api.h" -#include "mixer_list.h" +} #include <glib.h> diff --git a/src/mixer_all.h b/src/MixerAll.hxx index fe873e713..23350a843 100644 --- a/src/mixer_all.h +++ b/src/MixerAll.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,10 +22,8 @@ * Functions which affect the mixers of all audio outputs. */ -#ifndef MPD_MIXER_ALL_H -#define MPD_MIXER_ALL_H - -#include <stdbool.h> +#ifndef MPD_MIXER_ALL_HXX +#define MPD_MIXER_ALL_HXX /** * Returns the average volume of all available mixers (range 0..100). diff --git a/src/MusicBuffer.cxx b/src/MusicBuffer.cxx new file mode 100644 index 000000000..ea03fc0b9 --- /dev/null +++ b/src/MusicBuffer.cxx @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "thread/Mutex.hxx" +#include "util/SliceBuffer.hxx" +#include "mpd_error.h" + +#include <assert.h> + +struct music_buffer : public SliceBuffer<music_chunk> { + /** a mutex which protects #available */ + Mutex mutex; + + music_buffer(unsigned num_chunks) + :SliceBuffer(num_chunks) { + if (IsOOM()) + MPD_ERROR("Failed to allocate buffer"); + } +}; + +struct music_buffer * +music_buffer_new(unsigned num_chunks) +{ + return new music_buffer(num_chunks); +} + +void +music_buffer_free(struct music_buffer *buffer) +{ + delete buffer; +} + +unsigned +music_buffer_size(const struct music_buffer *buffer) +{ + return buffer->GetCapacity(); +} + +struct music_chunk * +music_buffer_allocate(struct music_buffer *buffer) +{ + const ScopeLock protect(buffer->mutex); + return buffer->Allocate(); +} + +void +music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk) +{ + assert(buffer != NULL); + assert(chunk != NULL); + + const ScopeLock protect(buffer->mutex); + + if (chunk->other != nullptr) { + assert(chunk->other->other == nullptr); + buffer->Free(chunk->other); + } + + buffer->Free(chunk); +} diff --git a/src/buffer.h b/src/MusicBuffer.hxx index f860231e7..cc03dfcb3 100644 --- a/src/buffer.h +++ b/src/MusicBuffer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_MUSIC_BUFFER_H -#define MPD_MUSIC_BUFFER_H +#ifndef MPD_MUSIC_BUFFER_HXX +#define MPD_MUSIC_BUFFER_HXX /** * An allocator for #music_chunk objects. diff --git a/src/MusicChunk.cxx b/src/MusicChunk.cxx new file mode 100644 index 000000000..eefda24b5 --- /dev/null +++ b/src/MusicChunk.cxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MusicChunk.hxx" +#include "audio_format.h" +#include "tag.h" + +#include <assert.h> + +music_chunk::~music_chunk() +{ + if (tag != NULL) + tag_free(tag); +} + +#ifndef NDEBUG +bool +music_chunk::CheckFormat(const struct audio_format &other_format) const +{ + assert(audio_format_valid(&other_format)); + + return length == 0 || + audio_format_equals(&audio_format, &other_format); +} +#endif + +void * +music_chunk::Write(const struct audio_format &af, + float data_time, uint16_t _bit_rate, + size_t *max_length_r) +{ + assert(CheckFormat(af)); + assert(length == 0 || audio_format_valid(&audio_format)); + + if (length == 0) { + /* if the chunk is empty, nobody has set bitRate and + times yet */ + + bit_rate = _bit_rate; + times = data_time; + } + + const size_t frame_size = audio_format_frame_size(&af); + size_t num_frames = (sizeof(data) - length) / frame_size; + if (num_frames == 0) + return NULL; + +#ifndef NDEBUG + audio_format = af; +#endif + + *max_length_r = num_frames * frame_size; + return data + length; +} + +bool +music_chunk::Expand(const struct audio_format &af, size_t _length) +{ + const size_t frame_size = audio_format_frame_size(&af); + + assert(length + _length <= sizeof(data)); + assert(audio_format_equals(&audio_format, &af)); + + length += _length; + + return length + frame_size > sizeof(data); +} diff --git a/src/chunk.h b/src/MusicChunk.hxx index a06a203eb..c03e45517 100644 --- a/src/chunk.h +++ b/src/MusicChunk.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_CHUNK_H -#define MPD_CHUNK_H +#ifndef MPD_MUSIC_CHUNK_HXX +#define MPD_MUSIC_CHUNK_HXX #include "replay_gain_info.h" @@ -26,7 +26,6 @@ #include "audio_format.h" #endif -#include <stdbool.h> #include <stdint.h> #include <stddef.h> @@ -92,60 +91,63 @@ struct music_chunk { #ifndef NDEBUG struct audio_format audio_format; #endif -}; -void -music_chunk_init(struct music_chunk *chunk); + music_chunk() + :other(nullptr), + length(0), + tag(nullptr), + replay_gain_serial(0) {} -void -music_chunk_free(struct music_chunk *chunk); + ~music_chunk(); -static inline bool -music_chunk_is_empty(const struct music_chunk *chunk) -{ - return chunk->length == 0 && chunk->tag == NULL; -} + bool IsEmpty() const { + return length == 0 && tag == nullptr; + } #ifndef NDEBUG -/** - * Checks if the audio format if the chunk is equal to the specified - * audio_format. - */ -bool -music_chunk_check_format(const struct music_chunk *chunk, - const struct audio_format *audio_format); + /** + * Checks if the audio format if the chunk is equal to the + * specified audio_format. + */ + gcc_pure + bool CheckFormat(const struct audio_format &audio_format) const; #endif -/** - * Prepares appending to the music chunk. Returns a buffer where you - * may write into. After you are finished, call music_chunk_expand(). - * - * @param chunk the music_chunk object - * @param audio_format the audio format for the appended data; must - * stay the same for the life cycle of this chunk - * @param data_time the time within the song - * @param bit_rate the current bit rate of the source file - * @param max_length_r the maximum write length is returned here - * @return a writable buffer, or NULL if the chunk is full - */ -void * -music_chunk_write(struct music_chunk *chunk, - const struct audio_format *audio_format, - float data_time, uint16_t bit_rate, - size_t *max_length_r); + /** + * Prepares appending to the music chunk. Returns a buffer + * where you may write into. After you are finished, call + * music_chunk_expand(). + * + * @param chunk the music_chunk object + * @param audio_format the audio format for the appended data; + * must stay the same for the life cycle of this chunk + * @param data_time the time within the song + * @param bit_rate the current bit rate of the source file + * @param max_length_r the maximum write length is returned + * here + * @return a writable buffer, or NULL if the chunk is full + */ + void *Write(const struct audio_format &af, + float data_time, uint16_t bit_rate, + size_t *max_length_r); -/** - * Increases the length of the chunk after the caller has written to - * the buffer returned by music_chunk_write(). - * - * @param chunk the music_chunk object - * @param audio_format the audio format for the appended data; must - * stay the same for the life cycle of this chunk - * @param length the number of bytes which were appended - * @return true if the chunk is full - */ -bool -music_chunk_expand(struct music_chunk *chunk, - const struct audio_format *audio_format, size_t length); + /** + * Increases the length of the chunk after the caller has written to + * the buffer returned by music_chunk_write(). + * + * @param chunk the music_chunk object + * @param audio_format the audio format for the appended data; must + * stay the same for the life cycle of this chunk + * @param length the number of bytes which were appended + * @return true if the chunk is full + */ + bool Expand(const struct audio_format &af, size_t length); +}; + +void +music_chunk_init(struct music_chunk *chunk); + +void +music_chunk_free(struct music_chunk *chunk); #endif diff --git a/src/pipe.c b/src/MusicPipe.cxx index d8131432f..6f25eff82 100644 --- a/src/pipe.c +++ b/src/MusicPipe.cxx @@ -18,9 +18,10 @@ */ #include "config.h" -#include "pipe.h" -#include "buffer.h" -#include "chunk.h" +#include "MusicPipe.hxx" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "thread/Mutex.hxx" #include <glib.h> @@ -37,38 +38,35 @@ struct music_pipe { unsigned size; /** a mutex which protects #head and #tail_r */ - GMutex *mutex; + mutable Mutex mutex; #ifndef NDEBUG struct audio_format audio_format; #endif + + music_pipe() + :head(nullptr), tail_r(&head), size(0) { +#ifndef NDEBUG + audio_format_clear(&audio_format); +#endif + } + + ~music_pipe() { + assert(head == nullptr); + assert(tail_r == &head); + } }; struct music_pipe * music_pipe_new(void) { - struct music_pipe *mp = g_new(struct music_pipe, 1); - - mp->head = NULL; - mp->tail_r = &mp->head; - mp->size = 0; - mp->mutex = g_mutex_new(); - -#ifndef NDEBUG - audio_format_clear(&mp->audio_format); -#endif - - return mp; + return new music_pipe(); } void music_pipe_free(struct music_pipe *mp) { - assert(mp->head == NULL); - assert(mp->tail_r == &mp->head); - - g_mutex_free(mp->mutex); - g_free(mp); + delete mp; } #ifndef NDEBUG @@ -88,17 +86,12 @@ bool music_pipe_contains(const struct music_pipe *mp, const struct music_chunk *chunk) { - g_mutex_lock(mp->mutex); + const ScopeLock protect(mp->mutex); for (const struct music_chunk *i = mp->head; - i != NULL; i = i->next) { - if (i == chunk) { - g_mutex_unlock(mp->mutex); + i != NULL; i = i->next) + if (i == chunk) return true; - } - } - - g_mutex_unlock(mp->mutex); return false; } @@ -114,13 +107,11 @@ music_pipe_peek(const struct music_pipe *mp) struct music_chunk * music_pipe_shift(struct music_pipe *mp) { - struct music_chunk *chunk; - - g_mutex_lock(mp->mutex); + const ScopeLock protect(mp->mutex); - chunk = mp->head; + struct music_chunk *chunk = mp->head; if (chunk != NULL) { - assert(!music_chunk_is_empty(chunk)); + assert(!chunk->IsEmpty()); mp->head = chunk->next; --mp->size; @@ -137,15 +128,13 @@ music_pipe_shift(struct music_pipe *mp) #ifndef NDEBUG /* poison the "next" reference */ - chunk->next = (void*)0x01010101; + chunk->next = (struct music_chunk *)(void *)0x01010101; if (mp->size == 0) audio_format_clear(&mp->audio_format); #endif } - g_mutex_unlock(mp->mutex); - return chunk; } @@ -161,14 +150,14 @@ music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer) void music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk) { - assert(!music_chunk_is_empty(chunk)); + assert(!chunk->IsEmpty()); assert(chunk->length == 0 || audio_format_valid(&chunk->audio_format)); - g_mutex_lock(mp->mutex); + const ScopeLock protect(mp->mutex); assert(mp->size > 0 || !audio_format_defined(&mp->audio_format)); assert(!audio_format_defined(&mp->audio_format) || - music_chunk_check_format(chunk, &mp->audio_format)); + chunk->CheckFormat(mp->audio_format)); #ifndef NDEBUG if (!audio_format_defined(&mp->audio_format) && chunk->length > 0) @@ -180,15 +169,11 @@ music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk) mp->tail_r = &chunk->next; ++mp->size; - - g_mutex_unlock(mp->mutex); } unsigned music_pipe_size(const struct music_pipe *mp) { - g_mutex_lock(mp->mutex); - unsigned size = mp->size; - g_mutex_unlock(mp->mutex); - return size; + const ScopeLock protect(mp->mutex); + return mp->size; } diff --git a/src/pipe.h b/src/MusicPipe.hxx index 84b9869e0..99561ca62 100644 --- a/src/pipe.h +++ b/src/MusicPipe.hxx @@ -20,8 +20,7 @@ #ifndef MPD_PIPE_H #define MPD_PIPE_H -#include <glib.h> -#include <stdbool.h> +#include "gcc.h" #ifndef NDEBUG struct audio_format; @@ -39,7 +38,7 @@ struct music_pipe; /** * Creates a new #music_pipe object. It is empty. */ -G_GNUC_MALLOC +gcc_malloc struct music_pipe * music_pipe_new(void); @@ -72,7 +71,7 @@ music_pipe_contains(const struct music_pipe *mp, * Returns the first #music_chunk from the pipe. Returns NULL if the * pipe is empty. */ -G_GNUC_PURE +gcc_pure const struct music_chunk * music_pipe_peek(const struct music_pipe *mp); @@ -99,11 +98,11 @@ music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk); /** * Returns the number of chunks currently in this pipe. */ -G_GNUC_PURE +gcc_pure unsigned music_pipe_size(const struct music_pipe *mp); -G_GNUC_PURE +gcc_pure static inline bool music_pipe_empty(const struct music_pipe *mp) { diff --git a/src/OtherCommands.cxx b/src/OtherCommands.cxx new file mode 100644 index 000000000..b8cc36cc5 --- /dev/null +++ b/src/OtherCommands.cxx @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OtherCommands.hxx" +#include "DatabaseCommands.hxx" +#include "CommandError.hxx" +#include "UpdateGlue.hxx" +#include "Directory.hxx" +#include "song.h" +#include "SongPrint.hxx" +#include "TagPrint.hxx" +#include "TimePrint.hxx" +#include "Mapper.hxx" +#include "DecoderPrint.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "ls.hxx" +#include "Volume.hxx" + +extern "C" { +#include "uri.h" +#include "stats.h" +} + +#include "Permission.hxx" +#include "PlaylistFile.hxx" +#include "ClientIdle.hxx" +#include "ClientFile.hxx" +#include "Client.hxx" +#include "Idle.hxx" + +#ifdef ENABLE_SQLITE +#include "StickerDatabase.hxx" +#endif + +#include <assert.h> +#include <string.h> + +static void +print_spl_list(Client *client, const PlaylistVector &list) +{ + for (const auto &i : list) { + client_printf(client, "playlist: %s\n", i.name.c_str()); + + if (i.mtime > 0) + time_print(client, "Last-Modified", i.mtime); + } +} + +enum command_return +handle_urlhandlers(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + if (client_is_local(client)) + client_puts(client, "handler: file://\n"); + print_supported_uri_schemes(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_decoders(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + decoder_list_print(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_tagtypes(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + tag_print_types(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_kill(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + return COMMAND_RETURN_KILL; +} + +enum command_return +handle_close(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + return COMMAND_RETURN_CLOSE; +} + +enum command_return +handle_lsinfo(Client *client, int argc, char *argv[]) +{ + const char *uri; + + if (argc == 2) + uri = argv[1]; + else + /* default is root directory */ + uri = ""; + + if (strncmp(uri, "file:///", 8) == 0) { + /* print information about an arbitrary local file */ + const char *path = 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/output_all.c b/src/OutputAll.cxx index f56cd04ee..a18a63385 100644 --- a/src/output_all.c +++ b/src/OutputAll.cxx @@ -18,20 +18,21 @@ */ #include "config.h" -#include "output_all.h" +#include "OutputAll.hxx" + +extern "C" { #include "output_internal.h" -#include "output_control.h" -#include "chunk.h" -#include "conf.h" -#include "pipe.h" -#include "buffer.h" -#include "player_control.h" -#include "mpd_error.h" -#include "notify.h" +} -#ifndef NDEBUG -#include "chunk.h" -#endif +#include "PlayerControl.hxx" +#include "OutputControl.hxx" +#include "OutputError.hxx" +#include "MusicBuffer.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" +#include "mpd_error.h" +#include "conf.h" +#include "notify.hxx" #include <assert.h> #include <string.h> @@ -109,8 +110,6 @@ audio_output_all_init(struct player_control *pc) unsigned int i; GError *error = NULL; - notify_init(&audio_output_client_notify); - num_audio_outputs = audio_output_config_count(); audio_outputs = g_new(struct audio_output *, num_audio_outputs); @@ -157,8 +156,6 @@ audio_output_all_finish(void) g_free(audio_outputs); audio_outputs = NULL; num_audio_outputs = 0; - - notify_deinit(&audio_output_client_notify); } void @@ -207,7 +204,7 @@ audio_output_all_finished(void) static void audio_output_wait_all(void) { while (!audio_output_all_finished()) - notify_wait(&audio_output_client_notify); + audio_output_client_notify.Wait(); } /** @@ -269,8 +266,15 @@ audio_output_all_update(void) return ret; } +void +audio_output_all_set_replay_gain_mode(enum replay_gain_mode mode) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_set_replay_gain_mode(audio_outputs[i], mode); +} + bool -audio_output_all_play(struct music_chunk *chunk) +audio_output_all_play(struct music_chunk *chunk, GError **error_r) { bool ret; unsigned int i; @@ -278,11 +282,15 @@ audio_output_all_play(struct music_chunk *chunk) assert(g_music_buffer != NULL); assert(g_mp != NULL); assert(chunk != NULL); - assert(music_chunk_check_format(chunk, &input_audio_format)); + assert(chunk->CheckFormat(input_audio_format)); ret = audio_output_all_update(); - if (!ret) + if (!ret) { + /* TODO: obtain real error */ + g_set_error(error_r, output_quark(), 0, + "Failed to open audio output"); return false; + } music_pipe_push(g_mp, chunk); @@ -294,7 +302,8 @@ audio_output_all_play(struct music_chunk *chunk) bool audio_output_all_open(const struct audio_format *audio_format, - struct music_buffer *buffer) + struct music_buffer *buffer, + GError **error_r) { bool ret = false, enabled = false; unsigned int i; @@ -334,7 +343,12 @@ audio_output_all_open(const struct audio_format *audio_format, } if (!enabled) - g_warning("All audio outputs are disabled"); + g_set_error(error_r, output_quark(), 0, + "All audio outputs are disabled"); + else if (!ret) + /* TODO: obtain real error */ + g_set_error(error_r, output_quark(), 0, + "Failed to open audio output"); if (!ret) /* close all devices if there was an error */ diff --git a/src/output_all.h b/src/OutputAll.hxx index 4eeb94f13..becf4b695 100644 --- a/src/output_all.h +++ b/src/OutputAll.hxx @@ -26,6 +26,9 @@ #ifndef OUTPUT_ALL_H #define OUTPUT_ALL_H +#include "replay_gain_info.h" +#include "gerror.h" + #include <stdbool.h> #include <stddef.h> @@ -84,7 +87,8 @@ audio_output_all_enable_disable(void); */ bool audio_output_all_open(const struct audio_format *audio_format, - struct music_buffer *buffer); + struct music_buffer *buffer, + GError **error_r); /** * Closes all audio outputs. @@ -99,6 +103,9 @@ audio_output_all_close(void); void audio_output_all_release(void); +void +audio_output_all_set_replay_gain_mode(enum replay_gain_mode mode); + /** * Enqueue a #music_chunk object for playing, i.e. pushes it to a * #music_pipe. @@ -108,7 +115,7 @@ audio_output_all_release(void); * (all closed then) */ bool -audio_output_all_play(struct music_chunk *chunk); +audio_output_all_play(struct music_chunk *chunk, GError **error_r); /** * Checks if the output devices have drained their music pipe, and diff --git a/src/output_command.c b/src/OutputCommand.cxx index 3988f350a..f4edaa4a1 100644 --- a/src/output_command.c +++ b/src/OutputCommand.cxx @@ -25,13 +25,16 @@ */ #include "config.h" -#include "output_command.h" -#include "output_all.h" +#include "OutputCommand.hxx" +#include "OutputAll.hxx" +#include "PlayerControl.hxx" +#include "Idle.hxx" + +extern "C" { #include "output_internal.h" #include "output_plugin.h" #include "mixer_control.h" -#include "player_control.h" -#include "idle.h" +} extern unsigned audio_output_state_version; diff --git a/src/output_command.h b/src/OutputCommand.hxx index eda30acc8..74eaf8f1c 100644 --- a/src/output_command.h +++ b/src/OutputCommand.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,10 +24,8 @@ * */ -#ifndef OUTPUT_COMMAND_H -#define OUTPUT_COMMAND_H - -#include <stdbool.h> +#ifndef MPD_OUTPUT_COMMAND_HXX +#define MPD_OUTPUT_COMMAND_HXX /** * Enables an audio output. Returns false if the specified output diff --git a/src/OutputCommands.cxx b/src/OutputCommands.cxx new file mode 100644 index 000000000..7d626477a --- /dev/null +++ b/src/OutputCommands.cxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputCommands.hxx" +#include "OutputPrint.hxx" +#include "OutputCommand.hxx" +#include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" + +#include <string.h> + +enum command_return +handle_enableoutput(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned device; + bool ret; + + if (!check_unsigned(client, &device, argv[1])) + return COMMAND_RETURN_ERROR; + + ret = audio_output_enable_index(device); + if (!ret) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such audio output"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_disableoutput(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned device; + bool ret; + + if (!check_unsigned(client, &device, argv[1])) + return COMMAND_RETURN_ERROR; + + ret = audio_output_disable_index(device); + if (!ret) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such audio output"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_devices(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + printAudioDevices(client); + + return COMMAND_RETURN_OK; +} diff --git a/src/OutputCommands.hxx b/src/OutputCommands.hxx new file mode 100644 index 000000000..4f7082bfb --- /dev/null +++ b/src/OutputCommands.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_COMMANDS_HXX +#define MPD_OUTPUT_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_enableoutput(Client *client, int argc, char *argv[]); + +enum command_return +handle_disableoutput(Client *client, int argc, char *argv[]); + +enum command_return +handle_devices(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/output_control.c b/src/OutputControl.cxx index 7b95be49b..13625ade2 100644 --- a/src/output_control.c +++ b/src/OutputControl.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,19 @@ */ #include "config.h" -#include "output_control.h" +#include "OutputControl.hxx" +#include "OutputThread.hxx" + +extern "C" { #include "output_api.h" #include "output_internal.h" -#include "output_thread.h" #include "mixer_control.h" #include "mixer_plugin.h" +} + +#include "notify.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" #include "filter_plugin.h" -#include "notify.h" #include <assert.h> #include <stdlib.h> @@ -47,7 +52,7 @@ static void ao_command_wait(struct audio_output *ao) { while (ao->command != AO_COMMAND_NONE) { g_mutex_unlock(ao->mutex); - notify_wait(&audio_output_client_notify); + audio_output_client_notify.Wait(); g_mutex_lock(ao->mutex); } } @@ -92,6 +97,14 @@ ao_lock_command(struct audio_output *ao, enum audio_output_command cmd) } void +audio_output_set_replay_gain_mode(struct audio_output *ao, + enum replay_gain_mode mode) +{ + if (ao->replay_gain_filter != NULL) + replay_gain_filter_set_mode(ao->replay_gain_filter, mode); +} + +void audio_output_enable(struct audio_output *ao) { if (ao->thread == NULL) { diff --git a/src/output_control.h b/src/OutputControl.hxx index 874a53518..cf906d2f0 100644 --- a/src/output_control.h +++ b/src/OutputControl.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_OUTPUT_CONTROL_H -#define MPD_OUTPUT_CONTROL_H +#ifndef MPD_OUTPUT_CONTROL_HXX +#define MPD_OUTPUT_CONTROL_HXX + +#include "replay_gain_info.h" #include <glib.h> #include <stddef.h> -#include <stdbool.h> struct audio_output; struct audio_format; @@ -37,6 +38,10 @@ audio_output_quark(void) return g_quark_from_static_string("audio_output"); } +void +audio_output_set_replay_gain_mode(struct audio_output *ao, + enum replay_gain_mode mode); + /** * Enables the device. */ diff --git a/src/OutputError.hxx b/src/OutputError.hxx new file mode 100644 index 000000000..451df9857 --- /dev/null +++ b/src/OutputError.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_ERROR_HXX +#define MPD_OUTPUT_ERROR_HXX + +#include <glib.h> + +/** + * Quark for GError.domain. + */ +G_GNUC_CONST +static inline GQuark +output_quark(void) +{ + return g_quark_from_static_string("output"); +} + +#endif diff --git a/src/output_finish.c b/src/OutputFinish.cxx index e11b43675..ac6ad6977 100644 --- a/src/output_finish.c +++ b/src/OutputFinish.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,13 @@ */ #include "config.h" + +extern "C" { #include "output_internal.h" #include "output_plugin.h" #include "mixer_control.h" #include "filter_plugin.h" +} #include <assert.h> diff --git a/src/output_init.c b/src/OutputInit.cxx index c3b808e94..3c394ba44 100644 --- a/src/output_init.c +++ b/src/OutputInit.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,12 @@ */ #include "config.h" -#include "output_control.h" +#include "OutputControl.hxx" +#include "OutputList.hxx" + +extern "C" { #include "output_api.h" #include "output_internal.h" -#include "output_list.h" #include "audio_parser.h" #include "mixer_control.h" #include "mixer_type.h" @@ -32,7 +34,9 @@ #include "filter_config.h" #include "filter/chain_filter_plugin.h" #include "filter/autoconvert_filter_plugin.h" -#include "filter/replay_gain_filter_plugin.h" +} + +#include "filter/ReplayGainFilterPlugin.hxx" #include <glib.h> @@ -165,6 +169,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; @@ -297,14 +302,14 @@ audio_output_new(const struct config_param *param, if (p == NULL) { g_set_error(error_r, audio_output_quark(), 0, "Missing \"type\" configuration"); - return false; + return nullptr; } plugin = audio_output_plugin_get(p); if (plugin == NULL) { g_set_error(error_r, audio_output_quark(), 0, "No such audio output plugin: %s", p); - return false; + return nullptr; } } else { g_warning("No \"%s\" defined in config file\n", @@ -312,7 +317,7 @@ audio_output_new(const struct config_param *param, plugin = audio_output_detect(error_r); if (plugin == NULL) - return false; + return nullptr; g_message("Successfully detected a %s audio device", plugin->name); diff --git a/src/output_list.c b/src/OutputList.cxx index 835c02bba..df064219f 100644 --- a/src/output_list.c +++ b/src/OutputList.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,13 @@ */ #include "config.h" -#include "output_list.h" +#include "OutputList.hxx" #include "output_api.h" #include "output/alsa_output_plugin.h" #include "output/ao_output_plugin.h" #include "output/ffado_output_plugin.h" #include "output/fifo_output_plugin.h" -#include "output/httpd_output_plugin.h" +#include "output/HttpdOutputPlugin.hxx" #include "output/jack_output_plugin.h" #include "output/mvp_output_plugin.h" #include "output/null_output_plugin.h" diff --git a/src/output_list.h b/src/OutputList.hxx index 185ada716..b7716c67e 100644 --- a/src/output_list.h +++ b/src/OutputList.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_OUTPUT_LIST_H -#define MPD_OUTPUT_LIST_H +#ifndef MPD_OUTPUT_LIST_HXX +#define MPD_OUTPUT_LIST_HXX extern const struct audio_output_plugin *const audio_output_plugins[]; diff --git a/src/output_plugin.c b/src/OutputPlugin.cxx index 221570c1c..9aa0f7792 100644 --- a/src/output_plugin.c +++ b/src/OutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,11 @@ */ #include "config.h" + +extern "C" { #include "output_plugin.h" +} + #include "output_internal.h" struct audio_output * diff --git a/src/output_print.c b/src/OutputPrint.cxx index 483648ca2..240ea967b 100644 --- a/src/output_print.c +++ b/src/OutputPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,15 +23,15 @@ */ #include "config.h" -#include "output_print.h" +#include "OutputPrint.hxx" +#include "OutputAll.hxx" #include "output_internal.h" -#include "output_all.h" -#include "client.h" +#include "Client.hxx" void -printAudioDevices(struct client *client) +printAudioDevices(Client *client) { - unsigned n = audio_output_count(); + const unsigned n = audio_output_count(); for (unsigned i = 0; i < n; ++i) { const struct audio_output *ao = audio_output_get(i); diff --git a/src/output_print.h b/src/OutputPrint.hxx index 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..27fa34f8f 100644 --- a/src/output_state.c +++ b/src/OutputState.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,9 +23,9 @@ */ #include "config.h" -#include "output_state.h" +#include "OutputState.hxx" +#include "OutputAll.hxx" #include "output_internal.h" -#include "output_all.h" #include <glib.h> diff --git a/src/output_state.h b/src/OutputState.hxx index 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/output_thread.c b/src/OutputThread.cxx index 4eef2ccdd..59394e072 100644 --- a/src/output_thread.c +++ b/src/OutputThread.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,18 +18,23 @@ */ #include "config.h" -#include "output_thread.h" +#include "OutputThread.hxx" + +extern "C" { #include "output_api.h" #include "output_internal.h" -#include "chunk.h" -#include "pipe.h" -#include "player_control.h" #include "pcm_mix.h" #include "filter_plugin.h" #include "filter/convert_filter_plugin.h" -#include "filter/replay_gain_filter_plugin.h" +} + +#include "notify.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" +#include "PlayerControl.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" + #include "mpd_error.h" -#include "notify.h" #include "gcc.h" #include <glib.h> @@ -47,7 +52,7 @@ static void ao_command_finished(struct audio_output *ao) ao->command = AO_COMMAND_NONE; g_mutex_unlock(ao->mutex); - notify_signal(&audio_output_client_notify); + audio_output_client_notify.Signal(); g_mutex_lock(ao->mutex); } @@ -315,17 +320,17 @@ ao_wait(struct audio_output *ao) } } -static const char * +static const void * ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, struct filter *replay_gain_filter, unsigned *replay_gain_serial_p, size_t *length_r) { assert(chunk != NULL); - assert(!music_chunk_is_empty(chunk)); - assert(music_chunk_check_format(chunk, &ao->in_audio_format)); + assert(!chunk->IsEmpty()); + assert(chunk->CheckFormat(ao->in_audio_format)); - const char *data = chunk->data; + const void *data = chunk->data; size_t length = chunk->length; (void)ao; @@ -356,14 +361,14 @@ ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, return data; } -static const char * +static const void * ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, size_t *length_r) { GError *error = NULL; size_t length; - const char *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter, + const void *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter, &ao->replay_gain_serial, &length); if (data == NULL) return NULL; @@ -378,7 +383,7 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, if (chunk->other != NULL) { size_t other_length; - const char *other_data = + const void *other_data = ao_chunk_data(ao, chunk->other, ao->other_replay_gain_filter, &ao->other_replay_gain_serial, @@ -399,13 +404,14 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, if (length > other_length) length = other_length; - char *dest = pcm_buffer_get(&ao->cross_fade_buffer, + void *dest = pcm_buffer_get(&ao->cross_fade_buffer, other_length); memcpy(dest, other_data, other_length); - if (!pcm_mix(dest, data, length, ao->in_audio_format.format, + if (!pcm_mix(dest, data, length, + sample_format(ao->in_audio_format.format), 1.0 - chunk->mix_ratio)) { g_warning("Cannot cross-fade format %s", - sample_format_to_string(ao->in_audio_format.format)); + sample_format_to_string(sample_format(ao->in_audio_format.format))); return NULL; } @@ -435,7 +441,7 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) assert(ao != NULL); assert(ao->filter != NULL); - if (chunk->tag != NULL) { + if (ao->tags && gcc_unlikely(chunk->tag != NULL)) { g_mutex_unlock(ao->mutex); ao_plugin_send_tag(ao, chunk->tag); g_mutex_lock(ao->mutex); @@ -446,7 +452,7 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) /* workaround -Wmaybe-uninitialized false positive */ size = 0; #endif - const char *data = ao_filter_chunk(ao, chunk, &size); + const char *data = (const char *)ao_filter_chunk(ao, chunk, &size); if (data == NULL) { ao_close(ao, false); @@ -578,7 +584,7 @@ static void ao_pause(struct audio_output *ao) static gpointer audio_output_task(gpointer arg) { - struct audio_output *ao = arg; + struct audio_output *ao = (struct audio_output *)arg; g_mutex_lock(ao->mutex); diff --git a/src/output_thread.h b/src/OutputThread.hxx index 5ad9a7527..1a7932162 100644 --- a/src/output_thread.h +++ b/src/OutputThread.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_OUTPUT_THREAD_H -#define MPD_OUTPUT_THREAD_H +#ifndef MPD_OUTPUT_THREAD_HXX +#define MPD_OUTPUT_THREAD_HXX struct audio_output; diff --git a/src/Partition.hxx b/src/Partition.hxx new file mode 100644 index 000000000..9efde274a --- /dev/null +++ b/src/Partition.hxx @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PARTITION_HXX +#define MPD_PARTITION_HXX + +#include "Playlist.hxx" +#include "PlayerControl.hxx" + +/** + * A partition of the Music Player Daemon. It is a separate unit with + * a playlist, a player, outputs etc. + */ +struct Partition { + struct playlist playlist; + + player_control pc; + + Partition(unsigned max_length, + unsigned buffer_chunks, + unsigned buffered_before_play) + :playlist(max_length), + pc(buffer_chunks, buffered_before_play) { + } + + void ClearQueue() { + playlist.Clear(pc); + } + + enum playlist_result AppendFile(const char *path_fs, + unsigned *added_id=nullptr) { + return playlist.AppendFile(pc, path_fs, added_id); + } + + enum playlist_result AppendURI(const char *uri_utf8, + unsigned *added_id=nullptr) { + return playlist.AppendURI(pc, uri_utf8, added_id); + } + + enum playlist_result DeletePosition(unsigned position) { + return playlist.DeletePosition(pc, position); + } + + enum playlist_result DeleteId(unsigned id) { + return playlist.DeleteId(pc, id); + } + + /** + * Deletes a range of songs from the playlist. + * + * @param start the position of the first song to delete + * @param end the position after the last song to delete + */ + enum playlist_result DeleteRange(unsigned start, unsigned end) { + return playlist.DeleteRange(pc, start, end); + } + + void DeleteSong(const song &song) { + playlist.DeleteSong(pc, song); + } + + void Shuffle(unsigned start, unsigned end) { + playlist.Shuffle(pc, start, end); + } + + enum playlist_result MoveRange(unsigned start, unsigned end, int to) { + return playlist.MoveRange(pc, start, end, to); + } + + enum playlist_result MoveId(unsigned id, int to) { + return playlist.MoveId(pc, id, to); + } + + enum playlist_result SwapPositions(unsigned song1, unsigned song2) { + return playlist.SwapPositions(pc, song1, song2); + } + + enum playlist_result SwapIds(unsigned id1, unsigned id2) { + return playlist.SwapIds(pc, id1, id2); + } + + enum playlist_result SetPriorityRange(unsigned start_position, + unsigned end_position, + uint8_t priority) { + return playlist.SetPriorityRange(pc, + start_position, end_position, + priority); + } + + enum playlist_result SetPriorityId(unsigned song_id, + uint8_t priority) { + return playlist.SetPriorityId(pc, song_id, priority); + } + + void Stop() { + playlist.Stop(pc); + } + + enum playlist_result PlayPosition(int position) { + return playlist.PlayPosition(pc, position); + } + + enum playlist_result PlayId(int id) { + return playlist.PlayId(pc, id); + } + + void PlayNext() { + return playlist.PlayNext(pc); + } + + void PlayPrevious() { + return playlist.PlayPrevious(pc); + } + + enum playlist_result SeekSongPosition(unsigned song_position, + float seek_time) { + return playlist.SeekSongPosition(pc, song_position, seek_time); + } + + enum playlist_result SeekSongId(unsigned song_id, float seek_time) { + return playlist.SeekSongId(pc, song_id, seek_time); + } + + enum playlist_result SeekCurrent(float seek_time, bool relative) { + return playlist.SeekCurrent(pc, seek_time, relative); + } + + void SetRepeat(bool new_value) { + playlist.SetRepeat(pc, new_value); + } + + bool GetRandom() const { + return playlist.GetRandom(); + } + + void SetRandom(bool new_value) { + playlist.SetRandom(pc, new_value); + } + + void SetSingle(bool new_value) { + playlist.SetSingle(pc, new_value); + } + + void SetConsume(bool new_value) { + playlist.SetConsume(new_value); + } +}; + +#endif diff --git a/src/permission.c b/src/Permission.cxx index cd52b9c86..e6cf5cfb8 100644 --- a/src/permission.c +++ b/src/Permission.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,12 @@ */ #include "config.h" -#include "permission.h" -#include "conf.h" +#include "Permission.hxx" #include "mpd_error.h" +#include "conf.h" + +#include <map> +#include <string> #include <glib.h> @@ -35,7 +38,7 @@ #define PERMISSION_CONTROL_STRING "control" #define PERMISSION_ADMIN_STRING "admin" -static GHashTable *permission_passwords; +static std::map<std::string, unsigned> permission_passwords; static unsigned permission_default; @@ -75,9 +78,6 @@ void initPermissions(void) unsigned permission; const struct config_param *param; - permission_passwords = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, NULL); - permission_default = PERMISSION_READ | PERMISSION_ADD | PERMISSION_CONTROL | PERMISSION_ADMIN; @@ -101,9 +101,8 @@ void initPermissions(void) permission = parsePermissions(separator + 1); - g_hash_table_replace(permission_passwords, - password, - GINT_TO_POINTER(permission)); + permission_passwords.insert(std::make_pair(password, + permission)); } while ((param = config_get_next_param(CONF_PASSWORD, param))); } @@ -115,23 +114,14 @@ void initPermissions(void) int getPermissionFromPassword(char const* password, unsigned* permission) { - bool found; - gpointer key, value; - - found = g_hash_table_lookup_extended(permission_passwords, - password, &key, &value); - if (!found) + auto i = permission_passwords.find(password); + if (i == permission_passwords.end()) return -1; - *permission = GPOINTER_TO_INT(value); + *permission = i->second; return 0; } -void finishPermissions(void) -{ - g_hash_table_destroy(permission_passwords); -} - unsigned getDefaultPermissions(void) { return permission_default; diff --git a/src/permission.h b/src/Permission.hxx index 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..99ede9bd8 --- /dev/null +++ b/src/PlayerCommands.cxx @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlayerCommands.hxx" +#include "CommandError.hxx" +#include "Playlist.hxx" +#include "PlaylistPrint.hxx" +#include "UpdateGlue.hxx" +#include "ClientInternal.hxx" +#include "Volume.hxx" +#include "OutputAll.hxx" +#include "Partition.hxx" +#include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" + +extern "C" { +#include "audio_format.h" +} + +#include "replay_gain_config.h" + +#include <errno.h> + +#define COMMAND_STATUS_STATE "state" +#define COMMAND_STATUS_REPEAT "repeat" +#define COMMAND_STATUS_SINGLE "single" +#define COMMAND_STATUS_CONSUME "consume" +#define COMMAND_STATUS_RANDOM "random" +#define COMMAND_STATUS_PLAYLIST "playlist" +#define COMMAND_STATUS_PLAYLIST_LENGTH "playlistlength" +#define COMMAND_STATUS_SONG "song" +#define COMMAND_STATUS_SONGID "songid" +#define COMMAND_STATUS_NEXTSONG "nextsong" +#define COMMAND_STATUS_NEXTSONGID "nextsongid" +#define COMMAND_STATUS_TIME "time" +#define COMMAND_STATUS_BITRATE "bitrate" +#define COMMAND_STATUS_ERROR "error" +#define COMMAND_STATUS_CROSSFADE "xfade" +#define COMMAND_STATUS_MIXRAMPDB "mixrampdb" +#define COMMAND_STATUS_MIXRAMPDELAY "mixrampdelay" +#define COMMAND_STATUS_AUDIO "audio" +#define COMMAND_STATUS_UPDATING_DB "updating_db" + +enum command_return +handle_play(Client *client, int argc, char *argv[]) +{ + int song = -1; + + if (argc == 2 && !check_int(client, &song, argv[1])) + return COMMAND_RETURN_ERROR; + enum playlist_result result = client->partition.PlayPosition(song); + return print_playlist_result(client, result); +} + +enum command_return +handle_playid(Client *client, int argc, char *argv[]) +{ + int id = -1; + + if (argc == 2 && !check_int(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = client->partition.PlayId(id); + return print_playlist_result(client, result); +} + +enum command_return +handle_stop(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + client->partition.Stop(); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_currentsong(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist_print_current(client, &client->playlist); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_pause(Client *client, + int argc, char *argv[]) +{ + if (argc == 2) { + bool pause_flag; + if (!check_bool(client, &pause_flag, argv[1])) + return COMMAND_RETURN_ERROR; + + 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; + } + + const playlist &playlist = client->playlist; + client_printf(client, + "volume: %i\n" + COMMAND_STATUS_REPEAT ": %i\n" + COMMAND_STATUS_RANDOM ": %i\n" + COMMAND_STATUS_SINGLE ": %i\n" + COMMAND_STATUS_CONSUME ": %i\n" + COMMAND_STATUS_PLAYLIST ": %li\n" + COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" + COMMAND_STATUS_CROSSFADE ": %i\n" + COMMAND_STATUS_MIXRAMPDB ": %f\n" + COMMAND_STATUS_MIXRAMPDELAY ": %f\n" + COMMAND_STATUS_STATE ": %s\n", + volume_level_get(), + playlist.GetRepeat(), + playlist.GetRandom(), + playlist.GetSingle(), + playlist.GetConsume(), + (unsigned long)playlist.GetVersion(), + playlist.GetLength(), + (int)(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.GetCurrentPosition(); + if (song >= 0) { + client_printf(client, + COMMAND_STATUS_SONG ": %i\n" + COMMAND_STATUS_SONGID ": %u\n", + song, playlist.PositionToId(song)); + } + + if (player_status.state != PLAYER_STATE_STOP) { + struct audio_format_string af_string; + + client_printf(client, + COMMAND_STATUS_TIME ": %i:%i\n" + "elapsed: %1.3f\n" + COMMAND_STATUS_BITRATE ": %u\n" + COMMAND_STATUS_AUDIO ": %s\n", + (int)(player_status.elapsed_time + 0.5), + (int)(player_status.total_time + 0.5), + player_status.elapsed_time, + player_status.bit_rate, + audio_format_to_string(&player_status.audio_format, + &af_string)); + } + + if ((updateJobId = isUpdatingDB())) { + client_printf(client, + COMMAND_STATUS_UPDATING_DB ": %i\n", + updateJobId); + } + + 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.GetNextPosition(); + if (song >= 0) { + client_printf(client, + COMMAND_STATUS_NEXTSONG ": %i\n" + COMMAND_STATUS_NEXTSONGID ": %u\n", + song, playlist.PositionToId(song)); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_next(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist &playlist = client->playlist; + + /* single mode is not considered when this is user who + * wants to change song. */ + const bool single = playlist.queue.single; + playlist.queue.single = false; + + client->partition.PlayNext(); + + playlist.queue.single = single; + return COMMAND_RETURN_OK; +} + +enum command_return +handle_previous(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + client->partition.PlayPrevious(); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_repeat(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.SetRepeat(status); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_single(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.SetSingle(status); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_consume(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.SetConsume(status); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_random(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.SetRandom(status); + audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client->partition.GetRandom())); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_clearerror(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + 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; + + if (!check_unsigned(client, &song, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &seek_time, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SeekSongPosition(song, seek_time); + return print_playlist_result(client, result); +} + +enum command_return +handle_seekid(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned id, seek_time; + + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &seek_time, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SeekSongId(id, seek_time); + return print_playlist_result(client, result); +} + +enum command_return +handle_seekcur(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *p = argv[1]; + bool relative = *p == '+' || *p == '-'; + int seek_time; + if (!check_int(client, &seek_time, p)) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SeekCurrent(seek_time, relative); + return print_playlist_result(client, result); +} + +enum command_return +handle_crossfade(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned xfade_time; + + if (!check_unsigned(client, &xfade_time, argv[1])) + return COMMAND_RETURN_ERROR; + 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; + } + + audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client->playlist.queue.random)); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_replay_gain_status(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + client_printf(client, "replay_gain_mode: %s\n", + replay_gain_get_mode_string()); + return COMMAND_RETURN_OK; +} diff --git a/src/PlayerCommands.hxx b/src/PlayerCommands.hxx new file mode 100644 index 000000000..a2fed5853 --- /dev/null +++ b/src/PlayerCommands.hxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYER_COMMANDS_HXX +#define MPD_PLAYER_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_play(Client *client, int argc, char *argv[]); + +enum command_return +handle_playid(Client *client, int argc, char *argv[]); + +enum command_return +handle_stop(Client *client, int argc, char *argv[]); + +enum command_return +handle_currentsong(Client *client, int argc, char *argv[]); + +enum command_return +handle_pause(Client *client, int argc, char *argv[]); + +enum command_return +handle_status(Client *client, int argc, char *argv[]); + +enum command_return +handle_next(Client *client, int argc, char *argv[]); + +enum command_return +handle_previous(Client *client, int argc, char *avg[]); + +enum command_return +handle_repeat(Client *client, int argc, char *argv[]); + +enum command_return +handle_single(Client *client, int argc, char *argv[]); + +enum command_return +handle_consume(Client *client, int argc, char *argv[]); + +enum command_return +handle_random(Client *client, int argc, char *argv[]); + +enum command_return +handle_clearerror(Client *client, int argc, char *argv[]); + +enum command_return +handle_seek(Client *client, int argc, char *argv[]); + +enum command_return +handle_seekid(Client *client, int argc, char *argv[]); + +enum command_return +handle_seekcur(Client *client, int argc, char *argv[]); + +enum command_return +handle_crossfade(Client *client, int argc, char *argv[]); + +enum command_return +handle_mixrampdb(Client *client, int argc, char *argv[]); + +enum command_return +handle_mixrampdelay(Client *client, int argc, char *argv[]); + +enum command_return +handle_replay_gain_mode(Client *client, int argc, char *argv[]); + +enum command_return +handle_replay_gain_status(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/player_control.c b/src/PlayerControl.cxx index 90f616d77..62a8a4f80 100644 --- a/src/player_control.c +++ b/src/PlayerControl.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,78 +18,61 @@ */ #include "config.h" -#include "player_control.h" -#include "decoder_control.h" -#include "path.h" -#include "log.h" -#include "tag.h" +#include "PlayerControl.hxx" +#include "Idle.hxx" #include "song.h" -#include "idle.h" -#include "pcm_volume.h" -#include "main.h" +#include "DecoderControl.hxx" +#include "Main.hxx" + +#include <cmath> #include <assert.h> #include <stdio.h> -#include <math.h> static void pc_enqueue_song_locked(struct player_control *pc, struct song *song); -struct player_control * -pc_new(unsigned buffer_chunks, unsigned int buffered_before_play) +player_control::player_control(unsigned _buffer_chunks, + unsigned _buffered_before_play) + :buffer_chunks(_buffer_chunks), + buffered_before_play(_buffered_before_play), + thread(nullptr), + command(PLAYER_COMMAND_NONE), + state(PLAYER_STATE_STOP), + error_type(PLAYER_ERROR_NONE), + error(nullptr), + next_song(nullptr), + cross_fade_seconds(0), + mixramp_db(0), + mixramp_delay_seconds(std::nanf("")), + total_play_time(0), + border_pause(false) { - struct player_control *pc = g_new0(struct player_control, 1); - - pc->buffer_chunks = buffer_chunks; - pc->buffered_before_play = buffered_before_play; - - pc->mutex = g_mutex_new(); - pc->cond = g_cond_new(); - - pc->command = PLAYER_COMMAND_NONE; - pc->error = PLAYER_ERROR_NOERROR; - pc->state = PLAYER_STATE_STOP; - pc->cross_fade_seconds = 0; - pc->mixramp_db = 0; - pc->mixramp_delay_seconds = nanf(""); - - return pc; } -void -pc_free(struct player_control *pc) +player_control::~player_control() { - g_cond_free(pc->cond); - g_mutex_free(pc->mutex); - g_free(pc); + if (next_song != nullptr) + song_free(next_song); } void -player_wait_decoder(struct player_control *pc, struct decoder_control *dc) +player_wait_decoder(gcc_unused struct player_control *pc, + struct decoder_control *dc) { assert(pc != NULL); assert(dc != NULL); - assert(dc->client_cond == pc->cond); /* during this function, the decoder lock is held, because we're waiting for the decoder thread */ - g_cond_wait(pc->cond, dc->mutex); -} - -void -pc_song_deleted(struct player_control *pc, const struct song *song) -{ - if (pc->errored_song == song) { - pc->error = PLAYER_ERROR_NOERROR; - pc->errored_song = NULL; - } + g_cond_wait(dc->client_cond, dc->mutex); } static void player_command_wait_locked(struct player_control *pc) { while (pc->command != PLAYER_COMMAND_NONE) - g_cond_wait(main_cond, pc->mutex); + pc->cond.wait(pc->mutex); } static void @@ -236,70 +219,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 +284,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 +316,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 +324,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 +331,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/PlayerControl.hxx index a77d31ec5..322b95c84 100644 --- a/src/player_control.h +++ b/src/PlayerControl.hxx @@ -21,6 +21,8 @@ #define MPD_PLAYER_H #include "audio_format.h" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" #include <glib.h> @@ -66,12 +68,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 { @@ -94,22 +101,39 @@ struct player_control { /** * This lock protects #command, #state, #error. */ - GMutex *mutex; + Mutex mutex; /** * Trigger this object after you have modified #command. */ - GCond *cond; + Cond cond; 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; @@ -124,13 +148,11 @@ struct player_control { * time. */ bool border_pause; -}; - -struct player_control * -pc_new(unsigned buffer_chunks, unsigned buffered_before_play); -void -pc_free(struct player_control *pc); + player_control(unsigned buffer_chunks, + unsigned buffered_before_play); + ~player_control(); +}; /** * Locks the #player_control object. @@ -138,7 +160,7 @@ pc_free(struct player_control *pc); static inline void player_lock(struct player_control *pc) { - g_mutex_lock(pc->mutex); + pc->mutex.lock(); } /** @@ -147,7 +169,7 @@ player_lock(struct player_control *pc) static inline void player_unlock(struct player_control *pc) { - g_mutex_unlock(pc->mutex); + pc->mutex.unlock(); } /** @@ -158,7 +180,7 @@ player_unlock(struct player_control *pc) static inline void player_wait(struct player_control *pc) { - g_cond_wait(pc->cond, pc->mutex); + pc->cond.wait(pc->mutex); } /** @@ -178,7 +200,7 @@ player_wait_decoder(struct player_control *pc, struct decoder_control *dc); static inline void player_signal(struct player_control *pc) { - g_cond_signal(pc->cond); + pc->cond.signal(); } /** @@ -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/player_thread.c b/src/PlayerThread.cxx index 707fb27ae..fb7432368 100644 --- a/src/player_thread.c +++ b/src/PlayerThread.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,23 +18,23 @@ */ #include "config.h" -#include "player_thread.h" -#include "player_control.h" -#include "decoder_control.h" -#include "decoder_thread.h" -#include "output_all.h" -#include "pcm_volume.h" -#include "path.h" -#include "event_pipe.h" -#include "crossfade.h" +#include "PlayerThread.hxx" +#include "DecoderThread.hxx" +#include "DecoderControl.hxx" +#include "MusicPipe.hxx" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" #include "song.h" -#include "tag.h" -#include "pipe.h" -#include "chunk.h" -#include "idle.h" -#include "main.h" -#include "buffer.h" +#include "Main.hxx" #include "mpd_error.h" +#include "CrossFade.hxx" +#include "PlayerControl.hxx" +#include "OutputAll.hxx" +#include "tag.h" +#include "Idle.hxx" +#include "GlobalEvents.hxx" + +#include <cmath> #include <glib.h> @@ -123,6 +123,20 @@ struct player { * precisely. */ float elapsed_time; + + player(player_control *_pc, decoder_control *_dc) + :pc(_pc), dc(_dc), + buffering(false), + decoder_starting(false), + paused(false), + queued(true), + output_open(false), + song(NULL), + xfade(XFADE_UNKNOWN), + cross_fading(false), + cross_fade_chunks(0), + cross_fade_tag(NULL), + elapsed_time(0.0) {} }; static struct music_buffer *player_buffer; @@ -133,7 +147,7 @@ player_command_finished_locked(struct player_control *pc) assert(pc->command != PLAYER_COMMAND_NONE); pc->command = PLAYER_COMMAND_NONE; - g_cond_signal(main_cond); + pc->cond.signal(); } static void @@ -162,7 +176,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 +249,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; @@ -265,7 +285,7 @@ player_wait_for_decoder(struct player *player) player_unlock(pc); /* call syncPlaylistWithQueue() in the main thread */ - event_pipe_emit(PIPE_EVENT_PLAYLIST); + GlobalEvents::Emit(GlobalEvents::PLAYLIST); return true; } @@ -305,7 +325,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 +337,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 +346,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 +371,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 +453,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 +480,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 +506,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 +627,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); @@ -635,7 +665,7 @@ update_song_tag(struct song *song, const struct tag *new_tag) /* the main thread will update the playlist version when he receives this event */ - event_pipe_emit(PIPE_EVENT_TAG); + GlobalEvents::Emit(GlobalEvents::TAG); /* notify all clients that the tag of the current song has changed */ @@ -652,9 +682,10 @@ update_song_tag(struct song *song, const struct tag *new_tag) static bool play_chunk(struct player_control *pc, struct song *song, struct music_chunk *chunk, - const struct audio_format *format) + const struct audio_format *format, + GError **error_r) { - assert(music_chunk_check_format(chunk, format)); + assert(chunk->CheckFormat(*format)); if (chunk->tag != NULL) update_song_tag(song, chunk->tag); @@ -670,7 +701,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 / @@ -727,14 +758,14 @@ play_next_chunk(struct player *player) other_chunk->tag); other_chunk->tag = NULL; - if (isnan(pc->mixramp_delay_seconds)) { + if (std::isnan(pc->mixramp_delay_seconds)) { chunk->mix_ratio = ((float)cross_fade_position) / player->cross_fade_chunks; } else { chunk->mix_ratio = nan(""); } - if (music_chunk_is_empty(other_chunk)) { + if (other_chunk->IsEmpty()) { /* the "other" chunk was a music_chunk which had only a tag, but no music data - we cannot cross-fade that; @@ -785,13 +816,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 +896,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,10 +904,12 @@ 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); - event_pipe_emit(PIPE_EVENT_PLAYLIST); + GlobalEvents::Emit(GlobalEvents::PLAYLIST); player_lock(pc); return; } @@ -1049,10 +1071,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; } @@ -1060,7 +1086,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) player_unlock(pc); - event_pipe_emit(PIPE_EVENT_PLAYLIST); + GlobalEvents::Emit(GlobalEvents::PLAYLIST); player_lock(pc); } @@ -1068,9 +1094,9 @@ 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); + struct decoder_control *dc = dc_new(); decoder_thread_start(dc); player_buffer = music_buffer_new(pc->buffer_chunks); @@ -1094,7 +1120,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 +1165,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.cxx b/src/Playlist.cxx new file mode 100644 index 000000000..1977188da --- /dev/null +++ b/src/Playlist.cxx @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlayerControl.hxx" +#include "song.h" +#include "Idle.hxx" + +#include <glib.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "playlist" + +void +playlist::FullIncrementVersions() +{ + queue.ModifyAll(); + idle_add(IDLE_PLAYLIST); +} + +void +playlist::TagChanged() +{ + if (!playing) + return; + + assert(current >= 0); + + queue.ModifyAtOrder(current); + idle_add(IDLE_PLAYLIST); +} + +/** + * Queue a song, addressed by its order number. + */ +static void +playlist_queue_song_order(struct playlist *playlist, struct player_control *pc, + unsigned order) +{ + char *uri; + + assert(playlist->queue.IsValidOrder(order)); + + playlist->queued = order; + + struct song *song = + song_dup_detached(playlist->queue.GetOrder(order)); + + uri = song_get_uri(song); + g_debug("queue song %i:\"%s\"", playlist->queued, uri); + g_free(uri); + + pc_enqueue_song(pc, song); +} + +/** + * Called if the player thread has started playing the "queued" song. + */ +static void +playlist_song_started(struct playlist *playlist, struct player_control *pc) +{ + assert(pc->next_song == NULL); + assert(playlist->queued >= -1); + + /* queued song has started: copy queued to current, + and notify the clients */ + + int current = playlist->current; + playlist->current = playlist->queued; + playlist->queued = -1; + + if(playlist->queue.consume) + playlist->DeleteOrder(*pc, current); + + idle_add(IDLE_PLAYER); +} + +const struct song * +playlist::GetQueuedSong() const +{ + return playing && queued >= 0 + ? queue.GetOrder(queued) + : nullptr; +} + +void +playlist::UpdateQueuedSong(player_control &pc, const song *prev) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + assert((queued < 0) == (prev == NULL)); + + const int next_order = current >= 0 + ? queue.GetNextOrder(current) + : 0; + + if (next_order == 0 && queue.random && !queue.single) { + /* shuffle the song order again, so we get a different + order each time the playlist is played + completely */ + const unsigned current_position = + queue.OrderToPosition(current); + + queue.ShuffleOrder(); + + /* make sure that the current still points to + the current song, after the song order has been + shuffled */ + current = queue.PositionToOrder(current_position); + } + + const struct song *const next_song = next_order >= 0 + ? queue.GetOrder(next_order) + : nullptr; + + if (prev != NULL && next_song != prev) { + /* clear the currently queued song */ + pc_cancel(&pc); + queued = -1; + } + + if (next_order >= 0) { + if (next_song != prev) + playlist_queue_song_order(this, &pc, next_order); + else + queued = next_order; + } +} + +void +playlist::PlayOrder(player_control &pc, int order) +{ + playing = true; + queued = -1; + + struct song *song = song_dup_detached(queue.GetOrder(order)); + + char *uri = song_get_uri(song); + g_debug("play %i:\"%s\"", order, uri); + g_free(uri); + + pc_play(&pc, song); + current = order; +} + +static void +playlist_resume_playback(struct playlist *playlist, struct player_control *pc); + +void +playlist::SyncWithPlayer(player_control &pc) +{ + if (!playing) + /* this event has reached us out of sync: we aren't + playing anymore; ignore the event */ + return; + + player_lock(&pc); + const enum player_state pc_state = pc_get_state(&pc); + const song *pc_next_song = pc.next_song; + player_unlock(&pc); + + if (pc_state == PLAYER_STATE_STOP) + /* the player thread has stopped: check if playback + should be restarted with the next song. That can + happen if the playlist isn't filling the queue fast + enough */ + playlist_resume_playback(this, &pc); + else { + /* check if the player thread has already started + playing the queued song */ + if (pc_next_song == nullptr && queued != -1) + playlist_song_started(this, &pc); + + player_lock(&pc); + pc_next_song = pc.next_song; + player_unlock(&pc); + + /* make sure the queued song is always set (if + possible) */ + if (pc_next_song == nullptr && queued < 0) + UpdateQueuedSong(pc, nullptr); + } +} + +/** + * The player has stopped for some reason. Check the error, and + * decide whether to re-start playback + */ +static void +playlist_resume_playback(struct playlist *playlist, struct player_control *pc) +{ + enum player_error error; + + assert(playlist->playing); + assert(pc_get_state(pc) == PLAYER_STATE_STOP); + + 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_NONE) || + error == PLAYER_ERROR_OUTPUT || + playlist->error_count >= playlist->queue.GetLength()) + /* too many errors, or critical error: stop + playback */ + playlist->Stop(*pc); + else + /* continue playback at the next song */ + playlist->PlayNext(*pc); +} + +void +playlist::SetRepeat(player_control &pc, bool status) +{ + if (status == queue.repeat) + return; + + queue.repeat = status; + + pc_set_border_pause(&pc, queue.single && !queue.repeat); + + /* if the last song is currently being played, the "next song" + might change when repeat mode is toggled */ + UpdateQueuedSong(pc, GetQueuedSong()); + + idle_add(IDLE_OPTIONS); +} + +static void +playlist_order(struct playlist *playlist) +{ + if (playlist->current >= 0) + /* update playlist.current, order==position now */ + playlist->current = playlist->queue.OrderToPosition(playlist->current); + + playlist->queue.RestoreOrder(); +} + +void +playlist::SetSingle(player_control &pc, bool status) +{ + if (status == queue.single) + return; + + queue.single = status; + + pc_set_border_pause(&pc, queue.single && !queue.repeat); + + /* if the last song is currently being played, the "next song" + might change when single mode is toggled */ + UpdateQueuedSong(pc, GetQueuedSong()); + + idle_add(IDLE_OPTIONS); +} + +void +playlist::SetConsume(bool status) +{ + if (status == queue.consume) + return; + + queue.consume = status; + idle_add(IDLE_OPTIONS); +} + +void +playlist::SetRandom(player_control &pc, bool status) +{ + if (status == queue.random) + return; + + const struct song *const queued_song = GetQueuedSong(); + + queue.random = status; + + if (queue.random) { + /* shuffle the queue order, but preserve current */ + + const int current_position = GetCurrentPosition(); + + queue.ShuffleOrder(); + + if (current_position >= 0) { + /* make sure the current song is the first in + the order list, so the whole rest of the + playlist is played after that */ + unsigned current_order = + queue.PositionToOrder(current_position); + queue.SwapOrders(0, current_order); + current = 0; + } else + current = -1; + } else + playlist_order(this); + + UpdateQueuedSong(pc, queued_song); + + idle_add(IDLE_OPTIONS); +} + +int +playlist::GetCurrentPosition() const +{ + return current >= 0 + ? queue.OrderToPosition(current) + : -1; +} + +int +playlist::GetNextPosition() const +{ + if (current < 0) + return -1; + + if (queue.single && queue.repeat) + return queue.OrderToPosition(current); + else if (queue.IsValidOrder(current + 1)) + return queue.OrderToPosition(current + 1); + else if (queue.repeat) + return queue.OrderToPosition(0); + + return -1; +} diff --git a/src/Playlist.hxx b/src/Playlist.hxx new file mode 100644 index 000000000..46a9250a7 --- /dev/null +++ b/src/Playlist.hxx @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_HXX +#define MPD_PLAYLIST_HXX + +#include "Queue.hxx" +#include "playlist_error.h" + +#include <stdbool.h> + +struct player_control; + +struct playlist { + /** + * The song queue - it contains the "real" playlist. + */ + struct queue queue; + + /** + * This value is true if the player is currently playing (or + * should be playing). + */ + bool playing; + + /** + * If true, then any error is fatal; if false, MPD will + * attempt to play the next song on non-fatal errors. During + * seeking, this flag is set. + */ + bool stop_on_error; + + /** + * Number of errors since playback was started. If this + * number exceeds the length of the playlist, MPD gives up, + * because all songs have been tried. + */ + unsigned error_count; + + /** + * The "current song pointer". This is the song which is + * played when we get the "play" command. It is also the song + * which is currently being played. + */ + int current; + + /** + * The "next" song to be played, when the current one + * finishes. The decoder thread may start decoding and + * buffering it, while the "current" song is still playing. + * + * This variable is only valid if #playing is true. + */ + int queued; + + playlist(unsigned max_length) + :queue(max_length), playing(false), current(-1), queued(-1) { + } + + ~playlist() { + } + + uint32_t GetVersion() const { + return queue.version; + } + + unsigned GetLength() const { + return queue.GetLength(); + } + + unsigned PositionToId(unsigned position) const { + return queue.PositionToId(position); + } + + gcc_pure + int GetCurrentPosition() const; + + gcc_pure + int GetNextPosition() const; + + /** + * Returns the song object which is currently queued. Returns + * none if there is none (yet?) or if MPD isn't playing. + */ + gcc_pure + const struct song *GetQueuedSong() const; + + /** + * This is the "PLAYLIST" event handler. It is invoked by the + * player thread whenever it requests a new queued song, or + * when it exits. + */ + void SyncWithPlayer(player_control &pc); + +protected: + /** + * Called by all editing methods after a modification. + * Updates the queue version and emits #IDLE_PLAYLIST. + */ + void OnModified(); + + /** + * Updates the "queued song". Calculates the next song + * according to the current one (if MPD isn't playing, it + * takes the first song), and queues this song. Clears the + * old queued song if there was one. + * + * @param prev the song which was previously queued, as + * determined by playlist_get_queued_song() + */ + void UpdateQueuedSong(player_control &pc, const song *prev); + +public: + void Clear(player_control &pc); + + void TagChanged(); + + void FullIncrementVersions(); + + enum playlist_result AppendSong(player_control &pc, + struct song *song, + unsigned *added_id=nullptr); + + /** + * Appends a local file (outside the music database) to the + * playlist. + * + * Note: the caller is responsible for checking permissions. + */ + enum playlist_result AppendFile(player_control &pc, + const char *path_fs, + unsigned *added_id=nullptr); + + enum playlist_result AppendURI(player_control &pc, + const char *uri_utf8, + unsigned *added_id=nullptr); + +protected: + void DeleteInternal(player_control &pc, + unsigned song, const struct song **queued_p); + +public: + enum playlist_result DeletePosition(player_control &pc, + unsigned position); + + enum playlist_result DeleteOrder(player_control &pc, + unsigned order) { + return DeletePosition(pc, queue.OrderToPosition(order)); + } + + enum playlist_result DeleteId(player_control &pc, unsigned id); + + /** + * Deletes a range of songs from the playlist. + * + * @param start the position of the first song to delete + * @param end the position after the last song to delete + */ + enum playlist_result DeleteRange(player_control &pc, + unsigned start, unsigned end); + + void DeleteSong(player_control &pc, const song &song); + + void Shuffle(player_control &pc, unsigned start, unsigned end); + + enum playlist_result MoveRange(player_control &pc, + unsigned start, unsigned end, int to); + + enum playlist_result MoveId(player_control &pc, unsigned id, int to); + + enum playlist_result SwapPositions(player_control &pc, + unsigned song1, unsigned song2); + + enum playlist_result SwapIds(player_control &pc, + unsigned id1, unsigned id2); + + enum playlist_result SetPriorityRange(player_control &pc, + unsigned start_position, + unsigned end_position, + uint8_t priority); + + enum playlist_result SetPriorityId(player_control &pc, + unsigned song_id, uint8_t priority); + + void Stop(player_control &pc); + + enum playlist_result PlayPosition(player_control &pc, int position); + + void PlayOrder(player_control &pc, int order); + + enum playlist_result PlayId(player_control &pc, int id); + + void PlayNext(player_control &pc); + + void PlayPrevious(player_control &pc); + + enum playlist_result SeekSongPosition(player_control &pc, + unsigned song_position, + float seek_time); + + enum playlist_result SeekSongId(player_control &pc, + unsigned song_id, float seek_time); + + /** + * Seek within the current song. Fails if MPD is not currently + * playing. + * + * @param time the time in seconds + * @param relative if true, then the specified time is relative to the + * current position + */ + enum playlist_result SeekCurrent(player_control &pc, + float seek_time, bool relative); + + bool GetRepeat() const { + return queue.repeat; + } + + void SetRepeat(player_control &pc, bool new_value); + + bool GetRandom() const { + return queue.random; + } + + void SetRandom(player_control &pc, bool new_value); + + bool GetSingle() const { + return queue.single; + } + + void SetSingle(player_control &pc, bool new_value); + + bool GetConsume() const { + return queue.consume; + } + + void SetConsume(bool new_value); +}; + +#endif diff --git a/src/playlist_any.c b/src/PlaylistAny.cxx index 450ca5932..f62634c96 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" +#include "input_stream.h" + +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..dc3b3e0de --- /dev/null +++ b/src/PlaylistCommands.cxx @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlaylistCommands.hxx" +#include "DatabasePlaylist.hxx" +#include "CommandError.hxx" +#include "PlaylistPrint.hxx" +#include "PlaylistSave.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistVector.hxx" +#include "PlaylistQueue.hxx" +#include "TimePrint.hxx" +#include "ClientInternal.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "ls.hxx" +#include "Playlist.hxx" + +extern "C" { +#include "uri.h" +} + +#include <assert.h> +#include <stdlib.h> + +static void +print_spl_list(Client *client, const PlaylistVector &list) +{ + for (const auto &i : list) { + client_printf(client, "playlist: %s\n", i.name.c_str()); + + if (i.mtime > 0) + time_print(client, "Last-Modified", i.mtime); + } +} + +enum command_return +handle_save(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + enum playlist_result result; + + result = spl_save_playlist(argv[1], &client->playlist); + return print_playlist_result(client, result); +} + +enum command_return +handle_load(Client *client, int argc, char *argv[]) +{ + unsigned start_index, end_index; + + if (argc < 3) { + start_index = 0; + end_index = G_MAXUINT; + } else if (!check_range(client, &start_index, &end_index, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result; + + result = playlist_open_into_queue(argv[1], + start_index, end_index, + &client->playlist, + client->player_control, true); + if (result != PLAYLIST_RESULT_NO_SUCH_LIST) + return print_playlist_result(client, result); + + GError *error = NULL; + if (playlist_load_spl(&client->playlist, client->player_control, + argv[1], start_index, end_index, + &error)) + return COMMAND_RETURN_OK; + + if (error->domain == playlist_quark() && + error->code == PLAYLIST_RESULT_BAD_NAME) + /* the message for BAD_NAME is confusing when the + client wants to load a playlist file from the music + directory; patch the GError object to show "no such + playlist" instead */ + error->code = PLAYLIST_RESULT_NO_SUCH_LIST; + + return print_error(client, error); +} + +enum command_return +handle_listplaylist(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + if (playlist_file_print(client, argv[1], false)) + return COMMAND_RETURN_OK; + + GError *error = NULL; + return spl_print(client, argv[1], false, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_listplaylistinfo(Client *client, + G_GNUC_UNUSED int argc, char *argv[]) +{ + if (playlist_file_print(client, argv[1], true)) + return COMMAND_RETURN_OK; + + GError *error = NULL; + return spl_print(client, argv[1], true, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_rm(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + GError *error = NULL; + return spl_delete(argv[1], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_rename(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + GError *error = NULL; + return spl_rename(argv[1], argv[2], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistdelete(Client *client, + G_GNUC_UNUSED int argc, char *argv[]) { + char *playlist = argv[1]; + unsigned from; + + if (!check_unsigned(client, &from, argv[2])) + return COMMAND_RETURN_ERROR; + + GError *error = NULL; + return spl_remove_index(playlist, from, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistmove(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + char *playlist = argv[1]; + unsigned from, to; + + if (!check_unsigned(client, &from, argv[2])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &to, argv[3])) + return COMMAND_RETURN_ERROR; + + GError *error = NULL; + return spl_move_index(playlist, from, to, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistclear(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + GError *error = NULL; + return spl_clear(argv[1], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistadd(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + char *playlist = argv[1]; + char *uri = argv[2]; + + bool success; + GError *error = NULL; + if (uri_has_scheme(uri)) { + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return COMMAND_RETURN_ERROR; + } + + success = spl_append_uri(argv[1], playlist, &error); + } else + success = search_add_to_playlist(uri, playlist, nullptr, + &error); + + if (!success && error == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "directory or file not found"); + return COMMAND_RETURN_ERROR; + } + + return success ? COMMAND_RETURN_OK : print_error(client, error); +} + +enum command_return +handle_listplaylists(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + GError *error = NULL; + const auto list = ListPlaylistFiles(&error); + if (list.empty() && error != NULL) + return print_error(client, error); + + print_spl_list(client, list); + return COMMAND_RETURN_OK; +} diff --git a/src/PlaylistCommands.hxx b/src/PlaylistCommands.hxx new file mode 100644 index 000000000..067f428b6 --- /dev/null +++ b/src/PlaylistCommands.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_COMMANDS_HXX +#define MPD_PLAYLIST_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_save(Client *client, int argc, char *argv[]); + +enum command_return +handle_load(Client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylist(Client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylistinfo(Client *client, int argc, char *argv[]); + +enum command_return +handle_rm(Client *client, int argc, char *argv[]); + +enum command_return +handle_rename(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistdelete(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistmove(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistclear(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistadd(Client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylists(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/PlaylistControl.cxx b/src/PlaylistControl.cxx new file mode 100644 index 000000000..323192242 --- /dev/null +++ b/src/PlaylistControl.cxx @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Functions for controlling playback on the playlist level. + * + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlayerControl.hxx" +#include "song.h" + +#include <glib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "playlist" + +void +playlist::Stop(player_control &pc) +{ + if (!playing) + return; + + assert(current >= 0); + + g_debug("stop"); + pc_stop(&pc); + queued = -1; + playing = false; + + if (queue.random) { + /* shuffle the playlist, so the next playback will + result in a new random order */ + + unsigned current_position = queue.OrderToPosition(current); + + queue.ShuffleOrder(); + + /* make sure that "current" stays valid, and the next + "play" command plays the same song again */ + current = queue.PositionToOrder(current_position); + } +} + +enum playlist_result +playlist::PlayPosition(player_control &pc, int song) +{ + pc_clear_error(&pc); + + unsigned i = song; + if (song == -1) { + /* play any song ("current" song, or the first song */ + + if (queue.IsEmpty()) + return PLAYLIST_RESULT_SUCCESS; + + if (playing) { + /* already playing: unpause playback, just in + case it was paused, and return */ + pc_set_pause(&pc, false); + return PLAYLIST_RESULT_SUCCESS; + } + + /* select a song: "current" song, or the first one */ + i = current >= 0 + ? current + : 0; + } else if (!queue.IsValidPosition(song)) + return PLAYLIST_RESULT_BAD_RANGE; + + if (queue.random) { + if (song >= 0) + /* "i" is currently the song position (which + would be equal to the order number in + no-random mode); convert it to a order + number, because random mode is enabled */ + i = queue.PositionToOrder(song); + + if (!playing) + current = 0; + + /* swap the new song with the previous "current" one, + so playback continues as planned */ + queue.SwapOrders(i, current); + i = current; + } + + stop_on_error = false; + error_count = 0; + + PlayOrder(pc, i); + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::PlayId(player_control &pc, int id) +{ + if (id == -1) + return PlayPosition(pc, id); + + int song = queue.IdToPosition(id); + if (song < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return PlayPosition(pc, song); +} + +void +playlist::PlayNext(player_control &pc) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + assert(queue.IsValidOrder(current)); + + const int old_current = current; + stop_on_error = false; + + /* determine the next song from the queue's order list */ + + const int next_order = queue.GetNextOrder(current); + if (next_order < 0) { + /* no song after this one: stop playback */ + Stop(pc); + + /* reset "current song" */ + current = -1; + } + else + { + if (next_order == 0 && queue.random) { + /* The queue told us that the next song is the first + song. This means we are in repeat mode. Shuffle + the queue order, so this time, the user hears the + songs in a different than before */ + assert(queue.repeat); + + queue.ShuffleOrder(); + + /* note that current and queued are + now invalid, but playlist_play_order() will + discard them anyway */ + } + + PlayOrder(pc, next_order); + } + + /* Consume mode removes each played songs. */ + if (queue.consume) + DeleteOrder(pc, old_current); +} + +void +playlist::PlayPrevious(player_control &pc) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + + int order; + if (current > 0) { + /* play the preceding song */ + order = current - 1; + } else if (queue.repeat) { + /* play the last song in "repeat" mode */ + order = queue.GetLength() - 1; + } else { + /* re-start playing the current song if it's + the first one */ + order = current; + } + + PlayOrder(pc, order); +} + +enum playlist_result +playlist::SeekSongPosition(player_control &pc, unsigned song, float seek_time) +{ + if (!queue.IsValidPosition(song)) + return PLAYLIST_RESULT_BAD_RANGE; + + const struct song *queued_song = GetQueuedSong(); + + unsigned i = queue.random + ? queue.PositionToOrder(song) + : song; + + pc_clear_error(&pc); + stop_on_error = true; + error_count = 0; + + if (!playing || (unsigned)current != i) { + /* seeking is not within the current song - prepare + song change */ + + playing = true; + current = i; + + queued_song = nullptr; + } + + struct song *the_song = song_dup_detached(queue.GetOrder(i)); + if (!pc_seek(&pc, the_song, seek_time)) { + UpdateQueuedSong(pc, queued_song); + + return PLAYLIST_RESULT_NOT_PLAYING; + } + + queued = -1; + UpdateQueuedSong(pc, NULL); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::SeekSongId(player_control &pc, unsigned id, float seek_time) +{ + int song = queue.IdToPosition(id); + if (song < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return SeekSongPosition(pc, song, seek_time); +} + +enum playlist_result +playlist::SeekCurrent(player_control &pc, float seek_time, bool relative) +{ + if (!playing) + return PLAYLIST_RESULT_NOT_PLAYING; + + if (relative) { + struct player_status status; + pc_get_status(&pc, &status); + + if (status.state != PLAYER_STATE_PLAY && + status.state != PLAYER_STATE_PAUSE) + return PLAYLIST_RESULT_NOT_PLAYING; + + seek_time += (int)status.elapsed_time; + } + + if (seek_time < 0) + seek_time = 0; + + return SeekSongPosition(pc, current, seek_time); +} 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/PlaylistEdit.cxx b/src/PlaylistEdit.cxx new file mode 100644 index 000000000..cdfb77150 --- /dev/null +++ b/src/PlaylistEdit.cxx @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Functions for editing the playlist (adding, removing, reordering + * songs in the queue). + * + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlayerControl.hxx" + +extern "C" { +#include "uri.h" +#include "song.h" +} + +#include "Idle.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +#include <stdlib.h> + +void +playlist::OnModified() +{ + queue.IncrementVersion(); + + idle_add(IDLE_PLAYLIST); +} + +void +playlist::Clear(player_control &pc) +{ + Stop(pc); + + queue.Clear(); + current = -1; + + OnModified(); +} + +enum playlist_result +playlist::AppendFile(struct player_control &pc, + const char *path_fs, unsigned *added_id) +{ + struct song *song = song_file_load(path_fs, NULL); + if (song == NULL) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return AppendSong(pc, song, added_id); +} + +enum playlist_result +playlist::AppendSong(struct player_control &pc, + struct song *song, unsigned *added_id) +{ + unsigned id; + + if (queue.IsFull()) + return PLAYLIST_RESULT_TOO_LARGE; + + const struct song *const queued_song = GetQueuedSong(); + + id = queue.Append(song, 0); + + if (queue.random) { + /* shuffle the new song into the list of remaining + songs to play */ + + unsigned start; + if (queued >= 0) + start = queued + 1; + else + start = current + 1; + if (start < queue.GetLength()) + queue.ShuffleOrderLast(start, queue.GetLength()); + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + if (added_id) + *added_id = id; + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::AppendURI(struct player_control &pc, + const char *uri, unsigned *added_id) +{ + g_debug("add to playlist: %s", uri); + + const Database *db = nullptr; + struct song *song; + if (uri_has_scheme(uri)) { + song = song_remote_new(uri); + } else { + db = GetDatabase(nullptr); + if (db == nullptr) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + song = db->GetSong(uri, nullptr); + if (song == nullptr) + return PLAYLIST_RESULT_NO_SUCH_SONG; + } + + enum playlist_result result = AppendSong(pc, song, added_id); + if (db != nullptr) + db->ReturnSong(song); + + return result; +} + +enum playlist_result +playlist::SwapPositions(player_control &pc, unsigned song1, unsigned song2) +{ + if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2)) + return PLAYLIST_RESULT_BAD_RANGE; + + const struct song *const queued_song = GetQueuedSong(); + + queue.SwapPositions(song1, song2); + + if (queue.random) { + /* update the queue order, so that current + still points to the current song order */ + + queue.SwapOrders(queue.PositionToOrder(song1), + queue.PositionToOrder(song2)); + } else { + /* correct the "current" song order */ + + if (current == (int)song1) + current = song2; + else if (current == (int)song2) + current = song1; + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::SwapIds(player_control &pc, unsigned id1, unsigned id2) +{ + int song1 = queue.IdToPosition(id1); + int song2 = queue.IdToPosition(id2); + + if (song1 < 0 || song2 < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return SwapPositions(pc, song1, song2); +} + +enum playlist_result +playlist::SetPriorityRange(player_control &pc, + unsigned start, unsigned end, + uint8_t priority) +{ + if (start >= GetLength()) + return PLAYLIST_RESULT_BAD_RANGE; + + if (end > GetLength()) + end = GetLength(); + + if (start >= end) + return PLAYLIST_RESULT_SUCCESS; + + /* remember "current" and "queued" */ + + const int current_position = GetCurrentPosition(); + const struct song *const queued_song = GetQueuedSong(); + + /* apply the priority changes */ + + queue.SetPriorityRange(start, end, priority, current); + + /* restore "current" and choose a new "queued" */ + + if (current_position >= 0) + current = queue.PositionToOrder(current_position); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::SetPriorityId(struct player_control &pc, + unsigned song_id, uint8_t priority) +{ + int song_position = queue.IdToPosition(song_id); + if (song_position < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return SetPriorityRange(pc, song_position, song_position + 1, + priority); + +} + +void +playlist::DeleteInternal(player_control &pc, + unsigned song, const struct song **queued_p) +{ + assert(song < GetLength()); + + unsigned songOrder = queue.PositionToOrder(song); + + if (playing && current == (int)songOrder) { + bool paused = pc_get_state(&pc) == PLAYER_STATE_PAUSE; + + /* the current song is going to be deleted: stop the player */ + + pc_stop(&pc); + playing = false; + + /* see which song is going to be played instead */ + + current = queue.GetNextOrder(current); + if (current == (int)songOrder) + current = -1; + + if (current >= 0 && !paused) + /* play the song after the deleted one */ + PlayOrder(pc, current); + else + /* no songs left to play, stop playback + completely */ + Stop(pc); + + *queued_p = NULL; + } else if (current == (int)songOrder) + /* there's a "current song" but we're not playing + currently - clear "current" */ + current = -1; + + /* now do it: remove the song */ + + queue.DeletePosition(song); + + /* update the "current" and "queued" variables */ + + if (current > (int)songOrder) + current--; +} + +enum playlist_result +playlist::DeletePosition(struct player_control &pc, unsigned song) +{ + if (song >= queue.GetLength()) + return PLAYLIST_RESULT_BAD_RANGE; + + const struct song *queued_song = GetQueuedSong(); + + DeleteInternal(pc, song, &queued_song); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::DeleteRange(struct player_control &pc, unsigned start, unsigned end) +{ + if (start >= queue.GetLength()) + return PLAYLIST_RESULT_BAD_RANGE; + + if (end > queue.GetLength()) + end = queue.GetLength(); + + if (start >= end) + return PLAYLIST_RESULT_SUCCESS; + + const struct song *queued_song = GetQueuedSong(); + + do { + DeleteInternal(pc, --end, &queued_song); + } while (end != start); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::DeleteId(struct player_control &pc, unsigned id) +{ + int song = queue.IdToPosition(id); + if (song < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return DeletePosition(pc, song); +} + +void +playlist::DeleteSong(struct player_control &pc, const struct song &song) +{ + for (int i = queue.GetLength() - 1; i >= 0; --i) + // TODO: compare URI instead of pointer + if (&song == queue.Get(i)) + DeletePosition(pc, i); +} + +enum playlist_result +playlist::MoveRange(player_control &pc, unsigned start, unsigned end, int to) +{ + if (!queue.IsValidPosition(start) || !queue.IsValidPosition(end - 1)) + return PLAYLIST_RESULT_BAD_RANGE; + + if ((to >= 0 && to + end - start - 1 >= GetLength()) || + (to < 0 && unsigned(abs(to)) > GetLength())) + return PLAYLIST_RESULT_BAD_RANGE; + + if ((int)start == to) + /* nothing happens */ + return PLAYLIST_RESULT_SUCCESS; + + const struct song *const queued_song = GetQueuedSong(); + + /* + * (to < 0) => move to offset from current song + * (-playlist.length == to) => move to position BEFORE current song + */ + const int currentSong = GetCurrentPosition(); + if (to < 0 && currentSong >= 0) { + if (start <= (unsigned)currentSong && (unsigned)currentSong < end) + /* no-op, can't be moved to offset of itself */ + return PLAYLIST_RESULT_SUCCESS; + to = (currentSong + abs(to)) % GetLength(); + if (start < (unsigned)to) + to--; + } + + queue.MoveRange(start, end, to); + + if (!queue.random) { + /* update current/queued */ + if ((int)start <= current && (unsigned)current < end) + current += to - start; + else if (current >= (int)end && current <= to) + current -= end - start; + else if (current >= to && current < (int)start) + current += end - start; + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::MoveId(player_control &pc, unsigned id1, int to) +{ + int song = queue.IdToPosition(id1); + if (song < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return MoveRange(pc, song, song + 1, to); +} + +void +playlist::Shuffle(player_control &pc, unsigned start, unsigned end) +{ + if (end > GetLength()) + /* correct the "end" offset */ + end = GetLength(); + + if (start + 1 >= end) + /* needs at least two entries. */ + return; + + const struct song *const queued_song = GetQueuedSong(); + if (playing && current >= 0) { + unsigned current_position = queue.OrderToPosition(current); + + if (current_position >= start && current_position < end) { + /* put current playing song first */ + queue.SwapPositions(start, current_position); + + if (queue.random) { + current = queue.PositionToOrder(start); + } else + current = start; + + /* start shuffle after the current song */ + start++; + } + } else { + /* no playback currently: reset current */ + + current = -1; + } + + queue.ShuffleRange(start, end); + + UpdateQueuedSong(pc, queued_song); + OnModified(); +} diff --git a/src/stored_playlist.c b/src/PlaylistFile.cxx index 39ba2bac1..d79bea9c5 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,24 @@ */ #include "config.h" -#include "stored_playlist.h" -#include "playlist_save.h" -#include "text_file.h" +#include "PlaylistFile.hxx" +#include "PlaylistSave.hxx" +#include "PlaylistInfo.hxx" +#include "PlaylistVector.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" #include "song.h" -#include "mapper.h" +#include "io_error.h" +#include "Mapper.hxx" +#include "TextFile.hxx" +#include "conf.h" +#include "Idle.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 +136,74 @@ playlist_errno(GError **error_r) break; default: - g_set_error_literal(error_r, g_file_error_quark(), errno, - g_strerror(errno)); + set_error_errno(error_r); break; } } -static struct stored_playlist_info * -load_playlist_info(const char *parent_path_fs, const char *name_fs) +static bool +LoadPlaylistFileInfo(PlaylistInfo &info, + const char *parent_path_fs, const char *name_fs) { size_t name_length = strlen(name_fs); - char *path_fs, *name, *name_utf8; - int ret; - struct stat st; - struct stored_playlist_info *playlist; if (name_length < sizeof(PLAYLIST_FILE_SUFFIX) || memchr(name_fs, '\n', name_length) != NULL) - return NULL; + return false; if (!g_str_has_suffix(name_fs, PLAYLIST_FILE_SUFFIX)) - return NULL; + return false; - path_fs = g_build_filename(parent_path_fs, name_fs, NULL); - ret = stat(path_fs, &st); + char *path_fs = g_build_filename(parent_path_fs, name_fs, NULL); + struct stat st; + int ret = stat(path_fs, &st); g_free(path_fs); if (ret < 0 || !S_ISREG(st.st_mode)) - return NULL; + return false; - name = g_strndup(name_fs, - name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX)); - name_utf8 = fs_charset_to_utf8(name); + char *name = g_strndup(name_fs, + name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX)); + 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; + g_free(name_utf8); + info.mtime = st.st_mtime; + return true; } -GPtrArray * -spl_list(GError **error_r) +PlaylistVector +ListPlaylistFiles(GError **error_r) { - const char *parent_path_fs = spl_map(error_r); - DIR *dir; - struct dirent *ent; - GPtrArray *list; - struct stored_playlist_info *playlist; + PlaylistVector list; + const char *parent_path_fs = spl_map(error_r); if (parent_path_fs == NULL) - return NULL; + return list; - dir = opendir(parent_path_fs); + DIR *dir = opendir(parent_path_fs); if (dir == NULL) { - g_set_error_literal(error_r, g_file_error_quark(), errno, - g_strerror(errno)); - return NULL; + set_error_errno(error_r); + return list; } - list = g_ptr_array_new(); - + PlaylistInfo info; + struct dirent *ent; while ((ent = readdir(dir)) != NULL) { - playlist = load_playlist_info(parent_path_fs, ent->d_name); - if (playlist != NULL) - g_ptr_array_add(list, playlist); + if (LoadPlaylistFileInfo(info, parent_path_fs, ent->d_name)) + list.push_back(std::move(info)); } closedir(dir); return list; } -void -spl_list_free(GPtrArray *list) -{ - for (unsigned i = 0; i < list->len; ++i) { - struct stored_playlist_info *playlist = - g_ptr_array_index(list, i); - g_free(playlist->name); - g_free(playlist); - } - - g_ptr_array_free(list, true); -} - static bool -spl_save(GPtrArray *list, const char *utf8path, GError **error_r) +SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path, + GError **error_r) { - FILE *file; - assert(utf8path != NULL); if (spl_map(error_r) == NULL) @@ -227,48 +213,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 +261,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 +333,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 +351,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 +377,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 +392,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 +418,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..3f63253ad 100644 --- a/src/stored_playlist.h +++ b/src/PlaylistFile.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,20 +17,20 @@ * 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 <vector> +#include <string> #include <glib.h> -#include <stdbool.h> #include <time.h> struct song; +struct PlaylistInfo; +class PlaylistVector; -struct stored_playlist_info { - char *name; - - time_t mtime; -}; +typedef std::vector<std::string> PlaylistFileContents; extern bool playlist_saveAbsolutePaths; @@ -40,6 +40,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 +53,11 @@ spl_valid_name(const char *name_utf8); * Returns a list of stored_playlist_info struct pointers. Returns * NULL if an error occurred. */ -GPtrArray * -spl_list(GError **error_r); - -void -spl_list_free(GPtrArray *list); +PlaylistVector +ListPlaylistFiles(GError **error_r); -GPtrArray * -spl_load(const char *utf8path, GError **error_r); - -void -spl_free(GPtrArray *list); +PlaylistFileContents +LoadPlaylistFile(const char *utf8path, GError **error_r); bool spl_move_index(const char *utf8path, unsigned src, unsigned dest, @@ -86,3 +82,5 @@ bool spl_rename(const char *utf8from, const char *utf8to, GError **error_r); #endif + +#endif diff --git a/src/playlist_global.c b/src/PlaylistGlobal.cxx index 650b88bb8..9dd971d3f 100644 --- a/src/playlist_global.c +++ b/src/PlaylistGlobal.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,36 +23,27 @@ */ #include "config.h" -#include "playlist.h" -#include "playlist_state.h" -#include "event_pipe.h" -#include "main.h" - -struct playlist g_playlist; +#include "PlaylistGlobal.hxx" +#include "Playlist.hxx" +#include "Main.hxx" +#include "Partition.hxx" +#include "GlobalEvents.hxx" static void playlist_tag_event(void) { - playlist_tag_changed(&g_playlist); + global_partition->playlist.TagChanged(); } static void playlist_event(void) { - playlist_sync(&g_playlist, global_player_control); -} - -void -playlist_global_init(void) -{ - playlist_init(&g_playlist); - - event_pipe_register(PIPE_EVENT_TAG, playlist_tag_event); - event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event); + global_partition->playlist.SyncWithPlayer(global_partition->pc); } void -playlist_global_finish(void) +playlist_global_init() { - playlist_finish(&g_playlist); + GlobalEvents::Register(GlobalEvents::TAG, playlist_tag_event); + GlobalEvents::Register(GlobalEvents::PLAYLIST, playlist_event); } diff --git a/src/PlaylistGlobal.hxx b/src/PlaylistGlobal.hxx new file mode 100644 index 000000000..4397292db --- /dev/null +++ b/src/PlaylistGlobal.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_GLOBAL_HXX +#define MPD_PLAYLIST_GLOBAL_HXX + +void +playlist_global_init(); + +#endif diff --git a/src/PlaylistInfo.hxx b/src/PlaylistInfo.hxx new file mode 100644 index 000000000..96e4f6db9 --- /dev/null +++ b/src/PlaylistInfo.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_INFO_HXX +#define MPD_PLAYLIST_INFO_HXX + +#include "check.h" +#include "gcc.h" + +#include <string> + +#include <sys/time.h> + +/** + * A directory entry pointing to a playlist file. + */ +struct PlaylistInfo { + /** + * The UTF-8 encoded name of the playlist file. + */ + std::string name; + + time_t mtime; + + class CompareName { + const char *const name; + + public: + constexpr CompareName(const char *_name):name(_name) {} + + gcc_pure + bool operator()(const PlaylistInfo &pi) const { + return pi.name.compare(name) == 0; + } + }; + + PlaylistInfo() = default; + + template<typename N> + PlaylistInfo(N &&_name, time_t _mtime) + :name(std::forward<N>(_name)), mtime(_mtime) {} + + PlaylistInfo(const PlaylistInfo &other) = delete; + PlaylistInfo(PlaylistInfo &&) = default; +}; + +#endif diff --git a/src/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..e6ece7e6b 100644 --- a/src/playlist_print.c +++ b/src/PlaylistPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,37 +18,41 @@ */ #include "config.h" -#include "playlist_print.h" +#include "PlaylistPrint.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistAny.hxx" +#include "PlaylistSong.hxx" +#include "Playlist.hxx" +#include "QueuePrint.hxx" +#include "SongPrint.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "Client.hxx" +#include "input_stream.h" + +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; - queue_print_uris(client, queue, 0, queue_length(queue)); + queue_print_uris(client, queue, 0, queue->GetLength()); } bool -playlist_print_info(struct client *client, const struct playlist *playlist, +playlist_print_info(Client *client, const struct playlist *playlist, unsigned start, unsigned end) { const struct queue *queue = &playlist->queue; - if (end > queue_length(queue)) + if (end > queue->GetLength()) /* correct the "end" offset */ - end = queue_length(queue); + end = queue->GetLength(); if (start > end) /* an invalid "start" offset is fatal */ @@ -59,13 +63,13 @@ playlist_print_info(struct client *client, const struct playlist *playlist, } bool -playlist_print_id(struct client *client, const struct playlist *playlist, +playlist_print_id(Client *client, const struct playlist *playlist, unsigned id) { const struct queue *queue = &playlist->queue; int position; - position = queue_id_to_position(queue, id); + position = queue->IdToPosition(id); if (position < 0) /* no such song */ return false; @@ -74,10 +78,9 @@ playlist_print_id(struct client *client, const struct playlist *playlist, } bool -playlist_print_current(struct client *client, const struct playlist *playlist) +playlist_print_current(Client *client, const struct playlist *playlist) { - int current_position = playlist_get_current_song(playlist); - + int current_position = playlist->GetCurrentPosition(); if (current_position < 0) return false; @@ -87,21 +90,14 @@ playlist_print_current(struct client *client, const struct playlist *playlist) } void -playlist_print_find(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list) +playlist_print_find(Client *client, const struct playlist *playlist, + const SongFilter &filter) { - queue_find(client, &playlist->queue, list); + queue_find(client, &playlist->queue, filter); } void -playlist_print_search(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list) -{ - queue_search(client, &playlist->queue, list); -} - -void -playlist_print_changes_info(struct client *client, +playlist_print_changes_info(Client *client, const struct playlist *playlist, uint32_t version) { @@ -109,46 +105,51 @@ playlist_print_changes_info(struct client *client, } void -playlist_print_changes_position(struct client *client, +playlist_print_changes_position(Client *client, const struct playlist *playlist, uint32_t version) { queue_print_changes_position(client, &playlist->queue, version); } +static bool +PrintSongDetails(Client *client, const char *uri_utf8) +{ + const Database *db = GetDatabase(nullptr); + if (db == nullptr) + return false; + + song *song = db->GetSong(uri_utf8, nullptr); + if (song == nullptr) + return false; + + song_print_info(client, song); + db->ReturnSong(song); + return true; +} + bool -spl_print(struct client *client, const char *name_utf8, bool detail, +spl_print(Client *client, const char *name_utf8, bool detail, GError **error_r) { - GPtrArray *list; - - list = spl_load(name_utf8, error_r); - if (list == NULL) + GError *error = NULL; + PlaylistFileContents contents = LoadPlaylistFile(name_utf8, &error); + if (contents.empty() && error != nullptr) { + g_propagate_error(error_r, error); return false; + } - for (unsigned i = 0; i < list->len; ++i) { - const char *temp = g_ptr_array_index(list, i); - bool wrote = false; - - if (detail) { - struct song *song = db_get_song(temp); - if (song) { - song_print_info(client, song); - wrote = true; - } - } - - if (!wrote) { - client_printf(client, SONG_FILE "%s\n", temp); - } + for (const auto &uri_utf8 : contents) { + if (!detail || !PrintSongDetails(client, uri_utf8.c_str())) + client_printf(client, SONG_FILE "%s\n", + uri_utf8.c_str()); } - spl_free(list); return true; } static void -playlist_provider_print(struct client *client, const char *uri, +playlist_provider_print(Client *client, const char *uri, struct playlist_provider *playlist, bool detail) { struct song *song; @@ -164,15 +165,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..1335fa401 100644 --- a/src/playlist_queue.c +++ b/src/PlaylistQueue.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,17 @@ */ #include "config.h" -#include "playlist_queue.h" +#include "PlaylistQueue.hxx" #include "playlist_plugin.h" -#include "playlist_any.h" -#include "playlist_song.h" -#include "playlist.h" -#include "song.h" +#include "PlaylistAny.hxx" +#include "PlaylistSong.hxx" +#include "Playlist.hxx" #include "input_stream.h" +extern "C" { +#include "song.h" +} + enum playlist_result playlist_load_into_queue(const char *uri, struct playlist_provider *source, unsigned start_index, unsigned end_index, @@ -41,8 +44,7 @@ playlist_load_into_queue(const char *uri, struct playlist_provider *source, ++i) { if (i < start_index) { /* skip songs before the start index */ - if (!song_in_database(song)) - song_free(song); + song_free(song); continue; } @@ -50,10 +52,9 @@ playlist_load_into_queue(const char *uri, struct playlist_provider *source, if (song == NULL) continue; - result = playlist_append_song(dest, pc, song, NULL); + result = dest->AppendSong(*pc, song); + song_free(song); if (result != PLAYLIST_RESULT_SUCCESS) { - if (!song_in_database(song)) - song_free(song); g_free(base_uri); return result; } 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..745744490 100644 --- a/src/playlist_save.c +++ b/src/PlaylistSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,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 "Playlist.hxx" #include "song.h" -#include "mapper.h" +#include "Mapper.hxx" +#include "Idle.hxx" + +extern "C" { #include "path.h" #include "uri.h" -#include "database.h" -#include "idle.h" +} + #include "glib_compat.h" #include <glib.h> @@ -96,8 +98,8 @@ spl_save_queue(const char *name_utf8, const struct queue *queue) if (file == NULL) return PLAYLIST_RESULT_ERRNO; - for (unsigned i = 0; i < queue_length(queue); i++) - playlist_print_song(file, queue_get(queue, i)); + for (unsigned i = 0; i < queue->GetLength(); i++) + playlist_print_song(file, queue->Get(i)); fclose(file); @@ -117,34 +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->AppendURI(*pc, uri_utf8.c_str())) != PLAYLIST_RESULT_SUCCESS) { /* for windows compatibility, convert slashes */ - char *temp2 = g_strdup(temp); + char *temp2 = g_strdup(uri_utf8.c_str()); char *p = temp2; while (*p) { if (*p == '\\') *p = '/'; p++; } - if ((playlist_append_uri(playlist, pc, temp2, - NULL)) != PLAYLIST_RESULT_SUCCESS) { + + if (playlist->AppendURI(*pc, temp2) != PLAYLIST_RESULT_SUCCESS) g_warning("can't add file \"%s\"", temp2); - } + g_free(temp2); } } - spl_free(list); return true; } diff --git a/src/playlist_save.h b/src/PlaylistSave.hxx index 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..118fb5715 100644 --- a/src/playlist_song.c +++ b/src/PlaylistSong.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,20 @@ */ #include "config.h" -#include "playlist_song.h" -#include "database.h" -#include "mapper.h" +#include "PlaylistSong.hxx" +#include "Mapper.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" +#include "ls.hxx" +#include "tag.h" + +extern "C" { #include "song.h" #include "uri.h" #include "path.h" -#include "ls.h" -#include "tag.h" +} + +#include <glib.h> #include <assert.h> #include <string.h> @@ -86,9 +92,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 +108,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..14c6d231e 100644 --- a/src/playlist_state.c +++ b/src/PlaylistState.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,12 +23,11 @@ */ #include "config.h" -#include "playlist_state.h" -#include "playlist.h" -#include "player_control.h" -#include "queue_save.h" -#include "path.h" -#include "text_file.h" +#include "PlaylistState.hxx" +#include "Playlist.hxx" +#include "QueueSave.hxx" +#include "TextFile.hxx" +#include "PlayerControl.hxx" #include "conf.h" #include <string.h> @@ -72,8 +71,7 @@ playlist_state_save(FILE *fp, const struct playlist *playlist, fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp); } fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", - queue_order_to_position(&playlist->queue, - playlist->current)); + playlist->queue.OrderToPosition(playlist->current)); fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n", (int)player_status.elapsed_time); } else { @@ -81,8 +79,7 @@ playlist_state_save(FILE *fp, const struct playlist *playlist, if (playlist->current >= 0) fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", - queue_order_to_position(&playlist->queue, - playlist->current)); + playlist->queue.OrderToPosition(playlist->current)); } fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist->queue.random); @@ -102,18 +99,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"); @@ -121,11 +118,11 @@ playlist_state_load(FILE *fp, GString *buffer, struct playlist *playlist) } } - queue_increment_version(&playlist->queue); + playlist->queue.IncrementVersion(); } bool -playlist_state_restore(const char *line, FILE *fp, GString *buffer, +playlist_state_restore(const char *line, TextFile &file, struct playlist *playlist, struct player_control *pc) { int current = -1; @@ -143,31 +140,21 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0) state = PLAYER_STATE_PAUSE; - while ((line = read_text_line(fp, buffer)) != NULL) { + while ((line = file.ReadLine()) != NULL) { if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_TIME)) { seek_time = atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_REPEAT)) { - if (strcmp - (&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), - "1") == 0) { - playlist_set_repeat(playlist, pc, true); - } else - playlist_set_repeat(playlist, pc, false); + playlist->SetRepeat(*pc, + strcmp(&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), + "1") == 0); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) { - if (strcmp - (&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), - "1") == 0) { - playlist_set_single(playlist, pc, true); - } else - playlist_set_single(playlist, pc, false); + playlist->SetSingle(*pc, + strcmp(&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), + "1") == 0); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) { - if (strcmp - (&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), - "1") == 0) { - playlist_set_consume(playlist, true); - } else - playlist_set_consume(playlist, false); + playlist->SetConsume(strcmp(&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), + "1") == 0); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CROSSFADE)) { pc_set_cross_fade(pc, atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); @@ -187,14 +174,14 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, (PLAYLIST_STATE_FILE_CURRENT)])); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { - playlist_state_load(fp, buffer, playlist); + playlist_state_load(file, playlist); } } - playlist_set_random(playlist, pc, random_mode); + playlist->SetRandom(*pc, random_mode); - if (!queue_is_empty(&playlist->queue)) { - if (!queue_valid_position(&playlist->queue, current)) + if (!playlist->queue.IsEmpty()) { + if (!playlist->queue.IsValidPosition(current)) current = 0; if (state == PLAYER_STATE_PLAY && @@ -213,9 +200,9 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, if (state == PLAYER_STATE_STOP /* && config_option */) playlist->current = current; else if (seek_time == 0) - playlist_play(playlist, pc, current); + playlist->PlayPosition(*pc, current); else - playlist_seek_song(playlist, pc, current, seek_time); + playlist->SeekSongPosition(*pc, current, seek_time); if (state == PLAYER_STATE_PAUSE) pc_pause(pc); @@ -237,8 +224,7 @@ playlist_state_get_hash(const struct playlist *playlist, ? ((int)player_status.elapsed_time << 8) : 0) ^ (playlist->current >= 0 - ? (queue_order_to_position(&playlist->queue, - playlist->current) << 16) + ? (playlist->queue.OrderToPosition(playlist->current) << 16) : 0) ^ ((int)pc_get_cross_fade(pc) << 20) ^ (player_status.state << 24) ^ 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..d10c90fda --- /dev/null +++ b/src/PlaylistVector.hxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_VECTOR_HXX +#define MPD_PLAYLIST_VECTOR_HXX + +#include "PlaylistInfo.hxx" +#include "gcc.h" + +#include <list> + +class PlaylistVector : protected std::list<PlaylistInfo> { +protected: + /** + * Caller must lock the #db_mutex. + */ + gcc_pure + iterator find(const char *name); + +public: + using std::list<PlaylistInfo>::empty; + using std::list<PlaylistInfo>::begin; + using std::list<PlaylistInfo>::end; + using std::list<PlaylistInfo>::push_back; + using std::list<PlaylistInfo>::erase; + + /** + * Caller must lock the #db_mutex. + * + * @return true if the vector or one of its items was modified + */ + bool UpdateOrInsert(PlaylistInfo &&pi); + + /** + * Caller must lock the #db_mutex. + */ + bool erase(const char *name); +}; + +#endif /* SONGVEC_H */ diff --git a/src/Queue.cxx b/src/Queue.cxx new file mode 100644 index 000000000..3fdb9ed1e --- /dev/null +++ b/src/Queue.cxx @@ -0,0 +1,499 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Queue.hxx" +#include "song.h" + +#include <stdlib.h> + +queue::queue(unsigned _max_length) + :max_length(_max_length), length(0), + version(1), + items(new Item[max_length]), + order(new unsigned[max_length]), + id_table(max_length * HASH_MULT), + repeat(false), + single(false), + consume(false), + random(false) +{ +} + +queue::~queue() +{ + Clear(); + + delete[] items; + delete[] order; +} + +int +queue::GetNextOrder(unsigned _order) const +{ + assert(_order < length); + + if (single && repeat && !consume) + return _order; + else if (_order + 1 < length) + return _order + 1; + else if (repeat && (_order > 0 || !consume)) + /* restart at first song */ + return 0; + else + /* end of queue */ + return -1; +} + +void +queue::IncrementVersion() +{ + static unsigned long max = ((uint32_t) 1 << 31) - 1; + + version++; + + if (version >= max) { + for (unsigned i = 0; i < length; i++) + items[i].version = 0; + + version = 1; + } +} + +void +queue::ModifyAtOrder(unsigned _order) +{ + assert(_order < length); + + unsigned position = order[_order]; + items[position].version = version; + + IncrementVersion(); +} + +void +queue::ModifyAll() +{ + for (unsigned i = 0; i < length; i++) + items[i].version = version; + + IncrementVersion(); +} + +unsigned +queue::Append(struct song *song, uint8_t priority) +{ + assert(!IsFull()); + + const unsigned position = length++; + const unsigned id = id_table.Insert(position); + + auto &item = items[position]; + item.song = song_dup_detached(song); + item.id = id; + item.version = version; + item.priority = priority; + + order[position] = position; + + return id; +} + +void +queue::SwapPositions(unsigned position1, unsigned position2) +{ + unsigned id1 = items[position1].id; + unsigned id2 = items[position2].id; + + std::swap(items[position1], items[position2]); + + items[position1].version = version; + items[position2].version = version; + + id_table.Move(id1, position2); + id_table.Move(id2, position1); +} + +void +queue::MovePostion(unsigned from, unsigned to) +{ + const Item tmp = items[from]; + + /* move songs to one less in from->to */ + + for (unsigned i = from; i < to; i++) + MoveItemTo(i + 1, i); + + /* move songs to one more in to->from */ + + for (unsigned i = from; i > to; i--) + MoveItemTo(i - 1, i); + + /* put song at _to_ */ + + id_table.Move(tmp.id, to); + items[to] = tmp; + items[to].version = version; + + /* now deal with order */ + + if (random) { + for (unsigned i = 0; i < length; i++) { + if (order[i] > from && order[i] <= to) + order[i]--; + else if (order[i] < from && + order[i] >= to) + order[i]++; + else if (from == order[i]) + order[i] = to; + } + } +} + +void +queue::MoveRange(unsigned start, unsigned end, unsigned to) +{ + Item tmp[end - start]; + // Copy the original block [start,end-1] + for (unsigned i = start; i < end; i++) + tmp[i - start] = items[i]; + + // If to > start, we need to move to-start items to start, starting from end + for (unsigned i = end; i < end + to - start; i++) + MoveItemTo(i, start + i - end); + + // If to < start, we need to move start-to items to newend (= end + to - start), starting from to + // This is the same as moving items from start-1 to to (decreasing), with start-1 going to end-1 + // We have to iterate in this order to avoid writing over something we haven't yet moved + for (int i = start - 1; i >= int(to); i--) + MoveItemTo(i, i + end - start); + + // Copy the original block back in, starting at to. + for (unsigned i = start; i< end; i++) + { + id_table.Move(tmp[i - start].id, to + i - start); + items[to + i - start] = tmp[i-start]; + items[to + i - start].version = version; + } + + if (random) { + // Update the positions in the queue. + // Note that the ranges for these cases are the same as the ranges of + // the loops above. + for (unsigned i = 0; i < length; i++) { + if (order[i] >= end && order[i] < to + end - start) + order[i] -= end - start; + else if (order[i] < start && + order[i] >= to) + order[i] += end - start; + else if (start <= order[i] && order[i] < end) + order[i] += to - start; + } + } +} + +void +queue::MoveOrder(unsigned from_order, unsigned to_order) +{ + assert(from_order < length); + assert(to_order <= length); + + const unsigned from_position = OrderToPosition(from_order); + + if (from_order < to_order) { + for (unsigned i = from_order; i < to_order; ++i) + order[i] = order[i + 1]; + } else { + for (unsigned i = from_order; i > to_order; --i) + order[i] = order[i - 1]; + } + + order[to_order] = from_position; +} + +void +queue::DeletePosition(unsigned position) +{ + assert(position < length); + + struct song *song = Get(position); + assert(!song_in_database(song) || song_is_detached(song)); + song_free(song); + + const unsigned id = PositionToId(position); + const unsigned _order = PositionToOrder(position); + + --length; + + /* release the song id */ + + id_table.Erase(id); + + /* delete song from songs array */ + + for (unsigned i = position; i < length; i++) + MoveItemTo(i + 1, i); + + /* delete the entry from the order array */ + + for (unsigned i = _order; i < length; i++) + order[i] = order[i + 1]; + + /* readjust values in the order array */ + + for (unsigned i = 0; i < length; i++) + if (order[i] > position) + --order[i]; +} + +void +queue::Clear() +{ + for (unsigned i = 0; i < length; i++) { + Item *item = &items[i]; + + assert(!song_in_database(item->song) || + song_is_detached(item->song)); + song_free(item->song); + + id_table.Erase(item->id); + } + + length = 0; +} + +static void +queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end) +{ + assert(queue != NULL); + assert(queue->random); + assert(start <= end); + assert(end <= queue->length); + + auto cmp = [queue](unsigned a_pos, unsigned b_pos){ + const queue::Item &a = queue->items[a_pos]; + const queue::Item &b = queue->items[b_pos]; + + return a.priority > b.priority; + }; + + std::stable_sort(queue->order + start, queue->order + end, cmp); +} + +void +queue::ShuffleOrderRange(unsigned start, unsigned end) +{ + assert(random); + assert(start <= end); + assert(end <= length); + + rand.AutoCreate(); + std::shuffle(order + start, order + end, rand); +} + +/** + * Sort the "order" of items by priority, and then shuffle each + * priority group. + */ +void +queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end) +{ + assert(random); + assert(start <= end); + assert(end <= length); + + if (start == end) + return; + + /* first group the range by priority */ + queue_sort_order_by_priority(this, start, end); + + /* now shuffle each priority group */ + unsigned group_start = start; + uint8_t group_priority = GetOrderPriority(start); + + for (unsigned i = start + 1; i < end; ++i) { + const uint8_t priority = GetOrderPriority(i); + assert(priority <= group_priority); + + if (priority != group_priority) { + /* start of a new group - shuffle the one that + has just ended */ + ShuffleOrderRange(group_start, i); + group_start = i; + group_priority = priority; + } + } + + /* shuffle the last group */ + ShuffleOrderRange(group_start, end); +} + +void +queue::ShuffleOrder() +{ + ShuffleOrderRangeWithPriority(0, length); +} + +void +queue::ShuffleOrderFirst(unsigned start, unsigned end) +{ + rand.AutoCreate(); + + std::uniform_int_distribution<unsigned> distribution(start, end - 1); + SwapOrders(start, distribution(rand)); +} + +void +queue::ShuffleOrderLast(unsigned start, unsigned end) +{ + rand.AutoCreate(); + + std::uniform_int_distribution<unsigned> distribution(start, end - 1); + SwapOrders(end - 1, distribution(rand)); +} + +void +queue::ShuffleRange(unsigned start, unsigned end) +{ + assert(start <= end); + assert(end <= length); + + rand.AutoCreate(); + + for (unsigned i = start; i < end; i++) { + std::uniform_int_distribution<unsigned> distribution(start, + end - 1); + unsigned ri = distribution(rand); + SwapPositions(i, ri); + } +} + +unsigned +queue::FindPriorityOrder(unsigned start_order, uint8_t priority, + unsigned exclude_order) const +{ + assert(random); + assert(start_order <= length); + + for (unsigned i = start_order; i < length; ++i) { + const unsigned position = OrderToPosition(i); + const Item *item = &items[position]; + if (item->priority <= priority && i != exclude_order) + return i; + } + + return length; +} + +unsigned +queue::CountSamePriority(unsigned start_order, uint8_t priority) const +{ + assert(random); + assert(start_order <= length); + + for (unsigned i = start_order; i < length; ++i) { + const unsigned position = OrderToPosition(i); + const Item *item = &items[position]; + if (item->priority != priority) + return i - start_order; + } + + return length - start_order; +} + +bool +queue::SetPriority(unsigned position, uint8_t priority, int after_order) +{ + assert(position < length); + + Item *item = &items[position]; + uint8_t old_priority = item->priority; + if (old_priority == priority) + return false; + + item->version = version; + item->priority = priority; + + if (!random) + /* don't reorder if not in random mode */ + return true; + + unsigned _order = PositionToOrder(position); + if (after_order >= 0) { + if (_order == (unsigned)after_order) + /* don't reorder the current song */ + return true; + + if (_order < (unsigned)after_order) { + /* the specified song has been played already + - enqueue it only if its priority has just + become bigger than the current one's */ + + const unsigned after_position = + OrderToPosition(after_order); + const Item *after_item = + &items[after_position]; + if (old_priority > after_item->priority || + priority <= after_item->priority) + /* priority hasn't become bigger */ + return true; + } + } + + /* move the item to the beginning of the priority group (or + create a new priority group) */ + + const unsigned before_order = + FindPriorityOrder(after_order + 1, priority, _order); + const unsigned new_order = before_order > _order + ? before_order - 1 + : before_order; + MoveOrder(_order, new_order); + + /* shuffle the song within that priority group */ + + const unsigned priority_count = CountSamePriority(new_order, priority); + assert(priority_count >= 1); + ShuffleOrderFirst(new_order, new_order + priority_count); + + return true; +} + +bool +queue::SetPriorityRange(unsigned start_position, unsigned end_position, + uint8_t priority, int after_order) +{ + assert(start_position <= end_position); + assert(end_position <= length); + + bool modified = false; + int after_position = after_order >= 0 + ? (int)OrderToPosition(after_order) + : -1; + for (unsigned i = start_position; i < end_position; ++i) { + after_order = after_position >= 0 + ? (int)PositionToOrder(after_position) + : -1; + + modified |= SetPriority(i, priority, after_order); + } + + return modified; +} diff --git a/src/Queue.hxx b/src/Queue.hxx new file mode 100644 index 000000000..6e7786bcd --- /dev/null +++ b/src/Queue.hxx @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_QUEUE_HXX +#define MPD_QUEUE_HXX + +#include "gcc.h" +#include "IdTable.hxx" +#include "util/LazyRandomEngine.hxx" + +#include <algorithm> + +#include <assert.h> +#include <stdint.h> + +/** + * A queue of songs. This is the backend of the playlist: it contains + * an ordered list of songs. + * + * Songs can be addressed in three possible ways: + * + * - the position in the queue + * - the unique id (which stays the same, regardless of moves) + * - the order number (which only differs from "position" in random mode) + */ +struct queue { + /** + * reserve max_length * HASH_MULT elements in the id + * number space + */ + static constexpr unsigned HASH_MULT = 4; + + /** + * One element of the queue: basically a song plus some queue specific + * information attached. + */ + struct Item { + struct song *song; + + /** the unique id of this item in the queue */ + unsigned id; + + /** when was this item last changed? */ + uint32_t version; + + /** + * The priority of this item, between 0 and 255. High + * priority value means that this song gets played first in + * "random" mode. + */ + uint8_t priority; + }; + + /** configured maximum length of the queue */ + unsigned max_length; + + /** number of songs in the queue */ + unsigned length; + + /** the current version number */ + uint32_t version; + + /** all songs in "position" order */ + Item *items; + + /** map order numbers to positions */ + unsigned *order; + + /** map song ids to positions */ + IdTable id_table; + + /** repeat playback when the end of the queue has been + reached? */ + bool repeat; + + /** play only current song. */ + bool single; + + /** remove each played files. */ + bool consume; + + /** play back songs in random order? */ + bool random; + + /** random number generator for shuffle and random mode */ + LazyRandomEngine rand; + + queue(unsigned max_length); + + /** + * Deinitializes a queue object. It does not free the queue + * pointer itself. + */ + ~queue(); + + queue(const queue &other) = delete; + queue &operator=(const queue &other) = delete; + + unsigned GetLength() const { + assert(length <= max_length); + + return length; + } + + /** + * Determine if the queue is empty, i.e. there are no songs. + */ + bool IsEmpty() const { + return length == 0; + } + + /** + * Determine if the maximum number of songs has been reached. + */ + bool IsFull() const { + assert(length <= max_length); + + return length >= max_length; + } + + /** + * Is that a valid position number? + */ + bool IsValidPosition(unsigned position) const { + return position < length; + } + + /** + * Is that a valid order number? + */ + bool IsValidOrder(unsigned _order) const { + return _order < length; + } + + int IdToPosition(unsigned id) const { + return id_table.IdToPosition(id); + } + + int PositionToId(unsigned position) const + { + assert(position < length); + + return items[position].id; + } + + gcc_pure + unsigned OrderToPosition(unsigned _order) const { + assert(_order < length); + + return order[_order]; + } + + gcc_pure + unsigned PositionToOrder(unsigned position) const { + assert(position < length); + + for (unsigned i = 0;; ++i) { + assert(i < length); + + if (order[i] == position) + return i; + } + } + + gcc_pure + uint8_t GetPriorityAtPosition(unsigned position) const { + assert(position < length); + + return items[position].priority; + } + + const Item &GetOrderItem(unsigned i) const { + assert(IsValidOrder(i)); + + return items[OrderToPosition(i)]; + } + + uint8_t GetOrderPriority(unsigned i) const { + return GetOrderItem(i).priority; + } + + /** + * Returns the song at the specified position. + */ + struct song *Get(unsigned position) const { + assert(position < length); + + return items[position].song; + } + + /** + * Returns the song at the specified order number. + */ + struct song *GetOrder(unsigned _order) const { + return Get(OrderToPosition(_order)); + } + + /** + * Is the song at the specified position newer than the specified + * version? + */ + bool IsNewerAtPosition(unsigned position, uint32_t _version) const { + assert(position < length); + + return _version > version || + items[position].version >= _version || + items[position].version == 0; + } + + /** + * Returns the order number following the specified one. This takes + * end of queue and "repeat" mode into account. + * + * @return the next order number, or -1 to stop playback + */ + gcc_pure + int GetNextOrder(unsigned order) const; + + /** + * Increments the queue's version number. This handles integer + * overflow well. + */ + void IncrementVersion(); + + /** + * Marks the specified song as "modified" and increments the version + * number. + */ + void ModifyAtOrder(unsigned order); + + /** + * Marks all songs as "modified" and increments the version number. + */ + void ModifyAll(); + + /** + * Appends a song to the queue and returns its position. Prior to + * that, the caller must check if the queue is already full. + * + * If a song is not in the database (determined by + * song_in_database()), it is freed when removed from the queue. + * + * @param priority the priority of this new queue item + */ + unsigned Append(struct song *song, uint8_t priority); + + /** + * Swaps two songs, addressed by their position. + */ + void SwapPositions(unsigned position1, unsigned position2); + + /** + * Swaps two songs, addressed by their order number. + */ + void SwapOrders(unsigned order1, unsigned order2) { + std::swap(order[order1], order[order2]); + } + + /** + * Moves a song to a new position. + */ + void MovePostion(unsigned from, unsigned to); + + /** + * Moves a range of songs to a new position. + */ + void MoveRange(unsigned start, unsigned end, unsigned to); + + /** + * Removes a song from the playlist. + */ + void DeletePosition(unsigned position); + + /** + * Removes all songs from the playlist. + */ + void Clear(); + + /** + * Initializes the "order" array, and restores "normal" order. + */ + void RestoreOrder() { + for (unsigned i = 0; i < length; ++i) + order[i] = i; + } + + /** + * Shuffle the order of items in the specified range, ignoring + * their priorities. + */ + void ShuffleOrderRange(unsigned start, unsigned end); + + /** + * Shuffle the order of items in the specified range, taking their + * priorities into account. + */ + void ShuffleOrderRangeWithPriority(unsigned start, unsigned end); + + /** + * Shuffles the virtual order of songs, but does not move them + * physically. This is used in random mode. + */ + void ShuffleOrder(); + + void ShuffleOrderFirst(unsigned start, unsigned end); + + /** + * Shuffles the virtual order of the last song in the specified + * (order) range. This is used in random mode after a song has been + * appended by queue_append(). + */ + void ShuffleOrderLast(unsigned start, unsigned end); + + /** + * Shuffles a (position) range in the queue. The songs are physically + * shuffled, not by using the "order" mapping. + */ + void ShuffleRange(unsigned start, unsigned end); + + bool SetPriority(unsigned position, uint8_t priority, int after_order); + + bool SetPriorityRange(unsigned start_position, unsigned end_position, + uint8_t priority, int after_order); + +private: + /** + * Moves a song to a new position in the "order" list. + */ + void MoveOrder(unsigned from_order, unsigned to_order); + + void MoveItemTo(unsigned from, unsigned to) { + unsigned from_id = items[from].id; + + items[to] = items[from]; + items[to].version = version; + id_table.Move(from_id, to); + } + + /** + * Find the first item that has this specified priority or + * higher. + */ + gcc_pure + unsigned FindPriorityOrder(unsigned start_order, uint8_t priority, + unsigned exclude_order) const; + + gcc_pure + unsigned CountSamePriority(unsigned start_order, + uint8_t priority) const; +}; + +#endif diff --git a/src/QueueCommands.cxx b/src/QueueCommands.cxx new file mode 100644 index 000000000..4a3e1312d --- /dev/null +++ b/src/QueueCommands.cxx @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "QueueCommands.hxx" +#include "CommandError.hxx" +#include "DatabaseQueue.hxx" +#include "SongFilter.hxx" +#include "DatabaseSelection.hxx" +#include "Playlist.hxx" +#include "PlaylistPrint.hxx" +#include "ClientFile.hxx" +#include "ClientInternal.hxx" +#include "Partition.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "ls.hxx" + +extern "C" { +#include "uri.h" +} + +#include <string.h> + +enum command_return +handle_add(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + char *uri = argv[1]; + enum playlist_result result; + + if (strncmp(uri, "file:///", 8) == 0) { + const char *path = uri + 7; + + GError *error = NULL; + if (!client_allow_file(client, path, &error)) + return print_error(client, error); + + result = client->partition.AppendFile(path); + return print_playlist_result(client, result); + } + + if (uri_has_scheme(uri)) { + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return COMMAND_RETURN_ERROR; + } + + result = client->partition.AppendURI(uri); + return print_playlist_result(client, result); + } + + const DatabaseSelection selection(uri, true); + GError *error = NULL; + return AddFromDatabase(client->partition, selection, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_addid(Client *client, int argc, char *argv[]) +{ + char *uri = argv[1]; + unsigned added_id; + enum playlist_result result; + + if (strncmp(uri, "file:///", 8) == 0) { + const char *path = uri + 7; + + GError *error = NULL; + if (!client_allow_file(client, path, &error)) + return print_error(client, error); + + result = client->partition.AppendFile(path, &added_id); + } else { + if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return COMMAND_RETURN_ERROR; + } + + result = client->partition.AppendURI(uri, &added_id); + } + + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + + if (argc == 3) { + unsigned to; + if (!check_unsigned(client, &to, argv[2])) + return COMMAND_RETURN_ERROR; + result = client->partition.MoveId(added_id, to); + if (result != PLAYLIST_RESULT_SUCCESS) { + enum command_return ret = + print_playlist_result(client, result); + client->partition.DeleteId(added_id); + return ret; + } + } + + client_printf(client, "Id: %u\n", added_id); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_delete(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned start, end; + + if (!check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = client->partition.DeleteRange(start, end); + return print_playlist_result(client, result); +} + +enum command_return +handle_deleteid(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned id; + + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = client->partition.DeleteId(id); + return print_playlist_result(client, result); +} + +enum command_return +handle_playlist(Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + playlist_print_uris(client, &client->playlist); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_shuffle(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + unsigned start = 0, end = client->playlist.queue.GetLength(); + if (argc == 2 && !check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.Shuffle(start, end); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_clear(G_GNUC_UNUSED Client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + client->partition.ClearQueue(); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_plchanges(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + uint32_t version; + + if (!check_uint32(client, &version, argv[1])) + return COMMAND_RETURN_ERROR; + + playlist_print_changes_info(client, &client->playlist, version); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_plchangesposid(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + uint32_t version; + + if (!check_uint32(client, &version, argv[1])) + return COMMAND_RETURN_ERROR; + + playlist_print_changes_position(client, &client->playlist, version); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_playlistinfo(Client *client, int argc, char *argv[]) +{ + unsigned start = 0, end = G_MAXUINT; + bool ret; + + if (argc == 2 && !check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + + ret = playlist_print_info(client, &client->playlist, start, end); + if (!ret) + return print_playlist_result(client, + PLAYLIST_RESULT_BAD_RANGE); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_playlistid(Client *client, int argc, char *argv[]) +{ + if (argc >= 2) { + unsigned id; + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + + bool ret = playlist_print_id(client, &client->playlist, id); + if (!ret) + return print_playlist_result(client, + PLAYLIST_RESULT_NO_SUCH_SONG); + } else { + playlist_print_info(client, &client->playlist, 0, G_MAXUINT); + } + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_playlist_match(Client *client, int argc, char *argv[], + bool fold_case) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + playlist_print_find(client, &client->playlist, filter); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_playlistfind(Client *client, int argc, char *argv[]) +{ + return handle_playlist_match(client, argc, argv, false); +} + +enum command_return +handle_playlistsearch(Client *client, int argc, char *argv[]) +{ + return handle_playlist_match(client, argc, argv, true); +} + +enum command_return +handle_prio(Client *client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return COMMAND_RETURN_ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return COMMAND_RETURN_ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned start_position, end_position; + if (!check_range(client, &start_position, &end_position, + argv[i])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SetPriorityRange(start_position, + end_position, + priority); + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_prioid(Client *client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return COMMAND_RETURN_ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return COMMAND_RETURN_ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned song_id; + if (!check_unsigned(client, &song_id, argv[i])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SetPriorityId(song_id, priority); + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_move(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned start, end; + int to; + + if (!check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_int(client, &to, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.MoveRange(start, end, to); + return print_playlist_result(client, result); +} + +enum command_return +handle_moveid(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned id; + int to; + + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_int(client, &to, argv[2])) + return COMMAND_RETURN_ERROR; + enum playlist_result result = client->partition.MoveId(id, to); + return print_playlist_result(client, result); +} + +enum command_return +handle_swap(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned song1, song2; + + if (!check_unsigned(client, &song1, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &song2, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SwapPositions(song1, song2); + return print_playlist_result(client, result); +} + +enum command_return +handle_swapid(Client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + unsigned id1, id2; + + if (!check_unsigned(client, &id1, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &id2, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = client->partition.SwapIds(id1, id2); + return print_playlist_result(client, result); +} diff --git a/src/QueueCommands.hxx b/src/QueueCommands.hxx new file mode 100644 index 000000000..97b61e212 --- /dev/null +++ b/src/QueueCommands.hxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_QUEUE_COMMANDS_HXX +#define MPD_QUEUE_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_add(Client *client, int argc, char *argv[]); + +enum command_return +handle_addid(Client *client, int argc, char *argv[]); + +enum command_return +handle_delete(Client *client, int argc, char *argv[]); + +enum command_return +handle_deleteid(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlist(Client *client, int argc, char *argv[]); + +enum command_return +handle_shuffle(Client *client, int argc, char *argv[]); + +enum command_return +handle_clear(Client *client, int argc, char *argv[]); + +enum command_return +handle_plchanges(Client *client, int argc, char *argv[]); + +enum command_return +handle_plchangesposid(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistinfo(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistid(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistfind(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistsearch(Client *client, int argc, char *argv[]); + +enum command_return +handle_prio(Client *client, int argc, char *argv[]); + +enum command_return +handle_prioid(Client *client, int argc, char *argv[]); + +enum command_return +handle_move(Client *client, int argc, char *argv[]); + +enum command_return +handle_moveid(Client *client, int argc, char *argv[]); + +enum command_return +handle_swap(Client *client, int argc, char *argv[]); + +enum command_return +handle_swapid(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/QueuePrint.cxx b/src/QueuePrint.cxx new file mode 100644 index 000000000..28abc5a8c --- /dev/null +++ b/src/QueuePrint.cxx @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "QueuePrint.hxx" +#include "Queue.hxx" +#include "SongFilter.hxx" +#include "SongPrint.hxx" +#include "Mapper.hxx" +#include "Client.hxx" + +extern "C" { +#include "song.h" +} + +/** + * Send detailed information about a range of songs in the queue to a + * client. + * + * @param client the client which has requested information + * @param start the index of the first song (including) + * @param end the index of the last song (excluding) + */ +static void +queue_print_song_info(Client *client, const struct queue *queue, + unsigned position) +{ + song_print_info(client, queue->Get(position)); + client_printf(client, "Pos: %u\nId: %u\n", + position, queue->PositionToId(position)); + + uint8_t priority = queue->GetPriorityAtPosition(position); + if (priority != 0) + client_printf(client, "Prio: %u\n", priority); +} + +void +queue_print_info(Client *client, const struct queue *queue, + unsigned start, unsigned end) +{ + assert(start <= end); + assert(end <= queue->GetLength()); + + for (unsigned i = start; i < end; ++i) + queue_print_song_info(client, queue, i); +} + +void +queue_print_uris(Client *client, const struct queue *queue, + unsigned start, unsigned end) +{ + assert(start <= end); + assert(end <= queue->GetLength()); + + for (unsigned i = start; i < end; ++i) { + client_printf(client, "%i:", i); + song_print_uri(client, queue->Get(i)); + } +} + +void +queue_print_changes_info(Client *client, const struct queue *queue, + uint32_t version) +{ + for (unsigned i = 0; i < queue->GetLength(); i++) { + if (queue->IsNewerAtPosition(i, version)) + queue_print_song_info(client, queue, i); + } +} + +void +queue_print_changes_position(Client *client, const struct queue *queue, + uint32_t version) +{ + for (unsigned i = 0; i < queue->GetLength(); i++) + if (queue->IsNewerAtPosition(i, version)) + client_printf(client, "cpos: %i\nId: %i\n", + i, queue->PositionToId(i)); +} + +void +queue_find(Client *client, const struct queue *queue, + const SongFilter &filter) +{ + for (unsigned i = 0; i < queue->GetLength(); i++) { + const struct song *song = queue->Get(i); + + if (filter.Match(*song)) + queue_print_song_info(client, queue, i); + } +} diff --git a/src/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..09b0645f8 100644 --- a/src/queue_save.c +++ b/src/QueueSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,17 @@ */ #include "config.h" -#include "queue_save.h" -#include "queue.h" +#include "QueueSave.hxx" +#include "Playlist.hxx" #include "song.h" +#include "SongSave.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" +#include "TextFile.hxx" + +extern "C" { #include "uri.h" -#include "database.h" -#include "song_save.h" -#include "text_file.h" +} #include <stdlib.h> @@ -57,48 +61,40 @@ queue_save_song(FILE *fp, int idx, const struct song *song) void queue_save(FILE *fp, const struct queue *queue) { - for (unsigned i = 0; i < queue_length(queue); i++) { - uint8_t prio = queue_get_priority_at_position(queue, i); + for (unsigned i = 0; i < queue->GetLength(); i++) { + uint8_t prio = queue->GetPriorityAtPosition(i); if (prio != 0) fprintf(fp, PRIO_LABEL "%u\n", prio); - queue_save_song(fp, i, queue_get(queue, i)); + queue_save_song(fp, i, queue->Get(i)); } } -static struct song * -get_song(const char *uri) -{ - return uri_has_scheme(uri) - ? song_remote_new(uri) - : db_get_song(uri); -} - void -queue_load_song(FILE *fp, GString *buffer, const char *line, - struct queue *queue) +queue_load_song(TextFile &file, const char *line, queue *queue) { - struct song *song; - - if (queue_is_full(queue)) + if (queue->IsFull()) return; uint8_t priority = 0; if (g_str_has_prefix(line, PRIO_LABEL)) { priority = strtoul(line + sizeof(PRIO_LABEL) - 1, NULL, 10); - line = read_text_line(fp, buffer); + line = file.ReadLine(); if (line == NULL) return; } + const Database *db = nullptr; + struct song *song; + if (g_str_has_prefix(line, SONG_BEGIN)) { const char *uri = line + sizeof(SONG_BEGIN) - 1; if (!uri_has_scheme(uri) && !g_path_is_absolute(uri)) return; GError *error = NULL; - song = song_load(fp, NULL, uri, buffer, &error); + song = song_load(file, NULL, uri, &error); if (song == NULL) { g_warning("%s", error->message); g_error_free(error); @@ -112,12 +108,23 @@ queue_load_song(FILE *fp, GString *buffer, const char *line, return; } - line = endptr + 1; + const char *uri = endptr + 1; - song = get_song(line); - if (song == NULL) - return; + if (uri_has_scheme(uri)) { + song = song_remote_new(uri); + } else { + db = GetDatabase(nullptr); + if (db == nullptr) + return; + + song = db->GetSong(uri, nullptr); + if (song == nullptr) + return; + } } - queue_append(queue, song, priority); + queue->Append(song, priority); + + if (db != nullptr) + db->ReturnSong(song); } diff --git a/src/queue_save.h b/src/QueueSave.hxx index 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/replay_gain_config.c b/src/ReplayGainConfig.cxx index 2181387b7..d86c70053 100644 --- a/src/replay_gain_config.c +++ b/src/ReplayGainConfig.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,9 +19,9 @@ #include "config.h" #include "replay_gain_config.h" -#include "playlist.h" +#include "Idle.hxx" #include "conf.h" -#include "idle.h" +#include "Playlist.hxx" #include "mpd_error.h" #include <glib.h> @@ -29,20 +29,14 @@ #include <assert.h> #include <stdlib.h> #include <string.h> -#include <math.h> - -static const char *const replay_gain_mode_names[] = { - [REPLAY_GAIN_ALBUM] = "album", - [REPLAY_GAIN_TRACK] = "track", -}; enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF; -const bool DEFAULT_REPLAYGAIN_LIMIT = true; +static constexpr bool DEFAULT_REPLAYGAIN_LIMIT = true; float replay_gain_preamp = 1.0; float replay_gain_missing_preamp = 1.0; -bool replay_gain_limit; +bool replay_gain_limit = DEFAULT_REPLAYGAIN_LIMIT; const char * replay_gain_get_mode_string(void) @@ -137,14 +131,15 @@ void replay_gain_global_init(void) replay_gain_limit = config_get_bool(CONF_REPLAYGAIN_LIMIT, DEFAULT_REPLAYGAIN_LIMIT); } -enum replay_gain_mode replay_gain_get_real_mode(void) +enum replay_gain_mode +replay_gain_get_real_mode(bool random_mode) { enum replay_gain_mode rgm; rgm = replay_gain_mode; if (rgm == REPLAY_GAIN_AUTO) - rgm = g_playlist.queue.random ? REPLAY_GAIN_TRACK : REPLAY_GAIN_ALBUM; + rgm = random_mode ? REPLAY_GAIN_TRACK : REPLAY_GAIN_ALBUM; return rgm; } diff --git a/src/replay_gain_info.c b/src/ReplayGainInfo.cxx index 1f09e7a1a..b9d1b82c6 100644 --- a/src/replay_gain_info.c +++ b/src/ReplayGainInfo.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/server_socket.c b/src/ServerSocket.cxx index 396399596..7f14168a8 100644 --- a/src/server_socket.c +++ b/src/ServerSocket.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,17 +23,19 @@ #define _GNU_SOURCE 1 #endif -#include "server_socket.h" -#include "socket_util.h" +#include "ServerSocket.hxx" +#include "SocketUtil.hxx" +#include "SocketError.hxx" +#include "event/SocketMonitor.hxx" #include "resolver.h" #include "fd_util.h" -#include "glib_socket.h" + +#include <forward_list> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> -#include <errno.h> #include <unistd.h> #include <stdlib.h> #include <assert.h> @@ -53,27 +55,72 @@ #define DEFAULT_PORT 6600 -struct one_socket { - struct one_socket *next; - struct server_socket *parent; - - unsigned serial; +struct OneServerSocket final : private SocketMonitor { + const server_socket &parent; - int fd; - guint source_id; + const unsigned serial; char *path; size_t address_length; - struct sockaddr address; + struct sockaddr *address; + + OneServerSocket(EventLoop &_loop, const server_socket &_parent, + unsigned _serial, + const struct sockaddr *_address, + size_t _address_length) + :SocketMonitor(_loop), + parent(_parent), serial(_serial), + path(nullptr), + address_length(_address_length), + address((sockaddr *)g_memdup(_address, _address_length)) + { + assert(_address != nullptr); + assert(_address_length > 0); + } + + OneServerSocket(const OneServerSocket &other) = delete; + OneServerSocket &operator=(const OneServerSocket &other) = delete; + + ~OneServerSocket() { + g_free(path); + g_free(address); + } + + bool Open(GError **error_r); + + using SocketMonitor::Close; + + char *ToString() const; + + void SetFD(int _fd) { + SocketMonitor::Open(_fd); + SocketMonitor::ScheduleRead(); + } + + void Accept(); + +private: + virtual void OnSocketReady(unsigned flags) override; }; struct server_socket { + EventLoop &loop; + server_socket_callback_t callback; void *callback_ctx; - struct one_socket *sockets, **sockets_tail_r; + std::forward_list<OneServerSocket> sockets; + unsigned next_serial; + + server_socket(EventLoop &_loop, + server_socket_callback_t _callback, void *_callback_ctx) + :loop(_loop), + callback(_callback), callback_ctx(_callback_ctx), + next_serial(1) {} + + void Close(); }; static GQuark @@ -83,43 +130,26 @@ server_socket_quark(void) } struct server_socket * -server_socket_new(server_socket_callback_t callback, void *callback_ctx) +server_socket_new(EventLoop &loop, + server_socket_callback_t callback, void *callback_ctx) { - struct server_socket *ss = g_new(struct server_socket, 1); - ss->callback = callback; - ss->callback_ctx = callback_ctx; - ss->sockets = NULL; - ss->sockets_tail_r = &ss->sockets; - ss->next_serial = 1; - return ss; + return new server_socket(loop, callback, callback_ctx); } void server_socket_free(struct server_socket *ss) { - server_socket_close(ss); - - while (ss->sockets != NULL) { - struct one_socket *s = ss->sockets; - ss->sockets = s->next; - - assert(s->fd < 0); - - g_free(s->path); - g_free(s); - } - - g_free(ss); + delete ss; } /** * Wraper for sockaddr_to_string() which never fails. */ -static char * -one_socket_to_string(const struct one_socket *s) +char * +OneServerSocket::ToString() const { - char *p = sockaddr_to_string(&s->address, s->address_length, NULL); - if (p == NULL) + char *p = sockaddr_to_string(address, address_length, nullptr); + if (p == nullptr) p = g_strdup("[unknown]"); return p; } @@ -149,72 +179,83 @@ get_remote_uid(int fd) #endif } -static gboolean -server_socket_in_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - gpointer data) +inline void +OneServerSocket::Accept() { - struct one_socket *s = data; + struct sockaddr_storage peer_address; + size_t peer_address_length = sizeof(peer_address); + int peer_fd = + accept_cloexec_nonblock(Get(), (struct sockaddr*)&peer_address, + &peer_address_length); + if (peer_fd < 0) { + const SocketErrorMessage msg; + g_warning("accept() failed: %s", (const char *)msg); + return; + } - struct sockaddr_storage address; - size_t address_length = sizeof(address); - int fd = accept_cloexec_nonblock(s->fd, (struct sockaddr*)&address, - &address_length); - if (fd >= 0) { - if (socket_keepalive(fd)) - g_warning("Could not set TCP keepalive option: %s", - g_strerror(errno)); - s->parent->callback(fd, (const struct sockaddr*)&address, - address_length, get_remote_uid(fd), - s->parent->callback_ctx); - } else { - g_warning("accept() failed: %s", g_strerror(errno)); + if (socket_keepalive(peer_fd)) { + const SocketErrorMessage msg; + g_warning("Could not set TCP keepalive option: %s", + (const char *)msg); } - return true; + parent.callback(peer_fd, + (const struct sockaddr*)&peer_address, + peer_address_length, get_remote_uid(peer_fd), + parent.callback_ctx); } -static void -set_fd(struct one_socket *s, int fd) +void +OneServerSocket::OnSocketReady(gcc_unused unsigned flags) { - assert(s != NULL); - assert(s->fd < 0); - assert(fd >= 0); + Accept(); +} + +inline bool +OneServerSocket::Open(GError **error_r) +{ + assert(!IsDefined()); - s->fd = fd; + int _fd = socket_bind_listen(address->sa_family, + SOCK_STREAM, 0, + address, address_length, 5, + error_r); + if (_fd < 0) + return false; + + /* allow everybody to connect */ + + if (path != nullptr) + chmod(path, 0666); + + /* register in the GLib main loop */ - GIOChannel *channel = g_io_channel_new_socket(s->fd); - s->source_id = g_io_add_watch(channel, G_IO_IN, - server_socket_in_event, s); - g_io_channel_unref(channel); + SetFD(_fd); + + return true; } bool server_socket_open(struct server_socket *ss, GError **error_r) { - struct one_socket *good = NULL, *bad = NULL; - GError *last_error = NULL; + struct OneServerSocket *good = nullptr, *bad = nullptr; + GError *last_error = nullptr; - for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) { - assert(s->serial > 0); - assert(good == NULL || s->serial >= good->serial); - assert(s->fd < 0); + for (auto &i : ss->sockets) { + assert(i.serial > 0); + assert(good == nullptr || i.serial <= good->serial); - if (bad != NULL && s->serial != bad->serial) { + if (bad != nullptr && i.serial != bad->serial) { server_socket_close(ss); g_propagate_error(error_r, last_error); return false; } - GError *error = NULL; - int fd = socket_bind_listen(s->address.sa_family, - SOCK_STREAM, 0, - &s->address, s->address_length, 5, - &error); - if (fd < 0) { - if (good != NULL && good->serial == s->serial) { - char *address_string = one_socket_to_string(s); - char *good_string = one_socket_to_string(good); + GError *error = nullptr; + if (!i.Open(&error)) { + if (good != nullptr && good->serial == i.serial) { + char *address_string = i.ToString(); + char *good_string = good->ToString(); g_warning("bind to '%s' failed: %s " "(continuing anyway, because " "binding to '%s' succeeded)", @@ -223,10 +264,10 @@ server_socket_open(struct server_socket *ss, GError **error_r) g_free(address_string); g_free(good_string); g_error_free(error); - } else if (bad == NULL) { - bad = s; + } else if (bad == nullptr) { + bad = &i; - char *address_string = one_socket_to_string(s); + char *address_string = i.ToString(); g_propagate_prefixed_error(&last_error, error, "Failed to bind to '%s': ", address_string); @@ -236,28 +277,19 @@ server_socket_open(struct server_socket *ss, GError **error_r) continue; } - /* allow everybody to connect */ - - if (s->path != NULL) - chmod(s->path, 0666); - - /* register in the GLib main loop */ - - set_fd(s, fd); - /* mark this socket as "good", and clear previous errors */ - good = s; + good = &i; - if (bad != NULL) { - bad = NULL; + if (bad != nullptr) { + bad = nullptr; g_error_free(last_error); - last_error = NULL; + last_error = nullptr; } } - if (bad != NULL) { + if (bad != nullptr) { server_socket_close(ss); g_propagate_error(error_r, last_error); return false; @@ -267,85 +299,54 @@ server_socket_open(struct server_socket *ss, GError **error_r) } void -server_socket_close(struct server_socket *ss) +server_socket::Close() { - for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) { - if (s->fd < 0) - continue; + for (auto &i : sockets) + i.Close(); +} - g_source_remove(s->source_id); - close_socket(s->fd); - s->fd = -1; - } +void +server_socket_close(struct server_socket *ss) +{ + ss->Close(); } -static struct one_socket * -one_socket_new(unsigned serial, const struct sockaddr *address, - size_t address_length) +static OneServerSocket & +server_socket_add_address(struct server_socket *ss, + const struct sockaddr *address, + size_t address_length) { - assert(address != NULL); - assert(address_length > 0); - - struct one_socket *s = g_malloc(sizeof(*s) - sizeof(s->address) + - address_length); - s->next = NULL; - s->serial = serial; - s->fd = -1; - s->path = NULL; - s->address_length = address_length; - memcpy(&s->address, address, address_length); - - return s; + assert(ss != nullptr); + + ss->sockets.emplace_front(ss->loop, *ss, ss->next_serial, + address, address_length); + + return ss->sockets.front(); } bool server_socket_add_fd(struct server_socket *ss, int fd, GError **error_r) { - assert(ss != NULL); - assert(ss->sockets_tail_r != NULL); - assert(*ss->sockets_tail_r == NULL); + assert(ss != nullptr); assert(fd >= 0); struct sockaddr_storage address; socklen_t address_length; if (getsockname(fd, (struct sockaddr *)&address, &address_length) < 0) { - g_set_error(error_r, server_socket_quark(), errno, - "Failed to get socket address: %s", - g_strerror(errno)); + SetSocketError(error_r); + g_prefix_error(error_r, "Failed to get socket address"); return false; } - struct one_socket *s = one_socket_new(ss->next_serial, - (struct sockaddr *)&address, - address_length); - s->parent = ss; - *ss->sockets_tail_r = s; - ss->sockets_tail_r = &s->next; - - set_fd(s, fd); + OneServerSocket &s = + server_socket_add_address(ss, (struct sockaddr *)&address, + address_length); + s.SetFD(fd); return true; } -static struct one_socket * -server_socket_add_address(struct server_socket *ss, - const struct sockaddr *address, - size_t address_length) -{ - assert(ss != NULL); - assert(ss->sockets_tail_r != NULL); - assert(*ss->sockets_tail_r == NULL); - - struct one_socket *s = one_socket_new(ss->next_serial, - address, address_length); - s->parent = ss; - *ss->sockets_tail_r = s; - ss->sockets_tail_r = &s->next; - - return s; -} - #ifdef HAVE_TCP /** @@ -424,10 +425,10 @@ server_socket_add_host(struct server_socket *ss, const char *hostname, struct addrinfo *ai = resolve_host_port(hostname, port, AI_PASSIVE, SOCK_STREAM, error_r); - if (ai == NULL) + if (ai == nullptr) return false; - for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next) + for (const struct addrinfo *i = ai; i != nullptr; i = i->ai_next) server_socket_add_address(ss, i->ai_addr, i->ai_addrlen); freeaddrinfo(ai); @@ -465,10 +466,10 @@ server_socket_add_path(struct server_socket *ss, const char *path, s_un.sun_family = AF_UNIX; memcpy(s_un.sun_path, path, path_length + 1); - struct one_socket *s = + OneServerSocket &s = server_socket_add_address(ss, (const struct sockaddr *)&s_un, sizeof(s_un)); - s->path = g_strdup(path); + s.path = g_strdup(path); return true; #else /* !HAVE_UN */ diff --git a/src/server_socket.h b/src/ServerSocket.hxx index 7caa4bbf2..eea4b0851 100644 --- a/src/server_socket.h +++ b/src/ServerSocket.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,14 +17,15 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_SERVER_SOCKET_H -#define MPD_SERVER_SOCKET_H +#ifndef MPD_SERVER_SOCKET_HXX +#define MPD_SERVER_SOCKET_HXX -#include <stdbool.h> +#include "gerror.h" -#include <glib.h> +#include <stddef.h> struct sockaddr; +class EventLoop; typedef void (*server_socket_callback_t)(int fd, const struct sockaddr *address, @@ -32,7 +33,8 @@ typedef void (*server_socket_callback_t)(int fd, void *ctx); struct server_socket * -server_socket_new(server_socket_callback_t callback, void *callback_ctx); +server_socket_new(EventLoop &loop, + server_socket_callback_t callback, void *callback_ctx); void server_socket_free(struct server_socket *ss); diff --git a/src/sig_handlers.c b/src/SignalHandlers.cxx index b23f9e778..d438eb703 100644 --- a/src/sig_handlers.c +++ b/src/SignalHandlers.cxx @@ -18,13 +18,14 @@ */ #include "config.h" -#include "sig_handlers.h" +#include "SignalHandlers.hxx" #ifndef WIN32 -#include "log.h" -#include "main.h" -#include "event_pipe.h" +#include "Log.hxx" +#include "Main.hxx" +#include "event/Loop.hxx" +#include "GlobalEvents.hxx" #include "mpd_error.h" #include <glib.h> @@ -35,12 +36,12 @@ static void exit_signal_handler(G_GNUC_UNUSED int signum) { - g_main_loop_quit(main_loop); + GlobalEvents::Emit(GlobalEvents::SHUTDOWN); } static void reload_signal_handler(G_GNUC_UNUSED int signum) { - event_pipe_emit_fast(PIPE_EVENT_RELOAD); + GlobalEvents::Emit(GlobalEvents::RELOAD); } static void @@ -73,7 +74,7 @@ void initSigHandlers(void) x_sigaction(SIGINT, &sa); x_sigaction(SIGTERM, &sa); - event_pipe_register(PIPE_EVENT_RELOAD, handle_reload_event); + GlobalEvents::Register(GlobalEvents::RELOAD, handle_reload_event); sa.sa_handler = reload_signal_handler; x_sigaction(SIGHUP, &sa); #endif diff --git a/src/SignalHandlers.hxx b/src/SignalHandlers.hxx new file mode 100644 index 000000000..99f347fb0 --- /dev/null +++ b/src/SignalHandlers.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SIGNAL_HANDLERS_HXX +#define MPD_SIGNAL_HANDLERS_HXX + +void initSigHandlers(void); + +#endif diff --git a/src/SocketError.hxx b/src/SocketError.hxx new file mode 100644 index 000000000..53a0c1d8f --- /dev/null +++ b/src/SocketError.hxx @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SOCKET_ERROR_HXX +#define MPD_SOCKET_ERROR_HXX + +#include "gcc.h" + +#include <glib.h> + +#ifdef WIN32 +#include <winsock2.h> +typedef DWORD socket_error_t; +#else +#include <errno.h> +typedef int socket_error_t; +#endif + +/** + * A GQuark for GError for socket I/O errors. The code is an errno + * value (or WSAGetLastError() on Windows). + */ +gcc_const +static inline GQuark +SocketErrorQuark(void) +{ + return g_quark_from_static_string("socket"); +} + +gcc_pure +static inline socket_error_t +GetSocketError() +{ +#ifdef WIN32 + return WSAGetLastError(); +#else + return errno; +#endif +} + +gcc_const +static inline bool +IsSocketErrorAgain(socket_error_t code) +{ +#ifdef WIN32 + return code == WSAEINPROGRESS; +#else + return code == EAGAIN; +#endif +} + +gcc_const +static inline bool +IsSocketErrorInterruped(socket_error_t code) +{ +#ifdef WIN32 + return code == WSAEINTR; +#else + return code == EINTR; +#endif +} + +gcc_const +static inline bool +IsSocketErrorClosed(socket_error_t code) +{ +#ifdef WIN32 + return code == WSAECONNRESET; +#else + return code == EPIPE || code == ECONNRESET; +#endif +} + +/** + * Helper class that formats a socket error message into a + * human-readable string. On Windows, a buffer is necessary for this, + * and this class hosts the buffer. + */ +class SocketErrorMessage { +#ifdef WIN32 + char msg[256]; +#else + const char *const msg; +#endif + +public: +#ifdef WIN32 + explicit SocketErrorMessage(socket_error_t code=GetSocketError()) { + DWORD nbytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, code, 0, + (LPSTR)msg, sizeof(msg), NULL); + if (nbytes == 0) + strcpy(msg, "Unknown error"); + } +#else + explicit SocketErrorMessage(socket_error_t code=GetSocketError()) + :msg(g_strerror(code)) {} +#endif + + operator const char *() const { + return msg; + } +}; + +static inline void +SetSocketError(GError **error_r, socket_error_t code) +{ +#ifdef WIN32 + if (error_r == NULL) + return; +#endif + + const SocketErrorMessage msg(code); + g_set_error_literal(error_r, SocketErrorQuark(), code, msg); +} + +static inline void +SetSocketError(GError **error_r) +{ + SetSocketError(error_r, GetSocketError()); +} + +gcc_malloc +static inline GError * +NewSocketError(socket_error_t code) +{ + const SocketErrorMessage msg(code); + return g_error_new_literal(SocketErrorQuark(), code, msg); +} + +gcc_malloc +static inline GError * +NewSocketError() +{ + return NewSocketError(GetSocketError()); +} + +#endif diff --git a/src/socket_util.c b/src/SocketUtil.cxx index a06a0cbd5..33ea4e2fe 100644 --- a/src/socket_util.c +++ b/src/SocketUtil.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,12 @@ */ #include "config.h" -#include "socket_util.h" +#include "SocketUtil.hxx" +#include "SocketError.hxx" #include "fd_util.h" -#include <errno.h> +#include <glib.h> + #include <unistd.h> #ifndef G_OS_WIN32 @@ -35,49 +37,42 @@ #include <string.h> #endif -static GQuark -listen_quark(void) -{ - return g_quark_from_static_string("listen"); -} - int socket_bind_listen(int domain, int type, int protocol, const struct sockaddr *address, size_t address_length, int backlog, - GError **error) + GError **error_r) { int fd, ret; const int reuse = 1; fd = socket_cloexec_nonblock(domain, type, protocol); if (fd < 0) { - g_set_error(error, listen_quark(), errno, - "Failed to create socket: %s", g_strerror(errno)); + SetSocketError(error_r); + g_prefix_error(error_r, "Failed to create socket"); return -1; } ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *) &reuse, sizeof(reuse)); if (ret < 0) { - g_set_error(error, listen_quark(), errno, - "setsockopt() failed: %s", g_strerror(errno)); + SetSocketError(error_r); + g_prefix_error(error_r, "setsockopt() failed"); close_socket(fd); return -1; } ret = bind(fd, address, address_length); if (ret < 0) { - g_set_error(error, listen_quark(), errno, - "%s", g_strerror(errno)); + SetSocketError(error_r); close_socket(fd); return -1; } ret = listen(fd, backlog); if (ret < 0) { - g_set_error(error, listen_quark(), errno, - "listen() failed: %s", g_strerror(errno)); + SetSocketError(error_r); + g_prefix_error(error_r, "listen() failed"); close_socket(fd); return -1; } diff --git a/src/socket_util.h b/src/SocketUtil.hxx index 93bd27362..3d0be78c4 100644 --- a/src/socket_util.h +++ b/src/SocketUtil.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,10 +23,12 @@ * */ -#ifndef SOCKET_UTIL_H -#define SOCKET_UTIL_H +#ifndef MPD_SOCKET_UTIL_HXX +#define MPD_SOCKET_UTIL_HXX -#include <glib.h> +#include "gerror.h" + +#include <stddef.h> struct sockaddr; diff --git a/src/song.c b/src/Song.cxx index f5cc7c35a..4c820c3f8 100644 --- a/src/song.c +++ b/src/Song.cxx @@ -19,26 +19,28 @@ #include "config.h" #include "song.h" -#include "uri.h" -#include "directory.h" +#include "Directory.hxx" #include "tag.h" #include <glib.h> #include <assert.h> +Directory detached_root; + static struct song * -song_alloc(const char *uri, struct directory *parent) +song_alloc(const char *uri, Directory *parent) { size_t uri_length; - struct song *song; assert(uri); uri_length = strlen(uri); assert(uri_length); - song = g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1); - song->tag = NULL; + struct song *song = (struct song *) + g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1); + + song->tag = nullptr; memcpy(song->uri, uri, uri_length + 1); song->parent = parent; song->mtime = 0; @@ -50,13 +52,13 @@ song_alloc(const char *uri, struct directory *parent) struct song * song_remote_new(const char *uri) { - return song_alloc(uri, NULL); + return song_alloc(uri, nullptr); } struct song * -song_file_new(const char *path, struct directory *parent) +song_file_new(const char *path, Directory *parent) { - assert((parent == NULL) == (*path == '/')); + assert((parent == nullptr) == (*path == '/')); return song_alloc(path, parent); } @@ -73,6 +75,35 @@ song_replace_uri(struct song *old_song, const char *uri) return new_song; } +struct song * +song_detached_new(const char *uri) +{ + assert(uri != nullptr); + + return song_alloc(uri, &detached_root); +} + +struct song * +song_dup_detached(const struct song *src) +{ + assert(src != nullptr); + + struct song *song; + if (song_in_database(src)) { + char *uri = song_get_uri(src); + song = song_detached_new(uri); + g_free(uri); + } else + song = song_alloc(src->uri, nullptr); + + song->tag = tag_dup(src->tag); + song->mtime = src->mtime; + song->start_ms = src->start_ms; + song->end_ms = src->end_ms; + + return song; +} + void song_free(struct song *song) { @@ -81,17 +112,57 @@ song_free(struct song *song) g_free(song); } +gcc_pure +static inline bool +directory_equals(const Directory &a, const Directory &b) +{ + return strcmp(a.path, b.path) == 0; +} + +gcc_pure +static inline bool +directory_is_same(const Directory *a, const Directory *b) +{ + return a == b || + (a != nullptr && b != nullptr && + directory_equals(*a, *b)); + +} + +bool +song_equals(const struct song *a, const struct song *b) +{ + assert(a != nullptr); + assert(b != nullptr); + + if (a->parent != nullptr && b->parent != nullptr && + !directory_equals(*a->parent, *b->parent) && + (a->parent == &detached_root || b->parent == &detached_root)) { + /* must compare the full URI if one of the objects is + "detached" */ + char *au = song_get_uri(a); + char *bu = song_get_uri(b); + const bool result = strcmp(au, bu) == 0; + g_free(bu); + g_free(au); + return result; + } + + return directory_is_same(a->parent, b->parent) && + strcmp(a->uri, b->uri) == 0; +} + char * song_get_uri(const struct song *song) { - assert(song != NULL); + assert(song != nullptr); assert(*song->uri); - if (!song_in_database(song) || directory_is_root(song->parent)) + if (!song_in_database(song) || song->parent->IsRoot()) return g_strdup(song->uri); else - return g_strconcat(directory_get_path(song->parent), - "/", song->uri, NULL); + return g_strconcat(song->parent->GetPath(), + "/", song->uri, nullptr); } double @@ -100,7 +171,7 @@ song_get_duration(const struct song *song) if (song->end_ms > 0) return (song->end_ms - song->start_ms) / 1000.0; - if (song->tag == NULL) + if (song->tag == nullptr) return 0; return song->tag->time - song->start_ms / 1000.0; diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx new file mode 100644 index 000000000..6d6841675 --- /dev/null +++ b/src/SongFilter.cxx @@ -0,0 +1,167 @@ +/* + * 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" +#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..2b74d9354 100644 --- a/src/song_save.c +++ b/src/SongSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,16 @@ */ #include "config.h" -#include "song_save.h" +#include "SongSave.hxx" #include "song.h" -#include "tag_save.h" -#include "directory.h" +#include "TagSave.hxx" +#include "Directory.hxx" +#include "TextFile.hxx" #include "tag.h" -#include "text_file.h" + +extern "C" { #include "string_util.h" +} #include <glib.h> @@ -60,8 +63,8 @@ song_save(FILE *fp, const struct song *song) } struct song * -song_load(FILE *fp, struct directory *parent, const char *uri, - GString *buffer, GError **error_r) +song_load(TextFile &file, Directory *parent, const char *uri, + GError **error_r) { struct song *song = parent != NULL ? song_file_new(uri, parent) @@ -70,7 +73,7 @@ song_load(FILE *fp, struct directory *parent, const char *uri, enum tag_type type; const char *value; - while ((line = read_text_line(fp, buffer)) != NULL && + while ((line = file.ReadLine()) != NULL && strcmp(line, SONG_END) != 0) { colon = strchr(line, ':'); if (colon == NULL || colon == line) { diff --git a/src/song_save.h b/src/SongSave.hxx index 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..b96c7c43e 100644 --- a/src/song_update.c +++ b/src/SongUpdate.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,17 +18,24 @@ */ #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" +#include "tag.h" +#include "input_stream.h" + +extern "C" { #include "decoder_list.h" #include "decoder_plugin.h" #include "tag_ape.h" #include "tag_id3.h" -#include "tag.h" #include "tag_handler.h" -#include "input_stream.h" +} #include <glib.h> @@ -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/StateFile.cxx b/src/StateFile.cxx new file mode 100644 index 000000000..b78056520 --- /dev/null +++ b/src/StateFile.cxx @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "StateFile.hxx" +#include "OutputState.hxx" +#include "PlaylistState.hxx" +#include "TextFile.hxx" +#include "Partition.hxx" +#include "Volume.hxx" +#include "event/Loop.hxx" + +#include <glib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "state_file" + +StateFile::StateFile(const char *_path, Partition &_partition, EventLoop &_loop) + :TimeoutMonitor(_loop), path(_path), partition(_partition), + prev_volume_version(0), prev_output_version(0), + prev_playlist_version(0) +{ + ScheduleSeconds(5 * 60); +} + +void +StateFile::Write() +{ + g_debug("Saving state file %s", path.c_str()); + + FILE *fp = fopen(path.c_str(), "w"); + if (G_UNLIKELY(!fp)) { + g_warning("failed to create %s: %s", + path.c_str(), g_strerror(errno)); + return; + } + + save_sw_volume_state(fp); + audio_output_state_save(fp); + playlist_state_save(fp, &partition.playlist, &partition.pc); + + fclose(fp); + + prev_volume_version = sw_volume_state_get_hash(); + prev_output_version = audio_output_state_get_version(); + prev_playlist_version = playlist_state_get_hash(&partition.playlist, + &partition.pc); +} + +void +StateFile::Read() +{ + bool success; + + g_debug("Loading state file %s", path.c_str()); + + TextFile file(path.c_str()); + if (file.HasFailed()) { + g_warning("failed to open %s: %s", + path.c_str(), g_strerror(errno)); + return; + } + + const char *line; + while ((line = file.ReadLine()) != NULL) { + success = read_sw_volume_state(line) || + audio_output_state_read(line) || + playlist_state_restore(line, file, &partition.playlist, + &partition.pc); + if (!success) + g_warning("Unrecognized line in state file: %s", line); + } + + prev_volume_version = sw_volume_state_get_hash(); + prev_output_version = audio_output_state_get_version(); + prev_playlist_version = playlist_state_get_hash(&partition.playlist, + &partition.pc); +} + +inline void +StateFile::AutoWrite() +{ + if (prev_volume_version == sw_volume_state_get_hash() && + prev_output_version == audio_output_state_get_version() && + prev_playlist_version == playlist_state_get_hash(&partition.playlist, + &partition.pc)) + /* nothing has changed - don't save the state file, + don't spin up the hard disk */ + return; + + Write(); +} + +/** + * This function is called every 5 minutes by the GLib main loop, and + * saves the state file. + */ +bool +StateFile::OnTimeout() +{ + AutoWrite(); + return true; +} diff --git a/src/StateFile.hxx b/src/StateFile.hxx new file mode 100644 index 000000000..39c3fcdf6 --- /dev/null +++ b/src/StateFile.hxx @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_STATE_FILE_HXX +#define MPD_STATE_FILE_HXX + +#include "event/TimeoutMonitor.hxx" +#include "gcc.h" + +#include <string> + +struct Partition; + +class StateFile final : private TimeoutMonitor { + std::string path; + + Partition &partition; + + /** + * These version numbers determine whether we need to save the state + * file. If nothing has changed, we won't let the hard drive spin up. + */ + unsigned prev_volume_version, prev_output_version, + prev_playlist_version; + +public: + StateFile(const char *path, Partition &partition, EventLoop &loop); + + void Read(); + void Write(); + void AutoWrite(); + +private: + virtual bool OnTimeout() override; +}; + +#endif /* STATE_FILE_H */ diff --git a/src/Stats.cxx b/src/Stats.cxx new file mode 100644 index 000000000..f848a9b03 --- /dev/null +++ b/src/Stats.cxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +extern "C" { +#include "stats.h" +} + +#include "PlayerControl.hxx" +#include "ClientInternal.hxx" +#include "DatabaseSelection.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseSimple.hxx" + +struct stats stats; + +void stats_global_init(void) +{ + stats.timer = g_timer_new(); +} + +void stats_global_finish(void) +{ + g_timer_destroy(stats.timer); +} + +void stats_update(void) +{ + GError *error = nullptr; + + DatabaseStats stats2; + + const DatabaseSelection selection("", true); + if (GetDatabase()->GetStats(selection, stats2, &error)) { + stats.song_count = stats2.song_count; + stats.song_duration = stats2.total_duration; + stats.artist_count = stats2.artist_count; + stats.album_count = stats2.album_count; + } else { + g_warning("%s", error->message); + g_error_free(error); + + stats.song_count = 0; + stats.song_duration = 0; + stats.artist_count = 0; + stats.album_count = 0; + } +} + +void +stats_print(Client *client) +{ + client_printf(client, + "artists: %u\n" + "albums: %u\n" + "songs: %i\n" + "uptime: %li\n" + "playtime: %li\n" + "db_playtime: %li\n", + stats.artist_count, + stats.album_count, + stats.song_count, + (long)g_timer_elapsed(stats.timer, NULL), + (long)(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..2d77e4b63 100644 --- a/src/sticker.c +++ b/src/StickerDatabase.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,11 @@ */ #include "config.h" -#include "sticker.h" -#include "idle.h" +#include "StickerDatabase.hxx" +#include "Idle.hxx" + +#include <string> +#include <map> #include <glib.h> #include <sqlite3.h> @@ -33,7 +36,7 @@ #endif struct sticker { - GHashTable *table; + std::map<std::string, std::string> table; }; enum sticker_sql { @@ -47,19 +50,19 @@ enum sticker_sql { }; static const char *const sticker_sql[] = { - [STICKER_SQL_GET] = + //[STICKER_SQL_GET] = "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_LIST] = + //[STICKER_SQL_LIST] = "SELECT name,value FROM sticker WHERE type=? AND uri=?", - [STICKER_SQL_UPDATE] = + //[STICKER_SQL_UPDATE] = "UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_INSERT] = + //[STICKER_SQL_INSERT] = "INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)", - [STICKER_SQL_DELETE] = + //[STICKER_SQL_DELETE] = "DELETE FROM sticker WHERE type=? AND uri=?", - [STICKER_SQL_DELETE_VALUE] = + //[STICKER_SQL_DELETE_VALUE] = "DELETE FROM sticker WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_FIND] = + //[STICKER_SQL_FIND] = "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?", }; @@ -226,13 +229,12 @@ sticker_load_value(const char *type, const char *uri, const char *name) } static bool -sticker_list_values(GHashTable *hash, const char *type, const char *uri) +sticker_list_values(std::map<std::string, std::string> &table, + const char *type, const char *uri) { sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_LIST]; int ret; - char *name, *value; - assert(hash != NULL); assert(type != NULL); assert(uri != NULL); assert(sticker_enabled()); @@ -256,10 +258,13 @@ sticker_list_values(GHashTable *hash, const char *type, const char *uri) do { ret = sqlite3_step(stmt); switch (ret) { + const char *name, *value; + case SQLITE_ROW: - name = g_strdup((const char*)sqlite3_column_text(stmt, 0)); - value = g_strdup((const char*)sqlite3_column_text(stmt, 1)); - g_hash_table_insert(hash, name, value); + name = (const char*)sqlite3_column_text(stmt, 0); + value = (const char*)sqlite3_column_text(stmt, 1); + + table.insert(std::make_pair(name, value)); break; case SQLITE_DONE: break; @@ -518,44 +523,20 @@ sticker_delete_value(const char *type, const char *uri, const char *name) return ret > 0; } -static struct sticker * -sticker_new(void) -{ - struct sticker *sticker = g_new(struct sticker, 1); - - sticker->table = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_free); - return sticker; -} - void sticker_free(struct sticker *sticker) { - assert(sticker != NULL); - assert(sticker->table != NULL); - - g_hash_table_destroy(sticker->table); - g_free(sticker); + delete sticker; } const char * sticker_get_value(const struct sticker *sticker, const char *name) { - return g_hash_table_lookup(sticker->table, name); -} - -struct sticker_foreach_data { - void (*func)(const char *name, const char *value, - gpointer user_data); - gpointer user_data; -}; - -static void -sticker_foreach_func(gpointer key, gpointer value, gpointer user_data) -{ - struct sticker_foreach_data *data = user_data; + auto i = sticker->table.find(name); + if (i == sticker->table.end()) + return nullptr; - data->func(key, value, data->user_data); + return i->second.c_str(); } void @@ -564,33 +545,23 @@ sticker_foreach(const struct sticker *sticker, gpointer user_data), gpointer user_data) { - struct sticker_foreach_data data = { - .func = func, - .user_data = user_data, - }; - - g_hash_table_foreach(sticker->table, sticker_foreach_func, &data); + for (const auto &i : sticker->table) + func(i.first.c_str(), i.second.c_str(), user_data); } struct sticker * sticker_load(const char *type, const char *uri) { - struct sticker *sticker = sticker_new(); - bool success; + sticker s; - success = sticker_list_values(sticker->table, type, uri); - if (!success) { - sticker_free(sticker); + if (!sticker_list_values(s.table, type, uri)) return NULL; - } - if (g_hash_table_size(sticker->table) == 0) { + if (s.table.empty()) /* don't return empty sticker objects */ - sticker_free(sticker); return NULL; - } - return sticker; + return new sticker(std::move(s)); } bool diff --git a/src/sticker.h b/src/StickerDatabase.hxx index 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.c b/src/Tag.cxx index c0faa7ab2..afdeb0558 100644 --- a/src/tag.c +++ b/src/Tag.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,8 +19,8 @@ #include "config.h" #include "tag.h" -#include "tag_internal.h" -#include "tag_pool.h" +#include "TagInternal.hxx" +#include "TagPool.hxx" #include "conf.h" #include "song.h" #include "mpd_error.h" @@ -43,38 +43,15 @@ static struct { struct tag_item *items[BULK_MAX]; } bulk; -const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = { - [TAG_ARTIST] = "Artist", - [TAG_ARTIST_SORT] = "ArtistSort", - [TAG_ALBUM] = "Album", - [TAG_ALBUM_ARTIST] = "AlbumArtist", - [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort", - [TAG_TITLE] = "Title", - [TAG_TRACK] = "Track", - [TAG_NAME] = "Name", - [TAG_GENRE] = "Genre", - [TAG_DATE] = "Date", - [TAG_COMPOSER] = "Composer", - [TAG_PERFORMER] = "Performer", - [TAG_COMMENT] = "Comment", - [TAG_DISC] = "Disc", - - /* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */ - [TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID", - [TAG_MUSICBRAINZ_ALBUMID] = "MUSICBRAINZ_ALBUMID", - [TAG_MUSICBRAINZ_ALBUMARTISTID] = "MUSICBRAINZ_ALBUMARTISTID", - [TAG_MUSICBRAINZ_TRACKID] = "MUSICBRAINZ_TRACKID", -}; - bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; enum tag_type tag_name_parse(const char *name) { - assert(name != NULL); + assert(name != nullptr); for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - assert(tag_item_names[i] != NULL); + assert(tag_item_names[i] != nullptr); if (strcmp(name, tag_item_names[i]) == 0) return (enum tag_type)i; @@ -86,10 +63,10 @@ tag_name_parse(const char *name) enum tag_type tag_name_parse_i(const char *name) { - assert(name != NULL); + assert(name != nullptr); for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - assert(tag_item_names[i] != NULL); + assert(tag_item_names[i] != nullptr); if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0) return (enum tag_type)i; @@ -117,8 +94,8 @@ void tag_lib_init(void) /* ignore comments by default */ ignore_tag_items[TAG_COMMENT] = true; - value = config_get_string(CONF_METADATA_TO_USE, NULL); - if (value == NULL) + value = config_get_string(CONF_METADATA_TO_USE, nullptr); + if (value == nullptr) return; memset(ignore_tag_items, true, TAG_NUM_OF_ITEM_TYPES); @@ -156,7 +133,7 @@ void tag_lib_init(void) struct tag *tag_new(void) { struct tag *ret = g_new(struct tag, 1); - ret->items = NULL; + ret->items = nullptr; ret->time = -1; ret->has_playlist = false; ret->num_items = 0; @@ -168,9 +145,9 @@ static void tag_delete_item(struct tag *tag, unsigned idx) assert(idx < tag->num_items); tag->num_items--; - g_mutex_lock(tag_pool_lock); + tag_pool_lock.lock(); tag_pool_put_item(tag->items[idx]); - g_mutex_unlock(tag_pool_lock); + tag_pool_lock.unlock(); if (tag->num_items - idx > 0) { memmove(tag->items + idx, tag->items + idx + 1, @@ -178,10 +155,11 @@ static void tag_delete_item(struct tag *tag, unsigned idx) } if (tag->num_items > 0) { - tag->items = g_realloc(tag->items, items_size(tag)); + tag->items = (struct tag_item **) + g_realloc(tag->items, items_size(tag)); } else { g_free(tag->items); - tag->items = NULL; + tag->items = nullptr; } } @@ -200,12 +178,12 @@ void tag_free(struct tag *tag) { int i; - assert(tag != NULL); + assert(tag != nullptr); - g_mutex_lock(tag_pool_lock); + tag_pool_lock.lock(); for (i = tag->num_items; --i >= 0; ) tag_pool_put_item(tag->items[i]); - g_mutex_unlock(tag_pool_lock); + tag_pool_lock.unlock(); if (tag->items == bulk.items) { #ifndef NDEBUG @@ -223,18 +201,20 @@ struct tag *tag_dup(const struct tag *tag) struct tag *ret; if (!tag) - return NULL; + return nullptr; ret = tag_new(); ret->time = tag->time; ret->has_playlist = tag->has_playlist; ret->num_items = tag->num_items; - ret->items = ret->num_items > 0 ? g_malloc(items_size(tag)) : NULL; + ret->items = ret->num_items > 0 + ? (struct tag_item **)g_malloc(items_size(tag)) + : nullptr; - g_mutex_lock(tag_pool_lock); + tag_pool_lock.lock(); for (unsigned i = 0; i < tag->num_items; i++) ret->items[i] = tag_pool_dup_item(tag->items[i]); - g_mutex_unlock(tag_pool_lock); + tag_pool_lock.unlock(); return ret; } @@ -245,17 +225,19 @@ tag_merge(const struct tag *base, const struct tag *add) struct tag *ret; unsigned n; - assert(base != NULL); - assert(add != NULL); + assert(base != nullptr); + assert(add != nullptr); /* allocate new tag object */ ret = tag_new(); ret->time = add->time > 0 ? add->time : base->time; ret->num_items = base->num_items + add->num_items; - ret->items = ret->num_items > 0 ? g_malloc(items_size(ret)) : NULL; + ret->items = ret->num_items > 0 + ? (struct tag_item **)g_malloc(items_size(ret)) + : nullptr; - g_mutex_lock(tag_pool_lock); + tag_pool_lock.lock(); /* copy all items from "add" */ @@ -270,7 +252,7 @@ tag_merge(const struct tag *base, const struct tag *add) if (!tag_has_type(add, base->items[i]->type)) ret->items[n++] = tag_pool_dup_item(base->items[i]); - g_mutex_unlock(tag_pool_lock); + tag_pool_lock.unlock(); assert(n <= ret->num_items); @@ -279,7 +261,8 @@ tag_merge(const struct tag *base, const struct tag *add) assert(n > 0); ret->num_items = n; - ret->items = g_realloc(ret->items, items_size(ret)); + ret->items = (struct tag_item **) + g_realloc(ret->items, items_size(ret)); } return ret; @@ -288,10 +271,10 @@ tag_merge(const struct tag *base, const struct tag *add) struct tag * tag_merge_replace(struct tag *base, struct tag *add) { - if (add == NULL) + if (add == nullptr) return base; - if (base == NULL) + if (base == nullptr) return add; struct tag *tag = tag_merge(base, add); @@ -304,24 +287,24 @@ tag_merge_replace(struct tag *base, struct tag *add) const char * tag_get_value(const struct tag *tag, enum tag_type type) { - assert(tag != NULL); + assert(tag != nullptr); assert(type < TAG_NUM_OF_ITEM_TYPES); for (unsigned i = 0; i < tag->num_items; i++) if (tag->items[i]->type == type) return tag->items[i]->value; - return NULL; + return nullptr; } bool tag_has_type(const struct tag *tag, enum tag_type type) { - return tag_get_value(tag, type) != NULL; + return tag_get_value(tag, type) != nullptr; } bool tag_equal(const struct tag *tag1, const struct tag *tag2) { - if (tag1 == NULL && tag2 == NULL) + if (tag1 == nullptr && tag2 == nullptr) return true; else if (!tag1 || !tag2) return false; @@ -367,16 +350,16 @@ fix_utf8(const char *str, size_t length) char *temp; gsize written; - assert(str != NULL); + assert(str != nullptr); /* check if the string is already valid UTF-8 */ if (g_utf8_validate(str, length, &end)) - return NULL; + return nullptr; /* no, it's not - try to import it from ISO-Latin-1 */ temp = g_convert(str, length, "utf-8", "iso-8859-1", - NULL, &written, NULL); - if (temp != NULL) + nullptr, &written, nullptr); + if (temp != nullptr) /* success! */ return temp; @@ -388,8 +371,8 @@ fix_utf8(const char *str, size_t length) void tag_begin_add(struct tag *tag) { assert(!bulk.busy); - assert(tag != NULL); - assert(tag->items == NULL); + assert(tag != nullptr); + assert(tag->items == nullptr); assert(tag->num_items == 0); #ifndef NDEBUG @@ -406,10 +389,11 @@ void tag_end_add(struct tag *tag) if (tag->num_items > 0) { /* copy the tag items from the bulk list over to a new list (which fits exactly) */ - tag->items = g_malloc(items_size(tag)); + tag->items = (struct tag_item **) + g_malloc(items_size(tag)); memcpy(tag->items, bulk.items, items_size(tag)); } else - tag->items = NULL; + tag->items = nullptr; } #ifndef NDEBUG @@ -430,12 +414,12 @@ find_non_printable(const char *p, size_t length) if (char_is_non_printable(p[i])) return p + i; - return NULL; + return nullptr; } /** * Clears all non-printable characters, convert them to space. - * Returns NULL if nothing needs to be cleared. + * Returns nullptr if nothing needs to be cleared. */ static char * clear_non_printable(const char *p, size_t length) @@ -443,8 +427,8 @@ clear_non_printable(const char *p, size_t length) const char *first = find_non_printable(p, length); char *dest; - if (first == NULL) - return NULL; + if (first == nullptr) + return nullptr; dest = g_strndup(p, length); @@ -461,13 +445,13 @@ fix_tag_value(const char *p, size_t length) char *utf8, *cleared; utf8 = fix_utf8(p, length); - if (utf8 != NULL) { + if (utf8 != nullptr) { p = utf8; length = strlen(p); } cleared = clear_non_printable(p, length); - if (cleared == NULL) + if (cleared == nullptr) cleared = utf8; else g_free(utf8); @@ -483,7 +467,7 @@ tag_add_item_internal(struct tag *tag, enum tag_type type, char *p; p = fix_tag_value(value, len); - if (p != NULL) { + if (p != nullptr) { value = p; len = strlen(value); } @@ -492,19 +476,20 @@ tag_add_item_internal(struct tag *tag, enum tag_type type, if (tag->items != bulk.items) /* bulk mode disabled */ - tag->items = g_realloc(tag->items, items_size(tag)); + tag->items = (struct tag_item **) + g_realloc(tag->items, items_size(tag)); else if (tag->num_items >= BULK_MAX) { /* bulk list already full - switch back to non-bulk */ assert(bulk.busy); - tag->items = g_malloc(items_size(tag)); + tag->items = (struct tag_item **)g_malloc(items_size(tag)); memcpy(tag->items, bulk.items, items_size(tag) - sizeof(struct tag_item *)); } - g_mutex_lock(tag_pool_lock); + tag_pool_lock.lock(); tag->items[i] = tag_pool_get_item(type, value, len); - g_mutex_unlock(tag_pool_lock); + tag_pool_lock.unlock(); g_free(p); } diff --git a/src/tag_internal.h b/src/TagInternal.hxx index af05cc6b6..afce0427e 100644 --- a/src/tag_internal.h +++ b/src/TagInternal.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_TAG_INTERNAL_H -#define MPD_TAG_INTERNAL_H +#ifndef MPD_TAG_INTERNAL_HXX +#define MPD_TAG_INTERNAL_HXX #include "tag.h" -#include <stdbool.h> - extern bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; #endif diff --git a/src/TagNames.c b/src/TagNames.c new file mode 100644 index 000000000..eca320afb --- /dev/null +++ b/src/TagNames.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "tag.h" + +const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = { + [TAG_ARTIST] = "Artist", + [TAG_ARTIST_SORT] = "ArtistSort", + [TAG_ALBUM] = "Album", + [TAG_ALBUM_ARTIST] = "AlbumArtist", + [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort", + [TAG_TITLE] = "Title", + [TAG_TRACK] = "Track", + [TAG_NAME] = "Name", + [TAG_GENRE] = "Genre", + [TAG_DATE] = "Date", + [TAG_COMPOSER] = "Composer", + [TAG_PERFORMER] = "Performer", + [TAG_COMMENT] = "Comment", + [TAG_DISC] = "Disc", + + /* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */ + [TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID", + [TAG_MUSICBRAINZ_ALBUMID] = "MUSICBRAINZ_ALBUMID", + [TAG_MUSICBRAINZ_ALBUMARTISTID] = "MUSICBRAINZ_ALBUMARTISTID", + [TAG_MUSICBRAINZ_TRACKID] = "MUSICBRAINZ_TRACKID", +}; diff --git a/src/tag_pool.c b/src/TagPool.cxx index eabf3e369..f529cda69 100644 --- a/src/tag_pool.c +++ b/src/TagPool.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,11 @@ */ #include "config.h" -#include "tag_pool.h" +#include "TagPool.hxx" #include <assert.h> -GMutex *tag_pool_lock = NULL; +Mutex tag_pool_lock; #define NUM_SLOTS 4096 @@ -39,7 +39,7 @@ calc_hash_n(enum tag_type type, const char *p, size_t length) { unsigned hash = 5381; - assert(p != NULL); + assert(p != nullptr); while (length-- > 0) hash = (hash << 5) + hash + *p++; @@ -52,7 +52,7 @@ calc_hash(enum tag_type type, const char *p) { unsigned hash = 5381; - assert(p != NULL); + assert(p != nullptr); while (*p != 0) hash = (hash << 5) + hash + *p++; @@ -72,7 +72,8 @@ static struct slot *slot_alloc(struct slot *next, { struct slot *slot; - slot = g_malloc(sizeof(*slot) - sizeof(slot->item.value) + length + 1); + slot = (struct slot *) + g_malloc(sizeof(*slot) - sizeof(slot->item.value) + length + 1); slot->next = next; slot->ref = 1; slot->item.type = type; @@ -81,26 +82,13 @@ static struct slot *slot_alloc(struct slot *next, return slot; } -void tag_pool_init(void) -{ - g_assert(tag_pool_lock == NULL); - tag_pool_lock = g_mutex_new(); -} - -void tag_pool_deinit(void) -{ - g_assert(tag_pool_lock != NULL); - g_mutex_free(tag_pool_lock); - tag_pool_lock = NULL; -} - struct tag_item * tag_pool_get_item(enum tag_type type, const char *value, size_t length) { struct slot **slot_p, *slot; slot_p = &slots[calc_hash_n(type, value, length) % NUM_SLOTS]; - for (slot = *slot_p; slot != NULL; slot = slot->next) { + for (slot = *slot_p; slot != nullptr; slot = slot->next) { if (slot->item.type == type && length == strlen(slot->item.value) && memcmp(value, slot->item.value, length) == 0 && @@ -153,7 +141,7 @@ void tag_pool_put_item(struct tag_item *item) for (slot_p = &slots[calc_hash(item->type, item->value) % NUM_SLOTS]; *slot_p != slot; slot_p = &(*slot_p)->next) { - assert(*slot_p != NULL); + assert(*slot_p != nullptr); } *slot_p = slot->next; diff --git a/src/tag_pool.h b/src/TagPool.hxx index a96c00d85..5d68a31d7 100644 --- a/src/tag_pool.h +++ b/src/TagPool.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,21 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_TAG_POOL_H -#define MPD_TAG_POOL_H +#ifndef MPD_TAG_POOL_HXX +#define MPD_TAG_POOL_HXX #include "tag.h" +#include "thread/Mutex.hxx" #include <glib.h> -extern GMutex *tag_pool_lock; +extern Mutex tag_pool_lock; struct tag_item; -void tag_pool_init(void); - -void tag_pool_deinit(void); - struct tag_item * tag_pool_get_item(enum tag_type type, const char *value, size_t length); diff --git a/src/tag_print.c b/src/TagPrint.cxx index 9a46b247a..b3ad07df4 100644 --- a/src/tag_print.c +++ b/src/TagPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,13 @@ */ #include "config.h" -#include "tag_print.h" +#include "TagPrint.hxx" #include "tag.h" -#include "tag_internal.h" -#include "client.h" +#include "TagInternal.hxx" #include "song.h" +#include "Client.hxx" -void tag_print_types(struct client *client) +void tag_print_types(Client *client) { int i; @@ -35,7 +35,7 @@ void tag_print_types(struct client *client) } } -void tag_print(struct client *client, const struct tag *tag) +void tag_print(Client *client, const struct tag *tag) { if (tag->time >= 0) client_printf(client, SONG_TIME "%i\n", tag->time); diff --git a/src/tag_print.h b/src/TagPrint.hxx index 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..15da9fc4b 100644 --- a/src/tag_save.c +++ b/src/TagSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,9 @@ */ #include "config.h" -#include "tag_save.h" +#include "TagSave.hxx" #include "tag.h" -#include "tag_internal.h" +#include "TagInternal.hxx" #include "song.h" void tag_save(FILE *file, const struct tag *tag) diff --git a/src/tag_save.h b/src/TagSave.hxx index 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..daa7f1ec4 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,18 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_container.h" -#include "update_internal.h" -#include "update_db.h" -#include "db_lock.h" -#include "directory.h" +#include "UpdateContainer.hxx" +#include "UpdateInternal.hxx" +#include "UpdateDatabase.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" #include "song.h" -#include "mapper.h" #include "decoder_plugin.h" -#include "tag.h" +#include "Mapper.hxx" + +extern "C" { #include "tag_handler.h" +} #include <glib.h> @@ -39,11 +41,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 +59,13 @@ make_directory_if_modified(struct directory *parent, const char *name, modified = true; } - directory = directory_make_child(parent, name); + directory = parent->MakeChild(name); directory->mtime = st->st_mtime; return directory; } bool -update_container_file(struct directory *directory, +update_container_file(Directory *directory, const char *name, const struct stat *st, const struct decoder_plugin *plugin) @@ -72,8 +74,7 @@ update_container_file(struct directory *directory, return false; db_lock(); - struct directory *contdir = - make_directory_if_modified(directory, name, st); + Directory *contdir = make_directory_if_modified(directory, name, st); if (contdir == NULL) { /* not modified */ db_unlock(); @@ -101,13 +102,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..22a33ade9 100644 --- a/src/update.c +++ b/src/UpdateGlue.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,19 +18,21 @@ */ #include "config.h" -#include "update.h" -#include "update_queue.h" -#include "update_walk.h" -#include "update_remove.h" -#include "update.h" -#include "database.h" -#include "mapper.h" -#include "playlist.h" -#include "event_pipe.h" -#include "update.h" -#include "idle.h" +#include "UpdateGlue.hxx" +#include "UpdateQueue.hxx" +#include "UpdateWalk.hxx" +#include "UpdateRemove.hxx" +#include "Mapper.hxx" +#include "DatabaseSimple.hxx" +#include "Idle.hxx" +#include "GlobalEvents.hxx" + +extern "C" { #include "stats.h" -#include "main.h" +} + +#include "Main.hxx" +#include "Partition.hxx" #include "mpd_error.h" #include <glib.h> @@ -65,7 +67,7 @@ isUpdatingDB(void) static void * update_task(void *_path) { - const char *path = _path; + const char *path = (const char *)_path; if (path != NULL && *path != 0) g_debug("starting: %s", path); @@ -90,7 +92,7 @@ static void * update_task(void *_path) g_free(_path); progress = UPDATE_PROGRESS_DONE; - event_pipe_emit(PIPE_EVENT_UPDATE); + GlobalEvents::Emit(GlobalEvents::UPDATE); return NULL; } @@ -118,7 +120,7 @@ update_enqueue(const char *path, bool _discard) { assert(g_thread_self() == main_task); - if (!mapper_has_music_directory()) + if (!db_is_simple() || !mapper_has_music_directory()) return 0; if (progress != UPDATE_PROGRESS_IDLE) { @@ -153,7 +155,7 @@ static void update_finished_event(void) if (modified) { /* send "idle" events */ - playlist_increment_version_all(&g_playlist); + global_partition->playlist.FullIncrementVersions(); idle_add(IDLE_DATABASE); } @@ -171,7 +173,7 @@ static void update_finished_event(void) void update_global_init(void) { - event_pipe_register(PIPE_EVENT_UPDATE, update_finished_event); + GlobalEvents::Register(GlobalEvents::UPDATE, update_finished_event); update_remove_global_init(); update_walk_global_init(); 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..f7e6c80ec 100644 --- a/src/update_remove.c +++ b/src/UpdateRemove.cxx @@ -18,15 +18,17 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_remove.h" -#include "event_pipe.h" +#include "UpdateRemove.hxx" +#include "Playlist.hxx" +#include "Partition.hxx" +#include "GlobalEvents.hxx" + #include "song.h" -#include "playlist.h" -#include "main.h" +#include "Main.hxx" #ifdef ENABLE_SQLITE -#include "sticker.h" -#include "song_sticker.h" +#include "StickerDatabase.hxx" +#include "SongSticker.hxx" #endif #include <glib.h> @@ -59,7 +61,7 @@ song_remove_event(void) sticker_song_delete(removed_song); #endif - playlist_delete_song(&g_playlist, global_player_control, removed_song); + global_partition->DeleteSong(*removed_song); /* clear "removed_song" and send signal to update thread */ g_mutex_lock(remove_mutex); @@ -74,7 +76,7 @@ update_remove_global_init(void) remove_mutex = g_mutex_new(); remove_cond = g_cond_new(); - event_pipe_register(PIPE_EVENT_DELETE, song_remove_event); + GlobalEvents::Register(GlobalEvents::DELETE, song_remove_event); } void @@ -91,7 +93,7 @@ update_remove_song(const struct song *song) removed_song = song; - event_pipe_emit(PIPE_EVENT_DELETE); + GlobalEvents::Emit(GlobalEvents::DELETE); g_mutex_lock(remove_mutex); 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..208e6290a 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" +#include "conf.h" + +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/volume.c b/src/Volume.cxx index d3ce47dd4..116f4aa18 100644 --- a/src/volume.c +++ b/src/Volume.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,21 +18,14 @@ */ #include "config.h" -#include "volume.h" -#include "conf.h" -#include "idle.h" -#include "pcm_volume.h" -#include "output_all.h" -#include "mixer_control.h" -#include "mixer_all.h" -#include "mixer_type.h" -#include "event_pipe.h" +#include "Volume.hxx" +#include "MixerAll.hxx" +#include "Idle.hxx" +#include "GlobalEvents.hxx" #include <glib.h> #include <assert.h> -#include <math.h> -#include <string.h> #include <stdlib.h> #undef G_LOG_DOMAIN @@ -48,7 +41,7 @@ static int last_hardware_volume = -1; static GTimer *hardware_volume_timer; /** - * Handler for #PIPE_EVENT_MIXER. + * Handler for #GlobalEvents::MIXER. */ static void mixer_event_callback(void) @@ -69,7 +62,7 @@ void volume_init(void) { hardware_volume_timer = g_timer_new(); - event_pipe_register(PIPE_EVENT_MIXER, mixer_event_callback); + GlobalEvents::Register(GlobalEvents::MIXER, mixer_event_callback); } int volume_level_get(void) diff --git a/src/volume.h b/src/Volume.hxx index b08899a84..024b2840a 100644 --- a/src/volume.h +++ b/src/Volume.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_VOLUME_H -#define MPD_VOLUME_H +#ifndef MPD_VOLUME_HXX +#define MPD_VOLUME_HXX -#include <stdbool.h> #include <stdio.h> void volume_init(void); diff --git a/src/main_win32.c b/src/Win32Main.cxx index aac7ad886..0cd5e445b 100644 --- a/src/main_win32.c +++ b/src/Win32Main.cxx @@ -18,12 +18,15 @@ */ #include "config.h" -#include "main.h" +#include "Main.hxx" #ifdef WIN32 #include "mpd_error.h" -#include "event_pipe.h" +#include "GlobalEvents.hxx" + +#include <cstdlib> +#include <atomic> #include <glib.h> @@ -32,7 +35,7 @@ static int service_argc; static char **service_argv; static char service_name[] = ""; -static BOOL ignore_console_events; +static std::atomic_bool running; static SERVICE_STATUS_HANDLE service_handle; static void WINAPI @@ -68,7 +71,7 @@ service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type, switch (control) { case SERVICE_CONTROL_SHUTDOWN: case SERVICE_CONTROL_STOP: - event_pipe_emit(PIPE_EVENT_SHUTDOWN); + GlobalEvents::Emit(GlobalEvents::SHUTDOWN); return NO_ERROR; default: return NO_ERROR; @@ -103,8 +106,22 @@ console_handler(DWORD event) switch (event) { case CTRL_C_EVENT: case CTRL_CLOSE_EVENT: - if (!ignore_console_events) - event_pipe_emit(PIPE_EVENT_SHUTDOWN); + if (running.load()) { + // Recent msdn docs that process is terminated + // if this function returns TRUE. + // We initiate correct shutdown sequence (if possible). + // Once main() returns CRT will terminate our process + // regardless our thread is still active. + // If this did not happen within 3 seconds + // let's shutdown anyway. + GlobalEvents::Emit(GlobalEvents::SHUTDOWN); + // Under debugger it's better to wait indefinitely + // to allow debugging of shutdown code. + Sleep(IsDebuggerPresent() ? INFINITE : 3000); + } + // If we're not running main loop there is no chance for + // clean shutdown. + std::exit(EXIT_FAILURE); return TRUE; default: return FALSE; @@ -125,8 +142,8 @@ int win32_main(int argc, char *argv[]) error_code = GetLastError(); if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) { /* running as console app */ + running.store(false); SetConsoleTitle("Music Player Daemon"); - ignore_console_events = TRUE; SetConsoleCtrlHandler(console_handler, TRUE); return mpd_main(argc, argv); } @@ -140,7 +157,7 @@ void win32_app_started() if (service_handle != 0) service_notify_status(SERVICE_RUNNING); else - ignore_console_events = FALSE; + running.store(true); } void win32_app_stopping() @@ -148,7 +165,7 @@ void win32_app_stopping() if (service_handle != 0) service_notify_status(SERVICE_STOP_PENDING); else - ignore_console_events = TRUE; + running.store(false); } #endif diff --git a/src/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_config.c b/src/audio_config.c index 72869c384..a32f43733 100644 --- a/src/audio_config.c +++ b/src/audio_config.c @@ -23,7 +23,6 @@ #include "audio_parser.h" #include "output_internal.h" #include "output_plugin.h" -#include "output_all.h" #include "conf.h" #include "mpd_error.h" diff --git a/src/audio_format.h b/src/audio_format.h index bf77add3b..f9b176bc1 100644 --- a/src/audio_format.h +++ b/src/audio_format.h @@ -20,7 +20,8 @@ #ifndef MPD_AUDIO_FORMAT_H #define MPD_AUDIO_FORMAT_H -#include <glib.h> +#include "gcc.h" + #include <stdint.h> #include <stdbool.h> #include <assert.h> @@ -189,7 +190,7 @@ audio_valid_channel_count(unsigned channels) * Returns false if the format is not valid for playback with MPD. * This function performs some basic validity checks. */ -G_GNUC_PURE +gcc_pure static inline bool audio_format_valid(const struct audio_format *af) { return audio_valid_sample_rate(af->sample_rate) && @@ -201,7 +202,7 @@ static inline bool audio_format_valid(const struct audio_format *af) * Returns false if the format mask is not valid for playback with * MPD. This function performs some basic validity checks. */ -G_GNUC_PURE +gcc_pure static inline bool audio_format_mask_valid(const struct audio_format *af) { return (af->sample_rate == 0 || @@ -219,11 +220,15 @@ static inline bool audio_format_equals(const struct audio_format *a, a->channels == b->channels; } +#ifdef __cplusplus +extern "C" { +#endif + void audio_format_mask_apply(struct audio_format *af, const struct audio_format *mask); -G_GNUC_CONST +gcc_const static inline unsigned sample_format_size(enum sample_format format) { @@ -254,7 +259,7 @@ sample_format_size(enum sample_format format) /** * Returns the size of each (mono) sample in bytes. */ -G_GNUC_PURE +gcc_pure static inline unsigned audio_format_sample_size(const struct audio_format *af) { return sample_format_size((enum sample_format)af->format); @@ -263,7 +268,7 @@ static inline unsigned audio_format_sample_size(const struct audio_format *af) /** * Returns the size of each full frame in bytes. */ -G_GNUC_PURE +gcc_pure static inline unsigned audio_format_frame_size(const struct audio_format *af) { @@ -274,7 +279,7 @@ audio_format_frame_size(const struct audio_format *af) * Returns the floating point factor which converts a time span to a * storage size in bytes. */ -G_GNUC_PURE +gcc_pure static inline double audio_format_time_to_size(const struct audio_format *af) { return af->sample_rate * audio_format_frame_size(af); @@ -287,7 +292,7 @@ static inline double audio_format_time_to_size(const struct audio_format *af) * @param format a #sample_format enum value * @return the string */ -G_GNUC_PURE G_GNUC_MALLOC +gcc_pure gcc_malloc const char * sample_format_to_string(enum sample_format format); @@ -299,9 +304,13 @@ sample_format_to_string(enum sample_format format); * @param s a buffer to print into * @return the string, or NULL if the #audio_format object is invalid */ -G_GNUC_PURE G_GNUC_MALLOC +gcc_pure gcc_malloc const char * audio_format_to_string(const struct audio_format *af, struct audio_format_string *s); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/audio_parser.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/buffer.c b/src/buffer.c deleted file mode 100644 index 559f39a9a..000000000 --- a/src/buffer.c +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "buffer.h" -#include "chunk.h" -#include "poison.h" - -#include <glib.h> - -#include <assert.h> - -struct music_buffer { - struct music_chunk *chunks; - unsigned num_chunks; - - struct music_chunk *available; - - /** a mutex which protects #available */ - GMutex *mutex; - -#ifndef NDEBUG - unsigned num_allocated; -#endif -}; - -struct music_buffer * -music_buffer_new(unsigned num_chunks) -{ - struct music_buffer *buffer; - struct music_chunk *chunk; - - assert(num_chunks > 0); - - buffer = g_new(struct music_buffer, 1); - - buffer->chunks = g_new(struct music_chunk, num_chunks); - buffer->num_chunks = num_chunks; - - chunk = buffer->available = buffer->chunks; - poison_undefined(chunk, sizeof(*chunk)); - - for (unsigned i = 1; i < num_chunks; ++i) { - chunk->next = &buffer->chunks[i]; - chunk = chunk->next; - poison_undefined(chunk, sizeof(*chunk)); - } - - chunk->next = NULL; - - buffer->mutex = g_mutex_new(); - -#ifndef NDEBUG - buffer->num_allocated = 0; -#endif - - return buffer; -} - -void -music_buffer_free(struct music_buffer *buffer) -{ - assert(buffer->chunks != NULL); - assert(buffer->num_chunks > 0); - assert(buffer->num_allocated == 0); - - g_mutex_free(buffer->mutex); - g_free(buffer->chunks); - g_free(buffer); -} - -unsigned -music_buffer_size(const struct music_buffer *buffer) -{ - return buffer->num_chunks; -} - -struct music_chunk * -music_buffer_allocate(struct music_buffer *buffer) -{ - struct music_chunk *chunk; - - g_mutex_lock(buffer->mutex); - - chunk = buffer->available; - if (chunk != NULL) { - buffer->available = chunk->next; - music_chunk_init(chunk); - -#ifndef NDEBUG - ++buffer->num_allocated; -#endif - } - - g_mutex_unlock(buffer->mutex); - return chunk; -} - -void -music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk) -{ - assert(buffer != NULL); - assert(chunk != NULL); - - if (chunk->other != NULL) - music_buffer_return(buffer, chunk->other); - - g_mutex_lock(buffer->mutex); - - music_chunk_free(chunk); - poison_undefined(chunk, sizeof(*chunk)); - - chunk->next = buffer->available; - buffer->available = chunk; - -#ifndef NDEBUG - --buffer->num_allocated; -#endif - - g_mutex_unlock(buffer->mutex); -} diff --git a/src/chunk.c b/src/chunk.c deleted file mode 100644 index 1eb96f4b9..000000000 --- a/src/chunk.c +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "chunk.h" -#include "audio_format.h" -#include "tag.h" - -#include <assert.h> - -void -music_chunk_init(struct music_chunk *chunk) -{ - chunk->other = NULL; - chunk->length = 0; - chunk->tag = NULL; - chunk->replay_gain_serial = 0; -} - -void -music_chunk_free(struct music_chunk *chunk) -{ - if (chunk->tag != NULL) - tag_free(chunk->tag); -} - -#ifndef NDEBUG -bool -music_chunk_check_format(const struct music_chunk *chunk, - const struct audio_format *audio_format) -{ - assert(chunk != NULL); - assert(audio_format != NULL); - assert(audio_format_valid(audio_format)); - - return chunk->length == 0 || - audio_format_equals(&chunk->audio_format, audio_format); -} -#endif - -void * -music_chunk_write(struct music_chunk *chunk, - const struct audio_format *audio_format, - float data_time, uint16_t bit_rate, - size_t *max_length_r) -{ - const size_t frame_size = audio_format_frame_size(audio_format); - size_t num_frames; - - assert(music_chunk_check_format(chunk, audio_format)); - assert(chunk->length == 0 || audio_format_valid(&chunk->audio_format)); - - if (chunk->length == 0) { - /* if the chunk is empty, nobody has set bitRate and - times yet */ - - chunk->bit_rate = bit_rate; - chunk->times = data_time; - } - - num_frames = (sizeof(chunk->data) - chunk->length) / frame_size; - if (num_frames == 0) - return NULL; - -#ifndef NDEBUG - chunk->audio_format = *audio_format; -#endif - - *max_length_r = num_frames * frame_size; - return chunk->data + chunk->length; -} - -bool -music_chunk_expand(struct music_chunk *chunk, - const struct audio_format *audio_format, size_t length) -{ - const size_t frame_size = audio_format_frame_size(audio_format); - - assert(chunk != NULL); - assert(chunk->length + length <= sizeof(chunk->data)); - assert(audio_format_equals(&chunk->audio_format, audio_format)); - - chunk->length += length; - - return chunk->length + frame_size > sizeof(chunk->data); -} diff --git a/src/client_event.c b/src/client_event.c deleted file mode 100644 index 4f54ae0a7..000000000 --- a/src/client_event.c +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" -#include "main.h" - -#include <assert.h> - -static gboolean -client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct client *client = data; - - assert(!client_is_expired(client)); - - if (condition != G_IO_OUT) { - client_set_expired(client); - return false; - } - - client_write_deferred(client); - - if (client_is_expired(client)) { - client_close(client); - return false; - } - - g_timer_start(client->last_activity); - - if (g_queue_is_empty(client->deferred_send)) { - /* done sending deferred buffers exist: schedule - read */ - client->source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - client_in_event, client); - return false; - } - - /* write more */ - return true; -} - -gboolean -client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct client *client = data; - enum command_return ret; - - assert(!client_is_expired(client)); - - if (condition != G_IO_IN) { - client_set_expired(client); - return false; - } - - g_timer_start(client->last_activity); - - ret = client_read(client); - switch (ret) { - case COMMAND_RETURN_OK: - case COMMAND_RETURN_ERROR: - break; - - case COMMAND_RETURN_KILL: - client_close(client); - g_main_loop_quit(main_loop); - return false; - - case COMMAND_RETURN_CLOSE: - client_close(client); - return false; - } - - if (client_is_expired(client)) { - client_close(client); - return false; - } - - if (!g_queue_is_empty(client->deferred_send)) { - /* deferred buffers exist: schedule write */ - client->source_id = g_io_add_watch(client->channel, - G_IO_OUT|G_IO_ERR|G_IO_HUP, - client_out_event, client); - return false; - } - - /* read more */ - return true; -} diff --git a/src/client_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_read.c b/src/client_read.c deleted file mode 100644 index 26ade264e..000000000 --- a/src/client_read.c +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" -#include "fifo_buffer.h" - -#include <assert.h> -#include <string.h> - -static char * -client_read_line(struct client *client) -{ - const char *p, *newline; - size_t length; - char *line; - - p = fifo_buffer_read(client->input, &length); - if (p == NULL) - return NULL; - - newline = memchr(p, '\n', length); - if (newline == NULL) - return NULL; - - line = g_strndup(p, newline - p); - fifo_buffer_consume(client->input, newline - p + 1); - - return g_strchomp(line); -} - -static enum command_return -client_input_received(struct client *client, size_t bytesRead) -{ - char *line; - - fifo_buffer_append(client->input, bytesRead); - - /* process all lines */ - - while ((line = client_read_line(client)) != NULL) { - enum command_return ret = client_process_line(client, line); - g_free(line); - - if (ret == COMMAND_RETURN_KILL || - ret == COMMAND_RETURN_CLOSE) - return ret; - if (client_is_expired(client)) - return COMMAND_RETURN_CLOSE; - } - - return COMMAND_RETURN_OK; -} - -enum command_return -client_read(struct client *client) -{ - char *p; - size_t max_length; - GError *error = NULL; - GIOStatus status; - gsize bytes_read; - - assert(client != NULL); - assert(client->channel != NULL); - - p = fifo_buffer_write(client->input, &max_length); - if (p == NULL) { - g_warning("[%u] buffer overflow", client->num); - return COMMAND_RETURN_CLOSE; - } - - status = g_io_channel_read_chars(client->channel, p, max_length, - &bytes_read, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - return client_input_received(client, bytes_read); - - case G_IO_STATUS_AGAIN: - /* try again later, after select() */ - return COMMAND_RETURN_OK; - - case G_IO_STATUS_EOF: - /* peer disconnected */ - return COMMAND_RETURN_CLOSE; - - case G_IO_STATUS_ERROR: - /* I/O error */ - g_warning("failed to read from client %d: %s", - client->num, error->message); - g_error_free(error); - return COMMAND_RETURN_CLOSE; - } - - /* unreachable */ - return COMMAND_RETURN_CLOSE; -} diff --git a/src/client_subscribe.c b/src/client_subscribe.c deleted file mode 100644 index c65a7ed31..000000000 --- a/src/client_subscribe.c +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "client_subscribe.h" -#include "client_internal.h" -#include "client_idle.h" -#include "idle.h" - -#include <string.h> - -G_GNUC_PURE -static GSList * -client_find_subscription(const struct client *client, const char *channel) -{ - for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i)) - if (strcmp((const char *)i->data, channel) == 0) - return i; - - return NULL; -} - -enum client_subscribe_result -client_subscribe(struct client *client, const char *channel) -{ - assert(client != NULL); - assert(channel != NULL); - - if (!client_message_valid_channel_name(channel)) - return CLIENT_SUBSCRIBE_INVALID; - - if (client_find_subscription(client, channel) != NULL) - return CLIENT_SUBSCRIBE_ALREADY; - - if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) - return CLIENT_SUBSCRIBE_FULL; - - client->subscriptions = g_slist_prepend(client->subscriptions, - g_strdup(channel)); - ++client->num_subscriptions; - - idle_add(IDLE_SUBSCRIPTION); - - return CLIENT_SUBSCRIBE_OK; -} - -bool -client_unsubscribe(struct client *client, const char *channel) -{ - GSList *i = client_find_subscription(client, channel); - if (i == NULL) - return false; - - assert(client->num_subscriptions > 0); - - client->subscriptions = g_slist_remove(client->subscriptions, i->data); - --client->num_subscriptions; - - idle_add(IDLE_SUBSCRIPTION); - - assert((client->num_subscriptions == 0) == - (client->subscriptions == NULL)); - - return true; -} - -void -client_unsubscribe_all(struct client *client) -{ - for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i)) - g_free(i->data); - - g_slist_free(client->subscriptions); - client->subscriptions = NULL; - client->num_subscriptions = 0; -} - -bool -client_push_message(struct client *client, const struct client_message *msg) -{ - assert(client != NULL); - assert(msg != NULL); - assert(client_message_defined(msg)); - - if (client->num_messages >= CLIENT_MAX_MESSAGES || - client_find_subscription(client, msg->channel) == NULL) - return false; - - if (client->messages == NULL) - client_idle_add(client, IDLE_MESSAGE); - - client->messages = g_slist_prepend(client->messages, - client_message_dup(msg)); - ++client->num_messages; - - return true; -} - -GSList * -client_read_messages(struct client *client) -{ - GSList *messages = g_slist_reverse(client->messages); - - client->messages = NULL; - client->num_messages = 0; - - return messages; -} diff --git a/src/client_write.c b/src/client_write.c deleted file mode 100644 index 78cfca8a1..000000000 --- a/src/client_write.c +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" - -#include <assert.h> -#include <string.h> -#include <stdio.h> - -static size_t -client_write_deferred_buffer(struct client *client, - const struct deferred_buffer *buffer) -{ - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->channel != NULL); - assert(buffer != NULL); - - status = g_io_channel_write_chars - (client->channel, buffer->data, buffer->size, - &bytes_written, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - return bytes_written; - - case G_IO_STATUS_AGAIN: - return 0; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - client_set_expired(client); - return 0; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - client_set_expired(client); - g_warning("failed to flush buffer for %i: %s", - client->num, error->message); - g_error_free(error); - return 0; - } - - /* unreachable */ - return 0; -} - -void -client_write_deferred(struct client *client) -{ - size_t ret; - - while (!g_queue_is_empty(client->deferred_send)) { - struct deferred_buffer *buf = - g_queue_peek_head(client->deferred_send); - - assert(buf->size > 0); - assert(buf->size <= client->deferred_bytes); - - ret = client_write_deferred_buffer(client, buf); - if (ret == 0) - break; - - if (ret < buf->size) { - assert(client->deferred_bytes >= (size_t)ret); - client->deferred_bytes -= ret; - buf->size -= ret; - memmove(buf->data, buf->data + ret, buf->size); - break; - } else { - size_t decr = sizeof(*buf) - - sizeof(buf->data) + buf->size; - - assert(client->deferred_bytes >= decr); - client->deferred_bytes -= decr; - g_free(buf); - g_queue_pop_head(client->deferred_send); - } - - g_timer_start(client->last_activity); - } - - if (g_queue_is_empty(client->deferred_send)) { - g_debug("[%u] buffer empty %lu", client->num, - (unsigned long)client->deferred_bytes); - assert(client->deferred_bytes == 0); - } -} - -static void client_defer_output(struct client *client, - const void *data, size_t length) -{ - size_t alloc; - struct deferred_buffer *buf; - - assert(length > 0); - - alloc = sizeof(*buf) - sizeof(buf->data) + length; - client->deferred_bytes += alloc; - if (client->deferred_bytes > client_max_output_buffer_size) { - g_warning("[%u] output buffer size (%lu) is " - "larger than the max (%lu)", - client->num, - (unsigned long)client->deferred_bytes, - (unsigned long)client_max_output_buffer_size); - /* cause client to close */ - client_set_expired(client); - return; - } - - buf = g_malloc(alloc); - buf->size = length; - memcpy(buf->data, data, length); - - g_queue_push_tail(client->deferred_send, buf); -} - -static void client_write_direct(struct client *client, - const char *data, size_t length) -{ - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->channel != NULL); - assert(data != NULL); - assert(length > 0); - assert(g_queue_is_empty(client->deferred_send)); - - status = g_io_channel_write_chars(client->channel, data, length, - &bytes_written, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - case G_IO_STATUS_AGAIN: - break; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - client_set_expired(client); - return; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - client_set_expired(client); - g_warning("failed to write to %i: %s", - client->num, error->message); - g_error_free(error); - return; - } - - if (bytes_written < length) - client_defer_output(client, data + bytes_written, - length - bytes_written); - - if (!g_queue_is_empty(client->deferred_send)) - g_debug("[%u] buffer created", client->num); -} - -void -client_write_output(struct client *client) -{ - if (client_is_expired(client) || !client->send_buf_used) - return; - - if (!g_queue_is_empty(client->deferred_send)) { - client_defer_output(client, client->send_buf, - client->send_buf_used); - - if (client_is_expired(client)) - return; - - /* try to flush the deferred buffers now; the current - server command may take too long to finish, and - meanwhile try to feed output to the client, - otherwise it will time out. One reason why - deferring is slow might be that currently each - client_write() allocates a new deferred buffer. - This should be optimized after MPD 0.14. */ - client_write_deferred(client); - } else - client_write_direct(client, client->send_buf, - client->send_buf_used); - - client->send_buf_used = 0; -} - -/** - * Write a block of data to the client. - */ -static void client_write(struct client *client, const char *buffer, size_t buflen) -{ - /* if the client is going to be closed, do nothing */ - if (client_is_expired(client)) - return; - - while (buflen > 0 && !client_is_expired(client)) { - size_t copylen; - - assert(client->send_buf_used < sizeof(client->send_buf)); - - copylen = sizeof(client->send_buf) - client->send_buf_used; - if (copylen > buflen) - copylen = buflen; - - memcpy(client->send_buf + client->send_buf_used, buffer, - copylen); - buflen -= copylen; - client->send_buf_used += copylen; - buffer += copylen; - if (client->send_buf_used >= sizeof(client->send_buf)) - client_write_output(client); - } -} - -void client_puts(struct client *client, const char *s) -{ - client_write(client, s, strlen(s)); -} - -void client_vprintf(struct client *client, const char *fmt, va_list args) -{ -#ifndef G_OS_WIN32 - va_list tmp; - int length; - char *buffer; - - va_copy(tmp, args); - length = vsnprintf(NULL, 0, fmt, tmp); - va_end(tmp); - - if (length <= 0) - /* wtf.. */ - return; - - buffer = g_malloc(length + 1); - vsnprintf(buffer, length + 1, fmt, args); - client_write(client, buffer, length); - g_free(buffer); -#else - /* On mingw32, snprintf() expects a 64 bit integer instead of - a "long int" for "%li". This is not consistent with our - expectation, so we're using plain sprintf() here, hoping - the static buffer is large enough. Sorry for this hack, - but WIN32 development is so painful, I'm not in the mood to - do it properly now. */ - - static char buffer[4096]; - vsprintf(buffer, fmt, args); - client_write(client, buffer, strlen(buffer)); -#endif -} - -G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...) -{ - va_list args; - - va_start(args, fmt); - client_vprintf(client, fmt, args); - va_end(args); -} diff --git a/src/clock.h b/src/clock.h index 4ece35ab1..f1338938f 100644 --- a/src/clock.h +++ b/src/clock.h @@ -20,21 +20,21 @@ #ifndef MPD_CLOCK_H #define MPD_CLOCK_H -#include <glib.h> +#include "gcc.h" #include <stdint.h> /** * Returns the value of a monotonic clock in milliseconds. */ -G_GNUC_PURE +gcc_pure unsigned monotonic_clock_ms(void); /** * Returns the value of a monotonic clock in microseconds. */ -G_GNUC_PURE +gcc_pure uint64_t monotonic_clock_us(void); diff --git a/src/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.h b/src/conf.h index 815c739b1..d20dc78bc 100644 --- a/src/conf.h +++ b/src/conf.h @@ -118,6 +118,8 @@ config_quark(void) return g_quark_from_static_string("config"); } +G_BEGIN_DECLS + void config_global_init(void); void config_global_finish(void); @@ -224,4 +226,6 @@ void config_add_block_param(struct config_param * param, const char *name, const char *value, int line); +G_END_DECLS + #endif diff --git a/src/cue/cue_parser.c b/src/cue/cue_parser.c index 9ccc3bcdd..bee757c9c 100644 --- a/src/cue/cue_parser.c +++ b/src/cue/cue_parser.c @@ -23,6 +23,8 @@ #include "song.h" #include "tag.h" +#include <glib.h> + #include <assert.h> #include <stdlib.h> diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx new file mode 100644 index 000000000..fcdbae9f0 --- /dev/null +++ b/src/db/ProxyDatabasePlugin.cxx @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ProxyDatabasePlugin.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseSelection.hxx" +#include "PlaylistVector.hxx" +#include "Directory.hxx" +#include "gcc.h" +#include "conf.h" + +extern "C" { +#include "db_error.h" +#include "song.h" +} + +#undef MPD_DIRECTORY_H +#undef MPD_SONG_H +#include <mpd/client.h> + +#include <cassert> +#include <string> +#include <list> + +class ProxyDatabase : public Database { + std::string host; + unsigned port; + + struct mpd_connection *connection; + Directory *root; + +public: + static Database *Create(const struct config_param *param, + GError **error_r); + + virtual bool Open(GError **error_r) override; + virtual void Close() override; + virtual struct song *GetSong(const char *uri_utf8, + GError **error_r) const override; + virtual void ReturnSong(struct song *song) const; + + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const override; + + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + GError **error_r) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + GError **error_r) const override; + +protected: + bool Configure(const struct config_param *param, GError **error_r); +}; + +G_GNUC_CONST +static inline GQuark +libmpdclient_quark(void) +{ + return g_quark_from_static_string("libmpdclient"); +} + +static constexpr struct { + enum tag_type d; + enum mpd_tag_type s; +} tag_table[] = { + { TAG_ARTIST, MPD_TAG_ARTIST }, + { TAG_ALBUM, MPD_TAG_ALBUM }, + { TAG_ALBUM_ARTIST, MPD_TAG_ALBUM_ARTIST }, + { TAG_TITLE, MPD_TAG_TITLE }, + { TAG_TRACK, MPD_TAG_TRACK }, + { TAG_NAME, MPD_TAG_NAME }, + { TAG_GENRE, MPD_TAG_GENRE }, + { TAG_DATE, MPD_TAG_DATE }, + { TAG_COMPOSER, MPD_TAG_COMPOSER }, + { TAG_PERFORMER, MPD_TAG_PERFORMER }, + { TAG_COMMENT, MPD_TAG_COMMENT }, + { TAG_DISC, MPD_TAG_DISC }, + { TAG_MUSICBRAINZ_ARTISTID, MPD_TAG_MUSICBRAINZ_ARTISTID }, + { TAG_MUSICBRAINZ_ALBUMID, MPD_TAG_MUSICBRAINZ_ALBUMID }, + { TAG_MUSICBRAINZ_ALBUMARTISTID, + MPD_TAG_MUSICBRAINZ_ALBUMARTISTID }, + { TAG_MUSICBRAINZ_TRACKID, MPD_TAG_MUSICBRAINZ_TRACKID }, + { TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT } +}; + +G_GNUC_CONST +static enum mpd_tag_type +Convert(enum tag_type tag_type) +{ + for (auto i = tag_table; 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..8eea81e30 --- /dev/null +++ b/src/db/SimpleDatabasePlugin.cxx @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SimpleDatabasePlugin.hxx" +#include "DatabaseSelection.hxx" +#include "DatabaseHelpers.hxx" +#include "Directory.hxx" +#include "SongFilter.hxx" +#include "DatabaseSave.hxx" +#include "DatabaseLock.hxx" +#include "db_error.h" +#include "TextFile.hxx" +#include "conf.h" + +#include <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..6d08fab56 --- /dev/null +++ b/src/decoder/AdPlugDecoderPlugin.cxx @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "AdPlugDecoderPlugin.h" +#include "tag_handler.h" +#include "decoder_api.h" + +extern "C" { +#include "audio_check.h" +} + +#include <adplug/adplug.h> +#include <adplug/emuopl.h> + +#include <glib.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "adplug" + +static unsigned sample_rate; + +static bool +adplug_init(const struct config_param *param) +{ + GError *error = NULL; + + sample_rate = config_get_block_unsigned(param, "sample_rate", 48000); + if (!audio_check_sample_rate(sample_rate, &error)) { + g_warning("%s\n", error->message); + g_error_free(error); + return false; + } + + return true; +} + +static void +adplug_file_decode(struct decoder *decoder, const char *path_fs) +{ + CEmuopl opl(sample_rate, true, true); + opl.init(); + + CPlayer *player = CAdPlug::factory(path_fs, &opl); + if (player == nullptr) + return; + + struct audio_format audio_format; + audio_format_init(&audio_format, sample_rate, SAMPLE_FORMAT_S16, 2); + assert(audio_format_valid(&audio_format)); + + decoder_initialized(decoder, &audio_format, false, + player->songlength() / 1000.); + + int16_t buffer[2048]; + const unsigned frames_per_buffer = G_N_ELEMENTS(buffer) / 2; + enum decoder_command cmd; + + do { + if (!player->update()) + break; + + opl.update(buffer, frames_per_buffer); + cmd = decoder_data(decoder, NULL, + buffer, sizeof(buffer), + 0); + } while (cmd == DECODE_COMMAND_NONE); + + delete player; +} + +static void +adplug_scan_tag(enum tag_type type, const std::string &value, + const struct tag_handler *handler, void *handler_ctx) +{ + if (!value.empty()) + tag_handler_invoke_tag(handler, handler_ctx, + type, value.c_str()); +} + +static bool +adplug_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + CEmuopl opl(sample_rate, true, true); + opl.init(); + + CPlayer *player = CAdPlug::factory(path_fs, &opl); + if (player == nullptr) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, + player->songlength() / 1000); + + if (handler->tag != nullptr) { + adplug_scan_tag(TAG_TITLE, player->gettitle(), + handler, handler_ctx); + adplug_scan_tag(TAG_ARTIST, player->getauthor(), + handler, handler_ctx); + adplug_scan_tag(TAG_COMMENT, player->getdesc(), + handler, handler_ctx); + } + + delete player; + return true; +} + +static const char *const adplug_suffixes[] = { + "amd", + "d00", + "hsc", + "laa", + "rad", + "raw", + "sa2", + nullptr +}; + +const struct decoder_plugin adplug_decoder_plugin = { + "adplug", + adplug_init, + nullptr, + nullptr, + adplug_file_decode, + adplug_scan_file, + nullptr, + nullptr, + adplug_suffixes, + nullptr, +}; diff --git a/src/decoder/AdPlugDecoderPlugin.h b/src/decoder/AdPlugDecoderPlugin.h new file mode 100644 index 000000000..9fdf438aa --- /dev/null +++ b/src/decoder/AdPlugDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_ADPLUG_H +#define MPD_DECODER_ADPLUG_H + +extern const struct decoder_plugin adplug_decoder_plugin; + +#endif diff --git a/src/decoder/_flac_common.c b/src/decoder/FLACCommon.cxx index d7f0c4a8a..25fd1f964 100644 --- a/src/decoder/_flac_common.c +++ b/src/decoder/FLACCommon.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,40 +22,35 @@ */ #include "config.h" -#include "_flac_common.h" -#include "flac_metadata.h" -#include "flac_pcm.h" +#include "FLACCommon.hxx" +#include "FLACMetaData.hxx" +#include "FLAC_PCM.hxx" + +extern "C" { #include "audio_check.h" +} #include <glib.h> #include <assert.h> -void -flac_data_init(struct flac_data *data, struct decoder * decoder, - struct input_stream *input_stream) +flac_data::flac_data(struct decoder *_decoder, + struct input_stream *_input_stream) + :FLACInput(_input_stream, _decoder), + initialized(false), unsupported(false), + total_frames(0), first_frame(0), next_frame(0), position(0), + decoder(_decoder), input_stream(_input_stream), + tag(nullptr) { - pcm_buffer_init(&data->buffer); - - data->unsupported = false; - data->initialized = false; - data->total_frames = 0; - data->first_frame = 0; - data->next_frame = 0; - - data->position = 0; - data->decoder = decoder; - data->input_stream = input_stream; - data->tag = NULL; + pcm_buffer_init(&buffer); } -void -flac_data_deinit(struct flac_data *data) +flac_data::~flac_data() { - pcm_buffer_deinit(&data->buffer); + pcm_buffer_deinit(&buffer); - if (data->tag != NULL) - tag_free(data->tag); + if (tag != nullptr) + tag_free(tag); } static enum sample_format @@ -86,7 +81,7 @@ flac_got_stream_info(struct flac_data *data, if (data->initialized || data->unsupported) return; - GError *error = NULL; + GError *error = nullptr; if (!audio_format_init_checked(&data->audio_format, stream_info->sample_rate, flac_sample_format(stream_info->bits_per_sample), @@ -114,7 +109,6 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, struct replay_gain_info rgi; char *mixramp_start; char *mixramp_end; - float replay_gain_db = 0; switch (block->type) { case FLAC__METADATA_TYPE_STREAMINFO: @@ -123,14 +117,14 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, case FLAC__METADATA_TYPE_VORBIS_COMMENT: if (flac_parse_replay_gain(&rgi, block)) - replay_gain_db = decoder_replay_gain(data->decoder, &rgi); + decoder_replay_gain(data->decoder, &rgi); if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block)) - decoder_mixramp(data->decoder, replay_gain_db, + decoder_mixramp(data->decoder, mixramp_start, mixramp_end); - if (data->tag != NULL) - flac_vorbis_comments_to_tag(data->tag, NULL, + if (data->tag != nullptr) + flac_vorbis_comments_to_tag(data->tag, &block->data.vorbis_comment); default: @@ -138,15 +132,6 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, } } -void flac_error_common_cb(const FLAC__StreamDecoderErrorStatus status, - struct flac_data *data) -{ - if (decoder_get_command(data->decoder) == DECODE_COMMAND_STOP) - return; - - g_warning("%s", FLAC__StreamDecoderErrorStatusString[status]); -} - /** * This function attempts to call decoder_initialized() in case there * was no STREAMINFO block. This is allowed for nonseekable streams, @@ -160,7 +145,7 @@ flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) if (data->unsupported) return false; - GError *error = NULL; + GError *error = nullptr; if (!audio_format_init_checked(&data->audio_format, header->sample_rate, flac_sample_format(header->bits_per_sample), @@ -199,7 +184,7 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame, buffer = pcm_buffer_get(&data->buffer, buffer_size); flac_convert(buffer, frame->header.channels, - data->audio_format.format, buf, + (enum sample_format)data->audio_format.format, buf, 0, frame->header.blocksize); if (nbytes > 0) diff --git a/src/decoder/_flac_common.h b/src/decoder/FLACCommon.hxx index 0d90ba656..b80372bbf 100644 --- a/src/decoder/_flac_common.h +++ b/src/decoder/FLACCommon.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,13 +21,15 @@ * Common data structures and functions used by FLAC and OggFLAC */ -#ifndef MPD_FLAC_COMMON_H -#define MPD_FLAC_COMMON_H +#ifndef MPD_FLAC_COMMON_HXX +#define MPD_FLAC_COMMON_HXX +#include "FLACInput.hxx" #include "decoder_api.h" -#include "pcm_buffer.h" -#include <glib.h> +extern "C" { +#include "pcm_buffer.h" +} #include <FLAC/stream_decoder.h> #include <FLAC/metadata.h> @@ -35,7 +37,7 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "flac" -struct flac_data { +struct flac_data : public FLACInput { struct pcm_buffer buffer; /** @@ -78,25 +80,19 @@ struct flac_data { FLAC__uint64 next_frame; FLAC__uint64 position; + struct decoder *decoder; struct input_stream *input_stream; - struct tag *tag; -}; -/* initializes a given FlacData struct */ -void -flac_data_init(struct flac_data *data, struct decoder * decoder, - struct input_stream *input_stream); + struct tag *tag; -void -flac_data_deinit(struct flac_data *data); + flac_data(struct decoder *decoder, struct input_stream *input_stream); + ~flac_data(); +}; void flac_metadata_common_cb(const FLAC__StreamMetadata * block, struct flac_data *data); -void flac_error_common_cb(FLAC__StreamDecoderErrorStatus status, - struct flac_data *data); - FLAC__StreamDecoderWriteStatus flac_common_write(struct flac_data *data, const FLAC__Frame * frame, const FLAC__int32 *const buf[], diff --git a/src/decoder/flac_decoder_plugin.c b/src/decoder/FLACDecoderPlugin.cxx index fb0b3502d..43b604097 100644 --- a/src/decoder/flac_decoder_plugin.c +++ b/src/decoder/FLACDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,10 @@ */ #include "config.h" /* must be first for large file support */ -#include "_flac_common.h" -#include "flac_compat.h" -#include "flac_metadata.h" - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 -#include "_ogg_common.h" -#endif +#include "FLACDecoderPlugin.h" +#include "FLACCommon.hxx" +#include "FLACMetaData.hxx" +#include "OggCodec.hxx" #include <glib.h> @@ -34,114 +31,10 @@ #include <sys/stat.h> #include <sys/types.h> -/* this code was based on flac123, from flac-tools */ - -static FLAC__StreamDecoderReadStatus -flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__byte buf[], flac_read_status_size_t *bytes, - void *fdata) -{ - struct flac_data *data = fdata; - size_t r; - - r = decoder_read(data->decoder, data->input_stream, - (void *)buf, *bytes); - *bytes = r; - - if (r == 0) { - if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || - input_stream_lock_eof(data->input_stream)) - return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; - else - return FLAC__STREAM_DECODER_READ_STATUS_ABORT; - } - - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; -} - -static FLAC__StreamDecoderSeekStatus -flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (!data->input_stream->seekable) - return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; - - if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET, - NULL)) - return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; - - return FLAC__STREAM_DECODER_SEEK_STATUS_OK; -} - -static FLAC__StreamDecoderTellStatus -flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 * offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (!data->input_stream->seekable) - return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; - - *offset = (long)(data->input_stream->offset); - - return FLAC__STREAM_DECODER_TELL_STATUS_OK; -} - -static FLAC__StreamDecoderLengthStatus -flac_length_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 * length, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (data->input_stream->size < 0) - return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; - - *length = (size_t) (data->input_stream->size); - - return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; -} - -static FLAC__bool -flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && - decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || - input_stream_lock_eof(data->input_stream); -} - -static void -flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__StreamDecoderErrorStatus status, void *fdata) -{ - flac_error_common_cb(status, (struct flac_data *) fdata); -} - #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) -{ - switch (state) { - case FLAC__SEEKABLE_STREAM_DECODER_OK: - case FLAC__SEEKABLE_STREAM_DECODER_SEEKING: - case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: - return; - - case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: - case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: - case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: - break; - } +#error libFLAC is too old +#endif - g_warning("%s\n", FLAC__SeekableStreamDecoderStateString[state]); -} -#else /* FLAC_API_VERSION_CURRENT >= 7 */ static void flacPrintErroredState(FLAC__StreamDecoderState state) { switch (state) { @@ -162,7 +55,6 @@ static void flacPrintErroredState(FLAC__StreamDecoderState state) g_warning("%s\n", FLAC__StreamDecoderStateString[state]); } -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec, const FLAC__StreamMetadata * block, void *vdata) @@ -195,7 +87,30 @@ static bool flac_scan_file(const char *file, const struct tag_handler *handler, void *handler_ctx) { - return flac_scan_file2(file, NULL, handler, handler_ctx); + FLACMetadataChain chain; + if (!chain.Read(file)) { + g_debug("Failed to read FLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static bool +flac_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + FLACMetadataChain chain; + if (!chain.Read(is)) { + g_debug("Failed to read FLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; } /** @@ -205,15 +120,13 @@ static FLAC__StreamDecoder * flac_decoder_new(void) { FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); - if (sd == NULL) { + if (sd == nullptr) { g_warning("FLAC__stream_decoder_new() failed"); - return NULL; + return nullptr; } -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) g_debug("FLAC__stream_decoder_set_metadata_respond() has failed"); -#endif return sd; } @@ -259,7 +172,7 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, data->first_frame = t_start; while (true) { - if (data->tag != NULL && !tag_is_empty(data->tag)) { + if (data->tag != nullptr && !tag_is_empty(data->tag)) { cmd = decoder_tag(data->decoder, data->input_stream, data->tag); tag_free(data->tag); @@ -300,34 +213,30 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, static FLAC__StreamDecoderInitStatus stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) { -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 return FLAC__stream_decoder_init_ogg_stream(flac_dec, - flac_read_cb, - flac_seek_cb, - flac_tell_cb, - flac_length_cb, - flac_eof_cb, + FLACInput::Read, + FLACInput::Seek, + FLACInput::Tell, + FLACInput::Length, + FLACInput::Eof, flac_write_cb, flacMetadata, - flac_error_cb, + FLACInput::Error, data); -#else - (void)flac_dec; - (void)data; - - return FLAC__STREAM_DECODER_INIT_STATUS_ERROR; -#endif } static FLAC__StreamDecoderInitStatus stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) { return FLAC__stream_decoder_init_stream(flac_dec, - flac_read_cb, flac_seek_cb, - flac_tell_cb, flac_length_cb, - flac_eof_cb, flac_write_cb, + FLACInput::Read, + FLACInput::Seek, + FLACInput::Tell, + FLACInput::Length, + FLACInput::Eof, + flac_write_cb, flacMetadata, - flac_error_cb, + FLACInput::Error, data); } @@ -345,28 +254,23 @@ flac_decode_internal(struct decoder * decoder, bool is_ogg) { FLAC__StreamDecoder *flac_dec; - struct flac_data data; flac_dec = flac_decoder_new(); - if (flac_dec == NULL) + if (flac_dec == nullptr) return; - flac_data_init(&data, decoder, input_stream); + struct flac_data data(decoder, input_stream); data.tag = tag_new(); FLAC__StreamDecoderInitStatus status = stream_init(flac_dec, &data, is_ogg); if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { - flac_data_deinit(&data); FLAC__stream_decoder_delete(flac_dec); -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 g_warning("%s", FLAC__StreamDecoderInitStatusString[status]); -#endif return; } if (!flac_decoder_initialize(&data, flac_dec, 0)) { - flac_data_deinit(&data); FLAC__stream_decoder_finish(flac_dec); FLAC__stream_decoder_delete(flac_dec); return; @@ -374,8 +278,6 @@ flac_decode_internal(struct decoder * decoder, flac_decoder_loop(&data, flac_dec, 0, 0); - flac_data_deinit(&data); - FLAC__stream_decoder_finish(flac_dec); FLAC__stream_decoder_delete(flac_dec); } @@ -386,101 +288,96 @@ flac_decode(struct decoder * decoder, struct input_stream *input_stream) flac_decode_internal(decoder, input_stream, false); } -#ifndef HAVE_OGGFLAC - static bool oggflac_init(G_GNUC_UNUSED const struct config_param *param) { -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 return !!FLAC_API_SUPPORTS_OGG_FLAC; -#else - /* disable oggflac when libflac is too old */ - return false; -#endif } -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - static bool oggflac_scan_file(const char *file, const struct tag_handler *handler, void *handler_ctx) { - FLAC__Metadata_Iterator *it; - FLAC__StreamMetadata *block; - FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); - - if (!(FLAC__metadata_chain_read_ogg(chain, file))) { - FLAC__metadata_chain_delete(chain); + FLACMetadataChain chain; + if (!chain.ReadOgg(file)) { + g_debug("Failed to read OggFLAC tags: %s", + chain.GetStatusString()); return false; } - it = FLAC__metadata_iterator_new(); - FLAC__metadata_iterator_init(it, chain); - - do { - if (!(block = FLAC__metadata_iterator_get_block(it))) - break; + chain.Scan(handler, handler_ctx); + return true; +} - flac_scan_metadata(NULL, block, - handler, handler_ctx); - } while (FLAC__metadata_iterator_next(it)); - FLAC__metadata_iterator_delete(it); +static bool +oggflac_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + FLACMetadataChain chain; + if (!chain.ReadOgg(is)) { + g_debug("Failed to read OggFLAC tags: %s", + chain.GetStatusString()); + return false; + } - FLAC__metadata_chain_delete(chain); + chain.Scan(handler, handler_ctx); return true; } static void oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) { - if (ogg_stream_type_detect(input_stream) != FLAC) + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_FLAC) return; - /* rewind the stream, because ogg_stream_type_detect() has + /* rewind the stream, because ogg_codec_detect() has moved it */ - input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); + input_stream_lock_seek(input_stream, 0, SEEK_SET, nullptr); flac_decode_internal(decoder, input_stream, true); } -static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; +static const char *const oggflac_suffixes[] = { "ogg", "oga", nullptr }; static const char *const oggflac_mime_types[] = { "application/ogg", "application/x-ogg", "audio/ogg", "audio/x-flac+ogg", "audio/x-ogg", - NULL + nullptr }; -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - const struct decoder_plugin oggflac_decoder_plugin = { - .name = "oggflac", - .init = oggflac_init, -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - .stream_decode = oggflac_decode, - .scan_file = oggflac_scan_file, - .suffixes = oggflac_suffixes, - .mime_types = oggflac_mime_types -#endif + "oggflac", + oggflac_init, + nullptr, + oggflac_decode, + nullptr, + oggflac_scan_file, + oggflac_scan_stream, + nullptr, + oggflac_suffixes, + oggflac_mime_types, }; -#endif /* HAVE_OGGFLAC */ - -static const char *const flac_suffixes[] = { "flac", NULL }; +static const char *const flac_suffixes[] = { "flac", nullptr }; static const char *const flac_mime_types[] = { "application/flac", "application/x-flac", "audio/flac", "audio/x-flac", - NULL + nullptr }; const struct decoder_plugin flac_decoder_plugin = { - .name = "flac", - .stream_decode = flac_decode, - .scan_file = flac_scan_file, - .suffixes = flac_suffixes, - .mime_types = flac_mime_types, + "flac", + nullptr, + nullptr, + flac_decode, + nullptr, + flac_scan_file, + flac_scan_stream, + nullptr, + flac_suffixes, + flac_mime_types, }; diff --git a/src/decoder/FLACDecoderPlugin.h b/src/decoder/FLACDecoderPlugin.h new file mode 100644 index 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..0c2c24770 --- /dev/null +++ b/src/decoder/FLACIOHandle.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_IO_HANDLE_HXX +#define MPD_FLAC_IO_HANDLE_HXX + +#include "gcc.h" +#include "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..99f321cdd --- /dev/null +++ b/src/decoder/FLACInput.cxx @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FLACInput.hxx" +#include "decoder_api.h" +#include "gcc.h" +#include "input_stream.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..8273a230b 100644 --- a/src/decoder/flac_metadata.c +++ b/src/decoder/FLACMetaData.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,16 +18,20 @@ */ #include "config.h" -#include "flac_metadata.h" -#include "replay_gain_info.h" +#include "FLACMetaData.hxx" + +extern "C" { +#include "XiphTags.h" +} + #include "tag.h" #include "tag_handler.h" #include "tag_table.h" +#include "replay_gain_info.h" #include <glib.h> #include <assert.h> -#include <stdbool.h> #include <stdlib.h> static bool @@ -91,7 +95,7 @@ flac_find_string_comment(const FLAC__StreamMetadata *block, int len; const unsigned char *p; - *str = NULL; + *str = nullptr; offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, cmnt); if (offset < 0) @@ -128,36 +132,21 @@ flac_parse_mixramp(char **mixramp_start, char **mixramp_end, */ static const char * flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, const char *char_tnum, size_t *length_r) + const char *name, size_t *length_r) { size_t name_length = strlen(name); - size_t char_tnum_length = 0; const char *comment = (const char*)entry->entry; if (entry->length <= name_length || g_ascii_strncasecmp(comment, name, name_length) != 0) - return NULL; - - if (char_tnum != NULL) { - char_tnum_length = strlen(char_tnum); - if (entry->length > name_length + char_tnum_length + 2 && - comment[name_length] == '[' && - g_ascii_strncasecmp(comment + name_length + 1, - char_tnum, char_tnum_length) == 0 && - comment[name_length + char_tnum_length + 1] == ']') - name_length = name_length + char_tnum_length + 2; - else if (entry->length > name_length + char_tnum_length && - g_ascii_strncasecmp(comment + name_length, - char_tnum, char_tnum_length) == 0) - name_length = name_length + char_tnum_length; - } + return nullptr; if (comment[name_length] == '=') { *length_r = entry->length - name_length - 1; return comment + name_length + 1; } - return NULL; + return nullptr; } /** @@ -167,14 +156,13 @@ flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, static bool flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, const char *name, enum tag_type tag_type, - const char *char_tnum, const struct tag_handler *handler, void *handler_ctx) { const char *value; size_t value_length; - value = flac_comment_value(entry, name, char_tnum, &value_length); - if (value != NULL) { + value = flac_comment_value(entry, name, &value_length); + if (value != nullptr) { char *p = g_strndup(value, value_length); tag_handler_invoke_tag(handler, handler_ctx, tag_type, p); g_free(p); @@ -184,23 +172,15 @@ flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, return false; } -static const struct tag_table flac_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - static void -flac_scan_comment(const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment_Entry *entry, +flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, const struct tag_handler *handler, void *handler_ctx) { - if (handler->pair != NULL) { + if (handler->pair != nullptr) { char *name = g_strdup((const char*)entry->entry); char *value = strchr(name, '='); - if (value != NULL && value > name) { + if (value != nullptr && value > name) { *value++ = 0; tag_handler_invoke_pair(handler, handler_ctx, name, value); @@ -209,36 +189,34 @@ flac_scan_comment(const char *char_tnum, g_free(name); } - for (const struct tag_table *i = flac_tags; i->name != NULL; ++i) - if (flac_copy_comment(entry, i->name, i->type, char_tnum, + for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) + if (flac_copy_comment(entry, i->name, i->type, handler, handler_ctx)) return; for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) if (flac_copy_comment(entry, - tag_item_names[i], i, char_tnum, + tag_item_names[i], (enum tag_type)i, handler, handler_ctx)) return; } static void -flac_scan_comments(const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment *comment, +flac_scan_comments(const FLAC__StreamMetadata_VorbisComment *comment, const struct tag_handler *handler, void *handler_ctx) { for (unsigned i = 0; i < comment->num_comments; ++i) - flac_scan_comment(char_tnum, &comment->comments[i], + flac_scan_comment(&comment->comments[i], handler, handler_ctx); } void -flac_scan_metadata(const char *track, - const FLAC__StreamMetadata *block, +flac_scan_metadata(const FLAC__StreamMetadata *block, const struct tag_handler *handler, void *handler_ctx) { switch (block->type) { case FLAC__METADATA_TYPE_VORBIS_COMMENT: - flac_scan_comments(track, &block->data.vorbis_comment, + flac_scan_comments(&block->data.vorbis_comment, handler, handler_ctx); break; @@ -254,70 +232,22 @@ flac_scan_metadata(const char *track, } void -flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, +flac_vorbis_comments_to_tag(struct tag *tag, const FLAC__StreamMetadata_VorbisComment *comment) { - flac_scan_comments(char_tnum, comment, - &add_tag_handler, tag); + flac_scan_comments(comment, &add_tag_handler, tag); } -bool -flac_scan_file2(const char *file, const char *char_tnum, - const struct tag_handler *handler, void *handler_ctx) +void +FLACMetadataChain::Scan(const struct tag_handler *handler, void *handler_ctx) { - FLAC__Metadata_SimpleIterator *it; - FLAC__StreamMetadata *block = NULL; - - it = FLAC__metadata_simple_iterator_new(); - if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) { - const char *err; - FLAC_API FLAC__Metadata_SimpleIteratorStatus s; - - s = FLAC__metadata_simple_iterator_status(it); - - switch (s) { /* slightly more human-friendly messages: */ - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT: - err = "illegal input"; - break; - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE: - err = "error opening file"; - break; - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE: - err = "not a FLAC file"; - break; - default: - err = FLAC__Metadata_SimpleIteratorStatusString[s]; - } - g_debug("Reading '%s' metadata gave the following error: %s\n", - file, err); - FLAC__metadata_simple_iterator_delete(it); - return false; - } + FLACMetadataIterator iterator(*this); do { - block = FLAC__metadata_simple_iterator_get_block(it); - if (!block) + FLAC__StreamMetadata *block = iterator.GetBlock(); + if (block == nullptr) break; - flac_scan_metadata(char_tnum, block, handler, handler_ctx); - FLAC__metadata_object_delete(block); - } while (FLAC__metadata_simple_iterator_next(it)); - - FLAC__metadata_simple_iterator_delete(it); - - return true; -} - -struct tag * -flac_tag_load(const char *file, const char *char_tnum) -{ - struct tag *tag = tag_new(); - - if (!flac_scan_file2(file, char_tnum, &add_tag_handler, tag) || - tag_is_empty(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; + flac_scan_metadata(block, handler, handler_ctx); + } while (iterator.Next()); } diff --git a/src/decoder/FLACMetaData.hxx b/src/decoder/FLACMetaData.hxx new file mode 100644 index 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/_ogg_common.c b/src/decoder/OggCodec.cxx index 09d2712da..5ad9c69d6 100644 --- a/src/decoder/_ogg_common.c +++ b/src/decoder/OggCodec.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,25 +22,27 @@ */ #include "config.h" -#include "_ogg_common.h" +#include "OggCodec.hxx" -ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream) +enum ogg_codec +ogg_codec_detect(struct decoder *decoder, struct input_stream *is) { /* oggflac detection based on code in ogg123 and this post * http://lists.xiph.org/pipermail/flac/2004-December/000393.html * ogg123 trunk still doesn't have this patch as of June 2005 */ unsigned char buf[41]; - size_t r; - - r = decoder_read(NULL, inStream, buf, sizeof(buf)); + size_t r = decoder_read(decoder, is, buf, sizeof(buf)); if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0) - return VORBIS; + return OGG_CODEC_UNKNOWN; if ((memcmp(buf + 29, "FLAC", 4) == 0 && memcmp(buf + 37, "fLaC", 4) == 0) || memcmp(buf + 28, "FLAC", 4) == 0 || memcmp(buf + 28, "fLaC", 4) == 0) - return FLAC; + return OGG_CODEC_FLAC; + + if (memcmp(buf + 28, "Opus", 4) == 0) + return OGG_CODEC_OPUS; - return VORBIS; + return OGG_CODEC_VORBIS; } diff --git a/src/decoder/_ogg_common.h b/src/decoder/OggCodec.hxx index 85e4ebba6..e241560fb 100644 --- a/src/decoder/_ogg_common.h +++ b/src/decoder/OggCodec.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,13 +21,19 @@ * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) */ -#ifndef MPD_OGG_COMMON_H -#define MPD_OGG_COMMON_H +#ifndef MPD_OGG_CODEC_HXX +#define MPD_OGG_CODEC_HXX #include "decoder_api.h" -typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type; +enum ogg_codec { + OGG_CODEC_UNKNOWN, + OGG_CODEC_VORBIS, + OGG_CODEC_FLAC, + OGG_CODEC_OPUS, +}; -ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream); +enum ogg_codec +ogg_codec_detect(struct decoder *decoder, struct input_stream *is); #endif /* _OGG_COMMON_H */ diff --git a/src/decoder/OggFind.cxx b/src/decoder/OggFind.cxx new file mode 100644 index 000000000..9df4c6455 --- /dev/null +++ b/src/decoder/OggFind.cxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OggFind.hxx" +#include "OggSyncState.hxx" + +bool +OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet) +{ + while (true) { + int r = ogg_stream_packetout(&os, &packet); + if (r == 0) { + if (!oy.ExpectPageIn(os)) + return false; + + continue; + } else if (r > 0 && packet.e_o_s) + return true; + } +} diff --git a/src/decoder/OggFind.hxx b/src/decoder/OggFind.hxx new file mode 100644 index 000000000..7d18d2067 --- /dev/null +++ b/src/decoder/OggFind.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OGG_FIND_HXX +#define MPD_OGG_FIND_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +class OggSyncState; + +/** + * Skip all pages/packets until an end-of-stream (EOS) packet for the + * specified stream is found. + * + * @return true if the EOS packet was found + */ +bool +OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet); + +#endif diff --git a/src/decoder/OggSyncState.hxx b/src/decoder/OggSyncState.hxx new file mode 100644 index 000000000..eaeb9bd8c --- /dev/null +++ b/src/decoder/OggSyncState.hxx @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OGG_SYNC_STATE_HXX +#define MPD_OGG_SYNC_STATE_HXX + +#include "check.h" +#include "OggUtil.hxx" + +#include <ogg/ogg.h> + +#include <stddef.h> + +/** + * Wrapper for an ogg_sync_state. + */ +class OggSyncState { + ogg_sync_state oy; + + input_stream &is; + struct decoder *const decoder; + +public: + OggSyncState(input_stream &_is, struct decoder *const _decoder=nullptr) + :is(_is), decoder(_decoder) { + ogg_sync_init(&oy); + } + + ~OggSyncState() { + ogg_sync_clear(&oy); + } + + void Reset() { + ogg_sync_reset(&oy); + } + + bool Feed(size_t size) { + return OggFeed(oy, decoder, &is, size); + } + + bool ExpectPage(ogg_page &page) { + return OggExpectPage(oy, page, decoder, &is); + } + + bool ExpectFirstPage(ogg_stream_state &os) { + return OggExpectFirstPage(oy, os, decoder, &is); + } + + bool ExpectPageIn(ogg_stream_state &os) { + return OggExpectPageIn(oy, os, decoder, &is); + } + + bool ExpectPageSeek(ogg_page &page) { + return OggExpectPageSeek(oy, page, decoder, &is); + } + + bool ExpectPageSeekIn(ogg_stream_state &os) { + return OggExpectPageSeekIn(oy, os, decoder, &is); + } +}; + +#endif diff --git a/src/decoder/OggUtil.cxx b/src/decoder/OggUtil.cxx new file mode 100644 index 000000000..a1125a2c6 --- /dev/null +++ b/src/decoder/OggUtil.cxx @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OggUtil.hxx" +#include "decoder_api.h" + +bool +OggFeed(ogg_sync_state &oy, struct decoder *decoder, + input_stream *input_stream, size_t size) +{ + char *buffer = ogg_sync_buffer(&oy, size); + if (buffer == nullptr) + return false; + + size_t nbytes = decoder_read(decoder, input_stream, + buffer, size); + if (nbytes == 0) + return false; + + ogg_sync_wrote(&oy, nbytes); + return true; +} + +bool +OggExpectPage(ogg_sync_state &oy, ogg_page &page, + decoder *decoder, input_stream *input_stream) +{ + while (true) { + int r = ogg_sync_pageout(&oy, &page); + if (r != 0) + return r > 0; + + if (!OggFeed(oy, decoder, input_stream, 1024)) + return false; + } +} + +bool +OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is) +{ + ogg_page page; + if (!OggExpectPage(oy, page, decoder, is)) + return false; + + ogg_stream_init(&os, ogg_page_serialno(&page)); + ogg_stream_pagein(&os, &page); + return true; +} + +bool +OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is) +{ + ogg_page page; + if (!OggExpectPage(oy, page, decoder, is)) + return false; + + ogg_stream_pagein(&os, &page); + return true; +} + +bool +OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, + decoder *decoder, input_stream *input_stream) +{ + size_t remaining_skipped = 16384; + + while (true) { + int r = ogg_sync_pageseek(&oy, &page); + if (r > 0) + return true; + + if (r < 0) { + /* skipped -r bytes */ + size_t nbytes = -r; + if (nbytes > remaining_skipped) + /* still no ogg page - we lost our + patience, abort */ + return false; + + remaining_skipped -= nbytes; + continue; + } + + if (!OggFeed(oy, decoder, input_stream, 1024)) + return false; + } +} + +bool +OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is) +{ + ogg_page page; + if (!OggExpectPageSeek(oy, page, decoder, is)) + return false; + + ogg_stream_pagein(&os, &page); + return true; +} diff --git a/src/decoder/OggUtil.hxx b/src/decoder/OggUtil.hxx new file mode 100644 index 000000000..324797815 --- /dev/null +++ b/src/decoder/OggUtil.hxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OGG_UTIL_HXX +#define MPD_OGG_UTIL_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +#include <stddef.h> + +struct input_stream; +struct decoder; + +/** + * Feed data from the #input_stream into the #ogg_sync_state. + * + * @return false on error or end-of-file + */ +bool +OggFeed(ogg_sync_state &oy, struct decoder *decoder, input_stream *is, + size_t size); + +/** + * Feed into the #ogg_sync_state until a page gets available. Garbage + * data at the beginning is considered a fatal error. + * + * @return true if a page is available + */ +bool +OggExpectPage(ogg_sync_state &oy, ogg_page &page, + decoder *decoder, input_stream *input_stream); + +/** + * Combines OggExpectPage(), ogg_stream_init() and + * ogg_stream_pagein(). + * + * @return true if the stream was initialized and the first page was + * delivered to it + */ +bool +OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is); + +/** + * Combines OggExpectPage() and ogg_stream_pagein(). + * + * @return true if a page was delivered to the stream + */ +bool +OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is); + +/** + * Like OggExpectPage(), but allow skipping garbage (after seeking). + */ +bool +OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, + decoder *decoder, input_stream *input_stream); + +/** + * Combines OggExpectPageSeek() and ogg_stream_pagein(). + * + * @return true if a page was delivered to the stream + */ +bool +OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is); + +#endif diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx new file mode 100644 index 000000000..f0c0a442d --- /dev/null +++ b/src/decoder/OpusDecoderPlugin.cxx @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "OpusDecoderPlugin.h" +#include "OpusHead.hxx" +#include "OpusTags.hxx" +#include "OggUtil.hxx" +#include "OggFind.hxx" +#include "OggSyncState.hxx" +#include "decoder_api.h" +#include "OggCodec.hxx" +#include "audio_check.h" +#include "tag_handler.h" + +#include <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(); + + bool ReadFirstPage(OggSyncState &oy); + bool ReadNextPage(OggSyncState &oy); + + enum decoder_command HandlePackets(); + enum decoder_command HandlePacket(const ogg_packet &packet); + enum decoder_command HandleBOS(const ogg_packet &packet); + enum decoder_command HandleTags(const ogg_packet &packet); + enum decoder_command HandleAudio(const ogg_packet &packet); +}; + +MPDOpusDecoder::~MPDOpusDecoder() +{ + g_free(output_buffer); + + if (opus_decoder != nullptr) + opus_decoder_destroy(opus_decoder); + + if (os_initialized) + ogg_stream_clear(&os); +} + +inline bool +MPDOpusDecoder::ReadFirstPage(OggSyncState &oy) +{ + assert(!os_initialized); + + if (!oy.ExpectFirstPage(os)) + return false; + + os_initialized = true; + return true; +} + +inline bool +MPDOpusDecoder::ReadNextPage(OggSyncState &oy) +{ + assert(os_initialized); + + ogg_page page; + if (!oy.ExpectPage(page)) + return false; + + const auto page_serialno = ogg_page_serialno(&page); + if (page_serialno != os.serialno) + ogg_stream_reset_serialno(&os, page_serialno); + + ogg_stream_pagein(&os, &page); + return true; +} + +inline enum decoder_command +MPDOpusDecoder::HandlePackets() +{ + ogg_packet packet; + while (ogg_stream_packetout(&os, &packet) == 1) { + enum decoder_command cmd = HandlePacket(packet); + if (cmd != DECODE_COMMAND_NONE) + return cmd; + } + + return DECODE_COMMAND_NONE; +} + +inline enum decoder_command +MPDOpusDecoder::HandlePacket(const ogg_packet &packet) +{ + if (packet.e_o_s) + return DECODE_COMMAND_STOP; + + if (packet.b_o_s) + return HandleBOS(packet); + else if (!found_opus) + return DECODE_COMMAND_STOP; + + if (IsOpusTags(packet)) + return HandleTags(packet); + + return HandleAudio(packet); +} + +inline enum decoder_command +MPDOpusDecoder::HandleBOS(const ogg_packet &packet) +{ + assert(packet.b_o_s); + + if (found_opus || !IsOpusHead(packet)) + return DECODE_COMMAND_STOP; + + unsigned channels; + if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || + !audio_valid_channel_count(channels)) + return DECODE_COMMAND_STOP; + + assert(opus_decoder == nullptr); + assert(output_buffer == nullptr); + + opus_serialno = os.serialno; + found_opus = true; + + /* TODO: parse attributes from the OpusHead (sample rate, + channels, ...) */ + + int opus_error; + opus_decoder = opus_decoder_create(opus_sample_rate, channels, + &opus_error); + if (opus_decoder == nullptr) { + g_warning("libopus error: %s", + opus_strerror(opus_error)); + return DECODE_COMMAND_STOP; + } + + struct audio_format audio_format; + audio_format_init(&audio_format, opus_sample_rate, + SAMPLE_FORMAT_S16, channels); + decoder_initialized(decoder, &audio_format, false, -1); + frame_size = audio_format_frame_size(&audio_format); + + /* allocate an output buffer for 16 bit PCM samples big enough + to hold a quarter second, larger than 120ms required by + libopus */ + output_size = audio_format.sample_rate / 4; + output_buffer = (opus_int16 *) + g_malloc(sizeof(*output_buffer) * output_size * + audio_format.channels); + + return decoder_get_command(decoder); +} + +inline enum decoder_command +MPDOpusDecoder::HandleTags(const ogg_packet &packet) +{ + struct tag *tag = tag_new(); + + enum decoder_command cmd; + if (ScanOpusTags(packet.packet, packet.bytes, &add_tag_handler, tag) && + !tag_is_empty(tag)) + cmd = decoder_tag(decoder, input_stream, tag); + else + cmd = decoder_get_command(decoder); + + tag_free(tag); + return cmd; +} + +inline enum decoder_command +MPDOpusDecoder::HandleAudio(const ogg_packet &packet) +{ + assert(opus_decoder != nullptr); + + int nframes = opus_decode(opus_decoder, + (const unsigned char*)packet.packet, + packet.bytes, + output_buffer, output_size, + 0); + if (nframes < 0) { + g_warning("%s", opus_strerror(nframes)); + return DECODE_COMMAND_STOP; + } + + if (nframes > 0) { + const size_t nbytes = nframes * frame_size; + enum decoder_command cmd = + decoder_data(decoder, input_stream, + output_buffer, nbytes, + 0); + if (cmd != DECODE_COMMAND_NONE) + return cmd; + } + + return DECODE_COMMAND_NONE; +} + +static void +mpd_opus_stream_decode(struct decoder *decoder, + struct input_stream *input_stream) +{ + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_OPUS) + return; + + /* rewind the stream, because ogg_codec_detect() has + moved it */ + input_stream_lock_seek(input_stream, 0, SEEK_SET, nullptr); + + MPDOpusDecoder d(decoder, input_stream); + OggSyncState oy(*input_stream, decoder); + + if (!d.ReadFirstPage(oy)) + return; + + while (true) { + enum decoder_command cmd = d.HandlePackets(); + if (cmd != DECODE_COMMAND_NONE) + break; + + if (!d.ReadNextPage(oy)) + break; + + } +} + +static bool +SeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, + input_stream *is) +{ + if (is->size > 0 && is->size - is->offset < 65536) + return OggFindEOS(oy, os, packet); + + if (!input_stream_cheap_seeking(is)) + return false; + + oy.Reset(); + + return input_stream_lock_seek(is, -65536, SEEK_END, nullptr) && + oy.ExpectPageSeekIn(os) && + OggFindEOS(oy, os, packet); +} + +static bool +mpd_opus_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + OggSyncState oy(*is); + + ogg_stream_state os; + if (!oy.ExpectFirstPage(os)) + return false; + + /* read at most two more pages */ + unsigned remaining_pages = 2; + + bool result = false; + + ogg_packet packet; + while (true) { + int r = ogg_stream_packetout(&os, &packet); + if (r < 0) { + result = false; + break; + } + + if (r == 0) { + if (remaining_pages-- == 0) + break; + + if (!oy.ExpectPageIn(os)) { + result = false; + break; + } + + continue; + } + + if (packet.b_o_s) { + if (!IsOpusHead(packet)) + break; + + unsigned channels; + if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || + !audio_valid_channel_count(channels)) { + result = false; + break; + } + + result = true; + } else if (!result) + break; + else if (IsOpusTags(packet)) { + if (!ScanOpusTags(packet.packet, packet.bytes, + handler, handler_ctx)) + result = false; + + break; + } + } + + if (packet.e_o_s || SeekFindEOS(oy, os, packet, is)) + tag_handler_invoke_duration(handler, handler_ctx, + packet.granulepos / opus_sample_rate); + + ogg_stream_clear(&os); + + return result; +} + +static const char *const opus_suffixes[] = { + "opus", + "ogg", + "oga", + nullptr +}; + +static const char *const opus_mime_types[] = { + "audio/opus", + nullptr +}; + +const struct decoder_plugin opus_decoder_plugin = { + "opus", + mpd_opus_init, + nullptr, + mpd_opus_stream_decode, + nullptr, + nullptr, + mpd_opus_scan_stream, + nullptr, + opus_suffixes, + opus_mime_types, +}; diff --git a/src/decoder/OpusDecoderPlugin.h b/src/decoder/OpusDecoderPlugin.h new file mode 100644 index 000000000..c95d6ded3 --- /dev/null +++ b/src/decoder/OpusDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_OPUS_H +#define MPD_DECODER_OPUS_H + +extern const struct decoder_plugin opus_decoder_plugin; + +#endif diff --git a/src/decoder/OpusHead.cxx b/src/decoder/OpusHead.cxx new file mode 100644 index 000000000..c57e08e10 --- /dev/null +++ b/src/decoder/OpusHead.cxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OpusHead.hxx" + +#include <stdint.h> +#include <string.h> + +struct OpusHead { + char signature[8]; + uint8_t version, channels; + uint16_t pre_skip; + uint32_t sample_rate; + uint16_t output_gain; + uint8_t channel_mapping; +}; + +bool +ScanOpusHeader(const void *data, size_t size, unsigned &channels_r) +{ + const OpusHead *h = (const OpusHead *)data; + if (size < 19 || (h->version & 0xf0) != 0) + return false; + + channels_r = h->channels; + return true; +} diff --git a/src/decoder/OpusHead.hxx b/src/decoder/OpusHead.hxx new file mode 100644 index 000000000..9f75c4f70 --- /dev/null +++ b/src/decoder/OpusHead.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_HEAD_HXX +#define MPD_OPUS_HEAD_HXX + +#include "check.h" + +#include <stddef.h> + +bool +ScanOpusHeader(const void *data, size_t size, unsigned &channels_r); + +#endif diff --git a/src/decoder/OpusReader.hxx b/src/decoder/OpusReader.hxx new file mode 100644 index 000000000..1fd07b55c --- /dev/null +++ b/src/decoder/OpusReader.hxx @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_READER_HXX +#define MPD_OPUS_READER_HXX + +#include "check.h" +#include "string_util.h" + +#include <stdint.h> +#include <string.h> + +class OpusReader { + const uint8_t *p, *const end; + +public: + OpusReader(const void *_p, size_t size) + :p((const uint8_t *)_p), end(p + size) {} + + bool Skip(size_t length) { + p += length; + return p <= end; + } + + const void *Read(size_t length) { + const uint8_t *result = p; + return Skip(length) + ? result + : nullptr; + } + + bool Expect(const void *value, size_t length) { + const void *data = Read(length); + return data != nullptr && memcmp(value, data, length) == 0; + } + + bool ReadByte(uint8_t &value_r) { + if (p >= end) + return false; + + value_r = *p++; + return true; + } + + bool ReadShort(uint16_t &value_r) { + const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); + if (value == nullptr) + return false; + + value_r = value[0] | (value[1] << 8); + return true; + } + + bool ReadWord(uint32_t &value_r) { + const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); + if (value == nullptr) + return false; + + value_r = value[0] | (value[1] << 8) + | (value[2] << 16) | (value[3] << 24); + return true; + } + + bool SkipString() { + uint32_t length; + return ReadWord(length) && Skip(length); + } + + char *ReadString() { + uint32_t length; + if (!ReadWord(length)) + return nullptr; + + const char *src = (const char *)Read(length); + if (src == nullptr) + return nullptr; + + return strndup(src, length); + } +}; + +#endif diff --git a/src/decoder/OpusTags.cxx b/src/decoder/OpusTags.cxx new file mode 100644 index 000000000..cb35a6247 --- /dev/null +++ b/src/decoder/OpusTags.cxx @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OpusTags.hxx" +#include "OpusReader.hxx" +#include "XiphTags.h" +#include "tag_handler.h" + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +static void +ScanOneOpusTag(const char *name, const char *value, + const struct tag_handler *handler, void *ctx) +{ + tag_handler_invoke_pair(handler, ctx, name, value); + + if (handler->tag != nullptr) { + enum tag_type t = tag_table_lookup_i(xiph_tags, name); + if (t != TAG_NUM_OF_ITEM_TYPES) + tag_handler_invoke_tag(handler, ctx, t, value); + } +} + +bool +ScanOpusTags(const void *data, size_t size, + const struct tag_handler *handler, void *ctx) +{ + OpusReader r(data, size); + if (!r.Expect("OpusTags", 8)) + return false; + + if (handler->pair == nullptr && handler->tag == nullptr) + return true; + + if (!r.SkipString()) + return false; + + uint32_t n; + if (!r.ReadWord(n)) + return false; + + while (n-- > 0) { + char *p = r.ReadString(); + if (p == nullptr) + return false; + + char *eq = strchr(p, '='); + if (eq != nullptr && eq > p) { + *eq = 0; + + ScanOneOpusTag(p, eq + 1, handler, ctx); + } + + free(p); + } + + return true; +} diff --git a/src/decoder/OpusTags.hxx b/src/decoder/OpusTags.hxx new file mode 100644 index 000000000..2f3eec844 --- /dev/null +++ b/src/decoder/OpusTags.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_TAGS_HXX +#define MPD_OPUS_TAGS_HXX + +#include "check.h" + +#include <stddef.h> + +bool +ScanOpusTags(const void *data, size_t size, + const struct tag_handler *handler, void *ctx); + +#endif diff --git a/src/decoder/vorbis_comments.c b/src/decoder/VorbisComments.cxx index 6c2d57b72..10fe22369 100644 --- a/src/decoder/vorbis_comments.c +++ b/src/decoder/VorbisComments.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,8 @@ */ #include "config.h" -#include "vorbis_comments.h" +#include "VorbisComments.hxx" +#include "XiphTags.h" #include "tag.h" #include "tag_table.h" #include "tag_handler.h" @@ -95,13 +96,6 @@ vorbis_copy_comment(const char *comment, return false; } -static const struct tag_table vorbis_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - static void vorbis_scan_comment(const char *comment, const struct tag_handler *handler, void *handler_ctx) @@ -119,14 +113,14 @@ vorbis_scan_comment(const char *comment, g_free(name); } - for (const struct tag_table *i = vorbis_tags; i->name != NULL; ++i) + for (const struct tag_table *i = xiph_tags; i->name != NULL; ++i) if (vorbis_copy_comment(comment, i->name, i->type, handler, handler_ctx)) return; for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) if (vorbis_copy_comment(comment, - tag_item_names[i], i, + tag_item_names[i], tag_type(i), handler, handler_ctx)) return; } diff --git a/src/decoder/vorbis_comments.h b/src/decoder/VorbisComments.hxx index c15096930..8212cac47 100644 --- a/src/decoder/vorbis_comments.h +++ b/src/decoder/VorbisComments.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_VORBIS_COMMENTS_H -#define MPD_VORBIS_COMMENTS_H +#ifndef MPD_VORBIS_COMMENTS_HXX +#define MPD_VORBIS_COMMENTS_HXX #include "check.h" diff --git a/src/decoder/vorbis_decoder_plugin.c b/src/decoder/VorbisDecoderPlugin.cxx index 15cdc0ca9..01a558def 100644 --- a/src/decoder/vorbis_decoder_plugin.c +++ b/src/decoder/VorbisDecoderPlugin.cxx @@ -18,10 +18,16 @@ */ #include "config.h" -#include "vorbis_comments.h" -#include "_ogg_common.h" +#include "VorbisDecoderPlugin.h" +#include "VorbisComments.hxx" +#include "decoder_api.h" +#include "OggCodec.hxx" + +extern "C" { #include "audio_check.h" #include "uri.h" +} + #include "tag_handler.h" #ifndef HAVE_TREMOR @@ -48,12 +54,11 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "vorbis" -#define OGG_CHUNK_SIZE 4096 #if G_BYTE_ORDER == G_BIG_ENDIAN -#define OGG_DECODE_USE_BIGENDIAN 1 +#define VORBIS_BIG_ENDIAN true #else -#define OGG_DECODE_USE_BIGENDIAN 0 +#define VORBIS_BIG_ENDIAN false #endif struct vorbis_input_stream { @@ -65,10 +70,9 @@ struct vorbis_input_stream { static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) { - struct vorbis_input_stream *vis = data; - size_t ret; - - ret = decoder_read(vis->decoder, vis->input_stream, ptr, size * nmemb); + struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; + size_t ret = decoder_read(vis->decoder, vis->input_stream, + ptr, size * nmemb); errno = 0; @@ -77,7 +81,7 @@ static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence) { - struct vorbis_input_stream *vis = data; + struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; return vis->seekable && (!vis->decoder || decoder_get_command(vis->decoder) != DECODE_COMMAND_STOP) && @@ -93,16 +97,16 @@ static int ogg_close_cb(G_GNUC_UNUSED void *data) static long ogg_tell_cb(void *data) { - const struct vorbis_input_stream *vis = data; + struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; return (long)vis->input_stream->offset; } static const ov_callbacks vorbis_is_callbacks = { - .read_func = ogg_read_cb, - .seek_func = ogg_seek_cb, - .close_func = ogg_close_cb, - .tell_func = ogg_tell_cb, + ogg_read_cb, + ogg_seek_cb, + ogg_close_cb, + ogg_tell_cb, }; static const char * @@ -135,9 +139,7 @@ vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf, { vis->decoder = decoder; vis->input_stream = input_stream; - vis->seekable = input_stream->seekable && - (input_stream->uri == NULL || - !uri_has_scheme(input_stream->uri)); + vis->seekable = input_stream_cheap_seeking(input_stream); int ret = ov_open_callbacks(vis, vf, NULL, 0, vorbis_is_callbacks); if (ret < 0) { @@ -155,9 +157,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 +165,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 +247,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 +288,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 +298,13 @@ vorbis_stream_decode(struct decoder *decoder, prev_section = current_section; } - if ((test = ov_bitrate_instant(&vf)) > 0) - bitRate = test / 1000; + long test = ov_bitrate_instant(&vf); + if (test > 0) + kbit_rate = test / 1000; cmd = decoder_data(decoder, input_stream, - chunk, ret, - bitRate); + buffer, nbytes, + kbit_rate); } while (cmd != DECODE_COMMAND_STOP); ov_clear(&vf); @@ -306,9 +347,14 @@ static const char *const vorbis_mime_types[] = { }; const struct decoder_plugin vorbis_decoder_plugin = { - .name = "vorbis", - .stream_decode = vorbis_stream_decode, - .scan_stream = vorbis_scan_stream, - .suffixes = vorbis_suffixes, - .mime_types = vorbis_mime_types + "vorbis", + nullptr, + nullptr, + vorbis_stream_decode, + nullptr, + nullptr, + vorbis_scan_stream, + nullptr, + vorbis_suffixes, + vorbis_mime_types }; diff --git a/src/decoder/VorbisDecoderPlugin.h b/src/decoder/VorbisDecoderPlugin.h new file mode 100644 index 000000000..618c9ffde --- /dev/null +++ b/src/decoder/VorbisDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_VORBIS_H +#define MPD_DECODER_VORBIS_H + +extern const struct decoder_plugin vorbis_decoder_plugin; + +#endif diff --git a/src/decoder/wavpack_decoder_plugin.c b/src/decoder/WavpackDecoderPlugin.cxx index 9ebd0fccc..fdd910c84 100644 --- a/src/decoder/wavpack_decoder_plugin.c +++ b/src/decoder/WavpackDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,13 @@ */ #include "config.h" +#include "WavpackDecoderPlugin.hxx" #include "decoder_api.h" + +extern "C" { #include "audio_check.h" -#include "path.h" -#include "utils.h" -#include "tag_table.h" +} + #include "tag_handler.h" #include "tag_ape.h" @@ -30,7 +32,6 @@ #include <glib.h> #include <assert.h> -#include <unistd.h> #include <stdio.h> #include <stdlib.h> @@ -53,18 +54,18 @@ typedef void (*format_samples_t)( static void format_samples_int(int bytes_per_sample, void *buffer, uint32_t count) { - int32_t *src = buffer; + int32_t *src = (int32_t *)buffer; switch (bytes_per_sample) { case 1: { - int8_t *dst = buffer; + int8_t *dst = (int8_t *)buffer; /* * The asserts like the following one are because we do the * formatting of samples within a single buffer. The size * of the output samples never can be greater than the size * of the input ones. Otherwise we would have an overflow. */ - assert_static(sizeof(*dst) <= sizeof(*src)); + static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); /* pass through and align 8-bit samples */ while (count--) { @@ -73,8 +74,8 @@ format_samples_int(int bytes_per_sample, void *buffer, uint32_t count) break; } case 2: { - uint16_t *dst = buffer; - assert_static(sizeof(*dst) <= sizeof(*src)); + uint16_t *dst = (uint16_t *)buffer; + static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); /* pass through and align 16-bit samples */ while (count--) { @@ -97,7 +98,7 @@ static void format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer, uint32_t count) { - float *p = buffer; + float *p = (float *)buffer; while (count--) { *p /= (1 << 23); @@ -359,7 +360,7 @@ static struct wavpack_input * wpin(void *id) { assert(id); - return id; + return (struct wavpack_input *)id; } static int32_t @@ -441,14 +442,14 @@ wavpack_input_can_seek(void *id) } static WavpackStreamReader mpd_is_reader = { - .read_bytes = wavpack_input_read_bytes, - .get_pos = wavpack_input_get_pos, - .set_pos_abs = wavpack_input_set_pos_abs, - .set_pos_rel = wavpack_input_set_pos_rel, - .push_back_byte = wavpack_input_push_back_byte, - .get_length = wavpack_input_get_length, - .can_seek = wavpack_input_can_seek, - .write_bytes = NULL /* no need to write edited tags */ + wavpack_input_read_bytes, + wavpack_input_get_pos, + wavpack_input_set_pos_abs, + wavpack_input_set_pos_rel, + wavpack_input_push_back_byte, + wavpack_input_get_length, + wavpack_input_can_seek, + nullptr /* no need to write edited tags */ }; static void @@ -475,7 +476,7 @@ wavpack_open_wvc(struct decoder *decoder, const char *uri, * single files. utf8url is not absolute file path :/ */ if (uri == NULL) - return false; + return nullptr; wvc_url = g_strconcat(uri, "c", NULL); is_wvc = input_stream_open(wvc_url, mutex, cond, NULL); @@ -587,10 +588,14 @@ static char const *const wavpack_mime_types[] = { }; const struct decoder_plugin wavpack_decoder_plugin = { - .name = "wavpack", - .stream_decode = wavpack_streamdecode, - .file_decode = wavpack_filedecode, - .scan_file = wavpack_scan_file, - .suffixes = wavpack_suffixes, - .mime_types = wavpack_mime_types + "wavpack", + nullptr, + nullptr, + wavpack_streamdecode, + wavpack_filedecode, + wavpack_scan_file, + nullptr, + nullptr, + wavpack_suffixes, + wavpack_mime_types }; diff --git a/src/decoder/WavpackDecoderPlugin.hxx b/src/decoder/WavpackDecoderPlugin.hxx new file mode 100644 index 000000000..9ebe6354f --- /dev/null +++ b/src/decoder/WavpackDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_WAVPACK_HXX +#define MPD_DECODER_WAVPACK_HXX + +extern const struct decoder_plugin wavpack_decoder_plugin; + +#endif diff --git a/src/decoder/XiphTags.c b/src/decoder/XiphTags.c new file mode 100644 index 000000000..d55787b94 --- /dev/null +++ b/src/decoder/XiphTags.c @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "XiphTags.h" + +const struct tag_table xiph_tags[] = { + { "tracknumber", TAG_TRACK }, + { "discnumber", TAG_DISC }, + { "album artist", TAG_ALBUM_ARTIST }, + { NULL, TAG_NUM_OF_ITEM_TYPES } +}; diff --git a/src/decoder/XiphTags.h b/src/decoder/XiphTags.h new file mode 100644 index 000000000..22a4e2204 --- /dev/null +++ b/src/decoder/XiphTags.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_XIPH_TAGS_H +#define MPD_XIPH_TAGS_H + +#include "check.h" +#include "tag_table.h" + +extern const struct tag_table xiph_tags[]; + +#endif diff --git a/src/decoder/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..0c27ac119 100644 --- a/src/decoder/mad_decoder_plugin.c +++ b/src/decoder/mad_decoder_plugin.c @@ -76,9 +76,9 @@ mad_fixed_to_24_sample(mad_fixed_t sample) sample = sample + (1L << (MAD_F_FRACBITS - bits)); /* clip */ - if (sample > MAX) + if (gcc_unlikely(sample > MAX)) sample = MAX; - else if (sample < MIN) + else if (gcc_unlikely(sample < MIN)) sample = MIN; /* quantize */ @@ -359,15 +359,14 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize, struct replay_gain_info rgi; char *mixramp_start; char *mixramp_end; - float replay_gain_db = 0; if (parse_id3_replay_gain_info(&rgi, id3_tag)) { - replay_gain_db = decoder_replay_gain(data->decoder, &rgi); + decoder_replay_gain(data->decoder, &rgi); data->found_replay_gain = true; } if (parse_id3_mixramp(&mixramp_start, &mixramp_end, id3_tag)) - decoder_mixramp(data->decoder, replay_gain_db, + decoder_mixramp(data->decoder, mixramp_start, mixramp_end); } diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx index 5d162f179..175d2ee7c 100644 --- a/src/decoder/sidplay_decoder_plugin.cxx +++ b/src/decoder/sidplay_decoder_plugin.cxx @@ -18,9 +18,9 @@ */ #include "config.h" +#include "../decoder_api.h" extern "C" { -#include "../decoder_api.h" #include "tag_handler.h" } @@ -104,7 +104,7 @@ sidplay_init(const struct config_param *param) return true; } -void +static void sidplay_finish() { g_pattern_spec_free(path_with_subtune); @@ -136,7 +136,7 @@ get_container_name(const char *path_fs) * returns tune number from file.sid/tune_xxx.sid style path or 1 if * no subtune is appended */ -static int +static unsigned get_song_num(const char *path_fs) { if(g_pattern_match(path_with_subtune, @@ -172,7 +172,7 @@ get_song_length(const char *path_fs) char md5sum[SIDTUNE_MD5_LENGTH+1]; tune.createMD5(md5sum); - int song_num=get_song_num(path_fs); + const unsigned song_num = get_song_num(path_fs); gsize num_items; gchar **values=g_key_file_get_string_list(songlength_database, @@ -330,7 +330,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) decoder_command_finished(decoder); } - if (song_len > 0 && player.time() >= song_len) + if (song_len > 0 && player.time() >= (unsigned)song_len) break; } while (cmd != DECODE_COMMAND_STOP); diff --git a/src/decoder_api.h b/src/decoder_api.h index 6e011c395..3f84ca8bc 100644 --- a/src/decoder_api.h +++ b/src/decoder_api.h @@ -38,6 +38,10 @@ #include <stdbool.h> +#ifdef __cplusplus +extern "C" { +#endif + /** * Notify the player thread that it has finished initialization and * that it has read the song's meta data. @@ -152,9 +156,8 @@ decoder_tag(struct decoder *decoder, struct input_stream *is, * @param decoder the decoder object * @param rgi the replay_gain_info object; may be NULL to invalidate * the previous replay gain values - * @return the replay gain adjustment used */ -float +void decoder_replay_gain(struct decoder *decoder, const struct replay_gain_info *replay_gain_info); @@ -162,12 +165,15 @@ decoder_replay_gain(struct decoder *decoder, * Store MixRamp tags. * * @param decoder the decoder object - * @param replay_gain_db the ReplayGain adjustment used for this song * @param mixramp_start the mixramp_start tag; may be NULL to invalidate * @param mixramp_end the mixramp_end tag; may be NULL to invalidate */ void -decoder_mixramp(struct decoder *decoder, float replay_gain_db, +decoder_mixramp(struct decoder *decoder, char *mixramp_start, char *mixramp_end); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/decoder_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_list.c b/src/decoder_list.c index 177b632ad..d6818d109 100644 --- a/src/decoder_list.c +++ b/src/decoder_list.c @@ -20,12 +20,16 @@ #include "config.h" #include "decoder_list.h" #include "decoder_plugin.h" -#include "utils.h" #include "conf.h" #include "mpd_error.h" #include "decoder/pcm_decoder_plugin.h" #include "decoder/dsdiff_decoder_plugin.h" #include "decoder/dsf_decoder_plugin.h" +#include "decoder/FLACDecoderPlugin.h" +#include "decoder/OpusDecoderPlugin.h" +#include "decoder/VorbisDecoderPlugin.h" +#include "decoder/AdPlugDecoderPlugin.h" +#include "decoder/WavpackDecoderPlugin.hxx" #include <glib.h> @@ -33,15 +37,11 @@ extern const struct decoder_plugin mad_decoder_plugin; extern const struct decoder_plugin mpg123_decoder_plugin; -extern const struct decoder_plugin vorbis_decoder_plugin; -extern const struct decoder_plugin flac_decoder_plugin; -extern const struct decoder_plugin oggflac_decoder_plugin; extern const struct decoder_plugin sndfile_decoder_plugin; extern const struct decoder_plugin audiofile_decoder_plugin; extern const struct decoder_plugin mp4ff_decoder_plugin; extern const struct decoder_plugin faad_decoder_plugin; extern const struct decoder_plugin mpcdec_decoder_plugin; -extern const struct decoder_plugin wavpack_decoder_plugin; extern const struct decoder_plugin modplug_decoder_plugin; extern const struct decoder_plugin mikmod_decoder_plugin; extern const struct decoder_plugin sidplay_decoder_plugin; @@ -66,6 +66,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 +104,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..53d16a8e4 --- /dev/null +++ b/src/encoder/OpusEncoderPlugin.cxx @@ -0,0 +1,433 @@ +/* + * 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; + + opus_encoder() { + encoder_struct_init(&encoder, &opus_encoder_plugin); + } +}; + +gcc_const +static inline GQuark +opus_encoder_quark(void) +{ + return g_quark_from_static_string("opus_encoder"); +} + +static bool +opus_encoder_configure(struct opus_encoder *encoder, + const struct config_param *param, GError **error_r) +{ + const char *value = config_get_block_string(param, "bitrate", "auto"); + if (strcmp(value, "auto") == 0) + encoder->bitrate = OPUS_AUTO; + else if (strcmp(value, "max") == 0) + encoder->bitrate = OPUS_BITRATE_MAX; + else { + char *endptr; + encoder->bitrate = strtoul(value, &endptr, 10); + if (endptr == value || *endptr != 0 || + encoder->bitrate < 500 || encoder->bitrate > 512000) { + g_set_error(error_r, opus_encoder_quark(), 0, + "Invalid bit rate"); + return false; + } + } + + encoder->complexity = config_get_block_unsigned(param, "complexity", + 10); + if (encoder->complexity > 10) { + g_set_error(error_r, opus_encoder_quark(), 0, + "Invalid complexity"); + return false; + } + + value = config_get_block_string(param, "signal", "auto"); + if (strcmp(value, "auto") == 0) + encoder->bitrate = OPUS_AUTO; + else if (strcmp(value, "voice") == 0) + encoder->bitrate = OPUS_SIGNAL_VOICE; + else if (strcmp(value, "music") == 0) + encoder->bitrate = OPUS_SIGNAL_MUSIC; + else { + g_set_error(error_r, opus_encoder_quark(), 0, + "Invalid signal"); + return false; + } + + return true; +} + +static struct encoder * +opus_encoder_init(const struct config_param *param, GError **error) +{ + opus_encoder *encoder = new opus_encoder(); + + /* load configuration from "param" */ + if (!opus_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + delete encoder; + return NULL; + } + + return &encoder->encoder; +} + +static void +opus_encoder_finish(struct encoder *_encoder) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + /* the real libopus cleanup was already performed by + opus_encoder_close(), so no real work here */ + delete encoder; +} + +static bool +opus_encoder_open(struct encoder *_encoder, + struct audio_format *audio_format, + GError **error_r) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + /* libopus supports only 48 kHz */ + audio_format->sample_rate = 48000; + + if (audio_format->channels > 2) + audio_format->channels = 1; + + switch ((enum sample_format)audio_format->format) { + case SAMPLE_FORMAT_S16: + case SAMPLE_FORMAT_FLOAT: + break; + + case SAMPLE_FORMAT_S8: + audio_format->format = SAMPLE_FORMAT_S16; + break; + + default: + audio_format->format = SAMPLE_FORMAT_FLOAT; + break; + } + + encoder->audio_format = *audio_format; + encoder->frame_size = audio_format_frame_size(audio_format); + + int error; + encoder->enc = opus_encoder_create(audio_format->sample_rate, + audio_format->channels, + OPUS_APPLICATION_AUDIO, + &error); + if (encoder->enc == nullptr) { + g_set_error_literal(error_r, opus_encoder_quark(), error, + opus_strerror(error)); + return false; + } + + opus_encoder_ctl(encoder->enc, OPUS_SET_BITRATE(encoder->bitrate)); + opus_encoder_ctl(encoder->enc, + OPUS_SET_COMPLEXITY(encoder->complexity)); + opus_encoder_ctl(encoder->enc, OPUS_SET_SIGNAL(encoder->signal)); + + opus_encoder_ctl(encoder->enc, OPUS_GET_LOOKAHEAD(&encoder->lookahead)); + + encoder->buffer_frames = audio_format->sample_rate / 50; + encoder->buffer_size = encoder->frame_size * encoder->buffer_frames; + encoder->buffer_position = 0; + encoder->buffer = (unsigned char *)g_malloc(encoder->buffer_size); + + encoder->stream.Initialize(g_random_int()); + encoder->packetno = 0; + + return true; +} + +static void +opus_encoder_close(struct encoder *_encoder) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Deinitialize(); + g_free(encoder->buffer); + opus_encoder_destroy(encoder->enc); +} + +static bool +opus_encoder_do_encode(struct opus_encoder *encoder, bool eos, + GError **error_r) +{ + assert(encoder->buffer_position == encoder->buffer_size); + + opus_int32 result = + encoder->audio_format.format == SAMPLE_FORMAT_S16 + ? opus_encode(encoder->enc, + (const opus_int16 *)encoder->buffer, + encoder->buffer_frames, + encoder->buffer2, + sizeof(encoder->buffer2)) + : opus_encode_float(encoder->enc, + (const float *)encoder->buffer, + encoder->buffer_frames, + encoder->buffer2, + sizeof(encoder->buffer2)); + if (result < 0) { + g_set_error_literal(error_r, opus_encoder_quark(), 0, + "Opus encoder error"); + return false; + } + + encoder->granulepos += encoder->buffer_frames; + + ogg_packet packet; + packet.packet = encoder->buffer2; + packet.bytes = result; + packet.b_o_s = false; + packet.e_o_s = eos; + packet.granulepos = encoder->granulepos; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + + encoder->buffer_position = 0; + + return true; +} + +static bool +opus_encoder_end(struct encoder *_encoder, GError **error_r) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Flush(); + + memset(encoder->buffer + encoder->buffer_position, 0, + encoder->buffer_size - encoder->buffer_position); + encoder->buffer_position = encoder->buffer_size; + + return opus_encoder_do_encode(encoder, true, error_r); +} + +static bool +opus_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Flush(); + return true; +} + +static bool +opus_encoder_write_silence(struct opus_encoder *encoder, unsigned fill_frames, + GError **error_r) +{ + size_t fill_bytes = fill_frames * encoder->frame_size; + + while (fill_bytes > 0) { + size_t nbytes = + encoder->buffer_size - encoder->buffer_position; + if (nbytes > fill_bytes) + nbytes = fill_bytes; + + memset(encoder->buffer + encoder->buffer_position, + 0, nbytes); + encoder->buffer_position += nbytes; + fill_bytes -= nbytes; + + if (encoder->buffer_position == encoder->buffer_size && + !opus_encoder_do_encode(encoder, false, error_r)) + return false; + } + + return true; +} + +static bool +opus_encoder_write(struct encoder *_encoder, + const void *_data, size_t length, + GError **error_r) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + const uint8_t *data = (const uint8_t *)_data; + + if (encoder->lookahead > 0) { + /* generate some silence at the beginning of the + stream */ + + assert(encoder->buffer_position == 0); + + if (!opus_encoder_write_silence(encoder, encoder->lookahead, + error_r)) + return false; + + encoder->lookahead = 0; + } + + while (length > 0) { + size_t nbytes = + encoder->buffer_size - encoder->buffer_position; + if (nbytes > length) + nbytes = length; + + memcpy(encoder->buffer + encoder->buffer_position, + data, nbytes); + data += nbytes; + length -= nbytes; + encoder->buffer_position += nbytes; + + if (encoder->buffer_position == encoder->buffer_size && + !opus_encoder_do_encode(encoder, false, error_r)) + return false; + } + + return true; +} + +static void +opus_encoder_generate_head(struct opus_encoder *encoder) +{ + unsigned char header[19]; + memcpy(header, "OpusHead", 8); + header[8] = 1; + header[9] = encoder->audio_format.channels; + *(uint16_t *)(header + 10) = GUINT16_TO_LE(encoder->lookahead); + *(uint32_t *)(header + 12) = + GUINT32_TO_LE(encoder->audio_format.sample_rate); + header[16] = 0; + header[17] = 0; + header[18] = 0; + + ogg_packet packet; + packet.packet = header; + packet.bytes = 19; + packet.b_o_s = true; + packet.e_o_s = false; + packet.granulepos = 0; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + encoder->stream.Flush(); +} + +static void +opus_encoder_generate_tags(struct opus_encoder *encoder) +{ + const char *version = opus_get_version_string(); + size_t version_length = strlen(version); + + size_t comments_size = 8 + 4 + version_length + 4; + unsigned char *comments = (unsigned char *)g_malloc(comments_size); + memcpy(comments, "OpusTags", 8); + *(uint32_t *)(comments + 8) = GUINT32_TO_LE(version_length); + memcpy(comments + 12, version, version_length); + *(uint32_t *)(comments + 12 + version_length) = GUINT32_TO_LE(0); + + ogg_packet packet; + packet.packet = comments; + packet.bytes = comments_size; + packet.b_o_s = false; + packet.e_o_s = false; + packet.granulepos = 0; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + encoder->stream.Flush(); + + g_free(comments); +} + +static size_t +opus_encoder_read(struct encoder *_encoder, void *dest, size_t length) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + if (encoder->packetno == 0) + opus_encoder_generate_head(encoder); + else if (encoder->packetno == 1) + opus_encoder_generate_tags(encoder); + + return encoder->stream.PageOut(dest, length); +} + +static const char * +opus_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +{ + return "audio/ogg"; +} + +const struct encoder_plugin opus_encoder_plugin = { + "opus", + opus_encoder_init, + opus_encoder_finish, + opus_encoder_open, + opus_encoder_close, + opus_encoder_end, + opus_encoder_flush, + nullptr, + nullptr, + opus_encoder_write, + opus_encoder_read, + opus_encoder_get_mime_type, +}; diff --git a/src/encoder/OpusEncoderPlugin.hxx b/src/encoder/OpusEncoderPlugin.hxx new file mode 100644 index 000000000..f54377202 --- /dev/null +++ b/src/encoder/OpusEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ENCODER_OPUS_H +#define MPD_ENCODER_OPUS_H + +extern const struct encoder_plugin opus_encoder_plugin; + +#endif diff --git a/src/encoder/vorbis_encoder.c b/src/encoder/VorbisEncoderPlugin.cxx index 468cf38ee..74048e0cd 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,16 +50,16 @@ struct vorbis_encoder { struct audio_format audio_format; - ogg_stream_state os; - vorbis_dsp_state vd; vorbis_block vb; vorbis_info vi; - bool flush; -}; + OggStream stream; -extern const struct encoder_plugin vorbis_encoder_plugin; + vorbis_encoder() { + encoder_struct_init(&encoder, &vorbis_encoder_plugin); + } +}; static inline GQuark vorbis_encoder_quark(void) @@ -65,8 +71,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 +87,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 +97,8 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder, } else { /* a bit rate was configured */ - value = config_get_block_string(param, "bitrate", NULL); - if (value == NULL) { + value = config_get_block_string(param, "bitrate", nullptr); + if (value == nullptr) { g_set_error(error, vorbis_encoder_quark(), 0, "neither bitrate nor quality defined " "at line %i", @@ -118,14 +124,13 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder, static struct encoder * vorbis_encoder_init(const struct config_param *param, GError **error) { - struct vorbis_encoder *encoder = g_new(struct vorbis_encoder, 1); - encoder_struct_init(&encoder->encoder, &vorbis_encoder_plugin); + vorbis_encoder *encoder = new vorbis_encoder(); /* load configuration from "param" */ if (!vorbis_encoder_configure(encoder, param, error)) { /* configuration has failed, roll back and return error */ - g_free(encoder); - return NULL; + delete encoder; + return nullptr; } return &encoder->encoder; @@ -138,7 +143,7 @@ vorbis_encoder_finish(struct encoder *_encoder) /* the real libvorbis/libogg cleanup was already performed by vorbis_encoder_close(), so no real work here */ - g_free(encoder); + delete encoder; } static bool @@ -174,7 +179,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 +192,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 +214,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 +223,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 +247,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 +261,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 +280,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 +309,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 +340,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 +352,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 +366,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..db6503fb0 100644 --- a/src/encoder/flac_encoder.c +++ b/src/encoder/flac_encoder.c @@ -22,14 +22,18 @@ #include "encoder_plugin.h" #include "audio_format.h" #include "pcm_buffer.h" -#include "fifo_buffer.h" -#include "growing_fifo.h" +#include "util/fifo_buffer.h" +#include "util/growing_fifo.h" #include <assert.h> #include <string.h> #include <FLAC/stream_encoder.h> +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +#error libFLAC is too old +#endif + struct flac_encoder { struct encoder encoder; @@ -98,8 +102,6 @@ static bool flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample, GError **error) { -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -#else if ( !FLAC__stream_encoder_set_compression_level(encoder->fse, encoder->compression)) { g_set_error(error, flac_encoder_quark(), 0, @@ -107,7 +109,7 @@ flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample, encoder->compression); return false; } -#endif + if ( !FLAC__stream_encoder_set_channels(encoder->fse, encoder->audio_format.channels)) { g_set_error(error, flac_encoder_quark(), 0, @@ -135,11 +137,7 @@ flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample, static FLAC__StreamEncoderWriteStatus flac_write_callback(G_GNUC_UNUSED const FLAC__StreamEncoder *fse, const FLAC__byte data[], -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 - unsigned bytes, -#else size_t bytes, -#endif G_GNUC_UNUSED unsigned samples, G_GNUC_UNUSED unsigned current_frame, void *client_data) { @@ -209,24 +207,6 @@ flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, /* this immediately outputs data through callback */ -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 - { - FLAC__StreamEncoderState init_status; - - FLAC__stream_encoder_set_write_callback(encoder->fse, - flac_write_callback); - - init_status = FLAC__stream_encoder_init(encoder->fse); - - if (init_status != FLAC__STREAM_ENCODER_OK) { - g_set_error(error, flac_encoder_quark(), 0, - "failed to initialize encoder: %s\n", - FLAC__StreamEncoderStateString[init_status]); - flac_encoder_close(_encoder); - return false; - } - } -#else { FLAC__StreamEncoderInitStatus init_status; @@ -242,7 +222,6 @@ flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, return false; } } -#endif return true; } diff --git a/src/encoder/null_encoder.c b/src/encoder/null_encoder.c index 48cdf139b..fa1a0a034 100644 --- a/src/encoder/null_encoder.c +++ b/src/encoder/null_encoder.c @@ -20,8 +20,8 @@ #include "config.h" #include "encoder_api.h" #include "encoder_plugin.h" -#include "fifo_buffer.h" -#include "growing_fifo.h" +#include "util/fifo_buffer.h" +#include "util/growing_fifo.h" #include <assert.h> #include <string.h> diff --git a/src/encoder/wave_encoder.c b/src/encoder/wave_encoder.c index 9eeb4d513..a6ee512d4 100644 --- a/src/encoder/wave_encoder.c +++ b/src/encoder/wave_encoder.c @@ -20,8 +20,8 @@ #include "config.h" #include "encoder_api.h" #include "encoder_plugin.h" -#include "fifo_buffer.h" -#include "growing_fifo.h" +#include "util/fifo_buffer.h" +#include "util/growing_fifo.h" #include <assert.h> #include <string.h> diff --git a/src/encoder_list.c b/src/encoder_list.c index 2326c1099..029b4be34 100644 --- a/src/encoder_list.c +++ b/src/encoder_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,11 +20,12 @@ #include "config.h" #include "encoder_list.h" #include "encoder_plugin.h" +#include "encoder/VorbisEncoderPlugin.hxx" +#include "encoder/OpusEncoderPlugin.hxx" #include <string.h> extern const struct encoder_plugin null_encoder_plugin; -extern const struct encoder_plugin vorbis_encoder_plugin; extern const struct encoder_plugin lame_encoder_plugin; extern const struct encoder_plugin twolame_encoder_plugin; extern const struct encoder_plugin wave_encoder_plugin; @@ -35,6 +36,9 @@ const struct encoder_plugin *const encoder_plugins[] = { #ifdef ENABLE_VORBIS_ENCODER &vorbis_encoder_plugin, #endif +#ifdef HAVE_OPUS + &opus_encoder_plugin, +#endif #ifdef ENABLE_LAME_ENCODER &lame_encoder_plugin, #endif diff --git a/src/encoder_list.h b/src/encoder_list.h index fb1c9bf9c..31663c751 100644 --- a/src/encoder_list.h +++ b/src/encoder_list.h @@ -30,6 +30,10 @@ extern const struct encoder_plugin *const encoder_plugins[]; (plugin = *encoder_plugin_iterator) != NULL; \ ++encoder_plugin_iterator) +#ifdef __cplusplus +extern "C" { +#endif + /** * Looks up an encoder plugin by its name. * @@ -40,4 +44,8 @@ extern const struct encoder_plugin *const encoder_plugins[]; const struct encoder_plugin * encoder_plugin_get(const char *name); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/encoder_plugin.h b/src/encoder_plugin.h index 3a42d79f4..e0748a136 100644 --- a/src/encoder_plugin.h +++ b/src/encoder_plugin.h @@ -20,7 +20,7 @@ #ifndef MPD_ENCODER_PLUGIN_H #define MPD_ENCODER_PLUGIN_H -#include <glib.h> +#include "gerror.h" #include <assert.h> #include <stdbool.h> diff --git a/src/event/BufferedSocket.cxx b/src/event/BufferedSocket.cxx new file mode 100644 index 000000000..f84fe808b --- /dev/null +++ b/src/event/BufferedSocket.cxx @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "BufferedSocket.hxx" +#include "SocketError.hxx" +#include "util/fifo_buffer.h" + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +#ifndef WIN32 +#include <sys/types.h> +#include <sys/socket.h> +#endif + +BufferedSocket::~BufferedSocket() +{ + if (input != nullptr) + fifo_buffer_free(input); +} + +BufferedSocket::ssize_t +BufferedSocket::DirectWrite(const void *data, size_t length) +{ + int flags = 0; +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif +#ifdef MSG_DONTWAIT + flags |= MSG_DONTWAIT; +#endif + + const auto nbytes = send(Get(), (const char *)data, length, flags); + if (gcc_unlikely(nbytes < 0)) { + const auto code = GetSocketError(); + if (IsSocketErrorAgain(code)) + return 0; + + Cancel(); + + if (IsSocketErrorClosed(code)) + OnSocketClosed(); + else + OnSocketError(NewSocketError(code)); + } + + return nbytes; +} + +ssize_t +BufferedSocket::DirectRead(void *data, size_t length) +{ + int flags = 0; +#ifdef MSG_DONTWAIT + flags |= MSG_DONTWAIT; +#endif + + const auto nbytes = recv(Get(), (char *)data, length, flags); + if (gcc_likely(nbytes > 0)) + return nbytes; + + if (nbytes == 0) { + OnSocketClosed(); + return -1; + } + + const auto code = GetSocketError(); + if (IsSocketErrorAgain(code)) + return 0; + + if (IsSocketErrorClosed(code)) + OnSocketClosed(); + else + OnSocketError(NewSocketError(code)); + return -1; +} + +bool +BufferedSocket::WriteFromBuffer() +{ + assert(IsDefined()); + + size_t length; + const void *data = output.Read(&length); + if (data == nullptr) { + CancelWrite(); + return true; + } + + auto nbytes = DirectWrite(data, length); + if (gcc_unlikely(nbytes <= 0)) + return nbytes == 0; + + output.Consume(nbytes); + + if (output.IsEmpty()) + CancelWrite(); + + return true; +} + +bool +BufferedSocket::ReadToBuffer() +{ + assert(IsDefined()); + + if (input == nullptr) + input = fifo_buffer_new(8192); + + size_t length; + void *buffer = fifo_buffer_write(input, &length); + assert(buffer != nullptr); + + const auto nbytes = DirectRead(buffer, length); + if (nbytes > 0) + fifo_buffer_append(input, nbytes); + + return nbytes >= 0; +} + +bool +BufferedSocket::Write(const void *data, size_t length) +{ + assert(IsDefined()); + +#if 0 + /* TODO: disabled because this would add overhead on some callers (the ones that often), but it may be useful */ + + if (output.IsEmpty()) { + /* try to write it directly first */ + const auto nbytes = DirectWrite(data, length); + if (gcc_likely(nbytes > 0)) { + data = (const uint8_t *)data + nbytes; + length -= nbytes; + if (length == 0) + return true; + } else if (nbytes < 0) + return false; + } +#endif + + if (!output.Append(data, length)) { + // TODO + OnSocketError(g_error_new_literal(g_quark_from_static_string("buffered_socket"), + 0, "Output buffer is full")); + return false; + } + + ScheduleWrite(); + return true; +} + +bool +BufferedSocket::ResumeInput() +{ + assert(IsDefined()); + + if (input == nullptr) { + ScheduleRead(); + return true; + } + + while (true) { + size_t length; + const void *data = fifo_buffer_read(input, &length); + if (data == nullptr) { + ScheduleRead(); + return true; + } + + const auto result = OnSocketInput(data, length); + switch (result) { + case InputResult::MORE: + if (fifo_buffer_is_full(input)) { + // TODO + OnSocketError(g_error_new_literal(g_quark_from_static_string("buffered_socket"), + 0, "Input buffer is full")); + return false; + } + + ScheduleRead(); + return true; + + case InputResult::PAUSE: + CancelRead(); + return true; + + case InputResult::AGAIN: + continue; + + case InputResult::CLOSED: + return false; + } + } +} + +void +BufferedSocket::ConsumeInput(size_t nbytes) +{ + assert(IsDefined()); + + fifo_buffer_consume(input, nbytes); +} + +void +BufferedSocket::OnSocketReady(unsigned flags) +{ + assert(IsDefined()); + + if (gcc_unlikely(flags & (ERROR|HANGUP))) { + OnSocketClosed(); + return; + } + + if (flags & READ) { + assert(input == nullptr || !fifo_buffer_is_full(input)); + + if (!ReadToBuffer() || !ResumeInput()) + return; + + if (input == nullptr || !fifo_buffer_is_full(input)) + ScheduleRead(); + + /* just in case the OnSocketInput() method has added + data to the output buffer: try to send it now + instead of waiting for the next event loop + iteration */ + if (!output.IsEmpty()) + flags |= WRITE; + } + + if (flags & WRITE) { + assert(!output.IsEmpty()); + + if (!WriteFromBuffer()) + return; + } +} diff --git a/src/event/BufferedSocket.hxx b/src/event/BufferedSocket.hxx new file mode 100644 index 000000000..49b17c86f --- /dev/null +++ b/src/event/BufferedSocket.hxx @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_BUFFERED_SOCKET_HXX +#define MPD_BUFFERED_SOCKET_HXX + +#include "check.h" +#include "SocketMonitor.hxx" +#include "util/PeakBuffer.hxx" +#include "gcc.h" + +#include <type_traits> + +#include <stddef.h> + +struct fifo_buffer; +class EventLoop; + +class BufferedSocket : private SocketMonitor { + typedef std::make_signed<size_t>::type ssize_t; + + fifo_buffer *input; + PeakBuffer output; + +public: + BufferedSocket(int _fd, EventLoop &_loop, + size_t normal_size, size_t peak_size=0) + :SocketMonitor(_fd, _loop), input(nullptr), + output(normal_size, peak_size) { + ScheduleRead(); + } + + ~BufferedSocket(); + + using SocketMonitor::IsDefined; + using SocketMonitor::Close; + +private: + ssize_t DirectWrite(const void *data, size_t length); + ssize_t DirectRead(void *data, size_t length); + + /** + * Send data from the output buffer to the socket. + * + * @return false if the socket has been closed + */ + bool WriteFromBuffer(); + + /** + * Receive data from the socket to the input buffer. + * + * @return false if the socket has been closed + */ + bool ReadToBuffer(); + +protected: + /** + * @return false if the socket has been closed + */ + bool Write(const void *data, size_t length); + + /** + * @return false if the socket has been closed + */ + bool ResumeInput(); + + /** + * Mark a portion of the input buffer "consumed". Only + * allowed to be called from OnSocketInput(). This method + * does not invalidate the pointer passed to OnSocketInput() + * yet. + */ + void ConsumeInput(size_t nbytes); + + enum class InputResult { + /** + * The method was successful, and it is ready to + * read more data. + */ + MORE, + + /** + * The method does not want to get more data for now. + * It will call ResumeInput() when it's ready for + * more. + */ + PAUSE, + + /** + * The method wants to be called again immediately, if + * there's more data in the buffer. + */ + AGAIN, + + /** + * The method has closed the socket. + */ + CLOSED, + }; + + virtual InputResult OnSocketInput(const void *data, size_t length) = 0; + virtual void OnSocketError(GError *error) = 0; + virtual void OnSocketClosed() = 0; + +private: + virtual void OnSocketReady(unsigned flags) override; +}; + +#endif diff --git a/src/event/Loop.hxx b/src/event/Loop.hxx new file mode 100644 index 000000000..31ef1613c --- /dev/null +++ b/src/event/Loop.hxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_EVENT_LOOP_HXX +#define MPD_EVENT_LOOP_HXX + +#include "check.h" +#include "gcc.h" + +#include <glib.h> + +class EventLoop { + GMainContext *context; + GMainLoop *loop; + +public: + EventLoop() + :context(g_main_context_new()), + loop(g_main_loop_new(context, false)) {} + + struct Default {}; + EventLoop(gcc_unused Default _dummy) + :context(g_main_context_ref(g_main_context_default())), + loop(g_main_loop_new(context, false)) {} + + ~EventLoop() { + g_main_loop_unref(loop); + g_main_context_unref(context); + } + + GMainContext *GetContext() { + return context; + } + + void Break() { + g_main_loop_quit(loop); + } + + void Run() { + g_main_loop_run(loop); + } + + guint AddIdle(GSourceFunc function, gpointer data) { + GSource *source = g_idle_source_new(); + g_source_set_callback(source, function, data, NULL); + guint id = g_source_attach(source, GetContext()); + g_source_unref(source); + return id; + } + + GSource *AddTimeout(guint interval_ms, + GSourceFunc function, gpointer data) { + GSource *source = g_timeout_source_new(interval_ms); + g_source_set_callback(source, function, data, nullptr); + g_source_attach(source, GetContext()); + return source; + } + + GSource *AddTimeoutSeconds(guint interval_s, + GSourceFunc function, gpointer data) { + GSource *source = g_timeout_source_new_seconds(interval_s); + g_source_set_callback(source, function, data, nullptr); + g_source_attach(source, GetContext()); + return source; + } +}; + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event/MultiSocketMonitor.cxx b/src/event/MultiSocketMonitor.cxx new file mode 100644 index 000000000..6f20b907c --- /dev/null +++ b/src/event/MultiSocketMonitor.cxx @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MultiSocketMonitor.hxx" +#include "Loop.hxx" +#include "fd_util.h" +#include "gcc.h" + +#include <assert.h> + +/** + * The vtable for our GSource implementation. Unfortunately, we + * cannot declare it "const", because g_source_new() takes a non-const + * pointer, for whatever reason. + */ +static GSourceFuncs multi_socket_monitor_source_funcs = { + MultiSocketMonitor::Prepare, + MultiSocketMonitor::Check, + MultiSocketMonitor::Dispatch, + nullptr, + nullptr, + nullptr, +}; + +MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop) + :loop(_loop), + source((Source *)g_source_new(&multi_socket_monitor_source_funcs, + sizeof(*source))) { + source->monitor = this; + + g_source_attach(&source->base, loop.GetContext()); +} + +MultiSocketMonitor::~MultiSocketMonitor() +{ + g_source_destroy(&source->base); + g_source_unref(&source->base); + source = nullptr; +} + +bool +MultiSocketMonitor::Check() const +{ + if (CheckSockets()) + return true; + + for (const auto &i : fds) + if (i.revents != 0) + return true; + + return false; +} + +/* + * GSource methods + * + */ + +gboolean +MultiSocketMonitor::Prepare(GSource *_source, gint *timeout_r) +{ + Source &source = *(Source *)_source; + MultiSocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + return monitor.Prepare(timeout_r); +} + +gboolean +MultiSocketMonitor::Check(GSource *_source) +{ + const Source &source = *(const Source *)_source; + const MultiSocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + return monitor.Check(); +} + +gboolean +MultiSocketMonitor::Dispatch(GSource *_source, + gcc_unused GSourceFunc callback, + gcc_unused gpointer user_data) +{ + Source &source = *(Source *)_source; + MultiSocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + monitor.Dispatch(); + return true; +} diff --git a/src/event/MultiSocketMonitor.hxx b/src/event/MultiSocketMonitor.hxx new file mode 100644 index 000000000..9d0e1502b --- /dev/null +++ b/src/event/MultiSocketMonitor.hxx @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MULTI_SOCKET_MONITOR_HXX +#define MPD_MULTI_SOCKET_MONITOR_HXX + +#include "check.h" +#include "gcc.h" + +#include <glib.h> + +#include <forward_list> + +#include <assert.h> + +#ifdef WIN32 +/* ERRORis a WIN32 macro that poisons our namespace; this is a + kludge to allow us to use it anyway */ +#ifdef ERROR +#undef ERROR +#endif +#endif + +class EventLoop; + +/** + * Monitor multiple sockets. + */ +class MultiSocketMonitor { + struct Source { + GSource base; + + MultiSocketMonitor *monitor; + }; + + EventLoop &loop; + Source *source; + std::forward_list<GPollFD> fds; + +public: + static constexpr unsigned READ = G_IO_IN; + static constexpr unsigned WRITE = G_IO_OUT; + static constexpr unsigned ERROR = G_IO_ERR; + static constexpr unsigned HANGUP = G_IO_HUP; + + MultiSocketMonitor(EventLoop &_loop); + ~MultiSocketMonitor(); + +public: + gcc_pure + gint64 GetTime() const { + return g_source_get_time(&source->base); + } + + void InvalidateSockets() { + /* no-op because GLib always calls the GSource's + "prepare" method before each poll() anyway */ + } + + void AddSocket(int fd, unsigned events) { + fds.push_front({fd, gushort(events), 0}); + g_source_add_poll(&source->base, &fds.front()); + } + + template<typename E> + void UpdateSocketList(E &&e) { + for (auto prev = fds.before_begin(), end = fds.end(), + i = std::next(prev); + i != end; i = std::next(prev)) { + assert(i->events != 0); + + unsigned events = e(i->fd); + if (events != 0) { + i->events = events; + prev = i; + } else { + g_source_remove_poll(&source->base, &*i); + fds.erase_after(prev); + } + } + } + +protected: + virtual void PrepareSockets(gcc_unused gint *timeout_r) {} + virtual bool CheckSockets() const { return false; } + virtual void DispatchSockets() = 0; + +public: + /* GSource callbacks */ + static gboolean Prepare(GSource *source, gint *timeout_r); + static gboolean Check(GSource *source); + static gboolean Dispatch(GSource *source, GSourceFunc callback, + gpointer user_data); + +private: + bool Prepare(gint *timeout_r) { + PrepareSockets(timeout_r); + return false; + } + + bool Check() const; + + void Dispatch() { + DispatchSockets(); + } +}; + +#endif diff --git a/src/event/SocketMonitor.cxx b/src/event/SocketMonitor.cxx new file mode 100644 index 000000000..b75dc72a4 --- /dev/null +++ b/src/event/SocketMonitor.cxx @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SocketMonitor.hxx" +#include "Loop.hxx" +#include "fd_util.h" +#include "gcc.h" + +#include <assert.h> + +/* + * GSource methods + * + */ + +gboolean +SocketMonitor::Prepare(gcc_unused GSource *source, gcc_unused gint *timeout_r) +{ + return false; +} + +gboolean +SocketMonitor::Check(GSource *_source) +{ + const Source &source = *(const Source *)_source; + const SocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + return monitor.Check(); +} + +gboolean +SocketMonitor::Dispatch(GSource *_source, + gcc_unused GSourceFunc callback, + gcc_unused gpointer user_data) +{ + Source &source = *(Source *)_source; + SocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + monitor.Dispatch(); + return true; +} + +/** + * The vtable for our GSource implementation. Unfortunately, we + * cannot declare it "const", because g_source_new() takes a non-const + * pointer, for whatever reason. + */ +static GSourceFuncs socket_monitor_source_funcs = { + SocketMonitor::Prepare, + SocketMonitor::Check, + SocketMonitor::Dispatch, + nullptr, + nullptr, + nullptr, +}; + +SocketMonitor::SocketMonitor(int _fd, EventLoop &_loop) + :fd(-1), loop(_loop), + source(nullptr) { + assert(_fd >= 0); + + Open(_fd); +} + +SocketMonitor::~SocketMonitor() +{ + if (IsDefined()) + Close(); +} + +void +SocketMonitor::Open(int _fd) +{ + assert(fd < 0); + assert(source == nullptr); + assert(_fd >= 0); + + fd = _fd; + poll = {fd, 0, 0}; + + source = (Source *)g_source_new(&socket_monitor_source_funcs, + sizeof(*source)); + source->monitor = this; + + g_source_attach(&source->base, loop.GetContext()); + g_source_add_poll(&source->base, &poll); +} + +void +SocketMonitor::Close() +{ + assert(IsDefined()); + + Cancel(); + + close_socket(fd); + fd = -1; + + g_source_destroy(&source->base); + g_source_unref(&source->base); + source = nullptr; +} diff --git a/src/event/SocketMonitor.hxx b/src/event/SocketMonitor.hxx new file mode 100644 index 000000000..236e5fbda --- /dev/null +++ b/src/event/SocketMonitor.hxx @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SOCKET_MONITOR_HXX +#define MPD_SOCKET_MONITOR_HXX + +#include "check.h" + +#include <glib.h> + +#include <assert.h> + +#ifdef WIN32 +/* ERRORis a WIN32 macro that poisons our namespace; this is a + kludge to allow us to use it anyway */ +#ifdef ERROR +#undef ERROR +#endif +#endif + +class EventLoop; + +class SocketMonitor { + struct Source { + GSource base; + + SocketMonitor *monitor; + }; + + int fd; + EventLoop &loop; + Source *source; + GPollFD poll; + +public: + static constexpr unsigned READ = G_IO_IN; + static constexpr unsigned WRITE = G_IO_OUT; + static constexpr unsigned ERROR = G_IO_ERR; + static constexpr unsigned HANGUP = G_IO_HUP; + + SocketMonitor(EventLoop &_loop) + :fd(-1), loop(_loop), source(nullptr) {} + + SocketMonitor(int _fd, EventLoop &_loop); + + ~SocketMonitor(); + + bool IsDefined() const { + return fd >= 0; + } + + int Get() const { + assert(IsDefined()); + + return fd; + } + + void Open(int _fd); + + void Close(); + + void Schedule(unsigned flags) { + poll.events = flags; + poll.revents &= flags; + } + + void Cancel() { + poll.events = 0; + } + + void ScheduleRead() { + poll.events |= READ|HANGUP|ERROR; + } + + void ScheduleWrite() { + poll.events |= WRITE; + } + + void CancelRead() { + poll.events &= ~(READ|HANGUP|ERROR); + } + + void CancelWrite() { + poll.events &= ~WRITE; + } + +protected: + virtual void OnSocketReady(unsigned flags) = 0; + +public: + /* GSource callbacks */ + static gboolean Prepare(GSource *source, gint *timeout_r); + static gboolean Check(GSource *source); + static gboolean Dispatch(GSource *source, GSourceFunc callback, + gpointer user_data); + +private: + bool Check() const { + return (poll.revents & poll.events) != 0; + } + + void Dispatch() { + OnSocketReady(poll.revents & poll.events); + } +}; + +#endif diff --git a/src/event/TimeoutMonitor.cxx b/src/event/TimeoutMonitor.cxx new file mode 100644 index 000000000..e0bf997a0 --- /dev/null +++ b/src/event/TimeoutMonitor.cxx @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TimeoutMonitor.hxx" +#include "Loop.hxx" + +void +TimeoutMonitor::Cancel() +{ + if (source != nullptr) { + g_source_destroy(source); + g_source_unref(source); + source = nullptr; + } +} + +void +TimeoutMonitor::Schedule(unsigned ms) +{ + Cancel(); + source = loop.AddTimeout(ms, Callback, this); +} + +void +TimeoutMonitor::ScheduleSeconds(unsigned s) +{ + Cancel(); + source = loop.AddTimeoutSeconds(s, Callback, this); +} + +bool +TimeoutMonitor::Run() +{ + bool result = OnTimeout(); + if (!result && source != nullptr) { + g_source_unref(source); + source = nullptr; + } + + return result; +} + +gboolean +TimeoutMonitor::Callback(gpointer data) +{ + TimeoutMonitor &monitor = *(TimeoutMonitor *)data; + return monitor.Run(); +} diff --git a/src/event/TimeoutMonitor.hxx b/src/event/TimeoutMonitor.hxx new file mode 100644 index 000000000..6914bcb61 --- /dev/null +++ b/src/event/TimeoutMonitor.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SOCKET_TIMEOUT_MONITOR_HXX +#define MPD_SOCKET_TIMEOUT_MONITOR_HXX + +#include "check.h" + +#include <glib.h> + +class EventLoop; + +class TimeoutMonitor { + EventLoop &loop; + GSource *source; + +public: + TimeoutMonitor(EventLoop &_loop) + :loop(_loop), source(nullptr) {} + + ~TimeoutMonitor() { + Cancel(); + } + + bool IsActive() const { + return source != nullptr; + } + + void Schedule(unsigned ms); + void ScheduleSeconds(unsigned s); + void Cancel(); + +protected: + /** + * @return true reschedules the timeout again + */ + virtual bool OnTimeout() = 0; + +private: + bool Run(); + static gboolean Callback(gpointer data); +}; + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event/WakeFD.cxx b/src/event/WakeFD.cxx new file mode 100644 index 000000000..1a84f5645 --- /dev/null +++ b/src/event/WakeFD.cxx @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "WakeFD.hxx" +#include "fd_util.h" +#include "gcc.h" + +#include <unistd.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock2.h> +#include <cstring> /* for memset() */ +#endif + +#ifdef HAVE_EVENTFD +#include <sys/eventfd.h> +#endif + +#ifdef WIN32 +static bool PoorSocketPair(int fd[2]); +#endif + +bool +WakeFD::Create() +{ + assert(fds[0] == -1); + assert(fds[1] == -1); + +#ifdef WIN32 + return PoorSocketPair(fds); +#else +#ifdef HAVE_EVENTFD + fds[0] = eventfd_cloexec_nonblock(0, 0); + if (fds[0] >= 0) { + fds[1] = -2; + return true; + } +#endif + return pipe_cloexec_nonblock(fds) >= 0; +#endif +} + +void +WakeFD::Destroy() +{ +#ifdef WIN32 + closesocket(fds[0]); + closesocket(fds[1]); +#else + close(fds[0]); +#ifdef HAVE_EVENTFD + if (!IsEventFD()) +#endif + close(fds[1]); +#endif + +#ifndef NDEBUG + fds[0] = -1; + fds[1] = -1; +#endif +} + +bool +WakeFD::Read() +{ + assert(fds[0] >= 0); + +#ifdef WIN32 + assert(fds[1] >= 0); + char buffer[256]; + return recv(fds[0], buffer, sizeof(buffer), 0) > 0; +#else + +#ifdef HAVE_EVENTFD + if (IsEventFD()) { + eventfd_t value; + return read(fds[0], &value, + sizeof(value)) == (ssize_t)sizeof(value); + } +#endif + + assert(fds[1] >= 0); + + char buffer[256]; + return read(fds[0], buffer, sizeof(buffer)) > 0; +#endif +} + +void +WakeFD::Write() +{ + assert(fds[0] >= 0); + +#ifdef WIN32 + assert(fds[1] >= 0); + + send(fds[1], "", 1, 0); +#else + +#ifdef HAVE_EVENTFD + if (IsEventFD()) { + static constexpr eventfd_t value = 1; + gcc_unused ssize_t nbytes = + write(fds[0], &value, sizeof(value)); + return; + } +#endif + + assert(fds[1] >= 0); + + gcc_unused ssize_t nbytes = write(fds[1], "", 1); +#endif +} + +#ifdef WIN32 + +static void SafeCloseSocket(SOCKET s) +{ + int error = WSAGetLastError(); + closesocket(s); + WSASetLastError(error); +} + +/* Our poor man's socketpair() implementation + * Due to limited protocol/address family support and primitive error handling + * it's better to keep this as a private implementation detail of WakeFD + * rather than wide-available API. + */ +static bool PoorSocketPair(int fd[2]) +{ + assert (fd != nullptr); + + SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listen_socket == INVALID_SOCKET) + return false; + + sockaddr_in address; + std::memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + int ret = bind(listen_socket, + reinterpret_cast<sockaddr*>(&address), + sizeof(address)); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + return false; + } + + ret = listen(listen_socket, 1); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + return false; + } + + int address_len = sizeof(address); + ret = getsockname(listen_socket, + reinterpret_cast<sockaddr*>(&address), + &address_len); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + return false; + } + + SOCKET socket0 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (socket0 == INVALID_SOCKET) { + SafeCloseSocket(listen_socket); + return false; + } + + ret = connect(socket0, + reinterpret_cast<sockaddr*>(&address), + sizeof(address)); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + SafeCloseSocket(socket0); + return false; + } + + SOCKET socket1 = accept(listen_socket, nullptr, nullptr); + if (socket1 == INVALID_SOCKET) { + SafeCloseSocket(listen_socket); + SafeCloseSocket(socket0); + return false; + } + + SafeCloseSocket(listen_socket); + + u_long non_block = 1; + if (ioctlsocket(socket0, FIONBIO, &non_block) < 0 + || ioctlsocket(socket1, FIONBIO, &non_block) < 0) { + SafeCloseSocket(socket0); + SafeCloseSocket(socket1); + return false; + } + + fd[0] = static_cast<int>(socket0); + fd[1] = static_cast<int>(socket1); + + return true; +} + +#endif diff --git a/src/event/WakeFD.hxx b/src/event/WakeFD.hxx new file mode 100644 index 000000000..15b66b4cf --- /dev/null +++ b/src/event/WakeFD.hxx @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_WAKE_FD_HXX +#define MPD_WAKE_FD_HXX + +#include "check.h" + +#include <assert.h> + +/** + * This class can be used to wake up an I/O event loop. + * + * For optimization purposes, this class does not have a constructor + * or a destructor. + */ +class WakeFD { + int fds[2]; + +public: +#ifdef NDEBUG + WakeFD() = default; +#else + WakeFD():fds{-1, -1} {}; +#endif + + WakeFD(const WakeFD &other) = delete; + WakeFD &operator=(const WakeFD &other) = delete; + + bool Create(); + void Destroy(); + + int Get() const { + assert(fds[0] >= 0); +#ifndef HAVE_EVENTFD + assert(fds[1] >= 0); +#endif + + return fds[0]; + } + + /** + * Checks if Write() was called at least once since the last + * Read() call. + */ + bool Read(); + + /** + * Wakes up the reader. Multiple calls to this function will + * be combined to one wakeup. + */ + void Write(); + +private: +#ifdef HAVE_EVENTFD + bool IsEventFD() { + assert(fds[0] >= 0); + + return fds[1] == -2; + } +#endif +}; + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event_pipe.c b/src/event_pipe.c deleted file mode 100644 index d5c3b9564..000000000 --- a/src/event_pipe.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "event_pipe.h" -#include "fd_util.h" -#include "mpd_error.h" - -#include <stdbool.h> -#include <assert.h> -#include <glib.h> -#include <string.h> -#include <errno.h> -#include <sys/types.h> -#include <unistd.h> - -#ifdef WIN32 -/* for _O_BINARY */ -#include <fcntl.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "event_pipe" - -static int event_pipe[2]; -static GIOChannel *event_channel; -static guint event_pipe_source_id; -static GMutex *event_pipe_mutex; -static bool pipe_events[PIPE_EVENT_MAX]; -static event_pipe_callback_t event_pipe_callbacks[PIPE_EVENT_MAX]; - -/** - * Invoke the callback for a certain event. - */ -static void -event_pipe_invoke(enum pipe_event event) -{ - assert((unsigned)event < PIPE_EVENT_MAX); - assert(event_pipe_callbacks[event] != NULL); - - event_pipe_callbacks[event](); -} - -static gboolean -main_notify_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - G_GNUC_UNUSED gpointer data) -{ - char buffer[256]; - gsize bytes_read; - GError *error = NULL; - GIOStatus status = g_io_channel_read_chars(event_channel, - buffer, sizeof(buffer), - &bytes_read, &error); - if (status == G_IO_STATUS_ERROR) - MPD_ERROR("error reading from pipe: %s", error->message); - - bool events[PIPE_EVENT_MAX]; - g_mutex_lock(event_pipe_mutex); - memcpy(events, pipe_events, sizeof(events)); - memset(pipe_events, 0, sizeof(pipe_events)); - g_mutex_unlock(event_pipe_mutex); - - for (unsigned i = 0; i < PIPE_EVENT_MAX; ++i) - if (events[i]) - /* invoke the event handler */ - event_pipe_invoke(i); - - return true; -} - -void event_pipe_init(void) -{ - GIOChannel *channel; - int ret; - - ret = pipe_cloexec_nonblock(event_pipe); - if (ret < 0) - MPD_ERROR("Couldn't open pipe: %s", strerror(errno)); - -#ifndef G_OS_WIN32 - channel = g_io_channel_unix_new(event_pipe[0]); -#else - channel = g_io_channel_win32_new_fd(event_pipe[0]); -#endif - g_io_channel_set_encoding(channel, NULL, NULL); - g_io_channel_set_buffered(channel, false); - - event_pipe_source_id = g_io_add_watch(channel, G_IO_IN, - main_notify_event, NULL); - - event_channel = channel; - - event_pipe_mutex = g_mutex_new(); -} - -void event_pipe_deinit(void) -{ - g_mutex_free(event_pipe_mutex); - - g_source_remove(event_pipe_source_id); - g_io_channel_unref(event_channel); - -#ifndef WIN32 - /* By some strange reason this call hangs on Win32 */ - close(event_pipe[0]); -#endif - close(event_pipe[1]); -} - -void -event_pipe_register(enum pipe_event event, event_pipe_callback_t callback) -{ - assert((unsigned)event < PIPE_EVENT_MAX); - assert(event_pipe_callbacks[event] == NULL); - - event_pipe_callbacks[event] = callback; -} - -void event_pipe_emit(enum pipe_event event) -{ - ssize_t w; - - assert((unsigned)event < PIPE_EVENT_MAX); - - g_mutex_lock(event_pipe_mutex); - if (pipe_events[event]) { - /* already set: don't write */ - g_mutex_unlock(event_pipe_mutex); - return; - } - - pipe_events[event] = true; - g_mutex_unlock(event_pipe_mutex); - - w = write(event_pipe[1], "", 1); - if (w < 0 && errno != EAGAIN && errno != EINTR) - MPD_ERROR("error writing to pipe: %s", strerror(errno)); -} - -void event_pipe_emit_fast(enum pipe_event event) -{ - assert((unsigned)event < PIPE_EVENT_MAX); - - pipe_events[event] = true; - - G_GNUC_UNUSED ssize_t nbytes = write(event_pipe[1], "", 1); -} diff --git a/src/event_pipe.h b/src/event_pipe.h deleted file mode 100644 index 3734bb86c..000000000 --- a/src/event_pipe.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef EVENT_PIPE_H -#define EVENT_PIPE_H - -#include <glib.h> - -enum pipe_event { - /** database update was finished */ - PIPE_EVENT_UPDATE, - - /** during database update, a song was deleted */ - PIPE_EVENT_DELETE, - - /** an idle event was emitted */ - PIPE_EVENT_IDLE, - - /** must call playlist_sync() */ - PIPE_EVENT_PLAYLIST, - - /** the current song's tag has changed */ - PIPE_EVENT_TAG, - - /** SIGHUP received: reload configuration, roll log file */ - PIPE_EVENT_RELOAD, - - /** a hardware mixer plugin has detected a change */ - PIPE_EVENT_MIXER, - - /** shutdown requested */ - PIPE_EVENT_SHUTDOWN, - - PIPE_EVENT_MAX -}; - -typedef void (*event_pipe_callback_t)(void); - -void event_pipe_init(void); - -void event_pipe_deinit(void); - -void -event_pipe_register(enum pipe_event event, event_pipe_callback_t callback); - -void event_pipe_emit(enum pipe_event event); - -/** - * Similar to event_pipe_emit(), but aimed for use in signal handlers: - * it doesn't lock the mutex, and doesn't log on error. That makes it - * potentially lossy, but for its intended use, that does not matter. - */ -void event_pipe_emit_fast(enum pipe_event event); - -#endif /* MAIN_NOTIFY_H */ diff --git a/src/fd_util.c b/src/fd_util.c index 882b4c7d5..ea29d6eaa 100644 --- a/src/fd_util.c +++ b/src/fd_util.c @@ -49,6 +49,10 @@ #include <sys/inotify.h> #endif +#ifdef HAVE_EVENTFD +#include <sys/eventfd.h> +#endif + #ifndef WIN32 static int @@ -328,6 +332,16 @@ inotify_init_cloexec(void) #endif +#ifdef HAVE_EVENTFD + +int +eventfd_cloexec_nonblock(unsigned initval, int flags) +{ + return eventfd(initval, flags | EFD_CLOEXEC | EFD_NONBLOCK); +} + +#endif + int close_socket(int fd) { diff --git a/src/fd_util.h b/src/fd_util.h index dd4df7a13..e65c6a69b 100644 --- a/src/fd_util.h +++ b/src/fd_util.h @@ -51,6 +51,10 @@ struct sockaddr; +#ifdef __cplusplus +extern "C" { +#endif + /** * Wrapper for dup(), which sets the CLOEXEC flag on the new * descriptor. @@ -140,10 +144,25 @@ inotify_init_cloexec(void); #endif +#ifdef HAVE_EVENTFD + +/** + * Wrapper for eventfd() which sets the flags CLOEXEC and NONBLOCK + * flag (atomically if supported by the OS). + */ +int +eventfd_cloexec_nonblock(unsigned initval, int flags); + +#endif + /** * Portable wrapper for close(); use closesocket() on WIN32/WinSock. */ int close_socket(int fd); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif diff --git a/src/filter/replay_gain_filter_plugin.c b/src/filter/ReplayGainFilterPlugin.cxx index 583a09f90..1c2f40260 100644 --- a/src/filter/replay_gain_filter_plugin.c +++ b/src/filter/ReplayGainFilterPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,16 +18,19 @@ */ #include "config.h" -#include "filter/replay_gain_filter_plugin.h" +#include "ReplayGainFilterPlugin.hxx" #include "filter_plugin.h" #include "filter_internal.h" #include "filter_registry.h" #include "audio_format.h" -#include "pcm_buffer.h" -#include "pcm_volume.h" #include "replay_gain_info.h" #include "replay_gain_config.h" + +extern "C" { +#include "pcm_buffer.h" +#include "pcm_volume.h" #include "mixer_control.h" +} #include <assert.h> #include <string.h> @@ -119,7 +122,7 @@ replay_gain_filter_init(G_GNUC_UNUSED const struct config_param *param, filter_init(&filter->filter, &replay_gain_filter_plugin); filter->mixer = NULL; - filter->mode = replay_gain_get_real_mode(); + filter->mode = REPLAY_GAIN_OFF; replay_gain_info_init(&filter->info); filter->volume = PCM_VOLUME_1; @@ -164,16 +167,6 @@ replay_gain_filter_filter(struct filter *_filter, (struct replay_gain_filter *)_filter; bool success; void *dest; - enum replay_gain_mode rg_mode; - - /* check if the mode has been changed since the last call */ - rg_mode = replay_gain_get_real_mode(); - - if (filter->mode != rg_mode) { - g_debug("replay gain mode has changed %d->%d\n", filter->mode, rg_mode); - filter->mode = rg_mode; - replay_gain_filter_update(filter); - } *dest_size_r = src_size; @@ -193,7 +186,8 @@ replay_gain_filter_filter(struct filter *_filter, memcpy(dest, src, src_size); - success = pcm_volume(dest, src_size, filter->audio_format.format, + success = pcm_volume(dest, src_size, + sample_format(filter->audio_format.format), filter->volume); if (!success) { g_set_error(error_r, replay_gain_quark(), 0, @@ -205,12 +199,12 @@ replay_gain_filter_filter(struct filter *_filter, } const struct filter_plugin replay_gain_filter_plugin = { - .name = "replay_gain", - .init = replay_gain_filter_init, - .finish = replay_gain_filter_finish, - .open = replay_gain_filter_open, - .close = replay_gain_filter_close, - .filter = replay_gain_filter_filter, + "replay_gain", + replay_gain_filter_init, + replay_gain_filter_finish, + replay_gain_filter_open, + replay_gain_filter_close, + replay_gain_filter_filter, }; void @@ -243,3 +237,19 @@ replay_gain_filter_set_info(struct filter *_filter, replay_gain_filter_update(filter); } + +void +replay_gain_filter_set_mode(struct filter *_filter, enum replay_gain_mode mode) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + if (mode == filter->mode) + /* no change */ + return; + + g_debug("replay gain mode has changed %d->%d\n", filter->mode, mode); + + filter->mode = mode; + replay_gain_filter_update(filter); +} diff --git a/src/filter/replay_gain_filter_plugin.h b/src/filter/ReplayGainFilterPlugin.hxx index 45b738e40..9b4ffc522 100644 --- a/src/filter/replay_gain_filter_plugin.h +++ b/src/filter/ReplayGainFilterPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef REPLAY_GAIN_FILTER_PLUGIN_H -#define REPLAY_GAIN_FILTER_PLUGIN_H +#ifndef MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX +#define MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX #include "replay_gain_info.h" @@ -47,4 +47,7 @@ void replay_gain_filter_set_info(struct filter *filter, const struct replay_gain_info *info); +void +replay_gain_filter_set_mode(struct filter *filter, enum replay_gain_mode mode); + #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,30 @@ # 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 +#define final +#endif + #endif #endif /* MPD_GCC_H */ diff --git a/src/sig_handlers.h b/src/gerror.h index 32e9bad95..fe4c54da9 100644 --- a/src/sig_handlers.h +++ b/src/gerror.h @@ -17,9 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_SIG_HANDLERS_H -#define MPD_SIG_HANDLERS_H +#ifndef MPD_GERROR_H +#define MPD_GERROR_H -void initSigHandlers(void); +typedef struct _GError GError; #endif diff --git a/src/icy_metadata.h b/src/icy_metadata.h deleted file mode 100644 index 9797122ca..000000000 --- a/src/icy_metadata.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef ICY_METADATA_H -#define ICY_METADATA_H - -#include <stdbool.h> -#include <stddef.h> - -struct icy_metadata { - size_t data_size, data_rest; - - size_t meta_size, meta_position; - char *meta_data; - - struct tag *tag; -}; - -/** - * Initialize a disabled icy_metadata object. - */ -static inline void -icy_clear(struct icy_metadata *im) -{ - im->data_size = 0; -} - -/** - * Initialize an enabled icy_metadata object with the specified - * data_size (from the icy-metaint HTTP response header). - */ -static inline void -icy_start(struct icy_metadata *im, size_t data_size) -{ - im->data_size = im->data_rest = data_size; - im->meta_size = 0; - im->tag = NULL; -} - -/** - * Resets the icy_metadata. Call this after rewinding the stream. - */ -void -icy_reset(struct icy_metadata *im); - -void -icy_deinit(struct icy_metadata *im); - -/** - * Checks whether the icy_metadata object is enabled. - */ -static inline bool -icy_defined(const struct icy_metadata *im) -{ - return im->data_size > 0; -} - -/** - * Evaluates data. Returns the number of bytes of normal data which - * can be read by the caller, but not more than "length". If the - * return value is smaller than "length", the caller should invoke - * icy_meta(). - */ -size_t -icy_data(struct icy_metadata *im, size_t length); - -/** - * Reads metadata from the stream. Returns the number of bytes - * consumed. If the return value is smaller than "length", the caller - * should invoke icy_data(). - */ -size_t -icy_meta(struct icy_metadata *im, const void *data, size_t length); - -static inline struct tag * -icy_tag(struct icy_metadata *im) -{ - struct tag *tag = im->tag; - im->tag = NULL; - return tag; -} - -#endif diff --git a/src/icy_server.h b/src/icy_server.h index 04f21d2ad..17fb391ba 100644 --- a/src/icy_server.h +++ b/src/icy_server.h @@ -25,6 +25,10 @@ #include <stdarg.h> +#ifdef __cplusplus +extern "C" { +#endif + char* icy_server_metadata_header(const char *name, const char *genre, const char *url, @@ -33,4 +37,8 @@ icy_server_metadata_header(const char *name, struct page* icy_server_metadata_page(const struct tag *tag, ...); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/inotify_source.h b/src/inotify_source.h deleted file mode 100644 index f92e18e39..000000000 --- a/src/inotify_source.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INOTIFY_SOURCE_H -#define MPD_INOTIFY_SOURCE_H - -#include <glib.h> - -typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask, - const char *name, void *ctx); - -struct mpd_inotify_source; - -/** - * Creates a new inotify source and registers it in the GLib main - * loop. - * - * @param a callback invoked for events received from the kernel - */ -struct mpd_inotify_source * -mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx, - GError **error_r); - -void -mpd_inotify_source_free(struct mpd_inotify_source *source); - -/** - * Adds a path to the notify list. - * - * @return a watch descriptor or -1 on error - */ -int -mpd_inotify_source_add(struct mpd_inotify_source *source, - const char *path_fs, unsigned mask, - GError **error_r); - -/** - * Removes a path from the notify list. - * - * @param wd the watch descriptor returned by mpd_inotify_source_add() - */ -void -mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd); - -#endif diff --git a/src/input/curl_input_plugin.c b/src/input/CurlInputPlugin.cxx index 3f191141e..29d8266ce 100644 --- a/src/input/curl_input_plugin.c +++ b/src/input/CurlInputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,19 @@ */ #include "config.h" -#include "input/curl_input_plugin.h" -#include "input_internal.h" +#include "CurlInputPlugin.hxx" #include "input_plugin.h" #include "conf.h" #include "tag.h" -#include "icy_metadata.h" -#include "io_thread.h" +#include "IcyMetaDataParser.hxx" +#include "event/MultiSocketMonitor.hxx" + +extern "C" { +#include "input_internal.h" +} + +#include "event/Loop.hxx" +#include "IOThread.hxx" #include "glib_compat.h" #include <assert.h> @@ -38,9 +44,16 @@ #include <string.h> #include <errno.h> +#include <list> +#include <forward_list> + #include <curl/curl.h> #include <glib.h> +#if LIBCURL_VERSION_NUM < 0x071200 +#error libcurl is too old +#endif + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "input_curl" @@ -59,7 +72,7 @@ static const size_t CURL_RESUME_AT = 384 * 1024; /** * Buffers created by input_curl_writefunction(). */ -struct buffer { +class CurlInputBuffer { /** size of the payload */ size_t size; @@ -67,7 +80,55 @@ struct buffer { size_t consumed; /** the payload */ - unsigned char data[sizeof(long)]; + uint8_t *data; + +public: + CurlInputBuffer(const void *_data, size_t _size) + :size(_size), consumed(0), data(new uint8_t[size]) { + memcpy(data, _data, size); + } + + ~CurlInputBuffer() { + delete[] data; + } + + CurlInputBuffer(const CurlInputBuffer &) = delete; + CurlInputBuffer &operator=(const CurlInputBuffer &) = delete; + + const void *Begin() const { + return data + consumed; + } + + size_t TotalSize() const { + return size; + } + + size_t Available() const { + return size - consumed; + } + + /** + * Mark a part of the buffer as consumed. + * + * @return false if the buffer is now empty + */ + bool Consume(size_t length) { + assert(consumed < size); + + consumed += length; + if (consumed < size) + return true; + + assert(consumed == size); + return false; + } + + bool Read(void *dest, size_t length) { + assert(consumed + length <= size); + + memcpy(dest, data + consumed, length); + return Consume(length); + } }; struct input_curl { @@ -75,40 +136,28 @@ struct input_curl { /* some buffers which were passed to libcurl, which we have too free */ - char *url, *range; + char *range; struct curl_slist *request_headers; /** the curl handles */ CURL *easy; - /** the GMainLoop source used to poll all CURL file - descriptors */ - GSource *source; - - /** the source id of #source */ - guint source_id; - - /** a linked list of all registered GPollFD objects */ - GSList *fds; - /** list of buffers, where input_curl_writefunction() appends to, and input_curl_read() reads from them */ - GQueue *buffers; + std::list<CurlInputBuffer> buffers; -#if LIBCURL_VERSION_NUM >= 0x071200 /** * Is the connection currently paused? That happens when the * buffer was getting too large. It will be unpaused when the * buffer is below the threshold again. */ bool paused; -#endif /** error message provided by libcurl */ char error[CURL_ERROR_SIZE]; /** parser for icy-metadata */ - struct icy_metadata icy_metadata; + IcyMetaDataParser icy; /** the stream name from the icy-name response header */ char *meta_name; @@ -118,6 +167,50 @@ struct input_curl { struct tag *tag; GError *postponed_error; + + input_curl(const char *url, GMutex *mutex, GCond *cond) + :range(nullptr), request_headers(nullptr), + paused(false), + meta_name(nullptr), + tag(nullptr), + postponed_error(nullptr) { + input_stream_init(&base, &input_plugin_curl, url, mutex, cond); + } + + ~input_curl(); + + input_curl(const input_curl &) = delete; + input_curl &operator=(const input_curl &) = delete; +}; + +/** + * This class monitors all CURL file descriptors. + */ +class CurlSockets final : private MultiSocketMonitor { + /** + * Did CURL give us a timeout? If yes, then we need to call + * curl_multi_perform(), even if there was no event on any + * file descriptor. + */ + bool have_timeout; + + /** + * The absolute time stamp when the timeout expires. + */ + gint64 absolute_timeout; + +public: + CurlSockets(EventLoop &_loop) + :MultiSocketMonitor(_loop) {} + + using MultiSocketMonitor::InvalidateSockets; + +private: + void UpdateSockets(); + + virtual void PrepareSockets(gcc_unused gint *timeout_r) override; + virtual bool CheckSockets() const override; + virtual void DispatchSockets() override; }; /** libcurl should accept "ICY 200 OK" */ @@ -134,35 +227,9 @@ static struct { * A linked list of all active HTTP requests. An active * request is one that doesn't have the "eof" flag set. */ - GSList *requests; - - /** - * The GMainLoop source used to poll all CURL file - * descriptors. - */ - GSource *source; - - /** - * The source id of #source. - */ - guint source_id; - - GSList *fds; - -#if LIBCURL_VERSION_NUM >= 0x070f04 - /** - * Did CURL give us a timeout? If yes, then we need to call - * curl_multi_perform(), even if there was no event on any - * file descriptor. - */ - bool timeout; + std::forward_list<input_curl *> requests; - /** - * The absolute time stamp when the timeout expires. This is - * used in the GSource method check(). - */ - gint64 absolute_timeout; -#endif + CurlSockets *sockets; } curl; static inline GQuark @@ -181,23 +248,19 @@ input_curl_find_request(CURL *easy) { assert(io_thread_inside()); - for (GSList *i = curl.requests; i != NULL; i = g_slist_next(i)) { - struct input_curl *c = i->data; + for (auto c : curl.requests) if (c->easy == easy) return c; - } return NULL; } -#if LIBCURL_VERSION_NUM >= 0x071200 - static gpointer input_curl_resume(gpointer data) { assert(io_thread_inside()); - struct input_curl *c = data; + struct input_curl *c = (struct input_curl *)data; if (c->paused) { c->paused = false; @@ -207,13 +270,11 @@ input_curl_resume(gpointer data) return NULL; } -#endif - /** * Calculates the GLib event bit mask for one file descriptor, * obtained from three #fd_set objects filled by curl_multi_fdset(). */ -static gushort +static unsigned input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds) { gushort events = 0; @@ -242,8 +303,8 @@ input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds) * * Runs in the I/O thread. No lock needed. */ -static void -curl_update_fds(void) +void +CurlSockets::UpdateSockets() { assert(io_thread_inside()); @@ -262,42 +323,15 @@ curl_update_fds(void) return; } - GSList *fds = curl.fds; - curl.fds = NULL; - - while (fds != NULL) { - GPollFD *poll_fd = fds->data; - gushort events = input_curl_fd_events(poll_fd->fd, &rfds, - &wfds, &efds); - - assert(poll_fd->events != 0); - - fds = g_slist_remove(fds, poll_fd); - - if (events != poll_fd->events) - g_source_remove_poll(curl.source, poll_fd); - - if (events != 0) { - if (events != poll_fd->events) { - poll_fd->events = events; - g_source_add_poll(curl.source, poll_fd); - } - - curl.fds = g_slist_prepend(curl.fds, poll_fd); - } else { - g_free(poll_fd); - } - } + UpdateSocketList([&rfds, &wfds, &efds](int fd){ + return input_curl_fd_events(fd, &rfds, + &wfds, &efds); + }); for (int fd = 0; fd <= max_fd; ++fd) { - gushort events = input_curl_fd_events(fd, &rfds, &wfds, &efds); - if (events != 0) { - GPollFD *poll_fd = g_new(GPollFD, 1); - poll_fd->fd = fd; - poll_fd->events = events; - g_source_add_poll(curl.source, poll_fd); - curl.fds = g_slist_prepend(curl.fds, poll_fd); - } + unsigned events = input_curl_fd_events(fd, &rfds, &wfds, &efds); + if (events != 0) + AddSocket(fd, events); } } @@ -312,7 +346,7 @@ input_curl_easy_add(struct input_curl *c, GError **error_r) assert(c->easy != NULL); assert(input_curl_find_request(c->easy) == NULL); - curl.requests = g_slist_prepend(curl.requests, c); + curl.requests.push_front(c); CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy); if (mcode != CURLM_OK) { @@ -322,7 +356,7 @@ input_curl_easy_add(struct input_curl *c, GError **error_r) return false; } - curl_update_fds(); + curl.sockets->InvalidateSockets(); return true; } @@ -335,7 +369,8 @@ struct easy_add_params { static gpointer input_curl_easy_add_callback(gpointer data) { - const struct easy_add_params *params = data; + const struct easy_add_params *params = + (const struct easy_add_params *)data; bool success = input_curl_easy_add(params->c, params->error_r); return GUINT_TO_POINTER(success); @@ -352,8 +387,8 @@ input_curl_easy_add_indirect(struct input_curl *c, GError **error_r) assert(c->easy != NULL); struct easy_add_params params = { - .c = c, - .error_r = error_r, + c, + error_r, }; gpointer result = @@ -376,7 +411,7 @@ input_curl_easy_free(struct input_curl *c) if (c->easy == NULL) return; - curl.requests = g_slist_remove(curl.requests, c); + curl.requests.remove(c); curl_multi_remove_handle(curl.multi, c->easy); curl_easy_cleanup(c->easy); @@ -392,10 +427,10 @@ input_curl_easy_free(struct input_curl *c) static gpointer input_curl_easy_free_callback(gpointer data) { - struct input_curl *c = data; + struct input_curl *c = (struct input_curl *)data; input_curl_easy_free(c); - curl_update_fds(); + curl.sockets->InvalidateSockets(); return NULL; } @@ -424,8 +459,8 @@ input_curl_abort_all_requests(GError *error) assert(io_thread_inside()); assert(error != NULL); - while (curl.requests != NULL) { - struct input_curl *c = curl.requests->data; + while (!curl.requests.empty()) { + struct input_curl *c = curl.requests.front(); assert(c->postponed_error == NULL); input_curl_easy_free(c); @@ -532,28 +567,18 @@ input_curl_perform(void) return true; } -/* - * GSource methods - * - */ - -/** - * The GSource prepare() method implementation. - */ -static gboolean -input_curl_source_prepare(G_GNUC_UNUSED GSource *source, gint *timeout_r) +void +CurlSockets::PrepareSockets(gint *timeout_r) { - curl_update_fds(); + UpdateSockets(); -#if LIBCURL_VERSION_NUM >= 0x070f04 - curl.timeout = false; + have_timeout = false; long timeout2; CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2); if (mcode == CURLM_OK) { if (timeout2 >= 0) - curl.absolute_timeout = g_source_get_time(source) - + timeout2 * 1000; + absolute_timeout = GetTime() + timeout2 * 1000; if (timeout2 >= 0 && timeout2 < 10) /* CURL 7.21.1 likes to report "timeout=0", @@ -564,69 +589,28 @@ input_curl_source_prepare(G_GNUC_UNUSED GSource *source, gint *timeout_r) *timeout_r = timeout2; - curl.timeout = timeout2 >= 0; + have_timeout = timeout2 >= 0; } else g_warning("curl_multi_timeout() failed: %s\n", curl_multi_strerror(mcode)); -#else - (void)timeout_r; -#endif - - return false; } -/** - * The GSource check() method implementation. - */ -static gboolean -input_curl_source_check(G_GNUC_UNUSED GSource *source) +bool +CurlSockets::CheckSockets() const { -#if LIBCURL_VERSION_NUM >= 0x070f04 - if (curl.timeout) { - /* when a timeout has expired, we need to call - curl_multi_perform(), even if there was no file - descriptor event */ - - if (g_source_get_time(source) >= curl.absolute_timeout) - return true; - } -#endif - - for (GSList *i = curl.fds; i != NULL; i = i->next) { - GPollFD *poll_fd = i->data; - if (poll_fd->revents != 0) - return true; - } - - return false; + /* when a timeout has expired, we need to call + curl_multi_perform(), even if there was no file descriptor + event */ + return have_timeout && GetTime() >= absolute_timeout; } -/** - * The GSource dispatch() method implementation. The callback isn't - * used, because we're handling all events directly. - */ -static gboolean -input_curl_source_dispatch(G_GNUC_UNUSED GSource *source, - G_GNUC_UNUSED GSourceFunc callback, - G_GNUC_UNUSED gpointer user_data) +void +CurlSockets::DispatchSockets() { if (input_curl_perform()) input_curl_info_read(); - - return true; } -/** - * The vtable for our GSource implementation. Unfortunately, we - * cannot declare it "const", because g_source_new() takes a non-const - * pointer, for whatever reason. - */ -static GSourceFuncs curl_source_funcs = { - .prepare = input_curl_source_prepare, - .check = input_curl_source_check, - .dispatch = input_curl_source_dispatch, -}; - /* * input_plugin methods * @@ -668,8 +652,7 @@ input_curl_init(const struct config_param *param, return false; } - curl.source = g_source_new(&curl_source_funcs, sizeof(*curl.source)); - curl.source_id = g_source_attach(curl.source, io_thread_context()); + curl.sockets = new CurlSockets(io_thread_get()); return true; } @@ -677,7 +660,7 @@ input_curl_init(const struct config_param *param, static gpointer curl_destroy_sources(G_GNUC_UNUSED gpointer data) { - g_source_destroy(curl.source); + delete curl.sockets; return NULL; } @@ -685,7 +668,7 @@ curl_destroy_sources(G_GNUC_UNUSED gpointer data) static void input_curl_finish(void) { - assert(curl.requests == NULL); + assert(curl.requests.empty()); io_thread_call(curl_destroy_sources, NULL); @@ -696,8 +679,6 @@ input_curl_finish(void) curl_global_cleanup(); } -#if LIBCURL_VERSION_NUM >= 0x071200 - /** * Determine the total sizes of all buffers, including portions that * have already been consumed. @@ -710,55 +691,24 @@ curl_total_buffer_size(const struct input_curl *c) { size_t total = 0; - for (GList *i = g_queue_peek_head_link(c->buffers); - i != NULL; i = g_list_next(i)) { - struct buffer *buffer = i->data; - total += buffer->size; - } + for (const auto &i : c->buffers) + total += i.TotalSize(); return total; } -#endif - -static void -buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +input_curl::~input_curl() { - struct buffer *buffer = data; + if (tag != NULL) + tag_free(tag); + g_free(meta_name); - assert(buffer->consumed <= buffer->size); + input_curl_easy_free_indirect(this); - g_free(buffer); -} + if (postponed_error != NULL) + g_error_free(postponed_error); -static void -input_curl_flush_buffers(struct input_curl *c) -{ - g_queue_foreach(c->buffers, buffer_free_callback, NULL); - g_queue_clear(c->buffers); -} - -/** - * Frees this stream, including the input_stream struct. - */ -static void -input_curl_free(struct input_curl *c) -{ - if (c->tag != NULL) - tag_free(c->tag); - g_free(c->meta_name); - - input_curl_easy_free_indirect(c); - input_curl_flush_buffers(c); - - g_queue_free(c->buffers); - - if (c->postponed_error != NULL) - g_error_free(c->postponed_error); - - g_free(c->url); - input_stream_deinit(&c->base); - g_free(c); + input_stream_deinit(&base); } static bool @@ -788,7 +738,7 @@ input_curl_tag(struct input_stream *is) static bool fill_buffer(struct input_curl *c, GError **error_r) { - while (c->easy != NULL && g_queue_is_empty(c->buffers)) + while (c->easy != NULL && c->buffers.empty()) g_cond_wait(c->base.cond, c->base.mutex); if (c->postponed_error != NULL) { @@ -797,86 +747,63 @@ fill_buffer(struct input_curl *c, GError **error_r) return false; } - return !g_queue_is_empty(c->buffers); -} - -/** - * Mark a part of the buffer object as consumed. - */ -static struct buffer * -consume_buffer(struct buffer *buffer, size_t length) -{ - assert(buffer != NULL); - assert(buffer->consumed < buffer->size); - - buffer->consumed += length; - if (buffer->consumed < buffer->size) - return buffer; - - assert(buffer->consumed == buffer->size); - - g_free(buffer); - - return NULL; + return !c->buffers.empty(); } static size_t -read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers, +read_from_buffer(IcyMetaDataParser &icy, std::list<CurlInputBuffer> &buffers, void *dest0, size_t length) { - struct buffer *buffer = g_queue_pop_head(buffers); - uint8_t *dest = dest0; + auto &buffer = buffers.front(); + uint8_t *dest = (uint8_t *)dest0; size_t nbytes = 0; - assert(buffer->size > 0); - assert(buffer->consumed < buffer->size); - - if (length > buffer->size - buffer->consumed) - length = buffer->size - buffer->consumed; + if (length > buffer.Available()) + length = buffer.Available(); while (true) { size_t chunk; - chunk = icy_data(icy_metadata, length); + chunk = icy.Data(length); if (chunk > 0) { - memcpy(dest, buffer->data + buffer->consumed, - chunk); - buffer = consume_buffer(buffer, chunk); + const bool empty = !buffer.Read(dest, chunk); nbytes += chunk; dest += chunk; length -= chunk; - if (length == 0) + if (empty) { + buffers.pop_front(); break; + } - assert(buffer != NULL); + if (length == 0) + break; } - chunk = icy_meta(icy_metadata, buffer->data + buffer->consumed, - length); + chunk = icy.Meta(buffer.Begin(), length); if (chunk > 0) { - buffer = consume_buffer(buffer, chunk); + const bool empty = !buffer.Consume(chunk); length -= chunk; - if (length == 0) + if (empty) { + buffers.pop_front(); break; + } - assert(buffer != NULL); + if (length == 0) + break; } } - if (buffer != NULL) - g_queue_push_head(buffers, buffer); - return nbytes; } static void copy_icy_tag(struct input_curl *c) { - struct tag *tag = icy_tag(&c->icy_metadata); + struct tag *tag = c->icy.ReadTag(); if (tag == NULL) return; @@ -896,7 +823,7 @@ input_curl_available(struct input_stream *is) struct input_curl *c = (struct input_curl *)is; return c->postponed_error != NULL || c->easy == NULL || - !g_queue_is_empty(c->buffers); + !c->buffers.empty(); } static size_t @@ -906,7 +833,7 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size, struct input_curl *c = (struct input_curl *)is; bool success; size_t nbytes = 0; - char *dest = ptr; + char *dest = (char *)ptr; do { /* fill the buffer */ @@ -917,8 +844,8 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size, /* send buffer contents */ - while (size > 0 && !g_queue_is_empty(c->buffers)) { - size_t copy = read_from_buffer(&c->icy_metadata, c->buffers, + while (size > 0 && !c->buffers.empty()) { + size_t copy = read_from_buffer(c->icy, c->buffers, dest + nbytes, size); nbytes += copy; @@ -926,18 +853,16 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size, } } while (nbytes == 0); - if (icy_defined(&c->icy_metadata)) + if (c->icy.IsDefined()) copy_icy_tag(c); is->offset += (goffset)nbytes; -#if LIBCURL_VERSION_NUM >= 0x071200 if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) { g_mutex_unlock(c->base.mutex); io_thread_call(input_curl_resume, c); g_mutex_lock(c->base.mutex); } -#endif return nbytes; } @@ -947,7 +872,7 @@ input_curl_close(struct input_stream *is) { struct input_curl *c = (struct input_curl *)is; - input_curl_free(c); + delete c; } static bool @@ -955,7 +880,7 @@ input_curl_eof(G_GNUC_UNUSED struct input_stream *is) { struct input_curl *c = (struct input_curl *)is; - return c->easy == NULL && g_queue_is_empty(c->buffers); + return c->easy == NULL && c->buffers.empty(); } /** called by curl when new data is available */ @@ -963,13 +888,14 @@ static size_t input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) { struct input_curl *c = (struct input_curl *)stream; - const char *header = ptr, *end, *value; char name[64]; size *= nmemb; - end = header + size; - value = memchr(header, ':', size); + const char *header = (const char *)ptr; + const char *end = header + size; + + const char *value = (const char *)memchr(header, ':', size); if (value == NULL || (size_t)(value - header) >= sizeof(name)) return size; @@ -990,7 +916,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) if (g_ascii_strcasecmp(name, "accept-ranges") == 0) { /* a stream with icy-metadata is not seekable */ - if (!icy_defined(&c->icy_metadata)) + if (!c->icy.IsDefined()) c->base.seekable = true; } else if (g_ascii_strcasecmp(name, "content-length") == 0) { char buffer[64]; @@ -1021,7 +947,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) size_t icy_metaint; if ((size_t)(end - header) >= sizeof(buffer) || - icy_defined(&c->icy_metadata)) + c->icy.IsDefined()) return size; memcpy(buffer, value, end - value); @@ -1031,7 +957,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) g_debug("icy-metaint=%zu", icy_metaint); if (icy_metaint > 0) { - icy_start(&c->icy_metadata, icy_metaint); + c->icy.Start(icy_metaint); /* a stream with icy-metadata is not seekable */ @@ -1047,7 +973,6 @@ static size_t input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) { struct input_curl *c = (struct input_curl *)stream; - struct buffer *buffer; size *= nmemb; if (size == 0) @@ -1055,20 +980,13 @@ input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) g_mutex_lock(c->base.mutex); -#if LIBCURL_VERSION_NUM >= 0x071200 if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) { c->paused = true; g_mutex_unlock(c->base.mutex); return CURL_WRITEFUNC_PAUSE; } -#endif - - buffer = g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size); - buffer->size = size; - buffer->consumed = 0; - memcpy(buffer->data, ptr, size); - g_queue_push_tail(c->buffers, buffer); + c->buffers.emplace_back(ptr, size); c->base.ready = true; g_cond_broadcast(c->base.cond); @@ -1120,7 +1038,7 @@ input_curl_easy_init(struct input_curl *c, GError **error_r) g_free(proxy_auth_str); } - code = curl_easy_setopt(c->easy, CURLOPT_URL, c->url); + code = curl_easy_setopt(c->easy, CURLOPT_URL, c->base.uri); if (code != CURLE_OK) { g_set_error(error_r, curl_quark(), code, "curl_easy_setopt() failed: %s", @@ -1179,19 +1097,15 @@ input_curl_seek(struct input_stream *is, goffset offset, int whence, /* check if we can fast-forward the buffer */ - while (offset > is->offset && !g_queue_is_empty(c->buffers)) { - struct buffer *buffer; - size_t length; - - buffer = (struct buffer *)g_queue_pop_head(c->buffers); - - length = buffer->size - buffer->consumed; + while (offset > is->offset && !c->buffers.empty()) { + auto &buffer = c->buffers.front(); + size_t length = buffer.Available(); if (offset - is->offset < (goffset)length) length = offset - is->offset; - buffer = consume_buffer(buffer, length); - if (buffer != NULL) - g_queue_push_head(c->buffers, buffer); + const bool empty = !buffer.Consume(length); + if (empty) + c->buffers.pop_front(); is->offset += length; } @@ -1204,7 +1118,7 @@ input_curl_seek(struct input_stream *is, goffset offset, int whence, g_mutex_unlock(c->base.mutex); input_curl_easy_free_indirect(c); - input_curl_flush_buffers(c); + c->buffers.clear(); is->offset = offset; if (is->offset == is->size) { @@ -1251,34 +1165,18 @@ input_curl_open(const char *url, GMutex *mutex, GCond *cond, assert(mutex != NULL); assert(cond != NULL); - struct input_curl *c; - if (strncmp(url, "http://", 7) != 0) return NULL; - c = g_new0(struct input_curl, 1); - input_stream_init(&c->base, &input_plugin_curl, url, - mutex, cond); - - c->url = g_strdup(url); - c->buffers = g_queue_new(); - - icy_clear(&c->icy_metadata); - c->tag = NULL; - - c->postponed_error = NULL; - -#if LIBCURL_VERSION_NUM >= 0x071200 - c->paused = false; -#endif + struct input_curl *c = new input_curl(url, mutex, cond); if (!input_curl_easy_init(c, error_r)) { - input_curl_free(c); + delete c; return NULL; } if (!input_curl_easy_add_indirect(c, error_r)) { - input_curl_free(c); + delete c; return NULL; } @@ -1286,16 +1184,16 @@ input_curl_open(const char *url, GMutex *mutex, GCond *cond, } const struct input_plugin input_plugin_curl = { - .name = "curl", - .init = input_curl_init, - .finish = input_curl_finish, - - .open = input_curl_open, - .close = input_curl_close, - .check = input_curl_check, - .tag = input_curl_tag, - .available = input_curl_available, - .read = input_curl_read, - .eof = input_curl_eof, - .seek = input_curl_seek, + "curl", + input_curl_init, + input_curl_finish, + input_curl_open, + input_curl_close, + input_curl_check, + nullptr, + input_curl_tag, + input_curl_available, + input_curl_read, + input_curl_eof, + input_curl_seek, }; diff --git a/src/input/curl_input_plugin.h b/src/input/CurlInputPlugin.hxx index c6e71bf40..20d1309d8 100644 --- a/src/input/curl_input_plugin.h +++ b/src/input/CurlInputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INPUT_CURL_H -#define MPD_INPUT_CURL_H +#ifndef MPD_INPUT_CURL_HXX +#define MPD_INPUT_CURL_HXX struct input_stream; diff --git a/src/input/soup_input_plugin.c b/src/input/SoupInputPlugin.cxx index fc903b48c..5a60fa725 100644 --- a/src/input/soup_input_plugin.c +++ b/src/input/SoupInputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,21 @@ */ #include "config.h" -#include "input/soup_input_plugin.h" -#include "input_internal.h" +#include "SoupInputPlugin.hxx" #include "input_plugin.h" -#include "io_thread.h" + +extern "C" { +#include "input_internal.h" +} + +#include "IOThread.hxx" +#include "event/Loop.hxx" #include "conf.h" +extern "C" { #include <libsoup/soup-uri.h> #include <libsoup/soup-session-async.h> +} #include <assert.h> #include <string.h> @@ -99,7 +106,7 @@ input_soup_init(const struct config_param *param, GError **error_r) soup_session_async_new_with_options(SOUP_SESSION_PROXY_URI, soup_proxy, SOUP_SESSION_ASYNC_CONTEXT, - io_thread_context(), + io_thread_get().GetContext(), NULL); return true; @@ -156,7 +163,7 @@ static void input_soup_session_callback(G_GNUC_UNUSED SoupSession *session, SoupMessage *msg, gpointer user_data) { - struct input_soup *s = user_data; + struct input_soup *s = (struct input_soup *)user_data; assert(msg == s->msg); assert(!s->completed); @@ -177,7 +184,7 @@ input_soup_session_callback(G_GNUC_UNUSED SoupSession *session, static void input_soup_got_headers(SoupMessage *msg, gpointer user_data) { - struct input_soup *s = user_data; + struct input_soup *s = (struct input_soup *)user_data; g_mutex_lock(s->base.mutex); @@ -199,7 +206,7 @@ input_soup_got_headers(SoupMessage *msg, gpointer user_data) static void input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) { - struct input_soup *s = user_data; + struct input_soup *s = (struct input_soup *)user_data; assert(msg == s->msg); @@ -220,7 +227,7 @@ input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) static void input_soup_got_body(G_GNUC_UNUSED SoupMessage *msg, gpointer user_data) { - struct input_soup *s = user_data; + struct input_soup *s = (struct input_soup *)user_data; assert(msg == s->msg); @@ -256,7 +263,7 @@ input_soup_wait_data(struct input_soup *s) static gpointer input_soup_queue(gpointer data) { - struct input_soup *s = data; + struct input_soup *s = (struct input_soup *)data; soup_session_queue_message(soup_session, s->msg, input_soup_session_callback, s); @@ -320,7 +327,7 @@ input_soup_open(const char *uri, static gpointer input_soup_cancel(gpointer data) { - struct input_soup *s = data; + struct input_soup *s = (struct input_soup *)data; if (!s->completed) soup_session_cancel_message(soup_session, s->msg, @@ -352,7 +359,7 @@ input_soup_close(struct input_stream *is) g_mutex_unlock(s->base.mutex); SoupBuffer *buffer; - while ((buffer = g_queue_pop_head(s->buffers)) != NULL) + while ((buffer = (SoupBuffer *)g_queue_pop_head(s->buffers)) != NULL) soup_buffer_free(buffer); g_queue_free(s->buffers); @@ -403,10 +410,11 @@ input_soup_read(struct input_stream *is, void *ptr, size_t size, return 0; } - char *p0 = ptr, *p = p0, *p_end = p0 + size; + char *p0 = (char *)ptr, *p = p0, *p_end = p0 + size; while (p < p_end) { - SoupBuffer *buffer = g_queue_pop_head(s->buffers); + SoupBuffer *buffer = (SoupBuffer *) + g_queue_pop_head(s->buffers); if (buffer == NULL) { assert(s->current_consumed == 0); break; @@ -460,14 +468,16 @@ input_soup_eof(G_GNUC_UNUSED struct input_stream *is) } const struct input_plugin input_plugin_soup = { - .name = "soup", - .init = input_soup_init, - .finish = input_soup_finish, - - .open = input_soup_open, - .close = input_soup_close, - .check = input_soup_check, - .available = input_soup_available, - .read = input_soup_read, - .eof = input_soup_eof, + "soup", + input_soup_init, + input_soup_finish, + input_soup_open, + input_soup_close, + input_soup_check, + nullptr, + nullptr, + input_soup_available, + input_soup_read, + input_soup_eof, + nullptr, }; diff --git a/src/input/soup_input_plugin.h b/src/input/SoupInputPlugin.hxx index 689b2d971..4c089b39b 100644 --- a/src/input/soup_input_plugin.h +++ b/src/input/SoupInputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INPUT_SOUP_H -#define MPD_INPUT_SOUP_H +#ifndef MPD_INPUT_SOUP_HXX +#define MPD_INPUT_SOUP_HXX extern const struct input_plugin input_plugin_soup; diff --git a/src/input/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_stream.h b/src/input_stream.h index 10ad97161..d5b1dae2e 100644 --- a/src/input_stream.h +++ b/src/input_stream.h @@ -88,6 +88,10 @@ struct input_stream { char *mime; }; +#ifdef __cplusplus +extern "C" { +#endif + /** * Opens a new input stream. You may not access it until the "ready" * flag is set. @@ -164,6 +168,13 @@ void input_stream_lock_wait_ready(struct input_stream *is); /** + * Determines whether seeking is cheap. This is true for local files. + */ +gcc_pure gcc_nonnull(1) +bool +input_stream_cheap_seeking(const struct input_stream *is); + +/** * Seeks to the specified position in the stream. This will most * likely fail if the "seekable" flag is false. * @@ -264,4 +275,8 @@ size_t input_stream_lock_read(struct input_stream *is, void *ptr, size_t size, GError **error_r); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/io_error.h b/src/io_error.h new file mode 100644 index 000000000..930ced108 --- /dev/null +++ b/src/io_error.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_IO_ERROR_H +#define MPD_IO_ERROR_H + +#include <glib.h> + +#include <errno.h> + +/** + * A GQuark for GError for I/O errors. The code is an errno value. + */ +G_GNUC_CONST +static inline GQuark +errno_quark(void) +{ + return g_quark_from_static_string("errno"); +} + +static inline void +set_error_errno(GError **error_r) +{ + g_set_error_literal(error_r, errno_quark(), errno, + g_strerror(errno)); +} + +static inline GError * +new_error_errno(void) +{ + return g_error_new_literal(errno_quark(), errno, + g_strerror(errno)); +} + +#endif diff --git a/src/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/alsa_mixer_plugin.c b/src/mixer/AlsaMixerPlugin.cxx index 22e4e22bd..17f8b9a6f 100644 --- a/src/mixer/alsa_mixer_plugin.c +++ b/src/mixer/AlsaMixerPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,11 @@ #include "config.h" #include "mixer_api.h" #include "output_api.h" -#include "event_pipe.h" +#include "GlobalEvents.hxx" +#include "Main.hxx" +#include "event/MultiSocketMonitor.hxx" + +#include <algorithm> #include <glib.h> #include <alsa/asoundlib.h> @@ -29,13 +33,16 @@ #define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" #define VOLUME_MIXER_ALSA_INDEX_DEFAULT 0 -struct alsa_mixer_source { - GSource source; +class AlsaMixerMonitor final : private MultiSocketMonitor { + snd_mixer_t *const mixer; - snd_mixer_t *mixer; +public: + AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer) + :MultiSocketMonitor(_loop), mixer(_mixer) {} - /** a linked list of all registered GPollFD objects */ - GSList *fds; +private: + virtual void PrepareSockets(gcc_unused gint *timeout_r) override; + virtual void DispatchSockets() override; }; struct alsa_mixer { @@ -52,7 +59,7 @@ struct alsa_mixer { long volume_max; int volume_set; - struct alsa_mixer_source *source; + AlsaMixerMonitor *monitor; }; /** @@ -64,142 +71,45 @@ alsa_mixer_quark(void) return g_quark_from_static_string("alsa_mixer"); } -/* - * GSource helper functions - * - */ - -static GSList ** -find_fd(GSList **list_r, int fd) -{ - while (true) { - GSList *list = *list_r; - if (list == NULL) - return NULL; - - GPollFD *p = list->data; - if (p->fd == fd) - return list_r; - - list_r = &list->next; - } -} - -static void -alsa_mixer_update_fd(struct alsa_mixer_source *source, const struct pollfd *p, - GSList **old_r) -{ - GSList **found_r = find_fd(old_r, p->fd); - if (found_r == NULL) { - /* new fd */ - GPollFD *q = g_new(GPollFD, 1); - q->fd = p->fd; - q->events = p->events; - g_source_add_poll(&source->source, q); - source->fds = g_slist_prepend(source->fds, q); - return; - } - - GSList *found = *found_r; - *found_r = found->next; - - GPollFD *q = found->data; - if (q->events != p->events) { - /* refresh events */ - g_source_remove_poll(&source->source, q); - q->events = p->events; - g_source_add_poll(&source->source, q); - } - - found->next = source->fds; - source->fds = found; -} - -static void -alsa_mixer_update_fds(struct alsa_mixer_source *source) +void +AlsaMixerMonitor::PrepareSockets(gcc_unused gint *timeout_r) { - int count = snd_mixer_poll_descriptors_count(source->mixer); + int count = snd_mixer_poll_descriptors_count(mixer); if (count < 0) count = 0; struct pollfd *pfds = g_new(struct pollfd, count); - count = snd_mixer_poll_descriptors(source->mixer, pfds, count); + count = snd_mixer_poll_descriptors(mixer, pfds, count); if (count < 0) count = 0; - GSList *old = source->fds; - source->fds = NULL; + struct pollfd *end = pfds + count; - for (int i = 0; i < count; ++i) - alsa_mixer_update_fd(source, &pfds[i], &old); - g_free(pfds); + UpdateSocketList([pfds, end](int fd) -> unsigned { + auto i = std::find_if(pfds, end, [fd](const struct pollfd &pfd){ + return pfd.fd == fd; + }); + if (i == end) + return 0; - for (; old != NULL; old = old->next) { - GPollFD *q = old->data; - g_source_remove_poll(&source->source, q); - g_free(q); - } + auto events = i->events; + i->events = 0; + return events; + }); - g_slist_free(old); -} + for (auto i = pfds; i != end; ++i) + if (i->events != 0) + AddSocket(i->fd, i->events); -/* - * GSource methods - * - */ - -static gboolean -alsa_mixer_source_prepare(GSource *_source, G_GNUC_UNUSED gint *timeout_r) -{ - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - alsa_mixer_update_fds(source); - - return false; -} - -static gboolean -alsa_mixer_source_check(GSource *_source) -{ - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - - for (const GSList *i = source->fds; i != NULL; i = i->next) { - const GPollFD *poll_fd = i->data; - if (poll_fd->revents != 0) - return true; - } - - return false; + g_free(pfds); } -static gboolean -alsa_mixer_source_dispatch(GSource *_source, - G_GNUC_UNUSED GSourceFunc callback, - G_GNUC_UNUSED gpointer user_data) +void +AlsaMixerMonitor::DispatchSockets() { - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - - snd_mixer_handle_events(source->mixer); - return true; + snd_mixer_handle_events(mixer); } -static void -alsa_mixer_source_finalize(GSource *_source) -{ - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - - for (GSList *i = source->fds; i != NULL; i = i->next) - g_free(i->data); - - g_slist_free(source->fds); -} - -static GSourceFuncs alsa_mixer_source_funcs = { - .prepare = alsa_mixer_source_prepare, - .check = alsa_mixer_source_check, - .dispatch = alsa_mixer_source_dispatch, - .finalize = alsa_mixer_source_finalize, -}; - /* * libasound callbacks * @@ -209,7 +119,7 @@ static int alsa_mixer_elem_callback(G_GNUC_UNUSED snd_mixer_elem_t *elem, unsigned mask) { if (mask & SND_CTL_EVENT_MASK_VALUE) - event_pipe_emit(PIPE_EVENT_MIXER); + GlobalEvents::Emit(GlobalEvents::MIXER); return 0; } @@ -304,11 +214,7 @@ alsa_mixer_setup(struct alsa_mixer *am, GError **error_r) snd_mixer_elem_set_callback(am->elem, alsa_mixer_elem_callback); - am->source = (struct alsa_mixer_source *) - g_source_new(&alsa_mixer_source_funcs, sizeof(*am->source)); - am->source->mixer = am->handle; - am->source->fds = NULL; - g_source_attach(&am->source->source, g_main_context_default()); + am->monitor = new AlsaMixerMonitor(*main_loop, am->handle); return true; } @@ -343,8 +249,7 @@ alsa_mixer_close(struct mixer *data) assert(am->handle != NULL); - g_source_destroy(&am->source->source); - g_source_unref(&am->source->source); + delete am->monitor; snd_mixer_elem_set_callback(am->elem, NULL); snd_mixer_close(am->handle); @@ -421,11 +326,11 @@ alsa_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) } const struct mixer_plugin alsa_mixer_plugin = { - .init = alsa_mixer_init, - .finish = alsa_mixer_finish, - .open = alsa_mixer_open, - .close = alsa_mixer_close, - .get_volume = alsa_mixer_get_volume, - .set_volume = alsa_mixer_set_volume, - .global = true, + alsa_mixer_init, + alsa_mixer_finish, + alsa_mixer_open, + alsa_mixer_close, + alsa_mixer_get_volume, + alsa_mixer_set_volume, + true, }; diff --git a/src/mixer/pulse_mixer_plugin.c b/src/mixer/PulseMixerPlugin.cxx index a82c032b3..65dbc01fe 100644 --- a/src/mixer/pulse_mixer_plugin.c +++ b/src/mixer/PulseMixerPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,11 @@ */ #include "config.h" -#include "pulse_mixer_plugin.h" +#include "PulseMixerPlugin.h" #include "mixer_api.h" #include "output/pulse_output_plugin.h" #include "conf.h" -#include "event_pipe.h" +#include "GlobalEvents.hxx" #include <glib.h> @@ -66,7 +66,7 @@ pulse_mixer_offline(struct pulse_mixer *pm) pm->online = false; - event_pipe_emit(PIPE_EVENT_MIXER); + GlobalEvents::Emit(GlobalEvents::MIXER); } /** @@ -77,7 +77,7 @@ static void pulse_mixer_volume_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i, int eol, void *userdata) { - struct pulse_mixer *pm = userdata; + struct pulse_mixer *pm = (struct pulse_mixer *)userdata; if (eol) return; @@ -90,7 +90,7 @@ pulse_mixer_volume_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_inf pm->online = true; pm->volume = i->volume; - event_pipe_emit(PIPE_EVENT_MIXER); + GlobalEvents::Emit(GlobalEvents::MIXER); } static void @@ -153,16 +153,15 @@ static struct mixer * pulse_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, GError **error_r) { - struct pulse_mixer *pm; - struct pulse_output *po = ao; + struct pulse_output *po = (struct pulse_output *)ao; if (ao == NULL) { g_set_error(error_r, pulse_mixer_quark(), 0, "The pulse mixer cannot work without the audio output"); - return false; + return nullptr; } - pm = g_new(struct pulse_mixer,1); + struct pulse_mixer *pm = g_new(struct pulse_mixer,1); mixer_init(&pm->base, &pulse_mixer_plugin); pm->online = false; @@ -229,8 +228,11 @@ pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) } const struct mixer_plugin pulse_mixer_plugin = { - .init = pulse_mixer_init, - .finish = pulse_mixer_finish, - .get_volume = pulse_mixer_get_volume, - .set_volume = pulse_mixer_set_volume, + pulse_mixer_init, + pulse_mixer_finish, + nullptr, + nullptr, + pulse_mixer_get_volume, + pulse_mixer_set_volume, + false, }; diff --git a/src/mixer/pulse_mixer_plugin.h b/src/mixer/PulseMixerPlugin.h index 461633d37..f432c44a0 100644 --- a/src/mixer/pulse_mixer_plugin.h +++ b/src/mixer/PulseMixerPlugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,6 +26,10 @@ struct pulse_mixer; struct pa_context; struct pa_stream; +#ifdef __cplusplus +extern "C" { +#endif + void pulse_mixer_on_connect(struct pulse_mixer *pm, struct pa_context *context); @@ -36,4 +40,8 @@ void pulse_mixer_on_change(struct pulse_mixer *pm, struct pa_context *context, struct pa_stream *stream); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/mixer/winmm_mixer_plugin.c b/src/mixer/winmm_mixer_plugin.c index ceddf6afd..99da60cce 100644 --- a/src/mixer/winmm_mixer_plugin.c +++ b/src/mixer/winmm_mixer_plugin.c @@ -22,6 +22,8 @@ #include "output_api.h" #include "output/winmm_output_plugin.h" +#include <mmsystem.h> + #include <assert.h> #include <math.h> #include <windows.h> diff --git a/src/mixer_api.h b/src/mixer_api.h index 29c1e00ca..f0c9a0937 100644 --- a/src/mixer_api.h +++ b/src/mixer_api.h @@ -46,7 +46,15 @@ struct mixer { bool failed; }; +#ifdef __cplusplus +extern "C" { +#endif + void mixer_init(struct mixer *mixer, const struct mixer_plugin *plugin); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/mixer_control.h b/src/mixer_control.h index 6c3468aca..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/mpd_error.h b/src/mpd_error.h index 219738ced..e0b7d29a4 100644 --- a/src/mpd_error.h +++ b/src/mpd_error.h @@ -20,6 +20,7 @@ #ifndef MPD_ERROR_H #define MPD_ERROR_H +#include <glib.h> #include <stdlib.h> /* This macro is used as an intermediate step to a proper error handling diff --git a/src/notify.c b/src/notify.c deleted file mode 100644 index 3c0112c91..000000000 --- a/src/notify.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "notify.h" - -void notify_init(struct notify *notify) -{ - notify->mutex = g_mutex_new(); - notify->cond = g_cond_new(); - notify->pending = false; -} - -void notify_deinit(struct notify *notify) -{ - g_mutex_free(notify->mutex); - g_cond_free(notify->cond); -} - -void notify_wait(struct notify *notify) -{ - g_mutex_lock(notify->mutex); - while (!notify->pending) - g_cond_wait(notify->cond, notify->mutex); - notify->pending = false; - g_mutex_unlock(notify->mutex); -} - -void notify_signal(struct notify *notify) -{ - g_mutex_lock(notify->mutex); - notify->pending = true; - g_cond_signal(notify->cond); - g_mutex_unlock(notify->mutex); -} - -void notify_clear(struct notify *notify) -{ - g_mutex_lock(notify->mutex); - notify->pending = false; - g_mutex_unlock(notify->mutex); -} diff --git a/src/state_file.h b/src/notify.cxx index 4c4f881cc..64018968c 100644 --- a/src/state_file.h +++ b/src/notify.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,17 +17,29 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_STATE_FILE_H -#define MPD_STATE_FILE_H - -struct player_control; +#include "config.h" +#include "notify.hxx" void -state_file_init(const char *path, struct player_control *pc); +notify::Wait() +{ + const ScopeLock protect(mutex); + while (!pending) + cond.wait(mutex); + pending = false; +} void -state_file_finish(struct player_control *pc); - -void write_state_file(void); +notify::Signal() +{ + const ScopeLock protect(mutex); + pending = true; + cond.signal(); +} -#endif /* STATE_FILE_H */ +void +notify::Clear() +{ + const ScopeLock protect(mutex); + pending = false; +} diff --git a/src/notify.hxx b/src/notify.hxx new file mode 100644 index 000000000..6b9e95368 --- /dev/null +++ b/src/notify.hxx @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NOTIFY_HXX +#define MPD_NOTIFY_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +struct notify { + Mutex mutex; + Cond cond; + bool pending; + +#ifndef WIN32 + constexpr +#endif + notify():pending(false) {} + + /** + * Wait for a notification. Return immediately if we have already + * been notified since we last returned from notify_wait(). + */ + void Wait(); + + /** + * Notify the thread. This function never blocks. + */ + void Signal(); + + /** + * Clears a pending notification. + */ + void Clear(); +}; + +#endif diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx new file mode 100644 index 000000000..33aad1919 --- /dev/null +++ b/src/output/HttpdClient.cxx @@ -0,0 +1,592 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "HttpdClient.hxx" +#include "HttpdInternal.hxx" +#include "util/fifo_buffer.h" +#include "page.h" +#include "icy_server.h" +#include "glib_socket.h" + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "httpd_output" + +HttpdClient::~HttpdClient() +{ + if (state == RESPONSE) { + if (write_source_id != 0) + g_source_remove(write_source_id); + + if (current_page != nullptr) + page_unref(current_page); + + for (auto page : pages) + page_unref(page); + } else + fifo_buffer_free(input); + + if (metadata) + page_unref(metadata); + + g_source_remove(read_source_id); + g_io_channel_unref(channel); +} + +void +HttpdClient::Close() +{ + httpd_output_remove_client(httpd, this); +} + +void +HttpdClient::LockClose() +{ + const ScopeLock protect(httpd->mutex); + Close(); +} + +void +HttpdClient::BeginResponse() +{ + assert(state != RESPONSE); + + state = RESPONSE; + write_source_id = 0; + current_page = nullptr; + + httpd_output_send_header(httpd, this); +} + +/** + * Handle a line of the HTTP request. + */ +bool +HttpdClient::HandleLine(const char *line) +{ + assert(state != RESPONSE); + + if (state == REQUEST) { + if (strncmp(line, "GET /", 5) != 0) { + /* only GET is supported */ + g_warning("malformed request line from client"); + return false; + } + + line = strchr(line + 5, ' '); + if (line == nullptr || strncmp(line + 1, "HTTP/", 5) != 0) { + /* HTTP/0.9 without request headers */ + BeginResponse(); + return true; + } + + /* after the request line, request headers follow */ + state = HEADERS; + return true; + } else { + if (*line == 0) { + /* empty line: request is finished */ + BeginResponse(); + return true; + } + + if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) { + /* Send icy metadata */ + metadata_requested = metadata_supported; + return true; + } + + if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { + /* Send as dlna */ + dlna_streaming_requested = true; + /* metadata is not supported by dlna streaming, so disable it */ + metadata_supported = false; + metadata_requested = false; + return true; + } + + /* expect more request headers */ + return true; + } +} + +char * +HttpdClient::ReadLine() +{ + assert(state != RESPONSE); + + const ScopeLock protect(httpd->mutex); + + size_t length; + const char *p = (const char *)fifo_buffer_read(input, &length); + if (p == nullptr) + /* empty input buffer */ + return nullptr; + + const char *newline = (const char *)memchr(p, '\n', length); + if (newline == nullptr) + /* incomplete line */ + return nullptr; + + char *line = g_strndup(p, newline - p); + fifo_buffer_consume(input, newline - p + 1); + + /* remove trailing whitespace (e.g. '\r') */ + return g_strchomp(line); +} + +/** + * Sends the status line and response headers to the client. + */ +bool +HttpdClient::SendResponse() +{ + char buffer[1024]; + GError *error = nullptr; + GIOStatus status; + gsize bytes_written; + + assert(state == RESPONSE); + + if (dlna_streaming_requested) { + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 206 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: 10000\r\n" + "Content-RangeX: 0-1000000/1000000\r\n" + "transferMode.dlna.org: Streaming\r\n" + "Accept-Ranges: bytes\r\n" + "Connection: close\r\n" + "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" + "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" + "\r\n", + httpd->content_type); + + } else if (metadata_requested) { + gchar *metadata_header; + + metadata_header = + icy_server_metadata_header(httpd->name, httpd->genre, + httpd->website, + httpd->content_type, + metaint); + + g_strlcpy(buffer, metadata_header, sizeof(buffer)); + + g_free(metadata_header); + + } else { /* revert to a normal HTTP request */ + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + httpd->content_type); + } + + status = g_io_channel_write_chars(channel, + buffer, strlen(buffer), + &bytes_written, &error); + + switch (status) { + case G_IO_STATUS_NORMAL: + case G_IO_STATUS_AGAIN: + return true; + + case G_IO_STATUS_EOF: + /* client has disconnected */ + + Close(); + return false; + + case G_IO_STATUS_ERROR: + /* I/O error */ + + g_warning("failed to write to client: %s", error->message); + g_error_free(error); + + Close(); + return false; + } + + /* unreachable */ + Close(); + return false; +} + +bool +HttpdClient::Received() +{ + assert(state != RESPONSE); + + char *line; + bool success; + + while ((line = ReadLine()) != nullptr) { + success = HandleLine(line); + g_free(line); + if (!success) { + assert(state != RESPONSE); + return false; + } + + if (state == RESPONSE) { + if (!fifo_buffer_is_empty(input)) { + g_warning("unexpected input from client"); + return false; + } + + fifo_buffer_free(input); + + return SendResponse(); + } + } + + return true; +} + +bool +HttpdClient::Read() +{ + size_t max_length; + GError *error = nullptr; + GIOStatus status; + gsize bytes_read; + + if (state == RESPONSE) { + /* the client has already sent the request, and he + must not send more */ + char buffer[1]; + + status = g_io_channel_read_chars(channel, buffer, + sizeof(buffer), &bytes_read, + nullptr); + if (status == G_IO_STATUS_NORMAL) + g_warning("unexpected input from client"); + + return false; + } + + char *p = (char *)fifo_buffer_write(input, &max_length); + if (p == nullptr) { + g_warning("buffer overflow"); + return false; + } + + status = g_io_channel_read_chars(channel, p, max_length, + &bytes_read, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + fifo_buffer_append(input, bytes_read); + return Received(); + + case G_IO_STATUS_AGAIN: + /* try again later, after select() */ + return true; + + case G_IO_STATUS_EOF: + /* peer disconnected */ + return false; + + case G_IO_STATUS_ERROR: + /* I/O error */ + g_warning("failed to read from client: %s", + error->message); + g_error_free(error); + return false; + } + + /* unreachable */ + return false; +} + +static gboolean +httpd_client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, + gpointer data) +{ + HttpdClient *client = (HttpdClient *)data; + + if (condition == G_IO_IN && client->Read()) { + return true; + } else { + client->LockClose(); + return false; + } +} + +HttpdClient::HttpdClient(httpd_output *_httpd, int _fd, + bool _metadata_supported) + :httpd(_httpd), + channel(g_io_channel_new_socket(_fd)), + input(fifo_buffer_new(4096)), + state(REQUEST), + dlna_streaming_requested(false), + metadata_supported(_metadata_supported), + metadata_requested(false), metadata_sent(true), + metaint(8192), /*TODO: just a std value */ + metadata(nullptr), + metadata_current_position(0), metadata_fill(0) +{ + /* GLib is responsible for closing the file descriptor */ + g_io_channel_set_close_on_unref(channel, true); + /* NULL encoding means the stream is binary safe */ + g_io_channel_set_encoding(channel, nullptr, nullptr); + /* we prefer to do buffering */ + g_io_channel_set_buffered(channel, false); + + read_source_id = g_io_add_watch(channel, + GIOCondition(G_IO_IN|G_IO_ERR|G_IO_HUP), + httpd_client_in_event, this); +} + +size_t +HttpdClient::GetQueueSize() const +{ + if (state != RESPONSE) + return 0; + + size_t size = 0; + for (auto page : pages) + size += page->size; + return size; +} + +void +HttpdClient::CancelQueue() +{ + if (state != RESPONSE) + return; + + for (auto page : pages) + page_unref(page); + pages.clear(); + + if (write_source_id != 0 && current_page == nullptr) { + g_source_remove(write_source_id); + write_source_id = 0; + } +} + +static GIOStatus +write_page_to_channel(GIOChannel *channel, + const struct page *page, size_t position, + gsize *bytes_written_r, GError **error) +{ + assert(channel != nullptr); + assert(page != nullptr); + assert(position < page->size); + + return g_io_channel_write_chars(channel, + (const gchar*)page->data + position, + page->size - position, + bytes_written_r, error); +} + +static GIOStatus +write_n_bytes_to_channel(GIOChannel *channel, const struct page *page, + size_t position, gint n, + gsize *bytes_written_r, GError **error) +{ + GIOStatus status; + + assert(channel != nullptr); + assert(page != nullptr); + assert(position < page->size); + + if (n == -1) { + status = write_page_to_channel (channel, page, position, + bytes_written_r, error); + } else { + status = g_io_channel_write_chars(channel, + (const gchar*)page->data + position, + n, bytes_written_r, error); + } + + return status; +} + +int +HttpdClient::GetBytesTillMetaData() const +{ + if (metadata_requested && + current_page->size - current_position > metaint - metadata_fill) + return metaint - metadata_fill; + + return -1; +} + +inline bool +HttpdClient::Write() +{ + GError *error = nullptr; + GIOStatus status; + gsize bytes_written; + + const ScopeLock protect(httpd->mutex); + + assert(state == RESPONSE); + + if (write_source_id == 0) + /* another thread has removed the event source while + this thread was waiting for httpd->mutex */ + return false; + + if (current_page == nullptr) { + current_page = pages.front(); + pages.pop_front(); + current_position = 0; + } + + const gint bytes_to_write = GetBytesTillMetaData(); + if (bytes_to_write == 0) { + gint metadata_to_write; + + metadata_to_write = metadata_current_position; + + if (!metadata_sent) { + status = write_page_to_channel(channel, + metadata, + metadata_to_write, + &bytes_written, &error); + + metadata_current_position += bytes_written; + + if (metadata->size - metadata_current_position == 0) { + metadata_fill = 0; + metadata_current_position = 0; + metadata_sent = true; + } + } else { + struct page *empty_meta; + guchar empty_data = 0; + + empty_meta = page_new_copy(&empty_data, 1); + + status = write_page_to_channel(channel, + empty_meta, + metadata_to_write, + &bytes_written, &error); + + metadata_current_position += bytes_written; + + if (empty_meta->size - metadata_current_position == 0) { + metadata_fill = 0; + metadata_current_position = 0; + } + } + + bytes_written = 0; + } else { + status = write_n_bytes_to_channel(channel, current_page, + current_position, bytes_to_write, + &bytes_written, &error); + } + + switch (status) { + case G_IO_STATUS_NORMAL: + current_position += bytes_written; + assert(current_position <= current_page->size); + + if (metadata_requested) + metadata_fill += bytes_written; + + if (current_position >= current_page->size) { + page_unref(current_page); + current_page = nullptr; + + if (pages.empty()) { + /* all pages are sent: remove the + event source */ + write_source_id = 0; + + return false; + } + } + + return true; + + case G_IO_STATUS_AGAIN: + return true; + + case G_IO_STATUS_EOF: + /* client has disconnected */ + + Close(); + return false; + + case G_IO_STATUS_ERROR: + /* I/O error */ + + g_warning("failed to write to client: %s", error->message); + g_error_free(error); + + Close(); + return false; + } + + /* unreachable */ + Close(); + return false; +} + +static gboolean +httpd_client_out_event(gcc_unused GIOChannel *source, + gcc_unused GIOCondition condition, gpointer data) +{ + assert(condition == G_IO_OUT); + + HttpdClient *client = (HttpdClient *)data; + return client->Write(); +} + +void +HttpdClient::PushPage(struct page *page) +{ + if (state != RESPONSE) + /* the client is still writing the HTTP request */ + return; + + page_ref(page); + pages.push_back(page); + + if (write_source_id == 0) + write_source_id = g_io_add_watch(channel, G_IO_OUT, + httpd_client_out_event, + this); +} + +void +HttpdClient::PushMetaData(struct page *page) +{ + if (metadata) { + page_unref(metadata); + metadata = nullptr; + } + + g_return_if_fail (page); + + page_ref(page); + metadata = page; + metadata_sent = false; +} diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx new file mode 100644 index 000000000..d3ed2746d --- /dev/null +++ b/src/output/HttpdClient.hxx @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_HTTPD_CLIENT_HXX +#define MPD_OUTPUT_HTTPD_CLIENT_HXX + +#include "gcc.h" + +#include <glib.h> + +#include <list> + +#include <stddef.h> + +struct httpd_output; +struct page; + +class HttpdClient final { + /** + * The httpd output object this client is connected to. + */ + httpd_output *const httpd; + + /** + * The TCP socket. + */ + GIOChannel *channel; + + /** + * The GLib main loop source id for reading from the socket, + * and to detect errors. + */ + guint read_source_id; + + /** + * The GLib main loop source id for writing to the socket. If + * 0, then there is no event source currently (because there + * are no queued pages). + */ + guint write_source_id; + + /** + * For buffered reading. This pointer is only valid while the + * HTTP request is read. + */ + struct fifo_buffer *input; + + /** + * The current state of the client. + */ + enum { + /** reading the request line */ + REQUEST, + + /** reading the request headers */ + HEADERS, + + /** sending the HTTP response */ + RESPONSE, + } state; + + /** + * A queue of #page objects to be sent to the client. + */ + std::list<page *> pages; + + /** + * The #page which is currently being sent to the client. + */ + page *current_page; + + /** + * The amount of bytes which were already sent from + * #current_page. + */ + size_t current_position; + + /** + * If DLNA streaming was an option. + */ + bool dlna_streaming_requested; + + /* ICY */ + + /** + * Do we support sending Icy-Metadata to the client? This is + * disabled if the httpd audio output uses encoder tags. + */ + bool metadata_supported; + + /** + * If we should sent icy metadata. + */ + bool metadata_requested; + + /** + * If the current metadata was already sent to the client. + */ + bool metadata_sent; + + /** + * The amount of streaming data between each metadata block + */ + guint metaint; + + /** + * The metadata as #page which is currently being sent to the client. + */ + page *metadata; + + /* + * The amount of bytes which were already sent from the metadata. + */ + size_t metadata_current_position; + + /** + * The amount of streaming data sent to the client + * since the last icy information was sent. + */ + guint metadata_fill; + +public: + /** + * @param httpd the HTTP output device + * @param fd the socket file descriptor + */ + HttpdClient(httpd_output *httpd, int _fd, bool _metadata_supported); + + /** + * Note: this does not remove the client from the + * #httpd_output object. + */ + ~HttpdClient(); + + /** + * Frees the client and removes it from the server's client list. + */ + void Close(); + + void LockClose(); + + /** + * Returns the total size of this client's page queue. + */ + gcc_pure + size_t GetQueueSize() const; + + /** + * Clears the page queue. + */ + void CancelQueue(); + + bool Read(); + + /** + * Data has been received from the client and it is appended + * to the input buffer. + */ + bool Received(); + + /** + * Check if a complete line of input is present in the input + * buffer, and duplicates it. It is removed from the input + * buffer. The return value has to be freed with g_free(). + */ + char *ReadLine(); + + /** + * Handle a line of the HTTP request. + */ + bool HandleLine(const char *line); + + /** + * Switch the client to the "RESPONSE" state. + */ + void BeginResponse(); + + /** + * Sends the status line and response headers to the client. + */ + bool SendResponse(); + + gcc_pure + int GetBytesTillMetaData() const; + + bool Write(); + + /** + * Appends a page to the client's queue. + */ + void PushPage(page *page); + + /** + * Sends the passed metadata. + */ + void PushMetaData(page *page); +}; + +#endif diff --git a/src/output/httpd_internal.h b/src/output/HttpdInternal.hxx index 5dcb8ab9b..601c43162 100644 --- a/src/output/httpd_internal.h +++ b/src/output/HttpdInternal.hxx @@ -25,14 +25,18 @@ #ifndef MPD_OUTPUT_HTTPD_INTERNAL_H #define MPD_OUTPUT_HTTPD_INTERNAL_H +#include "HttpdClient.hxx" #include "output_internal.h" #include "timer.h" +#include "thread/Mutex.hxx" #include <glib.h> +#include <forward_list> + #include <stdbool.h> -struct httpd_client; +class HttpdClient; struct httpd_output { struct audio_output base; @@ -65,7 +69,7 @@ struct httpd_output { * This mutex protects the listener socket and the client * list. */ - GMutex *mutex; + mutable Mutex mutex; /** * A #timer object to synchronize this output with the @@ -105,7 +109,7 @@ struct httpd_output { * A linked list containing all clients which are currently * connected. */ - GList *clients; + std::forward_list<HttpdClient> clients; /** * A temporary buffer for the httpd_output_read_page() @@ -125,7 +129,7 @@ struct httpd_output { */ void httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client); + HttpdClient *client); /** * Sends the encoder header to the client. This is called right after @@ -133,6 +137,6 @@ httpd_output_remove_client(struct httpd_output *httpd, */ void httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client); + HttpdClient *client); #endif diff --git a/src/output/httpd_output_plugin.c b/src/output/HttpdOutputPlugin.cxx index 1d730df7f..d1a1a36df 100644 --- a/src/output/httpd_output_plugin.c +++ b/src/output/HttpdOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,9 @@ */ #include "config.h" -#include "httpd_output_plugin.h" -#include "httpd_internal.h" -#include "httpd_client.h" +#include "HttpdOutputPlugin.hxx" +#include "HttpdInternal.hxx" +#include "HttpdClient.hxx" #include "output_api.h" #include "encoder_plugin.h" #include "encoder_list.h" @@ -28,7 +28,8 @@ #include "page.h" #include "icy_server.h" #include "fd_util.h" -#include "server_socket.h" +#include "ServerSocket.hxx" +#include "Main.hxx" #include <assert.h> @@ -60,9 +61,9 @@ httpd_output_quark(void) */ G_GNUC_PURE static bool -httpd_output_has_clients(const struct httpd_output *httpd) +httpd_output_has_clients(const httpd_output *httpd) { - return httpd->clients != NULL; + return !httpd->clients.empty(); } /** @@ -70,12 +71,10 @@ httpd_output_has_clients(const struct httpd_output *httpd) */ G_GNUC_PURE static bool -httpd_output_lock_has_clients(const struct httpd_output *httpd) +httpd_output_lock_has_clients(const httpd_output *httpd) { - g_mutex_lock(httpd->mutex); - bool result = httpd_output_has_clients(httpd); - g_mutex_unlock(httpd->mutex); - return result; + const ScopeLock protect(httpd->mutex); + return httpd_output_has_clients(httpd); } static void @@ -83,32 +82,28 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, size_t address_length, int uid, void *ctx); static bool -httpd_output_bind(struct httpd_output *httpd, GError **error_r) +httpd_output_bind(httpd_output *httpd, GError **error_r) { httpd->open = false; - g_mutex_lock(httpd->mutex); - bool success = server_socket_open(httpd->server_socket, error_r); - g_mutex_unlock(httpd->mutex); - - return success; + const ScopeLock protect(httpd->mutex); + return server_socket_open(httpd->server_socket, error_r); } static void -httpd_output_unbind(struct httpd_output *httpd) +httpd_output_unbind(httpd_output *httpd) { assert(!httpd->open); - g_mutex_lock(httpd->mutex); + const ScopeLock protect(httpd->mutex); server_socket_close(httpd->server_socket); - g_mutex_unlock(httpd->mutex); } static struct audio_output * httpd_output_init(const struct config_param *param, GError **error) { - struct httpd_output *httpd = g_new(struct httpd_output, 1); + httpd_output *httpd = new httpd_output(); if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) { g_free(httpd); return NULL; @@ -140,7 +135,8 @@ httpd_output_init(const struct config_param *param, /* set up bind_to_address */ - httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd); + httpd->server_socket = server_socket_new(*main_loop, + httpd_listen_in_event, httpd); const char *bind_to_address = config_get_block_string(param, "bind_to_address", NULL); @@ -174,50 +170,44 @@ httpd_output_init(const struct config_param *param, httpd->content_type = "application/octet-stream"; } - httpd->mutex = g_mutex_new(); - return &httpd->base; } static void httpd_output_finish(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; if (httpd->metadata) page_unref(httpd->metadata); encoder_finish(httpd->encoder); server_socket_free(httpd->server_socket); - g_mutex_free(httpd->mutex); ao_base_finish(&httpd->base); - g_free(httpd); + delete httpd; } /** - * Creates a new #httpd_client object and adds it into the + * Creates a new #HttpdClient object and adds it into the * httpd_output.clients linked list. */ static void -httpd_client_add(struct httpd_output *httpd, int fd) +httpd_client_add(httpd_output *httpd, int fd) { - struct httpd_client *client = - httpd_client_new(httpd, fd, - httpd->encoder->plugin->tag == NULL); - - httpd->clients = g_list_prepend(httpd->clients, client); + httpd->clients.emplace_front(httpd, fd, + httpd->encoder->plugin->tag == NULL); httpd->clients_cnt++; /* pass metadata to client */ if (httpd->metadata) - httpd_client_send_metadata(client, httpd->metadata); + httpd->clients.front().PushMetaData(httpd->metadata); } static void httpd_listen_in_event(int fd, const struct sockaddr *address, size_t address_length, G_GNUC_UNUSED int uid, void *ctx) { - struct httpd_output *httpd = ctx; + httpd_output *httpd = (httpd_output *)ctx; /* the listener socket has become readable - a client has connected */ @@ -238,7 +228,6 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, progname, hostaddr); g_free(hostaddr); close_socket(fd); - g_mutex_unlock(httpd->mutex); return; } @@ -249,7 +238,7 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, (void)address_length; #endif /* HAVE_WRAP */ - g_mutex_lock(httpd->mutex); + const ScopeLock protect(httpd->mutex); if (fd >= 0) { /* can we allow additional client */ @@ -262,8 +251,6 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, } else if (fd < 0 && errno != EINTR) { g_warning("accept() failed: %s", g_strerror(errno)); } - - g_mutex_unlock(httpd->mutex); } /** @@ -271,7 +258,7 @@ httpd_listen_in_event(int fd, const struct sockaddr *address, * as a new #page object. */ static struct page * -httpd_output_read_page(struct httpd_output *httpd) +httpd_output_read_page(httpd_output *httpd) { if (httpd->unflushed_input >= 65536) { /* we have fed a lot of input into the encoder, but it @@ -301,7 +288,7 @@ httpd_output_read_page(struct httpd_output *httpd) } static bool -httpd_output_encoder_open(struct httpd_output *httpd, +httpd_output_encoder_open(httpd_output *httpd, struct audio_format *audio_format, GError **error) { @@ -321,7 +308,7 @@ httpd_output_encoder_open(struct httpd_output *httpd, static bool httpd_output_enable(struct audio_output *ao, GError **error_r) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; return httpd_output_bind(httpd, error_r); } @@ -329,7 +316,7 @@ httpd_output_enable(struct audio_output *ao, GError **error_r) static void httpd_output_disable(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; httpd_output_unbind(httpd); } @@ -338,82 +325,75 @@ static bool httpd_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; + + assert(httpd->clients.empty()); - g_mutex_lock(httpd->mutex); + const ScopeLock protect(httpd->mutex); /* open the encoder */ - if (!httpd_output_encoder_open(httpd, audio_format, error)) { - g_mutex_unlock(httpd->mutex); + if (!httpd_output_encoder_open(httpd, audio_format, error)) return false; - } /* initialize other attributes */ - httpd->clients = NULL; httpd->clients_cnt = 0; httpd->timer = timer_new(audio_format); httpd->open = true; - g_mutex_unlock(httpd->mutex); return true; } static void -httpd_client_delete(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - httpd_client_free(client); -} - -static void httpd_output_close(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; - g_mutex_lock(httpd->mutex); + const ScopeLock protect(httpd->mutex); httpd->open = false; timer_free(httpd->timer); - g_list_foreach(httpd->clients, httpd_client_delete, NULL); - g_list_free(httpd->clients); + httpd->clients.clear(); if (httpd->header != NULL) page_unref(httpd->header); encoder_close(httpd->encoder); - - g_mutex_unlock(httpd->mutex); } void -httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client) +httpd_output_remove_client(httpd_output *httpd, HttpdClient *client) { assert(httpd != NULL); + assert(httpd->clients_cnt > 0); assert(client != NULL); - httpd->clients = g_list_remove(httpd->clients, client); - httpd->clients_cnt--; + for (auto prev = httpd->clients.before_begin(), i = std::next(prev);; + prev = i, i = std::next(prev)) { + assert(i != httpd->clients.end()); + if (&*i == client) { + httpd->clients.erase_after(prev); + httpd->clients_cnt--; + break; + } + } } void -httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client) +httpd_output_send_header(httpd_output *httpd, HttpdClient *client) { if (httpd->header != NULL) - httpd_client_send(client, httpd->header); + client->PushPage(httpd->header); } static unsigned httpd_output_delay(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; if (!httpd_output_lock_has_clients(httpd) && httpd->base.pause) { /* if there's no client and this output is paused, @@ -433,51 +413,35 @@ httpd_output_delay(struct audio_output *ao) : 0; } -static void -httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - if (httpd_client_queue_size(client) > 256 * 1024) { - g_debug("client is too slow, flushing its queue"); - httpd_client_cancel(client); - } -} - -static void -httpd_client_send_page(gpointer data, gpointer user_data) -{ - struct httpd_client *client = data; - struct page *page = user_data; - - httpd_client_send(client, page); -} - /** * Broadcasts a page struct to all clients. */ static void -httpd_output_broadcast_page(struct httpd_output *httpd, struct page *page) +httpd_output_broadcast_page(httpd_output *httpd, struct page *page) { assert(page != NULL); - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_send_page, page); - g_mutex_unlock(httpd->mutex); + const ScopeLock protect(httpd->mutex); + for (auto &client : httpd->clients) + client.PushPage(page); } /** * Broadcasts data from the encoder to all clients. */ static void -httpd_output_encoder_to_clients(struct httpd_output *httpd) +httpd_output_encoder_to_clients(httpd_output *httpd) { - struct page *page; - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_check_queue, NULL); - g_mutex_unlock(httpd->mutex); + httpd->mutex.lock(); + for (auto &client : httpd->clients) { + if (client.GetQueueSize() > 256 * 1024) { + g_debug("client is too slow, flushing its queue"); + client.CancelQueue(); + } + } + httpd->mutex.unlock(); + struct page *page; while ((page = httpd_output_read_page(httpd)) != NULL) { httpd_output_broadcast_page(httpd, page); page_unref(page); @@ -485,7 +449,7 @@ httpd_output_encoder_to_clients(struct httpd_output *httpd) } static bool -httpd_output_encode_and_play(struct httpd_output *httpd, +httpd_output_encode_and_play(httpd_output *httpd, const void *chunk, size_t size, GError **error) { if (!encoder_write(httpd->encoder, chunk, size, error)) @@ -502,7 +466,7 @@ static size_t httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error_r) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; if (httpd_output_lock_has_clients(httpd)) { if (!httpd_output_encode_and_play(httpd, chunk, size, error_r)) @@ -519,10 +483,10 @@ httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, static bool httpd_output_pause(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; if (httpd_output_lock_has_clients(httpd)) { - static const char silence[1020]; + static const char silence[1020] = { 0 }; return httpd_output_play(ao, silence, sizeof(silence), NULL) > 0; } else { @@ -531,18 +495,9 @@ httpd_output_pause(struct audio_output *ao) } static void -httpd_send_metadata(gpointer data, gpointer user_data) -{ - struct httpd_client *client = data; - struct page *icy_metadata = user_data; - - httpd_client_send_metadata(client, icy_metadata); -} - -static void httpd_output_tag(struct audio_output *ao, const struct tag *tag) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; assert(tag != NULL); @@ -581,43 +536,37 @@ httpd_output_tag(struct audio_output *ao, const struct tag *tag) TAG_ARTIST, TAG_TITLE, TAG_NUM_OF_ITEM_TYPES); if (httpd->metadata != NULL) { - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, - httpd_send_metadata, httpd->metadata); - g_mutex_unlock(httpd->mutex); + const ScopeLock protect(httpd->mutex); + for (auto &client : httpd->clients) + client.PushMetaData(httpd->metadata); } } } static void -httpd_client_cancel_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - httpd_client_cancel(client); -} - -static void httpd_output_cancel(struct audio_output *ao) { - struct httpd_output *httpd = (struct httpd_output *)ao; + httpd_output *httpd = (httpd_output *)ao; - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL); - g_mutex_unlock(httpd->mutex); + const ScopeLock protect(httpd->mutex); + for (auto &client : httpd->clients) + client.CancelQueue(); } const struct audio_output_plugin httpd_output_plugin = { - .name = "httpd", - .init = httpd_output_init, - .finish = httpd_output_finish, - .enable = httpd_output_enable, - .disable = httpd_output_disable, - .open = httpd_output_open, - .close = httpd_output_close, - .delay = httpd_output_delay, - .send_tag = httpd_output_tag, - .play = httpd_output_play, - .pause = httpd_output_pause, - .cancel = httpd_output_cancel, + "httpd", + nullptr, + httpd_output_init, + httpd_output_finish, + httpd_output_enable, + httpd_output_disable, + httpd_output_open, + httpd_output_close, + httpd_output_delay, + httpd_output_tag, + httpd_output_play, + nullptr, + httpd_output_cancel, + httpd_output_pause, + nullptr, }; diff --git a/src/output/httpd_output_plugin.h b/src/output/HttpdOutputPlugin.hxx index d0eb1533f..c74d2bd4a 100644 --- a/src/output/httpd_output_plugin.h +++ b/src/output/HttpdOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_HTTPD_OUTPUT_PLUGIN_H -#define MPD_HTTPD_OUTPUT_PLUGIN_H +#ifndef MPD_HTTPD_OUTPUT_PLUGIN_HXX +#define MPD_HTTPD_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin httpd_output_plugin; diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c index 022be0b4a..3d6171ae2 100644 --- a/src/output/fifo_output_plugin.c +++ b/src/output/fifo_output_plugin.c @@ -20,7 +20,6 @@ #include "config.h" #include "fifo_output_plugin.h" #include "output_api.h" -#include "utils.h" #include "timer.h" #include "fd_util.h" #include "open.h" diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c deleted file mode 100644 index 72de90457..000000000 --- a/src/output/httpd_client.c +++ /dev/null @@ -1,764 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "httpd_client.h" -#include "httpd_internal.h" -#include "fifo_buffer.h" -#include "page.h" -#include "icy_server.h" -#include "glib_socket.h" - -#include <stdbool.h> -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "httpd_output" - -struct httpd_client { - /** - * The httpd output object this client is connected to. - */ - struct httpd_output *httpd; - - /** - * The TCP socket. - */ - GIOChannel *channel; - - /** - * The GLib main loop source id for reading from the socket, - * and to detect errors. - */ - guint read_source_id; - - /** - * The GLib main loop source id for writing to the socket. If - * 0, then there is no event source currently (because there - * are no queued pages). - */ - guint write_source_id; - - /** - * For buffered reading. This pointer is only valid while the - * HTTP request is read. - */ - struct fifo_buffer *input; - - /** - * The current state of the client. - */ - enum { - /** reading the request line */ - REQUEST, - - /** reading the request headers */ - HEADERS, - - /** sending the HTTP response */ - RESPONSE, - } state; - - /** - * A queue of #page objects to be sent to the client. - */ - GQueue *pages; - - /** - * The #page which is currently being sent to the client. - */ - struct page *current_page; - - /** - * The amount of bytes which were already sent from - * #current_page. - */ - size_t current_position; - - /** - * If DLNA streaming was an option. - */ - bool dlna_streaming_requested; - - /* ICY */ - - /** - * Do we support sending Icy-Metadata to the client? This is - * disabled if the httpd audio output uses encoder tags. - */ - bool metadata_supported; - - /** - * If we should sent icy metadata. - */ - bool metadata_requested; - - /** - * If the current metadata was already sent to the client. - */ - bool metadata_sent; - - /** - * The amount of streaming data between each metadata block - */ - guint metaint; - - /** - * The metadata as #page which is currently being sent to the client. - */ - struct page *metadata; - - /* - * The amount of bytes which were already sent from the metadata. - */ - size_t metadata_current_position; - - /** - * The amount of streaming data sent to the client - * since the last icy information was sent. - */ - guint metadata_fill; -}; - -static void -httpd_client_unref_page(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct page *page = data; - - page_unref(page); -} - -void -httpd_client_free(struct httpd_client *client) -{ - assert(client != NULL); - - if (client->state == RESPONSE) { - if (client->write_source_id != 0) - g_source_remove(client->write_source_id); - - if (client->current_page != NULL) - page_unref(client->current_page); - - g_queue_foreach(client->pages, httpd_client_unref_page, NULL); - g_queue_free(client->pages); - } else - fifo_buffer_free(client->input); - - if (client->metadata) - page_unref (client->metadata); - - g_source_remove(client->read_source_id); - g_io_channel_unref(client->channel); - g_free(client); -} - -/** - * Frees the client and removes it from the server's client list. - */ -static void -httpd_client_close(struct httpd_client *client) -{ - assert(client != NULL); - - httpd_output_remove_client(client->httpd, client); - httpd_client_free(client); -} - -/** - * Switch the client to the "RESPONSE" state. - */ -static void -httpd_client_begin_response(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - client->state = RESPONSE; - client->write_source_id = 0; - client->pages = g_queue_new(); - client->current_page = NULL; - - httpd_output_send_header(client->httpd, client); -} - -/** - * Handle a line of the HTTP request. - */ -static bool -httpd_client_handle_line(struct httpd_client *client, const char *line) -{ - assert(client->state != RESPONSE); - - if (client->state == REQUEST) { - if (strncmp(line, "GET /", 5) != 0) { - /* only GET is supported */ - g_warning("malformed request line from client"); - return false; - } - - line = strchr(line + 5, ' '); - if (line == NULL || strncmp(line + 1, "HTTP/", 5) != 0) { - /* HTTP/0.9 without request headers */ - httpd_client_begin_response(client); - return true; - } - - /* after the request line, request headers follow */ - client->state = HEADERS; - return true; - } else { - if (*line == 0) { - /* empty line: request is finished */ - httpd_client_begin_response(client); - return true; - } - - if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) { - /* Send icy metadata */ - client->metadata_requested = - client->metadata_supported; - return true; - } - - if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { - /* Send as dlna */ - client->dlna_streaming_requested = true; - /* metadata is not supported by dlna streaming, so disable it */ - client->metadata_supported = false; - client->metadata_requested = false; - return true; - } - - /* expect more request headers */ - return true; - } -} - -/** - * Check if a complete line of input is present in the input buffer, - * and duplicates it. It is removed from the input buffer. The - * return value has to be freed with g_free(). - */ -static char * -httpd_client_read_line(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - const char *p, *newline; - size_t length; - char *line; - - p = fifo_buffer_read(client->input, &length); - if (p == NULL) - /* empty input buffer */ - return NULL; - - newline = memchr(p, '\n', length); - if (newline == NULL) - /* incomplete line */ - return NULL; - - line = g_strndup(p, newline - p); - fifo_buffer_consume(client->input, newline - p + 1); - - /* remove trailing whitespace (e.g. '\r') */ - return g_strchomp(line); -} - -/** - * Sends the status line and response headers to the client. - */ -static bool -httpd_client_send_response(struct httpd_client *client) -{ - char buffer[1024]; - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->state == RESPONSE); - - if (client->dlna_streaming_requested) { - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 206 OK\r\n" - "Content-Type: %s\r\n" - "Content-Length: 10000\r\n" - "Content-RangeX: 0-1000000/1000000\r\n" - "transferMode.dlna.org: Streaming\r\n" - "Accept-Ranges: bytes\r\n" - "Connection: close\r\n" - "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" - "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" - "\r\n", - client->httpd->content_type); - - } else if (client->metadata_requested) { - gchar *metadata_header; - - metadata_header = icy_server_metadata_header( - client->httpd->name, - client->httpd->genre, - client->httpd->website, - client->httpd->content_type, - client->metaint); - - g_strlcpy(buffer, metadata_header, sizeof(buffer)); - - g_free(metadata_header); - - } else { /* revert to a normal HTTP request */ - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 200 OK\r\n" - "Content-Type: %s\r\n" - "Connection: close\r\n" - "Pragma: no-cache\r\n" - "Cache-Control: no-cache, no-store\r\n" - "\r\n", - client->httpd->content_type); - } - - status = g_io_channel_write_chars(client->channel, - buffer, strlen(buffer), - &bytes_written, &error); - - switch (status) { - case G_IO_STATUS_NORMAL: - case G_IO_STATUS_AGAIN: - return true; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - httpd_client_close(client); - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - g_warning("failed to write to client: %s", error->message); - g_error_free(error); - - httpd_client_close(client); - return false; - } - - /* unreachable */ - httpd_client_close(client); - return false; -} - -/** - * Data has been received from the client and it is appended to the - * input buffer. - */ -static bool -httpd_client_received(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - char *line; - bool success; - - while ((line = httpd_client_read_line(client)) != NULL) { - success = httpd_client_handle_line(client, line); - g_free(line); - if (!success) { - assert(client->state != RESPONSE); - return false; - } - - if (client->state == RESPONSE) { - if (!fifo_buffer_is_empty(client->input)) { - g_warning("unexpected input from client"); - return false; - } - - fifo_buffer_free(client->input); - - return httpd_client_send_response(client); - } - } - - return true; -} - -static bool -httpd_client_read(struct httpd_client *client) -{ - char *p; - size_t max_length; - GError *error = NULL; - GIOStatus status; - gsize bytes_read; - - if (client->state == RESPONSE) { - /* the client has already sent the request, and he - must not send more */ - char buffer[1]; - - status = g_io_channel_read_chars(client->channel, buffer, - sizeof(buffer), &bytes_read, - NULL); - if (status == G_IO_STATUS_NORMAL) - g_warning("unexpected input from client"); - - return false; - } - - p = fifo_buffer_write(client->input, &max_length); - if (p == NULL) { - g_warning("buffer overflow"); - return false; - } - - status = g_io_channel_read_chars(client->channel, p, max_length, - &bytes_read, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - fifo_buffer_append(client->input, bytes_read); - return httpd_client_received(client); - - case G_IO_STATUS_AGAIN: - /* try again later, after select() */ - return true; - - case G_IO_STATUS_EOF: - /* peer disconnected */ - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - g_warning("failed to read from client: %s", - error->message); - g_error_free(error); - return false; - } - - /* unreachable */ - return false; -} - -static gboolean -httpd_client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct httpd_client *client = data; - struct httpd_output *httpd = client->httpd; - bool ret; - - g_mutex_lock(httpd->mutex); - - if (condition == G_IO_IN && httpd_client_read(client)) { - ret = true; - } else { - httpd_client_close(client); - ret = false; - } - - g_mutex_unlock(httpd->mutex); - - return ret; -} - -struct httpd_client * -httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported) -{ - struct httpd_client *client = g_new(struct httpd_client, 1); - - client->httpd = httpd; - - client->channel = g_io_channel_new_socket(fd); - - /* GLib is responsible for closing the file descriptor */ - g_io_channel_set_close_on_unref(client->channel, true); - /* NULL encoding means the stream is binary safe */ - g_io_channel_set_encoding(client->channel, NULL, NULL); - /* we prefer to do buffering */ - g_io_channel_set_buffered(client->channel, false); - - client->read_source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - httpd_client_in_event, client); - - client->input = fifo_buffer_new(4096); - client->state = REQUEST; - - client->dlna_streaming_requested = false; - client->metadata_supported = metadata_supported; - client->metadata_requested = false; - client->metadata_sent = true; - client->metaint = 8192; /*TODO: just a std value */ - client->metadata = NULL; - client->metadata_current_position = 0; - client->metadata_fill = 0; - - return client; -} - -static void -httpd_client_add_page_size(gpointer data, gpointer user_data) -{ - struct page *page = data; - size_t *size = user_data; - - *size += page->size; -} - -size_t -httpd_client_queue_size(const struct httpd_client *client) -{ - size_t size = 0; - - if (client->state != RESPONSE) - return 0; - - g_queue_foreach(client->pages, httpd_client_add_page_size, &size); - return size; -} - -void -httpd_client_cancel(struct httpd_client *client) -{ - if (client->state != RESPONSE) - return; - - g_queue_foreach(client->pages, httpd_client_unref_page, NULL); - g_queue_clear(client->pages); - - if (client->write_source_id != 0 && client->current_page == NULL) { - g_source_remove(client->write_source_id); - client->write_source_id = 0; - } -} - -static GIOStatus -write_page_to_channel(GIOChannel *channel, - const struct page *page, size_t position, - gsize *bytes_written_r, GError **error) -{ - assert(channel != NULL); - assert(page != NULL); - assert(position < page->size); - - return g_io_channel_write_chars(channel, - (const gchar*)page->data + position, - page->size - position, - bytes_written_r, error); -} - -static GIOStatus -write_n_bytes_to_channel(GIOChannel *channel, const struct page *page, - size_t position, gint n, - gsize *bytes_written_r, GError **error) -{ - GIOStatus status; - - assert(channel != NULL); - assert(page != NULL); - assert(position < page->size); - - if (n == -1) { - status = write_page_to_channel (channel, page, position, - bytes_written_r, error); - } else { - status = g_io_channel_write_chars(channel, - (const gchar*)page->data + position, - n, bytes_written_r, error); - } - - return status; -} - -static gint -bytes_left_till_metadata (struct httpd_client *client) -{ - assert(client != NULL); - - if (client->metadata_requested && - client->current_page->size - client->current_position - > client->metaint - client->metadata_fill) - return client->metaint - client->metadata_fill; - - return -1; -} - -static gboolean -httpd_client_out_event(GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, gpointer data) -{ - struct httpd_client *client = data; - struct httpd_output *httpd = client->httpd; - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - gint bytes_to_write; - - g_mutex_lock(httpd->mutex); - - assert(condition == G_IO_OUT); - assert(client->state == RESPONSE); - - if (client->write_source_id == 0) { - /* another thread has removed the event source while - this thread was waiting for httpd->mutex */ - g_mutex_unlock(httpd->mutex); - return false; - } - - if (client->current_page == NULL) { - client->current_page = g_queue_pop_head(client->pages); - client->current_position = 0; - } - - bytes_to_write = bytes_left_till_metadata(client); - - if (bytes_to_write == 0) { - gint metadata_to_write; - - metadata_to_write = client->metadata_current_position; - - if (!client->metadata_sent) { - status = write_page_to_channel(source, - client->metadata, - metadata_to_write, - &bytes_written, &error); - - client->metadata_current_position += bytes_written; - - if (client->metadata->size - - client->metadata_current_position == 0) { - client->metadata_fill = 0; - client->metadata_current_position = 0; - client->metadata_sent = true; - } - } else { - struct page *empty_meta; - guchar empty_data = 0; - - empty_meta = page_new_copy(&empty_data, 1); - - status = write_page_to_channel(source, - empty_meta, - metadata_to_write, - &bytes_written, &error); - - client->metadata_current_position += bytes_written; - - if (empty_meta->size - - client->metadata_current_position == 0) { - client->metadata_fill = 0; - client->metadata_current_position = 0; - } - } - - bytes_written = 0; - } else { - status = write_n_bytes_to_channel(source, client->current_page, - client->current_position, bytes_to_write, - &bytes_written, &error); - } - - switch (status) { - case G_IO_STATUS_NORMAL: - client->current_position += bytes_written; - assert(client->current_position <= client->current_page->size); - - if (client->metadata_requested) - client->metadata_fill += bytes_written; - - if (client->current_position >= client->current_page->size) { - page_unref(client->current_page); - client->current_page = NULL; - - if (g_queue_is_empty(client->pages)) { - /* all pages are sent: remove the - event source */ - client->write_source_id = 0; - - g_mutex_unlock(httpd->mutex); - return false; - } - } - - g_mutex_unlock(httpd->mutex); - return true; - - case G_IO_STATUS_AGAIN: - g_mutex_unlock(httpd->mutex); - return true; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - g_warning("failed to write to client: %s", error->message); - g_error_free(error); - - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; - } - - /* unreachable */ - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; -} - -void -httpd_client_send(struct httpd_client *client, struct page *page) -{ - if (client->state != RESPONSE) - /* the client is still writing the HTTP request */ - return; - - page_ref(page); - g_queue_push_tail(client->pages, page); - - if (client->write_source_id == 0) - client->write_source_id = - g_io_add_watch(client->channel, G_IO_OUT, - httpd_client_out_event, client); -} - -void -httpd_client_send_metadata(struct httpd_client *client, struct page *page) -{ - if (client->metadata) { - page_unref(client->metadata); - client->metadata = NULL; - } - - g_return_if_fail (page); - - page_ref(page); - client->metadata = page; - client->metadata_sent = false; -} diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h deleted file mode 100644 index 739163f42..000000000 --- a/src/output/httpd_client.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_HTTPD_CLIENT_H -#define MPD_OUTPUT_HTTPD_CLIENT_H - -#include <glib.h> - -#include <stdbool.h> - -struct httpd_client; -struct httpd_output; -struct page; - -/** - * Creates a new #httpd_client object - * - * @param httpd the HTTP output device - * @param fd the socket file descriptor - */ -struct httpd_client * -httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported); - -/** - * Frees memory and resources allocated by the #httpd_client object. - * This does not remove it from the #httpd_output object. - */ -void -httpd_client_free(struct httpd_client *client); - -/** - * Returns the total size of this client's page queue. - */ -size_t -httpd_client_queue_size(const struct httpd_client *client); - -/** - * Clears the page queue. - */ -void -httpd_client_cancel(struct httpd_client *client); - -/** - * Appends a page to the client's queue. - */ -void -httpd_client_send(struct httpd_client *client, struct page *page); - -/** - * Sends the passed metadata. - */ -void -httpd_client_send_metadata(struct httpd_client *client, struct page *page); - -#endif diff --git a/src/output/osx_output_plugin.c b/src/output/osx_output_plugin.c index cd51fca20..7b42589f7 100644 --- a/src/output/osx_output_plugin.c +++ b/src/output/osx_output_plugin.c @@ -20,7 +20,7 @@ #include "config.h" #include "osx_output_plugin.h" #include "output_api.h" -#include "fifo_buffer.h" +#include "util/fifo_buffer.h" #include <glib.h> #include <CoreAudio/AudioHardware.h> diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c index e267427df..457fa9f04 100644 --- a/src/output/pulse_output_plugin.c +++ b/src/output/pulse_output_plugin.c @@ -21,7 +21,7 @@ #include "pulse_output_plugin.h" #include "output_api.h" #include "mixer_list.h" -#include "mixer/pulse_mixer_plugin.h" +#include "mixer/PulseMixerPlugin.h" #include <glib.h> diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h index 02a51f27b..bcc8004a7 100644 --- a/src/output/pulse_output_plugin.h +++ b/src/output/pulse_output_plugin.h @@ -20,9 +20,9 @@ #ifndef MPD_PULSE_OUTPUT_PLUGIN_H #define MPD_PULSE_OUTPUT_PLUGIN_H -#include <stdbool.h> +#include "gerror.h" -#include <glib.h> +#include <stdbool.h> struct pulse_output; struct pulse_mixer; @@ -30,6 +30,10 @@ struct pa_cvolume; extern const struct audio_output_plugin pulse_output_plugin; +#ifdef __cplusplus +extern "C" { +#endif + void pulse_output_lock(struct pulse_output *po); @@ -46,4 +50,8 @@ bool pulse_output_set_volume(struct pulse_output *po, const struct pa_cvolume *volume, GError **error_r); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/output/shout_output_plugin.c b/src/output/shout_output_plugin.c index 56456a0ea..56d7a88b1 100644 --- a/src/output/shout_output_plugin.c +++ b/src/output/shout_output_plugin.c @@ -97,23 +97,22 @@ static void free_shout_data(struct shout_data *sd) g_free(sd); } -#define check_block_param(name) { \ - block_param = config_get_block_param(param, name); \ - if (!block_param) { \ - MPD_ERROR("no \"%s\" defined for shout device defined at line " \ - "%i\n", name, param->line); \ - } \ - } +gcc_pure +static const char * +require_block_string(const struct config_param *param, const char *name) +{ + const char *value = config_get_block_string(param, name, NULL); + if (value == NULL) + MPD_ERROR("no \"%s\" defined for shout device defined at line " \ + "%i\n", name, param->line); \ -static struct audio_output * -my_shout_init_driver(const struct config_param *param, - GError **error) + return value; +} + +static bool +my_shout_configure(struct shout_data *sd, const struct config_param *param, + GError **error) { - struct shout_data *sd = new_shout_data(); - if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) { - free_shout_data(sd); - return NULL; - } const struct audio_format *audio_format = &sd->base.config_audio_format; @@ -125,30 +124,18 @@ my_shout_init_driver(const struct config_param *param, return NULL; } - if (shout_init_count == 0) - shout_init(); - - shout_init_count++; - - const struct block_param *block_param; - check_block_param("host"); - char *host = block_param->value; - - check_block_param("mount"); - char *mount = block_param->value; + const char *host = require_block_string(param, "host"); + const char *mount = require_block_string(param, "mount"); unsigned port = config_get_block_unsigned(param, "port", 0); if (port == 0) { g_set_error(error, shout_output_quark(), 0, "shout port must be configured"); - goto failure; + return false; } - check_block_param("password"); - const char *passwd = block_param->value; - - check_block_param("name"); - const char *name = block_param->value; + const char *passwd = require_block_string(param, "password"); + const char *name = require_block_string(param, "name"); bool public = config_get_block_bool(param, "public", false); @@ -164,21 +151,21 @@ my_shout_init_driver(const struct config_param *param, "shout quality \"%s\" is not a number in the " "range -1 to 10, line %i", value, param->line); - goto failure; + return false; } if (config_get_block_string(param, "bitrate", NULL) != NULL) { g_set_error(error, shout_output_quark(), 0, "quality and bitrate are " "both defined"); - goto failure; + return false; } } else { value = config_get_block_string(param, "bitrate", NULL); if (value == NULL) { g_set_error(error, shout_output_quark(), 0, "neither bitrate nor quality defined"); - goto failure; + return false; } char *test; @@ -187,7 +174,7 @@ my_shout_init_driver(const struct config_param *param, if (*test != '\0' || sd->bitrate <= 0) { g_set_error(error, shout_output_quark(), 0, "bitrate must be a positive integer"); - goto failure; + return false; } } @@ -199,12 +186,12 @@ my_shout_init_driver(const struct config_param *param, g_set_error(error, shout_output_quark(), 0, "couldn't find shout encoder plugin \"%s\"", encoding); - goto failure; + return false; } sd->encoder = encoder_init(encoder_plugin, param, error); if (sd->encoder == NULL) - goto failure; + return false; unsigned shout_format; if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0) @@ -220,7 +207,7 @@ my_shout_init_driver(const struct config_param *param, g_set_error(error, shout_output_quark(), 0, "you cannot stream \"%s\" to shoutcast, use mp3", encoding); - goto failure; + return false; } else if (0 == strcmp(value, "shoutcast")) protocol = SHOUT_PROTOCOL_ICY; else if (0 == strcmp(value, "icecast1")) @@ -232,7 +219,7 @@ my_shout_init_driver(const struct config_param *param, "shout protocol \"%s\" is not \"shoutcast\" or " "\"icecast1\"or \"icecast2\"", value); - goto failure; + return false; } } else { protocol = SHOUT_PROTOCOL_HTTP; @@ -251,7 +238,7 @@ my_shout_init_driver(const struct config_param *param, shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } /* optional paramters */ @@ -262,21 +249,21 @@ my_shout_init_driver(const struct config_param *param, if (value != NULL && shout_set_genre(sd->shout_conn, value)) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } value = config_get_block_string(param, "description", NULL); if (value != NULL && shout_set_description(sd->shout_conn, value)) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } value = config_get_block_string(param, "url", NULL); if (value != NULL && shout_set_url(sd->shout_conn, value)) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } { @@ -301,12 +288,31 @@ my_shout_init_driver(const struct config_param *param, } } - return &sd->base; + return true; +} -failure: - ao_base_finish(&sd->base); - free_shout_data(sd); - return NULL; +static struct audio_output * +my_shout_init_driver(const struct config_param *param, + GError **error) +{ + struct shout_data *sd = new_shout_data(); + if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) { + free_shout_data(sd); + return NULL; + } + + if (!my_shout_configure(sd, param, error)) { + ao_base_finish(&sd->base); + free_shout_data(sd); + return NULL; + } + + if (shout_init_count == 0) + shout_init(); + + shout_init_count++; + + return &sd->base; } static bool diff --git a/src/output/winmm_output_plugin.c b/src/output/winmm_output_plugin.c index 4d95834b9..c1b3af126 100644 --- a/src/output/winmm_output_plugin.c +++ b/src/output/winmm_output_plugin.c @@ -26,7 +26,6 @@ #include <stdlib.h> #include <string.h> -#include <windows.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "winmm_output" diff --git a/src/output/winmm_output_plugin.h b/src/output/winmm_output_plugin.h index 0605530e1..364356483 100644 --- a/src/output/winmm_output_plugin.h +++ b/src/output/winmm_output_plugin.h @@ -25,6 +25,7 @@ #ifdef ENABLE_WINMM_OUTPUT #include <windows.h> +#include <mmsystem.h> struct winmm_output; diff --git a/src/output_internal.h b/src/output_internal.h index 9d975d789..692233f3c 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? */ @@ -250,6 +257,10 @@ audio_output_command_is_finished(const struct audio_output *ao) return ao->command == AO_COMMAND_NONE; } +#ifdef __cplusplus +extern "C" { +#endif + struct audio_output * audio_output_new(const struct config_param *param, struct player_control *pc, @@ -266,4 +277,8 @@ ao_base_finish(struct audio_output *ao); void audio_output_free(struct audio_output *ao); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/output_plugin.h b/src/output_plugin.h index 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/page.h b/src/page.h index 8a3aaf396..8629298cd 100644 --- a/src/page.h +++ b/src/page.h @@ -53,6 +53,10 @@ struct page { unsigned char data[sizeof(long)]; }; +#ifdef __cplusplus +extern "C" { +#endif + /** * Creates a new #page object, and copies data from the specified * buffer. It is initialized with a reference count of 1. @@ -91,4 +95,8 @@ page_ref(struct page *page); bool page_unref(struct page *page); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/path.c b/src/path.c index 59a91a0f7..21a4dd41e 100644 --- a/src/path.c +++ b/src/path.c @@ -21,6 +21,7 @@ #include "path.h" #include "conf.h" #include "mpd_error.h" +#include "gcc.h" #include <glib.h> @@ -60,18 +61,26 @@ utf8_to_fs_charset(const char *path_utf8) return p; } +gcc_pure +static bool +IsSupportedCharset(const char *charset) +{ + /* convert a space to check if the charset is valid */ + char *test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL); + if (test == NULL) + return false; + + g_free(test); + return true; +} + static void path_set_fs_charset(const char *charset) { - char *test; - assert(charset != NULL); - /* convert a space to ensure that the charset is valid */ - test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL); - if (test == NULL) + if (!IsSupportedCharset(charset)) MPD_ERROR("invalid filesystem charset: %s", charset); - g_free(test); g_free(fs_charset); fs_charset = g_strdup(charset); @@ -102,7 +111,7 @@ void path_global_init(void) * However this is true only if <gstdio.h> helpers are used. * MPD uses regular <stdio.h> functions. * Those functions use encoding determined by GetACP(). */ - char win_charset[13]; + static char win_charset[13]; sprintf(win_charset, "cp%u", GetACP()); charset = win_charset; #endif 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_convert.h b/src/pcm_convert.h index be11a6e41..0668a2b66 100644 --- a/src/pcm_convert.h +++ b/src/pcm_convert.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,8 @@ #include "pcm_dither.h" #include "pcm_buffer.h" +#include <glib.h> + struct audio_format; /** @@ -52,6 +54,8 @@ pcm_convert_quark(void) return g_quark_from_static_string("pcm_convert"); } +G_BEGIN_DECLS + /** * Initializes a pcm_convert_state object. */ @@ -91,4 +95,6 @@ pcm_convert(struct pcm_convert_state *state, size_t *dest_size_r, GError **error_r); +G_END_DECLS + #endif 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/playlist.c b/src/playlist.c deleted file mode 100644 index dc6d8c340..000000000 --- a/src/playlist.c +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_internal.h" -#include "playlist_save.h" -#include "player_control.h" -#include "command.h" -#include "tag.h" -#include "song.h" -#include "conf.h" -#include "stored_playlist.h" -#include "idle.h" - -#include <glib.h> - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "playlist" - -void -playlist_increment_version_all(struct playlist *playlist) -{ - queue_modify_all(&playlist->queue); - idle_add(IDLE_PLAYLIST); -} - -void -playlist_tag_changed(struct playlist *playlist) -{ - if (!playlist->playing) - return; - - assert(playlist->current >= 0); - - queue_modify(&playlist->queue, playlist->current); - idle_add(IDLE_PLAYLIST); -} - -void -playlist_init(struct playlist *playlist) -{ - queue_init(&playlist->queue, - config_get_positive(CONF_MAX_PLAYLIST_LENGTH, - DEFAULT_PLAYLIST_MAX_LENGTH)); - - playlist->queued = -1; - playlist->current = -1; -} - -void -playlist_finish(struct playlist *playlist) -{ - queue_finish(&playlist->queue); -} - -/** - * Queue a song, addressed by its order number. - */ -static void -playlist_queue_song_order(struct playlist *playlist, struct player_control *pc, - unsigned order) -{ - struct song *song; - char *uri; - - assert(queue_valid_order(&playlist->queue, order)); - - playlist->queued = order; - - song = queue_get_order(&playlist->queue, order); - uri = song_get_uri(song); - g_debug("queue song %i:\"%s\"", playlist->queued, uri); - g_free(uri); - - pc_enqueue_song(pc, song); -} - -/** - * Called if the player thread has started playing the "queued" song. - */ -static void -playlist_song_started(struct playlist *playlist, struct player_control *pc) -{ - assert(pc->next_song == NULL); - assert(playlist->queued >= -1); - - /* queued song has started: copy queued to current, - and notify the clients */ - - int current = playlist->current; - playlist->current = playlist->queued; - playlist->queued = -1; - - if(playlist->queue.consume) - playlist_delete(playlist, pc, - queue_order_to_position(&playlist->queue, - current)); - - idle_add(IDLE_PLAYER); -} - -const struct song * -playlist_get_queued_song(struct playlist *playlist) -{ - if (!playlist->playing || playlist->queued < 0) - return NULL; - - return queue_get_order(&playlist->queue, playlist->queued); -} - -void -playlist_update_queued_song(struct playlist *playlist, - struct player_control *pc, - const struct song *prev) -{ - int next_order; - const struct song *next_song; - - if (!playlist->playing) - return; - - assert(!queue_is_empty(&playlist->queue)); - assert((playlist->queued < 0) == (prev == NULL)); - - next_order = playlist->current >= 0 - ? queue_next_order(&playlist->queue, playlist->current) - : 0; - - if (next_order == 0 && playlist->queue.random && - !playlist->queue.single) { - /* shuffle the song order again, so we get a different - order each time the playlist is played - completely */ - unsigned current_position = - queue_order_to_position(&playlist->queue, - playlist->current); - - queue_shuffle_order(&playlist->queue); - - /* make sure that the playlist->current still points to - the current song, after the song order has been - shuffled */ - playlist->current = - queue_position_to_order(&playlist->queue, - current_position); - } - - if (next_order >= 0) - next_song = queue_get_order(&playlist->queue, next_order); - else - next_song = NULL; - - if (prev != NULL && next_song != prev) { - /* clear the currently queued song */ - pc_cancel(pc); - playlist->queued = -1; - } - - if (next_order >= 0) { - if (next_song != prev) - playlist_queue_song_order(playlist, pc, next_order); - else - playlist->queued = next_order; - } -} - -void -playlist_play_order(struct playlist *playlist, struct player_control *pc, - int orderNum) -{ - struct song *song; - char *uri; - - playlist->playing = true; - playlist->queued = -1; - - song = queue_get_order(&playlist->queue, orderNum); - - uri = song_get_uri(song); - g_debug("play %i:\"%s\"", orderNum, uri); - g_free(uri); - - pc_play(pc, song); - playlist->current = orderNum; -} - -static void -playlist_resume_playback(struct playlist *playlist, struct player_control *pc); - -/** - * This is the "PLAYLIST" event handler. It is invoked by the player - * thread whenever it requests a new queued song, or when it exits. - */ -void -playlist_sync(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - /* this event has reached us out of sync: we aren't - playing anymore; ignore the event */ - return; - - player_lock(pc); - enum player_state pc_state = pc_get_state(pc); - const struct song *pc_next_song = pc->next_song; - player_unlock(pc); - - if (pc_state == PLAYER_STATE_STOP) - /* the player thread has stopped: check if playback - should be restarted with the next song. That can - happen if the playlist isn't filling the queue fast - enough */ - playlist_resume_playback(playlist, pc); - else { - /* check if the player thread has already started - playing the queued song */ - if (pc_next_song == NULL && playlist->queued != -1) - playlist_song_started(playlist, pc); - - player_lock(pc); - pc_next_song = pc->next_song; - player_unlock(pc); - - /* make sure the queued song is always set (if - possible) */ - if (pc_next_song == NULL && playlist->queued < 0) - playlist_update_queued_song(playlist, pc, NULL); - } -} - -/** - * The player has stopped for some reason. Check the error, and - * decide whether to re-start playback - */ -static void -playlist_resume_playback(struct playlist *playlist, struct player_control *pc) -{ - enum player_error error; - - assert(playlist->playing); - assert(pc_get_state(pc) == PLAYER_STATE_STOP); - - error = pc_get_error(pc); - if (error == PLAYER_ERROR_NOERROR) - playlist->error_count = 0; - else - ++playlist->error_count; - - if ((playlist->stop_on_error && error != PLAYER_ERROR_NOERROR) || - error == PLAYER_ERROR_AUDIO || error == PLAYER_ERROR_SYSTEM || - playlist->error_count >= queue_length(&playlist->queue)) - /* too many errors, or critical error: stop - playback */ - playlist_stop(playlist, pc); - else - /* continue playback at the next song */ - playlist_next(playlist, pc); -} - -bool -playlist_get_repeat(const struct playlist *playlist) -{ - return playlist->queue.repeat; -} - -bool -playlist_get_random(const struct playlist *playlist) -{ - return playlist->queue.random; -} - -bool -playlist_get_single(const struct playlist *playlist) -{ - return playlist->queue.single; -} - -bool -playlist_get_consume(const struct playlist *playlist) -{ - return playlist->queue.consume; -} - -void -playlist_set_repeat(struct playlist *playlist, struct player_control *pc, - bool status) -{ - if (status == playlist->queue.repeat) - return; - - struct queue *queue = &playlist->queue; - - queue->repeat = status; - - pc_set_border_pause(pc, queue->single && !queue->repeat); - - /* if the last song is currently being played, the "next song" - might change when repeat mode is toggled */ - playlist_update_queued_song(playlist, pc, - playlist_get_queued_song(playlist)); - - idle_add(IDLE_OPTIONS); -} - -static void -playlist_order(struct playlist *playlist) -{ - if (playlist->current >= 0) - /* update playlist.current, order==position now */ - playlist->current = queue_order_to_position(&playlist->queue, - playlist->current); - - queue_restore_order(&playlist->queue); -} - -void -playlist_set_single(struct playlist *playlist, struct player_control *pc, - bool status) -{ - if (status == playlist->queue.single) - return; - - struct queue *queue = &playlist->queue; - - queue->single = status; - - pc_set_border_pause(pc, queue->single && !queue->repeat); - - /* if the last song is currently being played, the "next song" - might change when single mode is toggled */ - playlist_update_queued_song(playlist, pc, - playlist_get_queued_song(playlist)); - - idle_add(IDLE_OPTIONS); -} - -void -playlist_set_consume(struct playlist *playlist, bool status) -{ - if (status == playlist->queue.consume) - return; - - playlist->queue.consume = status; - idle_add(IDLE_OPTIONS); -} - -void -playlist_set_random(struct playlist *playlist, struct player_control *pc, - bool status) -{ - const struct song *queued; - - if (status == playlist->queue.random) - return; - - queued = playlist_get_queued_song(playlist); - - playlist->queue.random = status; - - if (playlist->queue.random) { - /* shuffle the queue order, but preserve - playlist->current */ - - int current_position = - playlist->playing && playlist->current >= 0 - ? (int)queue_order_to_position(&playlist->queue, - playlist->current) - : -1; - - queue_shuffle_order(&playlist->queue); - - if (current_position >= 0) { - /* make sure the current song is the first in - the order list, so the whole rest of the - playlist is played after that */ - unsigned current_order = - queue_position_to_order(&playlist->queue, - current_position); - queue_swap_order(&playlist->queue, 0, current_order); - playlist->current = 0; - } else - playlist->current = -1; - } else - playlist_order(playlist); - - playlist_update_queued_song(playlist, pc, queued); - - idle_add(IDLE_OPTIONS); -} - -int -playlist_get_current_song(const struct playlist *playlist) -{ - if (playlist->current >= 0) - return queue_order_to_position(&playlist->queue, - playlist->current); - - return -1; -} - -int -playlist_get_next_song(const struct playlist *playlist) -{ - if (playlist->current >= 0) - { - if (playlist->queue.single == 1 && playlist->queue.repeat == 1) - return queue_order_to_position(&playlist->queue, - playlist->current); - else if (playlist->current + 1 < (int)queue_length(&playlist->queue)) - return queue_order_to_position(&playlist->queue, - playlist->current + 1); - else if (playlist->queue.repeat == 1) - return queue_order_to_position(&playlist->queue, 0); - } - - return -1; -} - -unsigned long -playlist_get_version(const struct playlist *playlist) -{ - return playlist->queue.version; -} - -int -playlist_get_length(const struct playlist *playlist) -{ - return queue_length(&playlist->queue); -} - -unsigned -playlist_get_song_id(const struct playlist *playlist, unsigned song) -{ - return queue_position_to_id(&playlist->queue, song); -} diff --git a/src/playlist.h b/src/playlist.h deleted file mode 100644 index a21bdf24a..000000000 --- a/src/playlist.h +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_H -#define MPD_PLAYLIST_H - -#include "queue.h" -#include "playlist_error.h" - -#include <stdbool.h> - -struct player_control; - -struct playlist { - /** - * The song queue - it contains the "real" playlist. - */ - struct queue queue; - - /** - * This value is true if the player is currently playing (or - * should be playing). - */ - bool playing; - - /** - * If true, then any error is fatal; if false, MPD will - * attempt to play the next song on non-fatal errors. During - * seeking, this flag is set. - */ - bool stop_on_error; - - /** - * Number of errors since playback was started. If this - * number exceeds the length of the playlist, MPD gives up, - * because all songs have been tried. - */ - unsigned error_count; - - /** - * The "current song pointer". This is the song which is - * played when we get the "play" command. It is also the song - * which is currently being played. - */ - int current; - - /** - * The "next" song to be played, when the current one - * finishes. The decoder thread may start decoding and - * buffering it, while the "current" song is still playing. - * - * This variable is only valid if #playing is true. - */ - int queued; -}; - -/** the global playlist object */ -extern struct playlist g_playlist; - -void -playlist_global_init(void); - -void -playlist_global_finish(void); - -void -playlist_init(struct playlist *playlist); - -void -playlist_finish(struct playlist *playlist); - -void -playlist_tag_changed(struct playlist *playlist); - -/** - * Returns the "queue" object of the global playlist instance. - */ -static inline const struct queue * -playlist_get_queue(const struct playlist *playlist) -{ - return &playlist->queue; -} - -void -playlist_clear(struct playlist *playlist, struct player_control *pc); - -/** - * Appends a local file (outside the music database) to the playlist. - * - * Note: the caller is responsible for checking permissions. - */ -enum playlist_result -playlist_append_file(struct playlist *playlist, struct player_control *pc, - const char *path_fs, unsigned *added_id); - -enum playlist_result -playlist_append_uri(struct playlist *playlist, struct player_control *pc, - const char *file, unsigned *added_id); - -enum playlist_result -playlist_append_song(struct playlist *playlist, struct player_control *pc, - struct song *song, unsigned *added_id); - -enum playlist_result -playlist_delete(struct playlist *playlist, struct player_control *pc, - unsigned song); - -/** - * Deletes a range of songs from the playlist. - * - * @param start the position of the first song to delete - * @param end the position after the last song to delete - */ -enum playlist_result -playlist_delete_range(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end); - -enum playlist_result -playlist_delete_id(struct playlist *playlist, struct player_control *pc, - unsigned song); - -void -playlist_stop(struct playlist *playlist, struct player_control *pc); - -enum playlist_result -playlist_play(struct playlist *playlist, struct player_control *pc, - int song); - -enum playlist_result -playlist_play_id(struct playlist *playlist, struct player_control *pc, - int song); - -void -playlist_next(struct playlist *playlist, struct player_control *pc); - -void -playlist_sync(struct playlist *playlist, struct player_control *pc); - -void -playlist_previous(struct playlist *playlist, struct player_control *pc); - -void -playlist_shuffle(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end); - -void -playlist_delete_song(struct playlist *playlist, struct player_control *pc, - const struct song *song); - -enum playlist_result -playlist_move_range(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end, int to); - -enum playlist_result -playlist_move_id(struct playlist *playlist, struct player_control *pc, - unsigned id, int to); - -enum playlist_result -playlist_swap_songs(struct playlist *playlist, struct player_control *pc, - unsigned song1, unsigned song2); - -enum playlist_result -playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc, - unsigned id1, unsigned id2); - -enum playlist_result -playlist_set_priority(struct playlist *playlist, struct player_control *pc, - unsigned start_position, unsigned end_position, - uint8_t priority); - -enum playlist_result -playlist_set_priority_id(struct playlist *playlist, struct player_control *pc, - unsigned song_id, uint8_t priority); - -bool -playlist_get_repeat(const struct playlist *playlist); - -void -playlist_set_repeat(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_random(const struct playlist *playlist); - -void -playlist_set_random(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_single(const struct playlist *playlist); - -void -playlist_set_single(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_consume(const struct playlist *playlist); - -void -playlist_set_consume(struct playlist *playlist, bool status); - -int -playlist_get_current_song(const struct playlist *playlist); - -int -playlist_get_next_song(const struct playlist *playlist); - -unsigned -playlist_get_song_id(const struct playlist *playlist, unsigned song); - -int -playlist_get_length(const struct playlist *playlist); - -unsigned long -playlist_get_version(const struct playlist *playlist); - -enum playlist_result -playlist_seek_song(struct playlist *playlist, struct player_control *pc, - unsigned song, float seek_time); - -enum playlist_result -playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, - unsigned id, float seek_time); - -/** - * Seek within the current song. Fails if MPD is not currently - * playing. - * - * @param time the time in seconds - * @param relative if true, then the specified time is relative to the - * current position - */ -enum playlist_result -playlist_seek_current(struct playlist *playlist, struct player_control *pc, - float seek_time, bool relative); - -void -playlist_increment_version_all(struct playlist *playlist); - -#endif diff --git a/src/playlist_control.c b/src/playlist_control.c deleted file mode 100644 index 0dea7676a..000000000 --- a/src/playlist_control.c +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Functions for controlling playback on the playlist level. - * - */ - -#include "config.h" -#include "playlist_internal.h" -#include "player_control.h" -#include "idle.h" - -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "playlist" - -void -playlist_stop(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - return; - - assert(playlist->current >= 0); - - g_debug("stop"); - pc_stop(pc); - playlist->queued = -1; - playlist->playing = false; - - if (playlist->queue.random) { - /* shuffle the playlist, so the next playback will - result in a new random order */ - - unsigned current_position = - queue_order_to_position(&playlist->queue, - playlist->current); - - queue_shuffle_order(&playlist->queue); - - /* make sure that "current" stays valid, and the next - "play" command plays the same song again */ - playlist->current = - queue_position_to_order(&playlist->queue, - current_position); - } -} - -enum playlist_result -playlist_play(struct playlist *playlist, struct player_control *pc, - int song) -{ - unsigned i = song; - - pc_clear_error(pc); - - if (song == -1) { - /* play any song ("current" song, or the first song */ - - if (queue_is_empty(&playlist->queue)) - return PLAYLIST_RESULT_SUCCESS; - - if (playlist->playing) { - /* already playing: unpause playback, just in - case it was paused, and return */ - pc_set_pause(pc, false); - return PLAYLIST_RESULT_SUCCESS; - } - - /* select a song: "current" song, or the first one */ - i = playlist->current >= 0 - ? playlist->current - : 0; - } else if (!queue_valid_position(&playlist->queue, song)) - return PLAYLIST_RESULT_BAD_RANGE; - - if (playlist->queue.random) { - if (song >= 0) - /* "i" is currently the song position (which - would be equal to the order number in - no-random mode); convert it to a order - number, because random mode is enabled */ - i = queue_position_to_order(&playlist->queue, song); - - if (!playlist->playing) - playlist->current = 0; - - /* swap the new song with the previous "current" one, - so playback continues as planned */ - queue_swap_order(&playlist->queue, - i, playlist->current); - i = playlist->current; - } - - playlist->stop_on_error = false; - playlist->error_count = 0; - - playlist_play_order(playlist, pc, i); - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_play_id(struct playlist *playlist, struct player_control *pc, - int id) -{ - int song; - - if (id == -1) { - return playlist_play(playlist, pc, id); - } - - song = queue_id_to_position(&playlist->queue, id); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_play(playlist, pc, song); -} - -void -playlist_next(struct playlist *playlist, struct player_control *pc) -{ - int next_order; - int current; - - if (!playlist->playing) - return; - - assert(!queue_is_empty(&playlist->queue)); - assert(queue_valid_order(&playlist->queue, playlist->current)); - - current = playlist->current; - playlist->stop_on_error = false; - - /* determine the next song from the queue's order list */ - - next_order = queue_next_order(&playlist->queue, playlist->current); - if (next_order < 0) { - /* no song after this one: stop playback */ - playlist_stop(playlist, pc); - - /* reset "current song" */ - playlist->current = -1; - } - else - { - if (next_order == 0 && playlist->queue.random) { - /* The queue told us that the next song is the first - song. This means we are in repeat mode. Shuffle - the queue order, so this time, the user hears the - songs in a different than before */ - assert(playlist->queue.repeat); - - queue_shuffle_order(&playlist->queue); - - /* note that playlist->current and playlist->queued are - now invalid, but playlist_play_order() will - discard them anyway */ - } - - playlist_play_order(playlist, pc, next_order); - } - - /* Consume mode removes each played songs. */ - if(playlist->queue.consume) - playlist_delete(playlist, pc, - queue_order_to_position(&playlist->queue, - current)); -} - -void -playlist_previous(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - return; - - assert(queue_length(&playlist->queue) > 0); - - if (playlist->current > 0) { - /* play the preceding song */ - playlist_play_order(playlist, pc, - playlist->current - 1); - } else if (playlist->queue.repeat) { - /* play the last song in "repeat" mode */ - playlist_play_order(playlist, pc, - queue_length(&playlist->queue) - 1); - } else { - /* re-start playing the current song if it's - the first one */ - playlist_play_order(playlist, pc, playlist->current); - } -} - -enum playlist_result -playlist_seek_song(struct playlist *playlist, struct player_control *pc, - unsigned song, float seek_time) -{ - const struct song *queued; - unsigned i; - bool success; - - if (!queue_valid_position(&playlist->queue, song)) - return PLAYLIST_RESULT_BAD_RANGE; - - queued = playlist_get_queued_song(playlist); - - if (playlist->queue.random) - i = queue_position_to_order(&playlist->queue, song); - else - i = song; - - pc_clear_error(pc); - playlist->stop_on_error = true; - playlist->error_count = 0; - - if (!playlist->playing || (unsigned)playlist->current != i) { - /* seeking is not within the current song - prepare - song change */ - - playlist->playing = true; - playlist->current = i; - - queued = NULL; - } - - success = pc_seek(pc, queue_get_order(&playlist->queue, i), seek_time); - if (!success) { - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_NOT_PLAYING; - } - - playlist->queued = -1; - playlist_update_queued_song(playlist, pc, NULL); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, - unsigned id, float seek_time) -{ - int song = queue_id_to_position(&playlist->queue, id); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_seek_song(playlist, pc, song, seek_time); -} - -enum playlist_result -playlist_seek_current(struct playlist *playlist, struct player_control *pc, - float seek_time, bool relative) -{ - if (!playlist->playing) - return PLAYLIST_RESULT_NOT_PLAYING; - - if (relative) { - struct player_status status; - pc_get_status(pc, &status); - - if (status.state != PLAYER_STATE_PLAY && - status.state != PLAYER_STATE_PAUSE) - return PLAYLIST_RESULT_NOT_PLAYING; - - seek_time += (int)status.elapsed_time; - } - - if (seek_time < 0) - seek_time = 0; - - return playlist_seek_song(playlist, pc, playlist->current, seek_time); -} diff --git a/src/playlist_edit.c b/src/playlist_edit.c deleted file mode 100644 index d10f49451..000000000 --- a/src/playlist_edit.c +++ /dev/null @@ -1,487 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Functions for editing the playlist (adding, removing, reordering - * songs in the queue). - * - */ - -#include "config.h" -#include "playlist_internal.h" -#include "player_control.h" -#include "database.h" -#include "uri.h" -#include "song.h" -#include "idle.h" - -#include <stdlib.h> - -static void playlist_increment_version(struct playlist *playlist) -{ - queue_increment_version(&playlist->queue); - - idle_add(IDLE_PLAYLIST); -} - -void -playlist_clear(struct playlist *playlist, struct player_control *pc) -{ - playlist_stop(playlist, pc); - - /* make sure there are no references to allocated songs - anymore */ - for (unsigned i = 0; i < queue_length(&playlist->queue); i++) { - const struct song *song = queue_get(&playlist->queue, i); - if (!song_in_database(song)) - pc_song_deleted(pc, song); - } - - queue_clear(&playlist->queue); - - playlist->current = -1; - - playlist_increment_version(playlist); -} - -enum playlist_result -playlist_append_file(struct playlist *playlist, struct player_control *pc, - const char *path_fs, unsigned *added_id) -{ - struct song *song = song_file_load(path_fs, NULL); - if (song == NULL) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_append_song(playlist, pc, song, added_id); -} - -enum playlist_result -playlist_append_song(struct playlist *playlist, struct player_control *pc, - struct song *song, unsigned *added_id) -{ - const struct song *queued; - unsigned id; - - if (queue_is_full(&playlist->queue)) - return PLAYLIST_RESULT_TOO_LARGE; - - queued = playlist_get_queued_song(playlist); - - id = queue_append(&playlist->queue, song, 0); - - if (playlist->queue.random) { - /* shuffle the new song into the list of remaining - songs to play */ - - unsigned start; - if (playlist->queued >= 0) - start = playlist->queued + 1; - else - start = playlist->current + 1; - if (start < queue_length(&playlist->queue)) - queue_shuffle_order_last(&playlist->queue, start, - queue_length(&playlist->queue)); - } - - playlist_increment_version(playlist); - - playlist_update_queued_song(playlist, pc, queued); - - if (added_id) - *added_id = id; - - return PLAYLIST_RESULT_SUCCESS; -} - -static struct song * -song_by_uri(const char *uri) -{ - struct song *song; - - song = db_get_song(uri); - if (song != NULL) - return song; - - if (uri_has_scheme(uri)) - return song_remote_new(uri); - - return NULL; -} - -enum playlist_result -playlist_append_uri(struct playlist *playlist, struct player_control *pc, - const char *uri, unsigned *added_id) -{ - struct song *song; - - g_debug("add to playlist: %s", uri); - - song = song_by_uri(uri); - if (song == NULL) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_append_song(playlist, pc, song, added_id); -} - -enum playlist_result -playlist_swap_songs(struct playlist *playlist, struct player_control *pc, - unsigned song1, unsigned song2) -{ - const struct song *queued; - - if (!queue_valid_position(&playlist->queue, song1) || - !queue_valid_position(&playlist->queue, song2)) - return PLAYLIST_RESULT_BAD_RANGE; - - queued = playlist_get_queued_song(playlist); - - queue_swap(&playlist->queue, song1, song2); - - if (playlist->queue.random) { - /* update the queue order, so that playlist->current - still points to the current song order */ - - queue_swap_order(&playlist->queue, - queue_position_to_order(&playlist->queue, - song1), - queue_position_to_order(&playlist->queue, - song2)); - } else { - /* correct the "current" song order */ - - if (playlist->current == (int)song1) - playlist->current = song2; - else if (playlist->current == (int)song2) - playlist->current = song1; - } - - playlist_increment_version(playlist); - - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc, - unsigned id1, unsigned id2) -{ - int song1 = queue_id_to_position(&playlist->queue, id1); - int song2 = queue_id_to_position(&playlist->queue, id2); - - if (song1 < 0 || song2 < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_swap_songs(playlist, pc, song1, song2); -} - -enum playlist_result -playlist_set_priority(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end, - uint8_t priority) -{ - if (start >= queue_length(&playlist->queue)) - return PLAYLIST_RESULT_BAD_RANGE; - - if (end > queue_length(&playlist->queue)) - end = queue_length(&playlist->queue); - - if (start >= end) - return PLAYLIST_RESULT_SUCCESS; - - /* remember "current" and "queued" */ - - int current_position = playlist->current >= 0 - ? (int)queue_order_to_position(&playlist->queue, - playlist->current) - : -1; - - const struct song *queued = playlist_get_queued_song(playlist); - - /* apply the priority changes */ - - queue_set_priority_range(&playlist->queue, start, end, priority, - playlist->current); - - playlist_increment_version(playlist); - - /* restore "current" and choose a new "queued" */ - - if (current_position >= 0) - playlist->current = queue_position_to_order(&playlist->queue, - current_position); - - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_set_priority_id(struct playlist *playlist, struct player_control *pc, - unsigned song_id, uint8_t priority) -{ - int song_position = queue_id_to_position(&playlist->queue, song_id); - if (song_position < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_set_priority(playlist, pc, - song_position, song_position + 1, - priority); - -} - -static void -playlist_delete_internal(struct playlist *playlist, struct player_control *pc, - unsigned song, const struct song **queued_p) -{ - unsigned songOrder; - - assert(song < queue_length(&playlist->queue)); - - songOrder = queue_position_to_order(&playlist->queue, song); - - if (playlist->playing && playlist->current == (int)songOrder) { - bool paused = pc_get_state(pc) == PLAYER_STATE_PAUSE; - - /* the current song is going to be deleted: stop the player */ - - pc_stop(pc); - playlist->playing = false; - - /* see which song is going to be played instead */ - - playlist->current = queue_next_order(&playlist->queue, - playlist->current); - if (playlist->current == (int)songOrder) - playlist->current = -1; - - if (playlist->current >= 0 && !paused) - /* play the song after the deleted one */ - playlist_play_order(playlist, pc, playlist->current); - else - /* no songs left to play, stop playback - completely */ - playlist_stop(playlist, pc); - - *queued_p = NULL; - } else if (playlist->current == (int)songOrder) - /* there's a "current song" but we're not playing - currently - clear "current" */ - playlist->current = -1; - - /* now do it: remove the song */ - - if (!song_in_database(queue_get(&playlist->queue, song))) - pc_song_deleted(pc, queue_get(&playlist->queue, song)); - - queue_delete(&playlist->queue, song); - - /* update the "current" and "queued" variables */ - - if (playlist->current > (int)songOrder) { - playlist->current--; - } -} - -enum playlist_result -playlist_delete(struct playlist *playlist, struct player_control *pc, - unsigned song) -{ - const struct song *queued; - - if (song >= queue_length(&playlist->queue)) - return PLAYLIST_RESULT_BAD_RANGE; - - queued = playlist_get_queued_song(playlist); - - playlist_delete_internal(playlist, pc, song, &queued); - - playlist_increment_version(playlist); - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_delete_range(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end) -{ - const struct song *queued; - - if (start >= queue_length(&playlist->queue)) - return PLAYLIST_RESULT_BAD_RANGE; - - if (end > queue_length(&playlist->queue)) - end = queue_length(&playlist->queue); - - if (start >= end) - return PLAYLIST_RESULT_SUCCESS; - - queued = playlist_get_queued_song(playlist); - - do { - playlist_delete_internal(playlist, pc, --end, &queued); - } while (end != start); - - playlist_increment_version(playlist); - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_delete_id(struct playlist *playlist, struct player_control *pc, - unsigned id) -{ - int song = queue_id_to_position(&playlist->queue, id); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_delete(playlist, pc, song); -} - -void -playlist_delete_song(struct playlist *playlist, struct player_control *pc, - const struct song *song) -{ - for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i) - if (song == queue_get(&playlist->queue, i)) - playlist_delete(playlist, pc, i); - - pc_song_deleted(pc, song); -} - -enum playlist_result -playlist_move_range(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end, int to) -{ - const struct song *queued; - int currentSong; - - if (!queue_valid_position(&playlist->queue, start) || - !queue_valid_position(&playlist->queue, end - 1)) - return PLAYLIST_RESULT_BAD_RANGE; - - if ((to >= 0 && to + end - start - 1 >= queue_length(&playlist->queue)) || - (to < 0 && abs(to) > (int)queue_length(&playlist->queue))) - return PLAYLIST_RESULT_BAD_RANGE; - - if ((int)start == to) - /* nothing happens */ - return PLAYLIST_RESULT_SUCCESS; - - queued = playlist_get_queued_song(playlist); - - /* - * (to < 0) => move to offset from current song - * (-playlist.length == to) => move to position BEFORE current song - */ - currentSong = playlist->current >= 0 - ? (int)queue_order_to_position(&playlist->queue, - playlist->current) - : -1; - if (to < 0 && playlist->current >= 0) { - if (start <= (unsigned)currentSong && (unsigned)currentSong < end) - /* no-op, can't be moved to offset of itself */ - return PLAYLIST_RESULT_SUCCESS; - to = (currentSong + abs(to)) % queue_length(&playlist->queue); - if (start < (unsigned)to) - to--; - } - - queue_move_range(&playlist->queue, start, end, to); - - if (!playlist->queue.random) { - /* update current/queued */ - if ((int)start <= playlist->current && - (unsigned)playlist->current < end) - playlist->current += to - start; - else if (playlist->current >= (int)end && - playlist->current <= to) { - playlist->current -= end - start; - } else if (playlist->current >= to && - playlist->current < (int)start) { - playlist->current += end - start; - } - } - - playlist_increment_version(playlist); - - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_move_id(struct playlist *playlist, struct player_control *pc, - unsigned id1, int to) -{ - int song = queue_id_to_position(&playlist->queue, id1); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_move_range(playlist, pc, song, song+1, to); -} - -void -playlist_shuffle(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end) -{ - const struct song *queued; - - if (end > queue_length(&playlist->queue)) - /* correct the "end" offset */ - end = queue_length(&playlist->queue); - - if ((start+1) >= end) - /* needs at least two entries. */ - return; - - queued = playlist_get_queued_song(playlist); - if (playlist->playing && playlist->current >= 0) { - unsigned current_position; - current_position = queue_order_to_position(&playlist->queue, - playlist->current); - - if (current_position >= start && current_position < end) - { - /* put current playing song first */ - queue_swap(&playlist->queue, start, current_position); - - if (playlist->queue.random) { - playlist->current = - queue_position_to_order(&playlist->queue, start); - } else - playlist->current = start; - - /* start shuffle after the current song */ - start++; - } - } else { - /* no playback currently: reset playlist->current */ - - playlist->current = -1; - } - - queue_shuffle_range(&playlist->queue, start, end); - - playlist_increment_version(playlist); - - playlist_update_queued_song(playlist, pc, queued); -} diff --git a/src/playlist_internal.h b/src/playlist_internal.h deleted file mode 100644 index 81b175176..000000000 --- a/src/playlist_internal.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Internal header for the components of the playlist code. - * - */ - -#ifndef PLAYLIST_INTERNAL_H -#define PLAYLIST_INTERNAL_H - -#include "playlist.h" - -struct player_control; - -/** - * Returns the song object which is currently queued. Returns none if - * there is none (yet?) or if MPD isn't playing. - */ -const struct song * -playlist_get_queued_song(struct playlist *playlist); - -/** - * Updates the "queued song". Calculates the next song according to - * the current one (if MPD isn't playing, it takes the first song), - * and queues this song. Clears the old queued song if there was one. - * - * @param prev the song which was previously queued, as determined by - * playlist_get_queued_song() - */ -void -playlist_update_queued_song(struct playlist *playlist, - struct player_control *pc, - const struct song *prev); - -void -playlist_play_order(struct playlist *playlist, struct player_control *pc, - int orderNum); - -#endif diff --git a/src/playlist_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 deleted file mode 100644 index 4fe564a35..000000000 --- a/src/queue.c +++ /dev/null @@ -1,603 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "queue.h" -#include "song.h" - -#include <stdlib.h> - -/** - * Generate a non-existing id number. - */ -static unsigned -queue_generate_id(const struct queue *queue) -{ - static unsigned cur = (unsigned)-1; - - do { - cur++; - - if (cur >= queue->max_length * QUEUE_HASH_MULT) - cur = 0; - } while (queue->id_to_position[cur] != -1); - - return cur; -} - -int -queue_next_order(const struct queue *queue, unsigned order) -{ - assert(order < queue->length); - - if (queue->single && queue->repeat && !queue->consume) - return order; - else if (order + 1 < queue->length) - return order + 1; - else if (queue->repeat && (order > 0 || !queue->consume)) - /* restart at first song */ - return 0; - else - /* end of queue */ - return -1; -} - -void -queue_increment_version(struct queue *queue) -{ - static unsigned long max = ((uint32_t) 1 << 31) - 1; - - queue->version++; - - if (queue->version >= max) { - for (unsigned i = 0; i < queue->length; i++) - queue->items[i].version = 0; - - queue->version = 1; - } -} - -void -queue_modify(struct queue *queue, unsigned order) -{ - unsigned position; - - assert(order < queue->length); - - position = queue->order[order]; - queue->items[position].version = queue->version; - - queue_increment_version(queue); -} - -void -queue_modify_all(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; i++) - queue->items[i].version = queue->version; - - queue_increment_version(queue); -} - -unsigned -queue_append(struct queue *queue, struct song *song, uint8_t priority) -{ - unsigned id = queue_generate_id(queue); - - assert(!queue_is_full(queue)); - - queue->items[queue->length] = (struct queue_item){ - .song = song, - .id = id, - .version = queue->version, - .priority = priority, - }; - - queue->order[queue->length] = queue->length; - queue->id_to_position[id] = queue->length; - - ++queue->length; - - return id; -} - -void -queue_swap(struct queue *queue, unsigned position1, unsigned position2) -{ - struct queue_item tmp; - unsigned id1 = queue->items[position1].id; - unsigned id2 = queue->items[position2].id; - - tmp = queue->items[position1]; - queue->items[position1] = queue->items[position2]; - queue->items[position2] = tmp; - - queue->items[position1].version = queue->version; - queue->items[position2].version = queue->version; - - queue->id_to_position[id1] = position2; - queue->id_to_position[id2] = position1; -} - -static void -queue_move_song_to(struct queue *queue, unsigned from, unsigned to) -{ - unsigned from_id = queue->items[from].id; - - queue->items[to] = queue->items[from]; - queue->items[to].version = queue->version; - queue->id_to_position[from_id] = to; -} - -void -queue_move(struct queue *queue, unsigned from, unsigned to) -{ - struct queue_item item = queue->items[from]; - - /* move songs to one less in from->to */ - - for (unsigned i = from; i < to; i++) - queue_move_song_to(queue, i + 1, i); - - /* move songs to one more in to->from */ - - for (unsigned i = from; i > to; i--) - queue_move_song_to(queue, i - 1, i); - - /* put song at _to_ */ - - queue->id_to_position[item.id] = to; - queue->items[to] = item; - queue->items[to].version = queue->version; - - /* now deal with order */ - - if (queue->random) { - for (unsigned i = 0; i < queue->length; i++) { - if (queue->order[i] > from && queue->order[i] <= to) - queue->order[i]--; - else if (queue->order[i] < from && - queue->order[i] >= to) - queue->order[i]++; - else if (from == queue->order[i]) - queue->order[i] = to; - } - } -} - -void -queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to) -{ - struct queue_item items[end - start]; - // Copy the original block [start,end-1] - for (unsigned i = start; i < end; i++) - items[i - start] = queue->items[i]; - - // If to > start, we need to move to-start items to start, starting from end - for (unsigned i = end; i < end + to - start; i++) - queue_move_song_to(queue, i, start + i - end); - - // If to < start, we need to move start-to items to newend (= end + to - start), starting from to - // This is the same as moving items from start-1 to to (decreasing), with start-1 going to end-1 - // We have to iterate in this order to avoid writing over something we haven't yet moved - for (unsigned i = start - 1; i >= to && i != G_MAXUINT; i--) - queue_move_song_to(queue, i, i + end - start); - - // Copy the original block back in, starting at to. - for (unsigned i = start; i< end; i++) - { - queue->id_to_position[items[i-start].id] = to + i - start; - queue->items[to + i - start] = items[i-start]; - queue->items[to + i - start].version = queue->version; - } - - if (queue->random) { - // Update the positions in the queue. - // Note that the ranges for these cases are the same as the ranges of - // the loops above. - for (unsigned i = 0; i < queue->length; i++) { - if (queue->order[i] >= end && queue->order[i] < to + end - start) - queue->order[i] -= end - start; - else if (queue->order[i] < start && - queue->order[i] >= to) - queue->order[i] += end - start; - else if (start <= queue->order[i] && queue->order[i] < end) - queue->order[i] += to - start; - } - } -} - -/** - * Moves a song to a new position in the "order" list. - */ -static void -queue_move_order(struct queue *queue, unsigned from_order, unsigned to_order) -{ - assert(queue != NULL); - assert(from_order < queue->length); - assert(to_order <= queue->length); - - const unsigned from_position = - queue_order_to_position(queue, from_order); - - if (from_order < to_order) { - for (unsigned i = from_order; i < to_order; ++i) - queue->order[i] = queue->order[i + 1]; - } else { - for (unsigned i = from_order; i > to_order; --i) - queue->order[i] = queue->order[i - 1]; - } - - queue->order[to_order] = from_position; -} - -void -queue_delete(struct queue *queue, unsigned position) -{ - struct song *song; - unsigned id, order; - - assert(position < queue->length); - - song = queue_get(queue, position); - if (!song_in_database(song)) - song_free(song); - - id = queue_position_to_id(queue, position); - order = queue_position_to_order(queue, position); - - --queue->length; - - /* release the song id */ - - queue->id_to_position[id] = -1; - - /* delete song from songs array */ - - for (unsigned i = position; i < queue->length; i++) - queue_move_song_to(queue, i + 1, i); - - /* delete the entry from the order array */ - - for (unsigned i = order; i < queue->length; i++) - queue->order[i] = queue->order[i + 1]; - - /* readjust values in the order array */ - - for (unsigned i = 0; i < queue->length; i++) - if (queue->order[i] > position) - --queue->order[i]; -} - -void -queue_clear(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; i++) { - struct queue_item *item = &queue->items[i]; - - if (!song_in_database(item->song)) - song_free(item->song); - - queue->id_to_position[item->id] = -1; - } - - queue->length = 0; -} - -void -queue_init(struct queue *queue, unsigned max_length) -{ - queue->max_length = max_length; - queue->length = 0; - queue->version = 1; - queue->repeat = false; - queue->random = false; - queue->single = false; - queue->consume = false; - - queue->items = g_new(struct queue_item, max_length); - queue->order = g_malloc(sizeof(queue->order[0]) * - max_length); - queue->id_to_position = g_malloc(sizeof(queue->id_to_position[0]) * - max_length * QUEUE_HASH_MULT); - - for (unsigned i = 0; i < max_length * QUEUE_HASH_MULT; ++i) - queue->id_to_position[i] = -1; - - queue->rand = g_rand_new(); -} - -void -queue_finish(struct queue *queue) -{ - queue_clear(queue); - - g_free(queue->items); - g_free(queue->order); - g_free(queue->id_to_position); - - g_rand_free(queue->rand); -} - -static const struct queue_item * -queue_get_order_item_const(const struct queue *queue, unsigned order) -{ - assert(queue != NULL); - assert(order < queue->length); - - return &queue->items[queue->order[order]]; -} - -static uint8_t -queue_get_order_priority(const struct queue *queue, unsigned order) -{ - return queue_get_order_item_const(queue, order)->priority; -} - -static gint -queue_item_compare_order_priority(gconstpointer av, gconstpointer bv, - gpointer user_data) -{ - const struct queue *queue = user_data; - const unsigned *const ap = av; - const unsigned *const bp = bv; - assert(ap >= queue->order && ap < queue->order + queue->length); - assert(bp >= queue->order && bp < queue->order + queue->length); - uint8_t a = queue->items[*ap].priority; - uint8_t b = queue->items[*bp].priority; - - if (G_LIKELY(a == b)) - return 0; - else if (a > b) - return -1; - else - return 1; -} - -static void -queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - g_qsort_with_data(&queue->order[start], end - start, - sizeof(queue->order[0]), - queue_item_compare_order_priority, - queue); -} - -/** - * Shuffle the order of items in the specified range, ignoring their - * priorities. - */ -static void -queue_shuffle_order_range(struct queue *queue, unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - for (unsigned i = start; i < end; ++i) - queue_swap_order(queue, i, - g_rand_int_range(queue->rand, i, end)); -} - -/** - * Sort the "order" of items by priority, and then shuffle each - * priority group. - */ -void -queue_shuffle_order_range_with_priority(struct queue *queue, - unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - if (start == end) - return; - - /* first group the range by priority */ - queue_sort_order_by_priority(queue, start, end); - - /* now shuffle each priority group */ - unsigned group_start = start; - uint8_t group_priority = queue_get_order_priority(queue, start); - - for (unsigned i = start + 1; i < end; ++i) { - uint8_t priority = queue_get_order_priority(queue, i); - assert(priority <= group_priority); - - if (priority != group_priority) { - /* start of a new group - shuffle the one that - has just ended */ - queue_shuffle_order_range(queue, group_start, i); - group_start = i; - group_priority = priority; - } - } - - /* shuffle the last group */ - queue_shuffle_order_range(queue, group_start, end); -} - -void -queue_shuffle_order(struct queue *queue) -{ - queue_shuffle_order_range_with_priority(queue, 0, queue->length); -} - -static void -queue_shuffle_order_first(struct queue *queue, unsigned start, unsigned end) -{ - queue_swap_order(queue, start, - g_rand_int_range(queue->rand, start, end)); -} - -void -queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end) -{ - queue_swap_order(queue, end - 1, - g_rand_int_range(queue->rand, start, end)); -} - -void -queue_shuffle_range(struct queue *queue, unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue->length); - - for (unsigned i = start; i < end; i++) { - unsigned ri = g_rand_int_range(queue->rand, i, end); - queue_swap(queue, i, ri); - } -} - -/** - * Find the first item that has this specified priority or higher. - */ -G_GNUC_PURE -static unsigned -queue_find_priority_order(const struct queue *queue, unsigned start_order, - uint8_t priority, unsigned exclude_order) -{ - assert(queue != NULL); - assert(queue->random); - assert(start_order <= queue->length); - - for (unsigned order = start_order; order < queue->length; ++order) { - const unsigned position = queue_order_to_position(queue, order); - const struct queue_item *item = &queue->items[position]; - if (item->priority <= priority && order != exclude_order) - return order; - } - - return queue->length; -} - -G_GNUC_PURE -static unsigned -queue_count_same_priority(const struct queue *queue, unsigned start_order, - uint8_t priority) -{ - assert(queue != NULL); - assert(queue->random); - assert(start_order <= queue->length); - - for (unsigned order = start_order; order < queue->length; ++order) { - const unsigned position = queue_order_to_position(queue, order); - const struct queue_item *item = &queue->items[position]; - if (item->priority != priority) - return order - start_order; - } - - return queue->length - start_order; -} - -bool -queue_set_priority(struct queue *queue, unsigned position, uint8_t priority, - int after_order) -{ - assert(queue != NULL); - assert(position < queue->length); - - struct queue_item *item = &queue->items[position]; - uint8_t old_priority = item->priority; - if (old_priority == priority) - return false; - - item->version = queue->version; - item->priority = priority; - - if (!queue->random) - /* don't reorder if not in random mode */ - return true; - - unsigned order = queue_position_to_order(queue, position); - if (after_order >= 0) { - if (order == (unsigned)after_order) - /* don't reorder the current song */ - return true; - - if (order < (unsigned)after_order) { - /* the specified song has been played already - - enqueue it only if its priority has just - become bigger than the current one's */ - - const unsigned after_position = - queue_order_to_position(queue, after_order); - const struct queue_item *after_item = - &queue->items[after_position]; - if (old_priority > after_item->priority || - priority <= after_item->priority) - /* priority hasn't become bigger */ - return true; - } - } - - /* move the item to the beginning of the priority group (or - create a new priority group) */ - - const unsigned before_order = - queue_find_priority_order(queue, after_order + 1, priority, - order); - const unsigned new_order = before_order > order - ? before_order - 1 - : before_order; - queue_move_order(queue, order, new_order); - - /* shuffle the song within that priority group */ - - const unsigned priority_count = - queue_count_same_priority(queue, new_order, priority); - assert(priority_count >= 1); - queue_shuffle_order_first(queue, new_order, - new_order + priority_count); - - return true; -} - -bool -queue_set_priority_range(struct queue *queue, - unsigned start_position, unsigned end_position, - uint8_t priority, int after_order) -{ - assert(queue != NULL); - assert(start_position <= end_position); - assert(end_position <= queue->length); - - bool modified = false; - int after_position = after_order >= 0 - ? (int)queue_order_to_position(queue, after_order) - : -1; - for (unsigned i = start_position; i < end_position; ++i) { - after_order = after_position >= 0 - ? (int)queue_position_to_order(queue, after_position) - : -1; - - modified |= queue_set_priority(queue, i, priority, - after_order); - } - - return modified; -} diff --git a/src/queue.h b/src/queue.h deleted file mode 100644 index e4bfcdffa..000000000 --- a/src/queue.h +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef QUEUE_H -#define QUEUE_H - -#include <glib.h> - -#include <assert.h> -#include <stdbool.h> -#include <stdint.h> - -enum { - /** - * reserve max_length * QUEUE_HASH_MULT elements in the id - * number space - */ - QUEUE_HASH_MULT = 4, -}; - -/** - * One element of the queue: basically a song plus some queue specific - * information attached. - */ -struct queue_item { - struct song *song; - - /** the unique id of this item in the queue */ - unsigned id; - - /** when was this item last changed? */ - uint32_t version; - - /** - * The priority of this item, between 0 and 255. High - * priority value means that this song gets played first in - * "random" mode. - */ - uint8_t priority; -}; - -/** - * A queue of songs. This is the backend of the playlist: it contains - * an ordered list of songs. - * - * Songs can be addressed in three possible ways: - * - * - the position in the queue - * - the unique id (which stays the same, regardless of moves) - * - the order number (which only differs from "position" in random mode) - */ -struct queue { - /** configured maximum length of the queue */ - unsigned max_length; - - /** number of songs in the queue */ - unsigned length; - - /** the current version number */ - uint32_t version; - - /** all songs in "position" order */ - struct queue_item *items; - - /** map order numbers to positions */ - unsigned *order; - - /** map song ids to positions */ - int *id_to_position; - - /** repeat playback when the end of the queue has been - reached? */ - bool repeat; - - /** play only current song. */ - bool single; - - /** remove each played files. */ - bool consume; - - /** play back songs in random order? */ - bool random; - - /** random number generator for shuffle and random mode */ - GRand *rand; -}; - -static inline unsigned -queue_length(const struct queue *queue) -{ - assert(queue->length <= queue->max_length); - - return queue->length; -} - -/** - * Determine if the queue is empty, i.e. there are no songs. - */ -static inline bool -queue_is_empty(const struct queue *queue) -{ - return queue->length == 0; -} - -/** - * Determine if the maximum number of songs has been reached. - */ -static inline bool -queue_is_full(const struct queue *queue) -{ - assert(queue->length <= queue->max_length); - - return queue->length >= queue->max_length; -} - -/** - * Is that a valid position number? - */ -static inline bool -queue_valid_position(const struct queue *queue, unsigned position) -{ - return position < queue->length; -} - -/** - * Is that a valid order number? - */ -static inline bool -queue_valid_order(const struct queue *queue, unsigned order) -{ - return order < queue->length; -} - -static inline int -queue_id_to_position(const struct queue *queue, unsigned id) -{ - if (id >= queue->max_length * QUEUE_HASH_MULT) - return -1; - - assert(queue->id_to_position[id] >= -1); - assert(queue->id_to_position[id] < (int)queue->length); - - return queue->id_to_position[id]; -} - -static inline int -queue_position_to_id(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].id; -} - -static inline unsigned -queue_order_to_position(const struct queue *queue, unsigned order) -{ - assert(order < queue->length); - - return queue->order[order]; -} - -static inline unsigned -queue_position_to_order(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - for (unsigned i = 0;; ++i) { - assert(i < queue->length); - - if (queue->order[i] == position) - return i; - } -} - -G_GNUC_PURE -static inline uint8_t -queue_get_priority_at_position(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].priority; -} - -/** - * Returns the song at the specified position. - */ -static inline struct song * -queue_get(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].song; -} - -/** - * Returns the song at the specified order number. - */ -static inline struct song * -queue_get_order(const struct queue *queue, unsigned order) -{ - return queue_get(queue, queue_order_to_position(queue, order)); -} - -/** - * Is the song at the specified position newer than the specified - * version? - */ -static inline bool -queue_song_newer(const struct queue *queue, unsigned position, - uint32_t version) -{ - assert(position < queue->length); - - return version > queue->version || - queue->items[position].version >= version || - queue->items[position].version == 0; -} - -/** - * Initialize a queue object. - */ -void -queue_init(struct queue *queue, unsigned max_length); - -/** - * Deinitializes a queue object. It does not free the queue pointer - * itself. - */ -void -queue_finish(struct queue *queue); - -/** - * Returns the order number following the specified one. This takes - * end of queue and "repeat" mode into account. - * - * @return the next order number, or -1 to stop playback - */ -int -queue_next_order(const struct queue *queue, unsigned order); - -/** - * Increments the queue's version number. This handles integer - * overflow well. - */ -void -queue_increment_version(struct queue *queue); - -/** - * Marks the specified song as "modified" and increments the version - * number. - */ -void -queue_modify(struct queue *queue, unsigned order); - -/** - * Marks all songs as "modified" and increments the version number. - */ -void -queue_modify_all(struct queue *queue); - -/** - * Appends a song to the queue and returns its position. Prior to - * that, the caller must check if the queue is already full. - * - * If a song is not in the database (determined by - * song_in_database()), it is freed when removed from the queue. - * - * @param priority the priority of this new queue item - */ -unsigned -queue_append(struct queue *queue, struct song *song, uint8_t priority); - -/** - * Swaps two songs, addressed by their position. - */ -void -queue_swap(struct queue *queue, unsigned position1, unsigned position2); - -/** - * Swaps two songs, addressed by their order number. - */ -static inline void -queue_swap_order(struct queue *queue, unsigned order1, unsigned order2) -{ - unsigned tmp = queue->order[order1]; - queue->order[order1] = queue->order[order2]; - queue->order[order2] = tmp; -} - -/** - * Moves a song to a new position. - */ -void -queue_move(struct queue *queue, unsigned from, unsigned to); - -/** - * Moves a range of songs to a new position. - */ -void -queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to); - -/** - * Removes a song from the playlist. - */ -void -queue_delete(struct queue *queue, unsigned position); - -/** - * Removes all songs from the playlist. - */ -void -queue_clear(struct queue *queue); - -/** - * Initializes the "order" array, and restores "normal" order. - */ -static inline void -queue_restore_order(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; ++i) - queue->order[i] = i; -} - -/** - * Shuffle the order of items in the specified range, taking their - * priorities into account. - */ -void -queue_shuffle_order_range_with_priority(struct queue *queue, - unsigned start, unsigned end); - -/** - * Shuffles the virtual order of songs, but does not move them - * physically. This is used in random mode. - */ -void -queue_shuffle_order(struct queue *queue); - -/** - * Shuffles the virtual order of the last song in the specified - * (order) range. This is used in random mode after a song has been - * appended by queue_append(). - */ -void -queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end); - -/** - * Shuffles a (position) range in the queue. The songs are physically - * shuffled, not by using the "order" mapping. - */ -void -queue_shuffle_range(struct queue *queue, unsigned start, unsigned end); - -bool -queue_set_priority(struct queue *queue, unsigned position, - uint8_t priority, int after_order); - -bool -queue_set_priority_range(struct queue *queue, - unsigned start_position, unsigned end_position, - uint8_t priority, int after_order); - -#endif diff --git a/src/queue_print.c b/src/queue_print.c deleted file mode 100644 index d149e8b6f..000000000 --- a/src/queue_print.c +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "queue_print.h" -#include "queue.h" -#include "song.h" -#include "song_print.h" -#include "locate.h" -#include "client.h" -#include "mapper.h" - -/** - * Send detailed information about a range of songs in the queue to a - * client. - * - * @param client the client which has requested information - * @param start the index of the first song (including) - * @param end the index of the last song (excluding) - */ -static void -queue_print_song_info(struct client *client, const struct queue *queue, - unsigned position) -{ - song_print_info(client, queue_get(queue, position)); - client_printf(client, "Pos: %u\nId: %u\n", - position, queue_position_to_id(queue, position)); - - uint8_t priority = queue_get_priority_at_position(queue, position); - if (priority != 0) - client_printf(client, "Prio: %u\n", priority); -} - -void -queue_print_info(struct client *client, const struct queue *queue, - unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue_length(queue)); - - for (unsigned i = start; i < end; ++i) - queue_print_song_info(client, queue, i); -} - -void -queue_print_uris(struct client *client, const struct queue *queue, - unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue_length(queue)); - - for (unsigned i = start; i < end; ++i) { - client_printf(client, "%i:", i); - song_print_uri(client, queue_get(queue, i)); - } -} - -void -queue_print_changes_info(struct client *client, const struct queue *queue, - uint32_t version) -{ - for (unsigned i = 0; i < queue_length(queue); i++) { - if (queue_song_newer(queue, i, version)) - queue_print_song_info(client, queue, i); - } -} - -void -queue_print_changes_position(struct client *client, const struct queue *queue, - uint32_t version) -{ - for (unsigned i = 0; i < queue_length(queue); i++) - if (queue_song_newer(queue, i, version)) - client_printf(client, "cpos: %i\nId: %i\n", - i, queue_position_to_id(queue, i)); -} - -void -queue_search(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria) -{ - unsigned i; - struct locate_item_list *new_list = - locate_item_list_casefold(criteria); - - for (i = 0; i < queue_length(queue); i++) { - const struct song *song = queue_get(queue, i); - - if (locate_song_search(song, new_list)) - queue_print_song_info(client, queue, i); - } - - locate_item_list_free(new_list); -} - -void -queue_find(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria) -{ - for (unsigned i = 0; i < queue_length(queue); i++) { - const struct song *song = queue_get(queue, i); - - if (locate_song_match(song, criteria)) - queue_print_song_info(client, queue, i); - } -} diff --git a/src/replay_gain_config.h b/src/replay_gain_config.h index 18747cef2..4b59334c0 100644 --- a/src/replay_gain_config.h +++ b/src/replay_gain_config.h @@ -30,6 +30,10 @@ extern float replay_gain_preamp; extern float replay_gain_missing_preamp; extern bool replay_gain_limit; +#ifdef __cplusplus +extern "C" { +#endif + void replay_gain_global_init(void); /** @@ -50,6 +54,10 @@ replay_gain_set_mode_string(const char *p); * Returns the "real" mode according to the "auto" setting" */ enum replay_gain_mode -replay_gain_get_real_mode(void); +replay_gain_get_real_mode(bool random_mode); + +#ifdef __cplusplus +} +#endif #endif diff --git a/src/replay_gain_info.h b/src/replay_gain_info.h index 9097c3e02..b06dc6cf0 100644 --- a/src/replay_gain_info.h +++ b/src/replay_gain_info.h @@ -22,8 +22,12 @@ #include "check.h" +#ifdef __cplusplus +#include <cmath> +#else #include <stdbool.h> #include <math.h> +#endif enum replay_gain_mode { REPLAY_GAIN_AUTO = -2, @@ -58,9 +62,17 @@ replay_gain_info_init(struct replay_gain_info *info) static inline bool replay_gain_tuple_defined(const struct replay_gain_tuple *tuple) { +#ifdef __cplusplus + return !std::isinf(tuple->gain); +#else return !isinf(tuple->gain); +#endif } +#ifdef __cplusplus +extern "C" { +#endif + float replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, float missing_preamp, bool peak_limit); @@ -71,4 +83,8 @@ replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, flo void replay_gain_info_complete(struct replay_gain_info *info); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/resolver.h b/src/resolver.h index e5ad06754..af14f5f23 100644 --- a/src/resolver.h +++ b/src/resolver.h @@ -20,18 +20,24 @@ #ifndef MPD_RESOLVER_H #define MPD_RESOLVER_H +#include "gcc.h" + #include <glib.h> struct sockaddr; struct addrinfo; -G_GNUC_CONST +gcc_const static inline GQuark resolver_quark(void) { return g_quark_from_static_string("resolver"); } +#ifdef __cplusplus +extern "C" { +#endif + /** * Converts the specified socket address into a string in the form * "IP:PORT". The return value must be freed with g_free() when you @@ -42,7 +48,7 @@ resolver_quark(void) * @param error location to store the error occurring, or NULL to * ignore errors */ -G_GNUC_MALLOC +gcc_malloc char * sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error); @@ -61,4 +67,8 @@ resolve_host_port(const char *host_port, unsigned default_port, int flags, int socktype, GError **error_r); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/song.h b/src/song.h index 8b97d45d0..db582beea 100644 --- a/src/song.h +++ b/src/song.h @@ -21,7 +21,9 @@ #define MPD_SONG_H #include "util/list.h" +#include "gcc.h" +#include <assert.h> #include <stddef.h> #include <stdbool.h> #include <sys/time.h> @@ -41,7 +43,7 @@ struct song { struct list_head siblings; struct tag *tag; - struct directory *parent; + struct Directory *parent; time_t mtime; /** @@ -58,13 +60,23 @@ struct song { char uri[sizeof(int)]; }; +/** + * A dummy #directory instance that is used for "detached" song + * copies. + */ +extern struct Directory detached_root; + +#ifdef __cplusplus +extern "C" { +#endif + /** allocate a new song with a remote URL */ struct song * song_remote_new(const char *uri); /** allocate a new song with a local file name */ struct song * -song_file_new(const char *path, struct directory *parent); +song_file_new(const char *path, struct Directory *parent); /** * allocate a new song structure with a local file name and attempt to @@ -72,7 +84,7 @@ song_file_new(const char *path, struct directory *parent); * data, NULL is returned. */ struct song * -song_file_load(const char *path, struct directory *parent); +song_file_load(const char *path, struct Directory *parent); /** * Replaces the URI of a song object. The given song object is @@ -83,9 +95,52 @@ song_file_load(const char *path, struct directory *parent); struct song * song_replace_uri(struct song *song, const char *uri); +/** + * Creates a "detached" song object. + */ +struct song * +song_detached_new(const char *uri); + +/** + * Creates a duplicate of the song object. If the object is in the + * database, it creates a "detached" copy of this song, see + * song_is_detached(). + */ +gcc_malloc +struct song * +song_dup_detached(const struct song *src); + void song_free(struct song *song); +static inline bool +song_in_database(const struct song *song) +{ + return song->parent != NULL; +} + +static inline bool +song_is_file(const struct song *song) +{ + return song_in_database(song) || song->uri[0] == '/'; +} + +static inline bool +song_is_detached(const struct song *song) +{ + assert(song != NULL); + assert(song_in_database(song)); + + return song->parent == &detached_root; +} + +/** + * Returns true if both objects refer to the same physical song. + */ +gcc_pure +bool +song_equals(const struct song *a, const struct song *b); + bool song_file_update(struct song *song); @@ -105,16 +160,8 @@ song_get_uri(const struct song *song); double song_get_duration(const struct song *song); -static inline bool -song_in_database(const struct song *song) -{ - return song->parent != NULL; -} - -static inline bool -song_is_file(const struct song *song) -{ - return song_in_database(song) || song->uri[0] == '/'; +#ifdef __cplusplus } +#endif #endif diff --git a/src/state_file.c b/src/state_file.c deleted file mode 100644 index de0e70538..000000000 --- a/src/state_file.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "state_file.h" -#include "output_state.h" -#include "playlist.h" -#include "playlist_state.h" -#include "volume.h" -#include "text_file.h" - -#include <glib.h> -#include <assert.h> -#include <string.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "state_file" - -static char *state_file_path; - -/** the GLib source id for the save timer */ -static guint save_state_source_id; - -/** - * These version numbers determine whether we need to save the state - * file. If nothing has changed, we won't let the hard drive spin up. - */ -static unsigned prev_volume_version, prev_output_version, - prev_playlist_version; - -static void -state_file_write(struct player_control *pc) -{ - FILE *fp; - - assert(state_file_path != NULL); - - g_debug("Saving state file %s", state_file_path); - - fp = fopen(state_file_path, "w"); - if (G_UNLIKELY(!fp)) { - g_warning("failed to create %s: %s", - state_file_path, g_strerror(errno)); - return; - } - - save_sw_volume_state(fp); - audio_output_state_save(fp); - playlist_state_save(fp, &g_playlist, pc); - - fclose(fp); - - prev_volume_version = sw_volume_state_get_hash(); - prev_output_version = audio_output_state_get_version(); - prev_playlist_version = playlist_state_get_hash(&g_playlist, pc); -} - -static void -state_file_read(struct player_control *pc) -{ - FILE *fp; - bool success; - - assert(state_file_path != NULL); - - g_debug("Loading state file %s", state_file_path); - - fp = fopen(state_file_path, "r"); - if (G_UNLIKELY(!fp)) { - g_warning("failed to open %s: %s", - state_file_path, g_strerror(errno)); - return; - } - - GString *buffer = g_string_sized_new(1024); - const char *line; - while ((line = read_text_line(fp, buffer)) != NULL) { - success = read_sw_volume_state(line) || - audio_output_state_read(line) || - playlist_state_restore(line, fp, buffer, - &g_playlist, pc); - if (!success) - g_warning("Unrecognized line in state file: %s", line); - } - - fclose(fp); - - prev_volume_version = sw_volume_state_get_hash(); - prev_output_version = audio_output_state_get_version(); - prev_playlist_version = playlist_state_get_hash(&g_playlist, pc); - - - g_string_free(buffer, true); -} - -/** - * This function is called every 5 minutes by the GLib main loop, and - * saves the state file. - */ -static gboolean -timer_save_state_file(gpointer data) -{ - struct player_control *pc = data; - - if (prev_volume_version == sw_volume_state_get_hash() && - prev_output_version == audio_output_state_get_version() && - prev_playlist_version == playlist_state_get_hash(&g_playlist, pc)) - /* nothing has changed - don't save the state file, - don't spin up the hard disk */ - return true; - - state_file_write(pc); - return true; -} - -void -state_file_init(const char *path, struct player_control *pc) -{ - assert(state_file_path == NULL); - - if (path == NULL) - return; - - state_file_path = g_strdup(path); - state_file_read(pc); - - save_state_source_id = g_timeout_add_seconds(5 * 60, - timer_save_state_file, - pc); -} - -void -state_file_finish(struct player_control *pc) -{ - if (state_file_path == NULL) - /* no state file configured, no cleanup required */ - return; - - if (save_state_source_id != 0) - g_source_remove(save_state_source_id); - - state_file_write(pc); - - g_free(state_file_path); -} diff --git a/src/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.c b/src/string_util.c index 6e5429076..b76b257ba 100644 --- a/src/string_util.c +++ b/src/string_util.c @@ -20,6 +20,8 @@ #include "config.h" #include "string_util.h" +#include <stdlib.h> /* for malloc() */ +#include <string.h> /* for strnlen() */ #include <glib.h> #include <assert.h> @@ -45,3 +47,22 @@ string_array_contains(const char *const* haystack, const char *needle) return false; } + +#if !defined(HAVE_STRNDUP) + +char * +strndup(const char *str, size_t n) +{ + assert(str != NULL); + + size_t len = strnlen(str, n); + char* ret = (char *) malloc(len + 1); + if (ret == NULL) + return NULL; + + memcpy(ret, str, len); + ret[len] = '\0'; + return ret; +} + +#endif diff --git a/src/string_util.h b/src/string_util.h index dc80a46ef..374fd0f91 100644 --- a/src/string_util.h +++ b/src/string_util.h @@ -20,18 +20,26 @@ #ifndef MPD_STRING_UTIL_H #define MPD_STRING_UTIL_H -#include <glib.h> +#include "gcc.h" #include <stdbool.h> +#include <stdlib.h> /* for size_t */ + +#ifdef __cplusplus +extern "C" { +#endif /** * Remove the "const" attribute from a string pointer. This is a * dirty hack, don't use it unless you know what you're doing! */ -G_GNUC_CONST +gcc_const static inline char * deconst_string(const char *p) { +#ifdef __cplusplus + return const_cast<char *>(p); +#else union { const char *in; char *out; @@ -40,6 +48,7 @@ deconst_string(const char *p) }; return u.out; +#endif } /** @@ -49,14 +58,14 @@ deconst_string(const char *p) * This is a faster version of g_strchug(), because it does not move * data. */ -G_GNUC_PURE +gcc_pure const char * strchug_fast_c(const char *p); /** * Same as strchug_fast_c(), but works with a writable pointer. */ -G_GNUC_PURE +gcc_pure static inline char * strchug_fast(char *p) { @@ -74,4 +83,25 @@ strchug_fast(char *p) bool string_array_contains(const char *const* haystack, const char *needle); +#if !defined(HAVE_STRNDUP) + +/** + * Duplicates the string to a newly allocated buffer + * copying at most n characters. + * + * @param str a string to duplicate + * @param n maximal number of characters to copy + * @return a pointer to the duplicated string, + * or NULL if memory allocation failed. + */ +gcc_malloc +char * +strndup(const char *str, size_t n); + +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif diff --git a/src/strset.c b/src/strset.c deleted file mode 100644 index 5862e4075..000000000 --- a/src/strset.c +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "strset.h" - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -#define NUM_SLOTS 16384 - -struct strset_slot { - struct strset_slot *next; - const char *value; -}; - -struct strset { - unsigned size; - - struct strset_slot *current_slot; - unsigned next_slot; - - struct strset_slot slots[NUM_SLOTS]; -}; - -static unsigned calc_hash(const char *p) { - unsigned hash = 5381; - - assert(p != NULL); - - while (*p != 0) - hash = (hash << 5) + hash + *p++; - - return hash; -} - -G_GNUC_MALLOC struct strset *strset_new(void) -{ - struct strset *set = g_new0(struct strset, 1); - return set; -} - -void strset_free(struct strset *set) -{ - unsigned i; - - for (i = 0; i < NUM_SLOTS; ++i) { - struct strset_slot *slot = set->slots[i].next, *next; - - while (slot != NULL) { - next = slot->next; - g_free(slot); - slot = next; - } - } - - g_free(set); -} - -void strset_add(struct strset *set, const char *value) -{ - struct strset_slot *base_slot - = &set->slots[calc_hash(value) % NUM_SLOTS]; - struct strset_slot *slot = base_slot; - - if (base_slot->value == NULL) { - /* empty slot - put into base_slot */ - assert(base_slot->next == NULL); - - base_slot->value = value; - ++set->size; - return; - } - - for (slot = base_slot; slot != NULL; slot = slot->next) - if (strcmp(slot->value, value) == 0) - /* found it - do nothing */ - return; - - /* insert it into the slot chain */ - slot = g_new(struct strset_slot, 1); - slot->next = base_slot->next; - slot->value = value; - base_slot->next = slot; - ++set->size; -} - -int strset_get(const struct strset *set, const char *value) -{ - const struct strset_slot *slot = &set->slots[calc_hash(value)]; - - if (slot->value == NULL) - return 0; - - for (slot = slot->next; slot != NULL; slot = slot->next) - if (strcmp(slot->value, value) == 0) - /* found it - do nothing */ - return 1; - - return 0; -} - -unsigned strset_size(const struct strset *set) -{ - return set->size; -} - -void strset_rewind(struct strset *set) -{ - set->current_slot = NULL; - set->next_slot = 0; -} - -const char *strset_next(struct strset *set) -{ - if (set->current_slot != NULL && set->current_slot->next != NULL) { - set->current_slot = set->current_slot->next; - return set->current_slot->value; - } - - while (set->next_slot < NUM_SLOTS && - set->slots[set->next_slot].value == NULL) - ++set->next_slot; - - if (set->next_slot >= NUM_SLOTS) - return NULL; - - set->current_slot = &set->slots[set->next_slot++]; - return set->current_slot->value; -} - diff --git a/src/strset.h b/src/strset.h deleted file mode 100644 index 5382e59b8..000000000 --- a/src/strset.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * "struct strset" is a hashed string set: you can add strings to this - * library, and it stores them as a set of unique strings. You can - * get the size of the set, and you can enumerate through all values. - * - * It is important to note that the strset does not copy the string - * values - it stores the exact pointers it was given in strset_add(). - */ - -#ifndef MPD_STRSET_H -#define MPD_STRSET_H - -#include <glib.h> - -struct strset; - -G_GNUC_MALLOC struct strset *strset_new(void); - -void strset_free(struct strset *set); - -void strset_add(struct strset *set, const char *value); - -int strset_get(const struct strset *set, const char *value); - -unsigned strset_size(const struct strset *set); - -void strset_rewind(struct strset *set); - -const char *strset_next(struct strset *set); - -#endif @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -101,6 +101,10 @@ struct tag { unsigned num_items; }; +#ifdef __cplusplus +extern "C" { +#endif + /** * Parse the string, and convert it into a #tag_type. Returns * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized. @@ -236,4 +240,8 @@ bool tag_has_type(const struct tag *tag, enum tag_type type); */ bool tag_equal(const struct tag *tag1, const struct tag *tag2); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/tag_id3.c b/src/tag_id3.c index 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/text_file.h b/src/text_file.h deleted file mode 100644 index 9dd810943..000000000 --- a/src/text_file.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TEXT_FILE_H -#define MPD_TEXT_FILE_H - -#include <glib.h> - -#include <stdio.h> - -/** - * Reads a line from the input file, and strips trailing space. There - * is a reasonable maximum line length, only to prevent denial of - * service. - * - * @param file the source file, opened in text mode - * @param buffer an allocator for the buffer - * @return a pointer to the line, or NULL on end-of-file or error - */ -char * -read_text_line(FILE *file, GString *buffer); - -#endif diff --git a/src/text_input_stream.c b/src/text_input_stream.c index 4a858fc85..4a2eeb817 100644 --- a/src/text_input_stream.c +++ b/src/text_input_stream.c @@ -20,7 +20,7 @@ #include "config.h" #include "text_input_stream.h" #include "input_stream.h" -#include "fifo_buffer.h" +#include "util/fifo_buffer.h" #include <glib.h> diff --git a/src/thread/Cond.hxx b/src/thread/Cond.hxx new file mode 100644 index 000000000..bbaabddc2 --- /dev/null +++ b/src/thread/Cond.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_THREAD_COND_HXX +#define MPD_THREAD_COND_HXX + +#ifdef WIN32 + +/* mingw-w64 4.6.3 lacks a std::cond implementation */ + +#include "WindowsCond.hxx" +typedef WindowsCond Cond; + +#else + +#include "PosixCond.hxx" +typedef PosixCond Cond; + +#endif + +#endif diff --git a/src/thread/CriticalSection.hxx b/src/thread/CriticalSection.hxx new file mode 100644 index 000000000..8bc05b8f5 --- /dev/null +++ b/src/thread/CriticalSection.hxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009-2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_CRITICAL_SECTION_HXX +#define MPD_THREAD_CRITICAL_SECTION_HXX + +#include <windows.h> + +/** + * Wrapper for a CRITICAL_SECTION, backend for the Mutex class. + */ +class CriticalSection { + friend class WindowsCond; + + CRITICAL_SECTION critical_section; + +public: + CriticalSection() { + ::InitializeCriticalSection(&critical_section); + } + + ~CriticalSection() { + ::DeleteCriticalSection(&critical_section); + } + + CriticalSection(const CriticalSection &other) = delete; + CriticalSection &operator=(const CriticalSection &other) = delete; + + void lock() { + ::EnterCriticalSection(&critical_section); + }; + + bool try_lock() { + return ::TryEnterCriticalSection(&critical_section) != 0; + }; + + void unlock() { + ::LeaveCriticalSection(&critical_section); + } +}; + +#endif diff --git a/src/thread/GLibCond.hxx b/src/thread/GLibCond.hxx new file mode 100644 index 000000000..9ab08e9fd --- /dev/null +++ b/src/thread/GLibCond.hxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_GLIB_COND_HXX +#define MPD_THREAD_GLIB_COND_HXX + +#include "GLibMutex.hxx" + +/** + * A wrapper for GCond. + */ +class GLibCond { +#if GLIB_CHECK_VERSION(2,32,0) + GCond cond; +#else + GCond *cond; +#endif + +public: + GLibCond() { +#if GLIB_CHECK_VERSION(2,32,0) + g_cond_init(&cond); +#else + cond = g_cond_new(); +#endif + } + + ~GLibCond() { +#if GLIB_CHECK_VERSION(2,32,0) + g_cond_clear(&cond); +#else + g_cond_free(cond); +#endif + } + + GLibCond(const GLibCond &other) = delete; + GLibCond &operator=(const GLibCond &other) = delete; + +private: + GCond *GetNative() { +#if GLIB_CHECK_VERSION(2,32,0) + return &cond; +#else + return cond; +#endif + } + +public: + void signal() { + g_cond_signal(GetNative()); + } + + void broadcast() { + g_cond_broadcast(GetNative()); + } + + void wait(GLibMutex &mutex) { + g_cond_wait(GetNative(), mutex.GetNative()); + } +}; + +#endif diff --git a/src/thread/GLibMutex.hxx b/src/thread/GLibMutex.hxx new file mode 100644 index 000000000..2c666c1ea --- /dev/null +++ b/src/thread/GLibMutex.hxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_GLIB_MUTEX_HXX +#define MPD_THREAD_GLIB_MUTEX_HXX + +#include <glib.h> + +/** + * A wrapper for GMutex. + */ +class GLibMutex { + friend class GLibCond; + +#if GLIB_CHECK_VERSION(2,32,0) + GMutex mutex; +#else + GMutex *mutex; +#endif + +public: + GLibMutex() { +#if GLIB_CHECK_VERSION(2,32,0) + g_mutex_init(&mutex); +#else + mutex = g_mutex_new(); +#endif + } + + ~GLibMutex() { +#if GLIB_CHECK_VERSION(2,32,0) + g_mutex_clear(&mutex); +#else + g_mutex_free(mutex); +#endif + } + + GLibMutex(const GLibMutex &other) = delete; + GLibMutex &operator=(const GLibMutex &other) = delete; + +private: + GMutex *GetNative() { +#if GLIB_CHECK_VERSION(2,32,0) + return &mutex; +#else + return mutex; +#endif + } + +public: + void lock() { + g_mutex_lock(GetNative()); + } + + bool try_lock() { + return g_mutex_trylock(GetNative()); + } + + void unlock() { + g_mutex_lock(GetNative()); + } +}; + +#endif diff --git a/src/thread/Mutex.hxx b/src/thread/Mutex.hxx new file mode 100644 index 000000000..675af74b1 --- /dev/null +++ b/src/thread/Mutex.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_THREAD_MUTEX_HXX +#define MPD_THREAD_MUTEX_HXX + +#ifdef WIN32 + +/* mingw-w64 4.6.3 lacks a std::mutex implementation */ + +#include "CriticalSection.hxx" +typedef CriticalSection Mutex; + +#else + +#include "PosixMutex.hxx" + +typedef PosixMutex Mutex; + +#endif + +class ScopeLock { + Mutex &mutex; + +public: + ScopeLock(Mutex &_mutex):mutex(_mutex) { + mutex.lock(); + }; + + ~ScopeLock() { + mutex.unlock(); + }; + + ScopeLock(const ScopeLock &other) = delete; + ScopeLock &operator=(const ScopeLock &other) = delete; +}; + +#endif diff --git a/src/thread/PosixCond.hxx b/src/thread/PosixCond.hxx new file mode 100644 index 000000000..acdc05edc --- /dev/null +++ b/src/thread/PosixCond.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2009-2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_POSIX_COND_HXX +#define MPD_THREAD_POSIX_COND_HXX + +#include "PosixMutex.hxx" + +/** + * Low-level wrapper for a pthread_cond_t. + */ +class PosixCond { + pthread_cond_t cond; + +public: + constexpr PosixCond():cond(PTHREAD_COND_INITIALIZER) {} + + PosixCond(const PosixCond &other) = delete; + PosixCond &operator=(const PosixCond &other) = delete; + + void signal() { + pthread_cond_signal(&cond); + } + + void broadcast() { + pthread_cond_broadcast(&cond); + } + + void wait(PosixMutex &mutex) { + pthread_cond_wait(&cond, &mutex.mutex); + } +}; + +#endif diff --git a/src/thread/PosixMutex.hxx b/src/thread/PosixMutex.hxx new file mode 100644 index 000000000..d50764af4 --- /dev/null +++ b/src/thread/PosixMutex.hxx @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2009-2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_POSIX_MUTEX_HXX +#define MPD_THREAD_POSIX_MUTEX_HXX + +#include <pthread.h> + +/** + * Low-level wrapper for a pthread_mutex_t. + */ +class PosixMutex { + friend class PosixCond; + + pthread_mutex_t mutex; + +public: + constexpr PosixMutex():mutex(PTHREAD_MUTEX_INITIALIZER) {} + + PosixMutex(const PosixMutex &other) = delete; + PosixMutex &operator=(const PosixMutex &other) = delete; + + void lock() { + pthread_mutex_lock(&mutex); + } + + bool try_lock() { + return pthread_mutex_trylock(&mutex) == 0; + } + + void unlock() { + pthread_mutex_unlock(&mutex); + } +}; + +#endif diff --git a/src/thread/WindowsCond.hxx b/src/thread/WindowsCond.hxx new file mode 100644 index 000000000..f4e909c72 --- /dev/null +++ b/src/thread/WindowsCond.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009-2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_THREAD_WINDOWS_COND_HXX +#define MPD_THREAD_WINDOWS_COND_HXX + +#include "CriticalSection.hxx" + +/** + * Wrapper for a CONDITION_VARIABLE, backend for the Cond class. + */ +class WindowsCond { + CONDITION_VARIABLE cond; + +public: + WindowsCond() { + InitializeConditionVariable(&cond); + } + + WindowsCond(const WindowsCond &other) = delete; + WindowsCond &operator=(const WindowsCond &other) = delete; + + void signal() { + WakeConditionVariable(&cond); + } + + void broadcast() { + WakeAllConditionVariable(&cond); + } + + void wait(CriticalSection &mutex) { + SleepConditionVariableCS(&cond, &mutex.critical_section, + INFINITE); + } +}; + +#endif diff --git a/src/timer.h b/src/timer.h index 184881249..1506c9173 100644 --- a/src/timer.h +++ b/src/timer.h @@ -30,6 +30,10 @@ struct timer { int rate; }; +#ifdef __cplusplus +extern "C" { +#endif + struct timer *timer_new(const struct audio_format *af); void timer_free(struct timer *timer); @@ -48,4 +52,8 @@ timer_delay(const struct timer *timer); void timer_sync(struct timer *timer); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/tokenizer.c b/src/tokenizer.c index bbb34e100..4a98e882f 100644 --- a/src/tokenizer.c +++ b/src/tokenizer.c @@ -21,6 +21,8 @@ #include "tokenizer.h" #include "string_util.h" +#include <glib.h> + #include <stdbool.h> #include <assert.h> #include <string.h> diff --git a/src/tokenizer.h b/src/tokenizer.h index d55eb3ca6..2026e5ad6 100644 --- a/src/tokenizer.h +++ b/src/tokenizer.h @@ -20,7 +20,7 @@ #ifndef MPD_TOKENIZER_H #define MPD_TOKENIZER_H -#include <glib.h> +#include "gerror.h" /** * Reads the next word from the input string. This function modifies @@ -20,7 +20,7 @@ #ifndef MPD_URI_H #define MPD_URI_H -#include <glib.h> +#include "gcc.h" #include <stdbool.h> @@ -28,10 +28,10 @@ * Checks whether the specified URI has a scheme in the form * "scheme://". */ -G_GNUC_PURE +gcc_pure bool uri_has_scheme(const char *uri); -G_GNUC_PURE +gcc_pure const char * uri_get_suffix(const char *uri); @@ -43,7 +43,7 @@ uri_get_suffix(const char *uri); * - no double slashes * - no path component begins with a dot */ -G_GNUC_PURE +gcc_pure bool uri_safe_local(const char *uri); @@ -53,7 +53,7 @@ uri_safe_local(const char *uri); * NULL if nothing needs to be removed, or if the URI is not * recognized. */ -G_GNUC_MALLOC +gcc_malloc char * uri_remove_auth(const char *uri); diff --git a/src/util/HugeAllocator.cxx b/src/util/HugeAllocator.cxx new file mode 100644 index 000000000..d1c55c965 --- /dev/null +++ b/src/util/HugeAllocator.cxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "HugeAllocator.hxx" + +#ifdef __linux__ +#include <sys/mman.h> +#include <unistd.h> +#else +#include <stdlib.h> +#endif + +#ifdef __linux__ + +/** + * Round up the parameter, make it page-aligned. + */ +gcc_const +static size_t +AlignToPageSize(size_t size) +{ + static const long page_size = sysconf(_SC_PAGESIZE); + if (page_size > 0) + return size; + + size_t ps(page_size); + return (size + ps - 1) / ps * ps; +} + +void * +HugeAllocate(size_t size) +{ + size = AlignToPageSize(size); + + constexpr int flags = MAP_ANONYMOUS|MAP_PRIVATE|MAP_NORESERVE; + void *p = mmap(nullptr, size, + PROT_READ|PROT_WRITE, flags, + -1, 0); + if (p == (void *)-1) + return nullptr; + +#ifdef MADV_HUGEPAGE + /* allow the Linux kernel to use "Huge Pages", which reduces page + table overhead for this big chunk of data */ + madvise(p, size, MADV_HUGEPAGE); +#endif + +#ifdef MADV_DONTFORK + /* just in case MPD needs to fork, don't copy this allocation + to the child process, to reduce overhead */ + madvise(p, size, MADV_DONTFORK); +#endif + + return p; +} + +void +HugeFree(void *p, size_t size) +{ + munmap(p, AlignToPageSize(size)); +} + +void +HugeDiscard(void *p, size_t size) +{ +#ifdef MADV_DONTNEED + madvise(p, AlignToPageSize(size), MADV_DONTNEED); +#endif +} + +#endif diff --git a/src/util/HugeAllocator.hxx b/src/util/HugeAllocator.hxx new file mode 100644 index 000000000..01c92cd43 --- /dev/null +++ b/src/util/HugeAllocator.hxx @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_HUGE_ALLOCATOR_HXX +#define MPD_HUGE_ALLOCATOR_HXX + +#include "gcc.h" + +#include <stddef.h> + +#ifdef __linux__ + +/** + * Allocate a huge amount of memory. This will be done in a way that + * allows giving the memory back to the kernel as soon as we don't + * need it anymore. On the downside, this call is expensive. + */ +gcc_malloc +void * +HugeAllocate(size_t size); + +/** + * @param p an allocation returned by HugeAllocate() + * @param size the allocation's size as passed to HugeAllocate() + */ +void +HugeFree(void *p, size_t size); + +/** + * Discard any data stored in the allocation and give the memory back + * to the kernel. After returning, the allocation still exists and + * can be reused at any time, but its contents are undefined. + * + * @param p an allocation returned by HugeAllocate() + * @param size the allocation's size as passed to HugeAllocate() + */ +void +HugeDiscard(void *p, size_t size); + +#else + +/* not Linux: fall back to standard C calls */ + +#include <stdlib.h> + +gcc_malloc +static inline void * +HugeAllocate(size_t size) +{ + return malloc(size); +} + +static inline void +HugeFree(void *p, size_t) +{ + free(p); +} + +static inline void +HugeDiscard(void *, size_t) +{ +} + +#endif + +#endif diff --git a/src/inotify_queue.h b/src/util/LazyRandomEngine.cxx index cfc28ebfe..0f90ebb2e 100644 --- a/src/inotify_queue.h +++ b/src/util/LazyRandomEngine.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,16 +17,15 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_INOTIFY_QUEUE_H -#define MPD_INOTIFY_QUEUE_H +#include "config.h" +#include "LazyRandomEngine.hxx" void -mpd_inotify_queue_init(void); +LazyRandomEngine::AutoCreate() +{ + if (engine != nullptr) + return; -void -mpd_inotify_queue_finish(void); - -void -mpd_inotify_enqueue(char *uri_utf8); - -#endif + std::random_device rd; + engine = new std::mt19937(rd()); +} diff --git a/src/util/LazyRandomEngine.hxx b/src/util/LazyRandomEngine.hxx new file mode 100644 index 000000000..8afe1d1c0 --- /dev/null +++ b/src/util/LazyRandomEngine.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_LAZY_RANDOM_ENGINE_HXX +#define MPD_LAZY_RANDOM_ENGINE_HXX + +#include "check.h" + +#include <random> + +#include <assert.h> + +/** + * A random engine that will be created and seeded on demand. + */ +class LazyRandomEngine { + std::mt19937 *engine; + +public: + typedef std::mt19937::result_type result_type; + + LazyRandomEngine():engine(nullptr) {} + ~LazyRandomEngine() { + delete engine; + } + + LazyRandomEngine(const LazyRandomEngine &other) = delete; + LazyRandomEngine &operator=(const LazyRandomEngine &other) = delete; + + /** + * Create and seed the real engine. Call this before any + * other method. + */ + void AutoCreate(); + + result_type min() const { + return engine->min(); + } + + result_type max() const { + return engine->max(); + } + + result_type operator()() { + assert(engine != nullptr); + + return engine->operator()(); + } +}; + +#endif diff --git a/src/util/PeakBuffer.cxx b/src/util/PeakBuffer.cxx new file mode 100644 index 000000000..a3659b8f4 --- /dev/null +++ b/src/util/PeakBuffer.cxx @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "PeakBuffer.hxx" +#include "HugeAllocator.hxx" +#include "fifo_buffer.h" + +#include <algorithm> + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +PeakBuffer::~PeakBuffer() +{ + if (normal_buffer != nullptr) + fifo_buffer_free(normal_buffer); + + if (peak_buffer != nullptr) + HugeFree(peak_buffer, peak_size); +} + +bool +PeakBuffer::IsEmpty() const +{ + return (normal_buffer == nullptr || + fifo_buffer_is_empty(normal_buffer)) && + (peak_buffer == nullptr || + fifo_buffer_is_empty(peak_buffer)); +} + +const void * +PeakBuffer::Read(size_t *length_r) const +{ + if (normal_buffer != nullptr) { + const void *p = fifo_buffer_read(normal_buffer, length_r); + if (p != nullptr) + return p; + } + + if (peak_buffer != nullptr) { + const void *p = fifo_buffer_read(peak_buffer, length_r); + if (p != nullptr) + return p; + } + + return nullptr; +} + +void +PeakBuffer::Consume(size_t length) +{ + if (normal_buffer != nullptr && !fifo_buffer_is_empty(normal_buffer)) { + fifo_buffer_consume(normal_buffer, length); + return; + } + + if (peak_buffer != nullptr && !fifo_buffer_is_empty(peak_buffer)) { + fifo_buffer_consume(peak_buffer, length); + if (fifo_buffer_is_empty(peak_buffer)) { + HugeFree(peak_buffer, peak_size); + peak_buffer = nullptr; + } + + return; + } +} + +static size_t +AppendTo(fifo_buffer *buffer, const void *data, size_t length) +{ + assert(data != nullptr); + assert(length > 0); + + size_t total = 0; + + do { + size_t max_length; + void *p = fifo_buffer_write(buffer, &max_length); + if (p == nullptr) + break; + + const size_t nbytes = std::min(length, max_length); + memcpy(p, data, nbytes); + fifo_buffer_append(buffer, nbytes); + + data = (const uint8_t *)data + nbytes; + length -= nbytes; + total += nbytes; + } while (length > 0); + + return total; +} + +bool +PeakBuffer::Append(const void *data, size_t length) +{ + if (length == 0) + return true; + + if (peak_buffer != nullptr && !fifo_buffer_is_empty(peak_buffer)) { + size_t nbytes = AppendTo(peak_buffer, data, length); + return nbytes == length; + } + + if (normal_buffer == nullptr) + normal_buffer = fifo_buffer_new(normal_size); + + size_t nbytes = AppendTo(normal_buffer, data, length); + if (nbytes > 0) { + data = (const uint8_t *)data + nbytes; + length -= nbytes; + if (length == 0) + return true; + } + + if (peak_buffer == nullptr && peak_size > 0) { + peak_buffer = (fifo_buffer *)HugeAllocate(peak_size); + if (peak_buffer == nullptr) + return false; + + fifo_buffer_init(peak_buffer, peak_size); + } + + nbytes = AppendTo(peak_buffer, data, length); + return nbytes == length; +} diff --git a/src/util/PeakBuffer.hxx b/src/util/PeakBuffer.hxx new file mode 100644 index 000000000..0fbba8d77 --- /dev/null +++ b/src/util/PeakBuffer.hxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PEAK_BUFFER_HXX +#define MPD_PEAK_BUFFER_HXX + +#include "gcc.h" + +#include <stddef.h> + +struct fifo_buffer; + +/** + * A FIFO-like buffer that will allocate more memory on demand to + * allow large peaks. This second buffer will be given back to the + * kernel when it has been consumed. + */ +class PeakBuffer { + size_t normal_size, peak_size; + + fifo_buffer *normal_buffer, *peak_buffer; + +public: + PeakBuffer(size_t _normal_size, size_t _peak_size) + :normal_size(_normal_size), peak_size(_peak_size), + normal_buffer(nullptr), peak_buffer(nullptr) {} + + PeakBuffer(PeakBuffer &&other) + :normal_size(other.normal_size), peak_size(other.peak_size), + normal_buffer(other.normal_buffer), + peak_buffer(other.peak_buffer) { + other.normal_buffer = nullptr; + other.peak_buffer = nullptr; + } + + ~PeakBuffer(); + + PeakBuffer(const PeakBuffer &) = delete; + PeakBuffer &operator=(const PeakBuffer &) = delete; + + gcc_pure + bool IsEmpty() const; + + const void *Read(size_t *length_r) const; + void Consume(size_t length); + + bool Append(const void *data, size_t length); +}; + +#endif diff --git a/src/util/SliceBuffer.hxx b/src/util/SliceBuffer.hxx new file mode 100644 index 000000000..c61f164f4 --- /dev/null +++ b/src/util/SliceBuffer.hxx @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SLICE_BUFFER_HXX +#define MPD_SLICE_BUFFER_HXX + +#include "HugeAllocator.hxx" +#include "gcc.h" + +#include <utility> +#include <new> + +#include <assert.h> +#include <stddef.h> + +/** + * This class pre-allocates a certain number of objects, and allows + * callers to allocate and free these objects ("slices"). + */ +template<typename T> +class SliceBuffer { + union Slice { + Slice *next; + + T value; + }; + + /** + * The maximum number of slices in this container. + */ + const unsigned n_max; + + /** + * The number of slices that are initialized. This is used to + * avoid page faulting on the new allocation, so the kernel + * does not need to reserve physical memory pages. + */ + unsigned n_initialized; + + /** + * The number of slices currently allocated. + */ + unsigned n_allocated; + + Slice *const data; + + /** + * Pointer to the first free element in the chain. + */ + Slice *available; + + size_t CalcAllocationSize() const { + return n_max * sizeof(Slice); + } + +public: + SliceBuffer(unsigned _count) + :n_max(_count), n_initialized(0), n_allocated(0), + data((Slice *)HugeAllocate(CalcAllocationSize())), + available(nullptr) { + assert(n_max > 0); + } + + ~SliceBuffer() { + /* all slices must be freed explicitly, and this + assertion checks for leaks */ + assert(n_allocated == 0); + + HugeFree(data, CalcAllocationSize()); + } + + SliceBuffer(const SliceBuffer &other) = delete; + SliceBuffer &operator=(const SliceBuffer &other) = delete; + + /** + * @return true if buffer allocation (by the constructor) has failed + */ + bool IsOOM() { + return data == nullptr; + } + + unsigned GetCapacity() const { + return n_max; + } + + bool IsEmpty() const { + return n_allocated == 0; + } + + bool IsFull() const { + return n_allocated == n_max; + } + + template<typename... Args> + T *Allocate(Args&&... args) { + assert(n_initialized <= n_max); + assert(n_allocated <= n_initialized); + + if (available == nullptr) { + if (n_initialized == n_max) { + /* out of (internal) memory, buffer is full */ + assert(n_allocated == n_max); + return nullptr; + } + + available = &data[n_initialized++]; + available->next = nullptr; + } + + /* allocate a slice */ + T *value = &available->value; + available = available->next; + ++n_allocated; + + /* construct the object */ + return ::new((void *)value) T(std::forward<Args>(args)...); + } + + void Free(T *value) { + assert(n_initialized <= n_max); + assert(n_allocated > 0); + assert(n_allocated <= n_initialized); + + Slice *slice = reinterpret_cast<Slice *>(value); + assert(slice >= data && slice < data + n_max); + + /* destruct the object */ + value->~T(); + + /* insert the slice in the "available" linked list */ + slice->next = available; + available = slice; + --n_allocated; + + /* give memory back to the kernel when the last slice + was freed */ + if (n_allocated == 0) { + HugeDiscard(data, CalcAllocationSize()); + n_initialized = 0; + available = nullptr; + } + } +}; + +#endif diff --git a/src/util/bit_reverse.h b/src/util/bit_reverse.h index e44693b1d..54cb789bb 100644 --- a/src/util/bit_reverse.h +++ b/src/util/bit_reverse.h @@ -20,12 +20,13 @@ #ifndef MPD_BIT_REVERSE_H #define MPD_BIT_REVERSE_H -#include <glib.h> +#include "gcc.h" + #include <stdint.h> extern const uint8_t bit_reverse_table[256]; -G_GNUC_CONST +gcc_const static inline uint8_t bit_reverse(uint8_t x) { diff --git a/src/fifo_buffer.c b/src/util/fifo_buffer.c index 915fb0579..162ddf946 100644 --- a/src/fifo_buffer.c +++ b/src/util/fifo_buffer.c @@ -58,6 +58,14 @@ fifo_buffer_new(size_t size) return buffer; } +void +fifo_buffer_init(struct fifo_buffer *buffer, size_t size) +{ + buffer->size = size - (sizeof(*buffer) - sizeof(buffer->buffer)); + buffer->start = 0; + buffer->end = 0; +} + static void fifo_buffer_move(struct fifo_buffer *buffer); diff --git a/src/fifo_buffer.h b/src/util/fifo_buffer.h index 3bdb23938..ccea97d86 100644 --- a/src/fifo_buffer.h +++ b/src/util/fifo_buffer.h @@ -46,6 +46,10 @@ struct fifo_buffer; +#ifdef __cplusplus +extern "C" { +#endif + /** * Creates a new #fifo_buffer object. Free this object with * fifo_buffer_free(). @@ -56,6 +60,9 @@ struct fifo_buffer; struct fifo_buffer * fifo_buffer_new(size_t size); +void +fifo_buffer_init(struct fifo_buffer *buffer, size_t size); + /** * Change the capacity of the #fifo_buffer, while preserving existing * data. @@ -150,4 +157,8 @@ fifo_buffer_is_empty(struct fifo_buffer *buffer); bool fifo_buffer_is_full(struct fifo_buffer *buffer); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/growing_fifo.c b/src/util/growing_fifo.c index 88431f60e..88431f60e 100644 --- a/src/growing_fifo.c +++ b/src/util/growing_fifo.c diff --git a/src/growing_fifo.h b/src/util/growing_fifo.h index 723c3b3ff..723c3b3ff 100644 --- a/src/growing_fifo.h +++ b/src/util/growing_fifo.h diff --git a/src/util/list.h b/src/util/list.h index fdab47675..73d99befa 100644 --- a/src/util/list.h +++ b/src/util/list.h @@ -25,8 +25,6 @@ #ifndef _LINUX_LIST_H #define _LINUX_LIST_H -#include <glib.h> - #ifdef __clang__ /* allow typeof() */ #pragma GCC diagnostic ignored "-Wlanguage-extension-token" @@ -40,15 +38,15 @@ * */ #define container_of(ptr, type, member) \ - (&G_STRUCT_MEMBER(type, ptr, -G_STRUCT_OFFSET(type, member))) + ((type *)((uint8_t *)ptr - offsetof(type, member))) /* * These are non-NULL pointers that will result in page faults * under normal circumstances, used to verify that nobody uses * non-initialized list entries. */ -#define LIST_POISON1 ((void *) 0x00100100) -#define LIST_POISON2 ((void *) 0x00200200) +#define LIST_POISON1 ((struct list_head *)(void *) 0x00100100) +#define LIST_POISON2 ((struct list_head *)(void *) 0x00200200) /* * Simple doubly linked list implementation. @@ -82,46 +80,47 @@ static inline void INIT_LIST_HEAD(struct list_head *list) * the prev/next entries already! */ #ifndef CONFIG_DEBUG_LIST -static inline void __list_add(struct list_head *new, +static inline void __list_add(struct list_head *new_item, struct list_head *prev, struct list_head *next) { - next->prev = new; - new->next = next; - new->prev = prev; - prev->next = new; + next->prev = new_item; + new_item->next = next; + new_item->prev = prev; + prev->next = new_item; } #else -extern void __list_add(struct list_head *new, - struct list_head *prev, - struct list_head *next); +extern void __list_add(struct list_head *new_item, + struct list_head *prev, + struct list_head *next); #endif /** * list_add - add a new entry - * @new: new entry to be added + * @new_item: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ -static inline void list_add(struct list_head *new, struct list_head *head) +static inline void list_add(struct list_head *new_item, struct list_head *head) { - __list_add(new, head, head->next); + __list_add(new_item, head, head->next); } /** * list_add_tail - add a new entry - * @new: new entry to be added + * @new_item: new entry to be added * @head: list head to add it before * * Insert a new entry before the specified head. * This is useful for implementing queues. */ -static inline void list_add_tail(struct list_head *new, struct list_head *head) +static inline void +list_add_tail(struct list_head *new_item, struct list_head *head) { - __list_add(new, head->prev, head); + __list_add(new_item, head->prev, head); } /* @@ -163,23 +162,23 @@ extern void list_del(struct list_head *entry); /** * list_replace - replace old entry by new one * @old : the element to be replaced - * @new : the new element to insert + * @new_item : the new element to insert * * If @old was empty, it will be overwritten. */ static inline void list_replace(struct list_head *old, - struct list_head *new) + struct list_head *new_item) { - new->next = old->next; - new->next->prev = new; - new->prev = old->prev; - new->prev->next = new; + new_item->next = old->next; + new_item->next->prev = new_item; + new_item->prev = old->prev; + new_item->prev->next = new_item; } static inline void list_replace_init(struct list_head *old, - struct list_head *new) + struct list_head *new_item) { - list_replace(old, new); + list_replace(old, new_item); INIT_LIST_HEAD(old); } diff --git a/src/utils.h b/src/utils.h index f8d6657f2..059d44fa3 100644 --- a/src/utils.h +++ b/src/utils.h @@ -20,17 +20,7 @@ #ifndef MPD_UTILS_H #define MPD_UTILS_H -#include <glib.h> -#include <stdbool.h> - -#ifndef assert_static -/* Compile time assertion developed by Ralf Holly */ -/* http://pera-software.com/articles/compile-time-assertions.pdf */ -#define assert_static(e) \ - do { \ - enum { assert_static__ = 1/(e) }; \ - } while (0) -#endif /* !assert_static */ +#include "gerror.h" char * parsePath(const char *path, GError **error_r); diff --git a/src/zeroconf-avahi.c b/src/zeroconf-avahi.c index f2cc5359b..d2c6c084c 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> @@ -234,13 +234,8 @@ void init_avahi(const char *serviceName) if (!avahiClient) { g_warning("Failed to create client: %s", avahi_strerror(error)); - goto fail; + avahi_finish(); } - - return; - -fail: - avahi_finish(); } void avahi_finish(void) diff --git a/src/zeroconf-bonjour.c b/src/zeroconf-bonjour.c index 0f216aade..f3776ca29 100644 --- a/src/zeroconf-bonjour.c +++ b/src/zeroconf-bonjour.c @@ -19,7 +19,7 @@ #include "config.h" #include "zeroconf-internal.h" -#include "listen.h" +#include "Listen.hxx" #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> |