Compare commits
	
		
			127 Commits
		
	
	
		
			v0.16_alph
			...
			v0.16.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					9ad862d3a6 | ||
| 
						 | 
					77d71c4ee6 | ||
| 
						 | 
					8c0afd8557 | ||
| 
						 | 
					2a56300f7b | ||
| 
						 | 
					5f06999686 | ||
| 
						 | 
					4c09aeb5a1 | ||
| 
						 | 
					af892e7e80 | ||
| 
						 | 
					0022fb100b | ||
| 
						 | 
					4f2d67dfb0 | ||
| 
						 | 
					b5645ab29f | ||
| 
						 | 
					3149d1abf9 | ||
| 
						 | 
					59a417fc84 | ||
| 
						 | 
					b75d53413d | ||
| 
						 | 
					c44a744c0b | ||
| 
						 | 
					76cddfab90 | ||
| 
						 | 
					60b4f6b3eb | ||
| 
						 | 
					546232b1c0 | ||
| 
						 | 
					42c5788de3 | ||
| 
						 | 
					fb00e7fddc | ||
| 
						 | 
					41fdcf328c | ||
| 
						 | 
					144ad7992e | ||
| 
						 | 
					a0dd1a1b8b | ||
| 
						 | 
					c360e69162 | ||
| 
						 | 
					da01c6ef5b | ||
| 
						 | 
					fcd2355f4f | ||
| 
						 | 
					748a8a6f42 | ||
| 
						 | 
					cb9965bab5 | ||
| 
						 | 
					429ed24c99 | ||
| 
						 | 
					1ab46472ab | ||
| 
						 | 
					f6bbe1332f | ||
| 
						 | 
					11613347be | ||
| 
						 | 
					8f46f1520c | ||
| 
						 | 
					f2893b0d0f | ||
| 
						 | 
					c7265f9689 | ||
| 
						 | 
					46ab8d18e2 | ||
| 
						 | 
					f384f8da93 | ||
| 
						 | 
					23cd8a74be | ||
| 
						 | 
					cc1debc948 | ||
| 
						 | 
					5a3aa1262a | ||
| 
						 | 
					ad52eb236d | ||
| 
						 | 
					d2c2cbd0ae | ||
| 
						 | 
					af4a93dbcf | ||
| 
						 | 
					4478b3ef74 | ||
| 
						 | 
					462bba8e2f | ||
| 
						 | 
					dec7090198 | ||
| 
						 | 
					83ec0e5552 | ||
| 
						 | 
					cc261872c2 | ||
| 
						 | 
					5223261f19 | ||
| 
						 | 
					c594afeee7 | ||
| 
						 | 
					32d10eedbd | ||
| 
						 | 
					dfd98eede7 | ||
| 
						 | 
					a728d7a026 | ||
| 
						 | 
					5a26320680 | ||
| 
						 | 
					90dc880e67 | ||
| 
						 | 
					e11ff967d0 | ||
| 
						 | 
					2dc6ed7b3a | ||
| 
						 | 
					ad430c6617 | ||
| 
						 | 
					e8d8bd4c0d | ||
| 
						 | 
					8d5fa754e8 | ||
| 
						 | 
					2ee047a1dd | ||
| 
						 | 
					9562f66741 | ||
| 
						 | 
					21223154aa | ||
| 
						 | 
					ec48b5ea3a | ||
| 
						 | 
					754015544f | ||
| 
						 | 
					3f89f77429 | ||
| 
						 | 
					9dee419b7c | ||
| 
						 | 
					7612bf1bfa | ||
| 
						 | 
					ad56e10e5b | ||
| 
						 | 
					75f4772ba2 | ||
| 
						 | 
					fe1b626f76 | ||
| 
						 | 
					4e94516912 | ||
| 
						 | 
					dadb6747ad | ||
| 
						 | 
					188e1b440e | ||
| 
						 | 
					a57f9e712d | ||
| 
						 | 
					a549d871f3 | ||
| 
						 | 
					b552e9a120 | ||
| 
						 | 
					e6fc88a758 | ||
| 
						 | 
					20004b7ee0 | ||
| 
						 | 
					84e037631d | ||
| 
						 | 
					18e3d0b504 | ||
| 
						 | 
					04c4398bfc | ||
| 
						 | 
					39e42394bd | ||
| 
						 | 
					a39e6b43e8 | ||
| 
						 | 
					5923cfcde3 | ||
| 
						 | 
					e69df36e4a | ||
| 
						 | 
					e10b872fc3 | ||
| 
						 | 
					2b78358af5 | ||
| 
						 | 
					e3f4c7b91c | ||
| 
						 | 
					a59ab3e2ee | ||
| 
						 | 
					28bcb8bdf5 | ||
| 
						 | 
					9af9fd1400 | ||
| 
						 | 
					0c80bd5fc0 | ||
| 
						 | 
					7563ece236 | ||
| 
						 | 
					a14cd97f56 | ||
| 
						 | 
					0955f33a86 | ||
| 
						 | 
					a016fb4048 | ||
| 
						 | 
					e8ebb1af91 | ||
| 
						 | 
					9fa3d7c4fa | ||
| 
						 | 
					54294366d5 | ||
| 
						 | 
					9423b456a1 | ||
| 
						 | 
					64209749fb | ||
| 
						 | 
					586b7601c6 | ||
| 
						 | 
					4425989898 | ||
| 
						 | 
					5b996ab880 | ||
| 
						 | 
					635cfbae13 | ||
| 
						 | 
					922e51e8a9 | ||
| 
						 | 
					6d9f1ad61f | ||
| 
						 | 
					841b9b3d63 | ||
| 
						 | 
					a3745ae7f3 | ||
| 
						 | 
					27d7013ff8 | ||
| 
						 | 
					27d3340af2 | ||
| 
						 | 
					4a7abc9d44 | ||
| 
						 | 
					589bb54111 | ||
| 
						 | 
					d953225531 | ||
| 
						 | 
					663815ead8 | ||
| 
						 | 
					bc87ec0059 | ||
| 
						 | 
					917434269c | ||
| 
						 | 
					a77506ae21 | ||
| 
						 | 
					ed5d297301 | ||
| 
						 | 
					64dacd175a | ||
| 
						 | 
					625e4755d1 | ||
| 
						 | 
					92b6ba9eff | ||
| 
						 | 
					68c02fc95a | ||
| 
						 | 
					d18c1b1a0a | ||
| 
						 | 
					c980fc653d | ||
| 
						 | 
					36782a977a | ||
| 
						 | 
					676739c426 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -33,6 +33,7 @@ ltmain.sh
 | 
			
		||||
missing
 | 
			
		||||
mkinstalldirs
 | 
			
		||||
mpd
 | 
			
		||||
mpd.exe
 | 
			
		||||
stamp-h1
 | 
			
		||||
tags
 | 
			
		||||
*~
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								INSTALL
									
									
									
									
									
								
							@@ -59,6 +59,9 @@ You also need an encoder: either libvorbisenc (ogg), or liblame (mp3).
 | 
			
		||||
OpenAL - http://kcat.strangesoft.net/openal.html
 | 
			
		||||
Open Audio Library
 | 
			
		||||
 | 
			
		||||
libffado - http://www.ffado.org/
 | 
			
		||||
For FireWire audio devices.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Optional Input Dependencies
 | 
			
		||||
---------------------------
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								Makefile.am
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								Makefile.am
									
									
									
									
									
								
							@@ -7,6 +7,8 @@ AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"'
 | 
			
		||||
 | 
			
		||||
bin_PROGRAMS = src/mpd
 | 
			
		||||
 | 
			
		||||
noinst_LIBRARIES =
 | 
			
		||||
 | 
			
		||||
src_mpd_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
 | 
			
		||||
src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
 | 
			
		||||
	$(LIBWRAP_CFLAGS) \
 | 
			
		||||
@@ -34,6 +36,7 @@ mpd_headers = \
 | 
			
		||||
	src/check.h \
 | 
			
		||||
	src/notify.h \
 | 
			
		||||
	src/ack.h \
 | 
			
		||||
	src/ape.h \
 | 
			
		||||
	src/audio.h \
 | 
			
		||||
	src/audio_format.h \
 | 
			
		||||
	src/audio_check.h \
 | 
			
		||||
@@ -111,6 +114,7 @@ mpd_headers = \
 | 
			
		||||
	src/icy_metadata.h \
 | 
			
		||||
	src/client.h \
 | 
			
		||||
	src/client_internal.h \
 | 
			
		||||
	src/server_socket.h \
 | 
			
		||||
	src/listen.h \
 | 
			
		||||
	src/log.h \
 | 
			
		||||
	src/ls.h \
 | 
			
		||||
@@ -136,6 +140,7 @@ mpd_headers = \
 | 
			
		||||
	src/output/httpd_client.h \
 | 
			
		||||
	src/output/httpd_internal.h \
 | 
			
		||||
	src/output/pulse_output_plugin.h \
 | 
			
		||||
	src/output/winmm_output_plugin.h \
 | 
			
		||||
	src/page.h \
 | 
			
		||||
	src/pcm_buffer.h \
 | 
			
		||||
	src/pcm_utils.h \
 | 
			
		||||
@@ -171,6 +176,7 @@ mpd_headers = \
 | 
			
		||||
	src/playlist/pls_playlist_plugin.h \
 | 
			
		||||
	src/playlist/xspf_playlist_plugin.h \
 | 
			
		||||
	src/playlist/asx_playlist_plugin.h \
 | 
			
		||||
	src/playlist/rss_playlist_plugin.h \
 | 
			
		||||
	src/playlist/lastfm_playlist_plugin.h \
 | 
			
		||||
	src/playlist/cue_playlist_plugin.h \
 | 
			
		||||
	src/playlist/flac_playlist_plugin.h \
 | 
			
		||||
@@ -183,6 +189,7 @@ mpd_headers = \
 | 
			
		||||
	src/refcount.h \
 | 
			
		||||
	src/replay_gain_config.h \
 | 
			
		||||
	src/replay_gain_info.h \
 | 
			
		||||
	src/replay_gain_ape.h \
 | 
			
		||||
	src/sig_handlers.h \
 | 
			
		||||
	src/song.h \
 | 
			
		||||
	src/song_print.h \
 | 
			
		||||
@@ -220,7 +227,8 @@ mpd_headers = \
 | 
			
		||||
	src/archive/iso9660_archive_plugin.h \
 | 
			
		||||
	src/archive/zzip_archive_plugin.h \
 | 
			
		||||
	src/input/archive_input_plugin.h \
 | 
			
		||||
	src/cue/cue_tag.h
 | 
			
		||||
	src/cue/cue_tag.h\
 | 
			
		||||
	src/mpd_error.h
 | 
			
		||||
 | 
			
		||||
src_mpd_SOURCES = \
 | 
			
		||||
	$(mpd_headers) \
 | 
			
		||||
@@ -274,10 +282,12 @@ src_mpd_SOURCES = \
 | 
			
		||||
	src/client_process.c \
 | 
			
		||||
	src/client_read.c \
 | 
			
		||||
	src/client_write.c \
 | 
			
		||||
	src/server_socket.c \
 | 
			
		||||
	src/listen.c \
 | 
			
		||||
	src/log.c \
 | 
			
		||||
	src/ls.c \
 | 
			
		||||
	src/main.c \
 | 
			
		||||
	src/main_win32.c \
 | 
			
		||||
	src/event_pipe.c \
 | 
			
		||||
	src/daemon.c \
 | 
			
		||||
	src/AudioCompress/compress.c \
 | 
			
		||||
@@ -406,6 +416,8 @@ TAG_LIBS = \
 | 
			
		||||
	$(ID3TAG_LIBS)
 | 
			
		||||
 | 
			
		||||
TAG_SRC = \
 | 
			
		||||
	src/ape.c \
 | 
			
		||||
	src/replay_gain_ape.c \
 | 
			
		||||
	src/tag_ape.c
 | 
			
		||||
 | 
			
		||||
if HAVE_ID3TAG
 | 
			
		||||
@@ -422,7 +434,6 @@ DECODER_CFLAGS = \
 | 
			
		||||
	$(SNDFILE_CFLAGS) \
 | 
			
		||||
	$(AUDIOFILE_CFLAGS) \
 | 
			
		||||
	$(LIBMIKMOD_CFLAGS) \
 | 
			
		||||
	$(MODPLUG_CFLAGS) \
 | 
			
		||||
	$(GME_CFLAGS) \
 | 
			
		||||
	$(SIDPLAY_CFLAGS) \
 | 
			
		||||
	$(FLUIDSYNTH_CFLAGS) \
 | 
			
		||||
@@ -438,7 +449,6 @@ DECODER_LIBS = \
 | 
			
		||||
	$(FLAC_LIBS) \
 | 
			
		||||
	$(SNDFILE_LIBS) \
 | 
			
		||||
	$(AUDIOFILE_LIBS) $(LIBMIKMOD_LIBS) \
 | 
			
		||||
	$(MODPLUG_LIBS) \
 | 
			
		||||
	$(GME_LIBS) \
 | 
			
		||||
	$(SIDPLAY_LIBS) \
 | 
			
		||||
	$(FLUIDSYNTH_LIBS) \
 | 
			
		||||
@@ -511,7 +521,11 @@ DECODER_SRC += src/decoder/mikmod_decoder_plugin.c
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
if HAVE_MODPLUG
 | 
			
		||||
DECODER_SRC += src/decoder/modplug_decoder_plugin.c
 | 
			
		||||
libmodplug_decoder_plugin_a_SOURCES = src/decoder/modplug_decoder_plugin.c
 | 
			
		||||
libmodplug_decoder_plugin_a_CFLAGS = $(src_mpd_CFLAGS) $(MODPLUG_CFLAGS)
 | 
			
		||||
libmodplug_decoder_plugin_a_CPPFLAGS = $(src_mpd_CPPFLAGS)
 | 
			
		||||
noinst_LIBRARIES += libmodplug_decoder_plugin.a
 | 
			
		||||
DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS)
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
if ENABLE_SIDPLAY
 | 
			
		||||
@@ -634,6 +648,7 @@ endif
 | 
			
		||||
OUTPUT_CFLAGS = \
 | 
			
		||||
	$(AO_CFLAGS) \
 | 
			
		||||
	$(ALSA_CFLAGS) \
 | 
			
		||||
	$(FFADO_CFLAGS) \
 | 
			
		||||
	$(JACK_CFLAGS) \
 | 
			
		||||
	$(OPENAL_CFLAGS) \
 | 
			
		||||
	$(PULSE_CFLAGS) \
 | 
			
		||||
@@ -643,6 +658,7 @@ OUTPUT_LIBS = \
 | 
			
		||||
	$(LIBWRAP_LDFLAGS) \
 | 
			
		||||
	$(AO_LIBS) \
 | 
			
		||||
	$(ALSA_LIBS) \
 | 
			
		||||
	$(FFADO_LIBS) \
 | 
			
		||||
	$(JACK_LIBS) \
 | 
			
		||||
	$(OPENAL_LIBS) \
 | 
			
		||||
	$(PULSE_LIBS) \
 | 
			
		||||
@@ -675,6 +691,10 @@ OUTPUT_SRC += src/output/alsa_plugin.c
 | 
			
		||||
MIXER_SRC += src/mixer/alsa_mixer_plugin.c
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
if ENABLE_FFADO_OUTPUT
 | 
			
		||||
OUTPUT_SRC += src/output/ffado_output_plugin.c
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
if HAVE_AO
 | 
			
		||||
OUTPUT_SRC += src/output/ao_plugin.c
 | 
			
		||||
endif
 | 
			
		||||
@@ -732,8 +752,9 @@ if ENABLE_SOLARIS_OUTPUT
 | 
			
		||||
OUTPUT_SRC += src/output/solaris_output_plugin.c
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
if ENABLE_WIN32_OUTPUT
 | 
			
		||||
OUTPUT_SRC += src/output/win32_output_plugin.c
 | 
			
		||||
if ENABLE_WINMM_OUTPUT
 | 
			
		||||
OUTPUT_SRC += src/output/winmm_output_plugin.c
 | 
			
		||||
MIXER_SRC += src/mixer/winmm_mixer_plugin.c
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -747,6 +768,7 @@ PLAYLIST_SRC = \
 | 
			
		||||
	src/playlist/pls_playlist_plugin.c \
 | 
			
		||||
	src/playlist/xspf_playlist_plugin.c \
 | 
			
		||||
	src/playlist/asx_playlist_plugin.c \
 | 
			
		||||
	src/playlist/rss_playlist_plugin.c \
 | 
			
		||||
	src/playlist_list.c
 | 
			
		||||
 | 
			
		||||
if ENABLE_LASTFM
 | 
			
		||||
@@ -994,6 +1016,7 @@ if HAVE_LIBSAMPLERATE
 | 
			
		||||
test_run_convert_SOURCES += src/pcm_resample_libsamplerate.c
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
test_run_output_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
 | 
			
		||||
test_run_output_CPPFLAGS = $(AM_CPPFLAGS) \
 | 
			
		||||
	$(ENCODER_CFLAGS) \
 | 
			
		||||
	$(OUTPUT_CFLAGS)
 | 
			
		||||
@@ -1029,6 +1052,7 @@ test_run_output_SOURCES = test/run_output.c \
 | 
			
		||||
	src/replay_gain_info.c \
 | 
			
		||||
	src/replay_gain_config.c \
 | 
			
		||||
	src/fd_util.c \
 | 
			
		||||
	src/server_socket.c \
 | 
			
		||||
	$(OUTPUT_SRC)
 | 
			
		||||
 | 
			
		||||
test_read_mixer_CPPFLAGS = $(AM_CPPFLAGS) \
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								NEWS
									
									
									
									
									
								
							@@ -1,4 +1,20 @@
 | 
			
		||||
ver 0.16 (20??/??/??)
 | 
			
		||||
ver 0.16.1 (2010/01/09)
 | 
			
		||||
* audio_check: fix parameter in prototype
 | 
			
		||||
* add void casts to suppress "result unused" warnings (clang)
 | 
			
		||||
* input:
 | 
			
		||||
  - ffado: disable by default
 | 
			
		||||
* decoder:
 | 
			
		||||
  - mad: work around build failure on Solaris
 | 
			
		||||
  - resolve modplug vs. libsndfile cflags/headers conflict
 | 
			
		||||
* output:
 | 
			
		||||
  - solaris: add missing parameter to open_cloexec() cal
 | 
			
		||||
  - osx: fix up audio format first, then apply it to device
 | 
			
		||||
* player_thread: discard empty chunks while cross-fading
 | 
			
		||||
* player_thread: fix assertion failure due to early seek
 | 
			
		||||
* output_thread: fix double lock
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ver 0.16 (2010/12/11)
 | 
			
		||||
* protocol:
 | 
			
		||||
  - send song modification time to client
 | 
			
		||||
  - added "update" idle event
 | 
			
		||||
@@ -20,7 +36,9 @@ ver 0.16 (20??/??/??)
 | 
			
		||||
* tags:
 | 
			
		||||
  - added tags "ArtistSort", "AlbumArtistSort"
 | 
			
		||||
  - id3: revised "performer" tag support
 | 
			
		||||
  - id3: support multiple values
 | 
			
		||||
  - ape: MusicBrainz tags
 | 
			
		||||
  - ape: support multiple values
 | 
			
		||||
* decoders:
 | 
			
		||||
  - don't try a plugin twice (MIME type & suffix)
 | 
			
		||||
  - don't fall back to "mad" unless no plugin matches
 | 
			
		||||
@@ -35,6 +53,8 @@ ver 0.16 (20??/??/??)
 | 
			
		||||
  - sidplay: support sub-tunes
 | 
			
		||||
  - sidplay: implemented songlength database
 | 
			
		||||
  - sidplay: support seeking
 | 
			
		||||
  - sidplay: play monaural SID tunes in mono
 | 
			
		||||
  - sidplay: play mus, str, prg, x00 files
 | 
			
		||||
  - wavpack: activate 32 bit support
 | 
			
		||||
  - wavpack: allow more than 2 channels
 | 
			
		||||
  - mp4ff: rename plugin "mp4" to "mp4ff"
 | 
			
		||||
@@ -59,8 +79,11 @@ ver 0.16 (20??/??/??)
 | 
			
		||||
  - jack: support more than two audio channels
 | 
			
		||||
  - httpd: bind port when output is enabled
 | 
			
		||||
  - httpd: added name/genre/website configuration
 | 
			
		||||
  - httpd: implement "pause"
 | 
			
		||||
  - httpd: bind_to_address support (including IPv6)
 | 
			
		||||
  - oss: 24 bit support via OSS4
 | 
			
		||||
  - win32: new output plugin for Windows Wave
 | 
			
		||||
  - shout, httpd: more responsive to control commands
 | 
			
		||||
  - wildcards allowed in audio_format configuration
 | 
			
		||||
  - consistently lock audio output objects
 | 
			
		||||
* player:
 | 
			
		||||
@@ -83,6 +106,7 @@ ver 0.16 (20??/??/??)
 | 
			
		||||
  - fall back to track gain if album gain is unavailable
 | 
			
		||||
  - optionally use hardware mixer to apply replay gain
 | 
			
		||||
  - added mode "auto"
 | 
			
		||||
  - parse replay gain from APE tags
 | 
			
		||||
* log unused/unknown block parameters
 | 
			
		||||
* removed the deprecated "error_file" option
 | 
			
		||||
* save state when stopped
 | 
			
		||||
@@ -101,6 +125,38 @@ ver 0.16 (20??/??/??)
 | 
			
		||||
* added test suite ("make check")
 | 
			
		||||
* require GLib 2.12
 | 
			
		||||
* added libwrap support
 | 
			
		||||
* make single mode 'sticky'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ver 0.15.16 (2010/??/??)
 | 
			
		||||
* encoders:
 | 
			
		||||
  - lame: explicitly configure the output sample rate
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ver 0.15.15 (2010/11/08)
 | 
			
		||||
* input:
 | 
			
		||||
  - rewind: fix assertion failure
 | 
			
		||||
* output:
 | 
			
		||||
  - shout: artist comes first in stream title
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ver 0.15.14 (2010/11/06)
 | 
			
		||||
* player_thread: fix assertion failure due to wrong music pipe on seek
 | 
			
		||||
* output_thread: fix assertion failure due to race condition in OPEN
 | 
			
		||||
* input:
 | 
			
		||||
  - rewind: fix double free bug
 | 
			
		||||
* decoders:
 | 
			
		||||
  - mp4ff, ffmpeg: add extension ".m4b" (audio book)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ver 0.15.13 (2010/10/10)
 | 
			
		||||
* output_thread: fix race condition after CANCEL command
 | 
			
		||||
* output:
 | 
			
		||||
  - httpd: fix random data in stream title
 | 
			
		||||
  - httpd: MIME type audio/ogg for Ogg Vorbis
 | 
			
		||||
* input:
 | 
			
		||||
  - rewind: update MIME not only once
 | 
			
		||||
  - rewind: enable for MMS
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ver 0.15.12 (2010/07/20)
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ if test -n "$AM_FORCE_VERSION"
 | 
			
		||||
then
 | 
			
		||||
	AM_VERSIONS="$AM_FORCE_VERSION"
 | 
			
		||||
else
 | 
			
		||||
	AM_VERSIONS='1.10'
 | 
			
		||||
	AM_VERSIONS='1.11 1.10'
 | 
			
		||||
fi
 | 
			
		||||
if test -n "$AC_FORCE_VERSION"
 | 
			
		||||
then
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										102
									
								
								configure.ac
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								configure.ac
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
AC_PREREQ(2.60)
 | 
			
		||||
AC_INIT(mpd, 0.16~alpha2, musicpd-dev-team@lists.sourceforge.net)
 | 
			
		||||
AC_INIT(mpd, 0.16.1, musicpd-dev-team@lists.sourceforge.net)
 | 
			
		||||
AC_CONFIG_SRCDIR([src/main.c])
 | 
			
		||||
AM_INIT_AUTOMAKE([foreign 1.10 dist-bzip2 subdir-objects])
 | 
			
		||||
AM_CONFIG_HEADER(config.h)
 | 
			
		||||
@@ -13,6 +13,7 @@ dnl Programs
 | 
			
		||||
dnl ---------------------------------------------------------------------------
 | 
			
		||||
AC_PROG_CC_C99
 | 
			
		||||
AC_PROG_CXX
 | 
			
		||||
AC_PROG_RANLIB
 | 
			
		||||
 | 
			
		||||
HAVE_CXX=yes
 | 
			
		||||
if test x$CXX = xg++; then
 | 
			
		||||
@@ -155,6 +156,10 @@ AC_ARG_ENABLE(documentation,
 | 
			
		||||
		[build documentation (default: disable)]),,
 | 
			
		||||
	[enable_documentation=no])
 | 
			
		||||
 | 
			
		||||
AC_ARG_ENABLE(ffado,
 | 
			
		||||
	AS_HELP_STRING([--enable-ffado], [enable libffado (FireWire) support]),,
 | 
			
		||||
	[enable_ffado=no])
 | 
			
		||||
 | 
			
		||||
AC_ARG_ENABLE(ffmpeg,
 | 
			
		||||
	AS_HELP_STRING([--enable-ffmpeg],
 | 
			
		||||
		[enable FFMPEG support]),,
 | 
			
		||||
@@ -437,7 +442,7 @@ if test x$enable_tcp = xyes; then
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
case "$host_os" in
 | 
			
		||||
mingw* | windows*)
 | 
			
		||||
mingw* | windows* | cygwin*)
 | 
			
		||||
	enable_un=no
 | 
			
		||||
	;;
 | 
			
		||||
esac
 | 
			
		||||
@@ -630,7 +635,9 @@ fi
 | 
			
		||||
AM_CONDITIONAL(ENABLE_LASTFM, test x$enable_lastfm = xyes)
 | 
			
		||||
 | 
			
		||||
dnl ---------------------------------- libogg ---------------------------------
 | 
			
		||||
PKG_CHECK_MODULES(OGG, [ogg], enable_ogg=yes, enable_ogg=no)
 | 
			
		||||
if test x$with_tremor == xno || test -z $with_tremor; then
 | 
			
		||||
	PKG_CHECK_MODULES(OGG, [ogg], enable_ogg=yes, enable_ogg=no)
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
dnl ---------------------------------- libmms ---------------------------------
 | 
			
		||||
MPD_AUTO_PKG(mms, MMS, [libmms >= 0.4],
 | 
			
		||||
@@ -843,15 +850,6 @@ if test x$enable_modplug = xyes; then
 | 
			
		||||
fi
 | 
			
		||||
AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes)
 | 
			
		||||
 | 
			
		||||
dnl --------------------------- sndfile/modplug test --------------------------
 | 
			
		||||
if test x$enable_sndfile = xauto && test x$enable_modplug = xyes; then
 | 
			
		||||
	dnl If modplug is enabled, enable sndfile only if explicitly
 | 
			
		||||
	dnl requested - modplug's modplug/sndfile.h is known to
 | 
			
		||||
	dnl conflict with libsndfile's sndfile.h.
 | 
			
		||||
	AC_MSG_NOTICE([disabling libsndfile auto-detection, because the modplug decoder is enabled])
 | 
			
		||||
	enable_sndfile=no
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
dnl -------------------------------- libsndfile -------------------------------
 | 
			
		||||
dnl See above test, which may disable this.
 | 
			
		||||
MPD_AUTO_PKG(sndfile, SNDFILE, [sndfile],
 | 
			
		||||
@@ -914,13 +912,13 @@ AM_CONDITIONAL(HAVE_MPCDEC, test x$enable_mpc = xyes)
 | 
			
		||||
 | 
			
		||||
dnl -------------------------------- Ogg Tremor -------------------------------
 | 
			
		||||
if test x$with_tremor = xyes || test x$with_tremor = xno; then
 | 
			
		||||
	use_tremor="$with_tremor"
 | 
			
		||||
	enable_tremor="$with_tremor"
 | 
			
		||||
else
 | 
			
		||||
	tremor_prefix="$with_tremor"
 | 
			
		||||
	use_tremor=yes
 | 
			
		||||
	enable_tremor=yes
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if test x$use_tremor = xyes; then
 | 
			
		||||
if test x$enable_tremor = xyes; then
 | 
			
		||||
	if test "x$tremor_libraries" != "x" ; then
 | 
			
		||||
		TREMOR_LIBS="-L$tremor_libraries"
 | 
			
		||||
	elif test "x$tremor_prefix" != "x" ; then
 | 
			
		||||
@@ -951,7 +949,7 @@ AC_SUBST(TREMOR_LIBS)
 | 
			
		||||
dnl --------------------------------- OggFLAC ---------------------------------
 | 
			
		||||
dnl OggFLAC must go after Ogg Tremor
 | 
			
		||||
 | 
			
		||||
if test x$use_tremor = xyes && test $xenable_oggflac = xyes; then
 | 
			
		||||
if test x$enable_tremor = xyes && test x$enable_oggflac = xyes; then
 | 
			
		||||
	AC_MSG_WARN([disabling OggFLAC support because it is incompatible with tremor])
 | 
			
		||||
		enable_oggflac=no
 | 
			
		||||
fi
 | 
			
		||||
@@ -968,8 +966,11 @@ fi
 | 
			
		||||
AM_CONDITIONAL(HAVE_OGGFLAC, test x$enable_oggflac = xyes)
 | 
			
		||||
 | 
			
		||||
dnl -------------------------------- Ogg Vorbis -------------------------------
 | 
			
		||||
if test x$enable_tremor != xno && test x$enable_vorbis = xyes; then
 | 
			
		||||
	if test x$enable_ogg = xyes; then
 | 
			
		||||
if test x$enable_vorbis = xyes; then
 | 
			
		||||
	if test x$enable_tremor = xyes; then
 | 
			
		||||
		AC_MSG_WARN(["OggTremor detected, could not enable Vorbis."])
 | 
			
		||||
		enable_vorbis=no
 | 
			
		||||
	elif test x$enable_ogg = xyes; then
 | 
			
		||||
		PKG_CHECK_MODULES(VORBIS, [vorbis vorbisfile],
 | 
			
		||||
			AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support]),
 | 
			
		||||
			enable_vorbis=no)
 | 
			
		||||
@@ -1065,6 +1066,7 @@ if
 | 
			
		||||
	test x$enable_mpg123 = xno &&
 | 
			
		||||
	test x$enable_oggflac = xno &&
 | 
			
		||||
	test x$enable_sidplay = xno &&
 | 
			
		||||
	test x$enable_tremor = xno &&
 | 
			
		||||
	test x$enable_vorbis = xno &&
 | 
			
		||||
	test x$enable_wavpack = xno &&
 | 
			
		||||
	test x$enable_wildmidi = xno &&
 | 
			
		||||
@@ -1199,6 +1201,17 @@ fi
 | 
			
		||||
 | 
			
		||||
AM_CONDITIONAL(HAVE_ALSA, test x$enable_alsa = xyes)
 | 
			
		||||
 | 
			
		||||
dnl ----------------------------------- FFADO ---------------------------------
 | 
			
		||||
 | 
			
		||||
MPD_AUTO_PKG(ffado, FFADO, [libffado],
 | 
			
		||||
	[libffado output plugin], [libffado not found])
 | 
			
		||||
 | 
			
		||||
if test x$enable_ffado = xyes; then
 | 
			
		||||
	AC_DEFINE(ENABLE_FFADO_OUTPUT, 1, [Define to enable the libffado output plugin])
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
AM_CONDITIONAL(ENABLE_FFADO_OUTPUT, test x$enable_ffado = xyes)
 | 
			
		||||
 | 
			
		||||
dnl ----------------------------------- FIFO ----------------------------------
 | 
			
		||||
if test x$enable_fifo = xyes; then
 | 
			
		||||
	AC_CHECK_FUNC([mkfifo],
 | 
			
		||||
@@ -1369,26 +1382,27 @@ esac
 | 
			
		||||
 | 
			
		||||
AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes)
 | 
			
		||||
 | 
			
		||||
dnl --------------------------------- Solaris ---------------------------------
 | 
			
		||||
dnl --------------------------------- WinMM ---------------------------------
 | 
			
		||||
 | 
			
		||||
case "$host_os" in
 | 
			
		||||
	mingw32* | windows*)
 | 
			
		||||
		AC_DEFINE(ENABLE_WIN32_OUTPUT, 1, [Define to enable WIN32 wave support])
 | 
			
		||||
		enable_win32_output=yes
 | 
			
		||||
		AC_DEFINE(ENABLE_WINMM_OUTPUT, 1, [Define to enable WinMM support])
 | 
			
		||||
		enable_winmm_output=yes
 | 
			
		||||
		MPD_LIBS="$MPD_LIBS -lwinmm"
 | 
			
		||||
		;;
 | 
			
		||||
 | 
			
		||||
	*)
 | 
			
		||||
		enable_win32_output=no
 | 
			
		||||
		enable_winmm_output=no
 | 
			
		||||
		;;
 | 
			
		||||
esac
 | 
			
		||||
 | 
			
		||||
AM_CONDITIONAL(ENABLE_WIN32_OUTPUT, test x$enable_win32_output = xyes)
 | 
			
		||||
AM_CONDITIONAL(ENABLE_WINMM_OUTPUT, test x$enable_winmm_output = xyes)
 | 
			
		||||
 | 
			
		||||
dnl --------------------- Post Audio Output Plugins Tests ---------------------
 | 
			
		||||
if
 | 
			
		||||
	test x$enable_alsa = xno &&
 | 
			
		||||
	test x$enable_ao = xno &&
 | 
			
		||||
	test x$enable_ffado = xno &&
 | 
			
		||||
	test x$enable_fifo = xno &&
 | 
			
		||||
	test x$enable_httpd_output = xno &&
 | 
			
		||||
	test x$enable_jack = xno &&
 | 
			
		||||
@@ -1401,7 +1415,7 @@ if
 | 
			
		||||
	test x$enable_recorder_output = xno &&
 | 
			
		||||
	test x$enable_shout = xno &&
 | 
			
		||||
	test x$enable_solaris_output = xno &&
 | 
			
		||||
	test x$enable_win32_output = xno &&
 | 
			
		||||
	test x$enable_winmm_output = xno &&
 | 
			
		||||
 | 
			
		||||
		AC_MSG_ERROR([No Audio Output types configured!])
 | 
			
		||||
fi
 | 
			
		||||
@@ -1473,23 +1487,23 @@ dnl ---------------------------------------------------------------------------
 | 
			
		||||
echo ''
 | 
			
		||||
echo '########### MPD CONFIGURATION ############'
 | 
			
		||||
 | 
			
		||||
echo -ne '\nArchive support:\n\t'
 | 
			
		||||
printf '\nArchive support:\n\t'
 | 
			
		||||
results(bzip2,[bzip2])
 | 
			
		||||
results(iso9660,[ISO9660])
 | 
			
		||||
results(zzip,[ZIP])
 | 
			
		||||
 | 
			
		||||
if test x$with_zeroconf != xno; then
 | 
			
		||||
	echo -ne '\nAutodiscovery support:\n\t'
 | 
			
		||||
	printf '\nAutodiscovery support:\n\t'
 | 
			
		||||
	results(avahi, [Avahi])
 | 
			
		||||
	results(bonjour, [Bonjour])
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
echo -ne '\nClient support:\n\t'
 | 
			
		||||
printf '\nClient support:\n\t'
 | 
			
		||||
results(ipv6, "IPv6")
 | 
			
		||||
results(tcp, "TCP")
 | 
			
		||||
results(un,[UNIX Domain Sockets])
 | 
			
		||||
 | 
			
		||||
echo -ne '\nFile format support:\n\t'
 | 
			
		||||
printf '\nFile format support:\n\t'
 | 
			
		||||
results(aac, [AAC])
 | 
			
		||||
results(sidplay, [C64 SID])
 | 
			
		||||
results(ffmpeg, [FFMPEG])
 | 
			
		||||
@@ -1497,7 +1511,7 @@ results(flac, [FLAC])
 | 
			
		||||
results(fluidsynth, [FluidSynth])
 | 
			
		||||
results(gme, [GME])
 | 
			
		||||
results(sndfile, [libsndfile])
 | 
			
		||||
echo -ne '\n\t'
 | 
			
		||||
printf '\n\t'
 | 
			
		||||
results(mikmod, [MikMod])
 | 
			
		||||
results(modplug, [MODPLUG])
 | 
			
		||||
results(mad, [MAD])
 | 
			
		||||
@@ -1505,46 +1519,47 @@ results(mpg123, [MPG123])
 | 
			
		||||
results(mp4, [MP4])
 | 
			
		||||
results(mpc, [Musepack])
 | 
			
		||||
results(oggflac, [OggFLAC], flac)
 | 
			
		||||
echo -ne '\n\t'
 | 
			
		||||
results(with_tremor, [OggTremor])
 | 
			
		||||
printf '\n\t'
 | 
			
		||||
results(tremor, [OggTremor])
 | 
			
		||||
results(vorbis, [OggVorbis])
 | 
			
		||||
results(audiofile, [WAVE])
 | 
			
		||||
results(wavpack, [WavPack])
 | 
			
		||||
results(wildmidi, [WildMidi])
 | 
			
		||||
 | 
			
		||||
echo -en '\nOther features:\n\t'
 | 
			
		||||
printf '\nOther features:\n\t'
 | 
			
		||||
results(lsr, [libsamplerate])
 | 
			
		||||
results(inotify, [inotify])
 | 
			
		||||
results(sqlite, [SQLite])
 | 
			
		||||
 | 
			
		||||
echo -en '\nMetadata support:\n\t'
 | 
			
		||||
printf '\nMetadata support:\n\t'
 | 
			
		||||
results(cue,[cue])
 | 
			
		||||
results(id3,[ID3])
 | 
			
		||||
 | 
			
		||||
echo -en '\nPlayback support:\n\t'
 | 
			
		||||
printf '\nPlayback support:\n\t'
 | 
			
		||||
results(alsa,ALSA)
 | 
			
		||||
results(ffado,FFADO)
 | 
			
		||||
results(fifo,FIFO)
 | 
			
		||||
results(recorder_output,[File Recorder])
 | 
			
		||||
results(httpd_output,[HTTP Daemon])
 | 
			
		||||
results(jack,[JACK])
 | 
			
		||||
results(ao,[libao])
 | 
			
		||||
results(oss,[OSS])
 | 
			
		||||
echo -ne '\n\t'
 | 
			
		||||
printf '\n\t'
 | 
			
		||||
results(openal,[OpenAL])
 | 
			
		||||
results(osx, [OS X])
 | 
			
		||||
results(pipe_output, [Pipeline])
 | 
			
		||||
results(pulse, [PulseAudio])
 | 
			
		||||
results(mvp, [Media MVP])
 | 
			
		||||
results(shout, [SHOUTcast])
 | 
			
		||||
echo -ne '\n\t'
 | 
			
		||||
printf '\n\t'
 | 
			
		||||
results(solaris, [Solaris])
 | 
			
		||||
results(win32_output, [WIN32 wave])
 | 
			
		||||
results(winmm_output, [WinMM])
 | 
			
		||||
 | 
			
		||||
if
 | 
			
		||||
	test x$enable_shout = xyes ||
 | 
			
		||||
	test x$enable_recorder = xyes ||
 | 
			
		||||
	test x$enable_httpd_output = xyes; then
 | 
			
		||||
		echo -en '\nStreaming encoder support:\n\t'
 | 
			
		||||
		printf '\nStreaming encoder support:\n\t'
 | 
			
		||||
		results(flac_encoder, [FLAC])
 | 
			
		||||
		results(lame_encoder, [LAME])
 | 
			
		||||
		results(vorbis_encoder, [Ogg Vorbis])
 | 
			
		||||
@@ -1552,19 +1567,14 @@ if
 | 
			
		||||
		results(wave_encoder, [WAVE])
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
echo -en '\nStreaming support:\n\t'
 | 
			
		||||
printf '\nStreaming support:\n\t'
 | 
			
		||||
results(curl,[CURL])
 | 
			
		||||
results(lastfm,[Last.FM])
 | 
			
		||||
results(mms,[MMS])
 | 
			
		||||
 | 
			
		||||
echo -ne '\n\n##########################################\n\n'
 | 
			
		||||
printf '\n\n##########################################\n\n'
 | 
			
		||||
 | 
			
		||||
if test x$enable_sndfile = xyes && test x$enable_modplug = xyes; then
 | 
			
		||||
	AC_MSG_WARN([compilation may fail, because libmodplug conflicts with libsndfile])
 | 
			
		||||
	AC_MSG_WARN([libmodplug ships modplug/sndfile.h, which hides libsndfile's sndfile.h])
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
echo -ne 'Generating files needed for compilation\n'
 | 
			
		||||
echo 'Generating files needed for compilation'
 | 
			
		||||
 | 
			
		||||
dnl ---------------------------------------------------------------------------
 | 
			
		||||
dnl Generate files
 | 
			
		||||
 
 | 
			
		||||
@@ -260,6 +260,7 @@ input {
 | 
			
		||||
#	name		"My HTTP Stream"
 | 
			
		||||
#	encoder		"vorbis"		# optional, vorbis or lame
 | 
			
		||||
#	port		"8000"
 | 
			
		||||
#	bind_to_address	"0.0.0.0"		# optional, IPv4 or IPv6
 | 
			
		||||
##	quality		"5.0"			# do not define if bitrate is defined
 | 
			
		||||
#	bitrate		"128"			# do not define if quality is defined
 | 
			
		||||
#	format		"44100:16:1"
 | 
			
		||||
 
 | 
			
		||||
@@ -240,6 +240,12 @@
 | 
			
		||||
                  <returnvalue>0 or 1</returnvalue>
 | 
			
		||||
                </para>
 | 
			
		||||
              </listitem>
 | 
			
		||||
              <listitem>
 | 
			
		||||
                <para>
 | 
			
		||||
                  <varname>random</varname>:
 | 
			
		||||
                  <returnvalue>0 or 1</returnvalue>
 | 
			
		||||
                </para>
 | 
			
		||||
              </listitem>
 | 
			
		||||
              <listitem>
 | 
			
		||||
               <para>
 | 
			
		||||
                <varname>single</varname>:
 | 
			
		||||
@@ -1208,16 +1214,18 @@ OK
 | 
			
		||||
              <command>find</command>
 | 
			
		||||
              <arg choice="req"><replaceable>TYPE</replaceable></arg>
 | 
			
		||||
              <arg choice="req"><replaceable>WHAT</replaceable></arg>
 | 
			
		||||
              <arg choice="opt"><replaceable>...</replaceable></arg>
 | 
			
		||||
            </cmdsynopsis>
 | 
			
		||||
          </term>
 | 
			
		||||
          <listitem>
 | 
			
		||||
            <para>
 | 
			
		||||
              Finds songs in the db that are exactly
 | 
			
		||||
              <varname>WHAT</varname>.  <varname>TYPE</varname> should
 | 
			
		||||
              be <parameter>album</parameter>,
 | 
			
		||||
              <parameter>artist</parameter>, or
 | 
			
		||||
              <parameter>title</parameter>.  <varname>WHAT</varname>
 | 
			
		||||
              is what to find.
 | 
			
		||||
              <varname>WHAT</varname>.  <varname>TYPE</varname> can
 | 
			
		||||
              be any tag supported by MPD, or one of the two special
 | 
			
		||||
              parameters — <parameter>file</parameter> to search by
 | 
			
		||||
              full path (relative to database root), and
 | 
			
		||||
              <parameter>any</parameter> to match against all
 | 
			
		||||
              available tags.  <varname>WHAT</varname> is what to find.
 | 
			
		||||
            </para>
 | 
			
		||||
          </listitem>
 | 
			
		||||
        </varlistentry>
 | 
			
		||||
@@ -1227,14 +1235,14 @@ OK
 | 
			
		||||
              <command>findadd</command>
 | 
			
		||||
              <arg choice="req"><replaceable>TYPE</replaceable></arg>
 | 
			
		||||
              <arg choice="req"><replaceable>WHAT</replaceable></arg>
 | 
			
		||||
              <arg choice="opt"><replaceable>...</replaceable></arg>
 | 
			
		||||
            </cmdsynopsis>
 | 
			
		||||
          </term>
 | 
			
		||||
          <listitem>
 | 
			
		||||
            <para>
 | 
			
		||||
              Finds songs in the db that are exactly
 | 
			
		||||
              <varname>WHAT</varname> and adds them to current playlist.
 | 
			
		||||
              <varname>TYPE</varname> can be any tag supported by MPD.
 | 
			
		||||
              <varname>WHAT</varname> is what to find.
 | 
			
		||||
              Parameters have the same meaning as for <command>find</command>.
 | 
			
		||||
            </para>
 | 
			
		||||
          </listitem>
 | 
			
		||||
        </varlistentry>
 | 
			
		||||
@@ -1249,7 +1257,8 @@ OK
 | 
			
		||||
          <listitem>
 | 
			
		||||
            <para>
 | 
			
		||||
              Lists all tags of the specified type.
 | 
			
		||||
              <varname>TYPE</varname> should be album or artist.
 | 
			
		||||
              <varname>TYPE</varname> can be any tag supported by MPD or
 | 
			
		||||
              <parameter>file</parameter>.
 | 
			
		||||
            </para>
 | 
			
		||||
            <para>
 | 
			
		||||
              <varname>ARTIST</varname> is an optional parameter when
 | 
			
		||||
@@ -1312,17 +1321,15 @@ OK
 | 
			
		||||
              <command>search</command>
 | 
			
		||||
              <arg choice="req"><replaceable>TYPE</replaceable></arg>
 | 
			
		||||
              <arg choice="req"><replaceable>WHAT</replaceable></arg>
 | 
			
		||||
              <arg choice="opt"><replaceable>...</replaceable></arg>
 | 
			
		||||
            </cmdsynopsis>
 | 
			
		||||
          </term>
 | 
			
		||||
          <listitem>
 | 
			
		||||
            <para>
 | 
			
		||||
              Searches for any song that contains
 | 
			
		||||
              <varname>WHAT</varname>.  <varname>TYPE</varname> can be
 | 
			
		||||
              <parameter>title</parameter>,
 | 
			
		||||
              <parameter>artist</parameter>,
 | 
			
		||||
              <parameter>album</parameter> or
 | 
			
		||||
              <parameter>filename</parameter>.  Search is not case
 | 
			
		||||
              sensitive.
 | 
			
		||||
              <varname>WHAT</varname>. Parameters have the same meaning
 | 
			
		||||
              as for <command>find</command>, except that search is not
 | 
			
		||||
              case sensitive.
 | 
			
		||||
            </para>
 | 
			
		||||
          </listitem>
 | 
			
		||||
        </varlistentry>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										50
									
								
								doc/user.xml
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								doc/user.xml
									
									
									
									
									
								
							@@ -788,6 +788,43 @@ cd mpd-version</programlisting>
 | 
			
		||||
        </para>
 | 
			
		||||
      </section>
 | 
			
		||||
 | 
			
		||||
      <section>
 | 
			
		||||
        <title><varname>ffado</varname></title>
 | 
			
		||||
 | 
			
		||||
        <para>
 | 
			
		||||
          The <varname>ffado</varname> plugin connects to FireWire
 | 
			
		||||
          audio devices via <filename>libffado</filename>.
 | 
			
		||||
        </para>
 | 
			
		||||
 | 
			
		||||
        <para>
 | 
			
		||||
          Warning: this plugin was not tested successfully.  I just
 | 
			
		||||
          couldn't keep libffado2 from crashing.  Use at your own
 | 
			
		||||
          risk.
 | 
			
		||||
        </para>
 | 
			
		||||
 | 
			
		||||
        <informaltable>
 | 
			
		||||
          <tgroup cols="2">
 | 
			
		||||
            <thead>
 | 
			
		||||
              <row>
 | 
			
		||||
                <entry>Setting</entry>
 | 
			
		||||
                <entry>Description</entry>
 | 
			
		||||
              </row>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <row>
 | 
			
		||||
                <entry>
 | 
			
		||||
                  <varname>device</varname>
 | 
			
		||||
                  <parameter>NAME</parameter>
 | 
			
		||||
                </entry>
 | 
			
		||||
                <entry>
 | 
			
		||||
                  Sets the device which should be used, e.g. "hw:0".
 | 
			
		||||
                </entry>
 | 
			
		||||
              </row>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </tgroup>
 | 
			
		||||
        </informaltable>
 | 
			
		||||
      </section>
 | 
			
		||||
 | 
			
		||||
      <section>
 | 
			
		||||
        <title><varname>jack</varname></title>
 | 
			
		||||
 | 
			
		||||
@@ -914,8 +951,17 @@ cd mpd-version</programlisting>
 | 
			
		||||
                  <parameter>P</parameter>
 | 
			
		||||
                </entry>
 | 
			
		||||
                <entry>
 | 
			
		||||
                  Binds the HTTP server to the specified port (on all
 | 
			
		||||
                  interfaces).
 | 
			
		||||
                  Binds the HTTP server to the specified port.
 | 
			
		||||
                </entry>
 | 
			
		||||
              </row>
 | 
			
		||||
              <row>
 | 
			
		||||
                <entry>
 | 
			
		||||
                  <varname>bind_to_address</varname>
 | 
			
		||||
                  <parameter>ADDR</parameter>
 | 
			
		||||
                </entry>
 | 
			
		||||
                <entry>
 | 
			
		||||
                  Binds the HTTP server to the specified address (IPv4 or
 | 
			
		||||
                  IPv6). Multiple addresses in parallel are not supported.
 | 
			
		||||
                </entry>
 | 
			
		||||
              </row>
 | 
			
		||||
              <row>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								m4/faad.m4
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								m4/faad.m4
									
									
									
									
									
								
							@@ -58,7 +58,7 @@ if test x$enable_aac = xyes; then
 | 
			
		||||
	fi
 | 
			
		||||
	if test x$enable_aac = xyes; then
 | 
			
		||||
  		AC_MSG_CHECKING(that FAAD2 uses buffer and bufferlen)
 | 
			
		||||
		AC_COMPILE_IFELSE([
 | 
			
		||||
		AC_COMPILE_IFELSE([AC_LANG_SOURCE([
 | 
			
		||||
#include <faad.h>
 | 
			
		||||
 | 
			
		||||
int main() {
 | 
			
		||||
@@ -82,9 +82,9 @@ int main() {
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no);
 | 
			
		||||
])],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no);
 | 
			
		||||
		AC_MSG_CHECKING(that FAAD2 can even be used)
 | 
			
		||||
		AC_COMPILE_IFELSE([
 | 
			
		||||
		AC_COMPILE_IFELSE([AC_LANG_SOURCE([
 | 
			
		||||
#include <faad.h>
 | 
			
		||||
 | 
			
		||||
int main() {
 | 
			
		||||
@@ -113,7 +113,7 @@ int main() {
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no])
 | 
			
		||||
])],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no])
 | 
			
		||||
		])
 | 
			
		||||
	fi
 | 
			
		||||
	if test x$enable_aac = xyes; then
 | 
			
		||||
@@ -136,7 +136,7 @@ if test x$enable_aac = xyes; then
 | 
			
		||||
	CPPFLAGS=$CFLAGS
 | 
			
		||||
 | 
			
		||||
	AC_MSG_CHECKING(for broken libfaad headers)
 | 
			
		||||
	AC_COMPILE_IFELSE([
 | 
			
		||||
	AC_COMPILE_IFELSE([AC_LANG_SOURCE([
 | 
			
		||||
#include <faad.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
@@ -148,7 +148,7 @@ int main() {
 | 
			
		||||
	faacDecInit2(NULL, NULL, 0, &sample_rate, &channels);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
	],
 | 
			
		||||
	])],
 | 
			
		||||
		[AC_MSG_RESULT(correct)],
 | 
			
		||||
		[AC_MSG_RESULT(broken);
 | 
			
		||||
		AC_DEFINE(HAVE_FAAD_LONG, 1, [Define if faad.h uses the broken "unsigned long" pointers])])
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,9 @@ AC_DEFUN([MPD_CHECK_FLAG],[
 | 
			
		||||
    [mpd_check_cflag_$var],[
 | 
			
		||||
    save_CFLAGS="$CFLAGS"
 | 
			
		||||
    CFLAGS="$CFLAGS $1"
 | 
			
		||||
    AC_COMPILE_IFELSE([
 | 
			
		||||
    AC_COMPILE_IFELSE([AC_LANG_SOURCE([
 | 
			
		||||
     int main(void) { return 0; }
 | 
			
		||||
     ], [ eval "mpd_check_cflag_$var=yes"
 | 
			
		||||
     ])], [ eval "mpd_check_cflag_$var=yes"
 | 
			
		||||
     ], [ eval "mpd_check_cflag_$var=no" ])
 | 
			
		||||
    CFLAGS="$save_CFLAGS"
 | 
			
		||||
  ])
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,19 @@
 | 
			
		||||
AC_DEFUN([results], [
 | 
			
		||||
	dnl This is a hack to allow "with" names, otherwise "enable".
 | 
			
		||||
	num=`expr match $1 'with'`
 | 
			
		||||
	num=`expr $1 : 'with'`
 | 
			
		||||
	if test "$num" != "0"; then
 | 
			
		||||
		var="`echo '$'$1`"
 | 
			
		||||
	else
 | 
			
		||||
		var="`echo '$'enable_$1`"
 | 
			
		||||
	fi
 | 
			
		||||
 | 
			
		||||
	echo -n '('
 | 
			
		||||
	printf '('
 | 
			
		||||
	if eval "test x$var = xyes"; then
 | 
			
		||||
		echo -n '+'
 | 
			
		||||
		printf '+'
 | 
			
		||||
	elif test -n "$3" && eval "test x$var = x$3"; then
 | 
			
		||||
		echo -n '+' 
 | 
			
		||||
		printf '+'
 | 
			
		||||
	else
 | 
			
		||||
		echo -n '-'
 | 
			
		||||
		printf '-'
 | 
			
		||||
	fi
 | 
			
		||||
	echo -n "$2) "
 | 
			
		||||
	printf '%s) ' "$2"
 | 
			
		||||
])
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										113
									
								
								src/ape.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/ape.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "ape.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
struct ape_footer {
 | 
			
		||||
	unsigned char id[8];
 | 
			
		||||
	uint32_t version;
 | 
			
		||||
	uint32_t length;
 | 
			
		||||
	uint32_t count;
 | 
			
		||||
	unsigned char flags[4];
 | 
			
		||||
	unsigned char reserved[8];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
ape_scan_internal(FILE *fp, tag_ape_callback_t callback, void *ctx)
 | 
			
		||||
{
 | 
			
		||||
	/* determine if file has an apeV2 tag */
 | 
			
		||||
	struct ape_footer footer;
 | 
			
		||||
	if (fseek(fp, -(long)sizeof(footer), SEEK_END) ||
 | 
			
		||||
	    fread(&footer, 1, sizeof(footer), fp) != sizeof(footer) ||
 | 
			
		||||
	    memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0 ||
 | 
			
		||||
	    GUINT32_FROM_LE(footer.version) != 2000)
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	/* find beginning of ape tag */
 | 
			
		||||
	size_t remaining = GUINT32_FROM_LE(footer.length);
 | 
			
		||||
	if (remaining <= sizeof(footer) + 10 ||
 | 
			
		||||
	    /* refuse to load more than one megabyte of tag data */
 | 
			
		||||
	    remaining > 1024 * 1024 ||
 | 
			
		||||
	    fseek(fp, -(long)remaining, SEEK_END))
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	/* read tag into buffer */
 | 
			
		||||
	remaining -= sizeof(footer);
 | 
			
		||||
	assert(remaining > 10);
 | 
			
		||||
 | 
			
		||||
	char *buffer = g_malloc(remaining);
 | 
			
		||||
	if (fread(buffer, 1, remaining, fp) != remaining)
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	/* read tags */
 | 
			
		||||
	unsigned n = GUINT32_FROM_LE(footer.count);
 | 
			
		||||
	const char *p = buffer;
 | 
			
		||||
	while (n-- && remaining > 10) {
 | 
			
		||||
		size_t size = GUINT32_FROM_LE(*(const uint32_t *)p);
 | 
			
		||||
		p += 4;
 | 
			
		||||
		remaining -= 4;
 | 
			
		||||
		unsigned long flags = GUINT32_FROM_LE(*(const uint32_t *)p);
 | 
			
		||||
		p += 4;
 | 
			
		||||
		remaining -= 4;
 | 
			
		||||
 | 
			
		||||
		/* get the key */
 | 
			
		||||
		const char *key = p;
 | 
			
		||||
		while (remaining > size && *p != '\0') {
 | 
			
		||||
			p++;
 | 
			
		||||
			remaining--;
 | 
			
		||||
		}
 | 
			
		||||
		p++;
 | 
			
		||||
		remaining--;
 | 
			
		||||
 | 
			
		||||
		/* get the value */
 | 
			
		||||
		if (remaining < size)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		if (!callback(flags, key, p, size, ctx))
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		p += size;
 | 
			
		||||
		remaining -= size;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g_free(buffer);
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx)
 | 
			
		||||
{
 | 
			
		||||
	FILE *fp;
 | 
			
		||||
 | 
			
		||||
	fp = fopen(path_fs, "rb");
 | 
			
		||||
	if (fp == NULL)
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	bool success = ape_scan_internal(fp, callback, ctx);
 | 
			
		||||
	fclose(fp);
 | 
			
		||||
	return success;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								src/ape.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/ape.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef MPD_APE_H
 | 
			
		||||
#define MPD_APE_H
 | 
			
		||||
 | 
			
		||||
#include "check.h"
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
 | 
			
		||||
typedef bool (*tag_ape_callback_t)(unsigned long flags, const char *key,
 | 
			
		||||
				   const char *value, size_t value_length,
 | 
			
		||||
				   void *ctx);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Scans the APE tag values from a file.
 | 
			
		||||
 *
 | 
			
		||||
 * @param path_fs the path of the file in filesystem encoding
 | 
			
		||||
 * @return false if the file could not be opened or if no APE tag is
 | 
			
		||||
 * present
 | 
			
		||||
 */
 | 
			
		||||
bool
 | 
			
		||||
tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -25,6 +25,7 @@
 | 
			
		||||
#include "output_plugin.h"
 | 
			
		||||
#include "output_all.h"
 | 
			
		||||
#include "conf.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
@@ -52,6 +53,7 @@ void initAudioConfig(void)
 | 
			
		||||
	ret = audio_format_parse(&configured_audio_format, param->value,
 | 
			
		||||
				 true, &error);
 | 
			
		||||
	if (!ret)
 | 
			
		||||
		g_error("error parsing \"%s\" at line %i: %s",
 | 
			
		||||
			CONF_AUDIO_OUTPUT_FORMAT, param->line, error->message);
 | 
			
		||||
		MPD_ERROR("error parsing \"%s\" at line %i: %s",
 | 
			
		||||
			  CONF_AUDIO_OUTPUT_FORMAT, param->line,
 | 
			
		||||
			  error->message);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ bool
 | 
			
		||||
audio_check_sample_rate(unsigned long sample_rate, GError **error_r);
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
audio_check_sample_format(unsigned sample_format, GError **error_r);
 | 
			
		||||
audio_check_sample_format(enum sample_format, GError **error_r);
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
audio_check_channel_count(unsigned sample_format, GError **error_r);
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@
 | 
			
		||||
#include "decoder_plugin.h"
 | 
			
		||||
#include "output_list.h"
 | 
			
		||||
#include "ls.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_ENCODER
 | 
			
		||||
#include "encoder_list.h"
 | 
			
		||||
@@ -155,10 +156,8 @@ parse_cmdline(int argc, char **argv, struct options *options,
 | 
			
		||||
	ret = g_option_context_parse(context, &argc, &argv, &error);
 | 
			
		||||
	g_option_context_free(context);
 | 
			
		||||
 | 
			
		||||
	if (!ret) {
 | 
			
		||||
		g_error("option parsing failed: %s\n", error->message);
 | 
			
		||||
		exit(1);
 | 
			
		||||
	}
 | 
			
		||||
	if (!ret)
 | 
			
		||||
		MPD_ERROR("option parsing failed: %s\n", error->message);
 | 
			
		||||
 | 
			
		||||
	if (option_version)
 | 
			
		||||
		version();
 | 
			
		||||
 
 | 
			
		||||
@@ -1715,15 +1715,11 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		sticker = sticker_song_get(song);
 | 
			
		||||
		if (NULL == sticker) {
 | 
			
		||||
			command_error(client, ACK_ERROR_NO_EXIST,
 | 
			
		||||
				      "no stickers found");
 | 
			
		||||
			return COMMAND_RETURN_ERROR;
 | 
			
		||||
		if (sticker) {
 | 
			
		||||
			sticker_print(client, sticker);
 | 
			
		||||
			sticker_free(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) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								src/conf.c
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								src/conf.c
									
									
									
									
									
								
							@@ -23,6 +23,7 @@
 | 
			
		||||
#include "tokenizer.h"
 | 
			
		||||
#include "path.h"
 | 
			
		||||
#include "glib_compat.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
@@ -498,8 +499,8 @@ config_get_path(const char *name)
 | 
			
		||||
 | 
			
		||||
	path = parsePath(param->value);
 | 
			
		||||
	if (path == NULL)
 | 
			
		||||
		g_error("error parsing \"%s\" at line %i\n",
 | 
			
		||||
			name, param->line);
 | 
			
		||||
		MPD_ERROR("error parsing \"%s\" at line %i\n",
 | 
			
		||||
			  name, param->line);
 | 
			
		||||
 | 
			
		||||
	g_free(param->value);
 | 
			
		||||
	return param->value = path;
 | 
			
		||||
@@ -517,7 +518,8 @@ config_get_unsigned(const char *name, unsigned default_value)
 | 
			
		||||
 | 
			
		||||
	value = strtol(param->value, &endptr, 0);
 | 
			
		||||
	if (*endptr != 0 || value < 0)
 | 
			
		||||
		g_error("Not a valid non-negative number in line %i", param->line);
 | 
			
		||||
		MPD_ERROR("Not a valid non-negative number in line %i",
 | 
			
		||||
			  param->line);
 | 
			
		||||
 | 
			
		||||
	return (unsigned)value;
 | 
			
		||||
}
 | 
			
		||||
@@ -534,10 +536,10 @@ config_get_positive(const char *name, unsigned default_value)
 | 
			
		||||
 | 
			
		||||
	value = strtol(param->value, &endptr, 0);
 | 
			
		||||
	if (*endptr != 0)
 | 
			
		||||
		g_error("Not a valid number in line %i", param->line);
 | 
			
		||||
		MPD_ERROR("Not a valid number in line %i", param->line);
 | 
			
		||||
 | 
			
		||||
	if (value <= 0)
 | 
			
		||||
		g_error("Not a positive number in line %i", param->line);
 | 
			
		||||
		MPD_ERROR("Not a positive number in line %i", param->line);
 | 
			
		||||
 | 
			
		||||
	return (unsigned)value;
 | 
			
		||||
}
 | 
			
		||||
@@ -569,9 +571,9 @@ bool config_get_bool(const char *name, bool default_value)
 | 
			
		||||
 | 
			
		||||
	success = get_bool(param->value, &value);
 | 
			
		||||
	if (!success)
 | 
			
		||||
		g_error("%s is not a boolean value (yes, true, 1) or "
 | 
			
		||||
			"(no, false, 0) on line %i\n",
 | 
			
		||||
			name, param->line);
 | 
			
		||||
		MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
 | 
			
		||||
			  "(no, false, 0) on line %i\n",
 | 
			
		||||
			  name, param->line);
 | 
			
		||||
 | 
			
		||||
	return value;
 | 
			
		||||
}
 | 
			
		||||
@@ -601,10 +603,10 @@ config_get_block_unsigned(const struct config_param *param, const char *name,
 | 
			
		||||
 | 
			
		||||
	value = strtol(bp->value, &endptr, 0);
 | 
			
		||||
	if (*endptr != 0)
 | 
			
		||||
		g_error("Not a valid number in line %i", bp->line);
 | 
			
		||||
		MPD_ERROR("Not a valid number in line %i", bp->line);
 | 
			
		||||
 | 
			
		||||
	if (value < 0)
 | 
			
		||||
		g_error("Not a positive number in line %i", bp->line);
 | 
			
		||||
		MPD_ERROR("Not a positive number in line %i", bp->line);
 | 
			
		||||
 | 
			
		||||
	return (unsigned)value;
 | 
			
		||||
}
 | 
			
		||||
@@ -621,9 +623,9 @@ config_get_block_bool(const struct config_param *param, const char *name,
 | 
			
		||||
 | 
			
		||||
	success = get_bool(bp->value, &value);
 | 
			
		||||
	if (!success)
 | 
			
		||||
		g_error("%s is not a boolean value (yes, true, 1) or "
 | 
			
		||||
			"(no, false, 0) on line %i\n",
 | 
			
		||||
			name, bp->line);
 | 
			
		||||
		MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
 | 
			
		||||
			  "(no, false, 0) on line %i\n",
 | 
			
		||||
			  name, bp->line);
 | 
			
		||||
 | 
			
		||||
	return value;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								src/conf.h
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/conf.h
									
									
									
									
									
								
							@@ -28,7 +28,7 @@
 | 
			
		||||
#define CONF_FOLLOW_INSIDE_SYMLINKS     "follow_inside_symlinks"
 | 
			
		||||
#define CONF_FOLLOW_OUTSIDE_SYMLINKS    "follow_outside_symlinks"
 | 
			
		||||
#define CONF_DB_FILE                    "db_file"
 | 
			
		||||
#define CONF_STICKER_FILE "sticker_file"
 | 
			
		||||
#define CONF_STICKER_FILE               "sticker_file"
 | 
			
		||||
#define CONF_LOG_FILE                   "log_file"
 | 
			
		||||
#define CONF_PID_FILE                   "pid_file"
 | 
			
		||||
#define CONF_STATE_FILE                 "state_file"
 | 
			
		||||
@@ -38,7 +38,7 @@
 | 
			
		||||
#define CONF_PORT                       "port"
 | 
			
		||||
#define CONF_LOG_LEVEL                  "log_level"
 | 
			
		||||
#define CONF_ZEROCONF_NAME              "zeroconf_name"
 | 
			
		||||
#define CONF_ZEROCONF_ENABLED			"zeroconf_enabled"
 | 
			
		||||
#define CONF_ZEROCONF_ENABLED           "zeroconf_enabled"
 | 
			
		||||
#define CONF_PASSWORD                   "password"
 | 
			
		||||
#define CONF_DEFAULT_PERMS              "default_permissions"
 | 
			
		||||
#define CONF_AUDIO_OUTPUT               "audio_output"
 | 
			
		||||
@@ -48,7 +48,7 @@
 | 
			
		||||
#define CONF_REPLAYGAIN                 "replaygain"
 | 
			
		||||
#define CONF_REPLAYGAIN_PREAMP          "replaygain_preamp"
 | 
			
		||||
#define CONF_REPLAYGAIN_MISSING_PREAMP  "replaygain_missing_preamp"
 | 
			
		||||
#define CONF_REPLAYGAIN_LIMIT			"replaygain_limit"
 | 
			
		||||
#define CONF_REPLAYGAIN_LIMIT           "replaygain_limit"
 | 
			
		||||
#define CONF_VOLUME_NORMALIZATION       "volume_normalization"
 | 
			
		||||
#define CONF_SAMPLERATE_CONVERTER       "samplerate_converter"
 | 
			
		||||
#define CONF_AUDIO_BUFFER_SIZE          "audio_buffer_size"
 | 
			
		||||
@@ -66,12 +66,12 @@
 | 
			
		||||
#define CONF_ID3V1_ENCODING             "id3v1_encoding"
 | 
			
		||||
#define CONF_METADATA_TO_USE            "metadata_to_use"
 | 
			
		||||
#define CONF_SAVE_ABSOLUTE_PATHS        "save_absolute_paths_in_playlists"
 | 
			
		||||
#define CONF_DECODER "decoder"
 | 
			
		||||
#define CONF_INPUT "input"
 | 
			
		||||
#define CONF_GAPLESS_MP3_PLAYBACK	"gapless_mp3_playback"
 | 
			
		||||
#define CONF_PLAYLIST_PLUGIN "playlist_plugin"
 | 
			
		||||
#define CONF_AUTO_UPDATE		"auto_update"
 | 
			
		||||
#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth"
 | 
			
		||||
#define CONF_DECODER                    "decoder"
 | 
			
		||||
#define CONF_INPUT                      "input"
 | 
			
		||||
#define CONF_GAPLESS_MP3_PLAYBACK       "gapless_mp3_playback"
 | 
			
		||||
#define CONF_PLAYLIST_PLUGIN            "playlist_plugin"
 | 
			
		||||
#define CONF_AUTO_UPDATE                "auto_update"
 | 
			
		||||
#define CONF_AUTO_UPDATE_DEPTH          "auto_update_depth"
 | 
			
		||||
 | 
			
		||||
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
 | 
			
		||||
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								src/daemon.c
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								src/daemon.c
									
									
									
									
									
								
							@@ -65,23 +65,23 @@ daemonize_kill(void)
 | 
			
		||||
	int pid, ret;
 | 
			
		||||
 | 
			
		||||
	if (pidfile == NULL)
 | 
			
		||||
		g_error("no pid_file specified in the config file");
 | 
			
		||||
		MPD_ERROR("no pid_file specified in the config file");
 | 
			
		||||
 | 
			
		||||
	fp = fopen(pidfile, "r");
 | 
			
		||||
	if (fp == NULL)
 | 
			
		||||
		g_error("unable to open pid file \"%s\": %s",
 | 
			
		||||
			pidfile, g_strerror(errno));
 | 
			
		||||
		MPD_ERROR("unable to open pid file \"%s\": %s",
 | 
			
		||||
			  pidfile, g_strerror(errno));
 | 
			
		||||
 | 
			
		||||
	if (fscanf(fp, "%i", &pid) != 1) {
 | 
			
		||||
		g_error("unable to read the pid from file \"%s\"",
 | 
			
		||||
			pidfile);
 | 
			
		||||
		MPD_ERROR("unable to read the pid from file \"%s\"",
 | 
			
		||||
			  pidfile);
 | 
			
		||||
	}
 | 
			
		||||
	fclose(fp);
 | 
			
		||||
 | 
			
		||||
	ret = kill(pid, SIGTERM);
 | 
			
		||||
	if (ret < 0)
 | 
			
		||||
		g_error("unable to kill proccess %i: %s",
 | 
			
		||||
			pid, g_strerror(errno));
 | 
			
		||||
		MPD_ERROR("unable to kill proccess %i: %s",
 | 
			
		||||
			  pid, g_strerror(errno));
 | 
			
		||||
 | 
			
		||||
	exit(EXIT_SUCCESS);
 | 
			
		||||
}
 | 
			
		||||
@@ -102,8 +102,8 @@ daemonize_set_user(void)
 | 
			
		||||
	/* set gid */
 | 
			
		||||
	if (user_gid != (gid_t)-1 && user_gid != getgid()) {
 | 
			
		||||
		if (setgid(user_gid) == -1) {
 | 
			
		||||
			g_error("cannot setgid to %d: %s",
 | 
			
		||||
				(int)user_gid, g_strerror(errno));
 | 
			
		||||
			MPD_ERROR("cannot setgid to %d: %s",
 | 
			
		||||
				  (int)user_gid, g_strerror(errno));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -121,8 +121,8 @@ daemonize_set_user(void)
 | 
			
		||||
	/* set uid */
 | 
			
		||||
	if (user_uid != (uid_t)-1 && user_uid != getuid() &&
 | 
			
		||||
	    setuid(user_uid) == -1) {
 | 
			
		||||
		g_error("cannot change to uid of user \"%s\": %s",
 | 
			
		||||
			user_name, g_strerror(errno));
 | 
			
		||||
		MPD_ERROR("cannot change to uid of user \"%s\": %s",
 | 
			
		||||
			  user_name, g_strerror(errno));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -136,7 +136,7 @@ daemonize_detach(void)
 | 
			
		||||
#ifdef HAVE_DAEMON
 | 
			
		||||
 | 
			
		||||
	if (daemon(0, 1))
 | 
			
		||||
		g_error("daemon() failed: %s", g_strerror(errno));
 | 
			
		||||
		MPD_ERROR("daemon() failed: %s", g_strerror(errno));
 | 
			
		||||
 | 
			
		||||
#elif defined(HAVE_FORK)
 | 
			
		||||
 | 
			
		||||
@@ -144,7 +144,7 @@ daemonize_detach(void)
 | 
			
		||||
 | 
			
		||||
	switch (fork()) {
 | 
			
		||||
	case -1:
 | 
			
		||||
		g_error("fork() failed: %s", g_strerror(errno));
 | 
			
		||||
		MPD_ERROR("fork() failed: %s", g_strerror(errno));
 | 
			
		||||
	case 0:
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
@@ -155,14 +155,14 @@ daemonize_detach(void)
 | 
			
		||||
	/* release the current working directory */
 | 
			
		||||
 | 
			
		||||
	if (chdir("/") < 0)
 | 
			
		||||
		g_error("problems changing to root directory");
 | 
			
		||||
		MPD_ERROR("problems changing to root directory");
 | 
			
		||||
 | 
			
		||||
	/* detach from the current session */
 | 
			
		||||
 | 
			
		||||
	setsid();
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
	g_error("no support for daemonizing");
 | 
			
		||||
	MPD_ERROR("no support for daemonizing");
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	g_debug("daemonized!");
 | 
			
		||||
@@ -179,8 +179,8 @@ daemonize(bool detach)
 | 
			
		||||
		g_debug("opening pid file");
 | 
			
		||||
		fp = fopen(pidfile, "w+");
 | 
			
		||||
		if (!fp) {
 | 
			
		||||
			g_error("could not create pid file \"%s\": %s",
 | 
			
		||||
				pidfile, g_strerror(errno));
 | 
			
		||||
			MPD_ERROR("could not create pid file \"%s\": %s",
 | 
			
		||||
				  pidfile, g_strerror(errno));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -200,7 +200,7 @@ daemonize_init(const char *user, const char *group, const char *_pidfile)
 | 
			
		||||
	if (user) {
 | 
			
		||||
		struct passwd *pwd = getpwnam(user);
 | 
			
		||||
		if (!pwd)
 | 
			
		||||
			g_error("no such user \"%s\"", user);
 | 
			
		||||
			MPD_ERROR("no such user \"%s\"", user);
 | 
			
		||||
 | 
			
		||||
		user_uid = pwd->pw_uid;
 | 
			
		||||
		user_gid = pwd->pw_gid;
 | 
			
		||||
@@ -214,7 +214,7 @@ daemonize_init(const char *user, const char *group, const char *_pidfile)
 | 
			
		||||
	if (group) {
 | 
			
		||||
		struct group *grp = grp = getgrnam(group);
 | 
			
		||||
		if (!grp)
 | 
			
		||||
			g_error("no such group \"%s\"", group);
 | 
			
		||||
			MPD_ERROR("no such group \"%s\"", group);
 | 
			
		||||
		user_gid = grp->gr_gid;
 | 
			
		||||
		had_group = true;
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,8 @@
 | 
			
		||||
#ifndef DAEMON_H
 | 
			
		||||
#define DAEMON_H
 | 
			
		||||
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#ifndef WIN32
 | 
			
		||||
@@ -51,7 +53,7 @@ daemonize_kill(void);
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
static inline void
 | 
			
		||||
daemonize_kill(void)
 | 
			
		||||
{ g_error("--kill is not available on WIN32"); }
 | 
			
		||||
{ MPD_ERROR("--kill is not available on WIN32"); }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -231,16 +231,18 @@ static enum sample_format
 | 
			
		||||
ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
 | 
			
		||||
{
 | 
			
		||||
#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
 | 
			
		||||
	int bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
 | 
			
		||||
 | 
			
		||||
	/* XXX implement & test other sample formats */
 | 
			
		||||
 | 
			
		||||
	switch (bits) {
 | 
			
		||||
	case 16:
 | 
			
		||||
	switch (codec_context->sample_fmt) {
 | 
			
		||||
	case SAMPLE_FMT_S16:
 | 
			
		||||
		return SAMPLE_FORMAT_S16;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return SAMPLE_FORMAT_UNDEFINED;
 | 
			
		||||
	case SAMPLE_FMT_S32:
 | 
			
		||||
		return SAMPLE_FORMAT_S32;
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		g_warning("Unsupported libavcodec SampleFormat value: %d",
 | 
			
		||||
			  codec_context->sample_fmt);
 | 
			
		||||
		return SAMPLE_FORMAT_UNDEFINED;
 | 
			
		||||
	}
 | 
			
		||||
#else
 | 
			
		||||
	/* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */
 | 
			
		||||
	return SAMPLE_FORMAT_S16;
 | 
			
		||||
@@ -522,7 +524,9 @@ static const char *const ffmpeg_suffixes[] = {
 | 
			
		||||
	"atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak",
 | 
			
		||||
	"cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa",
 | 
			
		||||
	"eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726",
 | 
			
		||||
	"gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts", "m4a", "m4v", "mad",
 | 
			
		||||
	"gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts",
 | 
			
		||||
	"m4a", "m4b", "m4v",
 | 
			
		||||
	"mad",
 | 
			
		||||
	"mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+",
 | 
			
		||||
	"mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu",
 | 
			
		||||
	"mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv",
 | 
			
		||||
 
 | 
			
		||||
@@ -87,7 +87,7 @@ flac_find_string_comment(const FLAC__StreamMetadata *block,
 | 
			
		||||
	int offset;
 | 
			
		||||
	size_t pos;
 | 
			
		||||
	int len;
 | 
			
		||||
	unsigned char tmp, *p;
 | 
			
		||||
	const unsigned char *p;
 | 
			
		||||
 | 
			
		||||
	*str = NULL;
 | 
			
		||||
	offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
 | 
			
		||||
@@ -101,10 +101,7 @@ flac_find_string_comment(const FLAC__StreamMetadata *block,
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	p = &block->data.vorbis_comment.comments[offset].entry[pos];
 | 
			
		||||
	tmp = p[len];
 | 
			
		||||
	p[len] = '\0';
 | 
			
		||||
	*str = strdup((char *)p);
 | 
			
		||||
	p[len] = tmp;
 | 
			
		||||
	*str = g_strndup((const char *)p, len);
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,21 @@
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "../decoder_api.h"
 | 
			
		||||
#include "audio_check.h"
 | 
			
		||||
#include "uri.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <gme/gme.h>
 | 
			
		||||
 | 
			
		||||
#undef G_LOG_DOMAIN
 | 
			
		||||
#define G_LOG_DOMAIN "gme"
 | 
			
		||||
 | 
			
		||||
#define SUBTUNE_PREFIX "tune_"
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
	GME_SAMPLE_RATE = 44100,
 | 
			
		||||
	GME_CHANNELS = 2,
 | 
			
		||||
@@ -15,10 +23,91 @@ enum {
 | 
			
		||||
	GME_BUFFER_SAMPLES = GME_BUFFER_FRAMES * GME_CHANNELS,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * returns the file path stripped of any /tune_xxx.* subtune
 | 
			
		||||
 * suffix
 | 
			
		||||
 */
 | 
			
		||||
static char *
 | 
			
		||||
get_container_name(const char *path_fs)
 | 
			
		||||
{
 | 
			
		||||
	const char *subtune_suffix = uri_get_suffix(path_fs);
 | 
			
		||||
	char *path_container = g_strdup(path_fs);
 | 
			
		||||
	char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.", subtune_suffix, NULL);
 | 
			
		||||
	GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
 | 
			
		||||
	g_free(pat);
 | 
			
		||||
	if (!g_pattern_match(path_with_subtune,
 | 
			
		||||
			     strlen(path_container), path_container, NULL)) {
 | 
			
		||||
		g_pattern_spec_free(path_with_subtune);
 | 
			
		||||
		return path_container;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX);
 | 
			
		||||
	if (ptr != NULL)
 | 
			
		||||
		*ptr='\0';
 | 
			
		||||
 | 
			
		||||
	g_pattern_spec_free(path_with_subtune);
 | 
			
		||||
	return path_container;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune
 | 
			
		||||
 * is appended.
 | 
			
		||||
 */
 | 
			
		||||
static int
 | 
			
		||||
get_song_num(const char *path_fs)
 | 
			
		||||
{
 | 
			
		||||
	const char *subtune_suffix = uri_get_suffix(path_fs);
 | 
			
		||||
	char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.", subtune_suffix, NULL);
 | 
			
		||||
	GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
 | 
			
		||||
	g_free(pat);
 | 
			
		||||
 | 
			
		||||
	if (g_pattern_match(path_with_subtune,
 | 
			
		||||
			    strlen(path_fs), path_fs, NULL)) {
 | 
			
		||||
		char *sub = g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
 | 
			
		||||
		g_pattern_spec_free(path_with_subtune);
 | 
			
		||||
		if(!sub)
 | 
			
		||||
			return 0;
 | 
			
		||||
 | 
			
		||||
		sub += strlen("/" SUBTUNE_PREFIX);
 | 
			
		||||
		int song_num = strtol(sub, NULL, 10);
 | 
			
		||||
 | 
			
		||||
		return song_num - 1;
 | 
			
		||||
	} else {
 | 
			
		||||
		g_pattern_spec_free(path_with_subtune);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *
 | 
			
		||||
gme_container_scan(const char *path_fs, const unsigned int tnum)
 | 
			
		||||
{
 | 
			
		||||
	Music_Emu *emu;
 | 
			
		||||
	const char* gme_err;
 | 
			
		||||
	unsigned int num_songs;
 | 
			
		||||
 | 
			
		||||
	gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
 | 
			
		||||
	if (gme_err != NULL) {
 | 
			
		||||
		g_warning("%s", gme_err);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	num_songs = gme_track_count(emu);
 | 
			
		||||
	/* if it only contains a single tune, don't treat as container */
 | 
			
		||||
	if (num_songs < 2)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	const char *subtune_suffix = uri_get_suffix(path_fs);
 | 
			
		||||
	if (tnum <= num_songs){
 | 
			
		||||
		char *subtune = g_strdup_printf(
 | 
			
		||||
			SUBTUNE_PREFIX "%03u.%s", tnum, subtune_suffix);
 | 
			
		||||
		return subtune;
 | 
			
		||||
	} else
 | 
			
		||||
		return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
gme_file_decode(struct decoder *decoder, const char *path_fs)
 | 
			
		||||
{
 | 
			
		||||
	int track = 0; /* index of track to play */
 | 
			
		||||
	float song_len;
 | 
			
		||||
	Music_Emu *emu;
 | 
			
		||||
	gme_info_t *ti;
 | 
			
		||||
@@ -26,13 +115,17 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
 | 
			
		||||
	enum decoder_command cmd;
 | 
			
		||||
	short buf[GME_BUFFER_SAMPLES];
 | 
			
		||||
	const char* gme_err;
 | 
			
		||||
	char *path_container = get_container_name(path_fs);
 | 
			
		||||
	int song_num = get_song_num(path_fs);
 | 
			
		||||
 | 
			
		||||
	gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
 | 
			
		||||
	gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
 | 
			
		||||
	g_free(path_container);
 | 
			
		||||
	if (gme_err != NULL) {
 | 
			
		||||
		g_warning("%s", gme_err);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	if((gme_err = gme_track_info(emu, &ti, 0)) != NULL){
 | 
			
		||||
 | 
			
		||||
	if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){
 | 
			
		||||
		g_warning("%s", gme_err);
 | 
			
		||||
		gme_delete(emu);
 | 
			
		||||
		return;
 | 
			
		||||
@@ -57,7 +150,7 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
 | 
			
		||||
 | 
			
		||||
	decoder_initialized(decoder, &audio_format, true, song_len);
 | 
			
		||||
 | 
			
		||||
	if((gme_err = gme_start_track(emu, track)) != NULL)
 | 
			
		||||
	if((gme_err = gme_start_track(emu, song_num)) != NULL)
 | 
			
		||||
		g_warning("%s", gme_err);
 | 
			
		||||
 | 
			
		||||
	/* play */
 | 
			
		||||
@@ -90,13 +183,17 @@ gme_tag_dup(const char *path_fs)
 | 
			
		||||
	Music_Emu *emu;
 | 
			
		||||
	gme_info_t *ti;
 | 
			
		||||
	const char* gme_err;
 | 
			
		||||
	char *path_container=get_container_name(path_fs);
 | 
			
		||||
	int song_num;
 | 
			
		||||
	song_num=get_song_num(path_fs);
 | 
			
		||||
 | 
			
		||||
	gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
 | 
			
		||||
	gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
 | 
			
		||||
	g_free(path_container);
 | 
			
		||||
	if (gme_err != NULL) {
 | 
			
		||||
		g_warning("%s", gme_err);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
	if((gme_err = gme_track_info(emu, &ti, 0)) != NULL){
 | 
			
		||||
	if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){
 | 
			
		||||
		g_warning("%s", gme_err);
 | 
			
		||||
		gme_delete(emu);
 | 
			
		||||
		return NULL;
 | 
			
		||||
@@ -106,8 +203,16 @@ gme_tag_dup(const char *path_fs)
 | 
			
		||||
	if(ti != NULL){
 | 
			
		||||
		if(ti->length > 0)
 | 
			
		||||
			tag->time = ti->length / 1000;
 | 
			
		||||
		if(ti->song != NULL)
 | 
			
		||||
			tag_add_item(tag, TAG_TITLE, ti->song);
 | 
			
		||||
		if(ti->song != NULL){
 | 
			
		||||
			if(gme_track_count(emu) > 1){
 | 
			
		||||
				/* start numbering subtunes from 1 */
 | 
			
		||||
				char *tag_title=g_strdup_printf("%s (%d/%d)",
 | 
			
		||||
					ti->song, song_num+1, gme_track_count(emu));
 | 
			
		||||
				tag_add_item(tag, TAG_TITLE, tag_title);
 | 
			
		||||
				g_free(tag_title);
 | 
			
		||||
			}else
 | 
			
		||||
				tag_add_item(tag, TAG_TITLE, ti->song);
 | 
			
		||||
		}
 | 
			
		||||
		if(ti->author != NULL)
 | 
			
		||||
			tag_add_item(tag, TAG_ARTIST, ti->author);
 | 
			
		||||
		if(ti->game != NULL)
 | 
			
		||||
@@ -135,4 +240,5 @@ const struct decoder_plugin gme_decoder_plugin = {
 | 
			
		||||
	.file_decode = gme_file_decode,
 | 
			
		||||
	.tag_dup = gme_tag_dup,
 | 
			
		||||
	.suffixes = gme_suffixes,
 | 
			
		||||
	.container_scan = gme_container_scan,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -285,10 +285,10 @@ parse_id3_mixramp(char **mixramp_start, char **mixramp_end,
 | 
			
		||||
					     (&frame->fields[2]));
 | 
			
		||||
 | 
			
		||||
		if (g_ascii_strcasecmp(key, "mixramp_start") == 0) {
 | 
			
		||||
			*mixramp_start = strdup(value);
 | 
			
		||||
			*mixramp_start = g_strdup(value);
 | 
			
		||||
			found = true;
 | 
			
		||||
		} else if (g_ascii_strcasecmp(key, "mixramp_end") == 0) {
 | 
			
		||||
			*mixramp_end = strdup(value);
 | 
			
		||||
			*mixramp_end = g_strdup(value);
 | 
			
		||||
			found = true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -547,14 +547,14 @@ enum {
 | 
			
		||||
	XING_SCALE  = 0x00000008L
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct version {
 | 
			
		||||
struct lame_version {
 | 
			
		||||
	unsigned major;
 | 
			
		||||
	unsigned minor;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct lame {
 | 
			
		||||
	char encoder[10];       /* 9 byte encoder name/version ("LAME3.97b") */
 | 
			
		||||
	struct version version; /* struct containing just the version */
 | 
			
		||||
	struct lame_version version; /* struct containing just the version */
 | 
			
		||||
	float peak;             /* replaygain peak */
 | 
			
		||||
	float track_gain;       /* replaygain track gain */
 | 
			
		||||
	float album_gain;       /* replaygain album gain */
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "decoder_api.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
#include <mikmod.h>
 | 
			
		||||
@@ -110,8 +111,8 @@ mikmod_decoder_init(const struct config_param *param)
 | 
			
		||||
	mikmod_sample_rate = config_get_block_unsigned(param, "sample_rate",
 | 
			
		||||
						       44100);
 | 
			
		||||
	if (!audio_valid_sample_rate(mikmod_sample_rate))
 | 
			
		||||
		g_error("Invalid sample rate in line %d: %u",
 | 
			
		||||
			param->line, mikmod_sample_rate);
 | 
			
		||||
		MPD_ERROR("Invalid sample rate in line %d: %u",
 | 
			
		||||
			  param->line, mikmod_sample_rate);
 | 
			
		||||
 | 
			
		||||
	md_device = 0;
 | 
			
		||||
	md_reverb = 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -362,6 +362,10 @@ mp4ff_tag_name_parse(const char *name)
 | 
			
		||||
	if (type == TAG_NUM_OF_ITEM_TYPES)
 | 
			
		||||
		type = tag_name_parse_i(name);
 | 
			
		||||
 | 
			
		||||
	if (g_ascii_strcasecmp(name, "albumartist") == 0 ||
 | 
			
		||||
	    g_ascii_strcasecmp(name, "album_artist") == 0)
 | 
			
		||||
		return TAG_ALBUM_ARTIST;
 | 
			
		||||
 | 
			
		||||
	return type;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -413,7 +417,13 @@ mp4_stream_tag(struct input_stream *is)
 | 
			
		||||
	return tag;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char *const mp4_suffixes[] = { "m4a", "mp4", NULL };
 | 
			
		||||
static const char *const mp4_suffixes[] = {
 | 
			
		||||
	"m4a",
 | 
			
		||||
	"m4b",
 | 
			
		||||
	"mp4",
 | 
			
		||||
	NULL
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL };
 | 
			
		||||
 | 
			
		||||
const struct decoder_plugin mp4ff_decoder_plugin = {
 | 
			
		||||
 
 | 
			
		||||
@@ -201,6 +201,7 @@ static void
 | 
			
		||||
sidplay_file_decode(struct decoder *decoder, const char *path_fs)
 | 
			
		||||
{
 | 
			
		||||
	int ret;
 | 
			
		||||
	int channels;
 | 
			
		||||
 | 
			
		||||
	/* load the tune */
 | 
			
		||||
 | 
			
		||||
@@ -256,7 +257,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
 | 
			
		||||
	config.clockSpeed = SID2_CLOCK_CORRECT;
 | 
			
		||||
	config.frequency = 48000;
 | 
			
		||||
	config.optimisation = SID2_DEFAULT_OPTIMISATION;
 | 
			
		||||
	config.playback = sid2_stereo;
 | 
			
		||||
 | 
			
		||||
	config.precision = 16;
 | 
			
		||||
	config.sidDefault = SID2_MOS6581;
 | 
			
		||||
	config.sidEmulation = &builder;
 | 
			
		||||
@@ -267,6 +268,13 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
 | 
			
		||||
#else
 | 
			
		||||
	config.sampleFormat = SID2_BIG_SIGNED;
 | 
			
		||||
#endif
 | 
			
		||||
	if (tune.isStereo()) {
 | 
			
		||||
		config.playback = sid2_stereo;
 | 
			
		||||
		channels = 2;
 | 
			
		||||
	} else {
 | 
			
		||||
		config.playback = sid2_mono;
 | 
			
		||||
		channels = 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	iret = player.config(config);
 | 
			
		||||
	if (iret != 0) {
 | 
			
		||||
@@ -277,7 +285,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
 | 
			
		||||
	/* initialize the MPD decoder */
 | 
			
		||||
 | 
			
		||||
	struct audio_format audio_format;
 | 
			
		||||
	audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2);
 | 
			
		||||
	audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, channels);
 | 
			
		||||
	assert(audio_format_valid(&audio_format));
 | 
			
		||||
 | 
			
		||||
	decoder_initialized(decoder, &audio_format, true, (float)song_len);
 | 
			
		||||
@@ -399,6 +407,10 @@ sidplay_container_scan(const char *path_fs, const unsigned int tnum)
 | 
			
		||||
 | 
			
		||||
static const char *const sidplay_suffixes[] = {
 | 
			
		||||
	"sid",
 | 
			
		||||
	"mus",
 | 
			
		||||
	"str",
 | 
			
		||||
	"prg",
 | 
			
		||||
	"P00",
 | 
			
		||||
	NULL
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,9 +20,9 @@
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "decoder_control.h"
 | 
			
		||||
#include "player_control.h"
 | 
			
		||||
#include "pipe.h"
 | 
			
		||||
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <malloc.h>
 | 
			
		||||
 | 
			
		||||
#undef G_LOG_DOMAIN
 | 
			
		||||
#define G_LOG_DOMAIN "decoder_control"
 | 
			
		||||
@@ -50,12 +50,9 @@ dc_deinit(struct decoder_control *dc)
 | 
			
		||||
{
 | 
			
		||||
	g_cond_free(dc->cond);
 | 
			
		||||
	g_mutex_free(dc->mutex);
 | 
			
		||||
	if (dc->mixramp_start)
 | 
			
		||||
		free(dc->mixramp_start);
 | 
			
		||||
	if (dc->mixramp_end)
 | 
			
		||||
		free(dc->mixramp_end);
 | 
			
		||||
	if (dc->mixramp_prev_end)
 | 
			
		||||
		free(dc->mixramp_prev_end);
 | 
			
		||||
	g_free(dc->mixramp_start);
 | 
			
		||||
	g_free(dc->mixramp_end);
 | 
			
		||||
	g_free(dc->mixramp_prev_end);
 | 
			
		||||
	dc->mixramp_start = NULL;
 | 
			
		||||
	dc->mixramp_end = NULL;
 | 
			
		||||
	dc->mixramp_prev_end = NULL;
 | 
			
		||||
@@ -110,6 +107,7 @@ dc_start(struct decoder_control *dc, struct song *song,
 | 
			
		||||
	assert(song != NULL);
 | 
			
		||||
	assert(buffer != NULL);
 | 
			
		||||
	assert(pipe != NULL);
 | 
			
		||||
	assert(music_pipe_empty(pipe));
 | 
			
		||||
 | 
			
		||||
	dc->song = song;
 | 
			
		||||
	dc->buffer = buffer;
 | 
			
		||||
@@ -172,8 +170,7 @@ dc_mixramp_start(struct decoder_control *dc, char *mixramp_start)
 | 
			
		||||
{
 | 
			
		||||
	assert(dc != NULL);
 | 
			
		||||
 | 
			
		||||
	if (dc->mixramp_start)
 | 
			
		||||
		free(dc->mixramp_start);
 | 
			
		||||
	g_free(dc->mixramp_start);
 | 
			
		||||
	dc->mixramp_start = mixramp_start;
 | 
			
		||||
	g_debug("mixramp_start = %s", mixramp_start ? mixramp_start : "NULL");
 | 
			
		||||
}
 | 
			
		||||
@@ -183,8 +180,7 @@ dc_mixramp_end(struct decoder_control *dc, char *mixramp_end)
 | 
			
		||||
{
 | 
			
		||||
	assert(dc != NULL);
 | 
			
		||||
 | 
			
		||||
	if (dc->mixramp_end)
 | 
			
		||||
		free(dc->mixramp_end);
 | 
			
		||||
	g_free(dc->mixramp_end);
 | 
			
		||||
	dc->mixramp_end = mixramp_end;
 | 
			
		||||
	g_debug("mixramp_end = %s", mixramp_end ? mixramp_end : "NULL");
 | 
			
		||||
}
 | 
			
		||||
@@ -194,8 +190,7 @@ dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end)
 | 
			
		||||
{
 | 
			
		||||
	assert(dc != NULL);
 | 
			
		||||
 | 
			
		||||
	if (dc->mixramp_prev_end)
 | 
			
		||||
		free(dc->mixramp_prev_end);
 | 
			
		||||
	g_free(dc->mixramp_prev_end);
 | 
			
		||||
	dc->mixramp_prev_end = mixramp_prev_end;
 | 
			
		||||
	g_debug("mixramp_prev_end = %s", mixramp_prev_end ? mixramp_prev_end : "NULL");
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@
 | 
			
		||||
#include "decoder_plugin.h"
 | 
			
		||||
#include "utils.h"
 | 
			
		||||
#include "conf.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
@@ -198,8 +199,8 @@ decoder_plugin_config(const char *plugin_name)
 | 
			
		||||
		const char *name =
 | 
			
		||||
			config_get_block_string(param, "plugin", NULL);
 | 
			
		||||
		if (name == NULL)
 | 
			
		||||
			g_error("decoder configuration without 'plugin' name in line %d",
 | 
			
		||||
				param->line);
 | 
			
		||||
			MPD_ERROR("decoder configuration without 'plugin' name in line %d",
 | 
			
		||||
				  param->line);
 | 
			
		||||
 | 
			
		||||
		if (strcmp(name, plugin_name) == 0)
 | 
			
		||||
			return param;
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@
 | 
			
		||||
#include "decoder_list.h"
 | 
			
		||||
#include "decoder_plugin.h"
 | 
			
		||||
#include "decoder_api.h"
 | 
			
		||||
#include "replay_gain_ape.h"
 | 
			
		||||
#include "input_stream.h"
 | 
			
		||||
#include "player_control.h"
 | 
			
		||||
#include "pipe.h"
 | 
			
		||||
@@ -32,6 +33,7 @@
 | 
			
		||||
#include "mapper.h"
 | 
			
		||||
#include "path.h"
 | 
			
		||||
#include "uri.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
@@ -296,6 +298,18 @@ decoder_run_stream(struct decoder *decoder, const char *uri)
 | 
			
		||||
	return success;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Attempt to load replay gain data, and pass it to
 | 
			
		||||
 * decoder_replay_gain().
 | 
			
		||||
 */
 | 
			
		||||
static void
 | 
			
		||||
decoder_load_replay_gain(struct decoder *decoder, const char *path_fs)
 | 
			
		||||
{
 | 
			
		||||
	struct replay_gain_info info;
 | 
			
		||||
	if (replay_gain_ape_read(path_fs, &info))
 | 
			
		||||
		decoder_replay_gain(decoder, &info);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Try decoding a file.
 | 
			
		||||
 */
 | 
			
		||||
@@ -311,6 +325,8 @@ decoder_run_file(struct decoder *decoder, const char *path_fs)
 | 
			
		||||
 | 
			
		||||
	decoder_unlock(dc);
 | 
			
		||||
 | 
			
		||||
	decoder_load_replay_gain(decoder, path_fs);
 | 
			
		||||
 | 
			
		||||
	while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) {
 | 
			
		||||
		if (plugin->file_decode != NULL) {
 | 
			
		||||
			decoder_lock(dc);
 | 
			
		||||
@@ -479,5 +495,5 @@ decoder_thread_start(struct decoder_control *dc)
 | 
			
		||||
 | 
			
		||||
	dc->thread = g_thread_create(decoder_task, dc, true, &e);
 | 
			
		||||
	if (dc->thread == NULL)
 | 
			
		||||
		g_error("Failed to spawn decoder task: %s", e->message);
 | 
			
		||||
		MPD_ERROR("Failed to spawn decoder task: %s", e->message);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -30,8 +30,8 @@
 | 
			
		||||
 | 
			
		||||
#define DIRECTORY_DIR		"directory: "
 | 
			
		||||
 | 
			
		||||
#define DEVICE_INARCHIVE	(unsigned)(-1)
 | 
			
		||||
#define DEVICE_CONTAINER	(unsigned)(-2)
 | 
			
		||||
#define DEVICE_INARCHIVE (dev_t)(-1)
 | 
			
		||||
#define DEVICE_CONTAINER (dev_t)(-2)
 | 
			
		||||
 | 
			
		||||
struct directory {
 | 
			
		||||
	struct dirvec children;
 | 
			
		||||
 
 | 
			
		||||
@@ -170,6 +170,13 @@ lame_encoder_setup(struct lame_encoder *encoder, GError **error)
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (0 != lame_set_out_samplerate(encoder->gfp,
 | 
			
		||||
					 encoder->audio_format.sample_rate)) {
 | 
			
		||||
		g_set_error(error, lame_encoder_quark(), 0,
 | 
			
		||||
			    "error setting lame out sample rate");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (0 > lame_init_params(encoder->gfp)) {
 | 
			
		||||
		g_set_error(error, lame_encoder_quark(), 0,
 | 
			
		||||
			    "error initializing lame params");
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@
 | 
			
		||||
#include "encoder_plugin.h"
 | 
			
		||||
#include "tag.h"
 | 
			
		||||
#include "audio_format.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <vorbis/vorbisenc.h>
 | 
			
		||||
 | 
			
		||||
@@ -374,7 +375,7 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
 | 
			
		||||
 | 
			
		||||
	if (nbytes > length)
 | 
			
		||||
		/* XXX better error handling */
 | 
			
		||||
		g_error("buffer too small");
 | 
			
		||||
		MPD_ERROR("buffer too small");
 | 
			
		||||
 | 
			
		||||
	memcpy(dest, page.header, page.header_len);
 | 
			
		||||
	memcpy(dest + page.header_len, page.body, page.body_len);
 | 
			
		||||
@@ -385,7 +386,7 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
 | 
			
		||||
static const char *
 | 
			
		||||
vorbis_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
 | 
			
		||||
{
 | 
			
		||||
	return  "application/x-ogg";
 | 
			
		||||
	return  "audio/ogg";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const struct encoder_plugin vorbis_encoder_plugin = {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "event_pipe.h"
 | 
			
		||||
#include "fd_util.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
@@ -68,7 +69,7 @@ main_notify_event(G_GNUC_UNUSED GIOChannel *source,
 | 
			
		||||
						   buffer, sizeof(buffer),
 | 
			
		||||
						   &bytes_read, &error);
 | 
			
		||||
	if (status == G_IO_STATUS_ERROR)
 | 
			
		||||
		g_error("error reading from pipe: %s", error->message);
 | 
			
		||||
		MPD_ERROR("error reading from pipe: %s", error->message);
 | 
			
		||||
 | 
			
		||||
	bool events[PIPE_EVENT_MAX];
 | 
			
		||||
	g_mutex_lock(event_pipe_mutex);
 | 
			
		||||
@@ -91,7 +92,7 @@ void event_pipe_init(void)
 | 
			
		||||
 | 
			
		||||
	ret = pipe_cloexec_nonblock(event_pipe);
 | 
			
		||||
	if (ret < 0)
 | 
			
		||||
		g_error("Couldn't open pipe: %s", strerror(errno));
 | 
			
		||||
		MPD_ERROR("Couldn't open pipe: %s", strerror(errno));
 | 
			
		||||
 | 
			
		||||
#ifndef G_OS_WIN32
 | 
			
		||||
	channel = g_io_channel_unix_new(event_pipe[0]);
 | 
			
		||||
@@ -116,7 +117,10 @@ void event_pipe_deinit(void)
 | 
			
		||||
	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]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -147,7 +151,7 @@ void event_pipe_emit(enum pipe_event event)
 | 
			
		||||
 | 
			
		||||
	w = write(event_pipe[1], "", 1);
 | 
			
		||||
	if (w < 0 && errno != EAGAIN && errno != EINTR)
 | 
			
		||||
		g_error("error writing to pipe: %s", strerror(errno));
 | 
			
		||||
		MPD_ERROR("error writing to pipe: %s", strerror(errno));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void event_pipe_emit_fast(enum pipe_event event)
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,9 @@ enum pipe_event {
 | 
			
		||||
	/** a hardware mixer plugin has detected a change */
 | 
			
		||||
	PIPE_EVENT_MIXER,
 | 
			
		||||
 | 
			
		||||
	/** shutdown requested */
 | 
			
		||||
	PIPE_EVENT_SHUTDOWN,
 | 
			
		||||
 | 
			
		||||
	PIPE_EVENT_MAX
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -103,6 +103,16 @@ fd_set_nonblock(int fd)
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
dup_cloexec(int oldfd)
 | 
			
		||||
{
 | 
			
		||||
	int newfd = dup(oldfd);
 | 
			
		||||
	if (newfd >= 0)
 | 
			
		||||
		fd_set_nonblock(newfd);
 | 
			
		||||
 | 
			
		||||
	return newfd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
open_cloexec(const char *path_fs, int flags, int mode)
 | 
			
		||||
{
 | 
			
		||||
@@ -174,6 +184,30 @@ pipe_cloexec_nonblock(int fd[2])
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifndef WIN32
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
socketpair_cloexec(int domain, int type, int protocol, int sv[2])
 | 
			
		||||
{
 | 
			
		||||
	int ret;
 | 
			
		||||
 | 
			
		||||
#ifdef SOCK_CLOEXEC
 | 
			
		||||
	ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv);
 | 
			
		||||
	if (ret >= 0 || errno != EINVAL)
 | 
			
		||||
		return ret;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	ret = socketpair(domain, type, protocol, sv);
 | 
			
		||||
	if (ret >= 0) {
 | 
			
		||||
		fd_set_cloexec(sv[0], true);
 | 
			
		||||
		fd_set_cloexec(sv[1], true);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
socket_cloexec_nonblock(int domain, int type, int protocol)
 | 
			
		||||
{
 | 
			
		||||
@@ -222,6 +256,33 @@ accept_cloexec_nonblock(int fd, struct sockaddr *address,
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifndef WIN32
 | 
			
		||||
 | 
			
		||||
ssize_t
 | 
			
		||||
recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags)
 | 
			
		||||
{
 | 
			
		||||
#ifdef MSG_CMSG_CLOEXEC
 | 
			
		||||
	flags |= MSG_CMSG_CLOEXEC;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	ssize_t result = recvmsg(sockfd, msg, flags);
 | 
			
		||||
	if (result >= 0) {
 | 
			
		||||
		struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg);
 | 
			
		||||
		while (cmsg != NULL) {
 | 
			
		||||
			if (cmsg->cmsg_type == SCM_RIGHTS) {
 | 
			
		||||
				const int *fd_p = (const int *)CMSG_DATA(cmsg);
 | 
			
		||||
				fd_set_cloexec(*fd_p, true);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			cmsg = CMSG_NXTHDR(msg, cmsg);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_INOTIFY_INIT
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
 
 | 
			
		||||
@@ -39,8 +39,23 @@
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
 | 
			
		||||
#ifndef WIN32
 | 
			
		||||
#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4))
 | 
			
		||||
#define _GNU_SOURCE
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
struct sockaddr;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wrapper for dup(), which sets the CLOEXEC flag on the new
 | 
			
		||||
 * descriptor.
 | 
			
		||||
 */
 | 
			
		||||
int
 | 
			
		||||
dup_cloexec(int oldfd);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wrapper for open(), which sets the CLOEXEC flag (atomically if
 | 
			
		||||
 * supported by the OS).
 | 
			
		||||
@@ -65,6 +80,17 @@ pipe_cloexec(int fd[2]);
 | 
			
		||||
int
 | 
			
		||||
pipe_cloexec_nonblock(int fd[2]);
 | 
			
		||||
 | 
			
		||||
#ifndef WIN32
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wrapper for socketpair(), which sets the CLOEXEC flag (atomically
 | 
			
		||||
 * if supported by the OS).
 | 
			
		||||
 */
 | 
			
		||||
int
 | 
			
		||||
socketpair_cloexec(int domain, int type, int protocol, int sv[2]);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wrapper for socket(), which sets the CLOEXEC and the NONBLOCK flag
 | 
			
		||||
 * (atomically if supported by the OS).
 | 
			
		||||
@@ -80,6 +106,20 @@ int
 | 
			
		||||
accept_cloexec_nonblock(int fd, struct sockaddr *address,
 | 
			
		||||
			size_t *address_length_r);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#ifndef WIN32
 | 
			
		||||
 | 
			
		||||
struct msghdr;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wrapper for recvmsg(), which sets the CLOEXEC flag (atomically if
 | 
			
		||||
 * supported by the OS).
 | 
			
		||||
 */
 | 
			
		||||
ssize_t
 | 
			
		||||
recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wrapper for inotify_init(), which sets the CLOEXEC flag (atomically
 | 
			
		||||
 * if supported by the OS).
 | 
			
		||||
 
 | 
			
		||||
@@ -55,8 +55,16 @@ struct replay_gain_filter {
 | 
			
		||||
	struct replay_gain_info info;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The current volume, between 0 and #PCM_VOLUME_1 (both
 | 
			
		||||
	 * including).
 | 
			
		||||
	 * The current volume, between 0 and a value that may or may not exceed
 | 
			
		||||
	 * #PCM_VOLUME_1.
 | 
			
		||||
	 *
 | 
			
		||||
	 * If the default value of true is used for replaygain_limit, the
 | 
			
		||||
	 * application of the volume to the signal will never cause clipping.
 | 
			
		||||
	 *
 | 
			
		||||
	 * On the other hand, if the user has set replaygain_limit to false,
 | 
			
		||||
	 * the chance of clipping is explicitly preferred if that's required to
 | 
			
		||||
	 * maintain a consistent audio level. Whether clipping will actually
 | 
			
		||||
	 * occur depends on what value the user is using for replaygain_preamp.
 | 
			
		||||
	 */
 | 
			
		||||
	unsigned volume;
 | 
			
		||||
 | 
			
		||||
@@ -171,7 +179,7 @@ replay_gain_filter_filter(struct filter *_filter,
 | 
			
		||||
 | 
			
		||||
	*dest_size_r = src_size;
 | 
			
		||||
 | 
			
		||||
	if (filter->volume >= PCM_VOLUME_1)
 | 
			
		||||
	if (filter->volume == PCM_VOLUME_1)
 | 
			
		||||
		/* optimized special case: 100% volume = no-op */
 | 
			
		||||
		return src;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -96,6 +96,7 @@ icy_server_metadata_page(const struct tag *tag, ...)
 | 
			
		||||
	gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata -
 | 
			
		||||
						 // "StreamTitle='';StreamUrl='';"
 | 
			
		||||
						 // = 4081 - 28
 | 
			
		||||
	stream_title[0] =  '\0';
 | 
			
		||||
 | 
			
		||||
	last_item = -1;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@
 | 
			
		||||
#include "inotify_source.h"
 | 
			
		||||
#include "fifo_buffer.h"
 | 
			
		||||
#include "fd_util.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <sys/inotify.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
@@ -68,13 +69,14 @@ mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source,
 | 
			
		||||
 | 
			
		||||
	dest = fifo_buffer_write(source->buffer, &length);
 | 
			
		||||
	if (dest == NULL)
 | 
			
		||||
		g_error("buffer full");
 | 
			
		||||
		MPD_ERROR("buffer full");
 | 
			
		||||
 | 
			
		||||
	nbytes = read(source->fd, dest, length);
 | 
			
		||||
	if (nbytes < 0)
 | 
			
		||||
		g_error("failed to read from inotify: %s", g_strerror(errno));
 | 
			
		||||
		MPD_ERROR("failed to read from inotify: %s",
 | 
			
		||||
			  g_strerror(errno));
 | 
			
		||||
	if (nbytes == 0)
 | 
			
		||||
		g_error("end of file from inotify");
 | 
			
		||||
		MPD_ERROR("end of file from inotify");
 | 
			
		||||
 | 
			
		||||
	fifo_buffer_append(source->buffer, nbytes);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -264,7 +264,7 @@ input_curl_select(struct input_curl *c, GError **error_r)
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#if LIBCURL_VERSION_NUM >= 0x070f00
 | 
			
		||||
#if LIBCURL_VERSION_NUM >= 0x070f04
 | 
			
		||||
	long timeout2;
 | 
			
		||||
	mcode = curl_multi_timeout(c->multi, &timeout2);
 | 
			
		||||
	if (mcode != CURLM_OK) {
 | 
			
		||||
 
 | 
			
		||||
@@ -80,15 +80,18 @@ copy_attributes(struct input_rewind *r)
 | 
			
		||||
	struct input_stream *dest = &r->base;
 | 
			
		||||
	const struct input_stream *src = r->input;
 | 
			
		||||
 | 
			
		||||
	assert(dest != src);
 | 
			
		||||
	assert(src->mime == NULL || dest->mime != src->mime);
 | 
			
		||||
 | 
			
		||||
	dest->ready = src->ready;
 | 
			
		||||
	dest->seekable = src->seekable;
 | 
			
		||||
	dest->size = src->size;
 | 
			
		||||
	dest->offset = src->offset;
 | 
			
		||||
 | 
			
		||||
	if (dest->mime == NULL && src->mime != NULL)
 | 
			
		||||
		/* this is set only once, and the duplicated pointer
 | 
			
		||||
		   is freed by input_stream_close() */
 | 
			
		||||
	if (src->mime != NULL) {
 | 
			
		||||
		g_free(dest->mime);
 | 
			
		||||
		dest->mime = g_strdup(src->mime);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										372
									
								
								src/listen.c
									
									
									
									
									
								
							
							
						
						
									
										372
									
								
								src/listen.c
									
									
									
									
									
								
							@@ -19,333 +19,44 @@
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "listen.h"
 | 
			
		||||
#include "socket_util.h"
 | 
			
		||||
#include "server_socket.h"
 | 
			
		||||
#include "client.h"
 | 
			
		||||
#include "conf.h"
 | 
			
		||||
#include "fd_util.h"
 | 
			
		||||
#include "glib_compat.h"
 | 
			
		||||
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#include <sys/stat.h>
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
 | 
			
		||||
#ifdef WIN32
 | 
			
		||||
#define WINVER 0x0501
 | 
			
		||||
#include <ws2tcpip.h>
 | 
			
		||||
#include <winsock.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <netinet/in.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
#include <sys/un.h>
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#undef G_LOG_DOMAIN
 | 
			
		||||
#define G_LOG_DOMAIN "listen"
 | 
			
		||||
 | 
			
		||||
#define DEFAULT_PORT	6600
 | 
			
		||||
 | 
			
		||||
struct listen_socket {
 | 
			
		||||
	struct listen_socket *next;
 | 
			
		||||
 | 
			
		||||
	int fd;
 | 
			
		||||
 | 
			
		||||
	guint source_id;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct listen_socket *listen_sockets;
 | 
			
		||||
static struct server_socket *listen_socket;
 | 
			
		||||
int listen_port;
 | 
			
		||||
 | 
			
		||||
static GQuark
 | 
			
		||||
listen_quark(void)
 | 
			
		||||
static void
 | 
			
		||||
listen_callback(int fd, const struct sockaddr *address,
 | 
			
		||||
		size_t address_length, int uid, G_GNUC_UNUSED void *ctx)
 | 
			
		||||
{
 | 
			
		||||
	return g_quark_from_static_string("listen");
 | 
			
		||||
	client_new(fd, address, address_length, uid);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static gboolean
 | 
			
		||||
listen_in_event(GIOChannel *source, GIOCondition condition, gpointer data);
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
listen_add_address(int pf, const struct sockaddr *addrp, socklen_t addrlen,
 | 
			
		||||
		   GError **error)
 | 
			
		||||
{
 | 
			
		||||
	char *address_string;
 | 
			
		||||
	int fd;
 | 
			
		||||
	struct listen_socket *ls;
 | 
			
		||||
	GIOChannel *channel;
 | 
			
		||||
 | 
			
		||||
	address_string = sockaddr_to_string(addrp, addrlen, NULL);
 | 
			
		||||
	if (address_string != NULL) {
 | 
			
		||||
		g_debug("binding to socket address %s", address_string);
 | 
			
		||||
		g_free(address_string);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fd = socket_bind_listen(pf, SOCK_STREAM, 0, addrp, addrlen, 5, error);
 | 
			
		||||
	if (fd < 0)
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	ls = g_new(struct listen_socket, 1);
 | 
			
		||||
	ls->fd = fd;
 | 
			
		||||
 | 
			
		||||
	channel = g_io_channel_unix_new(fd);
 | 
			
		||||
	ls->source_id = g_io_add_watch(channel, G_IO_IN,
 | 
			
		||||
				       listen_in_event, GINT_TO_POINTER(fd));
 | 
			
		||||
	g_io_channel_unref(channel);
 | 
			
		||||
 | 
			
		||||
	ls->next = listen_sockets;
 | 
			
		||||
	listen_sockets = ls;
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_TCP
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a listener on a port on all IPv4 interfaces.
 | 
			
		||||
 *
 | 
			
		||||
 * @param port the TCP port
 | 
			
		||||
 * @param error location to store the error occuring, or NULL to ignore errors
 | 
			
		||||
 * @return true on success
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
listen_add_port_ipv4(unsigned int port, GError **error)
 | 
			
		||||
{
 | 
			
		||||
	struct sockaddr_in sin;
 | 
			
		||||
	const struct sockaddr *addrp = (const struct sockaddr *)&sin;
 | 
			
		||||
	socklen_t addrlen = sizeof(sin);
 | 
			
		||||
 | 
			
		||||
	memset(&sin, 0, sizeof(sin));
 | 
			
		||||
	sin.sin_port = htons(port);
 | 
			
		||||
	sin.sin_family = AF_INET;
 | 
			
		||||
	sin.sin_addr.s_addr = INADDR_ANY;
 | 
			
		||||
 | 
			
		||||
	return listen_add_address(PF_INET, addrp, addrlen, error);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_IPV6
 | 
			
		||||
/**
 | 
			
		||||
 * Add a listener on a port on all IPv6 interfaces.
 | 
			
		||||
 *
 | 
			
		||||
 * @param port the TCP port
 | 
			
		||||
 * @param error location to store the error occuring, or NULL to ignore errors
 | 
			
		||||
 * @return true on success
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
listen_add_port_ipv6(unsigned int port, GError **error)
 | 
			
		||||
{
 | 
			
		||||
	struct sockaddr_in6 sin;
 | 
			
		||||
	const struct sockaddr *addrp = (const struct sockaddr *)&sin;
 | 
			
		||||
	socklen_t addrlen = sizeof(sin);
 | 
			
		||||
 | 
			
		||||
	memset(&sin, 0, sizeof(sin));
 | 
			
		||||
	sin.sin6_port = htons(port);
 | 
			
		||||
	sin.sin6_family = AF_INET6;
 | 
			
		||||
 | 
			
		||||
	return listen_add_address(PF_INET6, addrp, addrlen, error);
 | 
			
		||||
}
 | 
			
		||||
#endif /* HAVE_IPV6 */
 | 
			
		||||
 | 
			
		||||
#endif /* HAVE_TCP */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a listener on a port on all interfaces.
 | 
			
		||||
 *
 | 
			
		||||
 * @param port the TCP port
 | 
			
		||||
 * @param error location to store the error occuring, or NULL to ignore errors
 | 
			
		||||
 * @return true on success
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
listen_add_port(unsigned int port, GError **error)
 | 
			
		||||
{
 | 
			
		||||
#ifdef HAVE_TCP
 | 
			
		||||
	bool success;
 | 
			
		||||
#ifdef HAVE_IPV6
 | 
			
		||||
	bool success6;
 | 
			
		||||
	GError *error2 = NULL;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	g_debug("binding to any address");
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_IPV6
 | 
			
		||||
	success6 = listen_add_port_ipv6(port, &error2);
 | 
			
		||||
	if (!success6) {
 | 
			
		||||
		if (error2->domain != listen_quark() ||
 | 
			
		||||
		    (error2->code != EAFNOSUPPORT && error2->code != EINVAL &&
 | 
			
		||||
		     error2->code != EPROTONOSUPPORT)) {
 | 
			
		||||
			g_propagate_error(error, error2);
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* although MPD was compiled with IPv6 support, this
 | 
			
		||||
		   host does not have it - ignore this error */
 | 
			
		||||
		g_error_free(error2);
 | 
			
		||||
	}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	success = listen_add_port_ipv4(port, error);
 | 
			
		||||
	if (!success) {
 | 
			
		||||
#ifdef HAVE_IPV6
 | 
			
		||||
		if (success6)
 | 
			
		||||
			/* non-critical: IPv6 listener is
 | 
			
		||||
			   already set up */
 | 
			
		||||
			g_clear_error(error);
 | 
			
		||||
		else
 | 
			
		||||
#endif
 | 
			
		||||
			return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
#else /* HAVE_TCP */
 | 
			
		||||
	(void)port;
 | 
			
		||||
 | 
			
		||||
	g_set_error(error, listen_quark(), 0,
 | 
			
		||||
		    "TCP support is disabled");
 | 
			
		||||
	return false;
 | 
			
		||||
#endif /* HAVE_TCP */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Resolves a host name, and adds listeners on all addresses in the
 | 
			
		||||
 * result set.
 | 
			
		||||
 *
 | 
			
		||||
 * @param hostname the host name to be resolved
 | 
			
		||||
 * @param port the TCP port
 | 
			
		||||
 * @param error location to store the error occuring, or NULL to ignore errors
 | 
			
		||||
 * @return true on success
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
listen_add_host(const char *hostname, unsigned port, GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
#ifdef HAVE_TCP
 | 
			
		||||
	struct addrinfo hints, *ai, *i;
 | 
			
		||||
	char service[20];
 | 
			
		||||
	int ret;
 | 
			
		||||
	bool success;
 | 
			
		||||
 | 
			
		||||
	g_debug("binding to address for %s", hostname);
 | 
			
		||||
 | 
			
		||||
	memset(&hints, 0, sizeof(hints));
 | 
			
		||||
	hints.ai_flags = AI_PASSIVE;
 | 
			
		||||
#ifdef AI_ADDRCONFIG
 | 
			
		||||
	hints.ai_flags |= AI_ADDRCONFIG;
 | 
			
		||||
#endif
 | 
			
		||||
	hints.ai_family = PF_UNSPEC;
 | 
			
		||||
	hints.ai_socktype = SOCK_STREAM;
 | 
			
		||||
	hints.ai_protocol = IPPROTO_TCP;
 | 
			
		||||
 | 
			
		||||
	g_snprintf(service, sizeof(service), "%u", port);
 | 
			
		||||
 | 
			
		||||
	ret = getaddrinfo(hostname, service, &hints, &ai);
 | 
			
		||||
	if (ret != 0) {
 | 
			
		||||
		g_set_error(error_r, listen_quark(), ret,
 | 
			
		||||
			    "Failed to look up host \"%s\": %s",
 | 
			
		||||
			    hostname, gai_strerror(ret));
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (i = ai; i != NULL; i = i->ai_next) {
 | 
			
		||||
		GError *error = NULL;
 | 
			
		||||
 | 
			
		||||
		success = listen_add_address(i->ai_family, i->ai_addr,
 | 
			
		||||
					     i->ai_addrlen, &error);
 | 
			
		||||
		if (!success) {
 | 
			
		||||
			if (i == ai) {
 | 
			
		||||
				/* first bind has failed: fatal
 | 
			
		||||
				   error */
 | 
			
		||||
				g_propagate_error(error_r, error);
 | 
			
		||||
				return false;
 | 
			
		||||
			} else {
 | 
			
		||||
				char *address_string =
 | 
			
		||||
					sockaddr_to_string(i->ai_addr,
 | 
			
		||||
							   i->ai_addrlen,
 | 
			
		||||
							   NULL);
 | 
			
		||||
				if (address_string == NULL)
 | 
			
		||||
					address_string = g_strdup("[unknown]");
 | 
			
		||||
 | 
			
		||||
				g_warning("bind to %s failed: %s "
 | 
			
		||||
					  "(continuing anyway, because at "
 | 
			
		||||
					  "least one address is bound)",
 | 
			
		||||
					  address_string, error->message);
 | 
			
		||||
				g_free(address_string);
 | 
			
		||||
				g_error_free(error);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	freeaddrinfo(ai);
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
#else /* HAVE_TCP */
 | 
			
		||||
 | 
			
		||||
	(void)hostname;
 | 
			
		||||
	(void)port;
 | 
			
		||||
 | 
			
		||||
	g_set_error(error_r, listen_quark(), 0,
 | 
			
		||||
		    "TCP support is disabled");
 | 
			
		||||
	return false;
 | 
			
		||||
#endif /* HAVE_TCP */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_UN
 | 
			
		||||
/**
 | 
			
		||||
 * Add a listener on a Unix domain socket.
 | 
			
		||||
 *
 | 
			
		||||
 * @param path the absolute socket path
 | 
			
		||||
 * @param error location to store the error occuring, or NULL to ignore errors
 | 
			
		||||
 * @return true on success
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
listen_add_path(const char *path, GError **error)
 | 
			
		||||
{
 | 
			
		||||
	size_t path_length;
 | 
			
		||||
	struct sockaddr_un s_un;
 | 
			
		||||
	const struct sockaddr *addrp = (const struct sockaddr *)&s_un;
 | 
			
		||||
	socklen_t addrlen = sizeof(s_un);
 | 
			
		||||
	bool success;
 | 
			
		||||
 | 
			
		||||
	path_length = strlen(path);
 | 
			
		||||
	if (path_length >= sizeof(s_un.sun_path)) {
 | 
			
		||||
		g_set_error(error, listen_quark(), 0,
 | 
			
		||||
			    "unix socket path is too long");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	unlink(path);
 | 
			
		||||
 | 
			
		||||
	s_un.sun_family = AF_UNIX;
 | 
			
		||||
	memcpy(s_un.sun_path, path, path_length + 1);
 | 
			
		||||
 | 
			
		||||
	success = listen_add_address(PF_UNIX, addrp, addrlen, error);
 | 
			
		||||
	if (!success)
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	/* allow everybody to connect */
 | 
			
		||||
	chmod(path, 0666);
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
#endif /* HAVE_UN */
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
listen_add_config_param(unsigned int port,
 | 
			
		||||
			const struct config_param *param,
 | 
			
		||||
			GError **error)
 | 
			
		||||
			GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	assert(param != NULL);
 | 
			
		||||
 | 
			
		||||
	if (0 == strcmp(param->value, "any")) {
 | 
			
		||||
		return listen_add_port(port, error);
 | 
			
		||||
#ifdef HAVE_UN
 | 
			
		||||
		return server_socket_add_port(listen_socket, port, error_r);
 | 
			
		||||
	} else if (param->value[0] == '/') {
 | 
			
		||||
		return listen_add_path(param->value, error);
 | 
			
		||||
#endif /* HAVE_UN */
 | 
			
		||||
		return server_socket_add_path(listen_socket, param->value,
 | 
			
		||||
					      error_r);
 | 
			
		||||
	} else {
 | 
			
		||||
		return listen_add_host(param->value, port, error);
 | 
			
		||||
		return server_socket_add_host(listen_socket, param->value,
 | 
			
		||||
					      port, error_r);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -358,6 +69,8 @@ listen_global_init(GError **error_r)
 | 
			
		||||
	bool success;
 | 
			
		||||
	GError *error = NULL;
 | 
			
		||||
 | 
			
		||||
	listen_socket = server_socket_new(listen_callback, NULL);
 | 
			
		||||
 | 
			
		||||
	if (param != NULL) {
 | 
			
		||||
		/* "bind_to_address" is configured, create listeners
 | 
			
		||||
		   for all values */
 | 
			
		||||
@@ -378,7 +91,7 @@ listen_global_init(GError **error_r)
 | 
			
		||||
		/* no "bind_to_address" configured, bind the
 | 
			
		||||
		   configured port on all interfaces */
 | 
			
		||||
 | 
			
		||||
		success = listen_add_port(port, &error);
 | 
			
		||||
		success = server_socket_add_port(listen_socket, port, error_r);
 | 
			
		||||
		if (!success) {
 | 
			
		||||
			g_propagate_prefixed_error(error_r, error,
 | 
			
		||||
						   "Failed to listen on *:%d: ",
 | 
			
		||||
@@ -387,6 +100,9 @@ listen_global_init(GError **error_r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!server_socket_open(listen_socket, error_r))
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	listen_port = port;
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
@@ -395,55 +111,7 @@ void listen_global_finish(void)
 | 
			
		||||
{
 | 
			
		||||
	g_debug("listen_global_finish called");
 | 
			
		||||
 | 
			
		||||
	while (listen_sockets != NULL) {
 | 
			
		||||
		struct listen_socket *ls = listen_sockets;
 | 
			
		||||
		listen_sockets = ls->next;
 | 
			
		||||
	assert(listen_socket != NULL);
 | 
			
		||||
 | 
			
		||||
		g_source_remove(ls->source_id);
 | 
			
		||||
		close(ls->fd);
 | 
			
		||||
		g_free(ls);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int get_remote_uid(int fd)
 | 
			
		||||
{
 | 
			
		||||
#ifdef HAVE_STRUCT_UCRED
 | 
			
		||||
	struct ucred cred;
 | 
			
		||||
	socklen_t len = sizeof (cred);
 | 
			
		||||
 | 
			
		||||
	if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	return cred.uid;
 | 
			
		||||
#else
 | 
			
		||||
#ifdef HAVE_GETPEEREID
 | 
			
		||||
	uid_t euid;
 | 
			
		||||
	gid_t egid;
 | 
			
		||||
 | 
			
		||||
	if (getpeereid(fd, &euid, &egid) == 0)
 | 
			
		||||
		return euid;
 | 
			
		||||
#endif
 | 
			
		||||
	return -1;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static gboolean
 | 
			
		||||
listen_in_event(G_GNUC_UNUSED GIOChannel *source,
 | 
			
		||||
		G_GNUC_UNUSED GIOCondition condition,
 | 
			
		||||
		gpointer data)
 | 
			
		||||
{
 | 
			
		||||
	int listen_fd = GPOINTER_TO_INT(data), fd;
 | 
			
		||||
	struct sockaddr_storage sa;
 | 
			
		||||
	size_t sa_length = sizeof(sa);
 | 
			
		||||
 | 
			
		||||
	fd = accept_cloexec_nonblock(listen_fd, (struct sockaddr*)&sa,
 | 
			
		||||
				     &sa_length);
 | 
			
		||||
	if (fd >= 0) {
 | 
			
		||||
		client_new(fd, (struct sockaddr*)&sa, sa_length,
 | 
			
		||||
			   get_remote_uid(fd));
 | 
			
		||||
	} else if (fd < 0 && errno != EINTR) {
 | 
			
		||||
		g_warning("Problems accept()'ing");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
	server_socket_free(listen_socket);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								src/log.c
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/log.c
									
									
									
									
									
								
							@@ -22,6 +22,7 @@
 | 
			
		||||
#include "conf.h"
 | 
			
		||||
#include "utils.h"
 | 
			
		||||
#include "fd_util.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
@@ -60,9 +61,9 @@ static void redirect_logs(int fd)
 | 
			
		||||
{
 | 
			
		||||
	assert(fd >= 0);
 | 
			
		||||
	if (dup2(fd, STDOUT_FILENO) < 0)
 | 
			
		||||
		g_error("problems dup2 stdout : %s\n", strerror(errno));
 | 
			
		||||
		MPD_ERROR("problems dup2 stdout : %s\n", strerror(errno));
 | 
			
		||||
	if (dup2(fd, STDERR_FILENO) < 0)
 | 
			
		||||
		g_error("problems dup2 stderr : %s\n", strerror(errno));
 | 
			
		||||
		MPD_ERROR("problems dup2 stderr : %s\n", strerror(errno));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char *log_date(void)
 | 
			
		||||
@@ -138,8 +139,8 @@ log_init_file(const char *path, unsigned line)
 | 
			
		||||
	out_filename = path;
 | 
			
		||||
	out_fd = open_log_file();
 | 
			
		||||
	if (out_fd < 0)
 | 
			
		||||
		g_error("problem opening log file \"%s\" (config line %u) for "
 | 
			
		||||
			"writing\n", path, line);
 | 
			
		||||
		MPD_ERROR("problem opening log file \"%s\" (config line %u) "
 | 
			
		||||
			  "for writing\n", path, line);
 | 
			
		||||
 | 
			
		||||
	g_log_set_default_handler(file_log_func, NULL);
 | 
			
		||||
}
 | 
			
		||||
@@ -216,8 +217,8 @@ parse_log_level(const char *value, unsigned line)
 | 
			
		||||
	else if (0 == strcmp(value, "verbose"))
 | 
			
		||||
		return G_LOG_LEVEL_DEBUG;
 | 
			
		||||
	else {
 | 
			
		||||
		g_error("unknown log level \"%s\" at line %u\n",
 | 
			
		||||
			value, line);
 | 
			
		||||
		MPD_ERROR("unknown log level \"%s\" at line %u\n",
 | 
			
		||||
			  value, line);
 | 
			
		||||
		return G_LOG_LEVEL_MESSAGE;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -252,8 +253,8 @@ void log_init(bool verbose, bool use_stdout)
 | 
			
		||||
			   available) */
 | 
			
		||||
			log_init_syslog();
 | 
			
		||||
#else
 | 
			
		||||
			g_error("config parameter \"%s\" not found\n",
 | 
			
		||||
				CONF_LOG_FILE);
 | 
			
		||||
			MPD_ERROR("config parameter \"%s\" not found\n",
 | 
			
		||||
				  CONF_LOG_FILE);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAVE_SYSLOG
 | 
			
		||||
		} else if (strcmp(param->value, "syslog") == 0) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										53
									
								
								src/main.c
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								src/main.c
									
									
									
									
									
								
							@@ -54,6 +54,7 @@
 | 
			
		||||
#include "dirvec.h"
 | 
			
		||||
#include "songvec.h"
 | 
			
		||||
#include "tag_pool.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_INOTIFY
 | 
			
		||||
#include "inotify_update.h"
 | 
			
		||||
@@ -141,7 +142,7 @@ glue_db_init_and_load(void)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (path == NULL)
 | 
			
		||||
		g_error(CONF_DB_FILE " setting missing");
 | 
			
		||||
		MPD_ERROR(CONF_DB_FILE " setting missing");
 | 
			
		||||
 | 
			
		||||
	db_init(path);
 | 
			
		||||
 | 
			
		||||
@@ -175,7 +176,7 @@ glue_sticker_init(void)
 | 
			
		||||
	success = sticker_global_init(config_get_path(CONF_STICKER_FILE),
 | 
			
		||||
				      &error);
 | 
			
		||||
	if (!success)
 | 
			
		||||
		g_error("%s", error->message);
 | 
			
		||||
		MPD_ERROR("%s", error->message);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -197,14 +198,14 @@ static void winsock_init(void)
 | 
			
		||||
	retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
 | 
			
		||||
	if(retval != 0)
 | 
			
		||||
	{
 | 
			
		||||
		g_error("Attempt to open Winsock2 failed; error code %d\n",
 | 
			
		||||
		MPD_ERROR("Attempt to open Winsock2 failed; error code %d\n",
 | 
			
		||||
			retval);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (LOBYTE(sockinfo.wVersion) != 2)
 | 
			
		||||
	{
 | 
			
		||||
		g_error("We use Winsock2 but your version is either too new or "
 | 
			
		||||
			"old; please install Winsock 2.x\n");
 | 
			
		||||
		MPD_ERROR("We use Winsock2 but your version is either too new "
 | 
			
		||||
			  "or old; please install Winsock 2.x\n");
 | 
			
		||||
	}
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
@@ -226,8 +227,8 @@ initialize_decoder_and_player(void)
 | 
			
		||||
	if (param != NULL) {
 | 
			
		||||
		buffer_size = strtol(param->value, &test, 10);
 | 
			
		||||
		if (*test != '\0' || buffer_size <= 0)
 | 
			
		||||
			g_error("buffer size \"%s\" is not a positive integer, "
 | 
			
		||||
				"line %i\n", param->value, param->line);
 | 
			
		||||
			MPD_ERROR("buffer size \"%s\" is not a positive integer, "
 | 
			
		||||
				  "line %i\n", param->value, param->line);
 | 
			
		||||
	} else
 | 
			
		||||
		buffer_size = DEFAULT_BUFFER_SIZE;
 | 
			
		||||
 | 
			
		||||
@@ -236,15 +237,15 @@ initialize_decoder_and_player(void)
 | 
			
		||||
	buffered_chunks = buffer_size / CHUNK_SIZE;
 | 
			
		||||
 | 
			
		||||
	if (buffered_chunks >= 1 << 15)
 | 
			
		||||
		g_error("buffer size \"%li\" is too big\n", (long)buffer_size);
 | 
			
		||||
		MPD_ERROR("buffer size \"%li\" is too big\n", (long)buffer_size);
 | 
			
		||||
 | 
			
		||||
	param = config_get_param(CONF_BUFFER_BEFORE_PLAY);
 | 
			
		||||
	if (param != NULL) {
 | 
			
		||||
		perc = strtod(param->value, &test);
 | 
			
		||||
		if (*test != '%' || perc < 0 || perc > 100) {
 | 
			
		||||
			g_error("buffered before play \"%s\" is not a positive "
 | 
			
		||||
				"percentage and less than 100 percent, line %i",
 | 
			
		||||
				param->value, param->line);
 | 
			
		||||
			MPD_ERROR("buffered before play \"%s\" is not a positive "
 | 
			
		||||
				  "percentage and less than 100 percent, line %i",
 | 
			
		||||
				  param->value, param->line);
 | 
			
		||||
		}
 | 
			
		||||
	} else
 | 
			
		||||
		perc = DEFAULT_BUFFER_BEFORE_PLAY;
 | 
			
		||||
@@ -269,7 +270,25 @@ idle_event_emitted(void)
 | 
			
		||||
		client_manager_idle_add(flags);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * event_pipe callback function for PIPE_EVENT_SHUTDOWN
 | 
			
		||||
 */
 | 
			
		||||
static void
 | 
			
		||||
shutdown_event_emitted(void)
 | 
			
		||||
{
 | 
			
		||||
	g_main_loop_quit(main_loop);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main(int argc, char *argv[])
 | 
			
		||||
{
 | 
			
		||||
#ifdef WIN32
 | 
			
		||||
	return win32_main(argc, argv);
 | 
			
		||||
#else
 | 
			
		||||
	return mpd_main(argc, argv);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int mpd_main(int argc, char *argv[])
 | 
			
		||||
{
 | 
			
		||||
	struct options options;
 | 
			
		||||
	clock_t start;
 | 
			
		||||
@@ -324,6 +343,7 @@ int main(int argc, char *argv[])
 | 
			
		||||
 | 
			
		||||
	event_pipe_init();
 | 
			
		||||
	event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted);
 | 
			
		||||
	event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_event_emitted);
 | 
			
		||||
 | 
			
		||||
	path_global_init();
 | 
			
		||||
	glue_mapper_init();
 | 
			
		||||
@@ -371,7 +391,7 @@ int main(int argc, char *argv[])
 | 
			
		||||
		   database */
 | 
			
		||||
		unsigned job = update_enqueue(NULL, true);
 | 
			
		||||
		if (job == 0)
 | 
			
		||||
			g_error("directory update failed");
 | 
			
		||||
			MPD_ERROR("directory update failed");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	glue_state_file_init();
 | 
			
		||||
@@ -392,10 +412,17 @@ int main(int argc, char *argv[])
 | 
			
		||||
	   playlist_state_restore() */
 | 
			
		||||
	pc_update_audio();
 | 
			
		||||
 | 
			
		||||
	/* run the main loop */
 | 
			
		||||
#ifdef WIN32
 | 
			
		||||
	win32_app_started();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	/* run the main loop */
 | 
			
		||||
	g_main_loop_run(main_loop);
 | 
			
		||||
 | 
			
		||||
#ifdef WIN32
 | 
			
		||||
	win32_app_stopping();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	/* cleanup */
 | 
			
		||||
 | 
			
		||||
	g_main_loop_unref(main_loop);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								src/main.h
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								src/main.h
									
									
									
									
									
								
							@@ -28,4 +28,45 @@ extern GMainLoop *main_loop;
 | 
			
		||||
 | 
			
		||||
extern GCond *main_cond;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A entry point for application.
 | 
			
		||||
 * On non-Windows platforms this is called directly from main()
 | 
			
		||||
 * On Windows platform this is called from win32_main()
 | 
			
		||||
 * after doing some initialization.
 | 
			
		||||
 */
 | 
			
		||||
int mpd_main(int argc, char *argv[]);
 | 
			
		||||
 | 
			
		||||
#ifdef WIN32
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * If program is run as windows service performs nessesary initialization
 | 
			
		||||
 * and then calls mpd_main() with specified arguments.
 | 
			
		||||
 * If program is run as a regular application calls mpd_main() immediately.
 | 
			
		||||
 */
 | 
			
		||||
int
 | 
			
		||||
win32_main(int argc, char *argv[]);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * When running as a service reports to service control manager
 | 
			
		||||
 * that our service is started.
 | 
			
		||||
 * When running as a console application enables console handler that will
 | 
			
		||||
 * trigger PIPE_EVENT_SHUTDOWN when user closes console window
 | 
			
		||||
 * or presses Ctrl+C.
 | 
			
		||||
 * This function should be called just before entering main loop.
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
win32_app_started(void);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * When running as a service reports to service control manager
 | 
			
		||||
 * that our service is about to stop.
 | 
			
		||||
 * When running as a console application enables console handler that will
 | 
			
		||||
 * catch all shutdown requests and ignore them.
 | 
			
		||||
 * This function should be called just after leaving main loop.
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
win32_app_stopping(void);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										155
									
								
								src/main_win32.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								src/main_win32.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "main.h"
 | 
			
		||||
 | 
			
		||||
#ifdef WIN32
 | 
			
		||||
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
#include "event_pipe.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
#define WINVER 0x0501
 | 
			
		||||
#include <windows.h>
 | 
			
		||||
 | 
			
		||||
static int service_argc;
 | 
			
		||||
static char **service_argv;
 | 
			
		||||
static char service_name[] = "";
 | 
			
		||||
static BOOL ignore_console_events;
 | 
			
		||||
static SERVICE_STATUS_HANDLE service_handle;
 | 
			
		||||
 | 
			
		||||
static void WINAPI
 | 
			
		||||
service_main(DWORD argc, CHAR *argv[]);
 | 
			
		||||
 | 
			
		||||
static SERVICE_TABLE_ENTRY service_registry[] = {
 | 
			
		||||
	{service_name, service_main},
 | 
			
		||||
	{NULL, NULL}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
service_notify_status(DWORD status_code)
 | 
			
		||||
{
 | 
			
		||||
	SERVICE_STATUS current_status;
 | 
			
		||||
 | 
			
		||||
	current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
 | 
			
		||||
	current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING
 | 
			
		||||
		? 0
 | 
			
		||||
		: SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
 | 
			
		||||
 | 
			
		||||
	current_status.dwCurrentState = status_code;
 | 
			
		||||
	current_status.dwWin32ExitCode = NO_ERROR;
 | 
			
		||||
	current_status.dwCheckPoint = 0;
 | 
			
		||||
	current_status.dwWaitHint = 1000;
 | 
			
		||||
 | 
			
		||||
	SetServiceStatus(service_handle, ¤t_status);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static DWORD WINAPI
 | 
			
		||||
service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type,
 | 
			
		||||
		   G_GNUC_UNUSED void *event_data, G_GNUC_UNUSED void *context)
 | 
			
		||||
{
 | 
			
		||||
	switch (control) {
 | 
			
		||||
	case SERVICE_CONTROL_SHUTDOWN:
 | 
			
		||||
	case SERVICE_CONTROL_STOP:
 | 
			
		||||
		event_pipe_emit(PIPE_EVENT_SHUTDOWN);
 | 
			
		||||
		return NO_ERROR;
 | 
			
		||||
	default:
 | 
			
		||||
		return NO_ERROR;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WINAPI
 | 
			
		||||
service_main(G_GNUC_UNUSED DWORD argc, G_GNUC_UNUSED CHAR *argv[])
 | 
			
		||||
{
 | 
			
		||||
	DWORD error_code;
 | 
			
		||||
	gchar* error_message;
 | 
			
		||||
 | 
			
		||||
	service_handle =
 | 
			
		||||
		RegisterServiceCtrlHandlerEx(service_name,
 | 
			
		||||
					     service_dispatcher, NULL);
 | 
			
		||||
 | 
			
		||||
	if (service_handle == 0) {
 | 
			
		||||
		error_code = GetLastError();
 | 
			
		||||
		error_message = g_win32_error_message(error_code);
 | 
			
		||||
		MPD_ERROR("RegisterServiceCtrlHandlerEx() failed: %s",
 | 
			
		||||
			  error_message);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	service_notify_status(SERVICE_START_PENDING);
 | 
			
		||||
	mpd_main(service_argc, service_argv);
 | 
			
		||||
	service_notify_status(SERVICE_STOPPED);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static BOOL WINAPI
 | 
			
		||||
console_handler(DWORD event)
 | 
			
		||||
{
 | 
			
		||||
	switch (event) {
 | 
			
		||||
	case CTRL_C_EVENT:
 | 
			
		||||
	case CTRL_CLOSE_EVENT:
 | 
			
		||||
		if (!ignore_console_events)
 | 
			
		||||
			event_pipe_emit(PIPE_EVENT_SHUTDOWN);
 | 
			
		||||
		return TRUE;
 | 
			
		||||
	default:
 | 
			
		||||
		return FALSE;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int win32_main(int argc, char *argv[])
 | 
			
		||||
{
 | 
			
		||||
	DWORD error_code;
 | 
			
		||||
	gchar* error_message;
 | 
			
		||||
 | 
			
		||||
	service_argc = argc;
 | 
			
		||||
	service_argv = argv;
 | 
			
		||||
 | 
			
		||||
	if (StartServiceCtrlDispatcher(service_registry))
 | 
			
		||||
		return 0; /* run as service successefully */
 | 
			
		||||
 | 
			
		||||
	error_code = GetLastError();
 | 
			
		||||
	if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
 | 
			
		||||
		/* running as console app */
 | 
			
		||||
		SetConsoleTitle("Music Player Daemon");
 | 
			
		||||
		ignore_console_events = TRUE;
 | 
			
		||||
		SetConsoleCtrlHandler(console_handler, TRUE);
 | 
			
		||||
		return mpd_main(argc, argv);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	error_message = g_win32_error_message(error_code);
 | 
			
		||||
	MPD_ERROR("StartServiceCtrlDispatcher() failed: %s", error_message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void win32_app_started()
 | 
			
		||||
{
 | 
			
		||||
	if (service_handle != 0)
 | 
			
		||||
		service_notify_status(SERVICE_RUNNING);
 | 
			
		||||
	else
 | 
			
		||||
		ignore_console_events = FALSE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void win32_app_stopping()
 | 
			
		||||
{
 | 
			
		||||
	if (service_handle != 0)
 | 
			
		||||
		service_notify_status(SERVICE_STOP_PENDING);
 | 
			
		||||
	else
 | 
			
		||||
		ignore_console_events = TRUE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										114
									
								
								src/mixer/winmm_mixer_plugin.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/mixer/winmm_mixer_plugin.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "mixer_api.h"
 | 
			
		||||
#include "output_api.h"
 | 
			
		||||
#include "output/winmm_output_plugin.h"
 | 
			
		||||
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <math.h>
 | 
			
		||||
#include <windows.h>
 | 
			
		||||
 | 
			
		||||
#undef G_LOG_DOMAIN
 | 
			
		||||
#define G_LOG_DOMAIN "winmm_mixer"
 | 
			
		||||
 | 
			
		||||
struct winmm_mixer {
 | 
			
		||||
	struct mixer base;
 | 
			
		||||
	struct winmm_output *output;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static inline GQuark
 | 
			
		||||
winmm_mixer_quark(void)
 | 
			
		||||
{
 | 
			
		||||
	return g_quark_from_static_string("winmm_mixer");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline int
 | 
			
		||||
winmm_volume_decode(DWORD volume)
 | 
			
		||||
{
 | 
			
		||||
	return lround((volume & 0xFFFF) / 655.35);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline DWORD
 | 
			
		||||
winmm_volume_encode(int volume)
 | 
			
		||||
{
 | 
			
		||||
	int value = lround(volume * 655.35);
 | 
			
		||||
	return MAKELONG(value, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct mixer *
 | 
			
		||||
winmm_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param,
 | 
			
		||||
		 G_GNUC_UNUSED GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	assert(ao != NULL);
 | 
			
		||||
	
 | 
			
		||||
	struct winmm_mixer *wm = g_new(struct winmm_mixer, 1);
 | 
			
		||||
	mixer_init(&wm->base, &winmm_mixer_plugin);
 | 
			
		||||
	wm->output = (struct winmm_output *) ao;
 | 
			
		||||
	
 | 
			
		||||
	return &wm->base;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
winmm_mixer_finish(struct mixer *data)
 | 
			
		||||
{
 | 
			
		||||
	g_free(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
winmm_mixer_get_volume(struct mixer *mixer, GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	struct winmm_mixer *wm = (struct winmm_mixer *) mixer;
 | 
			
		||||
	DWORD volume;
 | 
			
		||||
	HWAVEOUT handle = winmm_output_get_handle(wm->output);
 | 
			
		||||
	MMRESULT result = waveOutGetVolume(handle, &volume);
 | 
			
		||||
	
 | 
			
		||||
	if (result != MMSYSERR_NOERROR) {
 | 
			
		||||
		g_set_error(error_r, 0, winmm_mixer_quark(),
 | 
			
		||||
			    "Failed to get winmm volume");
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	return winmm_volume_decode(volume);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
winmm_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	struct winmm_mixer *wm = (struct winmm_mixer *) mixer;
 | 
			
		||||
	DWORD value = winmm_volume_encode(volume);
 | 
			
		||||
	HWAVEOUT handle = winmm_output_get_handle(wm->output);
 | 
			
		||||
	MMRESULT result = waveOutSetVolume(handle, value);
 | 
			
		||||
 | 
			
		||||
	if (result != MMSYSERR_NOERROR) {
 | 
			
		||||
		g_set_error(error_r, 0, winmm_mixer_quark(),
 | 
			
		||||
			    "Failed to set winmm volume");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const struct mixer_plugin winmm_mixer_plugin = {
 | 
			
		||||
	.init = winmm_mixer_init,
 | 
			
		||||
	.finish = winmm_mixer_finish,
 | 
			
		||||
	.get_volume = winmm_mixer_get_volume,
 | 
			
		||||
	.set_volume = winmm_mixer_set_volume,
 | 
			
		||||
};
 | 
			
		||||
@@ -29,5 +29,6 @@ extern const struct mixer_plugin software_mixer_plugin;
 | 
			
		||||
extern const struct mixer_plugin alsa_mixer_plugin;
 | 
			
		||||
extern const struct mixer_plugin oss_mixer_plugin;
 | 
			
		||||
extern const struct mixer_plugin pulse_mixer_plugin;
 | 
			
		||||
extern const struct mixer_plugin winmm_mixer_plugin;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								src/mpd_error.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/mpd_error.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef MPD_ERROR_H
 | 
			
		||||
#define MPD_ERROR_H
 | 
			
		||||
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
/* This macro is used as an intermediate step to a proper error handling
 | 
			
		||||
 * using GError in mpd. It is used for unrecoverable error conditions
 | 
			
		||||
 * and exits immediately. The long-term goal is to replace this macro by
 | 
			
		||||
 * proper error handling. */
 | 
			
		||||
 | 
			
		||||
#define MPD_ERROR(...) \
 | 
			
		||||
	do { \
 | 
			
		||||
		g_critical(__VA_ARGS__); \
 | 
			
		||||
		exit(EXIT_FAILURE); \
 | 
			
		||||
	} while(0)
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -49,3 +49,10 @@ void notify_signal(struct notify *notify)
 | 
			
		||||
	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);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -45,4 +45,9 @@ void notify_wait(struct notify *notify);
 | 
			
		||||
 */
 | 
			
		||||
void notify_signal(struct notify *notify);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Clears a pending notification.
 | 
			
		||||
 */
 | 
			
		||||
void notify_clear(struct notify *notify);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -408,6 +408,26 @@ configure_hw:
 | 
			
		||||
	}
 | 
			
		||||
	audio_format->sample_rate = sample_rate;
 | 
			
		||||
 | 
			
		||||
	snd_pcm_uframes_t buffer_size_min, buffer_size_max;
 | 
			
		||||
	snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
 | 
			
		||||
	snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
 | 
			
		||||
	unsigned buffer_time_min, buffer_time_max;
 | 
			
		||||
	snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0);
 | 
			
		||||
	snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0);
 | 
			
		||||
	g_debug("buffer: size=%u..%u time=%u..%u",
 | 
			
		||||
		(unsigned)buffer_size_min, (unsigned)buffer_size_max,
 | 
			
		||||
		buffer_time_min, buffer_time_max);
 | 
			
		||||
 | 
			
		||||
	snd_pcm_uframes_t period_size_min, period_size_max;
 | 
			
		||||
	snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0);
 | 
			
		||||
	snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0);
 | 
			
		||||
	unsigned period_time_min, period_time_max;
 | 
			
		||||
	snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0);
 | 
			
		||||
	snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0);
 | 
			
		||||
	g_debug("period: size=%u..%u time=%u..%u",
 | 
			
		||||
		(unsigned)period_size_min, (unsigned)period_size_max,
 | 
			
		||||
		period_time_min, period_time_max);
 | 
			
		||||
 | 
			
		||||
	if (ad->buffer_time > 0) {
 | 
			
		||||
		buffer_time = ad->buffer_time;
 | 
			
		||||
		cmd = "snd_pcm_hw_params_set_buffer_time_near";
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										347
									
								
								src/output/ffado_output_plugin.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										347
									
								
								src/output/ffado_output_plugin.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,347 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Warning: this plugin was not tested successfully.  I just couldn't
 | 
			
		||||
 * keep libffado2 from crashing.  Use at your own risk.
 | 
			
		||||
 *
 | 
			
		||||
 * For details, see my Debian bug reports:
 | 
			
		||||
 *
 | 
			
		||||
 *   http://bugs.debian.org/601657
 | 
			
		||||
 *   http://bugs.debian.org/601659
 | 
			
		||||
 *   http://bugs.debian.org/601663
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "output_api.h"
 | 
			
		||||
#include "timer.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
 | 
			
		||||
#include <libffado/ffado.h>
 | 
			
		||||
 | 
			
		||||
#undef G_LOG_DOMAIN
 | 
			
		||||
#define G_LOG_DOMAIN "ffado"
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
	MAX_STREAMS = 8,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct mpd_ffado_stream {
 | 
			
		||||
	/** libffado's stream number */
 | 
			
		||||
	int number;
 | 
			
		||||
 | 
			
		||||
	float *buffer;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct mpd_ffado_device {
 | 
			
		||||
	char *device_name;
 | 
			
		||||
	int verbose;
 | 
			
		||||
	unsigned period_size, nb_buffers;
 | 
			
		||||
 | 
			
		||||
	ffado_device_t *dev;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The current sample position inside the stream buffers.  New
 | 
			
		||||
	 * samples get appended at this position on all streams at the
 | 
			
		||||
	 * same time.  When the buffers are full
 | 
			
		||||
	 * (buffer_position==period_size),
 | 
			
		||||
	 * ffado_streaming_transfer_playback_buffers() gets called to
 | 
			
		||||
	 * hand them over to libffado.
 | 
			
		||||
	 */
 | 
			
		||||
	unsigned buffer_position;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The number of streams which are really used by MPD.
 | 
			
		||||
	 */
 | 
			
		||||
	int num_streams;
 | 
			
		||||
	struct mpd_ffado_stream streams[MAX_STREAMS];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static inline GQuark
 | 
			
		||||
ffado_output_quark(void)
 | 
			
		||||
{
 | 
			
		||||
	return g_quark_from_static_string("ffado_output");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void *
 | 
			
		||||
ffado_init(G_GNUC_UNUSED const struct audio_format *audio_format,
 | 
			
		||||
	   const struct config_param *param,
 | 
			
		||||
	   GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	g_debug("using libffado version %s, API=%d",
 | 
			
		||||
		ffado_get_version(), ffado_get_api_version());
 | 
			
		||||
 | 
			
		||||
	struct mpd_ffado_device *fd = g_new(struct mpd_ffado_device, 1);
 | 
			
		||||
	fd->device_name = config_dup_block_string(param, "device", NULL);
 | 
			
		||||
	fd->verbose = config_get_block_unsigned(param, "verbose", 0);
 | 
			
		||||
 | 
			
		||||
	fd->period_size = config_get_block_unsigned(param, "period_size",
 | 
			
		||||
						    1024);
 | 
			
		||||
	if (fd->period_size == 0 || fd->period_size > 1024 * 1024) {
 | 
			
		||||
		g_set_error(error_r, ffado_output_quark(), 0,
 | 
			
		||||
			    "invalid period_size setting");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3);
 | 
			
		||||
	if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) {
 | 
			
		||||
		g_set_error(error_r, ffado_output_quark(), 0,
 | 
			
		||||
			    "invalid nb_buffers setting");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
ffado_finish(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct mpd_ffado_device *fd = data;
 | 
			
		||||
 | 
			
		||||
	g_free(fd->device_name);
 | 
			
		||||
	g_free(fd);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
ffado_configure_stream(ffado_device_t *dev, struct mpd_ffado_stream *stream,
 | 
			
		||||
		       GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	char *buffer = (char *)stream->buffer;
 | 
			
		||||
	if (ffado_streaming_set_playback_stream_buffer(dev, stream->number,
 | 
			
		||||
						       buffer) != 0) {
 | 
			
		||||
		g_set_error(error_r, ffado_output_quark(), 0,
 | 
			
		||||
			    "failed to configure stream buffer");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ffado_streaming_playback_stream_onoff(dev, stream->number,
 | 
			
		||||
						  1) != 0) {
 | 
			
		||||
		g_set_error(error_r, ffado_output_quark(), 0,
 | 
			
		||||
			    "failed to disable stream");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
ffado_configure(struct mpd_ffado_device *fd, struct audio_format *audio_format,
 | 
			
		||||
		GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	assert(fd != NULL);
 | 
			
		||||
	assert(fd->dev != NULL);
 | 
			
		||||
	assert(audio_format->channels <= MAX_STREAMS);
 | 
			
		||||
 | 
			
		||||
	if (ffado_streaming_set_audio_datatype(fd->dev,
 | 
			
		||||
					       ffado_audio_datatype_float) != 0) {
 | 
			
		||||
		g_set_error(error_r, ffado_output_quark(), 0,
 | 
			
		||||
			    "ffado_streaming_set_audio_datatype() failed");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	int num_streams = ffado_streaming_get_nb_playback_streams(fd->dev);
 | 
			
		||||
	if (num_streams < 0) {
 | 
			
		||||
		g_set_error(error_r, ffado_output_quark(), 0,
 | 
			
		||||
			    "ffado_streaming_get_nb_playback_streams() failed");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g_debug("there are %d playback streams", num_streams);
 | 
			
		||||
 | 
			
		||||
	fd->num_streams = 0;
 | 
			
		||||
	for (int i = 0; i < num_streams; ++i) {
 | 
			
		||||
		char name[256];
 | 
			
		||||
		ffado_streaming_get_playback_stream_name(fd->dev, i, name,
 | 
			
		||||
							 sizeof(name) - 1);
 | 
			
		||||
 | 
			
		||||
		ffado_streaming_stream_type type =
 | 
			
		||||
			ffado_streaming_get_playback_stream_type(fd->dev, i);
 | 
			
		||||
		if (type != ffado_stream_type_audio) {
 | 
			
		||||
			g_debug("stream %d name='%s': not an audio stream",
 | 
			
		||||
				i, name);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (fd->num_streams >= audio_format->channels) {
 | 
			
		||||
			g_debug("stream %d name='%s': ignoring",
 | 
			
		||||
				i, name);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		g_debug("stream %d name='%s'", i, name);
 | 
			
		||||
 | 
			
		||||
		struct mpd_ffado_stream *stream =
 | 
			
		||||
			&fd->streams[fd->num_streams++];
 | 
			
		||||
 | 
			
		||||
		stream->number = i;
 | 
			
		||||
 | 
			
		||||
		/* allocated buffer is zeroed = silence */
 | 
			
		||||
		stream->buffer = g_new0(float, fd->period_size);
 | 
			
		||||
 | 
			
		||||
		if (!ffado_configure_stream(fd->dev, stream, error_r))
 | 
			
		||||
			return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!audio_valid_channel_count(fd->num_streams)) {
 | 
			
		||||
		g_set_error(error_r, ffado_output_quark(), 0,
 | 
			
		||||
			    "invalid channel count from libffado: %u",
 | 
			
		||||
			    audio_format->channels);
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g_debug("configured %d audio streams", fd->num_streams);
 | 
			
		||||
 | 
			
		||||
	if (ffado_streaming_prepare(fd->dev) != 0) {
 | 
			
		||||
		g_set_error(error_r, ffado_output_quark(), 0,
 | 
			
		||||
			    "ffado_streaming_prepare() failed");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ffado_streaming_start(fd->dev) != 0) {
 | 
			
		||||
		g_set_error(error_r, ffado_output_quark(), 0,
 | 
			
		||||
			    "ffado_streaming_start() failed");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	audio_format->channels = fd->num_streams;
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
ffado_open(void *data, struct audio_format *audio_format, GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	struct mpd_ffado_device *fd = data;
 | 
			
		||||
 | 
			
		||||
	/* will be converted to floating point, choose best input
 | 
			
		||||
	   format */
 | 
			
		||||
	audio_format->format = SAMPLE_FORMAT_S24_P32;
 | 
			
		||||
 | 
			
		||||
	ffado_device_info_t device_info;
 | 
			
		||||
	memset(&device_info, 0, sizeof(device_info));
 | 
			
		||||
	if (fd->device_name != NULL) {
 | 
			
		||||
		device_info.nb_device_spec_strings = 1;
 | 
			
		||||
		device_info.device_spec_strings = &fd->device_name;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ffado_options_t options;
 | 
			
		||||
	memset(&options, 0, sizeof(options));
 | 
			
		||||
	options.sample_rate = audio_format->sample_rate;
 | 
			
		||||
	options.period_size = fd->period_size;
 | 
			
		||||
	options.nb_buffers = fd->nb_buffers;
 | 
			
		||||
	options.verbose = fd->verbose;
 | 
			
		||||
 | 
			
		||||
	fd->dev = ffado_streaming_init(device_info, options);
 | 
			
		||||
	if (fd->dev == NULL) {
 | 
			
		||||
		g_set_error(error_r, ffado_output_quark(), 0,
 | 
			
		||||
			    "ffado_streaming_init() failed");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!ffado_configure(fd, audio_format, error_r)) {
 | 
			
		||||
		ffado_streaming_finish(fd->dev);
 | 
			
		||||
 | 
			
		||||
		for (int i = 0; i < fd->num_streams; ++i) {
 | 
			
		||||
			struct mpd_ffado_stream *stream = &fd->streams[i];
 | 
			
		||||
			g_free(stream->buffer);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fd->buffer_position = 0;
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
ffado_close(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct mpd_ffado_device *fd = data;
 | 
			
		||||
 | 
			
		||||
	ffado_streaming_stop(fd->dev);
 | 
			
		||||
	ffado_streaming_finish(fd->dev);
 | 
			
		||||
 | 
			
		||||
	for (int i = 0; i < fd->num_streams; ++i) {
 | 
			
		||||
		struct mpd_ffado_stream *stream = &fd->streams[i];
 | 
			
		||||
		g_free(stream->buffer);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static size_t
 | 
			
		||||
ffado_play(void *data, const void *chunk, size_t size, GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	struct mpd_ffado_device *fd = data;
 | 
			
		||||
 | 
			
		||||
	/* wait for prefious buffer to finish (if it was full) */
 | 
			
		||||
 | 
			
		||||
	if (fd->buffer_position >= fd->period_size) {
 | 
			
		||||
		switch (ffado_streaming_wait(fd->dev)) {
 | 
			
		||||
		case ffado_wait_ok:
 | 
			
		||||
		case ffado_wait_xrun:
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		default:
 | 
			
		||||
			g_set_error(error_r, ffado_output_quark(), 0,
 | 
			
		||||
				    "ffado_streaming_wait() failed");
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fd->buffer_position = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* copy samples to stream buffers, non-interleaved */
 | 
			
		||||
 | 
			
		||||
	const int32_t *p = chunk;
 | 
			
		||||
	unsigned num_frames = size / sizeof(*p) / fd->num_streams;
 | 
			
		||||
	if (num_frames > fd->period_size - fd->buffer_position)
 | 
			
		||||
		num_frames = fd->period_size - fd->buffer_position;
 | 
			
		||||
 | 
			
		||||
	for (unsigned i = num_frames; i > 0; --i) {
 | 
			
		||||
		for (int stream = 0; stream < fd->num_streams; ++stream)
 | 
			
		||||
			fd->streams[stream].buffer[fd->buffer_position] =
 | 
			
		||||
				*p++ / (float)(1 << 23);
 | 
			
		||||
		++fd->buffer_position;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* if buffer full, transfer to device */
 | 
			
		||||
 | 
			
		||||
	if (fd->buffer_position >= fd->period_size &&
 | 
			
		||||
	    /* libffado documentation says this function returns -1 on
 | 
			
		||||
	       error, but that is a lie - it returns a boolean value,
 | 
			
		||||
	       and "false" means error */
 | 
			
		||||
	    !ffado_streaming_transfer_playback_buffers(fd->dev)) {
 | 
			
		||||
		g_set_error(error_r, ffado_output_quark(), 0,
 | 
			
		||||
			    "ffado_streaming_transfer_playback_buffers() failed");
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return num_frames * sizeof(*p) * fd->num_streams;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const struct audio_output_plugin ffado_output_plugin = {
 | 
			
		||||
	.name = "ffado",
 | 
			
		||||
	.init = ffado_init,
 | 
			
		||||
	.finish = ffado_finish,
 | 
			
		||||
	.open = ffado_open,
 | 
			
		||||
	.close = ffado_close,
 | 
			
		||||
	.play = ffado_play,
 | 
			
		||||
};
 | 
			
		||||
@@ -29,6 +29,9 @@
 | 
			
		||||
#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.
 | 
			
		||||
 
 | 
			
		||||
@@ -29,12 +29,6 @@
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
#ifdef WIN32
 | 
			
		||||
#include <winsock2.h>
 | 
			
		||||
#include <ws2tcpip.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
#endif
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
struct httpd_client;
 | 
			
		||||
@@ -51,21 +45,19 @@ struct httpd_output {
 | 
			
		||||
	 */
 | 
			
		||||
	struct encoder *encoder;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Number of bytes which were fed into the encoder, without
 | 
			
		||||
	 * ever receiving new output.  This is used to estimate
 | 
			
		||||
	 * whether MPD should manually flush the encoder, to avoid
 | 
			
		||||
	 * buffer underruns in the client.
 | 
			
		||||
	 */
 | 
			
		||||
	size_t unflushed_input;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The MIME type produced by the #encoder.
 | 
			
		||||
	 */
 | 
			
		||||
	const char *content_type;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The configured address of the listener socket.
 | 
			
		||||
	 */
 | 
			
		||||
	struct sockaddr_storage address;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The size of #address.
 | 
			
		||||
	 */
 | 
			
		||||
	socklen_t address_size;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * This mutex protects the listener socket and the client
 | 
			
		||||
	 * list.
 | 
			
		||||
@@ -81,12 +73,7 @@ struct httpd_output {
 | 
			
		||||
	/**
 | 
			
		||||
	 * The listener socket.
 | 
			
		||||
	 */
 | 
			
		||||
	int fd;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * A GLib main loop source id for the listener socket.
 | 
			
		||||
	 */
 | 
			
		||||
	guint source_id;
 | 
			
		||||
	struct server_socket *server_socket;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The header page, which is sent to every client on connect.
 | 
			
		||||
 
 | 
			
		||||
@@ -27,17 +27,11 @@
 | 
			
		||||
#include "page.h"
 | 
			
		||||
#include "icy_server.h"
 | 
			
		||||
#include "fd_util.h"
 | 
			
		||||
#include "server_socket.h"
 | 
			
		||||
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#ifdef WIN32
 | 
			
		||||
#include <winsock2.h>
 | 
			
		||||
#include <ws2tcpip.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <netinet/in.h>
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#endif
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
 | 
			
		||||
@@ -57,37 +51,20 @@ httpd_output_quark(void)
 | 
			
		||||
	return g_quark_from_static_string("httpd_output");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static gboolean
 | 
			
		||||
httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
 | 
			
		||||
		      G_GNUC_UNUSED GIOCondition condition,
 | 
			
		||||
		      gpointer data);
 | 
			
		||||
static void
 | 
			
		||||
httpd_listen_in_event(int fd, const struct sockaddr *address,
 | 
			
		||||
		      size_t address_length, int uid, void *ctx);
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
httpd_output_bind(struct httpd_output *httpd, GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	GIOChannel *channel;
 | 
			
		||||
 | 
			
		||||
	httpd->open = false;
 | 
			
		||||
 | 
			
		||||
	/* create and set up listener socket */
 | 
			
		||||
 | 
			
		||||
	httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0,
 | 
			
		||||
				       (struct sockaddr *)&httpd->address,
 | 
			
		||||
				       httpd->address_size,
 | 
			
		||||
				       16, error_r);
 | 
			
		||||
	if (httpd->fd < 0)
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	g_mutex_lock(httpd->mutex);
 | 
			
		||||
 | 
			
		||||
	channel = g_io_channel_unix_new(httpd->fd);
 | 
			
		||||
	httpd->source_id = g_io_add_watch(channel, G_IO_IN,
 | 
			
		||||
					  httpd_listen_in_event, httpd);
 | 
			
		||||
	g_io_channel_unref(channel);
 | 
			
		||||
 | 
			
		||||
	bool success = server_socket_open(httpd->server_socket, error_r);
 | 
			
		||||
	g_mutex_unlock(httpd->mutex);
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
	return success;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
@@ -96,10 +73,7 @@ httpd_output_unbind(struct httpd_output *httpd)
 | 
			
		||||
	assert(!httpd->open);
 | 
			
		||||
 | 
			
		||||
	g_mutex_lock(httpd->mutex);
 | 
			
		||||
 | 
			
		||||
	g_source_remove(httpd->source_id);
 | 
			
		||||
	close(httpd->fd);
 | 
			
		||||
 | 
			
		||||
	server_socket_close(httpd->server_socket);
 | 
			
		||||
	g_mutex_unlock(httpd->mutex);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -109,10 +83,9 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
 | 
			
		||||
		  GError **error)
 | 
			
		||||
{
 | 
			
		||||
	struct httpd_output *httpd = g_new(struct httpd_output, 1);
 | 
			
		||||
	const char *encoder_name;
 | 
			
		||||
	const char *encoder_name, *bind_to_address;
 | 
			
		||||
	const struct encoder_plugin *encoder_plugin;
 | 
			
		||||
	guint port;
 | 
			
		||||
	struct sockaddr_in *sin;
 | 
			
		||||
 | 
			
		||||
	/* read configuration */
 | 
			
		||||
	httpd->name =
 | 
			
		||||
@@ -134,14 +107,19 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
 | 
			
		||||
 | 
			
		||||
	httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0);
 | 
			
		||||
 | 
			
		||||
	/* initialize listen address */
 | 
			
		||||
	/* set up bind_to_address */
 | 
			
		||||
 | 
			
		||||
	sin = (struct sockaddr_in *)&httpd->address;
 | 
			
		||||
	memset(sin, 0, sizeof(sin));
 | 
			
		||||
	sin->sin_port = htons(port);
 | 
			
		||||
	sin->sin_family = AF_INET;
 | 
			
		||||
	sin->sin_addr.s_addr = INADDR_ANY;
 | 
			
		||||
	httpd->address_size = sizeof(*sin);
 | 
			
		||||
	httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd);
 | 
			
		||||
 | 
			
		||||
	bind_to_address =
 | 
			
		||||
		config_get_block_string(param, "bind_to_address", NULL);
 | 
			
		||||
	bool success = bind_to_address != NULL &&
 | 
			
		||||
		strcmp(bind_to_address, "any") != 0
 | 
			
		||||
		? server_socket_add_host(httpd->server_socket, bind_to_address,
 | 
			
		||||
					 port, error)
 | 
			
		||||
		: server_socket_add_port(httpd->server_socket, port, error);
 | 
			
		||||
	if (!success)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	/* initialize metadata */
 | 
			
		||||
	httpd->metadata = NULL;
 | 
			
		||||
@@ -172,6 +150,7 @@ httpd_output_finish(void *data)
 | 
			
		||||
		page_unref(httpd->metadata);
 | 
			
		||||
 | 
			
		||||
	encoder_finish(httpd->encoder);
 | 
			
		||||
	server_socket_free(httpd->server_socket);
 | 
			
		||||
	g_mutex_free(httpd->mutex);
 | 
			
		||||
	g_free(httpd);
 | 
			
		||||
}
 | 
			
		||||
@@ -195,27 +174,18 @@ httpd_client_add(struct httpd_output *httpd, int fd)
 | 
			
		||||
		httpd_client_send_metadata(client, httpd->metadata);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static gboolean
 | 
			
		||||
httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
 | 
			
		||||
		      G_GNUC_UNUSED GIOCondition condition,
 | 
			
		||||
		      gpointer data)
 | 
			
		||||
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 = data;
 | 
			
		||||
	int fd;
 | 
			
		||||
	struct sockaddr_storage sa;
 | 
			
		||||
	size_t sa_length = sizeof(sa);
 | 
			
		||||
 | 
			
		||||
	g_mutex_lock(httpd->mutex);
 | 
			
		||||
	struct httpd_output *httpd = ctx;
 | 
			
		||||
 | 
			
		||||
	/* the listener socket has become readable - a client has
 | 
			
		||||
	   connected */
 | 
			
		||||
 | 
			
		||||
	fd = accept_cloexec_nonblock(httpd->fd, (struct sockaddr*)&sa,
 | 
			
		||||
				     &sa_length);
 | 
			
		||||
#ifdef HAVE_LIBWRAP
 | 
			
		||||
	struct sockaddr *sa_p = (struct sockaddr *)&sa;
 | 
			
		||||
	if (sa_p->sa_family != AF_UNIX) {
 | 
			
		||||
          char *hostaddr = sockaddr_to_string(sa_p, sa_length, NULL);
 | 
			
		||||
	if (address->sa_family != AF_UNIX) {
 | 
			
		||||
		char *hostaddr = sockaddr_to_string(address, address_length, NULL);
 | 
			
		||||
		const char *progname = g_get_prgname();
 | 
			
		||||
 | 
			
		||||
		struct request_info req;
 | 
			
		||||
@@ -230,12 +200,18 @@ httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
 | 
			
		||||
			g_free(hostaddr);
 | 
			
		||||
			close(fd);
 | 
			
		||||
			g_mutex_unlock(httpd->mutex);
 | 
			
		||||
			return true;
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		g_free(hostaddr);
 | 
			
		||||
	}
 | 
			
		||||
#else
 | 
			
		||||
	(void)address;
 | 
			
		||||
	(void)address_length;
 | 
			
		||||
#endif	/* HAVE_WRAP */
 | 
			
		||||
 | 
			
		||||
	g_mutex_lock(httpd->mutex);
 | 
			
		||||
 | 
			
		||||
	if (fd >= 0) {
 | 
			
		||||
		/* can we allow additional client */
 | 
			
		||||
		if (httpd->open &&
 | 
			
		||||
@@ -249,8 +225,6 @@ httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g_mutex_unlock(httpd->mutex);
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -262,12 +236,22 @@ httpd_output_read_page(struct httpd_output *httpd)
 | 
			
		||||
{
 | 
			
		||||
	size_t size = 0, nbytes;
 | 
			
		||||
 | 
			
		||||
	if (httpd->unflushed_input >= 65536) {
 | 
			
		||||
		/* we have fed a lot of input into the encoder, but it
 | 
			
		||||
		   didn't give anything back yet - flush now to avoid
 | 
			
		||||
		   buffer underruns */
 | 
			
		||||
		encoder_flush(httpd->encoder, NULL);
 | 
			
		||||
		httpd->unflushed_input = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	do {
 | 
			
		||||
		nbytes = encoder_read(httpd->encoder, httpd->buffer + size,
 | 
			
		||||
				      sizeof(httpd->buffer) - size);
 | 
			
		||||
		if (nbytes == 0)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		httpd->unflushed_input = 0;
 | 
			
		||||
 | 
			
		||||
		size += nbytes;
 | 
			
		||||
	} while (size < sizeof(httpd->buffer));
 | 
			
		||||
 | 
			
		||||
@@ -292,6 +276,9 @@ httpd_output_encoder_open(struct httpd_output *httpd,
 | 
			
		||||
	   bytes of encoder output after opening it, because it has to
 | 
			
		||||
	   be sent to every new client */
 | 
			
		||||
	httpd->header = httpd_output_read_page(httpd);
 | 
			
		||||
 | 
			
		||||
	httpd->unflushed_input = 0;
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -324,8 +311,6 @@ httpd_output_open(void *data, struct audio_format *audio_format,
 | 
			
		||||
 | 
			
		||||
	success = httpd_output_encoder_open(httpd, audio_format, error);
 | 
			
		||||
	if (!success) {
 | 
			
		||||
		g_source_remove(httpd->source_id);
 | 
			
		||||
		close(httpd->fd);
 | 
			
		||||
		g_mutex_unlock(httpd->mutex);
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
@@ -390,6 +375,16 @@ httpd_output_send_header(struct httpd_output *httpd,
 | 
			
		||||
		httpd_client_send(client, httpd->header);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static unsigned
 | 
			
		||||
httpd_output_delay(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct httpd_output *httpd = data;
 | 
			
		||||
 | 
			
		||||
	return httpd->timer->started
 | 
			
		||||
		? timer_delay(httpd->timer)
 | 
			
		||||
		: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data)
 | 
			
		||||
{
 | 
			
		||||
@@ -451,6 +446,8 @@ httpd_output_encode_and_play(struct httpd_output *httpd,
 | 
			
		||||
	if (!success)
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	httpd->unflushed_input += size;
 | 
			
		||||
 | 
			
		||||
	httpd_output_encoder_to_clients(httpd);
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
@@ -477,13 +474,29 @@ httpd_output_play(void *data, const void *chunk, size_t size, GError **error)
 | 
			
		||||
 | 
			
		||||
	if (!httpd->timer->started)
 | 
			
		||||
		timer_start(httpd->timer);
 | 
			
		||||
	else
 | 
			
		||||
		timer_sync(httpd->timer);
 | 
			
		||||
	timer_add(httpd->timer, size);
 | 
			
		||||
 | 
			
		||||
	return size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
httpd_output_pause(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct httpd_output *httpd = data;
 | 
			
		||||
 | 
			
		||||
	g_mutex_lock(httpd->mutex);
 | 
			
		||||
	bool has_clients = httpd->clients != NULL;
 | 
			
		||||
	g_mutex_unlock(httpd->mutex);
 | 
			
		||||
 | 
			
		||||
	if (has_clients) {
 | 
			
		||||
		static const char silence[1020];
 | 
			
		||||
		return httpd_output_play(data, silence, sizeof(silence), NULL);
 | 
			
		||||
	} else {
 | 
			
		||||
		g_usleep(100000);
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
httpd_send_metadata(gpointer data, gpointer user_data)
 | 
			
		||||
{
 | 
			
		||||
@@ -570,7 +583,9 @@ const struct audio_output_plugin httpd_output_plugin = {
 | 
			
		||||
	.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,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -214,15 +214,6 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
 | 
			
		||||
	stream_description.mSampleRate = audio_format->sample_rate;
 | 
			
		||||
	stream_description.mFormatID = kAudioFormatLinearPCM;
 | 
			
		||||
	stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
 | 
			
		||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
 | 
			
		||||
	stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	stream_description.mBytesPerPacket =
 | 
			
		||||
		audio_format_frame_size(audio_format);
 | 
			
		||||
	stream_description.mFramesPerPacket = 1;
 | 
			
		||||
	stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
 | 
			
		||||
	stream_description.mChannelsPerFrame = audio_format->channels;
 | 
			
		||||
 | 
			
		||||
	switch (audio_format->format) {
 | 
			
		||||
	case SAMPLE_FORMAT_S8:
 | 
			
		||||
@@ -239,6 +230,16 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
 | 
			
		||||
	stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	stream_description.mBytesPerPacket =
 | 
			
		||||
		audio_format_frame_size(audio_format);
 | 
			
		||||
	stream_description.mFramesPerPacket = 1;
 | 
			
		||||
	stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
 | 
			
		||||
	stream_description.mChannelsPerFrame = audio_format->channels;
 | 
			
		||||
 | 
			
		||||
	result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
 | 
			
		||||
				      kAudioUnitScope_Input, 0,
 | 
			
		||||
				      &stream_description,
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@
 | 
			
		||||
#include "output_api.h"
 | 
			
		||||
#include "encoder_plugin.h"
 | 
			
		||||
#include "encoder_list.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <shout/shout.h>
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
@@ -101,8 +102,8 @@ static void free_shout_data(struct shout_data *sd)
 | 
			
		||||
#define check_block_param(name) {		  \
 | 
			
		||||
		block_param = config_get_block_param(param, name);	\
 | 
			
		||||
		if (!block_param) {					\
 | 
			
		||||
			g_error("no \"%s\" defined for shout device defined at line " \
 | 
			
		||||
				"%i\n", name, param->line);		\
 | 
			
		||||
			MPD_ERROR("no \"%s\" defined for shout device defined at line " \
 | 
			
		||||
				  "%i\n", name, param->line);		\
 | 
			
		||||
		}							\
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -341,7 +342,6 @@ write_page(struct shout_data *sd, GError **error)
 | 
			
		||||
	if (sd->buf.len == 0)
 | 
			
		||||
		return true;
 | 
			
		||||
 | 
			
		||||
	shout_sync(sd->shout_conn);
 | 
			
		||||
	err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len);
 | 
			
		||||
	if (!handle_shout_error(sd, err, error))
 | 
			
		||||
		return false;
 | 
			
		||||
@@ -440,6 +440,18 @@ my_shout_open_device(void *data, struct audio_format *audio_format,
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static unsigned
 | 
			
		||||
my_shout_delay(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct shout_data *sd = (struct shout_data *)data;
 | 
			
		||||
 | 
			
		||||
	int delay = shout_delay(sd->shout_conn);
 | 
			
		||||
	if (delay < 0)
 | 
			
		||||
		delay = 0;
 | 
			
		||||
 | 
			
		||||
	return delay;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static size_t
 | 
			
		||||
my_shout_play(void *data, const void *chunk, size_t size, GError **error)
 | 
			
		||||
{
 | 
			
		||||
@@ -454,15 +466,8 @@ my_shout_play(void *data, const void *chunk, size_t size, GError **error)
 | 
			
		||||
static bool
 | 
			
		||||
my_shout_pause(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct shout_data *sd = (struct shout_data *)data;
 | 
			
		||||
	static const char silence[1020];
 | 
			
		||||
 | 
			
		||||
	if (shout_delay(sd->shout_conn) > 500) {
 | 
			
		||||
		/* cap the latency for unpause */
 | 
			
		||||
		g_usleep(500000);
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return my_shout_play(data, silence, sizeof(silence), NULL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -489,7 +494,7 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	snprintf(dest, size, "%s - %s", title, artist);
 | 
			
		||||
	snprintf(dest, size, "%s - %s", artist, title);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void my_shout_set_tag(void *data,
 | 
			
		||||
@@ -539,6 +544,7 @@ const struct audio_output_plugin shoutPlugin = {
 | 
			
		||||
	.init = my_shout_init_driver,
 | 
			
		||||
	.finish = my_shout_finish_driver,
 | 
			
		||||
	.open = my_shout_open_device,
 | 
			
		||||
	.delay = my_shout_delay,
 | 
			
		||||
	.play = my_shout_play,
 | 
			
		||||
	.pause = my_shout_pause,
 | 
			
		||||
	.cancel = my_shout_drop_buffered_audio,
 | 
			
		||||
 
 | 
			
		||||
@@ -93,7 +93,7 @@ solaris_output_open(void *data, struct audio_format *audio_format,
 | 
			
		||||
 | 
			
		||||
	/* open the device in non-blocking mode */
 | 
			
		||||
 | 
			
		||||
	so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK);
 | 
			
		||||
	so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0);
 | 
			
		||||
	if (so->fd < 0) {
 | 
			
		||||
		g_set_error(error, solaris_output_quark(), errno,
 | 
			
		||||
			    "Failed to open %s: %s",
 | 
			
		||||
 
 | 
			
		||||
@@ -20,19 +20,24 @@
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "output_api.h"
 | 
			
		||||
#include "pcm_buffer.h"
 | 
			
		||||
#include "mixer_list.h"
 | 
			
		||||
#include "winmm_output_plugin.h"
 | 
			
		||||
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <windows.h>
 | 
			
		||||
 | 
			
		||||
#undef G_LOG_DOMAIN
 | 
			
		||||
#define G_LOG_DOMAIN "win32_output"
 | 
			
		||||
#define G_LOG_DOMAIN "winmm_output"
 | 
			
		||||
 | 
			
		||||
struct win32_buffer {
 | 
			
		||||
struct winmm_buffer {
 | 
			
		||||
	struct pcm_buffer buffer;
 | 
			
		||||
 | 
			
		||||
	WAVEHDR hdr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct win32_output {
 | 
			
		||||
struct winmm_output {
 | 
			
		||||
	UINT device_id;
 | 
			
		||||
	HWAVEOUT handle;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
@@ -41,7 +46,7 @@ struct win32_output {
 | 
			
		||||
	 */
 | 
			
		||||
	HANDLE event;
 | 
			
		||||
 | 
			
		||||
	struct win32_buffer buffers[8];
 | 
			
		||||
	struct winmm_buffer buffers[8];
 | 
			
		||||
	unsigned next_buffer;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -49,45 +54,80 @@ struct win32_output {
 | 
			
		||||
 * The quark used for GError.domain.
 | 
			
		||||
 */
 | 
			
		||||
static inline GQuark
 | 
			
		||||
win32_output_quark(void)
 | 
			
		||||
winmm_output_quark(void)
 | 
			
		||||
{
 | 
			
		||||
	return g_quark_from_static_string("win32_output");
 | 
			
		||||
	return g_quark_from_static_string("winmm_output");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
HWAVEOUT
 | 
			
		||||
winmm_output_get_handle(struct winmm_output* output)
 | 
			
		||||
{
 | 
			
		||||
	return output->handle;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
win32_output_test_default_device(void)
 | 
			
		||||
winmm_output_test_default_device(void)
 | 
			
		||||
{
 | 
			
		||||
	/* we assume that Wave is always available */
 | 
			
		||||
	return true;
 | 
			
		||||
	return waveOutGetNumDevs() > 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static UINT
 | 
			
		||||
get_device_id(const char *device_name)
 | 
			
		||||
{
 | 
			
		||||
	/* if device is not specified use wave mapper */
 | 
			
		||||
	if (device_name == NULL)
 | 
			
		||||
		return WAVE_MAPPER;
 | 
			
		||||
 | 
			
		||||
	/* check for device id */
 | 
			
		||||
	char *endptr;
 | 
			
		||||
	UINT id = strtoul(device_name, &endptr, 0);
 | 
			
		||||
	if (endptr > device_name && *endptr == 0)
 | 
			
		||||
		return id;
 | 
			
		||||
 | 
			
		||||
	/* check for device name */
 | 
			
		||||
	for (UINT i = 0; i < waveOutGetNumDevs(); i++) {
 | 
			
		||||
		WAVEOUTCAPS caps;
 | 
			
		||||
		MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps));
 | 
			
		||||
		if (result != MMSYSERR_NOERROR)
 | 
			
		||||
			continue;
 | 
			
		||||
		/* szPname is only 32 chars long, so it is often truncated.
 | 
			
		||||
		   Use partial match to work around this. */
 | 
			
		||||
		if (strstr(device_name, caps.szPname) == device_name)
 | 
			
		||||
			return i;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* fallback to wave mapper */
 | 
			
		||||
	return WAVE_MAPPER;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void *
 | 
			
		||||
win32_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
 | 
			
		||||
winmm_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
 | 
			
		||||
		  G_GNUC_UNUSED const struct config_param *param,
 | 
			
		||||
		  G_GNUC_UNUSED GError **error)
 | 
			
		||||
{
 | 
			
		||||
	struct win32_output *wo = g_new(struct win32_output, 1);
 | 
			
		||||
 | 
			
		||||
	struct winmm_output *wo = g_new(struct winmm_output, 1);
 | 
			
		||||
	const char *device = config_get_block_string(param, "device", NULL);
 | 
			
		||||
	wo->device_id = get_device_id(device);
 | 
			
		||||
	return wo;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
win32_output_finish(void *data)
 | 
			
		||||
winmm_output_finish(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct win32_output *wo = data;
 | 
			
		||||
	struct winmm_output *wo = data;
 | 
			
		||||
 | 
			
		||||
	g_free(wo);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
win32_output_open(void *data, struct audio_format *audio_format,
 | 
			
		||||
winmm_output_open(void *data, struct audio_format *audio_format,
 | 
			
		||||
		  GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	struct win32_output *wo = data;
 | 
			
		||||
	struct winmm_output *wo = data;
 | 
			
		||||
 | 
			
		||||
	wo->event = CreateEvent(NULL, false, false, NULL);
 | 
			
		||||
	if (wo->event == NULL) {
 | 
			
		||||
		g_set_error(error_r, win32_output_quark(), 0,
 | 
			
		||||
		g_set_error(error_r, winmm_output_quark(), 0,
 | 
			
		||||
			    "CreateEvent() failed");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
@@ -119,11 +159,11 @@ win32_output_open(void *data, struct audio_format *audio_format,
 | 
			
		||||
	format.wBitsPerSample = audio_format_sample_size(audio_format) * 8;
 | 
			
		||||
	format.cbSize = 0;
 | 
			
		||||
 | 
			
		||||
	MMRESULT result = waveOutOpen(&wo->handle, WAVE_MAPPER, &format,
 | 
			
		||||
	MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format,
 | 
			
		||||
				      (DWORD_PTR)wo->event, 0, CALLBACK_EVENT);
 | 
			
		||||
	if (result != MMSYSERR_NOERROR) {
 | 
			
		||||
		CloseHandle(wo->event);
 | 
			
		||||
		g_set_error(error_r, win32_output_quark(), result,
 | 
			
		||||
		g_set_error(error_r, winmm_output_quark(), result,
 | 
			
		||||
			    "waveOutOpen() failed");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
@@ -139,9 +179,9 @@ win32_output_open(void *data, struct audio_format *audio_format,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
win32_output_close(void *data)
 | 
			
		||||
winmm_output_close(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct win32_output *wo = data;
 | 
			
		||||
	struct winmm_output *wo = data;
 | 
			
		||||
 | 
			
		||||
	for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i)
 | 
			
		||||
		pcm_buffer_deinit(&wo->buffers[i].buffer);
 | 
			
		||||
@@ -155,13 +195,13 @@ win32_output_close(void *data)
 | 
			
		||||
 * Copy data into a buffer, and prepare the wave header.
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
win32_set_buffer(struct win32_output *wo, struct win32_buffer *buffer,
 | 
			
		||||
winmm_set_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
 | 
			
		||||
		 const void *data, size_t size,
 | 
			
		||||
		 GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	void *dest = pcm_buffer_get(&buffer->buffer, size);
 | 
			
		||||
	if (dest == NULL) {
 | 
			
		||||
		g_set_error(error_r, win32_output_quark(), 0,
 | 
			
		||||
		g_set_error(error_r, winmm_output_quark(), 0,
 | 
			
		||||
			    "Out of memory");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
@@ -175,7 +215,7 @@ win32_set_buffer(struct win32_output *wo, struct win32_buffer *buffer,
 | 
			
		||||
	MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr,
 | 
			
		||||
					       sizeof(buffer->hdr));
 | 
			
		||||
	if (result != MMSYSERR_NOERROR) {
 | 
			
		||||
		g_set_error(error_r, win32_output_quark(), result,
 | 
			
		||||
		g_set_error(error_r, winmm_output_quark(), result,
 | 
			
		||||
			    "waveOutPrepareHeader() failed");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
@@ -187,7 +227,7 @@ win32_set_buffer(struct win32_output *wo, struct win32_buffer *buffer,
 | 
			
		||||
 * Wait until the buffer is finished.
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
win32_drain_buffer(struct win32_output *wo, struct win32_buffer *buffer,
 | 
			
		||||
winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
 | 
			
		||||
		   GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE)
 | 
			
		||||
@@ -201,7 +241,7 @@ win32_drain_buffer(struct win32_output *wo, struct win32_buffer *buffer,
 | 
			
		||||
		if (result == MMSYSERR_NOERROR)
 | 
			
		||||
			return true;
 | 
			
		||||
		else if (result != WAVERR_STILLPLAYING) {
 | 
			
		||||
			g_set_error(error_r, win32_output_quark(), result,
 | 
			
		||||
			g_set_error(error_r, winmm_output_quark(), result,
 | 
			
		||||
				    "waveOutUnprepareHeader() failed");
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
@@ -212,14 +252,14 @@ win32_drain_buffer(struct win32_output *wo, struct win32_buffer *buffer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static size_t
 | 
			
		||||
win32_output_play(void *data, const void *chunk, size_t size, GError **error_r)
 | 
			
		||||
winmm_output_play(void *data, const void *chunk, size_t size, GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	struct win32_output *wo = data;
 | 
			
		||||
	struct winmm_output *wo = data;
 | 
			
		||||
 | 
			
		||||
	/* get the next buffer from the ring and prepare it */
 | 
			
		||||
	struct win32_buffer *buffer = &wo->buffers[wo->next_buffer];
 | 
			
		||||
	if (!win32_drain_buffer(wo, buffer, error_r) ||
 | 
			
		||||
	    !win32_set_buffer(wo, buffer, chunk, size, error_r))
 | 
			
		||||
	struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer];
 | 
			
		||||
	if (!winmm_drain_buffer(wo, buffer, error_r) ||
 | 
			
		||||
	    !winmm_set_buffer(wo, buffer, chunk, size, error_r))
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* enqueue the buffer */
 | 
			
		||||
@@ -228,7 +268,7 @@ win32_output_play(void *data, const void *chunk, size_t size, GError **error_r)
 | 
			
		||||
	if (result != MMSYSERR_NOERROR) {
 | 
			
		||||
		waveOutUnprepareHeader(wo->handle, &buffer->hdr,
 | 
			
		||||
				       sizeof(buffer->hdr));
 | 
			
		||||
		g_set_error(error_r, win32_output_quark(), result,
 | 
			
		||||
		g_set_error(error_r, winmm_output_quark(), result,
 | 
			
		||||
			    "waveOutWrite() failed");
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
@@ -241,56 +281,57 @@ win32_output_play(void *data, const void *chunk, size_t size, GError **error_r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
win32_drain_all_buffers(struct win32_output *wo, GError **error_r)
 | 
			
		||||
winmm_drain_all_buffers(struct winmm_output *wo, GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(wo->buffers); ++i)
 | 
			
		||||
		if (!win32_drain_buffer(wo, &wo->buffers[i], error_r))
 | 
			
		||||
		if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r))
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
	for (unsigned i = 0; i < wo->next_buffer; ++i)
 | 
			
		||||
		if (!win32_drain_buffer(wo, &wo->buffers[i], error_r))
 | 
			
		||||
		if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r))
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
win32_stop(struct win32_output *wo)
 | 
			
		||||
winmm_stop(struct winmm_output *wo)
 | 
			
		||||
{
 | 
			
		||||
	waveOutReset(wo->handle);
 | 
			
		||||
 | 
			
		||||
	for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) {
 | 
			
		||||
		struct win32_buffer *buffer = &wo->buffers[i];
 | 
			
		||||
		struct winmm_buffer *buffer = &wo->buffers[i];
 | 
			
		||||
		waveOutUnprepareHeader(wo->handle, &buffer->hdr,
 | 
			
		||||
				       sizeof(buffer->hdr));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
win32_output_drain(void *data)
 | 
			
		||||
winmm_output_drain(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct win32_output *wo = data;
 | 
			
		||||
	struct winmm_output *wo = data;
 | 
			
		||||
 | 
			
		||||
	if (!win32_drain_all_buffers(wo, NULL))
 | 
			
		||||
		win32_stop(wo);
 | 
			
		||||
	if (!winmm_drain_all_buffers(wo, NULL))
 | 
			
		||||
		winmm_stop(wo);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
win32_output_cancel(void *data)
 | 
			
		||||
winmm_output_cancel(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct win32_output *wo = data;
 | 
			
		||||
	struct winmm_output *wo = data;
 | 
			
		||||
 | 
			
		||||
	win32_stop(wo);
 | 
			
		||||
	winmm_stop(wo);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const struct audio_output_plugin win32_output_plugin = {
 | 
			
		||||
	.name = "win32",
 | 
			
		||||
	.test_default_device = win32_output_test_default_device,
 | 
			
		||||
	.init = win32_output_init,
 | 
			
		||||
	.finish = win32_output_finish,
 | 
			
		||||
	.open = win32_output_open,
 | 
			
		||||
	.close = win32_output_close,
 | 
			
		||||
	.play = win32_output_play,
 | 
			
		||||
	.drain = win32_output_drain,
 | 
			
		||||
	.cancel = win32_output_cancel,
 | 
			
		||||
const struct audio_output_plugin winmm_output_plugin = {
 | 
			
		||||
	.name = "winmm",
 | 
			
		||||
	.test_default_device = winmm_output_test_default_device,
 | 
			
		||||
	.init = winmm_output_init,
 | 
			
		||||
	.finish = winmm_output_finish,
 | 
			
		||||
	.open = winmm_output_open,
 | 
			
		||||
	.close = winmm_output_close,
 | 
			
		||||
	.play = winmm_output_play,
 | 
			
		||||
	.drain = winmm_output_drain,
 | 
			
		||||
	.cancel = winmm_output_cancel,
 | 
			
		||||
	.mixer_plugin = &winmm_mixer_plugin,
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										29
									
								
								src/output/winmm_output_plugin.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/output/winmm_output_plugin.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef MPD_WINMM_OUTPUT_PLUGIN_H
 | 
			
		||||
#define MPD_WINMM_OUTPUT_PLUGIN_H
 | 
			
		||||
 | 
			
		||||
#include <windows.h>
 | 
			
		||||
 | 
			
		||||
struct winmm_output;
 | 
			
		||||
 | 
			
		||||
HWAVEOUT winmm_output_get_handle(struct winmm_output*);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -26,6 +26,7 @@
 | 
			
		||||
#include "pipe.h"
 | 
			
		||||
#include "buffer.h"
 | 
			
		||||
#include "player_control.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
#include "chunk.h"
 | 
			
		||||
@@ -122,17 +123,17 @@ audio_output_all_init(void)
 | 
			
		||||
 | 
			
		||||
		if (!audio_output_init(output, param, &error)) {
 | 
			
		||||
			if (param != NULL)
 | 
			
		||||
				g_error("line %i: %s",
 | 
			
		||||
					param->line, error->message);
 | 
			
		||||
				MPD_ERROR("line %i: %s",
 | 
			
		||||
					  param->line, error->message);
 | 
			
		||||
			else
 | 
			
		||||
				g_error("%s", error->message);
 | 
			
		||||
				MPD_ERROR("%s", error->message);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* require output names to be unique: */
 | 
			
		||||
		for (j = 0; j < i; j++) {
 | 
			
		||||
			if (!strcmp(output->name, audio_outputs[j].name)) {
 | 
			
		||||
				g_error("output devices with identical "
 | 
			
		||||
					"names: %s\n", output->name);
 | 
			
		||||
				MPD_ERROR("output devices with identical "
 | 
			
		||||
					  "names: %s\n", output->name);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -144,6 +145,7 @@ audio_output_all_finish(void)
 | 
			
		||||
	unsigned int i;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < num_audio_outputs; i++) {
 | 
			
		||||
		audio_output_disable(&audio_outputs[i]);
 | 
			
		||||
		audio_output_finish(&audio_outputs[i]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -321,7 +323,7 @@ audio_output_all_open(const struct audio_format *audio_format,
 | 
			
		||||
	else
 | 
			
		||||
		/* if the pipe hasn't been cleared, the the audio
 | 
			
		||||
		   format must not have changed */
 | 
			
		||||
		assert(music_pipe_size(g_mp) == 0 ||
 | 
			
		||||
		assert(music_pipe_empty(g_mp) ||
 | 
			
		||||
		       audio_format_equals(audio_format,
 | 
			
		||||
					   &input_audio_format));
 | 
			
		||||
 | 
			
		||||
@@ -434,7 +436,7 @@ audio_output_all_check(void)
 | 
			
		||||
	assert(g_mp != NULL);
 | 
			
		||||
 | 
			
		||||
	while ((chunk = music_pipe_peek(g_mp)) != NULL) {
 | 
			
		||||
		assert(music_pipe_size(g_mp) > 0);
 | 
			
		||||
		assert(!music_pipe_empty(g_mp));
 | 
			
		||||
 | 
			
		||||
		if (!chunk_is_consumed(chunk))
 | 
			
		||||
			/* at least one output is not finished playing
 | 
			
		||||
 
 | 
			
		||||
@@ -102,6 +102,12 @@ audio_output_disable(struct audio_output *ao)
 | 
			
		||||
	g_mutex_unlock(ao->mutex);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
audio_output_close_locked(struct audio_output *ao);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Object must be locked (and unlocked) by the caller.
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
audio_output_open(struct audio_output *ao,
 | 
			
		||||
		  const struct audio_format *audio_format,
 | 
			
		||||
@@ -173,6 +179,8 @@ audio_output_open(struct audio_output *ao,
 | 
			
		||||
static void
 | 
			
		||||
audio_output_close_locked(struct audio_output *ao)
 | 
			
		||||
{
 | 
			
		||||
	assert(ao != NULL);
 | 
			
		||||
 | 
			
		||||
	if (ao->mixer != NULL)
 | 
			
		||||
		mixer_auto_close(ao->mixer);
 | 
			
		||||
 | 
			
		||||
@@ -251,25 +259,6 @@ void audio_output_cancel(struct audio_output *ao)
 | 
			
		||||
	g_mutex_unlock(ao->mutex);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void audio_output_close(struct audio_output *ao)
 | 
			
		||||
{
 | 
			
		||||
	if (ao->mixer != NULL)
 | 
			
		||||
		mixer_auto_close(ao->mixer);
 | 
			
		||||
 | 
			
		||||
	g_mutex_lock(ao->mutex);
 | 
			
		||||
 | 
			
		||||
	assert(!ao->open || ao->fail_timer == NULL);
 | 
			
		||||
 | 
			
		||||
	if (ao->open)
 | 
			
		||||
		ao_command(ao, AO_COMMAND_CLOSE);
 | 
			
		||||
	else if (ao->fail_timer != NULL) {
 | 
			
		||||
		g_timer_destroy(ao->fail_timer);
 | 
			
		||||
		ao->fail_timer = NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g_mutex_unlock(ao->mutex);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
audio_output_release(struct audio_output *ao)
 | 
			
		||||
{
 | 
			
		||||
@@ -279,6 +268,16 @@ audio_output_release(struct audio_output *ao)
 | 
			
		||||
		audio_output_close(ao);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void audio_output_close(struct audio_output *ao)
 | 
			
		||||
{
 | 
			
		||||
	assert(ao != NULL);
 | 
			
		||||
	assert(!ao->open || ao->fail_timer == NULL);
 | 
			
		||||
 | 
			
		||||
	g_mutex_lock(ao->mutex);
 | 
			
		||||
	audio_output_close_locked(ao);
 | 
			
		||||
	g_mutex_unlock(ao->mutex);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void audio_output_finish(struct audio_output *ao)
 | 
			
		||||
{
 | 
			
		||||
	audio_output_close(ao);
 | 
			
		||||
 
 | 
			
		||||
@@ -196,7 +196,8 @@ struct audio_output {
 | 
			
		||||
	const struct music_pipe *pipe;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * This mutex protects #open, #chunk and #chunk_finished.
 | 
			
		||||
	 * This mutex protects #open, #fail_timer, #chunk and
 | 
			
		||||
	 * #chunk_finished.
 | 
			
		||||
	 */
 | 
			
		||||
	GMutex *mutex;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,8 @@ extern const struct audio_output_plugin mvp_output_plugin;
 | 
			
		||||
extern const struct audio_output_plugin jack_output_plugin;
 | 
			
		||||
extern const struct audio_output_plugin httpd_output_plugin;
 | 
			
		||||
extern const struct audio_output_plugin recorder_output_plugin;
 | 
			
		||||
extern const struct audio_output_plugin win32_output_plugin;
 | 
			
		||||
extern const struct audio_output_plugin winmm_output_plugin;
 | 
			
		||||
extern const struct audio_output_plugin ffado_output_plugin;
 | 
			
		||||
 | 
			
		||||
const struct audio_output_plugin *audio_output_plugins[] = {
 | 
			
		||||
#ifdef HAVE_SHOUT
 | 
			
		||||
@@ -82,8 +83,11 @@ const struct audio_output_plugin *audio_output_plugins[] = {
 | 
			
		||||
#ifdef ENABLE_RECORDER_OUTPUT
 | 
			
		||||
	&recorder_output_plugin,
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef ENABLE_WIN32_OUTPUT
 | 
			
		||||
	&win32_output_plugin,
 | 
			
		||||
#ifdef ENABLE_WINMM_OUTPUT
 | 
			
		||||
	&winmm_output_plugin,
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef ENABLE_FFADO_OUTPUT
 | 
			
		||||
	&ffado_output_plugin,
 | 
			
		||||
#endif
 | 
			
		||||
	NULL
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -100,6 +100,16 @@ struct audio_output_plugin {
 | 
			
		||||
	 */
 | 
			
		||||
	void (*close)(void *data);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Returns a positive number if the output thread shall delay
 | 
			
		||||
	 * the next call to play() or pause().  This should be
 | 
			
		||||
	 * implemented instead of doing a sleep inside the plugin,
 | 
			
		||||
	 * because this allows MPD to listen to commands meanwhile.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @return the number of milliseconds to wait
 | 
			
		||||
	 */
 | 
			
		||||
	unsigned (*delay)(void *data);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Display metadata for the next chunk.  Optional method,
 | 
			
		||||
	 * because not all devices can display metadata.
 | 
			
		||||
@@ -202,6 +212,14 @@ ao_plugin_close(const struct audio_output_plugin *plugin, void *data)
 | 
			
		||||
	plugin->close(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline unsigned
 | 
			
		||||
ao_plugin_delay(const struct audio_output_plugin *plugin, void *data)
 | 
			
		||||
{
 | 
			
		||||
	return plugin->delay != NULL
 | 
			
		||||
		? plugin->delay(data)
 | 
			
		||||
		: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline void
 | 
			
		||||
ao_plugin_send_tag(const struct audio_output_plugin *plugin,
 | 
			
		||||
		   void *data, const struct tag *tag)
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@
 | 
			
		||||
#include "filter_plugin.h"
 | 
			
		||||
#include "filter/convert_filter_plugin.h"
 | 
			
		||||
#include "filter/replay_gain_filter_plugin.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
@@ -133,10 +134,18 @@ ao_open(struct audio_output *ao)
 | 
			
		||||
	struct audio_format_string af_string;
 | 
			
		||||
 | 
			
		||||
	assert(!ao->open);
 | 
			
		||||
	assert(ao->fail_timer == NULL);
 | 
			
		||||
	assert(ao->pipe != NULL);
 | 
			
		||||
	assert(ao->chunk == NULL);
 | 
			
		||||
 | 
			
		||||
	if (ao->fail_timer != NULL) {
 | 
			
		||||
		/* this can only happen when this
 | 
			
		||||
		   output thread fails while
 | 
			
		||||
		   audio_output_open() is run in the
 | 
			
		||||
		   player thread */
 | 
			
		||||
		g_timer_destroy(ao->fail_timer);
 | 
			
		||||
		ao->fail_timer = NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* enable the device (just in case the last enable has failed) */
 | 
			
		||||
 | 
			
		||||
	if (!ao_enable(ao))
 | 
			
		||||
@@ -277,6 +286,30 @@ ao_reopen(struct audio_output *ao)
 | 
			
		||||
		ao_open(ao);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wait until the output's delay reaches zero.
 | 
			
		||||
 *
 | 
			
		||||
 * @return true if playback should be continued, false if a command
 | 
			
		||||
 * was issued
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
ao_wait(struct audio_output *ao)
 | 
			
		||||
{
 | 
			
		||||
	while (true) {
 | 
			
		||||
		unsigned delay = ao_plugin_delay(ao->plugin, ao->data);
 | 
			
		||||
		if (delay == 0)
 | 
			
		||||
			return true;
 | 
			
		||||
 | 
			
		||||
		GTimeVal tv;
 | 
			
		||||
		g_get_current_time(&tv);
 | 
			
		||||
		g_time_val_add(&tv, delay * 1000);
 | 
			
		||||
		(void)g_cond_timed_wait(ao->cond, ao->mutex, &tv);
 | 
			
		||||
 | 
			
		||||
		if (ao->command != AO_COMMAND_NONE)
 | 
			
		||||
			return false;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char *
 | 
			
		||||
ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk,
 | 
			
		||||
	      struct filter *replay_gain_filter,
 | 
			
		||||
@@ -413,6 +446,9 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
 | 
			
		||||
	while (size > 0 && ao->command == AO_COMMAND_NONE) {
 | 
			
		||||
		size_t nbytes;
 | 
			
		||||
 | 
			
		||||
		if (!ao_wait(ao))
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		g_mutex_unlock(ao->mutex);
 | 
			
		||||
		nbytes = ao_plugin_play(ao->plugin, ao->data, data, size,
 | 
			
		||||
					&error);
 | 
			
		||||
@@ -427,7 +463,9 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
 | 
			
		||||
 | 
			
		||||
			/* don't automatically reopen this device for
 | 
			
		||||
			   10 seconds */
 | 
			
		||||
			assert(ao->fail_timer == NULL);
 | 
			
		||||
			ao->fail_timer = g_timer_new();
 | 
			
		||||
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -510,6 +548,9 @@ static void ao_pause(struct audio_output *ao)
 | 
			
		||||
	ao_command_finished(ao);
 | 
			
		||||
 | 
			
		||||
	do {
 | 
			
		||||
		if (!ao_wait(ao))
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		g_mutex_unlock(ao->mutex);
 | 
			
		||||
		ret = ao_plugin_pause(ao->plugin, ao->data);
 | 
			
		||||
		g_mutex_lock(ao->mutex);
 | 
			
		||||
@@ -629,5 +670,5 @@ void audio_output_thread_start(struct audio_output *ao)
 | 
			
		||||
	assert(ao->command == AO_COMMAND_NONE);
 | 
			
		||||
 | 
			
		||||
	if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e)))
 | 
			
		||||
		g_error("Failed to spawn output task: %s\n", e->message);
 | 
			
		||||
		MPD_ERROR("Failed to spawn output task: %s\n", e->message);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "path.h"
 | 
			
		||||
#include "conf.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
@@ -64,7 +65,7 @@ path_set_fs_charset(const char *charset)
 | 
			
		||||
	/* convert a space to ensure that the charset is valid */
 | 
			
		||||
	test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL);
 | 
			
		||||
	if (test == NULL)
 | 
			
		||||
		g_error("invalid filesystem charset: %s", charset);
 | 
			
		||||
		MPD_ERROR("invalid filesystem charset: %s", charset);
 | 
			
		||||
	g_free(test);
 | 
			
		||||
 | 
			
		||||
	g_free(fs_charset);
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@
 | 
			
		||||
#include "pcm_volume.h"
 | 
			
		||||
#include "pcm_utils.h"
 | 
			
		||||
#include "audio_format.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
@@ -125,8 +126,8 @@ pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		g_error("format %s not supported by pcm_add_vol",
 | 
			
		||||
			sample_format_to_string(format->format));
 | 
			
		||||
		MPD_ERROR("format %s not supported by pcm_add_vol",
 | 
			
		||||
			  sample_format_to_string(format->format));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -208,8 +209,8 @@ pcm_add(void *buffer1, const void *buffer2, size_t size,
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		g_error("format %s not supported by pcm_add",
 | 
			
		||||
			sample_format_to_string(format->format));
 | 
			
		||||
		MPD_ERROR("format %s not supported by pcm_add",
 | 
			
		||||
			  sample_format_to_string(format->format));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "permission.h"
 | 
			
		||||
#include "conf.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
@@ -59,7 +60,7 @@ static unsigned parsePermissions(const char *string)
 | 
			
		||||
		} else if (strcmp(temp, PERMISSION_ADMIN_STRING) == 0) {
 | 
			
		||||
			permission |= PERMISSION_ADMIN;
 | 
			
		||||
		} else {
 | 
			
		||||
			g_error("unknown permission \"%s\"", temp);
 | 
			
		||||
			MPD_ERROR("unknown permission \"%s\"", temp);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -90,7 +91,7 @@ void initPermissions(void)
 | 
			
		||||
				strchr(param->value, PERMISSION_PASSWORD_CHAR);
 | 
			
		||||
 | 
			
		||||
			if (separator == NULL)
 | 
			
		||||
				g_error("\"%c\" not found in password string "
 | 
			
		||||
				MPD_ERROR("\"%c\" not found in password string "
 | 
			
		||||
					"\"%s\", line %i",
 | 
			
		||||
					PERMISSION_PASSWORD_CHAR,
 | 
			
		||||
					param->value, param->line);
 | 
			
		||||
 
 | 
			
		||||
@@ -99,4 +99,10 @@ music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk);
 | 
			
		||||
unsigned
 | 
			
		||||
music_pipe_size(const struct music_pipe *mp);
 | 
			
		||||
 | 
			
		||||
static inline bool
 | 
			
		||||
music_pipe_empty(const struct music_pipe *mp)
 | 
			
		||||
{
 | 
			
		||||
	return music_pipe_size(mp) == 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,7 @@
 | 
			
		||||
#include "idle.h"
 | 
			
		||||
#include "main.h"
 | 
			
		||||
#include "buffer.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
@@ -147,6 +148,32 @@ player_dc_start(struct player *player, struct music_pipe *pipe)
 | 
			
		||||
	dc_start(dc, pc.next_song, player_buffer, pipe);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Is the decoder still busy on the same song as the player?
 | 
			
		||||
 *
 | 
			
		||||
 * Note: this function does not check if the decoder is already
 | 
			
		||||
 * finished.
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
player_dc_at_current_song(const struct player *player)
 | 
			
		||||
{
 | 
			
		||||
	assert(player != NULL);
 | 
			
		||||
	assert(player->pipe != NULL);
 | 
			
		||||
 | 
			
		||||
	return player->dc->pipe == player->pipe;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns true if the decoder is decoding the next song (or has begun
 | 
			
		||||
 * decoding it, or has finished doing it), and the player hasn't
 | 
			
		||||
 * switched to that song yet.
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
player_dc_at_next_song(const struct player *player)
 | 
			
		||||
{
 | 
			
		||||
	return player->dc->pipe != NULL && !player_dc_at_current_song(player);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Stop the decoder and clears (and frees) its music pipe.
 | 
			
		||||
 *
 | 
			
		||||
@@ -171,17 +198,6 @@ player_dc_stop(struct player *player)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns true if the decoder is decoding the next song (or has begun
 | 
			
		||||
 * decoding it, or has finished doing it), and the player hasn't
 | 
			
		||||
 * switched to that song yet.
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
decoding_next_song(const struct player *player)
 | 
			
		||||
{
 | 
			
		||||
	return player->dc->pipe != NULL && player->dc->pipe != player->pipe;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * After the decoder has been started asynchronously, wait for the
 | 
			
		||||
 * "START" command to finish.  The decoder may not be initialized yet,
 | 
			
		||||
@@ -340,16 +356,9 @@ player_check_decoder_startup(struct player *player)
 | 
			
		||||
static bool
 | 
			
		||||
player_send_silence(struct player *player)
 | 
			
		||||
{
 | 
			
		||||
	struct music_chunk *chunk;
 | 
			
		||||
	size_t frame_size =
 | 
			
		||||
		audio_format_frame_size(&player->play_audio_format);
 | 
			
		||||
	/* this formula ensures that we don't send
 | 
			
		||||
	   partial frames */
 | 
			
		||||
	unsigned num_frames = sizeof(chunk->data) / frame_size;
 | 
			
		||||
 | 
			
		||||
	assert(audio_format_defined(&player->play_audio_format));
 | 
			
		||||
 | 
			
		||||
	chunk = music_buffer_allocate(player_buffer);
 | 
			
		||||
	struct music_chunk *chunk = music_buffer_allocate(player_buffer);
 | 
			
		||||
	if (chunk == NULL) {
 | 
			
		||||
		g_warning("Failed to allocate silence buffer");
 | 
			
		||||
		return false;
 | 
			
		||||
@@ -359,6 +368,12 @@ player_send_silence(struct player *player)
 | 
			
		||||
	chunk->audio_format = player->play_audio_format;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	size_t frame_size =
 | 
			
		||||
		audio_format_frame_size(&player->play_audio_format);
 | 
			
		||||
	/* this formula ensures that we don't send
 | 
			
		||||
	   partial frames */
 | 
			
		||||
	unsigned num_frames = sizeof(chunk->data) / frame_size;
 | 
			
		||||
 | 
			
		||||
	chunk->times = -1.0; /* undefined time stamp */
 | 
			
		||||
	chunk->length = num_frames * frame_size;
 | 
			
		||||
	memset(chunk->data, 0, chunk->length);
 | 
			
		||||
@@ -380,8 +395,6 @@ static bool player_seek_decoder(struct player *player)
 | 
			
		||||
{
 | 
			
		||||
	struct song *song = pc.next_song;
 | 
			
		||||
	struct decoder_control *dc = player->dc;
 | 
			
		||||
	double where;
 | 
			
		||||
	bool ret;
 | 
			
		||||
 | 
			
		||||
	assert(pc.next_song != NULL);
 | 
			
		||||
 | 
			
		||||
@@ -397,13 +410,20 @@ static bool player_seek_decoder(struct player *player)
 | 
			
		||||
 | 
			
		||||
		/* re-start the decoder */
 | 
			
		||||
		player_dc_start(player, player->pipe);
 | 
			
		||||
		ret = player_wait_for_decoder(player);
 | 
			
		||||
		if (!ret) {
 | 
			
		||||
		if (!player_wait_for_decoder(player)) {
 | 
			
		||||
			/* decoder failure */
 | 
			
		||||
			player_command_finished();
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if (!player_dc_at_current_song(player)) {
 | 
			
		||||
			/* the decoder is already decoding the "next" song,
 | 
			
		||||
			   but it is the same song file; exchange the pipe */
 | 
			
		||||
			music_pipe_clear(player->pipe, player_buffer);
 | 
			
		||||
			music_pipe_free(player->pipe);
 | 
			
		||||
			player->pipe = dc->pipe;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pc.next_song = NULL;
 | 
			
		||||
		player->queued = false;
 | 
			
		||||
	}
 | 
			
		||||
@@ -411,8 +431,7 @@ static bool player_seek_decoder(struct player *player)
 | 
			
		||||
	/* wait for the decoder to complete initialization */
 | 
			
		||||
 | 
			
		||||
	while (player->decoder_starting) {
 | 
			
		||||
		ret = player_check_decoder_startup(player);
 | 
			
		||||
		if (!ret) {
 | 
			
		||||
		if (!player_check_decoder_startup(player)) {
 | 
			
		||||
			/* decoder failure */
 | 
			
		||||
			player_command_finished();
 | 
			
		||||
			return false;
 | 
			
		||||
@@ -421,14 +440,13 @@ static bool player_seek_decoder(struct player *player)
 | 
			
		||||
 | 
			
		||||
	/* send the SEEK command */
 | 
			
		||||
 | 
			
		||||
	where = pc.seek_where;
 | 
			
		||||
	double where = pc.seek_where;
 | 
			
		||||
	if (where > pc.total_time)
 | 
			
		||||
		where = pc.total_time - 0.1;
 | 
			
		||||
	if (where < 0.0)
 | 
			
		||||
		where = 0.0;
 | 
			
		||||
 | 
			
		||||
	ret = dc_seek(dc, where + song->start_ms / 1000.0);
 | 
			
		||||
	if (!ret) {
 | 
			
		||||
	if (!dc_seek(dc, where + song->start_ms / 1000.0)) {
 | 
			
		||||
		/* decoder failure */
 | 
			
		||||
		player_command_finished();
 | 
			
		||||
		return false;
 | 
			
		||||
@@ -472,7 +490,7 @@ static void player_process_command(struct player *player)
 | 
			
		||||
	case PLAYER_COMMAND_QUEUE:
 | 
			
		||||
		assert(pc.next_song != NULL);
 | 
			
		||||
		assert(!player->queued);
 | 
			
		||||
		assert(dc->pipe == NULL || dc->pipe == player->pipe);
 | 
			
		||||
		assert(!player_dc_at_next_song(player));
 | 
			
		||||
 | 
			
		||||
		player->queued = true;
 | 
			
		||||
		player_command_finished_locked();
 | 
			
		||||
@@ -526,7 +544,7 @@ static void player_process_command(struct player *player)
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (decoding_next_song(player)) {
 | 
			
		||||
		if (player_dc_at_next_song(player)) {
 | 
			
		||||
			/* the decoder is already decoding the song -
 | 
			
		||||
			   stop it and reset the position */
 | 
			
		||||
			player_unlock();
 | 
			
		||||
@@ -559,14 +577,12 @@ static void player_process_command(struct player *player)
 | 
			
		||||
static void
 | 
			
		||||
update_song_tag(struct song *song, const struct tag *new_tag)
 | 
			
		||||
{
 | 
			
		||||
	struct tag *old_tag;
 | 
			
		||||
 | 
			
		||||
	if (song_is_file(song))
 | 
			
		||||
		/* don't update tags of local files, only remote
 | 
			
		||||
		   streams may change tags dynamically */
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	old_tag = song->tag;
 | 
			
		||||
	struct tag *old_tag = song->tag;
 | 
			
		||||
	song->tag = tag_dup(new_tag);
 | 
			
		||||
 | 
			
		||||
	if (old_tag != NULL)
 | 
			
		||||
@@ -624,17 +640,16 @@ static bool
 | 
			
		||||
play_next_chunk(struct player *player)
 | 
			
		||||
{
 | 
			
		||||
	struct decoder_control *dc = player->dc;
 | 
			
		||||
	struct music_chunk *chunk = NULL;
 | 
			
		||||
	unsigned cross_fade_position;
 | 
			
		||||
	bool success;
 | 
			
		||||
 | 
			
		||||
	if (!audio_output_all_wait(64))
 | 
			
		||||
		/* the output pipe is still large enough, don't send
 | 
			
		||||
		   another chunk */
 | 
			
		||||
		return true;
 | 
			
		||||
 | 
			
		||||
	unsigned cross_fade_position;
 | 
			
		||||
	struct music_chunk *chunk = NULL;
 | 
			
		||||
	if (player->xfade == XFADE_ENABLED &&
 | 
			
		||||
	    decoding_next_song(player) &&
 | 
			
		||||
	    player_dc_at_next_song(player) &&
 | 
			
		||||
	    (cross_fade_position = music_pipe_size(player->pipe))
 | 
			
		||||
	    <= player->cross_fade_chunks) {
 | 
			
		||||
		/* perform cross fade */
 | 
			
		||||
@@ -670,6 +685,19 @@ play_next_chunk(struct player *player)
 | 
			
		||||
				chunk->mix_ratio = nan("");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (music_chunk_is_empty(other_chunk)) {
 | 
			
		||||
				/* the "other" chunk was a music_chunk
 | 
			
		||||
				   which had only a tag, but no music
 | 
			
		||||
				   data - we cannot cross-fade that;
 | 
			
		||||
				   but since this happens only at the
 | 
			
		||||
				   beginning of the new song, we can
 | 
			
		||||
				   easily recover by throwing it away
 | 
			
		||||
				   now */
 | 
			
		||||
				music_buffer_return(player_buffer,
 | 
			
		||||
						    other_chunk);
 | 
			
		||||
				other_chunk = NULL;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			chunk->other = other_chunk;
 | 
			
		||||
		} else {
 | 
			
		||||
			/* there are not enough decoded chunks yet */
 | 
			
		||||
@@ -708,9 +736,7 @@ play_next_chunk(struct player *player)
 | 
			
		||||
 | 
			
		||||
	/* play the current chunk */
 | 
			
		||||
 | 
			
		||||
	success = play_chunk(player->song, chunk, &player->play_audio_format);
 | 
			
		||||
 | 
			
		||||
	if (!success) {
 | 
			
		||||
	if (!play_chunk(player->song, chunk, &player->play_audio_format)) {
 | 
			
		||||
		music_buffer_return(player_buffer, chunk);
 | 
			
		||||
 | 
			
		||||
		player_lock();
 | 
			
		||||
@@ -752,11 +778,9 @@ play_next_chunk(struct player *player)
 | 
			
		||||
static bool
 | 
			
		||||
player_song_border(struct player *player)
 | 
			
		||||
{
 | 
			
		||||
	char *uri;
 | 
			
		||||
 | 
			
		||||
	player->xfade = XFADE_UNKNOWN;
 | 
			
		||||
 | 
			
		||||
	uri = song_get_uri(player->song);
 | 
			
		||||
	char *uri = song_get_uri(player->song);
 | 
			
		||||
	g_message("played \"%s\"", uri);
 | 
			
		||||
	g_free(uri);
 | 
			
		||||
 | 
			
		||||
@@ -851,16 +875,17 @@ static void do_play(struct decoder_control *dc)
 | 
			
		||||
 | 
			
		||||
		if (player.decoder_starting) {
 | 
			
		||||
			/* wait until the decoder is initialized completely */
 | 
			
		||||
			bool success;
 | 
			
		||||
			const struct song *song;
 | 
			
		||||
 | 
			
		||||
			success = player_check_decoder_startup(&player);
 | 
			
		||||
			if (!success)
 | 
			
		||||
			if (!player_check_decoder_startup(&player))
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
			/* seek to the beginning of the range */
 | 
			
		||||
			song = decoder_current_song(dc);
 | 
			
		||||
			const struct song *song = decoder_current_song(dc);
 | 
			
		||||
			if (song != NULL && song->start_ms > 0 &&
 | 
			
		||||
			    /* we must not send a seek command until
 | 
			
		||||
			       the decoder is initialized
 | 
			
		||||
			       completely */
 | 
			
		||||
			    !player.decoder_starting &&
 | 
			
		||||
			    !dc_seek(dc, song->start_ms / 1000.0))
 | 
			
		||||
				player_dc_stop(&player);
 | 
			
		||||
 | 
			
		||||
@@ -880,12 +905,13 @@ static void do_play(struct decoder_control *dc)
 | 
			
		||||
		    dc->pipe == player.pipe) {
 | 
			
		||||
			/* the decoder has finished the current song;
 | 
			
		||||
			   make it decode the next song */
 | 
			
		||||
 | 
			
		||||
			assert(dc->pipe == NULL || dc->pipe == player.pipe);
 | 
			
		||||
 | 
			
		||||
			player_dc_start(&player, music_pipe_new());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (decoding_next_song(&player) &&
 | 
			
		||||
		if (player_dc_at_next_song(&player) &&
 | 
			
		||||
		    player.xfade == XFADE_UNKNOWN &&
 | 
			
		||||
		    !decoder_lock_is_starting(dc)) {
 | 
			
		||||
			/* enable cross fading in this song?  if yes,
 | 
			
		||||
@@ -918,7 +944,7 @@ static void do_play(struct decoder_control *dc)
 | 
			
		||||
			if (pc.command == PLAYER_COMMAND_NONE)
 | 
			
		||||
				player_wait();
 | 
			
		||||
			continue;
 | 
			
		||||
		} else if (music_pipe_size(player.pipe) > 0) {
 | 
			
		||||
		} else if (!music_pipe_empty(player.pipe)) {
 | 
			
		||||
			/* at least one music chunk is ready - send it
 | 
			
		||||
			   to the audio output */
 | 
			
		||||
 | 
			
		||||
@@ -930,7 +956,7 @@ static void do_play(struct decoder_control *dc)
 | 
			
		||||
 | 
			
		||||
			/* XXX synchronize in a better way */
 | 
			
		||||
			g_usleep(10000);
 | 
			
		||||
		} else if (decoding_next_song(&player)) {
 | 
			
		||||
		} else if (player_dc_at_next_song(&player)) {
 | 
			
		||||
			/* at the beginning of a new song */
 | 
			
		||||
 | 
			
		||||
			if (!player_song_border(&player))
 | 
			
		||||
@@ -939,7 +965,7 @@ static void do_play(struct decoder_control *dc)
 | 
			
		||||
			/* check the size of the pipe again, because
 | 
			
		||||
			   the decoder thread may have added something
 | 
			
		||||
			   since we last checked */
 | 
			
		||||
			if (music_pipe_size(player.pipe) == 0) {
 | 
			
		||||
			if (music_pipe_empty(player.pipe)) {
 | 
			
		||||
				/* wait for the hardware to finish
 | 
			
		||||
				   playback */
 | 
			
		||||
				audio_output_all_drain();
 | 
			
		||||
@@ -1067,11 +1093,10 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg)
 | 
			
		||||
 | 
			
		||||
void player_create(void)
 | 
			
		||||
{
 | 
			
		||||
	GError *e = NULL;
 | 
			
		||||
 | 
			
		||||
	assert(pc.thread == NULL);
 | 
			
		||||
 | 
			
		||||
	GError *e = NULL;
 | 
			
		||||
	pc.thread = g_thread_create(player_task, NULL, true, &e);
 | 
			
		||||
	if (pc.thread == NULL)
 | 
			
		||||
		g_error("Failed to spawn player task: %s", e->message);
 | 
			
		||||
		MPD_ERROR("Failed to spawn player task: %s", e->message);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -108,11 +108,8 @@ playlist_song_started(struct playlist *playlist)
 | 
			
		||||
	playlist->current = playlist->queued;
 | 
			
		||||
	playlist->queued = -1;
 | 
			
		||||
 | 
			
		||||
	/* Set pause and remove the single mode. */
 | 
			
		||||
	/* Pause if we are in single mode. */
 | 
			
		||||
	if(playlist->queue.single && !playlist->queue.repeat) {
 | 
			
		||||
		playlist->queue.single = false;
 | 
			
		||||
		idle_add(IDLE_OPTIONS);
 | 
			
		||||
 | 
			
		||||
		pc_set_pause(true);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -238,7 +235,7 @@ playlist_sync(struct playlist *playlist)
 | 
			
		||||
 | 
			
		||||
		/* make sure the queued song is always set (if
 | 
			
		||||
		   possible) */
 | 
			
		||||
		if (pc.next_song == NULL && playlist->queued != -1)
 | 
			
		||||
		if (pc.next_song == NULL && playlist->queued < 0)
 | 
			
		||||
			playlist_update_queued_song(playlist, NULL);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										321
									
								
								src/playlist/rss_playlist_plugin.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										321
									
								
								src/playlist/rss_playlist_plugin.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,321 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "playlist/rss_playlist_plugin.h"
 | 
			
		||||
#include "playlist_plugin.h"
 | 
			
		||||
#include "input_stream.h"
 | 
			
		||||
#include "song.h"
 | 
			
		||||
#include "tag.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#undef G_LOG_DOMAIN
 | 
			
		||||
#define G_LOG_DOMAIN "rss"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This is the state object for the GLib XML parser.
 | 
			
		||||
 */
 | 
			
		||||
struct rss_parser {
 | 
			
		||||
	/**
 | 
			
		||||
	 * The list of songs (in reverse order because that's faster
 | 
			
		||||
	 * while adding).
 | 
			
		||||
	 */
 | 
			
		||||
	GSList *songs;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The current position in the XML file.
 | 
			
		||||
	 */
 | 
			
		||||
	enum {
 | 
			
		||||
		ROOT, ITEM,
 | 
			
		||||
	} state;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The current tag within the "entry" element.  This is only
 | 
			
		||||
	 * valid if state==ITEM.  TAG_NUM_OF_ITEM_TYPES means there
 | 
			
		||||
	 * is no (known) tag.
 | 
			
		||||
	 */
 | 
			
		||||
	enum tag_type tag;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The current song.  It is allocated after the "location"
 | 
			
		||||
	 * element.
 | 
			
		||||
	 */
 | 
			
		||||
	struct song *song;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const gchar *
 | 
			
		||||
get_attribute(const gchar **attribute_names, const gchar **attribute_values,
 | 
			
		||||
	      const gchar *name)
 | 
			
		||||
{
 | 
			
		||||
	for (unsigned i = 0; attribute_names[i] != NULL; ++i)
 | 
			
		||||
		if (g_ascii_strcasecmp(attribute_names[i], name) == 0)
 | 
			
		||||
			return attribute_values[i];
 | 
			
		||||
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
rss_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
 | 
			
		||||
		  const gchar *element_name,
 | 
			
		||||
		  const gchar **attribute_names,
 | 
			
		||||
		  const gchar **attribute_values,
 | 
			
		||||
		  gpointer user_data, G_GNUC_UNUSED GError **error)
 | 
			
		||||
{
 | 
			
		||||
	struct rss_parser *parser = user_data;
 | 
			
		||||
 | 
			
		||||
	switch (parser->state) {
 | 
			
		||||
	case ROOT:
 | 
			
		||||
		if (g_ascii_strcasecmp(element_name, "item") == 0) {
 | 
			
		||||
			parser->state = ITEM;
 | 
			
		||||
			parser->song = song_remote_new("rss:");
 | 
			
		||||
			parser->tag = TAG_NUM_OF_ITEM_TYPES;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case ITEM:
 | 
			
		||||
		if (g_ascii_strcasecmp(element_name, "enclosure") == 0) {
 | 
			
		||||
			const gchar *href = get_attribute(attribute_names,
 | 
			
		||||
							  attribute_values,
 | 
			
		||||
							  "url");
 | 
			
		||||
			if (href != NULL) {
 | 
			
		||||
				/* create new song object, and copy
 | 
			
		||||
				   the existing tag over; we cannot
 | 
			
		||||
				   replace the existing song's URI,
 | 
			
		||||
				   because that attribute is
 | 
			
		||||
				   immutable */
 | 
			
		||||
				struct song *song = song_remote_new(href);
 | 
			
		||||
 | 
			
		||||
				if (parser->song != NULL) {
 | 
			
		||||
					song->tag = parser->song->tag;
 | 
			
		||||
					parser->song->tag = NULL;
 | 
			
		||||
					song_free(parser->song);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				parser->song = song;
 | 
			
		||||
			}
 | 
			
		||||
		} else if (g_ascii_strcasecmp(element_name, "title") == 0)
 | 
			
		||||
			parser->tag = TAG_TITLE;
 | 
			
		||||
		else if (g_ascii_strcasecmp(element_name, "itunes:author") == 0)
 | 
			
		||||
			parser->tag = TAG_ARTIST;
 | 
			
		||||
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
rss_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
 | 
			
		||||
		const gchar *element_name,
 | 
			
		||||
		gpointer user_data, G_GNUC_UNUSED GError **error)
 | 
			
		||||
{
 | 
			
		||||
	struct rss_parser *parser = user_data;
 | 
			
		||||
 | 
			
		||||
	switch (parser->state) {
 | 
			
		||||
	case ROOT:
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case ITEM:
 | 
			
		||||
		if (g_ascii_strcasecmp(element_name, "item") == 0) {
 | 
			
		||||
			if (strcmp(parser->song->uri, "rss:") != 0)
 | 
			
		||||
				parser->songs = g_slist_prepend(parser->songs,
 | 
			
		||||
								parser->song);
 | 
			
		||||
			else
 | 
			
		||||
				song_free(parser->song);
 | 
			
		||||
 | 
			
		||||
			parser->state = ROOT;
 | 
			
		||||
		} else
 | 
			
		||||
			parser->tag = TAG_NUM_OF_ITEM_TYPES;
 | 
			
		||||
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
rss_text(G_GNUC_UNUSED GMarkupParseContext *context,
 | 
			
		||||
	 const gchar *text, gsize text_len,
 | 
			
		||||
	 gpointer user_data, G_GNUC_UNUSED GError **error)
 | 
			
		||||
{
 | 
			
		||||
	struct rss_parser *parser = user_data;
 | 
			
		||||
 | 
			
		||||
	switch (parser->state) {
 | 
			
		||||
	case ROOT:
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case ITEM:
 | 
			
		||||
		if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
 | 
			
		||||
			if (parser->song->tag == NULL)
 | 
			
		||||
				parser->song->tag = tag_new();
 | 
			
		||||
			tag_add_item_n(parser->song->tag, parser->tag,
 | 
			
		||||
				       text, text_len);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const GMarkupParser rss_parser = {
 | 
			
		||||
	.start_element = rss_start_element,
 | 
			
		||||
	.end_element = rss_end_element,
 | 
			
		||||
	.text = rss_text,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
 | 
			
		||||
{
 | 
			
		||||
	struct song *song = data;
 | 
			
		||||
 | 
			
		||||
	song_free(song);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
rss_parser_destroy(gpointer data)
 | 
			
		||||
{
 | 
			
		||||
	struct rss_parser *parser = data;
 | 
			
		||||
 | 
			
		||||
	if (parser->state >= ITEM)
 | 
			
		||||
		song_free(parser->song);
 | 
			
		||||
 | 
			
		||||
	g_slist_foreach(parser->songs, song_free_callback, NULL);
 | 
			
		||||
	g_slist_free(parser->songs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * The playlist object
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
struct rss_playlist {
 | 
			
		||||
	struct playlist_provider base;
 | 
			
		||||
 | 
			
		||||
	GSList *songs;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct playlist_provider *
 | 
			
		||||
rss_open_stream(struct input_stream *is)
 | 
			
		||||
{
 | 
			
		||||
	struct rss_parser parser = {
 | 
			
		||||
		.songs = NULL,
 | 
			
		||||
		.state = ROOT,
 | 
			
		||||
	};
 | 
			
		||||
	struct rss_playlist *playlist;
 | 
			
		||||
	GMarkupParseContext *context;
 | 
			
		||||
	char buffer[1024];
 | 
			
		||||
	size_t nbytes;
 | 
			
		||||
	bool success;
 | 
			
		||||
	GError *error = NULL;
 | 
			
		||||
 | 
			
		||||
	/* parse the RSS XML file */
 | 
			
		||||
 | 
			
		||||
	context = g_markup_parse_context_new(&rss_parser,
 | 
			
		||||
					     G_MARKUP_TREAT_CDATA_AS_TEXT,
 | 
			
		||||
					     &parser, rss_parser_destroy);
 | 
			
		||||
 | 
			
		||||
	while (true) {
 | 
			
		||||
		nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
 | 
			
		||||
		if (nbytes == 0) {
 | 
			
		||||
			if (error != NULL) {
 | 
			
		||||
				g_markup_parse_context_free(context);
 | 
			
		||||
				g_warning("%s", error->message);
 | 
			
		||||
				g_error_free(error);
 | 
			
		||||
				return NULL;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		success = g_markup_parse_context_parse(context, buffer, nbytes,
 | 
			
		||||
						       &error);
 | 
			
		||||
		if (!success) {
 | 
			
		||||
			g_warning("XML parser failed: %s", error->message);
 | 
			
		||||
			g_error_free(error);
 | 
			
		||||
			g_markup_parse_context_free(context);
 | 
			
		||||
			return NULL;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	success = g_markup_parse_context_end_parse(context, &error);
 | 
			
		||||
	if (!success) {
 | 
			
		||||
		g_warning("XML parser failed: %s", error->message);
 | 
			
		||||
		g_error_free(error);
 | 
			
		||||
		g_markup_parse_context_free(context);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* create a #rss_playlist object from the parsed song list */
 | 
			
		||||
 | 
			
		||||
	playlist = g_new(struct rss_playlist, 1);
 | 
			
		||||
	playlist_provider_init(&playlist->base, &rss_playlist_plugin);
 | 
			
		||||
	playlist->songs = g_slist_reverse(parser.songs);
 | 
			
		||||
	parser.songs = NULL;
 | 
			
		||||
 | 
			
		||||
	g_markup_parse_context_free(context);
 | 
			
		||||
 | 
			
		||||
	return &playlist->base;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
rss_close(struct playlist_provider *_playlist)
 | 
			
		||||
{
 | 
			
		||||
	struct rss_playlist *playlist = (struct rss_playlist *)_playlist;
 | 
			
		||||
 | 
			
		||||
	g_slist_foreach(playlist->songs, song_free_callback, NULL);
 | 
			
		||||
	g_slist_free(playlist->songs);
 | 
			
		||||
	g_free(playlist);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct song *
 | 
			
		||||
rss_read(struct playlist_provider *_playlist)
 | 
			
		||||
{
 | 
			
		||||
	struct rss_playlist *playlist = (struct rss_playlist *)_playlist;
 | 
			
		||||
	struct song *song;
 | 
			
		||||
 | 
			
		||||
	if (playlist->songs == NULL)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	song = playlist->songs->data;
 | 
			
		||||
	playlist->songs = g_slist_remove(playlist->songs, song);
 | 
			
		||||
 | 
			
		||||
	return song;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char *const rss_suffixes[] = {
 | 
			
		||||
	"rss",
 | 
			
		||||
	NULL
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const char *const rss_mime_types[] = {
 | 
			
		||||
	"application/rss+xml",
 | 
			
		||||
	"text/xml",
 | 
			
		||||
	NULL
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct playlist_plugin rss_playlist_plugin = {
 | 
			
		||||
	.name = "rss",
 | 
			
		||||
 | 
			
		||||
	.open_stream = rss_open_stream,
 | 
			
		||||
	.close = rss_close,
 | 
			
		||||
	.read = rss_read,
 | 
			
		||||
 | 
			
		||||
	.suffixes = rss_suffixes,
 | 
			
		||||
	.mime_types = rss_mime_types,
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										25
									
								
								src/playlist/rss_playlist_plugin.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/playlist/rss_playlist_plugin.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef MPD_PLAYLIST_RSS_PLAYLIST_PLUGIN_H
 | 
			
		||||
#define MPD_PLAYLIST_RSS_PLAYLIST_PLUGIN_H
 | 
			
		||||
 | 
			
		||||
extern const struct playlist_plugin rss_playlist_plugin;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -46,7 +46,9 @@ bool
 | 
			
		||||
playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name,
 | 
			
		||||
		       GString *buffer, GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	struct playlist_metadata pm;
 | 
			
		||||
	struct playlist_metadata pm = {
 | 
			
		||||
		.mtime = 0,
 | 
			
		||||
	};
 | 
			
		||||
	char *line, *colon;
 | 
			
		||||
	const char *value;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@
 | 
			
		||||
#include "playlist/lastfm_playlist_plugin.h"
 | 
			
		||||
#include "playlist/pls_playlist_plugin.h"
 | 
			
		||||
#include "playlist/asx_playlist_plugin.h"
 | 
			
		||||
#include "playlist/rss_playlist_plugin.h"
 | 
			
		||||
#include "playlist/cue_playlist_plugin.h"
 | 
			
		||||
#include "playlist/flac_playlist_plugin.h"
 | 
			
		||||
#include "input_stream.h"
 | 
			
		||||
@@ -33,6 +34,7 @@
 | 
			
		||||
#include "utils.h"
 | 
			
		||||
#include "conf.h"
 | 
			
		||||
#include "glib_compat.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
@@ -44,6 +46,7 @@ static const struct playlist_plugin *const playlist_plugins[] = {
 | 
			
		||||
	&xspf_playlist_plugin,
 | 
			
		||||
	&pls_playlist_plugin,
 | 
			
		||||
	&asx_playlist_plugin,
 | 
			
		||||
	&rss_playlist_plugin,
 | 
			
		||||
#ifdef ENABLE_LASTFM
 | 
			
		||||
	&lastfm_playlist_plugin,
 | 
			
		||||
#endif
 | 
			
		||||
@@ -76,7 +79,7 @@ playlist_plugin_config(const char *plugin_name)
 | 
			
		||||
		const char *name =
 | 
			
		||||
			config_get_block_string(param, "name", NULL);
 | 
			
		||||
		if (name == NULL)
 | 
			
		||||
			g_error("playlist configuration without 'plugin' name in line %d",
 | 
			
		||||
			MPD_ERROR("playlist configuration without 'plugin' name in line %d",
 | 
			
		||||
				param->line);
 | 
			
		||||
 | 
			
		||||
		if (strcmp(name, plugin_name) == 0)
 | 
			
		||||
 
 | 
			
		||||
@@ -72,6 +72,14 @@ apply_song_metadata(struct song *dest, const struct song *src)
 | 
			
		||||
		song_free(dest);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (dest->tag != NULL && dest->tag->time > 0 &&
 | 
			
		||||
	    src->start_ms > 0 && src->end_ms == 0 &&
 | 
			
		||||
	    src->start_ms / 1000 < (unsigned)dest->tag->time)
 | 
			
		||||
		/* the range is open-ended, and the playlist plugin
 | 
			
		||||
		   did not know the total length of the song file
 | 
			
		||||
		   (e.g. last track on a CUE file); fix it up here */
 | 
			
		||||
		tmp->tag->time = dest->tag->time - src->start_ms / 1000;
 | 
			
		||||
 | 
			
		||||
	return tmp;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ playlist_state_save(FILE *fp, const struct playlist *playlist)
 | 
			
		||||
 | 
			
		||||
	pc_get_status(&player_status);
 | 
			
		||||
 | 
			
		||||
	fputs(PLAYLIST_STATE_FILE_STATE "\n", fp);
 | 
			
		||||
	fputs(PLAYLIST_STATE_FILE_STATE, fp);
 | 
			
		||||
 | 
			
		||||
	if (playlist->playing) {
 | 
			
		||||
		switch (player_status.state) {
 | 
			
		||||
 
 | 
			
		||||
@@ -93,16 +93,21 @@ playlist_vector_add(struct playlist_vector *pv,
 | 
			
		||||
	pv->head = pm;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
bool
 | 
			
		||||
playlist_vector_update_or_add(struct playlist_vector *pv,
 | 
			
		||||
			      const char *name, time_t mtime)
 | 
			
		||||
{
 | 
			
		||||
	struct playlist_metadata **pmp = playlist_vector_find_p(pv, name);
 | 
			
		||||
	if (pmp != NULL) {
 | 
			
		||||
		struct playlist_metadata *pm = *pmp;
 | 
			
		||||
		if (mtime == pm->mtime)
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
		pm->mtime = mtime;
 | 
			
		||||
	} else
 | 
			
		||||
		playlist_vector_add(pv, name, mtime);
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,10 @@ void
 | 
			
		||||
playlist_vector_add(struct playlist_vector *pv,
 | 
			
		||||
		    const char *name, time_t mtime);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
/**
 | 
			
		||||
 * @return true if the vector or one of its items was modified
 | 
			
		||||
 */
 | 
			
		||||
bool
 | 
			
		||||
playlist_vector_update_or_add(struct playlist_vector *pv,
 | 
			
		||||
			      const char *name, time_t mtime);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ poison_noaccess(void *p, size_t length)
 | 
			
		||||
	memset(p, 0x01, length);
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_VALGRIND_MEMCHECK_H
 | 
			
		||||
	VALGRIND_MAKE_MEM_NOACCESS(p, length);
 | 
			
		||||
	(void)VALGRIND_MAKE_MEM_NOACCESS(p, length);
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
@@ -68,7 +68,7 @@ poison_undefined(void *p, size_t length)
 | 
			
		||||
	memset(p, 0x02, length);
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_VALGRIND_MEMCHECK_H
 | 
			
		||||
	VALGRIND_MAKE_MEM_UNDEFINED(p, length);
 | 
			
		||||
	(void)VALGRIND_MAKE_MEM_UNDEFINED(p, length);
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										78
									
								
								src/replay_gain_ape.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/replay_gain_ape.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "replay_gain_ape.h"
 | 
			
		||||
#include "replay_gain_info.h"
 | 
			
		||||
#include "ape.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
struct rg_ape_ctx {
 | 
			
		||||
	struct replay_gain_info *info;
 | 
			
		||||
	bool found;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
replay_gain_ape_callback(unsigned long flags, const char *key,
 | 
			
		||||
			 const char *_value, size_t value_length, void *_ctx)
 | 
			
		||||
{
 | 
			
		||||
	struct rg_ape_ctx *ctx = _ctx;
 | 
			
		||||
 | 
			
		||||
	/* we only care about utf-8 text tags */
 | 
			
		||||
	if ((flags & (0x3 << 1)) != 0)
 | 
			
		||||
		return true;
 | 
			
		||||
 | 
			
		||||
	char value[16];
 | 
			
		||||
	if (value_length >= sizeof(value))
 | 
			
		||||
		return true;
 | 
			
		||||
	memcpy(value, _value, value_length);
 | 
			
		||||
	value[value_length] = 0;
 | 
			
		||||
 | 
			
		||||
	if (g_ascii_strcasecmp(key, "replaygain_track_gain") == 0) {
 | 
			
		||||
		ctx->info->tuples[REPLAY_GAIN_TRACK].gain = atof(value);
 | 
			
		||||
		ctx->found = true;
 | 
			
		||||
	} else if (g_ascii_strcasecmp(key, "replaygain_album_gain") == 0) {
 | 
			
		||||
		ctx->info->tuples[REPLAY_GAIN_ALBUM].gain = atof(value);
 | 
			
		||||
		ctx->found = true;
 | 
			
		||||
	} else if (g_ascii_strcasecmp(key, "replaygain_track_peak") == 0) {
 | 
			
		||||
		ctx->info->tuples[REPLAY_GAIN_TRACK].peak = atof(value);
 | 
			
		||||
		ctx->found = true;
 | 
			
		||||
	} else if (g_ascii_strcasecmp(key, "replaygain_album_peak") == 0) {
 | 
			
		||||
		ctx->info->tuples[REPLAY_GAIN_ALBUM].peak = atof(value);
 | 
			
		||||
		ctx->found = true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info)
 | 
			
		||||
{
 | 
			
		||||
	struct rg_ape_ctx ctx = {
 | 
			
		||||
		.info = info,
 | 
			
		||||
		.found = false,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	return tag_ape_scan(path_fs, replay_gain_ape_callback, &ctx) &&
 | 
			
		||||
		ctx.found;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								src/replay_gain_ape.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/replay_gain_ape.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef MPD_REPLAY_GAIN_APE_H
 | 
			
		||||
#define MPD_REPLAY_GAIN_APE_H
 | 
			
		||||
 | 
			
		||||
#include "check.h"
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
struct replay_gain_info;
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -22,6 +22,7 @@
 | 
			
		||||
#include "playlist.h"
 | 
			
		||||
#include "conf.h"
 | 
			
		||||
#include "idle.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
@@ -91,8 +92,8 @@ void replay_gain_global_init(void)
 | 
			
		||||
	const struct config_param *param = config_get_param(CONF_REPLAYGAIN);
 | 
			
		||||
 | 
			
		||||
	if (param != NULL && !replay_gain_set_mode_string(param->value)) {
 | 
			
		||||
		g_error("replaygain value \"%s\" at line %i is invalid\n",
 | 
			
		||||
			param->value, param->line);
 | 
			
		||||
		MPD_ERROR("replaygain value \"%s\" at line %i is invalid\n",
 | 
			
		||||
			  param->value, param->line);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	param = config_get_param(CONF_REPLAYGAIN_PREAMP);
 | 
			
		||||
@@ -102,13 +103,13 @@ void replay_gain_global_init(void)
 | 
			
		||||
		float f = strtod(param->value, &test);
 | 
			
		||||
 | 
			
		||||
		if (*test != '\0') {
 | 
			
		||||
			g_error("Replaygain preamp \"%s\" is not a number at "
 | 
			
		||||
				"line %i\n", param->value, param->line);
 | 
			
		||||
			MPD_ERROR("Replaygain preamp \"%s\" is not a number at "
 | 
			
		||||
				  "line %i\n", param->value, param->line);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (f < -15 || f > 15) {
 | 
			
		||||
			g_error("Replaygain preamp \"%s\" is not between -15 and"
 | 
			
		||||
				"15 at line %i\n", param->value, param->line);
 | 
			
		||||
			MPD_ERROR("Replaygain preamp \"%s\" is not between -15 and"
 | 
			
		||||
				  "15 at line %i\n", param->value, param->line);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		replay_gain_preamp = pow(10, f / 20.0);
 | 
			
		||||
@@ -121,13 +122,13 @@ void replay_gain_global_init(void)
 | 
			
		||||
		float f = strtod(param->value, &test);
 | 
			
		||||
 | 
			
		||||
		if (*test != '\0') {
 | 
			
		||||
			g_error("Replaygain missing preamp \"%s\" is not a number at "
 | 
			
		||||
				"line %i\n", param->value, param->line);
 | 
			
		||||
			MPD_ERROR("Replaygain missing preamp \"%s\" is not a number at "
 | 
			
		||||
				  "line %i\n", param->value, param->line);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (f < -15 || f > 15) {
 | 
			
		||||
			g_error("Replaygain missing preamp \"%s\" is not between -15 and"
 | 
			
		||||
				"15 at line %i\n", param->value, param->line);
 | 
			
		||||
			MPD_ERROR("Replaygain missing preamp \"%s\" is not between -15 and"
 | 
			
		||||
				  "15 at line %i\n", param->value, param->line);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		replay_gain_missing_preamp = pow(10, f / 20.0);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										444
									
								
								src/server_socket.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										444
									
								
								src/server_socket.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,444 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "server_socket.h"
 | 
			
		||||
#include "socket_util.h"
 | 
			
		||||
#include "fd_util.h"
 | 
			
		||||
#include "glib_compat.h"
 | 
			
		||||
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#include <sys/stat.h>
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
 | 
			
		||||
#ifdef WIN32
 | 
			
		||||
#define WINVER 0x0501
 | 
			
		||||
#include <ws2tcpip.h>
 | 
			
		||||
#include <winsock.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <netinet/in.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
#include <sys/un.h>
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#undef G_LOG_DOMAIN
 | 
			
		||||
#define G_LOG_DOMAIN "listen"
 | 
			
		||||
 | 
			
		||||
#define DEFAULT_PORT	6600
 | 
			
		||||
 | 
			
		||||
struct one_socket {
 | 
			
		||||
	struct one_socket *next;
 | 
			
		||||
	struct server_socket *parent;
 | 
			
		||||
 | 
			
		||||
	unsigned serial;
 | 
			
		||||
 | 
			
		||||
	int fd;
 | 
			
		||||
	guint source_id;
 | 
			
		||||
 | 
			
		||||
	char *path;
 | 
			
		||||
 | 
			
		||||
	size_t address_length;
 | 
			
		||||
	struct sockaddr address;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct server_socket {
 | 
			
		||||
	server_socket_callback_t callback;
 | 
			
		||||
	void *callback_ctx;
 | 
			
		||||
 | 
			
		||||
	struct one_socket *sockets, **sockets_tail_r;
 | 
			
		||||
	unsigned next_serial;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static GQuark
 | 
			
		||||
server_socket_quark(void)
 | 
			
		||||
{
 | 
			
		||||
	return g_quark_from_static_string("server_socket");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct server_socket *
 | 
			
		||||
server_socket_new(server_socket_callback_t callback, void *callback_ctx)
 | 
			
		||||
{
 | 
			
		||||
	struct server_socket *ss = g_new(struct server_socket, 1);
 | 
			
		||||
	ss->callback = callback;
 | 
			
		||||
	ss->callback_ctx = callback_ctx;
 | 
			
		||||
	ss->sockets = NULL;
 | 
			
		||||
	ss->sockets_tail_r = &ss->sockets;
 | 
			
		||||
	ss->next_serial = 1;
 | 
			
		||||
	return ss;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
server_socket_free(struct server_socket *ss)
 | 
			
		||||
{
 | 
			
		||||
	server_socket_close(ss);
 | 
			
		||||
 | 
			
		||||
	while (ss->sockets != NULL) {
 | 
			
		||||
		struct one_socket *s = ss->sockets;
 | 
			
		||||
		ss->sockets = s->next;
 | 
			
		||||
 | 
			
		||||
		assert(s->fd < 0);
 | 
			
		||||
 | 
			
		||||
		g_free(s->path);
 | 
			
		||||
		g_free(s);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g_free(ss);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wraper for sockaddr_to_string() which never fails.
 | 
			
		||||
 */
 | 
			
		||||
static char *
 | 
			
		||||
one_socket_to_string(const struct one_socket *s)
 | 
			
		||||
{
 | 
			
		||||
	char *p = sockaddr_to_string(&s->address, s->address_length, NULL);
 | 
			
		||||
	if (p == NULL)
 | 
			
		||||
		p = g_strdup("[unknown]");
 | 
			
		||||
	return p;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
get_remote_uid(int fd)
 | 
			
		||||
{
 | 
			
		||||
#ifdef HAVE_STRUCT_UCRED
 | 
			
		||||
	struct ucred cred;
 | 
			
		||||
	socklen_t len = sizeof (cred);
 | 
			
		||||
 | 
			
		||||
	if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	return cred.uid;
 | 
			
		||||
#else
 | 
			
		||||
#ifdef HAVE_GETPEEREID
 | 
			
		||||
	uid_t euid;
 | 
			
		||||
	gid_t egid;
 | 
			
		||||
 | 
			
		||||
	if (getpeereid(fd, &euid, &egid) == 0)
 | 
			
		||||
		return euid;
 | 
			
		||||
#else
 | 
			
		||||
	(void)fd;
 | 
			
		||||
#endif
 | 
			
		||||
	return -1;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static gboolean
 | 
			
		||||
server_socket_in_event(G_GNUC_UNUSED GIOChannel *source,
 | 
			
		||||
		       G_GNUC_UNUSED GIOCondition condition,
 | 
			
		||||
		       gpointer data)
 | 
			
		||||
{
 | 
			
		||||
	struct one_socket *s = data;
 | 
			
		||||
 | 
			
		||||
	struct sockaddr_storage address;
 | 
			
		||||
	size_t address_length = sizeof(address);
 | 
			
		||||
	int fd = accept_cloexec_nonblock(s->fd, (struct sockaddr*)&address,
 | 
			
		||||
					 &address_length);
 | 
			
		||||
	if (fd >= 0)
 | 
			
		||||
		s->parent->callback(fd, (const struct sockaddr*)&address,
 | 
			
		||||
				    address_length, get_remote_uid(fd),
 | 
			
		||||
				    s->parent->callback_ctx);
 | 
			
		||||
	else
 | 
			
		||||
		g_warning("accept() failed: %s", g_strerror(errno));
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
server_socket_open(struct server_socket *ss, GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
	struct one_socket *good = NULL, *bad = NULL;
 | 
			
		||||
	GError *last_error = NULL;
 | 
			
		||||
 | 
			
		||||
	for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) {
 | 
			
		||||
		assert(s->serial > 0);
 | 
			
		||||
		assert(good == NULL || s->serial >= good->serial);
 | 
			
		||||
		assert(s->fd < 0);
 | 
			
		||||
 | 
			
		||||
		if (bad != NULL && s->serial != bad->serial) {
 | 
			
		||||
			server_socket_close(ss);
 | 
			
		||||
			g_propagate_error(error_r, last_error);
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		GError *error = NULL;
 | 
			
		||||
		s->fd = socket_bind_listen(s->address.sa_family, SOCK_STREAM, 0,
 | 
			
		||||
					   &s->address, s->address_length, 5,
 | 
			
		||||
					   &error);
 | 
			
		||||
		if (s->fd < 0) {
 | 
			
		||||
			if (good != NULL && good->serial == s->serial) {
 | 
			
		||||
				char *address_string = one_socket_to_string(s);
 | 
			
		||||
				char *good_string = one_socket_to_string(good);
 | 
			
		||||
				g_warning("bind to '%s' failed: %s "
 | 
			
		||||
					  "(continuing anyway, because "
 | 
			
		||||
					  "binding to '%s' succeeded)",
 | 
			
		||||
					  address_string, error->message,
 | 
			
		||||
					  good_string);
 | 
			
		||||
				g_free(address_string);
 | 
			
		||||
				g_free(good_string);
 | 
			
		||||
				g_error_free(error);
 | 
			
		||||
			} else if (bad == NULL) {
 | 
			
		||||
				bad = s;
 | 
			
		||||
 | 
			
		||||
				char *address_string = one_socket_to_string(s);
 | 
			
		||||
				g_propagate_prefixed_error(&last_error, error,
 | 
			
		||||
							   "Failed to bind to '%s': ",
 | 
			
		||||
							   address_string);
 | 
			
		||||
				g_free(address_string);
 | 
			
		||||
			} else
 | 
			
		||||
				g_error_free(error);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* allow everybody to connect */
 | 
			
		||||
 | 
			
		||||
		if (s->path != NULL)
 | 
			
		||||
			chmod(s->path, 0666);
 | 
			
		||||
 | 
			
		||||
		/* register in the GLib main loop */
 | 
			
		||||
 | 
			
		||||
		GIOChannel *channel = g_io_channel_unix_new(s->fd);
 | 
			
		||||
		s->source_id = g_io_add_watch(channel, G_IO_IN,
 | 
			
		||||
					      server_socket_in_event, s);
 | 
			
		||||
		g_io_channel_unref(channel);
 | 
			
		||||
 | 
			
		||||
		/* mark this socket as "good", and clear previous
 | 
			
		||||
		   errors */
 | 
			
		||||
 | 
			
		||||
		good = s;
 | 
			
		||||
 | 
			
		||||
		if (bad != NULL) {
 | 
			
		||||
			bad = NULL;
 | 
			
		||||
			g_error_free(last_error);
 | 
			
		||||
			last_error = NULL;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (bad != NULL) {
 | 
			
		||||
		server_socket_close(ss);
 | 
			
		||||
		g_propagate_error(error_r, last_error);
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
server_socket_close(struct server_socket *ss)
 | 
			
		||||
{
 | 
			
		||||
	for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) {
 | 
			
		||||
		if (s->fd < 0)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		g_source_remove(s->source_id);
 | 
			
		||||
		close(s->fd);
 | 
			
		||||
		s->fd = -1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct one_socket *
 | 
			
		||||
one_socket_new(unsigned serial, const struct sockaddr *address,
 | 
			
		||||
	       size_t address_length)
 | 
			
		||||
{
 | 
			
		||||
	assert(address != NULL);
 | 
			
		||||
	assert(address_length > 0);
 | 
			
		||||
 | 
			
		||||
	struct one_socket *s = g_malloc(sizeof(*s) - sizeof(s->address) +
 | 
			
		||||
					address_length);
 | 
			
		||||
	s->next = NULL;
 | 
			
		||||
	s->serial = serial;
 | 
			
		||||
	s->fd = -1;
 | 
			
		||||
	s->path = NULL;
 | 
			
		||||
	s->address_length = address_length;
 | 
			
		||||
	memcpy(&s->address, address, address_length);
 | 
			
		||||
 | 
			
		||||
	return s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct one_socket *
 | 
			
		||||
server_socket_add_address(struct server_socket *ss,
 | 
			
		||||
			  const struct sockaddr *address,
 | 
			
		||||
			  size_t address_length)
 | 
			
		||||
{
 | 
			
		||||
	assert(ss != NULL);
 | 
			
		||||
	assert(ss->sockets_tail_r != NULL);
 | 
			
		||||
	assert(*ss->sockets_tail_r == NULL);
 | 
			
		||||
 | 
			
		||||
	struct one_socket *s = one_socket_new(ss->next_serial,
 | 
			
		||||
					      address, address_length);
 | 
			
		||||
	s->parent = ss;
 | 
			
		||||
	*ss->sockets_tail_r = s;
 | 
			
		||||
	ss->sockets_tail_r = &s->next;
 | 
			
		||||
 | 
			
		||||
	return s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_TCP
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a listener on a port on all IPv4 interfaces.
 | 
			
		||||
 *
 | 
			
		||||
 * @param port the TCP port
 | 
			
		||||
 */
 | 
			
		||||
static void
 | 
			
		||||
server_socket_add_port_ipv4(struct server_socket *ss, unsigned port)
 | 
			
		||||
{
 | 
			
		||||
	struct sockaddr_in sin;
 | 
			
		||||
	memset(&sin, 0, sizeof(sin));
 | 
			
		||||
	sin.sin_port = htons(port);
 | 
			
		||||
	sin.sin_family = AF_INET;
 | 
			
		||||
	sin.sin_addr.s_addr = INADDR_ANY;
 | 
			
		||||
 | 
			
		||||
	server_socket_add_address(ss, (const struct sockaddr *)&sin,
 | 
			
		||||
				  sizeof(sin));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_IPV6
 | 
			
		||||
/**
 | 
			
		||||
 * Add a listener on a port on all IPv6 interfaces.
 | 
			
		||||
 *
 | 
			
		||||
 * @param port the TCP port
 | 
			
		||||
 */
 | 
			
		||||
static void
 | 
			
		||||
server_socket_add_port_ipv6(struct server_socket *ss, unsigned port)
 | 
			
		||||
{
 | 
			
		||||
	struct sockaddr_in6 sin;
 | 
			
		||||
	memset(&sin, 0, sizeof(sin));
 | 
			
		||||
	sin.sin6_port = htons(port);
 | 
			
		||||
	sin.sin6_family = AF_INET6;
 | 
			
		||||
 | 
			
		||||
	server_socket_add_address(ss, (const struct sockaddr *)&sin,
 | 
			
		||||
				  sizeof(sin));
 | 
			
		||||
}
 | 
			
		||||
#endif /* HAVE_IPV6 */
 | 
			
		||||
 | 
			
		||||
#endif /* HAVE_TCP */
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
server_socket_add_port(struct server_socket *ss, unsigned port,
 | 
			
		||||
		       GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
#ifdef HAVE_TCP
 | 
			
		||||
	if (port == 0 || port > 0xffff) {
 | 
			
		||||
		g_set_error(error_r, server_socket_quark(), 0,
 | 
			
		||||
			    "Invalid TCP port");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_IPV6
 | 
			
		||||
	server_socket_add_port_ipv6(ss, port);
 | 
			
		||||
#endif
 | 
			
		||||
	server_socket_add_port_ipv4(ss, port);
 | 
			
		||||
 | 
			
		||||
	++ss->next_serial;
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
#else /* HAVE_TCP */
 | 
			
		||||
	(void)ss;
 | 
			
		||||
	(void)port;
 | 
			
		||||
 | 
			
		||||
	g_set_error(error_r, server_socket_quark(), 0,
 | 
			
		||||
		    "TCP support is disabled");
 | 
			
		||||
	return false;
 | 
			
		||||
#endif /* HAVE_TCP */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
server_socket_add_host(struct server_socket *ss, const char *hostname,
 | 
			
		||||
		       unsigned port, GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
#ifdef HAVE_TCP
 | 
			
		||||
	struct addrinfo hints;
 | 
			
		||||
	memset(&hints, 0, sizeof(hints));
 | 
			
		||||
	hints.ai_flags = AI_PASSIVE;
 | 
			
		||||
	hints.ai_family = PF_UNSPEC;
 | 
			
		||||
	hints.ai_socktype = SOCK_STREAM;
 | 
			
		||||
	hints.ai_protocol = IPPROTO_TCP;
 | 
			
		||||
 | 
			
		||||
	char service[20];
 | 
			
		||||
	g_snprintf(service, sizeof(service), "%u", port);
 | 
			
		||||
 | 
			
		||||
	struct addrinfo *ai;
 | 
			
		||||
	int ret = getaddrinfo(hostname, service, &hints, &ai);
 | 
			
		||||
	if (ret != 0) {
 | 
			
		||||
		g_set_error(error_r, server_socket_quark(), ret,
 | 
			
		||||
			    "Failed to look up host \"%s\": %s",
 | 
			
		||||
			    hostname, gai_strerror(ret));
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next)
 | 
			
		||||
		server_socket_add_address(ss, i->ai_addr, i->ai_addrlen);
 | 
			
		||||
 | 
			
		||||
	freeaddrinfo(ai);
 | 
			
		||||
 | 
			
		||||
	++ss->next_serial;
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
#else /* HAVE_TCP */
 | 
			
		||||
	(void)ss;
 | 
			
		||||
	(void)hostname;
 | 
			
		||||
	(void)port;
 | 
			
		||||
 | 
			
		||||
	g_set_error(error_r, server_socket_quark(), 0,
 | 
			
		||||
		    "TCP support is disabled");
 | 
			
		||||
	return false;
 | 
			
		||||
#endif /* HAVE_TCP */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
server_socket_add_path(struct server_socket *ss, const char *path,
 | 
			
		||||
		       GError **error_r)
 | 
			
		||||
{
 | 
			
		||||
#ifdef HAVE_UN
 | 
			
		||||
	struct sockaddr_un s_un;
 | 
			
		||||
 | 
			
		||||
	size_t path_length = strlen(path);
 | 
			
		||||
	if (path_length >= sizeof(s_un.sun_path)) {
 | 
			
		||||
		g_set_error(error_r, server_socket_quark(), 0,
 | 
			
		||||
			    "UNIX socket path is too long");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	unlink(path);
 | 
			
		||||
 | 
			
		||||
	s_un.sun_family = AF_UNIX;
 | 
			
		||||
	memcpy(s_un.sun_path, path, path_length + 1);
 | 
			
		||||
 | 
			
		||||
	struct one_socket *s =
 | 
			
		||||
		server_socket_add_address(ss, (const struct sockaddr *)&s_un,
 | 
			
		||||
					  sizeof(s_un));
 | 
			
		||||
	s->path = g_strdup(path);
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
#else /* !HAVE_UN */
 | 
			
		||||
	(void)ss;
 | 
			
		||||
	(void)path;
 | 
			
		||||
 | 
			
		||||
	g_set_error(error_r, server_socket_quark(), 0,
 | 
			
		||||
		    "UNIX domain socket support is disabled");
 | 
			
		||||
	return false;
 | 
			
		||||
#endif /* !HAVE_UN */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										84
									
								
								src/server_socket.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/server_socket.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef MPD_SERVER_SOCKET_H
 | 
			
		||||
#define MPD_SERVER_SOCKET_H
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
struct sockaddr;
 | 
			
		||||
 | 
			
		||||
typedef void (*server_socket_callback_t)(int fd,
 | 
			
		||||
					 const struct sockaddr *address,
 | 
			
		||||
					 size_t address_length, int uid,
 | 
			
		||||
					 void *ctx);
 | 
			
		||||
 | 
			
		||||
struct server_socket *
 | 
			
		||||
server_socket_new(server_socket_callback_t callback, void *callback_ctx);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
server_socket_free(struct server_socket *ss);
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
server_socket_open(struct server_socket *ss, GError **error_r);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
server_socket_close(struct server_socket *ss);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a listener on a port on all interfaces.
 | 
			
		||||
 *
 | 
			
		||||
 * @param port the TCP port
 | 
			
		||||
 * @param error_r location to store the error occuring, or NULL to
 | 
			
		||||
 * ignore errors
 | 
			
		||||
 * @return true on success
 | 
			
		||||
 */
 | 
			
		||||
bool
 | 
			
		||||
server_socket_add_port(struct server_socket *ss, unsigned port,
 | 
			
		||||
		       GError **error_r);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Resolves a host name, and adds listeners on all addresses in the
 | 
			
		||||
 * result set.
 | 
			
		||||
 *
 | 
			
		||||
 * @param hostname the host name to be resolved
 | 
			
		||||
 * @param port the TCP port
 | 
			
		||||
 * @param error_r location to store the error occuring, or NULL to
 | 
			
		||||
 * ignore errors
 | 
			
		||||
 * @return true on success
 | 
			
		||||
 */
 | 
			
		||||
bool
 | 
			
		||||
server_socket_add_host(struct server_socket *ss, const char *hostname,
 | 
			
		||||
		       unsigned port, GError **error_r);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a listener on a Unix domain socket.
 | 
			
		||||
 *
 | 
			
		||||
 * @param path the absolute socket path
 | 
			
		||||
 * @param error_r location to store the error occuring, or NULL to
 | 
			
		||||
 * ignore errors
 | 
			
		||||
 * @return true on success
 | 
			
		||||
 */
 | 
			
		||||
bool
 | 
			
		||||
server_socket_add_path(struct server_socket *ss, const char *path,
 | 
			
		||||
		       GError **error_r);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -25,6 +25,7 @@
 | 
			
		||||
#include "log.h"
 | 
			
		||||
#include "main.h"
 | 
			
		||||
#include "event_pipe.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
@@ -46,7 +47,7 @@ static void
 | 
			
		||||
x_sigaction(int signum, const struct sigaction *act)
 | 
			
		||||
{
 | 
			
		||||
	if (sigaction(signum, act, NULL) < 0)
 | 
			
		||||
		g_error("sigaction() failed: %s", strerror(errno));
 | 
			
		||||
		MPD_ERROR("sigaction() failed: %s", strerror(errno));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@
 | 
			
		||||
#include "tag_pool.h"
 | 
			
		||||
#include "conf.h"
 | 
			
		||||
#include "song.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
@@ -138,8 +139,8 @@ void tag_lib_init(void)
 | 
			
		||||
 | 
			
		||||
			type = tag_name_parse_i(c);
 | 
			
		||||
			if (type == TAG_NUM_OF_ITEM_TYPES)
 | 
			
		||||
				g_error("error parsing metadata item \"%s\"",
 | 
			
		||||
					c);
 | 
			
		||||
				MPD_ERROR("error parsing metadata item \"%s\"",
 | 
			
		||||
					  c);
 | 
			
		||||
 | 
			
		||||
			ignore_tag_items[type] = false;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										128
									
								
								src/tag_ape.c
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								src/tag_ape.c
									
									
									
									
									
								
							@@ -21,11 +21,7 @@
 | 
			
		||||
#include "tag_ape.h"
 | 
			
		||||
#include "tag.h"
 | 
			
		||||
#include "tag_table.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include "ape.h"
 | 
			
		||||
 | 
			
		||||
static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
 | 
			
		||||
	[TAG_ALBUM_ARTIST] = "album artist",
 | 
			
		||||
@@ -56,101 +52,45 @@ tag_ape_import_item(struct tag *tag, unsigned long flags,
 | 
			
		||||
 | 
			
		||||
	if (tag == NULL)
 | 
			
		||||
		tag = tag_new();
 | 
			
		||||
	tag_add_item_n(tag, type, value, value_length);
 | 
			
		||||
 | 
			
		||||
	const char *end = value + value_length;
 | 
			
		||||
	while (true) {
 | 
			
		||||
		/* multiple values are separated by null bytes */
 | 
			
		||||
		const char *n = memchr(value, 0, end - value);
 | 
			
		||||
		if (n != NULL) {
 | 
			
		||||
			if (n > value)
 | 
			
		||||
				tag_add_item_n(tag, type, value, n - value);
 | 
			
		||||
			value = n + 1;
 | 
			
		||||
		} else {
 | 
			
		||||
			if (end > value)
 | 
			
		||||
				tag_add_item_n(tag, type, value, end - value);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tag;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct tag_ape_ctx {
 | 
			
		||||
	struct tag *tag;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
tag_ape_callback(unsigned long flags, const char *key,
 | 
			
		||||
		 const char *value, size_t value_length, void *_ctx)
 | 
			
		||||
{
 | 
			
		||||
	struct tag_ape_ctx *ctx = _ctx;
 | 
			
		||||
 | 
			
		||||
	ctx->tag = tag_ape_import_item(ctx->tag, flags, key,
 | 
			
		||||
				       value, value_length);
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct tag *
 | 
			
		||||
tag_ape_load(const char *file)
 | 
			
		||||
{
 | 
			
		||||
	struct tag *ret = NULL;
 | 
			
		||||
	FILE *fp;
 | 
			
		||||
	int tagCount;
 | 
			
		||||
	char *buffer = NULL;
 | 
			
		||||
	char *p;
 | 
			
		||||
	size_t tagLen;
 | 
			
		||||
	size_t size;
 | 
			
		||||
	unsigned long flags;
 | 
			
		||||
	char *key;
 | 
			
		||||
	struct tag_ape_ctx ctx = { .tag = NULL };
 | 
			
		||||
 | 
			
		||||
	struct {
 | 
			
		||||
		unsigned char id[8];
 | 
			
		||||
		uint32_t version;
 | 
			
		||||
		uint32_t length;
 | 
			
		||||
		uint32_t tagCount;
 | 
			
		||||
		unsigned char flags[4];
 | 
			
		||||
		unsigned char reserved[8];
 | 
			
		||||
	} footer;
 | 
			
		||||
 | 
			
		||||
	fp = fopen(file, "rb");
 | 
			
		||||
	if (!fp)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	/* determine if file has an apeV2 tag */
 | 
			
		||||
	if (fseek(fp, 0, SEEK_END))
 | 
			
		||||
		goto fail;
 | 
			
		||||
	size = (size_t)ftell(fp);
 | 
			
		||||
	if (fseek(fp, size - sizeof(footer), SEEK_SET))
 | 
			
		||||
		goto fail;
 | 
			
		||||
	if (fread(&footer, 1, sizeof(footer), fp) != sizeof(footer))
 | 
			
		||||
		goto fail;
 | 
			
		||||
	if (memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0)
 | 
			
		||||
		goto fail;
 | 
			
		||||
	if (GUINT32_FROM_LE(footer.version) != 2000)
 | 
			
		||||
		goto fail;
 | 
			
		||||
 | 
			
		||||
	/* find beginning of ape tag */
 | 
			
		||||
	tagLen = GUINT32_FROM_LE(footer.length);
 | 
			
		||||
	if (tagLen <= sizeof(footer) + 10)
 | 
			
		||||
		goto fail;
 | 
			
		||||
	if (tagLen > 1024 * 1024)
 | 
			
		||||
		/* refuse to load more than one megabyte of tag data */
 | 
			
		||||
		goto fail;
 | 
			
		||||
	if (fseek(fp, size - tagLen, SEEK_SET))
 | 
			
		||||
		goto fail;
 | 
			
		||||
 | 
			
		||||
	/* read tag into buffer */
 | 
			
		||||
	tagLen -= sizeof(footer);
 | 
			
		||||
	assert(tagLen > 10);
 | 
			
		||||
 | 
			
		||||
	buffer = g_malloc(tagLen);
 | 
			
		||||
	if (fread(buffer, 1, tagLen, fp) != tagLen)
 | 
			
		||||
		goto fail;
 | 
			
		||||
 | 
			
		||||
	/* read tags */
 | 
			
		||||
	tagCount = GUINT32_FROM_LE(footer.tagCount);
 | 
			
		||||
	p = buffer;
 | 
			
		||||
	while (tagCount-- && tagLen > 10) {
 | 
			
		||||
		size = GUINT32_FROM_LE(*(const uint32_t *)p);
 | 
			
		||||
		p += 4;
 | 
			
		||||
		tagLen -= 4;
 | 
			
		||||
		flags = GUINT32_FROM_LE(*(const uint32_t *)p);
 | 
			
		||||
		p += 4;
 | 
			
		||||
		tagLen -= 4;
 | 
			
		||||
 | 
			
		||||
		/* get the key */
 | 
			
		||||
		key = p;
 | 
			
		||||
		while (tagLen > size && *p != '\0') {
 | 
			
		||||
			p++;
 | 
			
		||||
			tagLen--;
 | 
			
		||||
		}
 | 
			
		||||
		p++;
 | 
			
		||||
		tagLen--;
 | 
			
		||||
 | 
			
		||||
		/* get the value */
 | 
			
		||||
		if (tagLen < size)
 | 
			
		||||
			goto fail;
 | 
			
		||||
 | 
			
		||||
		ret = tag_ape_import_item(ret, flags, key, p, size);
 | 
			
		||||
 | 
			
		||||
		p += size;
 | 
			
		||||
		tagLen -= size;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
fail:
 | 
			
		||||
	if (fp)
 | 
			
		||||
		fclose(fp);
 | 
			
		||||
	g_free(buffer);
 | 
			
		||||
	return ret;
 | 
			
		||||
	tag_ape_scan(file, tag_ape_callback, &ctx);
 | 
			
		||||
	return ctx.tag;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -126,17 +126,16 @@ import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
 | 
			
		||||
 * - string list
 | 
			
		||||
 */
 | 
			
		||||
static void
 | 
			
		||||
tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
 | 
			
		||||
		    enum tag_type type)
 | 
			
		||||
tag_id3_import_text_frame(struct tag *dest, struct id3_tag *tag,
 | 
			
		||||
			  const struct id3_frame *frame,
 | 
			
		||||
			  enum tag_type type)
 | 
			
		||||
{
 | 
			
		||||
	struct id3_frame const *frame;
 | 
			
		||||
	id3_ucs4_t const *ucs4;
 | 
			
		||||
	id3_utf8_t *utf8;
 | 
			
		||||
	union id3_field const *field;
 | 
			
		||||
	unsigned int nstrings, i;
 | 
			
		||||
 | 
			
		||||
	frame = id3_tag_findframe(tag, id, 0);
 | 
			
		||||
	if (frame == NULL || frame->nfields != 2)
 | 
			
		||||
	if (frame->nfields != 2)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	/* check the encoding field */
 | 
			
		||||
@@ -170,6 +169,20 @@ tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Import all text frames with the specified id (ID3v2.4.0 section
 | 
			
		||||
 * 4.2).  This is a wrapper for tag_id3_import_text_frame().
 | 
			
		||||
 */
 | 
			
		||||
static void
 | 
			
		||||
tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
 | 
			
		||||
		    enum tag_type type)
 | 
			
		||||
{
 | 
			
		||||
	const struct id3_frame *frame;
 | 
			
		||||
	for (unsigned i = 0;
 | 
			
		||||
	     (frame = id3_tag_findframe(tag, id, i)) != NULL; ++i)
 | 
			
		||||
		tag_id3_import_text_frame(dest, tag, frame, type);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Import a "Comment frame" (ID3v2.4.0 section 4.10).  It
 | 
			
		||||
 * contains 4 fields:
 | 
			
		||||
@@ -180,16 +193,15 @@ tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
 | 
			
		||||
 * - full string (we use this one)
 | 
			
		||||
 */
 | 
			
		||||
static void
 | 
			
		||||
tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
 | 
			
		||||
		       enum tag_type type)
 | 
			
		||||
tag_id3_import_comment_frame(struct tag *dest, struct id3_tag *tag,
 | 
			
		||||
			     const struct id3_frame *frame,
 | 
			
		||||
			     enum tag_type type)
 | 
			
		||||
{
 | 
			
		||||
	struct id3_frame const *frame;
 | 
			
		||||
	id3_ucs4_t const *ucs4;
 | 
			
		||||
	id3_utf8_t *utf8;
 | 
			
		||||
	union id3_field const *field;
 | 
			
		||||
 | 
			
		||||
	frame = id3_tag_findframe(tag, id, 0);
 | 
			
		||||
	if (frame == NULL || frame->nfields != 4)
 | 
			
		||||
	if (frame->nfields != 4)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	/* for now I only read the 4th field, with the fullstring */
 | 
			
		||||
@@ -209,6 +221,20 @@ tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
 | 
			
		||||
	g_free(utf8);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Import all comment frames (ID3v2.4.0 section 4.10).  This is a
 | 
			
		||||
 * wrapper for tag_id3_import_comment_frame().
 | 
			
		||||
 */
 | 
			
		||||
static void
 | 
			
		||||
tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
 | 
			
		||||
		       enum tag_type type)
 | 
			
		||||
{
 | 
			
		||||
	const struct id3_frame *frame;
 | 
			
		||||
	for (unsigned i = 0;
 | 
			
		||||
	     (frame = id3_tag_findframe(tag, id, i)) != NULL; ++i)
 | 
			
		||||
		tag_id3_import_comment_frame(dest, tag, frame, type);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Parse a TXXX name, and convert it to a tag_type enum value.
 | 
			
		||||
 * Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								src/timer.c
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/timer.c
									
									
									
									
									
								
							@@ -71,6 +71,19 @@ void timer_add(Timer *timer, int size)
 | 
			
		||||
	timer->time += ((uint64_t)size * 1000000) / timer->rate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
unsigned
 | 
			
		||||
timer_delay(const Timer *timer)
 | 
			
		||||
{
 | 
			
		||||
	int64_t delay = (int64_t)(timer->time - now()) / 1000;
 | 
			
		||||
	if (delay < 0)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	if (delay > G_MAXINT)
 | 
			
		||||
		delay = G_MAXINT;
 | 
			
		||||
 | 
			
		||||
	return delay / 1000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void timer_sync(Timer *timer)
 | 
			
		||||
{
 | 
			
		||||
	int64_t sleep_duration;
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,12 @@ void timer_reset(Timer *timer);
 | 
			
		||||
 | 
			
		||||
void timer_add(Timer *timer, int size);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns the number of milliseconds to sleep to get back to sync.
 | 
			
		||||
 */
 | 
			
		||||
unsigned
 | 
			
		||||
timer_delay(const Timer *timer);
 | 
			
		||||
 | 
			
		||||
void timer_sync(Timer *timer);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@
 | 
			
		||||
#include "idle.h"
 | 
			
		||||
#include "stats.h"
 | 
			
		||||
#include "main.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
@@ -93,7 +94,7 @@ spawn_update_task(const char *path)
 | 
			
		||||
 | 
			
		||||
	update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e);
 | 
			
		||||
	if (update_thr == NULL)
 | 
			
		||||
		g_error("Failed to spawn update task: %s", e->message);
 | 
			
		||||
		MPD_ERROR("Failed to spawn update task: %s", e->message);
 | 
			
		||||
 | 
			
		||||
	if (++update_task_id > update_task_id_max)
 | 
			
		||||
		update_task_id = 1;
 | 
			
		||||
 
 | 
			
		||||
@@ -546,6 +546,31 @@ update_container_file(	struct directory* directory,
 | 
			
		||||
		return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks if the given permissions on the mapped file are given.
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
directory_child_access(const struct directory *directory,
 | 
			
		||||
		       const char *name, int mode)
 | 
			
		||||
{
 | 
			
		||||
#ifdef WIN32
 | 
			
		||||
	/* access() is useless on WIN32 */
 | 
			
		||||
	(void)directory;
 | 
			
		||||
	(void)name;
 | 
			
		||||
	return true;
 | 
			
		||||
#else
 | 
			
		||||
	char *path = map_directory_child_fs(directory, name);
 | 
			
		||||
	if (path == NULL)
 | 
			
		||||
		/* something went wrong, but that isn't a permission
 | 
			
		||||
		   problem */
 | 
			
		||||
		return true;
 | 
			
		||||
 | 
			
		||||
	bool success = access(path, mode) == 0 || errno != EACCES;
 | 
			
		||||
	g_free(path);
 | 
			
		||||
	return success;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
update_regular_file(struct directory *directory,
 | 
			
		||||
		    const char *name, const struct stat *st)
 | 
			
		||||
@@ -562,6 +587,14 @@ update_regular_file(struct directory *directory,
 | 
			
		||||
	{
 | 
			
		||||
		struct song* song = songvec_find(&directory->songs, name);
 | 
			
		||||
 | 
			
		||||
		if (!directory_child_access(directory, name, R_OK)) {
 | 
			
		||||
			g_warning("no read permissions on %s/%s",
 | 
			
		||||
				  directory_get_path(directory), name);
 | 
			
		||||
			if (song != NULL)
 | 
			
		||||
				delete_song(directory, song);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!(song != NULL && st->st_mtime == song->mtime &&
 | 
			
		||||
		      !walk_discard) &&
 | 
			
		||||
			plugin->container_scan != NULL)
 | 
			
		||||
@@ -604,7 +637,9 @@ update_regular_file(struct directory *directory,
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	} else if (playlist_suffix_supported(suffix)) {
 | 
			
		||||
		playlist_vector_add(&directory->playlists, name, st->st_mtime);
 | 
			
		||||
		if (playlist_vector_update_or_add(&directory->playlists, name,
 | 
			
		||||
						  st->st_mtime))
 | 
			
		||||
			modified = true;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "zeroconf-internal.h"
 | 
			
		||||
#include "listen.h"
 | 
			
		||||
#include "mpd_error.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
@@ -218,7 +219,7 @@ void init_avahi(const char *serviceName)
 | 
			
		||||
	g_debug("Initializing interface");
 | 
			
		||||
 | 
			
		||||
	if (!avahi_is_valid_service_name(serviceName))
 | 
			
		||||
		g_error("Invalid zeroconf_name \"%s\"", serviceName);
 | 
			
		||||
		MPD_ERROR("Invalid zeroconf_name \"%s\"", serviceName);
 | 
			
		||||
 | 
			
		||||
	avahiName = avahi_strdup(serviceName);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@ void init_zeroconf_osx(const char *serviceName)
 | 
			
		||||
	DNSServiceErrorType error = DNSServiceRegister(&dnsReference,
 | 
			
		||||
						       0, 0, serviceName,
 | 
			
		||||
						       SERVICE_TYPE, NULL, NULL,
 | 
			
		||||
						       htons(listen_port), 0,
 | 
			
		||||
						       g_htons(listen_port), 0,
 | 
			
		||||
						       NULL,
 | 
			
		||||
						       dnsRegisterCallback,
 | 
			
		||||
						       NULL);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user