Compare commits
	
		
			89 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					a3f3c7ba24 | ||
| 
						 | 
					94efeb2845 | ||
| 
						 | 
					a73834436f | ||
| 
						 | 
					85f4aeca05 | ||
| 
						 | 
					7db84a961a | ||
| 
						 | 
					a960e2ef48 | ||
| 
						 | 
					4fe272a7fb | ||
| 
						 | 
					a7d9f248ea | ||
| 
						 | 
					06aa689383 | ||
| 
						 | 
					835b0c44cd | ||
| 
						 | 
					54b6f8a4ae | ||
| 
						 | 
					18787ebe8f | ||
| 
						 | 
					47e8fcf37e | ||
| 
						 | 
					5958b78459 | ||
| 
						 | 
					9d9697b366 | ||
| 
						 | 
					6585e18571 | ||
| 
						 | 
					6f1b4292f0 | ||
| 
						 | 
					ef9ef03b1f | ||
| 
						 | 
					ecb67a1ed1 | ||
| 
						 | 
					0ef843f138 | ||
| 
						 | 
					eb79d83051 | ||
| 
						 | 
					ca1a11493d | ||
| 
						 | 
					69bb086ba5 | ||
| 
						 | 
					11a5ee821b | ||
| 
						 | 
					a8a85143f6 | ||
| 
						 | 
					e2cc328eef | ||
| 
						 | 
					344d10a8e3 | ||
| 
						 | 
					09384df32c | ||
| 
						 | 
					20538516b9 | ||
| 
						 | 
					0759421d11 | ||
| 
						 | 
					bf7417981f | ||
| 
						 | 
					dba41e2e4a | ||
| 
						 | 
					bc6472bb9e | ||
| 
						 | 
					d4bd947bf5 | ||
| 
						 | 
					d8e8eabf60 | ||
| 
						 | 
					a70443af31 | ||
| 
						 | 
					3f221e2edb | ||
| 
						 | 
					848ed14788 | ||
| 
						 | 
					4c8a5dfb05 | ||
| 
						 | 
					4f61ba766d | ||
| 
						 | 
					8bfdb4ed0c | ||
| 
						 | 
					70bd35abe2 | ||
| 
						 | 
					0efb67b51e | ||
| 
						 | 
					54ebf2a699 | ||
| 
						 | 
					d0119548c1 | ||
| 
						 | 
					95ac6071b9 | ||
| 
						 | 
					3a4e667078 | ||
| 
						 | 
					ce18c36ed9 | ||
| 
						 | 
					8e39cf62e7 | ||
| 
						 | 
					a9e351e00d | ||
| 
						 | 
					d65841a2db | ||
| 
						 | 
					2784d65618 | ||
| 
						 | 
					47ea69233b | ||
| 
						 | 
					a884e37de1 | ||
| 
						 | 
					0102a8665a | ||
| 
						 | 
					d34ae0850c | ||
| 
						 | 
					6526de024a | ||
| 
						 | 
					5e1e92626c | ||
| 
						 | 
					7fee85c80a | ||
| 
						 | 
					5d87a274a5 | ||
| 
						 | 
					57e862712a | ||
| 
						 | 
					ddb5390d88 | ||
| 
						 | 
					fce20e514e | ||
| 
						 | 
					af66ed2505 | ||
| 
						 | 
					ea4068757d | ||
| 
						 | 
					2b10ecfa37 | ||
| 
						 | 
					f7eb2b697e | ||
| 
						 | 
					da67260c95 | ||
| 
						 | 
					ab9c9068d4 | ||
| 
						 | 
					6b4d7d7315 | ||
| 
						 | 
					313d1d5d83 | ||
| 
						 | 
					b7d6133593 | ||
| 
						 | 
					5b6bb114ad | ||
| 
						 | 
					56f082c9d4 | ||
| 
						 | 
					a1b798e555 | ||
| 
						 | 
					c91e08fbfd | ||
| 
						 | 
					f882434547 | ||
| 
						 | 
					05ad335ae9 | ||
| 
						 | 
					7faeb2ff2b | ||
| 
						 | 
					fdd76b3461 | ||
| 
						 | 
					e490e5d0ab | ||
| 
						 | 
					3a05c421e0 | ||
| 
						 | 
					afc70c120e | ||
| 
						 | 
					d7f80eab68 | ||
| 
						 | 
					e30b356eb0 | ||
| 
						 | 
					09a0803116 | ||
| 
						 | 
					20ffedc745 | ||
| 
						 | 
					0b1ad27ba8 | ||
| 
						 | 
					6a1b2f0387 | 
							
								
								
									
										14
									
								
								Makefile.am
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								Makefile.am
									
									
									
									
									
								
							@@ -181,6 +181,7 @@ src_mpd_SOURCES = \
 | 
			
		||||
	src/PlaylistInfo.hxx \
 | 
			
		||||
	src/PlaylistDatabase.cxx src/PlaylistDatabase.hxx \
 | 
			
		||||
	src/PlaylistUpdate.cxx \
 | 
			
		||||
	src/BulkEdit.hxx \
 | 
			
		||||
	src/IdTable.hxx \
 | 
			
		||||
	src/Queue.cxx src/Queue.hxx \
 | 
			
		||||
	src/QueuePrint.cxx src/QueuePrint.hxx \
 | 
			
		||||
@@ -1061,6 +1062,7 @@ C_TESTS = \
 | 
			
		||||
	test/test_util \
 | 
			
		||||
	test/test_byte_reverse \
 | 
			
		||||
	test/test_mixramp \
 | 
			
		||||
	test/test_icy_parser \
 | 
			
		||||
	test/test_pcm \
 | 
			
		||||
	test/test_queue_priority
 | 
			
		||||
 | 
			
		||||
@@ -1217,6 +1219,7 @@ test_dump_playlist_LDADD = \
 | 
			
		||||
	libpcm.a \
 | 
			
		||||
	$(GLIB_LIBS)
 | 
			
		||||
test_dump_playlist_SOURCES = test/dump_playlist.cxx \
 | 
			
		||||
	test/FakeDecoderAPI.cxx \
 | 
			
		||||
	$(DECODER_SRC) \
 | 
			
		||||
	src/Log.cxx \
 | 
			
		||||
	src/IOThread.cxx \
 | 
			
		||||
@@ -1270,6 +1273,7 @@ test_read_tags_LDADD = \
 | 
			
		||||
	libutil.a \
 | 
			
		||||
	$(GLIB_LIBS)
 | 
			
		||||
test_read_tags_SOURCES = test/read_tags.cxx \
 | 
			
		||||
	test/FakeDecoderAPI.cxx \
 | 
			
		||||
	src/Log.cxx \
 | 
			
		||||
	src/IOThread.cxx \
 | 
			
		||||
	src/ReplayGainInfo.cxx \
 | 
			
		||||
@@ -1496,6 +1500,16 @@ test_test_mixramp_LDADD = \
 | 
			
		||||
	$(GLIB_LIBS) \
 | 
			
		||||
	$(CPPUNIT_LIBS)
 | 
			
		||||
 | 
			
		||||
test_test_icy_parser_SOURCES = \
 | 
			
		||||
	src/Log.cxx \
 | 
			
		||||
	test/test_icy_parser.cxx
 | 
			
		||||
test_test_icy_parser_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
 | 
			
		||||
test_test_icy_parser_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
 | 
			
		||||
test_test_icy_parser_LDADD = \
 | 
			
		||||
	libtag.a \
 | 
			
		||||
	$(GLIB_LIBS) \
 | 
			
		||||
	$(CPPUNIT_LIBS)
 | 
			
		||||
 | 
			
		||||
test_test_pcm_SOURCES = \
 | 
			
		||||
	test/test_pcm_util.hxx \
 | 
			
		||||
	test/test_pcm_dither.cxx \
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								NEWS
									
									
									
									
									
								
							@@ -1,3 +1,63 @@
 | 
			
		||||
ver 0.18.12 (2014/07/30)
 | 
			
		||||
* database
 | 
			
		||||
  - proxy: fix build failure with libmpdclient 2.2
 | 
			
		||||
  - proxy: fix add/search and other commands with libmpdclient < 2.9
 | 
			
		||||
* decoder
 | 
			
		||||
  - audiofile: improve responsiveness
 | 
			
		||||
  - audiofile: fix WAV stream playback
 | 
			
		||||
  - dsdiff, dsf: fix stream playback
 | 
			
		||||
  - dsdiff: fix metadata parser bug (uninitialized variables)
 | 
			
		||||
  - faad: estimate song duration for remote files
 | 
			
		||||
  - sndfile: improve responsiveness
 | 
			
		||||
* randomize next song when enabling "random" mode while not playing
 | 
			
		||||
* randomize next song when adding to single-song queue
 | 
			
		||||
 | 
			
		||||
ver 0.18.11 (2014/05/12)
 | 
			
		||||
* decoder
 | 
			
		||||
  - opus: fix missing song length on high-latency files
 | 
			
		||||
* fix race condition when using GLib event loop (non-Linux)
 | 
			
		||||
 | 
			
		||||
ver 0.18.10 (2014/04/10)
 | 
			
		||||
* decoder
 | 
			
		||||
  - ffmpeg: fix seeking bug
 | 
			
		||||
  - ffmpeg: handle unknown stream start time
 | 
			
		||||
  - gme: fix memory leak
 | 
			
		||||
  - sndfile: work around libsndfile bug on partial read
 | 
			
		||||
* don't interrupt playback when current song gets deleted
 | 
			
		||||
 | 
			
		||||
ver 0.18.9 (2014/03/02)
 | 
			
		||||
* protocol
 | 
			
		||||
  - "findadd" requires the "add" permission
 | 
			
		||||
* output
 | 
			
		||||
  - alsa: improved workaround for noise after manual song change
 | 
			
		||||
* decoder
 | 
			
		||||
  - vorbis: fix linker failure when libvorbis/libogg are static
 | 
			
		||||
* encoder
 | 
			
		||||
  - vorbis: fix another linker failure
 | 
			
		||||
* output
 | 
			
		||||
  - pipe: fix hanging child process due to blocked signals
 | 
			
		||||
* fix build failure due to missing signal.h include
 | 
			
		||||
 | 
			
		||||
ver 0.18.8 (2014/02/07)
 | 
			
		||||
* decoder
 | 
			
		||||
  - ffmpeg: support libav v10_alpha1
 | 
			
		||||
* encoder
 | 
			
		||||
  - vorbis: fix linker failure
 | 
			
		||||
* output
 | 
			
		||||
  - roar: documentation
 | 
			
		||||
* more robust Icy-Metadata parser
 | 
			
		||||
* fix Solaris build failure
 | 
			
		||||
 | 
			
		||||
ver 0.18.7 (2014/01/13)
 | 
			
		||||
* playlist
 | 
			
		||||
  - pls: fix crash after parser error
 | 
			
		||||
  - soundcloud: fix build failure with libyajl 2.0.1
 | 
			
		||||
* decoder
 | 
			
		||||
  - faad: fix memory leak
 | 
			
		||||
  - mpcdec: reject libmpcdec SV7 in configure script
 | 
			
		||||
* daemon: don't initialize supplementary groups when already running
 | 
			
		||||
  as the configured user
 | 
			
		||||
 | 
			
		||||
ver 0.18.6 (2013/12/24)
 | 
			
		||||
* input
 | 
			
		||||
  - cdio_paranoia: support libcdio-paranoia 0.90
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								configure.ac
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								configure.ac
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
AC_PREREQ(2.60)
 | 
			
		||||
 | 
			
		||||
AC_INIT(mpd, 0.18.6, musicpd-dev-team@lists.sourceforge.net)
 | 
			
		||||
AC_INIT(mpd, 0.18.12, mpd-devel@musicpd.org)
 | 
			
		||||
 | 
			
		||||
VERSION_MAJOR=0
 | 
			
		||||
VERSION_MINOR=18
 | 
			
		||||
@@ -732,7 +732,7 @@ dnl --------------------------------- Soundcloud ------------------------------
 | 
			
		||||
if test x$enable_soundcloud != xno; then
 | 
			
		||||
	PKG_CHECK_MODULES([YAJL], [yajl >= 2.0],
 | 
			
		||||
		[found_soundcloud=yes],
 | 
			
		||||
		AC_CHECK_LIB([yajl], [yajl_alloc],
 | 
			
		||||
		AC_CHECK_LIB([yajl], [yajl_parse_complete],
 | 
			
		||||
			[found_soundcloud=yes YAJL_CFLAGS=-DHAVE_YAJL1 YAJL_LIBS=-lyajl],
 | 
			
		||||
			[found_soundcloud=no]))
 | 
			
		||||
fi
 | 
			
		||||
@@ -960,7 +960,7 @@ AM_CONDITIONAL(ENABLE_SNDFILE, test x$enable_sndfile = xyes)
 | 
			
		||||
 | 
			
		||||
dnl --------------------------------- musepack --------------------------------
 | 
			
		||||
 | 
			
		||||
MPD_AUTO_LIB(mpc, MPCDEC, mpcdec, main, [-lmpcdec], [],
 | 
			
		||||
MPD_AUTO_LIB(mpc, MPCDEC, mpcdec, mpc_demux_init, [-lmpcdec], [],
 | 
			
		||||
	[mpcdec], [libmpcdec not found])
 | 
			
		||||
if test x$enable_mpc = xyes; then
 | 
			
		||||
	AC_DEFINE(HAVE_MPCDEC, 1, [Define to use libmpcdec for MPC decoding])
 | 
			
		||||
@@ -1023,7 +1023,7 @@ if test x$enable_tremor = xyes; then
 | 
			
		||||
	fi
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
MPD_AUTO_PKG(vorbis, VORBIS, [vorbis vorbisfile ogg],
 | 
			
		||||
MPD_AUTO_PKG(vorbis, VORBIS, [vorbisfile vorbis ogg],
 | 
			
		||||
	[Ogg Vorbis decoder], [libvorbis not found])
 | 
			
		||||
if test x$enable_vorbis = xyes; then
 | 
			
		||||
	AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support])
 | 
			
		||||
@@ -1139,7 +1139,7 @@ fi
 | 
			
		||||
AM_CONDITIONAL(ENABLE_FLAC_ENCODER, test x$enable_flac_encoder = xyes)
 | 
			
		||||
 | 
			
		||||
dnl ---------------------------- Ogg Vorbis Encoder ---------------------------
 | 
			
		||||
MPD_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc],
 | 
			
		||||
MPD_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc vorbis ogg],
 | 
			
		||||
	[Ogg Vorbis encoder], [libvorbisenc not found])
 | 
			
		||||
 | 
			
		||||
if test x$enable_vorbis_encoder = xyes; then
 | 
			
		||||
 
 | 
			
		||||
@@ -155,7 +155,7 @@ foo(const char *abc, int xyz)
 | 
			
		||||
 | 
			
		||||
    <para>
 | 
			
		||||
      Send your patches to the mailing list:
 | 
			
		||||
      musicpd-dev-team@lists.sourceforge.net
 | 
			
		||||
      mpd-devel@musicpd.org
 | 
			
		||||
    </para>
 | 
			
		||||
  </chapter>
 | 
			
		||||
</book>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								doc/user.xml
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								doc/user.xml
									
									
									
									
									
								
							@@ -829,7 +829,7 @@ systemctl start mpd.socket</programlisting>
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <row>
 | 
			
		||||
                <entry>
 | 
			
		||||
                  <varname>default_bute_order</varname>
 | 
			
		||||
                  <varname>default_byte_order</varname>
 | 
			
		||||
                <parameter>little_endian|big_endian</parameter>
 | 
			
		||||
                </entry>
 | 
			
		||||
                <entry>
 | 
			
		||||
@@ -1852,6 +1852,51 @@ systemctl start mpd.socket</programlisting>
 | 
			
		||||
        </informaltable>
 | 
			
		||||
      </section>
 | 
			
		||||
 | 
			
		||||
      <section>
 | 
			
		||||
        <title><varname>roar</varname></title>
 | 
			
		||||
 | 
			
		||||
        <para>
 | 
			
		||||
          The <varname>roar</varname> plugin connects to a <ulink
 | 
			
		||||
          url="http://roaraudio.keep-cool.org/">RoarAudio</ulink>
 | 
			
		||||
          server.
 | 
			
		||||
        </para>
 | 
			
		||||
 | 
			
		||||
        <informaltable>
 | 
			
		||||
          <tgroup cols="2">
 | 
			
		||||
            <thead>
 | 
			
		||||
              <row>
 | 
			
		||||
                <entry>Setting</entry>
 | 
			
		||||
                <entry>Description</entry>
 | 
			
		||||
              </row>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <row>
 | 
			
		||||
                <entry>
 | 
			
		||||
                  <varname>server</varname>
 | 
			
		||||
                  <parameter>HOSTNAME</parameter>
 | 
			
		||||
                </entry>
 | 
			
		||||
                <entry>
 | 
			
		||||
                  The host name of the RoarAudio server.  If not
 | 
			
		||||
                  specified, then MPD will connect to the default
 | 
			
		||||
                  locations.
 | 
			
		||||
                </entry>
 | 
			
		||||
              </row>
 | 
			
		||||
 | 
			
		||||
              <row>
 | 
			
		||||
                <entry>
 | 
			
		||||
                  <varname>role</varname>
 | 
			
		||||
                  <parameter>ROLE</parameter>
 | 
			
		||||
                </entry>
 | 
			
		||||
                <entry>
 | 
			
		||||
                  The "role" that MPD registers itself as in the
 | 
			
		||||
                  RoarAudio server.  The default is "music".
 | 
			
		||||
                </entry>
 | 
			
		||||
              </row>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </tgroup>
 | 
			
		||||
        </informaltable>
 | 
			
		||||
      </section>
 | 
			
		||||
 | 
			
		||||
      <section>
 | 
			
		||||
        <title><varname>recorder</varname></title>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								src/BulkEdit.hxx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/BulkEdit.hxx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2014 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef MPD_BULK_EDIT_HXX
 | 
			
		||||
#define MPD_BULK_EDIT_HXX
 | 
			
		||||
 | 
			
		||||
#include "Partition.hxx"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Begin a "bulk edit" and commit it automatically.
 | 
			
		||||
 */
 | 
			
		||||
class ScopeBulkEdit {
 | 
			
		||||
	Partition &partition;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	ScopeBulkEdit(Partition &_partition):partition(_partition) {
 | 
			
		||||
		partition.playlist.BeginBulk();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	~ScopeBulkEdit() {
 | 
			
		||||
		partition.playlist.CommitBulk(partition.pc);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -52,7 +52,7 @@ static char *user_name;
 | 
			
		||||
static uid_t user_uid = (uid_t)-1;
 | 
			
		||||
 | 
			
		||||
/** the Unix group id which MPD runs as */
 | 
			
		||||
static gid_t user_gid = (pid_t)-1;
 | 
			
		||||
static gid_t user_gid = (gid_t)-1;
 | 
			
		||||
 | 
			
		||||
/** the absolute path of the pidfile */
 | 
			
		||||
static AllocatedPath pidfile = AllocatedPath::Null();
 | 
			
		||||
@@ -106,18 +106,21 @@ daemonize_set_user(void)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	/* set gid */
 | 
			
		||||
	if (user_gid != (gid_t)-1 && user_gid != getgid()) {
 | 
			
		||||
		if (setgid(user_gid) == -1) {
 | 
			
		||||
			FormatFatalSystemError("Failed to set group %d",
 | 
			
		||||
					       (int)user_gid);
 | 
			
		||||
		}
 | 
			
		||||
	if (user_gid != (gid_t)-1 && user_gid != getgid() &&
 | 
			
		||||
	    setgid(user_gid) == -1) {
 | 
			
		||||
		FormatFatalSystemError("Failed to set group %d",
 | 
			
		||||
				       (int)user_gid);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#ifdef _BSD_SOURCE
 | 
			
		||||
	/* init suplementary groups
 | 
			
		||||
	/* init supplementary groups
 | 
			
		||||
	 * (must be done before we change our uid)
 | 
			
		||||
	 */
 | 
			
		||||
	if (!had_group && initgroups(user_name, user_gid) == -1) {
 | 
			
		||||
	if (!had_group &&
 | 
			
		||||
	    /* no need to set the new user's supplementary groups if
 | 
			
		||||
	       we are already this user */
 | 
			
		||||
	    user_uid != getuid() &&
 | 
			
		||||
	    initgroups(user_name, user_gid) == -1) {
 | 
			
		||||
		FormatFatalSystemError("Failed to set supplementary groups "
 | 
			
		||||
				       "of user \"%s\"",
 | 
			
		||||
				       user_name);
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,18 @@ DatabaseSelection::DatabaseSelection(const char *_uri, bool _recursive,
 | 
			
		||||
		uri = filter->GetBase();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
DatabaseSelection::IsEmpty() const
 | 
			
		||||
{
 | 
			
		||||
	return uri.empty() && (filter == nullptr || filter->IsEmpty());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
DatabaseSelection::HasOtherThanBase() const
 | 
			
		||||
{
 | 
			
		||||
	return filter != nullptr && filter->HasOtherThanBase();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
DatabaseSelection::Match(const Song &song) const
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,15 @@ struct DatabaseSelection {
 | 
			
		||||
	DatabaseSelection(const char *_uri, bool _recursive,
 | 
			
		||||
			  const SongFilter *_filter=nullptr);
 | 
			
		||||
 | 
			
		||||
	gcc_pure
 | 
			
		||||
	bool IsEmpty() const;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Does this selection contain constraints other than "base"?
 | 
			
		||||
	 */
 | 
			
		||||
	gcc_pure
 | 
			
		||||
	bool HasOtherThanBase() const;
 | 
			
		||||
 | 
			
		||||
	gcc_pure
 | 
			
		||||
	bool Match(const Song &song) const;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -292,6 +292,40 @@ decoder_read(Decoder *decoder,
 | 
			
		||||
	return nbytes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
decoder_read_full(Decoder *decoder, InputStream &is,
 | 
			
		||||
		  void *_buffer, size_t size)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t *buffer = (uint8_t *)_buffer;
 | 
			
		||||
 | 
			
		||||
	while (size > 0) {
 | 
			
		||||
		size_t nbytes = decoder_read(decoder, is, buffer, size);
 | 
			
		||||
		if (nbytes == 0)
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
		buffer += nbytes;
 | 
			
		||||
		size -= nbytes;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
decoder_skip(Decoder *decoder, InputStream &is, size_t size)
 | 
			
		||||
{
 | 
			
		||||
	while (size > 0) {
 | 
			
		||||
		char buffer[1024];
 | 
			
		||||
		size_t nbytes = decoder_read(decoder, is, buffer,
 | 
			
		||||
					     std::min(sizeof(buffer), size));
 | 
			
		||||
		if (nbytes == 0)
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
		size -= nbytes;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_timestamp(Decoder &decoder, double t)
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -112,6 +112,25 @@ decoder_read(Decoder &decoder, InputStream &is,
 | 
			
		||||
	return decoder_read(&decoder, is, buffer, length);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Blocking read from the input stream.  Attempts to fill the buffer
 | 
			
		||||
 * completely; there is no partial result.
 | 
			
		||||
 *
 | 
			
		||||
 * @return true on success, false on error or command or not enough
 | 
			
		||||
 * data
 | 
			
		||||
 */
 | 
			
		||||
bool
 | 
			
		||||
decoder_read_full(Decoder *decoder, InputStream &is,
 | 
			
		||||
		  void *buffer, size_t size);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Skip data on the #InputStream.
 | 
			
		||||
 *
 | 
			
		||||
 * @return true on success, false on error or command
 | 
			
		||||
 */
 | 
			
		||||
bool
 | 
			
		||||
decoder_skip(Decoder *decoder, InputStream &is, size_t size);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sets the time stamp for the next data chunk [seconds].  The MPD
 | 
			
		||||
 * core automatically counts it up, and a decoder plugin only needs to
 | 
			
		||||
 
 | 
			
		||||
@@ -70,6 +70,12 @@ decoder_buffer_free(DecoderBuffer *buffer)
 | 
			
		||||
	g_free(buffer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const InputStream &
 | 
			
		||||
decoder_buffer_get_stream(const DecoderBuffer *buffer)
 | 
			
		||||
{
 | 
			
		||||
	return *buffer->is;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
decoder_buffer_is_empty(const DecoderBuffer *buffer)
 | 
			
		||||
{
 | 
			
		||||
@@ -82,6 +88,12 @@ decoder_buffer_is_full(const DecoderBuffer *buffer)
 | 
			
		||||
	return buffer->consumed == 0 && buffer->length == buffer->size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_buffer_clear(DecoderBuffer *buffer)
 | 
			
		||||
{
 | 
			
		||||
	buffer->length = buffer->consumed = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
decoder_buffer_shift(DecoderBuffer *buffer)
 | 
			
		||||
{
 | 
			
		||||
@@ -118,6 +130,12 @@ decoder_buffer_fill(DecoderBuffer *buffer)
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t
 | 
			
		||||
decoder_buffer_available(const DecoderBuffer *buffer)
 | 
			
		||||
{
 | 
			
		||||
	return buffer->length - buffer->consumed;;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const void *
 | 
			
		||||
decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r)
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,8 @@
 | 
			
		||||
#ifndef MPD_DECODER_BUFFER_HXX
 | 
			
		||||
#define MPD_DECODER_BUFFER_HXX
 | 
			
		||||
 | 
			
		||||
#include "Compiler.h"
 | 
			
		||||
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -50,12 +52,21 @@ decoder_buffer_new(Decoder *decoder, InputStream &is,
 | 
			
		||||
void
 | 
			
		||||
decoder_buffer_free(DecoderBuffer *buffer);
 | 
			
		||||
 | 
			
		||||
gcc_pure
 | 
			
		||||
const InputStream &
 | 
			
		||||
decoder_buffer_get_stream(const DecoderBuffer *buffer);
 | 
			
		||||
 | 
			
		||||
gcc_pure
 | 
			
		||||
bool
 | 
			
		||||
decoder_buffer_is_empty(const DecoderBuffer *buffer);
 | 
			
		||||
 | 
			
		||||
gcc_pure
 | 
			
		||||
bool
 | 
			
		||||
decoder_buffer_is_full(const DecoderBuffer *buffer);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_buffer_clear(DecoderBuffer *buffer);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Read data from the input_stream and append it to the buffer.
 | 
			
		||||
 *
 | 
			
		||||
@@ -66,6 +77,13 @@ decoder_buffer_is_full(const DecoderBuffer *buffer);
 | 
			
		||||
bool
 | 
			
		||||
decoder_buffer_fill(DecoderBuffer *buffer);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * How many bytes are stored in the buffer?
 | 
			
		||||
 */
 | 
			
		||||
gcc_pure
 | 
			
		||||
size_t
 | 
			
		||||
decoder_buffer_available(const DecoderBuffer *buffer);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Reads data from the buffer.  This data is not yet consumed, you
 | 
			
		||||
 * have to call decoder_buffer_consume() to do that.  The returned
 | 
			
		||||
 
 | 
			
		||||
@@ -81,31 +81,85 @@ icy_add_item(Tag &tag, TagType type, const char *value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
icy_parse_tag_item(Tag &tag, const char *item)
 | 
			
		||||
icy_parse_tag_item(Tag &tag, const char *name, const char *value)
 | 
			
		||||
{
 | 
			
		||||
	gchar **p = g_strsplit(item, "=", 0);
 | 
			
		||||
	if (strcmp(name, "StreamTitle") == 0)
 | 
			
		||||
		icy_add_item(tag, TAG_TITLE, value);
 | 
			
		||||
	else
 | 
			
		||||
		FormatDebug(icy_metadata_domain,
 | 
			
		||||
			    "unknown icy-tag: '%s'", name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	if (p[0] != nullptr && p[1] != nullptr) {
 | 
			
		||||
		if (strcmp(p[0], "StreamTitle") == 0)
 | 
			
		||||
			icy_add_item(tag, TAG_TITLE, p[1]);
 | 
			
		||||
		else
 | 
			
		||||
			FormatDebug(icy_metadata_domain,
 | 
			
		||||
				    "unknown icy-tag: '%s'", p[0]);
 | 
			
		||||
/**
 | 
			
		||||
 * Find a single quote that is followed by a semicolon (or by the end
 | 
			
		||||
 * of the string).  If that fails, return the first single quote.  If
 | 
			
		||||
 * that also fails, return #end.
 | 
			
		||||
 */
 | 
			
		||||
static char *
 | 
			
		||||
find_end_quote(char *p, char *const end)
 | 
			
		||||
{
 | 
			
		||||
	char *fallback = std::find(p, end, '\'');
 | 
			
		||||
	if (fallback >= end - 1 || fallback[1] == ';')
 | 
			
		||||
		return fallback;
 | 
			
		||||
 | 
			
		||||
	p = fallback + 1;
 | 
			
		||||
	while (true) {
 | 
			
		||||
		p = std::find(p, end, '\'');
 | 
			
		||||
		if (p == end)
 | 
			
		||||
			return fallback;
 | 
			
		||||
 | 
			
		||||
		if (p == end - 1 || p[1] == ';')
 | 
			
		||||
			return p;
 | 
			
		||||
 | 
			
		||||
		++p;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g_strfreev(p);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static Tag *
 | 
			
		||||
icy_parse_tag(const char *p)
 | 
			
		||||
icy_parse_tag(char *p, char *const end)
 | 
			
		||||
{
 | 
			
		||||
	assert(p != nullptr);
 | 
			
		||||
	assert(end != nullptr);
 | 
			
		||||
	assert(p <= end);
 | 
			
		||||
 | 
			
		||||
	Tag *tag = new Tag();
 | 
			
		||||
	gchar **items = g_strsplit(p, ";", 0);
 | 
			
		||||
 | 
			
		||||
	for (unsigned i = 0; items[i] != nullptr; ++i)
 | 
			
		||||
		icy_parse_tag_item(*tag, items[i]);
 | 
			
		||||
	while (p != end) {
 | 
			
		||||
		const char *const name = p;
 | 
			
		||||
		char *eq = std::find(p, end, '=');
 | 
			
		||||
		if (eq == end)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
	g_strfreev(items);
 | 
			
		||||
		*eq = 0;
 | 
			
		||||
		p = eq + 1;
 | 
			
		||||
 | 
			
		||||
		if (*p != '\'') {
 | 
			
		||||
			/* syntax error; skip to the next semicolon,
 | 
			
		||||
			   try to recover */
 | 
			
		||||
			char *semicolon = std::find(p, end, ';');
 | 
			
		||||
			if (semicolon == end)
 | 
			
		||||
				break;
 | 
			
		||||
			p = semicolon + 1;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		++p;
 | 
			
		||||
 | 
			
		||||
		const char *const value = p;
 | 
			
		||||
		char *quote = find_end_quote(p, end);
 | 
			
		||||
		if (quote == end)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		*quote = 0;
 | 
			
		||||
		p = quote + 1;
 | 
			
		||||
 | 
			
		||||
		icy_parse_tag_item(*tag, name, value);
 | 
			
		||||
 | 
			
		||||
		char *semicolon = std::find(p, end, ';');
 | 
			
		||||
		if (semicolon == end)
 | 
			
		||||
			break;
 | 
			
		||||
		p = semicolon + 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tag;
 | 
			
		||||
}
 | 
			
		||||
@@ -152,15 +206,11 @@ IcyMetaDataParser::Meta(const void *data, size_t length)
 | 
			
		||||
		++length;
 | 
			
		||||
 | 
			
		||||
	if (meta_position == meta_size) {
 | 
			
		||||
		/* null-terminate the string */
 | 
			
		||||
 | 
			
		||||
		meta_data[meta_size] = 0;
 | 
			
		||||
 | 
			
		||||
		/* parse */
 | 
			
		||||
 | 
			
		||||
		delete tag;
 | 
			
		||||
 | 
			
		||||
		tag = icy_parse_tag(meta_data);
 | 
			
		||||
		tag = icy_parse_tag(meta_data, meta_data + meta_size);
 | 
			
		||||
		g_free(meta_data);
 | 
			
		||||
 | 
			
		||||
		/* change back to normal data mode */
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef MPD_OUTPUT_API_HXX
 | 
			
		||||
#define MPD_OUTPUT_API_HxX
 | 
			
		||||
#define MPD_OUTPUT_API_HXX
 | 
			
		||||
 | 
			
		||||
#include "OutputPlugin.hxx"
 | 
			
		||||
#include "OutputInternal.hxx"
 | 
			
		||||
 
 | 
			
		||||
@@ -103,6 +103,12 @@ playlist::UpdateQueuedSong(PlayerControl &pc, const Song *prev)
 | 
			
		||||
	if (!playing)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	if (prev == nullptr && bulk_edit)
 | 
			
		||||
		/* postponed until CommitBulk() to avoid always
 | 
			
		||||
		   queueing the first song that is being added (in
 | 
			
		||||
		   random mode) */
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	assert(!queue.IsEmpty());
 | 
			
		||||
	assert((queued < 0) == (prev == nullptr));
 | 
			
		||||
 | 
			
		||||
@@ -294,7 +300,9 @@ playlist::SetRandom(PlayerControl &pc, bool status)
 | 
			
		||||
	if (queue.random) {
 | 
			
		||||
		/* shuffle the queue order, but preserve current */
 | 
			
		||||
 | 
			
		||||
		const int current_position = GetCurrentPosition();
 | 
			
		||||
		const int current_position = playing
 | 
			
		||||
			? GetCurrentPosition()
 | 
			
		||||
			: -1;
 | 
			
		||||
 | 
			
		||||
		queue.ShuffleOrder();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ struct playlist {
 | 
			
		||||
	/**
 | 
			
		||||
	 * The song queue - it contains the "real" playlist.
 | 
			
		||||
	 */
 | 
			
		||||
	struct queue queue;
 | 
			
		||||
	struct Queue queue;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * This value is true if the player is currently playing (or
 | 
			
		||||
@@ -45,6 +45,18 @@ struct playlist {
 | 
			
		||||
	 */
 | 
			
		||||
	bool stop_on_error;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * If true, then a bulk edit has been initiated by
 | 
			
		||||
	 * BeginBulk(), and UpdateQueuedSong() and OnModified() will
 | 
			
		||||
	 * be postponed until CommitBulk()
 | 
			
		||||
	 */
 | 
			
		||||
	bool bulk_edit;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Has the queue been modified during bulk edit mode?
 | 
			
		||||
	 */
 | 
			
		||||
	bool bulk_modified;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Number of errors since playback was started.  If this
 | 
			
		||||
	 * number exceeds the length of the playlist, MPD gives up,
 | 
			
		||||
@@ -69,7 +81,9 @@ struct playlist {
 | 
			
		||||
	int queued;
 | 
			
		||||
 | 
			
		||||
	playlist(unsigned max_length)
 | 
			
		||||
		:queue(max_length), playing(false), current(-1), queued(-1) {
 | 
			
		||||
		:queue(max_length), playing(false),
 | 
			
		||||
		 bulk_edit(false),
 | 
			
		||||
		 current(-1), queued(-1) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	~playlist() {
 | 
			
		||||
@@ -126,6 +140,9 @@ protected:
 | 
			
		||||
	void UpdateQueuedSong(PlayerControl &pc, const Song *prev);
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	void BeginBulk();
 | 
			
		||||
	void CommitBulk(PlayerControl &pc);
 | 
			
		||||
 | 
			
		||||
	void Clear(PlayerControl &pc);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
 
 | 
			
		||||
@@ -153,7 +153,7 @@ playlist::PlayNext(PlayerControl &pc)
 | 
			
		||||
			queue.ShuffleOrder();
 | 
			
		||||
 | 
			
		||||
			/* note that current and queued are
 | 
			
		||||
			   now invalid, but playlist_play_order() will
 | 
			
		||||
			   now invalid, but PlayOrder() will
 | 
			
		||||
			   discard them anyway */
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,12 @@
 | 
			
		||||
void
 | 
			
		||||
playlist::OnModified()
 | 
			
		||||
{
 | 
			
		||||
	if (bulk_edit) {
 | 
			
		||||
		/* postponed to CommitBulk() */
 | 
			
		||||
		bulk_modified = true;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	queue.IncrementVersion();
 | 
			
		||||
 | 
			
		||||
	idle_add(IDLE_PLAYLIST);
 | 
			
		||||
@@ -56,6 +62,35 @@ playlist::Clear(PlayerControl &pc)
 | 
			
		||||
	OnModified();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
playlist::BeginBulk()
 | 
			
		||||
{
 | 
			
		||||
	assert(!bulk_edit);
 | 
			
		||||
 | 
			
		||||
	bulk_edit = true;
 | 
			
		||||
	bulk_modified = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
playlist::CommitBulk(PlayerControl &pc)
 | 
			
		||||
{
 | 
			
		||||
	assert(bulk_edit);
 | 
			
		||||
 | 
			
		||||
	bulk_edit = false;
 | 
			
		||||
	if (!bulk_modified)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	if (queued < 0)
 | 
			
		||||
		/* if no song was queued, UpdateQueuedSong() is being
 | 
			
		||||
		   ignored in "bulk" edit mode; now that we have
 | 
			
		||||
		   shuffled all new songs, we can pick a random one
 | 
			
		||||
		   (instead of always picking the first one that was
 | 
			
		||||
		   added) */
 | 
			
		||||
		UpdateQueuedSong(pc, nullptr);
 | 
			
		||||
 | 
			
		||||
	OnModified();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PlaylistResult
 | 
			
		||||
playlist::AppendFile(PlayerControl &pc,
 | 
			
		||||
		     const char *path_utf8, unsigned *added_id)
 | 
			
		||||
@@ -234,12 +269,8 @@ playlist::DeleteInternal(PlayerControl &pc,
 | 
			
		||||
	if (playing && current == (int)songOrder) {
 | 
			
		||||
		const bool paused = pc.GetState() == PlayerState::PAUSE;
 | 
			
		||||
 | 
			
		||||
		/* the current song is going to be deleted: stop the player */
 | 
			
		||||
 | 
			
		||||
		pc.Stop();
 | 
			
		||||
		playing = false;
 | 
			
		||||
 | 
			
		||||
		/* see which song is going to be played instead */
 | 
			
		||||
		/* the current song is going to be deleted: see which
 | 
			
		||||
		   song is going to be played instead */
 | 
			
		||||
 | 
			
		||||
		current = queue.GetNextOrder(current);
 | 
			
		||||
		if (current == (int)songOrder)
 | 
			
		||||
@@ -248,10 +279,12 @@ playlist::DeleteInternal(PlayerControl &pc,
 | 
			
		||||
		if (current >= 0 && !paused)
 | 
			
		||||
			/* play the song after the deleted one */
 | 
			
		||||
			PlayOrder(pc, current);
 | 
			
		||||
		else
 | 
			
		||||
			/* no songs left to play, stop playback
 | 
			
		||||
			   completely */
 | 
			
		||||
			Stop(pc);
 | 
			
		||||
		else {
 | 
			
		||||
			/* stop the player */
 | 
			
		||||
 | 
			
		||||
			pc.Stop();
 | 
			
		||||
			playing = false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		*queued_p = nullptr;
 | 
			
		||||
	} else if (current == (int)songOrder)
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
void
 | 
			
		||||
playlist_print_uris(Client &client, const playlist &playlist)
 | 
			
		||||
{
 | 
			
		||||
	const queue &queue = playlist.queue;
 | 
			
		||||
	const Queue &queue = playlist.queue;
 | 
			
		||||
 | 
			
		||||
	queue_print_uris(client, queue, 0, queue.GetLength());
 | 
			
		||||
}
 | 
			
		||||
@@ -49,7 +49,7 @@ bool
 | 
			
		||||
playlist_print_info(Client &client, const playlist &playlist,
 | 
			
		||||
		    unsigned start, unsigned end)
 | 
			
		||||
{
 | 
			
		||||
	const queue &queue = playlist.queue;
 | 
			
		||||
	const Queue &queue = playlist.queue;
 | 
			
		||||
 | 
			
		||||
	if (end > queue.GetLength())
 | 
			
		||||
		/* correct the "end" offset */
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,7 @@ playlist_print_uri(FILE *file, const char *uri)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PlaylistResult
 | 
			
		||||
spl_save_queue(const char *name_utf8, const queue &queue)
 | 
			
		||||
spl_save_queue(const char *name_utf8, const Queue &queue)
 | 
			
		||||
{
 | 
			
		||||
	if (map_spl_path().IsNull())
 | 
			
		||||
		return PlaylistResult::DISABLED;
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
struct Song;
 | 
			
		||||
struct queue;
 | 
			
		||||
struct Queue;
 | 
			
		||||
struct playlist;
 | 
			
		||||
struct PlayerControl;
 | 
			
		||||
class Error;
 | 
			
		||||
@@ -40,7 +40,7 @@ playlist_print_uri(FILE *fp, const char *uri);
 | 
			
		||||
 * Saves a queue object into a stored playlist file.
 | 
			
		||||
 */
 | 
			
		||||
PlaylistResult
 | 
			
		||||
spl_save_queue(const char *name_utf8, const queue &queue);
 | 
			
		||||
spl_save_queue(const char *name_utf8, const Queue &queue);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Saves a playlist object into a stored playlist file.
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
queue::queue(unsigned _max_length)
 | 
			
		||||
Queue::Queue(unsigned _max_length)
 | 
			
		||||
	:max_length(_max_length), length(0),
 | 
			
		||||
	 version(1),
 | 
			
		||||
	 items(new Item[max_length]),
 | 
			
		||||
@@ -36,7 +36,7 @@ queue::queue(unsigned _max_length)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
queue::~queue()
 | 
			
		||||
Queue::~Queue()
 | 
			
		||||
{
 | 
			
		||||
	Clear();
 | 
			
		||||
 | 
			
		||||
@@ -45,7 +45,7 @@ queue::~queue()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
queue::GetNextOrder(unsigned _order) const
 | 
			
		||||
Queue::GetNextOrder(unsigned _order) const
 | 
			
		||||
{
 | 
			
		||||
	assert(_order < length);
 | 
			
		||||
 | 
			
		||||
@@ -62,7 +62,7 @@ queue::GetNextOrder(unsigned _order) const
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue::IncrementVersion()
 | 
			
		||||
Queue::IncrementVersion()
 | 
			
		||||
{
 | 
			
		||||
	static unsigned long max = ((uint32_t) 1 << 31) - 1;
 | 
			
		||||
 | 
			
		||||
@@ -77,7 +77,7 @@ queue::IncrementVersion()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue::ModifyAtOrder(unsigned _order)
 | 
			
		||||
Queue::ModifyAtOrder(unsigned _order)
 | 
			
		||||
{
 | 
			
		||||
	assert(_order < length);
 | 
			
		||||
 | 
			
		||||
@@ -86,7 +86,7 @@ queue::ModifyAtOrder(unsigned _order)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
unsigned
 | 
			
		||||
queue::Append(Song *song, uint8_t priority)
 | 
			
		||||
Queue::Append(Song *song, uint8_t priority)
 | 
			
		||||
{
 | 
			
		||||
	assert(!IsFull());
 | 
			
		||||
 | 
			
		||||
@@ -105,7 +105,7 @@ queue::Append(Song *song, uint8_t priority)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue::SwapPositions(unsigned position1, unsigned position2)
 | 
			
		||||
Queue::SwapPositions(unsigned position1, unsigned position2)
 | 
			
		||||
{
 | 
			
		||||
	unsigned id1 = items[position1].id;
 | 
			
		||||
	unsigned id2 = items[position2].id;
 | 
			
		||||
@@ -120,7 +120,7 @@ queue::SwapPositions(unsigned position1, unsigned position2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue::MovePostion(unsigned from, unsigned to)
 | 
			
		||||
Queue::MovePostion(unsigned from, unsigned to)
 | 
			
		||||
{
 | 
			
		||||
	const Item tmp = items[from];
 | 
			
		||||
 | 
			
		||||
@@ -156,7 +156,7 @@ queue::MovePostion(unsigned from, unsigned to)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue::MoveRange(unsigned start, unsigned end, unsigned to)
 | 
			
		||||
Queue::MoveRange(unsigned start, unsigned end, unsigned to)
 | 
			
		||||
{
 | 
			
		||||
	Item tmp[end - start];
 | 
			
		||||
	// Copy the original block [start,end-1]
 | 
			
		||||
@@ -198,7 +198,7 @@ queue::MoveRange(unsigned start, unsigned end, unsigned to)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue::MoveOrder(unsigned from_order, unsigned to_order)
 | 
			
		||||
Queue::MoveOrder(unsigned from_order, unsigned to_order)
 | 
			
		||||
{
 | 
			
		||||
	assert(from_order < length);
 | 
			
		||||
	assert(to_order <= length);
 | 
			
		||||
@@ -217,7 +217,7 @@ queue::MoveOrder(unsigned from_order, unsigned to_order)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue::DeletePosition(unsigned position)
 | 
			
		||||
Queue::DeletePosition(unsigned position)
 | 
			
		||||
{
 | 
			
		||||
	assert(position < length);
 | 
			
		||||
 | 
			
		||||
@@ -254,7 +254,7 @@ queue::DeletePosition(unsigned position)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue::Clear()
 | 
			
		||||
Queue::Clear()
 | 
			
		||||
{
 | 
			
		||||
	for (unsigned i = 0; i < length; i++) {
 | 
			
		||||
		Item *item = &items[i];
 | 
			
		||||
@@ -270,7 +270,7 @@ queue::Clear()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end)
 | 
			
		||||
queue_sort_order_by_priority(Queue *queue, unsigned start, unsigned end)
 | 
			
		||||
{
 | 
			
		||||
	assert(queue != nullptr);
 | 
			
		||||
	assert(queue->random);
 | 
			
		||||
@@ -278,8 +278,8 @@ queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end)
 | 
			
		||||
	assert(end <= queue->length);
 | 
			
		||||
 | 
			
		||||
	auto cmp = [queue](unsigned a_pos, unsigned b_pos){
 | 
			
		||||
		const queue::Item &a = queue->items[a_pos];
 | 
			
		||||
		const queue::Item &b = queue->items[b_pos];
 | 
			
		||||
		const Queue::Item &a = queue->items[a_pos];
 | 
			
		||||
		const Queue::Item &b = queue->items[b_pos];
 | 
			
		||||
 | 
			
		||||
		return a.priority > b.priority;
 | 
			
		||||
	};
 | 
			
		||||
@@ -288,7 +288,7 @@ queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue::ShuffleOrderRange(unsigned start, unsigned end)
 | 
			
		||||
Queue::ShuffleOrderRange(unsigned start, unsigned end)
 | 
			
		||||
{
 | 
			
		||||
	assert(random);
 | 
			
		||||
	assert(start <= end);
 | 
			
		||||
@@ -303,7 +303,7 @@ queue::ShuffleOrderRange(unsigned start, unsigned end)
 | 
			
		||||
 * priority group.
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end)
 | 
			
		||||
Queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end)
 | 
			
		||||
{
 | 
			
		||||
	assert(random);
 | 
			
		||||
	assert(start <= end);
 | 
			
		||||
@@ -337,13 +337,13 @@ queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue::ShuffleOrder()
 | 
			
		||||
Queue::ShuffleOrder()
 | 
			
		||||
{
 | 
			
		||||
	ShuffleOrderRangeWithPriority(0, length);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue::ShuffleOrderFirst(unsigned start, unsigned end)
 | 
			
		||||
Queue::ShuffleOrderFirst(unsigned start, unsigned end)
 | 
			
		||||
{
 | 
			
		||||
	rand.AutoCreate();
 | 
			
		||||
 | 
			
		||||
@@ -352,7 +352,7 @@ queue::ShuffleOrderFirst(unsigned start, unsigned end)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue::ShuffleOrderLast(unsigned start, unsigned end)
 | 
			
		||||
Queue::ShuffleOrderLast(unsigned start, unsigned end)
 | 
			
		||||
{
 | 
			
		||||
	rand.AutoCreate();
 | 
			
		||||
 | 
			
		||||
@@ -361,7 +361,7 @@ queue::ShuffleOrderLast(unsigned start, unsigned end)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue::ShuffleRange(unsigned start, unsigned end)
 | 
			
		||||
Queue::ShuffleRange(unsigned start, unsigned end)
 | 
			
		||||
{
 | 
			
		||||
	assert(start <= end);
 | 
			
		||||
	assert(end <= length);
 | 
			
		||||
@@ -377,7 +377,7 @@ queue::ShuffleRange(unsigned start, unsigned end)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
unsigned
 | 
			
		||||
queue::FindPriorityOrder(unsigned start_order, uint8_t priority,
 | 
			
		||||
Queue::FindPriorityOrder(unsigned start_order, uint8_t priority,
 | 
			
		||||
			 unsigned exclude_order) const
 | 
			
		||||
{
 | 
			
		||||
	assert(random);
 | 
			
		||||
@@ -394,7 +394,7 @@ queue::FindPriorityOrder(unsigned start_order, uint8_t priority,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
unsigned
 | 
			
		||||
queue::CountSamePriority(unsigned start_order, uint8_t priority) const
 | 
			
		||||
Queue::CountSamePriority(unsigned start_order, uint8_t priority) const
 | 
			
		||||
{
 | 
			
		||||
	assert(random);
 | 
			
		||||
	assert(start_order <= length);
 | 
			
		||||
@@ -410,7 +410,7 @@ queue::CountSamePriority(unsigned start_order, uint8_t priority) const
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
queue::SetPriority(unsigned position, uint8_t priority, int after_order)
 | 
			
		||||
Queue::SetPriority(unsigned position, uint8_t priority, int after_order)
 | 
			
		||||
{
 | 
			
		||||
	assert(position < length);
 | 
			
		||||
 | 
			
		||||
@@ -468,7 +468,7 @@ queue::SetPriority(unsigned position, uint8_t priority, int after_order)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
queue::SetPriorityRange(unsigned start_position, unsigned end_position,
 | 
			
		||||
Queue::SetPriorityRange(unsigned start_position, unsigned end_position,
 | 
			
		||||
			uint8_t priority, int after_order)
 | 
			
		||||
{
 | 
			
		||||
	assert(start_position <= end_position);
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ struct Song;
 | 
			
		||||
 * - the unique id (which stays the same, regardless of moves)
 | 
			
		||||
 * - the order number (which only differs from "position" in random mode)
 | 
			
		||||
 */
 | 
			
		||||
struct queue {
 | 
			
		||||
struct Queue {
 | 
			
		||||
	/**
 | 
			
		||||
	 * reserve max_length * HASH_MULT elements in the id
 | 
			
		||||
	 * number space
 | 
			
		||||
@@ -103,16 +103,16 @@ struct queue {
 | 
			
		||||
	/** random number generator for shuffle and random mode */
 | 
			
		||||
	LazyRandomEngine rand;
 | 
			
		||||
 | 
			
		||||
	queue(unsigned max_length);
 | 
			
		||||
	explicit Queue(unsigned max_length);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Deinitializes a queue object.  It does not free the queue
 | 
			
		||||
	 * pointer itself.
 | 
			
		||||
	 */
 | 
			
		||||
	~queue();
 | 
			
		||||
	~Queue();
 | 
			
		||||
 | 
			
		||||
	queue(const queue &other) = delete;
 | 
			
		||||
	queue &operator=(const queue &other) = delete;
 | 
			
		||||
	Queue(const Queue &) = delete;
 | 
			
		||||
	Queue &operator=(const Queue &) = delete;
 | 
			
		||||
 | 
			
		||||
	unsigned GetLength() const {
 | 
			
		||||
		assert(length <= max_length);
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ extern "C" {
 | 
			
		||||
 * @param end the index of the last song (excluding)
 | 
			
		||||
 */
 | 
			
		||||
static void
 | 
			
		||||
queue_print_song_info(Client &client, const queue &queue,
 | 
			
		||||
queue_print_song_info(Client &client, const Queue &queue,
 | 
			
		||||
		      unsigned position)
 | 
			
		||||
{
 | 
			
		||||
	song_print_info(client, queue.Get(position));
 | 
			
		||||
@@ -51,7 +51,7 @@ queue_print_song_info(Client &client, const queue &queue,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue_print_info(Client &client, const queue &queue,
 | 
			
		||||
queue_print_info(Client &client, const Queue &queue,
 | 
			
		||||
		 unsigned start, unsigned end)
 | 
			
		||||
{
 | 
			
		||||
	assert(start <= end);
 | 
			
		||||
@@ -62,7 +62,7 @@ queue_print_info(Client &client, const queue &queue,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue_print_uris(Client &client, const queue &queue,
 | 
			
		||||
queue_print_uris(Client &client, const Queue &queue,
 | 
			
		||||
		 unsigned start, unsigned end)
 | 
			
		||||
{
 | 
			
		||||
	assert(start <= end);
 | 
			
		||||
@@ -75,7 +75,7 @@ queue_print_uris(Client &client, const queue &queue,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue_print_changes_info(Client &client, const queue &queue,
 | 
			
		||||
queue_print_changes_info(Client &client, const Queue &queue,
 | 
			
		||||
			 uint32_t version)
 | 
			
		||||
{
 | 
			
		||||
	for (unsigned i = 0; i < queue.GetLength(); i++) {
 | 
			
		||||
@@ -85,7 +85,7 @@ queue_print_changes_info(Client &client, const queue &queue,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue_print_changes_position(Client &client, const queue &queue,
 | 
			
		||||
queue_print_changes_position(Client &client, const Queue &queue,
 | 
			
		||||
			     uint32_t version)
 | 
			
		||||
{
 | 
			
		||||
	for (unsigned i = 0; i < queue.GetLength(); i++)
 | 
			
		||||
@@ -95,7 +95,7 @@ queue_print_changes_position(Client &client, const queue &queue,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue_find(Client &client, const queue &queue,
 | 
			
		||||
queue_find(Client &client, const Queue &queue,
 | 
			
		||||
	   const SongFilter &filter)
 | 
			
		||||
{
 | 
			
		||||
	for (unsigned i = 0; i < queue.GetLength(); i++) {
 | 
			
		||||
 
 | 
			
		||||
@@ -27,28 +27,28 @@
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
struct queue;
 | 
			
		||||
struct Queue;
 | 
			
		||||
class SongFilter;
 | 
			
		||||
class Client;
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue_print_info(Client &client, const queue &queue,
 | 
			
		||||
queue_print_info(Client &client, const Queue &queue,
 | 
			
		||||
		 unsigned start, unsigned end);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue_print_uris(Client &client, const queue &queue,
 | 
			
		||||
queue_print_uris(Client &client, const Queue &queue,
 | 
			
		||||
		 unsigned start, unsigned end);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue_print_changes_info(Client &client, const queue &queue,
 | 
			
		||||
queue_print_changes_info(Client &client, const Queue &queue,
 | 
			
		||||
			 uint32_t version);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue_print_changes_position(Client &client, const queue &queue,
 | 
			
		||||
queue_print_changes_position(Client &client, const Queue &queue,
 | 
			
		||||
			     uint32_t version);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue_find(Client &client, const queue &queue,
 | 
			
		||||
queue_find(Client &client, const Queue &queue,
 | 
			
		||||
	   const SongFilter &filter);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ queue_save_song(FILE *fp, int idx, const Song &song)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue_save(FILE *fp, const queue &queue)
 | 
			
		||||
queue_save(FILE *fp, const Queue &queue)
 | 
			
		||||
{
 | 
			
		||||
	for (unsigned i = 0; i < queue.GetLength(); i++) {
 | 
			
		||||
		uint8_t prio = queue.GetPriorityAtPosition(i);
 | 
			
		||||
@@ -72,7 +72,7 @@ queue_save(FILE *fp, const queue &queue)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue_load_song(TextFile &file, const char *line, queue &queue)
 | 
			
		||||
queue_load_song(TextFile &file, const char *line, Queue &queue)
 | 
			
		||||
{
 | 
			
		||||
	if (queue.IsFull())
 | 
			
		||||
		return;
 | 
			
		||||
 
 | 
			
		||||
@@ -27,16 +27,16 @@
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
struct queue;
 | 
			
		||||
struct Queue;
 | 
			
		||||
class TextFile;
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
queue_save(FILE *fp, const queue &queue);
 | 
			
		||||
queue_save(FILE *fp, const Queue &queue);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Loads one song from the state file and appends it to the queue.
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
queue_load_song(TextFile &file, const char *line, queue &queue);
 | 
			
		||||
queue_load_song(TextFile &file, const char *line, Queue &queue);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -101,7 +101,7 @@ bool
 | 
			
		||||
SongFilter::Item::Match(const Tag &_tag) const
 | 
			
		||||
{
 | 
			
		||||
	bool visited_types[TAG_NUM_OF_ITEM_TYPES];
 | 
			
		||||
	std::fill_n(visited_types, TAG_NUM_OF_ITEM_TYPES, false);
 | 
			
		||||
	std::fill_n(visited_types, size_t(TAG_NUM_OF_ITEM_TYPES), false);
 | 
			
		||||
 | 
			
		||||
	for (unsigned i = 0; i < _tag.num_items; i++) {
 | 
			
		||||
		visited_types[_tag.items[i]->type] = true;
 | 
			
		||||
@@ -203,6 +203,16 @@ SongFilter::Match(const Song &song) const
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
SongFilter::HasOtherThanBase() const
 | 
			
		||||
{
 | 
			
		||||
	for (const auto &i : items)
 | 
			
		||||
		if (i.GetTag() != LOCATE_TAG_BASE_TYPE)
 | 
			
		||||
			return true;
 | 
			
		||||
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string
 | 
			
		||||
SongFilter::GetBase() const
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -109,6 +109,11 @@ public:
 | 
			
		||||
		return items;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gcc_pure
 | 
			
		||||
	bool IsEmpty() const {
 | 
			
		||||
		return items.empty();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Is there at least one item with "fold case" enabled?
 | 
			
		||||
	 */
 | 
			
		||||
@@ -121,6 +126,12 @@ public:
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Does this filter contain constraints other than "base"?
 | 
			
		||||
	 */
 | 
			
		||||
	gcc_pure
 | 
			
		||||
	bool HasOtherThanBase() const;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Returns the "base" specification (if there is one) or an
 | 
			
		||||
	 * empty string.
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,7 @@ static const struct command commands[] = {
 | 
			
		||||
	{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
 | 
			
		||||
	{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
 | 
			
		||||
	{ "find", PERMISSION_READ, 2, -1, handle_find },
 | 
			
		||||
	{ "findadd", PERMISSION_READ, 2, -1, handle_findadd},
 | 
			
		||||
	{ "findadd", PERMISSION_ADD, 2, -1, handle_findadd},
 | 
			
		||||
	{ "idle", PERMISSION_READ, 0, -1, handle_idle },
 | 
			
		||||
	{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
 | 
			
		||||
	{ "list", PERMISSION_READ, 1, -1, handle_list },
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,7 @@
 | 
			
		||||
#include "util/Error.hxx"
 | 
			
		||||
#include "SongFilter.hxx"
 | 
			
		||||
#include "protocol/Result.hxx"
 | 
			
		||||
#include "BulkEdit.hxx"
 | 
			
		||||
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
@@ -92,6 +93,8 @@ handle_match_add(Client &client, int argc, char *argv[], bool fold_case)
 | 
			
		||||
		return CommandResult::ERROR;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const ScopeBulkEdit bulk_edit(client.partition);
 | 
			
		||||
 | 
			
		||||
	const DatabaseSelection selection("", true, &filter);
 | 
			
		||||
	Error error;
 | 
			
		||||
	return AddFromDatabase(client.partition, selection, error)
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@
 | 
			
		||||
#include "PlaylistFile.hxx"
 | 
			
		||||
#include "PlaylistVector.hxx"
 | 
			
		||||
#include "PlaylistQueue.hxx"
 | 
			
		||||
#include "BulkEdit.hxx"
 | 
			
		||||
#include "TimePrint.hxx"
 | 
			
		||||
#include "Client.hxx"
 | 
			
		||||
#include "protocol/ArgParser.hxx"
 | 
			
		||||
@@ -67,6 +68,8 @@ handle_load(Client &client, int argc, char *argv[])
 | 
			
		||||
	} else if (!check_range(client, &start_index, &end_index, argv[2]))
 | 
			
		||||
		return CommandResult::ERROR;
 | 
			
		||||
 | 
			
		||||
	const ScopeBulkEdit bulk_edit(client.partition);
 | 
			
		||||
 | 
			
		||||
	const PlaylistResult result =
 | 
			
		||||
		playlist_open_into_queue(argv[1],
 | 
			
		||||
					 start_index, end_index,
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@
 | 
			
		||||
#include "ClientFile.hxx"
 | 
			
		||||
#include "Client.hxx"
 | 
			
		||||
#include "Partition.hxx"
 | 
			
		||||
#include "BulkEdit.hxx"
 | 
			
		||||
#include "protocol/ArgParser.hxx"
 | 
			
		||||
#include "protocol/Result.hxx"
 | 
			
		||||
#include "ls.hxx"
 | 
			
		||||
@@ -43,7 +44,6 @@ CommandResult
 | 
			
		||||
handle_add(Client &client, gcc_unused int argc, char *argv[])
 | 
			
		||||
{
 | 
			
		||||
	char *uri = argv[1];
 | 
			
		||||
	PlaylistResult result;
 | 
			
		||||
 | 
			
		||||
	if (memcmp(uri, "file:///", 8) == 0) {
 | 
			
		||||
		const char *path_utf8 = uri + 7;
 | 
			
		||||
@@ -59,7 +59,7 @@ handle_add(Client &client, gcc_unused int argc, char *argv[])
 | 
			
		||||
		if (!client_allow_file(client, path_fs, error))
 | 
			
		||||
			return print_error(client, error);
 | 
			
		||||
 | 
			
		||||
		result = client.partition.AppendFile(path_utf8);
 | 
			
		||||
		auto result = client.partition.AppendFile(path_utf8);
 | 
			
		||||
		return print_playlist_result(client, result);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -70,10 +70,12 @@ handle_add(Client &client, gcc_unused int argc, char *argv[])
 | 
			
		||||
			return CommandResult::ERROR;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		result = client.partition.AppendURI(uri);
 | 
			
		||||
		auto result = client.partition.AppendURI(uri);
 | 
			
		||||
		return print_playlist_result(client, result);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const ScopeBulkEdit bulk_edit(client.partition);
 | 
			
		||||
 | 
			
		||||
	const DatabaseSelection selection(uri, true);
 | 
			
		||||
	Error error;
 | 
			
		||||
	return AddFromDatabase(client.partition, selection, error)
 | 
			
		||||
 
 | 
			
		||||
@@ -398,8 +398,13 @@ Convert(const struct mpd_song *song)
 | 
			
		||||
	Song *s = Song::NewDetached(mpd_song_get_uri(song));
 | 
			
		||||
 | 
			
		||||
	s->mtime = mpd_song_get_last_modified(song);
 | 
			
		||||
 | 
			
		||||
#if LIBMPDCLIENT_CHECK_VERSION(2,3,0)
 | 
			
		||||
	s->start_ms = mpd_song_get_start(song) * 1000;
 | 
			
		||||
	s->end_ms = mpd_song_get_end(song) * 1000;
 | 
			
		||||
#else
 | 
			
		||||
	s->start_ms = s->end_ms = 0;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	TagBuilder tag;
 | 
			
		||||
	tag.SetTime(mpd_song_get_duration(song));
 | 
			
		||||
@@ -561,6 +566,23 @@ SearchSongs(struct mpd_connection *connection,
 | 
			
		||||
	return result && CheckError(connection, error);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Check whether we can use the "base" constraint.  Requires
 | 
			
		||||
 * libmpdclient 2.9 and MPD 0.18.
 | 
			
		||||
 */
 | 
			
		||||
gcc_pure
 | 
			
		||||
static bool
 | 
			
		||||
ServerSupportsSearchBase(const struct mpd_connection *connection)
 | 
			
		||||
{
 | 
			
		||||
#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
 | 
			
		||||
	return mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0;
 | 
			
		||||
#else
 | 
			
		||||
	(void)connection;
 | 
			
		||||
 | 
			
		||||
	return false;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
ProxyDatabase::Visit(const DatabaseSelection &selection,
 | 
			
		||||
		     VisitDirectory visit_directory,
 | 
			
		||||
@@ -572,7 +594,10 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
 | 
			
		||||
	if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
 | 
			
		||||
		return nullptr;
 | 
			
		||||
 | 
			
		||||
	if (!visit_directory && !visit_playlist && selection.recursive)
 | 
			
		||||
	if (!visit_directory && !visit_playlist && selection.recursive &&
 | 
			
		||||
	    (ServerSupportsSearchBase(connection)
 | 
			
		||||
	     ? !selection.IsEmpty()
 | 
			
		||||
	     : selection.HasOtherThanBase()))
 | 
			
		||||
		/* this optimized code path can only be used under
 | 
			
		||||
		   certain conditions */
 | 
			
		||||
		return ::SearchSongs(connection, selection, visit_song, error);
 | 
			
		||||
 
 | 
			
		||||
@@ -31,12 +31,27 @@
 | 
			
		||||
#include <af_vfs.h>
 | 
			
		||||
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
 | 
			
		||||
#define CHUNK_SIZE		1020
 | 
			
		||||
 | 
			
		||||
static constexpr Domain audiofile_domain("audiofile");
 | 
			
		||||
 | 
			
		||||
struct AudioFileInputStream {
 | 
			
		||||
	Decoder *const decoder;
 | 
			
		||||
	InputStream &is;
 | 
			
		||||
 | 
			
		||||
	size_t Read(void *buffer, size_t size) {
 | 
			
		||||
		/* libaudiofile does not like partial reads at all,
 | 
			
		||||
		   and will abort playback; therefore always force full
 | 
			
		||||
		   reads */
 | 
			
		||||
		return decoder_read_full(decoder, is, buffer, size)
 | 
			
		||||
			? size
 | 
			
		||||
			: 0;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int audiofile_get_duration(const char *file)
 | 
			
		||||
{
 | 
			
		||||
	int total_time;
 | 
			
		||||
@@ -54,29 +69,26 @@ static int audiofile_get_duration(const char *file)
 | 
			
		||||
static ssize_t
 | 
			
		||||
audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
 | 
			
		||||
{
 | 
			
		||||
	InputStream &is = *(InputStream *)vfile->closure;
 | 
			
		||||
	AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
 | 
			
		||||
 | 
			
		||||
	Error error;
 | 
			
		||||
	size_t nbytes = is.LockRead(data, length, error);
 | 
			
		||||
	if (nbytes == 0 && error.IsDefined()) {
 | 
			
		||||
		LogError(error);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nbytes;
 | 
			
		||||
	return afis.Read(data, length);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static AFfileoffset
 | 
			
		||||
audiofile_file_length(AFvirtualfile *vfile)
 | 
			
		||||
{
 | 
			
		||||
	InputStream &is = *(InputStream *)vfile->closure;
 | 
			
		||||
	AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
 | 
			
		||||
	InputStream &is = afis.is;
 | 
			
		||||
 | 
			
		||||
	return is.GetSize();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static AFfileoffset
 | 
			
		||||
audiofile_file_tell(AFvirtualfile *vfile)
 | 
			
		||||
{
 | 
			
		||||
	InputStream &is = *(InputStream *)vfile->closure;
 | 
			
		||||
	AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
 | 
			
		||||
	InputStream &is = afis.is;
 | 
			
		||||
 | 
			
		||||
	return is.GetOffset();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -91,11 +103,14 @@ audiofile_file_destroy(AFvirtualfile *vfile)
 | 
			
		||||
static AFfileoffset
 | 
			
		||||
audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
 | 
			
		||||
{
 | 
			
		||||
	InputStream &is = *(InputStream *)vfile->closure;
 | 
			
		||||
	AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
 | 
			
		||||
	InputStream &is = afis.is;
 | 
			
		||||
 | 
			
		||||
	int whence = (is_relative ? SEEK_CUR : SEEK_SET);
 | 
			
		||||
 | 
			
		||||
	Error error;
 | 
			
		||||
	if (is.LockSeek(offset, whence, error)) {
 | 
			
		||||
		LogError(error, "Seek failed");
 | 
			
		||||
		return is.GetOffset();
 | 
			
		||||
	} else {
 | 
			
		||||
		return -1;
 | 
			
		||||
@@ -103,10 +118,10 @@ audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static AFvirtualfile *
 | 
			
		||||
setup_virtual_fops(InputStream &stream)
 | 
			
		||||
setup_virtual_fops(AudioFileInputStream &afis)
 | 
			
		||||
{
 | 
			
		||||
	AFvirtualfile *vf = new AFvirtualfile();
 | 
			
		||||
	vf->closure = &stream;
 | 
			
		||||
	vf->closure = &afis;
 | 
			
		||||
	vf->write = nullptr;
 | 
			
		||||
	vf->read    = audiofile_file_read;
 | 
			
		||||
	vf->length  = audiofile_file_length;
 | 
			
		||||
@@ -173,7 +188,8 @@ audiofile_stream_decode(Decoder &decoder, InputStream &is)
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vf = setup_virtual_fops(is);
 | 
			
		||||
	AudioFileInputStream afis{&decoder, is};
 | 
			
		||||
	vf = setup_virtual_fops(afis);
 | 
			
		||||
 | 
			
		||||
	af_fp = afOpenVirtualFile(vf, "r", nullptr);
 | 
			
		||||
	if (af_fp == AF_NULL_FILEHANDLE) {
 | 
			
		||||
 
 | 
			
		||||
@@ -49,14 +49,6 @@ DsdId::Equals(const char *s) const
 | 
			
		||||
	return memcmp(value, s, sizeof(value)) == 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
dsdlib_read(Decoder *decoder, InputStream &is,
 | 
			
		||||
	    void *data, size_t length)
 | 
			
		||||
{
 | 
			
		||||
	size_t nbytes = decoder_read(decoder, is, data, length);
 | 
			
		||||
	return nbytes == length;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Skip the #input_stream to the specified offset.
 | 
			
		||||
 */
 | 
			
		||||
@@ -149,7 +141,7 @@ dsdlib_tag_id3(InputStream &is,
 | 
			
		||||
	id3_byte_t *dsdid3data;
 | 
			
		||||
	dsdid3data = dsdid3;
 | 
			
		||||
 | 
			
		||||
	if (!dsdlib_read(nullptr, is, dsdid3data, count))
 | 
			
		||||
	if (!decoder_read_full(nullptr, is, dsdid3data, count))
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	id3_tag = id3_tag_parse(dsdid3data, count);
 | 
			
		||||
 
 | 
			
		||||
@@ -58,10 +58,6 @@ public:
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
dsdlib_read(Decoder *decoder, InputStream &is,
 | 
			
		||||
	    void *data, size_t length);
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
dsdlib_skip_to(Decoder *decoder, InputStream &is,
 | 
			
		||||
	       int64_t offset);
 | 
			
		||||
 
 | 
			
		||||
@@ -93,14 +93,14 @@ static bool
 | 
			
		||||
dsdiff_read_id(Decoder *decoder, InputStream &is,
 | 
			
		||||
	       DsdId *id)
 | 
			
		||||
{
 | 
			
		||||
	return dsdlib_read(decoder, is, id, sizeof(*id));
 | 
			
		||||
	return decoder_read_full(decoder, is, id, sizeof(*id));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
dsdiff_read_chunk_header(Decoder *decoder, InputStream &is,
 | 
			
		||||
			 DsdiffChunkHeader *header)
 | 
			
		||||
{
 | 
			
		||||
	return dsdlib_read(decoder, is, header, sizeof(*header));
 | 
			
		||||
	return decoder_read_full(decoder, is, header, sizeof(*header));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
@@ -112,8 +112,7 @@ dsdiff_read_payload(Decoder *decoder, InputStream &is,
 | 
			
		||||
	if (size != (uint64_t)length)
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	size_t nbytes = decoder_read(decoder, is, data, length);
 | 
			
		||||
	return nbytes == length;
 | 
			
		||||
	return decoder_read_full(decoder, is, data, length);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -145,8 +144,8 @@ dsdiff_read_prop_snd(Decoder *decoder, InputStream &is,
 | 
			
		||||
		} else if (header.id.Equals("CHNL")) {
 | 
			
		||||
			uint16_t channels;
 | 
			
		||||
			if (header.GetSize() < sizeof(channels) ||
 | 
			
		||||
			    !dsdlib_read(decoder, is,
 | 
			
		||||
					 &channels, sizeof(channels)) ||
 | 
			
		||||
			    !decoder_read_full(decoder, is,
 | 
			
		||||
					       &channels, sizeof(channels)) ||
 | 
			
		||||
			    !dsdlib_skip_to(decoder, is, chunk_end_offset))
 | 
			
		||||
				return false;
 | 
			
		||||
 | 
			
		||||
@@ -154,8 +153,8 @@ dsdiff_read_prop_snd(Decoder *decoder, InputStream &is,
 | 
			
		||||
		} else if (header.id.Equals("CMPR")) {
 | 
			
		||||
			DsdId type;
 | 
			
		||||
			if (header.GetSize() < sizeof(type) ||
 | 
			
		||||
			    !dsdlib_read(decoder, is,
 | 
			
		||||
					 &type, sizeof(type)) ||
 | 
			
		||||
			    !decoder_read_full(decoder, is,
 | 
			
		||||
					       &type, sizeof(type)) ||
 | 
			
		||||
			    !dsdlib_skip_to(decoder, is, chunk_end_offset))
 | 
			
		||||
				return false;
 | 
			
		||||
 | 
			
		||||
@@ -208,7 +207,7 @@ dsdiff_handle_native_tag(InputStream &is,
 | 
			
		||||
 | 
			
		||||
	struct dsdiff_native_tag metatag;
 | 
			
		||||
 | 
			
		||||
	if (!dsdlib_read(nullptr, is, &metatag, sizeof(metatag)))
 | 
			
		||||
	if (!decoder_read_full(nullptr, is, &metatag, sizeof(metatag)))
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	uint32_t length = FromBE32(metatag.size);
 | 
			
		||||
@@ -221,7 +220,7 @@ dsdiff_handle_native_tag(InputStream &is,
 | 
			
		||||
	char *label;
 | 
			
		||||
	label = string;
 | 
			
		||||
 | 
			
		||||
	if (!dsdlib_read(nullptr, is, label, (size_t)length))
 | 
			
		||||
	if (!decoder_read_full(nullptr, is, label, (size_t)length))
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	string[length] = '\0';
 | 
			
		||||
@@ -251,15 +250,17 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
 | 
			
		||||
	if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	metadata->diar_offset = 0;
 | 
			
		||||
	metadata->diti_offset = 0;
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_ID3TAG
 | 
			
		||||
	metadata->id3_size = 0;
 | 
			
		||||
	metadata->id3_offset = 0;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	/* Now process all the remaining chunk headers in the stream
 | 
			
		||||
	   and record their position and size */
 | 
			
		||||
 | 
			
		||||
	const auto size = is.GetSize();
 | 
			
		||||
	while (is.GetOffset() < size) {
 | 
			
		||||
	do {
 | 
			
		||||
		uint64_t chunk_size = chunk_header->GetSize();
 | 
			
		||||
 | 
			
		||||
		/* DIIN chunk, is directly followed by other chunks  */
 | 
			
		||||
@@ -285,16 +286,11 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
 | 
			
		||||
			metadata->id3_size = chunk_size;
 | 
			
		||||
		}
 | 
			
		||||
#endif
 | 
			
		||||
		if (chunk_size != 0) {
 | 
			
		||||
			if (!dsdlib_skip(decoder, is, chunk_size))
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (is.GetOffset() < size) {
 | 
			
		||||
			if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
 | 
			
		||||
				return false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
		if (!dsdlib_skip(decoder, is, chunk_size))
 | 
			
		||||
			break;
 | 
			
		||||
	} while (dsdiff_read_chunk_header(decoder, is, chunk_header));
 | 
			
		||||
 | 
			
		||||
	/* done processing chunk headers, process tags if any */
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_ID3TAG
 | 
			
		||||
@@ -328,7 +324,7 @@ dsdiff_read_metadata(Decoder *decoder, InputStream &is,
 | 
			
		||||
		     DsdiffChunkHeader *chunk_header)
 | 
			
		||||
{
 | 
			
		||||
	DsdiffHeader header;
 | 
			
		||||
	if (!dsdlib_read(decoder, is, &header, sizeof(header)) ||
 | 
			
		||||
	if (!decoder_read_full(decoder, is, &header, sizeof(header)) ||
 | 
			
		||||
	    !header.id.Equals("FRM8") ||
 | 
			
		||||
	    !header.format.Equals("DSD "))
 | 
			
		||||
		return false;
 | 
			
		||||
@@ -391,10 +387,10 @@ dsdiff_decode_chunk(Decoder &decoder, InputStream &is,
 | 
			
		||||
			now_size = now_frames * frame_size;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		size_t nbytes = decoder_read(decoder, is, buffer, now_size);
 | 
			
		||||
		if (nbytes != now_size)
 | 
			
		||||
		if (!decoder_read_full(&decoder, is, buffer, now_size))
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
		const size_t nbytes = now_size;
 | 
			
		||||
		chunk_size -= nbytes;
 | 
			
		||||
 | 
			
		||||
		if (lsbitfirst)
 | 
			
		||||
 
 | 
			
		||||
@@ -103,7 +103,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
 | 
			
		||||
		  DsfMetaData *metadata)
 | 
			
		||||
{
 | 
			
		||||
	DsfHeader dsf_header;
 | 
			
		||||
	if (!dsdlib_read(decoder, is, &dsf_header, sizeof(dsf_header)) ||
 | 
			
		||||
	if (!decoder_read_full(decoder, is, &dsf_header, sizeof(dsf_header)) ||
 | 
			
		||||
	    !dsf_header.id.Equals("DSD "))
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
@@ -117,7 +117,8 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
 | 
			
		||||
 | 
			
		||||
	/* read the 'fmt ' chunk of the DSF file */
 | 
			
		||||
	DsfFmtChunk dsf_fmt_chunk;
 | 
			
		||||
	if (!dsdlib_read(decoder, is, &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) ||
 | 
			
		||||
	if (!decoder_read_full(decoder, is,
 | 
			
		||||
			       &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) ||
 | 
			
		||||
	    !dsf_fmt_chunk.id.Equals("fmt "))
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
@@ -143,7 +144,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
 | 
			
		||||
 | 
			
		||||
	/* read the 'data' chunk of the DSF file */
 | 
			
		||||
	DsfDataChunk data_chunk;
 | 
			
		||||
	if (!dsdlib_read(decoder, is, &data_chunk, sizeof(data_chunk)) ||
 | 
			
		||||
	if (!decoder_read_full(decoder, is, &data_chunk, sizeof(data_chunk)) ||
 | 
			
		||||
	    !data_chunk.id.Equals("data"))
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
@@ -247,10 +248,10 @@ dsf_decode_chunk(Decoder &decoder, InputStream &is,
 | 
			
		||||
			now_size = now_frames * frame_size;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		size_t nbytes = decoder_read(&decoder, is, buffer, now_size);
 | 
			
		||||
		if (nbytes != now_size)
 | 
			
		||||
		if (!decoder_read_full(&decoder, is, buffer, now_size))
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
		const size_t nbytes = now_size;
 | 
			
		||||
		chunk_size -= nbytes;
 | 
			
		||||
 | 
			
		||||
		if (bitreverse)
 | 
			
		||||
 
 | 
			
		||||
@@ -34,8 +34,6 @@
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
 | 
			
		||||
#define AAC_MAX_CHANNELS	6
 | 
			
		||||
 | 
			
		||||
static const unsigned adts_sample_rates[] =
 | 
			
		||||
    { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
 | 
			
		||||
	16000, 12000, 11025, 8000, 7350, 0, 0, 0
 | 
			
		||||
@@ -66,16 +64,13 @@ adts_check_frame(const unsigned char *data)
 | 
			
		||||
static size_t
 | 
			
		||||
adts_find_frame(DecoderBuffer *buffer)
 | 
			
		||||
{
 | 
			
		||||
	size_t length, frame_length;
 | 
			
		||||
	bool ret;
 | 
			
		||||
 | 
			
		||||
	while (true) {
 | 
			
		||||
		size_t length;
 | 
			
		||||
		const uint8_t *data = (const uint8_t *)
 | 
			
		||||
			decoder_buffer_read(buffer, &length);
 | 
			
		||||
		if (data == nullptr || length < 8) {
 | 
			
		||||
			/* not enough data yet */
 | 
			
		||||
			ret = decoder_buffer_fill(buffer);
 | 
			
		||||
			if (!ret)
 | 
			
		||||
			if (!decoder_buffer_fill(buffer))
 | 
			
		||||
				/* failed */
 | 
			
		||||
				return 0;
 | 
			
		||||
 | 
			
		||||
@@ -86,7 +81,7 @@ adts_find_frame(DecoderBuffer *buffer)
 | 
			
		||||
		const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length);
 | 
			
		||||
		if (p == nullptr) {
 | 
			
		||||
			/* no marker - discard the buffer */
 | 
			
		||||
			decoder_buffer_consume(buffer, length);
 | 
			
		||||
			decoder_buffer_clear(buffer);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -97,7 +92,7 @@ adts_find_frame(DecoderBuffer *buffer)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* is it a frame? */
 | 
			
		||||
		frame_length = adts_check_frame(data);
 | 
			
		||||
		const size_t frame_length = adts_check_frame(data);
 | 
			
		||||
		if (frame_length == 0) {
 | 
			
		||||
			/* it's just some random 0xff byte; discard it
 | 
			
		||||
			   and continue searching */
 | 
			
		||||
@@ -109,15 +104,11 @@ adts_find_frame(DecoderBuffer *buffer)
 | 
			
		||||
			/* available buffer size is smaller than the
 | 
			
		||||
			   frame will be - attempt to read more
 | 
			
		||||
			   data */
 | 
			
		||||
			ret = decoder_buffer_fill(buffer);
 | 
			
		||||
			if (!ret) {
 | 
			
		||||
			if (!decoder_buffer_fill(buffer)) {
 | 
			
		||||
				/* not enough data; discard this frame
 | 
			
		||||
				   to prevent a possible buffer
 | 
			
		||||
				   overflow */
 | 
			
		||||
				data = (const uint8_t *)
 | 
			
		||||
					decoder_buffer_read(buffer, &length);
 | 
			
		||||
				if (data != nullptr)
 | 
			
		||||
					decoder_buffer_consume(buffer, length);
 | 
			
		||||
				decoder_buffer_clear(buffer);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			continue;
 | 
			
		||||
@@ -131,13 +122,18 @@ adts_find_frame(DecoderBuffer *buffer)
 | 
			
		||||
static float
 | 
			
		||||
adts_song_duration(DecoderBuffer *buffer)
 | 
			
		||||
{
 | 
			
		||||
	unsigned int frames, frame_length;
 | 
			
		||||
	const InputStream &is = decoder_buffer_get_stream(buffer);
 | 
			
		||||
	const bool estimate = !is.CheapSeeking();
 | 
			
		||||
	const auto file_size = is.GetSize();
 | 
			
		||||
	if (estimate && file_size <= 0)
 | 
			
		||||
		return -1;
 | 
			
		||||
 | 
			
		||||
	unsigned sample_rate = 0;
 | 
			
		||||
	float frames_per_second;
 | 
			
		||||
 | 
			
		||||
	/* Read all frames to ensure correct time and bitrate */
 | 
			
		||||
	unsigned frames;
 | 
			
		||||
	for (frames = 0;; frames++) {
 | 
			
		||||
		frame_length = adts_find_frame(buffer);
 | 
			
		||||
		const unsigned frame_length = adts_find_frame(buffer);
 | 
			
		||||
		if (frame_length == 0)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
@@ -150,36 +146,52 @@ adts_song_duration(DecoderBuffer *buffer)
 | 
			
		||||
			assert(frame_length <= buffer_length);
 | 
			
		||||
 | 
			
		||||
			sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2];
 | 
			
		||||
			if (sample_rate == 0)
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		decoder_buffer_consume(buffer, frame_length);
 | 
			
		||||
 | 
			
		||||
		if (estimate && frames == 128) {
 | 
			
		||||
			/* if this is a remote file, don't slurp the
 | 
			
		||||
			   whole file just for checking the song
 | 
			
		||||
			   duration; instead, stop after some time and
 | 
			
		||||
			   extrapolate the song duration from what we
 | 
			
		||||
			   have until now */
 | 
			
		||||
 | 
			
		||||
			const auto offset = is.GetOffset()
 | 
			
		||||
				- decoder_buffer_available(buffer);
 | 
			
		||||
			if (offset <= 0)
 | 
			
		||||
				return -1;
 | 
			
		||||
 | 
			
		||||
			frames = (frames * file_size) / offset;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	frames_per_second = (float)sample_rate / 1024.0;
 | 
			
		||||
	if (frames_per_second <= 0)
 | 
			
		||||
	if (sample_rate == 0)
 | 
			
		||||
		return -1;
 | 
			
		||||
 | 
			
		||||
	float frames_per_second = (float)sample_rate / 1024.0;
 | 
			
		||||
	assert(frames_per_second > 0);
 | 
			
		||||
 | 
			
		||||
	return (float)frames / frames_per_second;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static float
 | 
			
		||||
faad_song_duration(DecoderBuffer *buffer, InputStream &is)
 | 
			
		||||
{
 | 
			
		||||
	size_t fileread;
 | 
			
		||||
	size_t tagsize;
 | 
			
		||||
	size_t length;
 | 
			
		||||
	bool success;
 | 
			
		||||
 | 
			
		||||
	const auto size = is.GetSize();
 | 
			
		||||
	fileread = size >= 0 ? size : 0;
 | 
			
		||||
	const size_t fileread = size >= 0 ? size : 0;
 | 
			
		||||
 | 
			
		||||
	decoder_buffer_fill(buffer);
 | 
			
		||||
	size_t length;
 | 
			
		||||
	const uint8_t *data = (const uint8_t *)
 | 
			
		||||
		decoder_buffer_read(buffer, &length);
 | 
			
		||||
	if (data == nullptr)
 | 
			
		||||
		return -1;
 | 
			
		||||
 | 
			
		||||
	tagsize = 0;
 | 
			
		||||
	size_t tagsize = 0;
 | 
			
		||||
	if (length >= 10 && !memcmp(data, "ID3", 3)) {
 | 
			
		||||
		/* skip the ID3 tag */
 | 
			
		||||
 | 
			
		||||
@@ -188,7 +200,7 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
 | 
			
		||||
 | 
			
		||||
		tagsize += 10;
 | 
			
		||||
 | 
			
		||||
		success = decoder_buffer_skip(buffer, tagsize) &&
 | 
			
		||||
		const bool success = decoder_buffer_skip(buffer, tagsize) &&
 | 
			
		||||
			decoder_buffer_fill(buffer);
 | 
			
		||||
		if (!success)
 | 
			
		||||
			return -1;
 | 
			
		||||
@@ -198,22 +210,20 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
 | 
			
		||||
			return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (is.IsSeekable() && length >= 2 &&
 | 
			
		||||
	    data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) {
 | 
			
		||||
	if (length >= 8 && adts_check_frame(data) > 0) {
 | 
			
		||||
		/* obtain the duration from the ADTS header */
 | 
			
		||||
 | 
			
		||||
		if (!is.IsSeekable())
 | 
			
		||||
			return -1;
 | 
			
		||||
 | 
			
		||||
		float song_length = adts_song_duration(buffer);
 | 
			
		||||
 | 
			
		||||
		is.LockSeek(tagsize, SEEK_SET, IgnoreError());
 | 
			
		||||
 | 
			
		||||
		data = (const uint8_t *)decoder_buffer_read(buffer, &length);
 | 
			
		||||
		if (data != nullptr)
 | 
			
		||||
			decoder_buffer_consume(buffer, length);
 | 
			
		||||
		decoder_buffer_fill(buffer);
 | 
			
		||||
		decoder_buffer_clear(buffer);
 | 
			
		||||
 | 
			
		||||
		return song_length;
 | 
			
		||||
	} else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) {
 | 
			
		||||
		/* obtain the duration from the ADIF header */
 | 
			
		||||
		unsigned bit_rate;
 | 
			
		||||
		size_t skip_size = (data[4] & 0x80) ? 9 : 0;
 | 
			
		||||
 | 
			
		||||
		if (8 + skip_size > length)
 | 
			
		||||
@@ -221,7 +231,7 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
 | 
			
		||||
			   header */
 | 
			
		||||
			return -1;
 | 
			
		||||
 | 
			
		||||
		bit_rate = ((data[4 + skip_size] & 0x0F) << 19) |
 | 
			
		||||
		unsigned bit_rate = ((data[4 + skip_size] & 0x0F) << 19) |
 | 
			
		||||
			(data[5 + skip_size] << 11) |
 | 
			
		||||
			(data[6 + skip_size] << 3) |
 | 
			
		||||
			(data[7 + skip_size] & 0xE0);
 | 
			
		||||
@@ -234,6 +244,21 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
 | 
			
		||||
		return -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static NeAACDecHandle
 | 
			
		||||
faad_decoder_new()
 | 
			
		||||
{
 | 
			
		||||
	const NeAACDecHandle decoder = NeAACDecOpen();
 | 
			
		||||
 | 
			
		||||
	NeAACDecConfigurationPtr config =
 | 
			
		||||
		NeAACDecGetCurrentConfiguration(decoder);
 | 
			
		||||
	config->outputFormat = FAAD_FMT_16BIT;
 | 
			
		||||
	config->downMatrix = 1;
 | 
			
		||||
	config->dontUpSampleImplicitSBR = 0;
 | 
			
		||||
	NeAACDecSetConfiguration(decoder, config);
 | 
			
		||||
 | 
			
		||||
	return decoder;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wrapper for NeAACDecInit() which works around some API
 | 
			
		||||
 * inconsistencies in libfaad.
 | 
			
		||||
@@ -242,17 +267,6 @@ static bool
 | 
			
		||||
faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
 | 
			
		||||
		  AudioFormat &audio_format, Error &error)
 | 
			
		||||
{
 | 
			
		||||
	int32_t nbytes;
 | 
			
		||||
	uint32_t sample_rate;
 | 
			
		||||
	uint8_t channels;
 | 
			
		||||
#ifdef HAVE_FAAD_LONG
 | 
			
		||||
	/* neaacdec.h declares all arguments as "unsigned long", but
 | 
			
		||||
	   internally expects uint32_t pointers.  To avoid gcc
 | 
			
		||||
	   warnings, use this workaround. */
 | 
			
		||||
	unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
 | 
			
		||||
#else
 | 
			
		||||
	uint32_t *sample_rate_p = &sample_rate;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	size_t length;
 | 
			
		||||
	const unsigned char *data = (const unsigned char *)
 | 
			
		||||
@@ -262,11 +276,21 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nbytes = NeAACDecInit(decoder,
 | 
			
		||||
			      /* deconst hack, libfaad requires this */
 | 
			
		||||
			      const_cast<unsigned char *>(data),
 | 
			
		||||
			     length,
 | 
			
		||||
			     sample_rate_p, &channels);
 | 
			
		||||
	uint8_t channels;
 | 
			
		||||
	uint32_t sample_rate;
 | 
			
		||||
#ifdef HAVE_FAAD_LONG
 | 
			
		||||
	/* neaacdec.h declares all arguments as "unsigned long", but
 | 
			
		||||
	   internally expects uint32_t pointers.  To avoid gcc
 | 
			
		||||
	   warnings, use this workaround. */
 | 
			
		||||
	unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
 | 
			
		||||
#else
 | 
			
		||||
	uint32_t *sample_rate_p = &sample_rate;
 | 
			
		||||
#endif
 | 
			
		||||
	long nbytes = NeAACDecInit(decoder,
 | 
			
		||||
				   /* deconst hack, libfaad requires this */
 | 
			
		||||
				   const_cast<unsigned char *>(data),
 | 
			
		||||
				   length,
 | 
			
		||||
				   sample_rate_p, &channels);
 | 
			
		||||
	if (nbytes < 0) {
 | 
			
		||||
		error.Set(faad_decoder_domain, "Not an AAC stream");
 | 
			
		||||
		return false;
 | 
			
		||||
@@ -306,29 +330,21 @@ faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer,
 | 
			
		||||
static float
 | 
			
		||||
faad_get_file_time_float(InputStream &is)
 | 
			
		||||
{
 | 
			
		||||
	DecoderBuffer *buffer;
 | 
			
		||||
	float length;
 | 
			
		||||
	DecoderBuffer *const buffer =
 | 
			
		||||
		decoder_buffer_new(nullptr, is,
 | 
			
		||||
				   FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
 | 
			
		||||
 | 
			
		||||
	buffer = decoder_buffer_new(nullptr, is,
 | 
			
		||||
				    FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
 | 
			
		||||
	length = faad_song_duration(buffer, is);
 | 
			
		||||
	float length = faad_song_duration(buffer, is);
 | 
			
		||||
 | 
			
		||||
	if (length < 0) {
 | 
			
		||||
		bool ret;
 | 
			
		||||
		AudioFormat audio_format;
 | 
			
		||||
 | 
			
		||||
		NeAACDecHandle decoder = NeAACDecOpen();
 | 
			
		||||
 | 
			
		||||
		NeAACDecConfigurationPtr config =
 | 
			
		||||
			NeAACDecGetCurrentConfiguration(decoder);
 | 
			
		||||
		config->outputFormat = FAAD_FMT_16BIT;
 | 
			
		||||
		NeAACDecSetConfiguration(decoder, config);
 | 
			
		||||
		NeAACDecHandle decoder = faad_decoder_new();
 | 
			
		||||
 | 
			
		||||
		decoder_buffer_fill(buffer);
 | 
			
		||||
 | 
			
		||||
		ret = faad_decoder_init(decoder, buffer, audio_format,
 | 
			
		||||
					IgnoreError());
 | 
			
		||||
		if (ret)
 | 
			
		||||
		if (faad_decoder_init(decoder, buffer, audio_format,
 | 
			
		||||
				      IgnoreError()))
 | 
			
		||||
			length = 0;
 | 
			
		||||
 | 
			
		||||
		NeAACDecClose(decoder);
 | 
			
		||||
@@ -347,38 +363,25 @@ faad_get_file_time_float(InputStream &is)
 | 
			
		||||
static int
 | 
			
		||||
faad_get_file_time(InputStream &is)
 | 
			
		||||
{
 | 
			
		||||
	int file_time = -1;
 | 
			
		||||
	float length;
 | 
			
		||||
	float length = faad_get_file_time_float(is);
 | 
			
		||||
	if (length < 0)
 | 
			
		||||
		return -1;
 | 
			
		||||
 | 
			
		||||
	if ((length = faad_get_file_time_float(is)) >= 0)
 | 
			
		||||
		file_time = length + 0.5;
 | 
			
		||||
 | 
			
		||||
	return file_time;
 | 
			
		||||
	return int(length + 0.5);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
 | 
			
		||||
{
 | 
			
		||||
	float total_time = 0;
 | 
			
		||||
	AudioFormat audio_format;
 | 
			
		||||
	bool ret;
 | 
			
		||||
	uint16_t bit_rate = 0;
 | 
			
		||||
	DecoderBuffer *buffer;
 | 
			
		||||
	DecoderBuffer *const buffer =
 | 
			
		||||
		decoder_buffer_new(&mpd_decoder, is,
 | 
			
		||||
				   FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
 | 
			
		||||
 | 
			
		||||
	buffer = decoder_buffer_new(&mpd_decoder, is,
 | 
			
		||||
				    FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
 | 
			
		||||
	total_time = faad_song_duration(buffer, is);
 | 
			
		||||
	const float total_time = faad_song_duration(buffer, is);
 | 
			
		||||
 | 
			
		||||
	/* create the libfaad decoder */
 | 
			
		||||
 | 
			
		||||
	NeAACDecHandle decoder = NeAACDecOpen();
 | 
			
		||||
 | 
			
		||||
	NeAACDecConfigurationPtr config =
 | 
			
		||||
		NeAACDecGetCurrentConfiguration(decoder);
 | 
			
		||||
	config->outputFormat = FAAD_FMT_16BIT;
 | 
			
		||||
	config->downMatrix = 1;
 | 
			
		||||
	config->dontUpSampleImplicitSBR = 0;
 | 
			
		||||
	NeAACDecSetConfiguration(decoder, config);
 | 
			
		||||
	const NeAACDecHandle decoder = faad_decoder_new();
 | 
			
		||||
 | 
			
		||||
	while (!decoder_buffer_is_full(buffer) && !is.LockIsEOF() &&
 | 
			
		||||
	       decoder_get_command(mpd_decoder) == DecoderCommand::NONE) {
 | 
			
		||||
@@ -389,10 +392,11 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
 | 
			
		||||
	/* initialize it */
 | 
			
		||||
 | 
			
		||||
	Error error;
 | 
			
		||||
	ret = faad_decoder_init(decoder, buffer, audio_format, error);
 | 
			
		||||
	if (!ret) {
 | 
			
		||||
	AudioFormat audio_format;
 | 
			
		||||
	if (!faad_decoder_init(decoder, buffer, audio_format, error)) {
 | 
			
		||||
		LogError(error);
 | 
			
		||||
		NeAACDecClose(decoder);
 | 
			
		||||
		decoder_buffer_free(buffer);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -403,21 +407,20 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
 | 
			
		||||
	/* the decoder loop */
 | 
			
		||||
 | 
			
		||||
	DecoderCommand cmd;
 | 
			
		||||
	uint16_t bit_rate = 0;
 | 
			
		||||
	do {
 | 
			
		||||
		size_t frame_size;
 | 
			
		||||
		const void *decoded;
 | 
			
		||||
		NeAACDecFrameInfo frame_info;
 | 
			
		||||
 | 
			
		||||
		/* find the next frame */
 | 
			
		||||
 | 
			
		||||
		frame_size = adts_find_frame(buffer);
 | 
			
		||||
		const size_t frame_size = adts_find_frame(buffer);
 | 
			
		||||
		if (frame_size == 0)
 | 
			
		||||
			/* end of file */
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		/* decode it */
 | 
			
		||||
 | 
			
		||||
		decoded = faad_decoder_decode(decoder, buffer, &frame_info);
 | 
			
		||||
		NeAACDecFrameInfo frame_info;
 | 
			
		||||
		const void *const decoded =
 | 
			
		||||
			faad_decoder_decode(decoder, buffer, &frame_info);
 | 
			
		||||
 | 
			
		||||
		if (frame_info.error > 0) {
 | 
			
		||||
			FormatWarning(faad_decoder_domain,
 | 
			
		||||
@@ -461,6 +464,7 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
 | 
			
		||||
	/* cleanup */
 | 
			
		||||
 | 
			
		||||
	NeAACDecClose(decoder);
 | 
			
		||||
	decoder_buffer_free(buffer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
@@ -468,7 +472,6 @@ faad_scan_stream(InputStream &is,
 | 
			
		||||
		 const struct tag_handler *handler, void *handler_ctx)
 | 
			
		||||
{
 | 
			
		||||
	int file_time = faad_get_file_time(is);
 | 
			
		||||
 | 
			
		||||
	if (file_time < 0)
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,10 @@ extern "C" {
 | 
			
		||||
#include <libavutil/avutil.h>
 | 
			
		||||
#include <libavutil/log.h>
 | 
			
		||||
#include <libavutil/mathematics.h>
 | 
			
		||||
#include <libavutil/dict.h>
 | 
			
		||||
 | 
			
		||||
#if LIBAVUTIL_VERSION_MAJOR >= 53
 | 
			
		||||
#include <libavutil/frame.h>
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
@@ -194,6 +197,29 @@ time_to_ffmpeg(double t, const AVRational time_base)
 | 
			
		||||
			    time_base);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Replace #AV_NOPTS_VALUE with the given fallback.
 | 
			
		||||
 */
 | 
			
		||||
static constexpr int64_t
 | 
			
		||||
timestamp_fallback(int64_t t, int64_t fallback)
 | 
			
		||||
{
 | 
			
		||||
	return gcc_likely(t != int64_t(AV_NOPTS_VALUE))
 | 
			
		||||
		? t
 | 
			
		||||
		: fallback;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Accessor for AVStream::start_time that replaces AV_NOPTS_VALUE with
 | 
			
		||||
 * zero.  We can't use AV_NOPTS_VALUE in calculations, and we simply
 | 
			
		||||
 * assume that the stream's start time is zero, which appears to be
 | 
			
		||||
 * the best way out of that situation.
 | 
			
		||||
 */
 | 
			
		||||
static int64_t
 | 
			
		||||
start_time_fallback(const AVStream &stream)
 | 
			
		||||
{
 | 
			
		||||
	return timestamp_fallback(stream.start_time, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
copy_interleave_frame2(uint8_t *dest, uint8_t **src,
 | 
			
		||||
		       unsigned nframes, unsigned nchannels,
 | 
			
		||||
@@ -223,6 +249,9 @@ copy_interleave_frame(const AVCodecContext *codec_context,
 | 
			
		||||
					   codec_context->channels,
 | 
			
		||||
					   frame->nb_samples,
 | 
			
		||||
					   codec_context->sample_fmt, 1);
 | 
			
		||||
	if (data_size <= 0)
 | 
			
		||||
		return data_size;
 | 
			
		||||
 | 
			
		||||
	if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
 | 
			
		||||
	    codec_context->channels > 1) {
 | 
			
		||||
		if(*global_buffer_size < data_size) {
 | 
			
		||||
@@ -257,7 +286,7 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is,
 | 
			
		||||
{
 | 
			
		||||
	if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE)
 | 
			
		||||
		decoder_timestamp(decoder,
 | 
			
		||||
				  time_from_ffmpeg(packet->pts - stream->start_time,
 | 
			
		||||
				  time_from_ffmpeg(packet->pts - start_time_fallback(*stream),
 | 
			
		||||
				  stream->time_base));
 | 
			
		||||
 | 
			
		||||
	AVPacket packet2 = *packet;
 | 
			
		||||
@@ -451,7 +480,11 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
 | 
			
		||||
	decoder_initialized(decoder, audio_format,
 | 
			
		||||
			    input.seekable, total_time);
 | 
			
		||||
 | 
			
		||||
#if LIBAVUTIL_VERSION_MAJOR >= 53
 | 
			
		||||
	AVFrame *frame = av_frame_alloc();
 | 
			
		||||
#else
 | 
			
		||||
	AVFrame *frame = avcodec_alloc_frame();
 | 
			
		||||
#endif
 | 
			
		||||
	if (!frame) {
 | 
			
		||||
		LogError(ffmpeg_domain, "Could not allocate frame");
 | 
			
		||||
		avformat_close_input(&format_context);
 | 
			
		||||
@@ -483,10 +516,10 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
 | 
			
		||||
			int64_t where =
 | 
			
		||||
				time_to_ffmpeg(decoder_seek_where(decoder),
 | 
			
		||||
					       av_stream->time_base) +
 | 
			
		||||
				av_stream->start_time;
 | 
			
		||||
				start_time_fallback(*av_stream);
 | 
			
		||||
 | 
			
		||||
			if (av_seek_frame(format_context, audio_stream, where,
 | 
			
		||||
					  AV_TIME_BASE) < 0)
 | 
			
		||||
					  AVSEEK_FLAG_ANY) < 0)
 | 
			
		||||
				decoder_seek_error(decoder);
 | 
			
		||||
			else {
 | 
			
		||||
				avcodec_flush_buffers(codec_context);
 | 
			
		||||
@@ -495,7 +528,9 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
 | 
			
		||||
		}
 | 
			
		||||
	} while (cmd != DecoderCommand::STOP);
 | 
			
		||||
 | 
			
		||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
 | 
			
		||||
#if LIBAVUTIL_VERSION_MAJOR >= 53
 | 
			
		||||
	av_frame_free(&frame);
 | 
			
		||||
#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
 | 
			
		||||
	avcodec_free_frame(&frame);
 | 
			
		||||
#else
 | 
			
		||||
	av_freep(&frame);
 | 
			
		||||
 
 | 
			
		||||
@@ -21,8 +21,6 @@
 | 
			
		||||
#define MPD_FFMPEG_METADATA_HXX
 | 
			
		||||
 | 
			
		||||
extern "C" {
 | 
			
		||||
#include <libavformat/avformat.h>
 | 
			
		||||
#include <libavutil/avutil.h>
 | 
			
		||||
#include <libavutil/dict.h>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -35,6 +33,6 @@ struct tag_handler;
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
ffmpeg_scan_dictionary(AVDictionary *dict,
 | 
			
		||||
		       const struct tag_handler *handler, void *handler_ctx);
 | 
			
		||||
		       const tag_handler *handler, void *handler_ctx);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -117,6 +117,7 @@ gme_container_scan(const char *path_fs, const unsigned int tnum)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const unsigned num_songs = gme_track_count(emu);
 | 
			
		||||
	gme_delete(emu);
 | 
			
		||||
	/* if it only contains a single tune, don't treat as container */
 | 
			
		||||
	if (num_songs < 2)
 | 
			
		||||
		return nullptr;
 | 
			
		||||
 
 | 
			
		||||
@@ -353,18 +353,8 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
 | 
			
		||||
		memcpy(allocated, stream.this_frame, count);
 | 
			
		||||
		mad_stream_skip(&(stream), count);
 | 
			
		||||
 | 
			
		||||
		while (count < tagsize) {
 | 
			
		||||
			size_t len;
 | 
			
		||||
 | 
			
		||||
			len = decoder_read(decoder, input_stream,
 | 
			
		||||
					   allocated + count, tagsize - count);
 | 
			
		||||
			if (len == 0)
 | 
			
		||||
				break;
 | 
			
		||||
			else
 | 
			
		||||
				count += len;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (count != tagsize) {
 | 
			
		||||
		if (!decoder_read_full(decoder, input_stream,
 | 
			
		||||
				       allocated + count, tagsize - count)) {
 | 
			
		||||
			LogDebug(mad_domain, "error parsing ID3 tag");
 | 
			
		||||
			g_free(allocated);
 | 
			
		||||
			return;
 | 
			
		||||
@@ -413,20 +403,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
 | 
			
		||||
		mad_stream_skip(&stream, tagsize);
 | 
			
		||||
	} else {
 | 
			
		||||
		mad_stream_skip(&stream, count);
 | 
			
		||||
 | 
			
		||||
		while (count < tagsize) {
 | 
			
		||||
			size_t len = tagsize - count;
 | 
			
		||||
			char ignored[1024];
 | 
			
		||||
			if (len > sizeof(ignored))
 | 
			
		||||
				len = sizeof(ignored);
 | 
			
		||||
 | 
			
		||||
			len = decoder_read(decoder, input_stream,
 | 
			
		||||
					   ignored, len);
 | 
			
		||||
			if (len == 0)
 | 
			
		||||
				break;
 | 
			
		||||
			else
 | 
			
		||||
				count += len;
 | 
			
		||||
		}
 | 
			
		||||
		decoder_skip(decoder, input_stream, tagsize - count);
 | 
			
		||||
	}
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,7 @@ bool
 | 
			
		||||
OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page,
 | 
			
		||||
		  Decoder *decoder, InputStream &input_stream)
 | 
			
		||||
{
 | 
			
		||||
	size_t remaining_skipped = 16384;
 | 
			
		||||
	size_t remaining_skipped = 32768;
 | 
			
		||||
 | 
			
		||||
	while (true) {
 | 
			
		||||
		int r = ogg_sync_pageseek(&oy, &page);
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,7 @@
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
static constexpr opus_int32 opus_sample_rate = 48000;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -31,10 +31,24 @@
 | 
			
		||||
 | 
			
		||||
static constexpr Domain sndfile_domain("sndfile");
 | 
			
		||||
 | 
			
		||||
struct SndfileInputStream {
 | 
			
		||||
	Decoder *const decoder;
 | 
			
		||||
	InputStream &is;
 | 
			
		||||
 | 
			
		||||
	size_t Read(void *buffer, size_t size) {
 | 
			
		||||
		/* libsndfile chokes on partial reads; therefore
 | 
			
		||||
		   always force full reads */
 | 
			
		||||
		return decoder_read_full(decoder, is, buffer, size)
 | 
			
		||||
			? size
 | 
			
		||||
			: 0;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static sf_count_t
 | 
			
		||||
sndfile_vio_get_filelen(void *user_data)
 | 
			
		||||
{
 | 
			
		||||
	const InputStream &is = *(const InputStream *)user_data;
 | 
			
		||||
	SndfileInputStream &sis = *(SndfileInputStream *)user_data;
 | 
			
		||||
	const InputStream &is = sis.is;
 | 
			
		||||
 | 
			
		||||
	return is.GetSize();
 | 
			
		||||
}
 | 
			
		||||
@@ -42,10 +56,14 @@ sndfile_vio_get_filelen(void *user_data)
 | 
			
		||||
static sf_count_t
 | 
			
		||||
sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
 | 
			
		||||
{
 | 
			
		||||
	InputStream &is = *(InputStream *)user_data;
 | 
			
		||||
	SndfileInputStream &sis = *(SndfileInputStream *)user_data;
 | 
			
		||||
	InputStream &is = sis.is;
 | 
			
		||||
 | 
			
		||||
	if (!is.LockSeek(offset, whence, IgnoreError()))
 | 
			
		||||
	Error error;
 | 
			
		||||
	if (!is.LockSeek(offset, whence, error)) {
 | 
			
		||||
		LogError(error, "Seek failed");
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return is.GetOffset();
 | 
			
		||||
}
 | 
			
		||||
@@ -53,16 +71,9 @@ sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
 | 
			
		||||
static sf_count_t
 | 
			
		||||
sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
 | 
			
		||||
{
 | 
			
		||||
	InputStream &is = *(InputStream *)user_data;
 | 
			
		||||
	SndfileInputStream &sis = *(SndfileInputStream *)user_data;
 | 
			
		||||
 | 
			
		||||
	Error error;
 | 
			
		||||
	size_t nbytes = is.LockRead(ptr, count, error);
 | 
			
		||||
	if (nbytes == 0 && error.IsDefined()) {
 | 
			
		||||
		LogError(error);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nbytes;
 | 
			
		||||
	return sis.Read(ptr, count);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static sf_count_t
 | 
			
		||||
@@ -77,7 +88,8 @@ sndfile_vio_write(gcc_unused const void *ptr,
 | 
			
		||||
static sf_count_t
 | 
			
		||||
sndfile_vio_tell(void *user_data)
 | 
			
		||||
{
 | 
			
		||||
	const InputStream &is = *(const InputStream *)user_data;
 | 
			
		||||
	SndfileInputStream &sis = *(SndfileInputStream *)user_data;
 | 
			
		||||
	const InputStream &is = sis.is;
 | 
			
		||||
 | 
			
		||||
	return is.GetOffset();
 | 
			
		||||
}
 | 
			
		||||
@@ -123,7 +135,8 @@ sndfile_stream_decode(Decoder &decoder, InputStream &is)
 | 
			
		||||
 | 
			
		||||
	info.format = 0;
 | 
			
		||||
 | 
			
		||||
	sf = sf_open_virtual(&vio, SFM_READ, &info, &is);
 | 
			
		||||
	SndfileInputStream sis{&decoder, is};
 | 
			
		||||
	sf = sf_open_virtual(&vio, SFM_READ, &info, &sis);
 | 
			
		||||
	if (sf == nullptr) {
 | 
			
		||||
		LogWarning(sndfile_domain, "sf_open_virtual() failed");
 | 
			
		||||
		return;
 | 
			
		||||
 
 | 
			
		||||
@@ -27,9 +27,11 @@ DeferredMonitor::Cancel()
 | 
			
		||||
#ifdef USE_EPOLL
 | 
			
		||||
	pending = false;
 | 
			
		||||
#else
 | 
			
		||||
	const auto id = source_id.exchange(0);
 | 
			
		||||
	if (id != 0)
 | 
			
		||||
		g_source_remove(id);
 | 
			
		||||
	const ScopeLock protect(mutex);
 | 
			
		||||
	if (source_id != 0) {
 | 
			
		||||
		g_source_remove(source_id);
 | 
			
		||||
		source_id = 0;
 | 
			
		||||
	}
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -40,10 +42,9 @@ DeferredMonitor::Schedule()
 | 
			
		||||
	if (!pending.exchange(true))
 | 
			
		||||
		fd.Write();
 | 
			
		||||
#else
 | 
			
		||||
	const unsigned id = loop.AddIdle(Callback, this);
 | 
			
		||||
	const auto old_id = source_id.exchange(id);
 | 
			
		||||
	if (old_id != 0)
 | 
			
		||||
		g_source_remove(old_id);
 | 
			
		||||
	const ScopeLock protect(mutex);
 | 
			
		||||
	if (source_id == 0)
 | 
			
		||||
		source_id = loop.AddIdle(Callback, this);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -65,9 +66,16 @@ DeferredMonitor::OnSocketReady(unsigned)
 | 
			
		||||
void
 | 
			
		||||
DeferredMonitor::Run()
 | 
			
		||||
{
 | 
			
		||||
	const auto id = source_id.exchange(0);
 | 
			
		||||
	if (id != 0)
 | 
			
		||||
		RunDeferred();
 | 
			
		||||
	{
 | 
			
		||||
		const ScopeLock protect(mutex);
 | 
			
		||||
		if (source_id == 0)
 | 
			
		||||
			/* cancelled */
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		source_id = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	RunDeferred();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
gboolean
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@
 | 
			
		||||
#include "SocketMonitor.hxx"
 | 
			
		||||
#include "WakeFD.hxx"
 | 
			
		||||
#else
 | 
			
		||||
#include "thread/Mutex.hxx"
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@@ -48,7 +49,9 @@ class DeferredMonitor
 | 
			
		||||
#else
 | 
			
		||||
	EventLoop &loop;
 | 
			
		||||
 | 
			
		||||
	std::atomic<guint> source_id;
 | 
			
		||||
	Mutex mutex;
 | 
			
		||||
 | 
			
		||||
	guint source_id;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,12 @@
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SIGNALFD
 | 
			
		||||
#include <pthread.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <signal.h>
 | 
			
		||||
 | 
			
		||||
class SignalMonitor final : private SocketMonitor {
 | 
			
		||||
#ifdef USE_SIGNALFD
 | 
			
		||||
	SignalFD fd;
 | 
			
		||||
@@ -99,7 +105,21 @@ static std::atomic_bool signal_pending[MAX_SIGNAL];
 | 
			
		||||
 | 
			
		||||
static Manual<SignalMonitor> monitor;
 | 
			
		||||
 | 
			
		||||
#ifndef USE_SIGNALFD
 | 
			
		||||
#ifdef USE_SIGNALFD
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This is a pthread_atfork() callback that unblocks the signals that
 | 
			
		||||
 * were blocked for our signalfd().  Without this, our child processes
 | 
			
		||||
 * would inherit the blocked signals.
 | 
			
		||||
 */
 | 
			
		||||
static void
 | 
			
		||||
at_fork_child()
 | 
			
		||||
{
 | 
			
		||||
	sigprocmask(SIG_UNBLOCK, &signal_mask, nullptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
SignalCallback(int signo)
 | 
			
		||||
{
 | 
			
		||||
@@ -108,6 +128,7 @@ SignalCallback(int signo)
 | 
			
		||||
	if (!signal_pending[signo].exchange(true))
 | 
			
		||||
		monitor->WakeUp();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
@@ -115,6 +136,8 @@ SignalMonitorInit(EventLoop &loop)
 | 
			
		||||
{
 | 
			
		||||
#ifdef USE_SIGNALFD
 | 
			
		||||
	sigemptyset(&signal_mask);
 | 
			
		||||
 | 
			
		||||
	pthread_atfork(nullptr, nullptr, at_fork_child);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	monitor.Construct(loop);
 | 
			
		||||
 
 | 
			
		||||
@@ -106,12 +106,16 @@ struct AlsaOutput {
 | 
			
		||||
	snd_pcm_uframes_t period_position;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Set to non-zero when the Raspberry Pi workaround has been
 | 
			
		||||
	 * activated in alsa_recover(); decremented by each write.
 | 
			
		||||
	 * This will avoid activating it again, leading to an endless
 | 
			
		||||
	 * loop.  This problem was observed with a "RME Digi9636/52".
 | 
			
		||||
	 * Do we need to call snd_pcm_prepare() before the next write?
 | 
			
		||||
	 * It means that we put the device to SND_PCM_STATE_SETUP by
 | 
			
		||||
	 * calling snd_pcm_drop().
 | 
			
		||||
	 *
 | 
			
		||||
	 * Without this flag, we could easily recover after a failed
 | 
			
		||||
	 * optimistic write (returning -EBADFD), but the Raspberry Pi
 | 
			
		||||
	 * audio driver is infamous for generating ugly artefacts from
 | 
			
		||||
	 * this.
 | 
			
		||||
	 */
 | 
			
		||||
	unsigned pi_workaround;
 | 
			
		||||
	bool must_prepare;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * This buffer gets allocated after opening the ALSA device.
 | 
			
		||||
@@ -676,8 +680,6 @@ alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
 | 
			
		||||
{
 | 
			
		||||
	AlsaOutput *ad = (AlsaOutput *)ao;
 | 
			
		||||
 | 
			
		||||
	ad->pi_workaround = 0;
 | 
			
		||||
 | 
			
		||||
	int err = snd_pcm_open(&ad->pcm, alsa_device(ad),
 | 
			
		||||
			       SND_PCM_STREAM_PLAYBACK, ad->mode);
 | 
			
		||||
	if (err < 0) {
 | 
			
		||||
@@ -699,6 +701,8 @@ alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
 | 
			
		||||
	ad->in_frame_size = audio_format.GetFrameSize();
 | 
			
		||||
	ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format);
 | 
			
		||||
 | 
			
		||||
	ad->must_prepare = false;
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -736,29 +740,6 @@ alsa_recover(AlsaOutput *ad, int err)
 | 
			
		||||
	case SND_PCM_STATE_XRUN:
 | 
			
		||||
		ad->period_position = 0;
 | 
			
		||||
		err = snd_pcm_prepare(ad->pcm);
 | 
			
		||||
 | 
			
		||||
		if (err == 0 && ad->pi_workaround == 0) {
 | 
			
		||||
			/* this works around a driver bug observed on
 | 
			
		||||
			   the Raspberry Pi: after snd_pcm_drop(), the
 | 
			
		||||
			   whole ring buffer must be invalidated, but
 | 
			
		||||
			   the snd_pcm_prepare() call above makes the
 | 
			
		||||
			   driver play random data that just happens
 | 
			
		||||
			   to be still in the buffer; by adding and
 | 
			
		||||
			   cancelling some silence, this bug does not
 | 
			
		||||
			   occur */
 | 
			
		||||
			alsa_write_silence(ad, ad->period_frames);
 | 
			
		||||
 | 
			
		||||
			/* cancel the silence data right away to avoid
 | 
			
		||||
			   increasing latency; even though this
 | 
			
		||||
			   function call invalidates the portion of
 | 
			
		||||
			   silence, the driver seems to avoid the
 | 
			
		||||
			   bug */
 | 
			
		||||
			snd_pcm_reset(ad->pcm);
 | 
			
		||||
 | 
			
		||||
			/* disable the workaround for some time */
 | 
			
		||||
			ad->pi_workaround = 8;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		break;
 | 
			
		||||
	case SND_PCM_STATE_DISCONNECTED:
 | 
			
		||||
		break;
 | 
			
		||||
@@ -801,6 +782,7 @@ alsa_cancel(struct audio_output *ao)
 | 
			
		||||
	AlsaOutput *ad = (AlsaOutput *)ao;
 | 
			
		||||
 | 
			
		||||
	ad->period_position = 0;
 | 
			
		||||
	ad->must_prepare = true;
 | 
			
		||||
 | 
			
		||||
	snd_pcm_drop(ad->pcm);
 | 
			
		||||
}
 | 
			
		||||
@@ -822,6 +804,16 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size,
 | 
			
		||||
 | 
			
		||||
	assert(size % ad->in_frame_size == 0);
 | 
			
		||||
 | 
			
		||||
	if (ad->must_prepare) {
 | 
			
		||||
		ad->must_prepare = false;
 | 
			
		||||
 | 
			
		||||
		int err = snd_pcm_prepare(ad->pcm);
 | 
			
		||||
		if (err < 0) {
 | 
			
		||||
			error.Set(alsa_output_domain, err, snd_strerror(-err));
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	chunk = ad->pcm_export->Export(chunk, size, size);
 | 
			
		||||
 | 
			
		||||
	assert(size % ad->out_frame_size == 0);
 | 
			
		||||
@@ -834,9 +826,6 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size,
 | 
			
		||||
			ad->period_position = (ad->period_position + ret)
 | 
			
		||||
				% ad->period_frames;
 | 
			
		||||
 | 
			
		||||
			if (ad->pi_workaround > 0)
 | 
			
		||||
				--ad->pi_workaround;
 | 
			
		||||
 | 
			
		||||
			size_t bytes_written = ret * ad->out_frame_size;
 | 
			
		||||
			return ad->pcm_export->CalcSourceSize(bytes_written);
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,7 @@
 | 
			
		||||
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
HttpdClient::~HttpdClient()
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -369,8 +369,6 @@ pulse_output_enable(struct audio_output *ao, Error &error)
 | 
			
		||||
 | 
			
		||||
	po->mainloop = pa_threaded_mainloop_new();
 | 
			
		||||
	if (po->mainloop == nullptr) {
 | 
			
		||||
		g_free(po);
 | 
			
		||||
 | 
			
		||||
		error.Set(pulse_output_domain,
 | 
			
		||||
			  "pa_threaded_mainloop_new() has failed");
 | 
			
		||||
		return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
static constexpr Domain pls_domain("pls");
 | 
			
		||||
 | 
			
		||||
@@ -68,7 +69,6 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
 | 
			
		||||
			FormatError(pls_domain, "Invalid PLS entry %s: '%s'",
 | 
			
		||||
				    key, error->message);
 | 
			
		||||
			g_error_free(error);
 | 
			
		||||
			g_free(key);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@ TagLoadConfig()
 | 
			
		||||
	if (value == nullptr)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	std::fill_n(ignore_tag_items, TAG_NUM_OF_ITEM_TYPES, true);
 | 
			
		||||
	std::fill_n(ignore_tag_items, size_t(TAG_NUM_OF_ITEM_TYPES), true);
 | 
			
		||||
 | 
			
		||||
	if (StringEqualsCaseASCII(value, "none"))
 | 
			
		||||
		return;
 | 
			
		||||
 
 | 
			
		||||
@@ -130,8 +130,9 @@ PeakBuffer::Append(const void *data, size_t length)
 | 
			
		||||
			return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (peak_buffer == nullptr && peak_size > 0) {
 | 
			
		||||
		peak_buffer = (fifo_buffer *)HugeAllocate(peak_size);
 | 
			
		||||
	if (peak_buffer == nullptr) {
 | 
			
		||||
		if (peak_size > 0)
 | 
			
		||||
			peak_buffer = (fifo_buffer *)HugeAllocate(peak_size);
 | 
			
		||||
		if (peak_buffer == nullptr)
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,8 @@ const char *
 | 
			
		||||
uri_get_suffix(const char *uri)
 | 
			
		||||
{
 | 
			
		||||
	const char *suffix = strrchr(uri, '.');
 | 
			
		||||
	if (suffix == nullptr)
 | 
			
		||||
	if (suffix == nullptr || suffix == uri ||
 | 
			
		||||
	    suffix[-1] == '/' || suffix[-1] == '\\')
 | 
			
		||||
		return nullptr;
 | 
			
		||||
 | 
			
		||||
	++suffix;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										144
									
								
								test/FakeDecoderAPI.cxx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								test/FakeDecoderAPI.cxx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2003-2012 The Music Player Daemon Project
 | 
			
		||||
 * http://www.musicpd.org
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "DecoderAPI.hxx"
 | 
			
		||||
#include "InputStream.hxx"
 | 
			
		||||
#include "util/Error.hxx"
 | 
			
		||||
#include "Compiler.h"
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_initialized(gcc_unused Decoder &decoder,
 | 
			
		||||
		    gcc_unused const AudioFormat audio_format,
 | 
			
		||||
		    gcc_unused bool seekable,
 | 
			
		||||
		    gcc_unused float total_time)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DecoderCommand
 | 
			
		||||
decoder_get_command(gcc_unused Decoder &decoder)
 | 
			
		||||
{
 | 
			
		||||
	return DecoderCommand::NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_command_finished(gcc_unused Decoder &decoder)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double
 | 
			
		||||
decoder_seek_where(gcc_unused Decoder &decoder)
 | 
			
		||||
{
 | 
			
		||||
	return 1.0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_seek_error(gcc_unused Decoder &decoder)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t
 | 
			
		||||
decoder_read(gcc_unused Decoder *decoder,
 | 
			
		||||
	     InputStream &is,
 | 
			
		||||
	     void *buffer, size_t length)
 | 
			
		||||
{
 | 
			
		||||
	return is.LockRead(buffer, length, IgnoreError());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
decoder_read_full(Decoder *decoder, InputStream &is,
 | 
			
		||||
		  void *_buffer, size_t size)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t *buffer = (uint8_t *)_buffer;
 | 
			
		||||
 | 
			
		||||
	while (size > 0) {
 | 
			
		||||
		size_t nbytes = decoder_read(decoder, is, buffer, size);
 | 
			
		||||
		if (nbytes == 0)
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
		buffer += nbytes;
 | 
			
		||||
		size -= nbytes;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
decoder_skip(Decoder *decoder, InputStream &is, size_t size)
 | 
			
		||||
{
 | 
			
		||||
	while (size > 0) {
 | 
			
		||||
		char buffer[1024];
 | 
			
		||||
		size_t nbytes = decoder_read(decoder, is, buffer,
 | 
			
		||||
					     std::min(sizeof(buffer), size));
 | 
			
		||||
		if (nbytes == 0)
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
		size -= nbytes;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_timestamp(gcc_unused Decoder &decoder,
 | 
			
		||||
		  gcc_unused double t)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DecoderCommand
 | 
			
		||||
decoder_data(gcc_unused Decoder &decoder,
 | 
			
		||||
	     gcc_unused InputStream *is,
 | 
			
		||||
	     const void *data, size_t datalen,
 | 
			
		||||
	     gcc_unused uint16_t kbit_rate)
 | 
			
		||||
{
 | 
			
		||||
	gcc_unused ssize_t nbytes = write(1, data, datalen);
 | 
			
		||||
	return DecoderCommand::NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DecoderCommand
 | 
			
		||||
decoder_tag(gcc_unused Decoder &decoder,
 | 
			
		||||
	    gcc_unused InputStream *is,
 | 
			
		||||
	    gcc_unused Tag &&tag)
 | 
			
		||||
{
 | 
			
		||||
	return DecoderCommand::NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_replay_gain(gcc_unused Decoder &decoder,
 | 
			
		||||
		    const ReplayGainInfo *rgi)
 | 
			
		||||
{
 | 
			
		||||
	const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM];
 | 
			
		||||
	if (tuple->IsDefined())
 | 
			
		||||
		g_printerr("replay_gain[album]: gain=%f peak=%f\n",
 | 
			
		||||
			   tuple->gain, tuple->peak);
 | 
			
		||||
 | 
			
		||||
	tuple = &rgi->tuples[REPLAY_GAIN_TRACK];
 | 
			
		||||
	if (tuple->IsDefined())
 | 
			
		||||
		g_printerr("replay_gain[track]: gain=%f peak=%f\n",
 | 
			
		||||
			   tuple->gain, tuple->peak);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
@@ -24,7 +24,6 @@
 | 
			
		||||
#include "Directory.hxx"
 | 
			
		||||
#include "InputStream.hxx"
 | 
			
		||||
#include "ConfigGlobal.hxx"
 | 
			
		||||
#include "DecoderAPI.hxx"
 | 
			
		||||
#include "DecoderList.hxx"
 | 
			
		||||
#include "InputInit.hxx"
 | 
			
		||||
#include "IOThread.hxx"
 | 
			
		||||
@@ -53,88 +52,6 @@ my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
 | 
			
		||||
		g_printerr("%s\n", message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_initialized(gcc_unused Decoder &decoder,
 | 
			
		||||
		    gcc_unused const AudioFormat audio_format,
 | 
			
		||||
		    gcc_unused bool seekable,
 | 
			
		||||
		    gcc_unused float total_time)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DecoderCommand
 | 
			
		||||
decoder_get_command(gcc_unused Decoder &decoder)
 | 
			
		||||
{
 | 
			
		||||
	return DecoderCommand::NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_command_finished(gcc_unused Decoder &decoder)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double
 | 
			
		||||
decoder_seek_where(gcc_unused Decoder &decoder)
 | 
			
		||||
{
 | 
			
		||||
	return 1.0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_seek_error(gcc_unused Decoder &decoder)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t
 | 
			
		||||
decoder_read(gcc_unused Decoder *decoder,
 | 
			
		||||
	     InputStream &is,
 | 
			
		||||
	     void *buffer, size_t length)
 | 
			
		||||
{
 | 
			
		||||
	return is.LockRead(buffer, length, IgnoreError());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_timestamp(gcc_unused Decoder &decoder,
 | 
			
		||||
		  gcc_unused double t)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DecoderCommand
 | 
			
		||||
decoder_data(gcc_unused Decoder &decoder,
 | 
			
		||||
	     gcc_unused InputStream *is,
 | 
			
		||||
	     const void *data, size_t datalen,
 | 
			
		||||
	     gcc_unused uint16_t kbit_rate)
 | 
			
		||||
{
 | 
			
		||||
	gcc_unused ssize_t nbytes = write(1, data, datalen);
 | 
			
		||||
	return DecoderCommand::NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DecoderCommand
 | 
			
		||||
decoder_tag(gcc_unused Decoder &decoder,
 | 
			
		||||
	    gcc_unused InputStream *is,
 | 
			
		||||
	    gcc_unused Tag &&tag)
 | 
			
		||||
{
 | 
			
		||||
	return DecoderCommand::NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_replay_gain(gcc_unused Decoder &decoder,
 | 
			
		||||
		    const ReplayGainInfo *rgi)
 | 
			
		||||
{
 | 
			
		||||
	const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM];
 | 
			
		||||
	if (tuple->IsDefined())
 | 
			
		||||
		g_printerr("replay_gain[album]: gain=%f peak=%f\n",
 | 
			
		||||
			   tuple->gain, tuple->peak);
 | 
			
		||||
 | 
			
		||||
	tuple = &rgi->tuples[REPLAY_GAIN_TRACK];
 | 
			
		||||
	if (tuple->IsDefined())
 | 
			
		||||
		g_printerr("replay_gain[track]: gain=%f peak=%f\n",
 | 
			
		||||
			   tuple->gain, tuple->peak);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main(int argc, char **argv)
 | 
			
		||||
{
 | 
			
		||||
	const char *uri;
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "IOThread.hxx"
 | 
			
		||||
#include "DecoderList.hxx"
 | 
			
		||||
#include "DecoderAPI.hxx"
 | 
			
		||||
#include "DecoderPlugin.hxx"
 | 
			
		||||
#include "InputInit.hxx"
 | 
			
		||||
#include "InputStream.hxx"
 | 
			
		||||
#include "AudioFormat.hxx"
 | 
			
		||||
@@ -42,79 +42,6 @@
 | 
			
		||||
#include <locale.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_initialized(gcc_unused Decoder &decoder,
 | 
			
		||||
		    gcc_unused const AudioFormat audio_format,
 | 
			
		||||
		    gcc_unused bool seekable,
 | 
			
		||||
		    gcc_unused float total_time)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DecoderCommand
 | 
			
		||||
decoder_get_command(gcc_unused Decoder &decoder)
 | 
			
		||||
{
 | 
			
		||||
	return DecoderCommand::NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_command_finished(gcc_unused Decoder &decoder)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double
 | 
			
		||||
decoder_seek_where(gcc_unused Decoder &decoder)
 | 
			
		||||
{
 | 
			
		||||
	return 1.0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_seek_error(gcc_unused Decoder &decoder)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t
 | 
			
		||||
decoder_read(gcc_unused Decoder *decoder,
 | 
			
		||||
	     InputStream &is,
 | 
			
		||||
	     void *buffer, size_t length)
 | 
			
		||||
{
 | 
			
		||||
	return is.LockRead(buffer, length, IgnoreError());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_timestamp(gcc_unused Decoder &decoder,
 | 
			
		||||
		  gcc_unused double t)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DecoderCommand
 | 
			
		||||
decoder_data(gcc_unused Decoder &decoder,
 | 
			
		||||
	     gcc_unused InputStream *is,
 | 
			
		||||
	     const void *data, size_t datalen,
 | 
			
		||||
	     gcc_unused uint16_t kbit_rate)
 | 
			
		||||
{
 | 
			
		||||
	gcc_unused ssize_t nbytes = write(1, data, datalen);
 | 
			
		||||
	return DecoderCommand::NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DecoderCommand
 | 
			
		||||
decoder_tag(gcc_unused Decoder &decoder,
 | 
			
		||||
	    gcc_unused InputStream *is,
 | 
			
		||||
	    gcc_unused Tag &&tag)
 | 
			
		||||
{
 | 
			
		||||
	return DecoderCommand::NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_replay_gain(gcc_unused Decoder &decoder,
 | 
			
		||||
		    gcc_unused const ReplayGainInfo *replay_gain_info)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool empty = true;
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
 
 | 
			
		||||
@@ -101,6 +101,40 @@ decoder_read(gcc_unused Decoder *decoder,
 | 
			
		||||
	return is.LockRead(buffer, length, IgnoreError());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
decoder_read_full(Decoder *decoder, InputStream &is,
 | 
			
		||||
		  void *_buffer, size_t size)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t *buffer = (uint8_t *)_buffer;
 | 
			
		||||
 | 
			
		||||
	while (size > 0) {
 | 
			
		||||
		size_t nbytes = decoder_read(decoder, is, buffer, size);
 | 
			
		||||
		if (nbytes == 0)
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
		buffer += nbytes;
 | 
			
		||||
		size -= nbytes;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
decoder_skip(Decoder *decoder, InputStream &is, size_t size)
 | 
			
		||||
{
 | 
			
		||||
	while (size > 0) {
 | 
			
		||||
		char buffer[1024];
 | 
			
		||||
		size_t nbytes = decoder_read(decoder, is, buffer,
 | 
			
		||||
					     std::min(sizeof(buffer), size));
 | 
			
		||||
		if (nbytes == 0)
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
		size -= nbytes;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
decoder_timestamp(gcc_unused Decoder &decoder,
 | 
			
		||||
		  gcc_unused double t)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										85
									
								
								test/test_icy_parser.cxx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								test/test_icy_parser.cxx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Unit tests for class IcyMetaDataParser.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
 | 
			
		||||
/* include the .cxx file to get access to internal functions */
 | 
			
		||||
#include "IcyMetaDataParser.cxx"
 | 
			
		||||
 | 
			
		||||
#include <cppunit/TestFixture.h>
 | 
			
		||||
#include <cppunit/extensions/TestFactoryRegistry.h>
 | 
			
		||||
#include <cppunit/ui/text/TestRunner.h>
 | 
			
		||||
#include <cppunit/extensions/HelperMacros.h>
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
static Tag *
 | 
			
		||||
icy_parse_tag(const char *p)
 | 
			
		||||
{
 | 
			
		||||
	char *q = strdup(p);
 | 
			
		||||
	Tag *tag = icy_parse_tag(q, q + strlen(q));
 | 
			
		||||
	free(q);
 | 
			
		||||
	return tag;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
CompareTagTitle(const Tag &tag, const std::string &title)
 | 
			
		||||
{
 | 
			
		||||
	CPPUNIT_ASSERT_EQUAL(1u, tag.num_items);
 | 
			
		||||
 | 
			
		||||
	const TagItem &item = *tag.items[0];
 | 
			
		||||
	CPPUNIT_ASSERT_EQUAL(TAG_TITLE, item.type);
 | 
			
		||||
	CPPUNIT_ASSERT_EQUAL(title, std::string(item.value));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
TestIcyParserTitle(const char *input, const char *title)
 | 
			
		||||
{
 | 
			
		||||
	Tag *tag = icy_parse_tag(input);
 | 
			
		||||
	CompareTagTitle(*tag, title);
 | 
			
		||||
	delete tag;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
TestIcyParserEmpty(const char *input)
 | 
			
		||||
{
 | 
			
		||||
	Tag *tag = icy_parse_tag(input);
 | 
			
		||||
	CPPUNIT_ASSERT_EQUAL(0u, tag->num_items);
 | 
			
		||||
	delete tag;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class IcyTest : public CppUnit::TestFixture {
 | 
			
		||||
	CPPUNIT_TEST_SUITE(IcyTest);
 | 
			
		||||
	CPPUNIT_TEST(TestIcyMetadataParser);
 | 
			
		||||
	CPPUNIT_TEST_SUITE_END();
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	void TestIcyMetadataParser() {
 | 
			
		||||
		TestIcyParserEmpty("foo=bar;");
 | 
			
		||||
		TestIcyParserTitle("StreamTitle='foo bar'", "foo bar");
 | 
			
		||||
		TestIcyParserTitle("StreamTitle='foo bar';", "foo bar");
 | 
			
		||||
		TestIcyParserTitle("StreamTitle='foo\"bar';", "foo\"bar");
 | 
			
		||||
		TestIcyParserTitle("StreamTitle='foo=bar';", "foo=bar");
 | 
			
		||||
		TestIcyParserTitle("a=b;StreamTitle='foo';", "foo");
 | 
			
		||||
		TestIcyParserTitle("a=;StreamTitle='foo';", "foo");
 | 
			
		||||
		TestIcyParserTitle("a=b;StreamTitle='foo';c=d", "foo");
 | 
			
		||||
		TestIcyParserTitle("a=b;StreamTitle='foo'", "foo");
 | 
			
		||||
		TestIcyParserTitle("a='b;c';StreamTitle='foo;bar'", "foo;bar");
 | 
			
		||||
		TestIcyParserTitle("a='b'c';StreamTitle='foo'bar'", "foo'bar");
 | 
			
		||||
		TestIcyParserTitle("StreamTitle='fo'o'b'ar';a='b'c'd'", "fo'o'b'ar");
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
CPPUNIT_TEST_SUITE_REGISTRATION(IcyTest);
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
main(gcc_unused int argc, gcc_unused char **argv)
 | 
			
		||||
{
 | 
			
		||||
	CppUnit::TextUi::TestRunner runner;
 | 
			
		||||
	auto ®istry = CppUnit::TestFactoryRegistry::getRegistry();
 | 
			
		||||
	runner.addTest(registry.makeTest());
 | 
			
		||||
	return runner.run() ? EXIT_SUCCESS : EXIT_FAILURE;
 | 
			
		||||
}
 | 
			
		||||
@@ -26,7 +26,7 @@ Song::Free()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
check_descending_priority(const struct queue *queue,
 | 
			
		||||
check_descending_priority(const Queue *queue,
 | 
			
		||||
			  unsigned start_order)
 | 
			
		||||
{
 | 
			
		||||
	assert(start_order < queue->GetLength());
 | 
			
		||||
@@ -55,7 +55,7 @@ QueuePriorityTest::TestPriority()
 | 
			
		||||
{
 | 
			
		||||
	static Song songs[16];
 | 
			
		||||
 | 
			
		||||
	struct queue queue(32);
 | 
			
		||||
	Queue queue(32);
 | 
			
		||||
 | 
			
		||||
	for (unsigned i = 0; i < ARRAY_SIZE(songs); ++i)
 | 
			
		||||
		queue.Append(&songs[i], 0);
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,10 @@ public:
 | 
			
		||||
					       "jpg"));
 | 
			
		||||
		CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo.png/bar.jpg"),
 | 
			
		||||
					       "jpg"));
 | 
			
		||||
		CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
 | 
			
		||||
				     uri_get_suffix(".jpg"));
 | 
			
		||||
		CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
 | 
			
		||||
				     uri_get_suffix("/foo/.jpg"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void TestRemoveAuth() {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user