[SCM] forked-daapd/master: Imported Upstream version 24.2
rbalint at users.alioth.debian.org
rbalint at users.alioth.debian.org
Thu Nov 10 20:24:23 UTC 2016
The following commit has been merged in the master branch:
commit 8340f1059acb9c98753fd97bce16688fcc2bfb2c
Author: Balint Reczey <balint at balintreczey.hu>
Date: Thu Nov 10 12:58:41 2016 +0100
Imported Upstream version 24.2
diff --git a/ChangeLog b/ChangeLog
index 5b296ef..2ff4e98 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,21 @@
ChangeLog for forked-daapd
--------------------------
+version 24.2:
+ - Pulseaudio support (can be used for Bluetooth speakers)
+ - new pipe/"fifo" audio output
+ - fix misc Chromecast audio bugs
+ - fix for Apple tvOS10 disconnecting after some minutes
+ - enable CORS headers
+ - LastFM scrobbling during regular DAAP streaming
+ - smart playlist support for file modification time
+ - misc MPD improvements, e.g. new outputsvolume command
+ - ignore ipv6 link-local addresses (announced by e.g. ATV4)
+ - better inter-thread command handling
+ - improved cache performance and use of gzip
+ - fix possible segfault on http timeouts
+ - fix possible segfault when adding items during playback
+
version 24.1:
- support for Monkey's audio
- fix build problems on some platforms (e.g. OpenWrt)
diff --git a/INSTALL b/INSTALL
index 908db20..c0a675f 100644
--- a/INSTALL
+++ b/INSTALL
@@ -26,20 +26,27 @@ Quick version for Debian/Ubuntu users
If you are the lucky kind, this should get you all the required tools and
libraries:
-sudo apt-get install \
- build-essential git autotools-dev autoconf libtool gettext gawk gperf \
- antlr3 libantlr3c-dev libconfuse-dev libunistring-dev libsqlite3-dev \
- libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \
- libasound2-dev libmxml-dev libgcrypt11-dev libavahi-client-dev zlib1g-dev \
- libevent-dev
-
-To build with LastFM support, you should also install libcurl4-openssl-dev.
+ sudo apt-get install \
+ build-essential git autotools-dev autoconf libtool gettext gawk gperf \
+ antlr3 libantlr3c-dev libconfuse-dev libunistring-dev libsqlite3-dev \
+ libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \
+ libasound2-dev libmxml-dev libgcrypt11-dev libavahi-client-dev zlib1g-dev \
+ libevent-dev
+
+Optional packages:
+
+ Feature | Configure argument | Packages
+ -----------|---------------------|---------------------------------------------
+ Chromecast | --enable-chromecast | libjson-c-dev libgnutls-dev libprotobuf-c-dev
+ LastFM | --enable-lastfm | libcurl4-gnutls-dev OR libcurl4-openssl-dev
+ iTunes XML | --enable-itunes | libplist-dev
+ Pulseaudio | --with-pulseaudio | libpulse-dev
Note that while forked-daapd will work with versions of libevent between 2.0.0
and 2.1.3, it is recommended to use 2.1.4+. Otherwise you may not have support
for Shoutcast metadata and simultaneous streaming to multiple clients.
-Then run the following:
+Then run the following (adding configure arguments for optional features):
git clone https://github.com/ejurgensen/forked-daapd.git
cd forked-daapd
@@ -136,8 +143,10 @@ Libraries:
from <http://zlib.net/>
- libunistring 0.9.3+
from <http://www.gnu.org/software/libunistring/#downloading>
- - libasound (optional - local audio)
+ - libasound (optional - ALSA local audio)
often already installed as part of your distro
+ - libpulse (optional - Pulseaudio local audio)
+ from <https://www.freedesktop.org/wiki/Software/PulseAudio/Download/>
- libplist 0.16+ (optional - iTunes XML support)
from <http://github.com/JonathanBeck/libplist/downloads>
- libspotify (optional - Spotify support)
@@ -206,6 +215,8 @@ feature.
Support for Chromecast devices is optional. Use --enable-chromecast to enable
this feature.
+Building with Pulseaudio is optional. Use --with-pulseaudio to enable.
+
Recommended build settings:
./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var
diff --git a/README.md b/README.md
index 8d82903..77689e4 100644
--- a/README.md
+++ b/README.md
@@ -21,10 +21,10 @@ forked-daapd is a complete rewrite of mt-daapd (Firefly Media Server).
- [Using Remote](#using-remote)
- [AirPlay devices/speakers](#airplay-devicesspeakers)
- [Chromecast](#chromecast)
-- [Local audio output](#local-audio-output)
-- [MP3 network streaming (streaming to iOS)](#MP3-network-streaming-(streaming-to-iOS))
+- [Local audio through ALSA](#local-audio-through-alsa)
+- [Local audio, Bluetooth and more through Pulseaudio](#local-audio-bluetooth-and-more-through-pulseaudio)
+- [MP3 network streaming (streaming to iOS)](#mp3-network-streaming-streaming-to-ios)
- [Supported formats](#supported-formats)
-- [Streaming MPEG4](#streaming-mpeg4)
- [Playlists and internet radio](#playlists-and-internet-radio)
- [Artwork](#artwork)
- [Library](#library)
@@ -71,14 +71,15 @@ probably obsolete when you read it :-)
| Client | Developer | Type | Platform | Working (vers.) |
| ------------------------ | ---------- | ------ | ------------- | --------------- |
-| iTunes | Apple | DAAP | Win, OSX | Yes (12.3) |
+| iTunes | Apple | DAAP | Win, OSX | Yes (12.4) |
| Rhythmbox | Gnome | DAAP | Linux | Yes |
+| Diapente | diapente | DAAP | Android | Yes |
| WinAmp DAAPClient | WardFamily | DAAP | WinAmp | Yes |
| Amarok w/DAAP plugin | KDE | DAAP | Linux/Win | Yes (2.8.0) |
| Banshee | | DAAP | Linux/Win/OSX | No (2.6.2) |
| jtunes4 | | DAAP | Java | No |
| Firefly Client | | (DAAP) | Java | No |
-| Remote | Apple | Remote | iOS | Yes (4.2.2) |
+| Remote | Apple | Remote | iOS | Yes (4.3) |
| Retune | SquallyDoc | Remote | Android | Yes (3.5.23) |
| TunesRemote+ | Melloware | Remote | Android | Yes (2.5.3) |
| Remote for iTunes | Hyperfine | Remote | Android | Yes |
@@ -208,20 +209,34 @@ for the syntax.
## Chromecast
-Chromecast support is currently experimental. It requires that forked-daapd was
-built with "--enable-chromecast".
-
forked-daapd will discover Chromecast devices available on your network. There
is no configuration to be done. This feature relies on streaming the audio in
mp3 to your Chromecast device, which means that mp3 encoding must be supported
-by your ffmpeg/libav. See [MP3 network streaming](#MP3-network-streaming-(streaming-to-iOS)).
+by your ffmpeg/libav. See [MP3 network streaming](#mp3-network-streaming-streaming-to-ios).
+
+It is also required that forked-daapd is built with "--enable-chromecast".
+
+
+## Local audio through ALSA
+
+In the config file, you can select ALSA for local audio. This is the default.
+When using ALSA, the server will try to syncronize playback with AirPlay. You
+can adjust the syncronization in the config file.
-## Local audio output
-forked-daapd supports local audio output through ALSA. The server will try to
-syncronize playback with AirPlay. You can adjust the syncronization in the
-config file.
+## Local audio, Bluetooth and more through Pulseaudio
+
+In the config file, you can select Pulseaudio for local audio. In addition to
+local audio, Pulseaudio also supports an array of other targets, e.g. Bluetooth
+or DLNA. However, Pulseaudio does require some setup, so here is a separate page
+with some help on that: [README_PULSE.md](https://github.com/ejurgensen/forked-daapd/blob/master/README_PULSE.md)
+
+Note that if you select Pulseaudio the "card" setting in the config file has
+no effect. Instead all soundcards detected by Pulseaudio will be listed as
+speakers by forked-daapd.
+
+You can adjust the latency of Pulseaudio playback in the config file.
## MP3 network streaming (streaming to iOS)
@@ -260,6 +275,7 @@ added. Currently supported:
- WMA: wma (WMA Pro), wmal (WMA Lossless), wmav (WMA video)
- AIFF: aif
- WAV: wav
+ - Monkey's audio: ape
## Playlists and internet radio
@@ -448,6 +464,9 @@ will not store your password, but will still be able to log you in automatically
afterwards, because libspotify saves a login token. You can configure the
location of your Spotify user data in the configuration file.
+To permanently logout and remove credentials, delete the contents of
+`/var/cache/forked-daapd/libspotify` (while forked-daapd is stopped).
+
Limitations: You will only be able to play tracks from your Spotify playlists,
so you can't search and listen to music from the rest of the Spotify catalogue.
You will not be able to do any playlist management through forked-daapd - use
diff --git a/README_PULSE.md b/README_PULSE.md
new file mode 100644
index 0000000..24f9c6d
--- /dev/null
+++ b/README_PULSE.md
@@ -0,0 +1,92 @@
+# forked-daapd and Pulseaudio
+Credit: [Rob Pope](http://robpope.co.uk/blog/post/setting-up-forked-daapd-with-bluetooth)
+
+This guide was written based on headless Debian Jessie platforms. Most of the
+instructions will require that you are root.
+
+
+## Step 1: Setting up Pulseaudio in system mode with Bluetooth support
+
+If you see a "Connection refused" error when starting forked-daapd, then you
+will probably need to setup Pulseaudio to run in system mode [1]. This means
+that the Pulseaudio daemon will be started during boot and be available to all
+users.
+
+How to start Pulseaudio depends on your distribution, but in many cases you will
+need to add a pulseaudio.service file to /etc/systemd/system with the following
+content:
+
+```
+# systemd service file for Pulseaudio running in system mode
+[Unit]
+Description=Pulseaudio sound server
+Before=sound.target
+
+[Service]
+ExecStart=/usr/bin/pulseaudio --system --disallow-exit
+
+[Install]
+WantedBy=multi-user.target
+```
+
+If you want Bluetooth support, you must also configure Pulseaudio to load the
+Bluetooth module. First install it (Debian:
+`apt install pulseaudio-module-bluetooth`) and then add the following to
+/etc/pulse/system.pa:
+
+```
+### Enable Bluetooth
+.ifexists module-bluetooth-discover.so
+load-module module-bluetooth-discover
+.endif
+```
+
+Now you need to make sure that Pulseaudio can communicate with the Bluetooth
+daemon through D-Bus. On Raspbian this is already enabled, and you can skip this
+step. Otherwise do one of the following:
+
+1. Add the pulse user to the bluetooth group: `adduser pulse bluetooth`
+2. Edit /etc/dbus-1/system.d/bluetooth.conf and change the policy for
+\<policy context="default"\> to "allow"
+
+Phew, almost done with Pulseaudio! Now you should:
+1. enable system mode on boot with `systemctl enable pulseaudio`
+2. reboot (or at least restart dbus and pulseaudio)
+3. check that the Bluetooth module is loaded with `pactl list modules short`
+
+
+## Step 2: Setting up forked-daapd
+
+Add the user forked-daapd is running as (typically "daapd") to the
+"pulse-access" group:
+
+```
+adduser daapd pulse-access
+```
+
+Now (re)start forked-daapd.
+
+
+## Step 3: Adding a Bluetooth device
+
+To connect with the device, run `bluetoothctl` and then:
+
+```
+power on
+agent on
+scan on
+**Note MAC address of BT Speaker**
+pair [MAC address]
+**Type Pin if prompted**
+trust [MAC address]
+connect [MAC address]
+```
+
+Now the speaker should appear in forked-daapd. You can also verify that
+Pulseaudio has detected the speaker with `pactl list sinks short`.
+
+---
+
+[1] Note that Pulseaudio will warn against system mode. However, in this use
+case it is actually the solution recommended by the [Pulseaudio folks themselves](https://lists.freedesktop.org/archives/pulseaudio-discuss/2016-August/026823.html).
+
diff --git a/README_SMARTPL.md b/README_SMARTPL.md
index 541194a..035a8df 100644
--- a/README_SMARTPL.md
+++ b/README_SMARTPL.md
@@ -40,6 +40,7 @@ Where valid field-names (with there types) are:
* year (integer)
* compilation (integer)
* time_added (date)
+* time_modified (date)
* time_played (date)
Valid operators include:
diff --git a/configure.ac b/configure.ac
index 2aa02f9..3657803 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,9 +1,10 @@
dnl Process this file with autoconf to produce a configure script.
-AC_INIT([forked-daapd], [24.1])
+AC_INIT([forked-daapd], [24.2])
AC_CONFIG_SRCDIR([config.h.in])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS([config.h])
+AC_DEFINE_UNQUOTED([BUILDDATE], ["`date -Idate`"], [Build date])
AM_INIT_AUTOMAKE([foreign -Wno-portability subdir-objects])
AC_USE_SYSTEM_EXTENSIONS
@@ -159,17 +160,17 @@ AC_CHECK_HEADERS(stdint.h,,)
dnl --- Begin configuring the options ---
dnl iTunes playlists with libplist
-AC_ARG_ENABLE(itunes, AS_HELP_STRING([--enable-itunes], [enable iTunes library support (default=no)]))
+AC_ARG_ENABLE(itunes, AS_HELP_STRING([--enable-itunes], [enable iTunes Music Library XML support (default=no)]))
AS_IF([test "x$enable_itunes" = "xyes"], [
- CPPFLAGS="${CPPFLAGS} -DITUNES"
+ AC_DEFINE(ITUNES, 1, [Define to 1 to enable iTunes XML support])
PKG_CHECK_MODULES(LIBPLIST, [ libplist >= 0.16 ])
])
AM_CONDITIONAL(COND_ITUNES, [test "x$enable_itunes" = "xyes"])
dnl Spotify with dynamic linking to libspotify
-AC_ARG_ENABLE(spotify, AS_HELP_STRING([--enable-spotify], [enable Spotify library support (default=no)]))
+AC_ARG_ENABLE(spotify, AS_HELP_STRING([--enable-spotify], [enable Spotify support (default=no)]))
AS_IF([test "x$enable_spotify" = "xyes"], [
- CPPFLAGS="${CPPFLAGS} -DSPOTIFY"
+ AC_DEFINE(SPOTIFY, 1, [Define to 1 to enable Spotify support])
AC_CHECK_HEADER(libspotify/api.h, , AC_MSG_ERROR([libspotify/api.h not found]))
AC_DEFINE(HAVE_SPOTIFY_H, 1, [Define to 1 if you have the <libspotify/api.h> header file.])
@@ -184,7 +185,7 @@ AM_CONDITIONAL(COND_SPOTIFY, [test "x$enable_spotify" = "xyes"])
dnl LastFM support with libcurl
AC_ARG_ENABLE(lastfm, AS_HELP_STRING([--enable-lastfm], [enable LastFM support (default=no)]))
AS_IF([test "x$enable_lastfm" = "xyes"], [
- CPPFLAGS="${CPPFLAGS} -DLASTFM"
+ AC_DEFINE(LASTFM, 1, [Define to 1 to enable LastFM support])
PKG_CHECK_MODULES(LIBCURL, [ libcurl ])
AC_CHECK_LIB([mxml], [mxmlGetOpaque], AC_DEFINE(HAVE_MXML_GETOPAQUE, 1, [Define to 1 if your mxml has mxmlGetOpaque.]))
])
@@ -211,32 +212,29 @@ AM_CONDITIONAL(COND_PROTOBUF_OLD, [test "x$protobuf_old" = "xyes"])
dnl MPD support
AC_ARG_ENABLE(mpd, AS_HELP_STRING([--disable-mpd], [disable MPD client protocol support (default=no)]))
AS_IF([test "x$enable_mpd" != "xno"], [
- CPPFLAGS="${CPPFLAGS} -DMPD"
+ AC_DEFINE(MPD, 1, [Define to 1 to enable MPD support])
])
AM_CONDITIONAL(COND_MPD, [test "x$enable_mpd" != "xno"])
-dnl --- End options ---
-dnl Selection of local audio sound system
-dnl TODO exchange oss4 with Pulseaudio
-case "$host" in
- *-*-linux-*)
- use_alsa=true
- use_oss4=false
- ;;
- *-*-kfreebsd*-*|*-*-freebsd*)
- use_alsa=true
- use_oss4=false
- ;;
-esac
-
-AC_ARG_WITH(alsa, AS_HELP_STRING([--with-alsa], [use ALSA (default yes)]), [
- AS_IF([test "x$with_alsa" = "xyes"], [use_alsa=true], [use_alsa=false])
-])
-if test x$use_alsa = xtrue; then
- CPPFLAGS="${CPPFLAGS} -DALSA"
+dnl ALSA
+AC_ARG_WITH(alsa, AS_HELP_STRING([--without-alsa], [without ALSA support (default=no)]))
+AS_IF([test "x$with_alsa" != "xno"], [
+ AC_DEFINE(ALSA, 1, [Define to 1 to build with ALSA support])
PKG_CHECK_MODULES(ALSA, [ alsa ])
-fi
-AM_CONDITIONAL(COND_ALSA, test x$use_alsa = xtrue)
+])
+AM_CONDITIONAL(COND_ALSA, [test "x$with_alsa" != "xno"])
+
+dnl PULSEAUDIO
+AC_ARG_WITH(pulseaudio, AS_HELP_STRING([--with-pulseaudio], [with Pulseaudio support (default=no)]))
+AS_IF([test "x$with_pulseaudio" = "xyes"], [
+ AC_DEFINE(PULSEAUDIO, 1, [Define to 1 to build with Pulseaudio support])
+ PKG_CHECK_MODULES(LIBPULSE, [ libpulse ])
+ AC_SEARCH_LIBS([pa_threaded_mainloop_set_name], [pulse],
+ AC_DEFINE(HAVE_PULSE_MAINLOOP_SET_NAME, 1, [Define to 1 if you have Pulseaudio with pa_threaded_mainloop_set_name])
+ )
+])
+AM_CONDITIONAL(COND_PULSEAUDIO, [test "x$with_pulseaudio" = "xyes"])
+dnl --- End options ---
dnl Checks for header files.
AC_HEADER_STDC
diff --git a/forked-daapd.conf b/forked-daapd.conf
index a80b945..2f23e69 100644
--- a/forked-daapd.conf
+++ b/forked-daapd.conf
@@ -33,6 +33,10 @@ general {
# DAAP requests that take longer than this threshold (in msec) get their
# replies cached for next time. Set to 0 to disable caching.
# cache_daap_threshold = 1000
+
+ # When starting playback, autoselect speaker (if none of the previously
+ # selected speakers/outputs are available)
+# speaker_autoselect = yes
}
# Library configuration
@@ -134,6 +138,9 @@ library {
# Should iTunes metadata override ours?
# itunes_overrides = false
+ # Should we import the content of iTunes smart playlists?
+# itunes_smartpl = false
+
# Decoding options for DAAP clients
# Since iTunes has native support for mpeg, mp4a, mp4v, alac and wav,
# such files will be sent as they are. Any other formats will be decoded
@@ -153,16 +160,21 @@ audio {
# Name - used in the speaker list in Remote
nickname = "Computer"
- # Type of the output (alsa or dummy)
+ # Type of the output (alsa, pulseaudio or dummy)
# type = "alsa"
- # Audio device name for local audio output
+ # Audio PCM device name for local audio output - ALSA only
# card = "default"
- # Mixer channel to use for volume control - ALSA/Linux only
+ # Mixer channel to use for volume control - ALSA only
# If not set, PCM will be used if available, otherwise Master.
# mixer = ""
+ # Mixer device to use for volume control - ALSA only
+ # If not set, the value for "card" will be used.
+# mixer_device = ""
+
+ # Syncronization
# If your local audio is out of sync with AirPlay, you can adjust this
# value. Positive values correspond to moving local audio ahead,
# negative correspond to delaying it. The unit is samples, where is
@@ -170,6 +182,13 @@ audio {
# offset = 0
}
+# Pipe output
+# Allows forked-daapd to output audio data to a named pipe
+#fifo {
+# nickname = "fifo"
+# path = "/path/to/fifo"
+#}
+
# AirPlay/Airport Express device settings
# (make sure you get the capitalization of the device name right)
#airplay "My AirPlay device" {
@@ -198,18 +217,19 @@ spotify {
# playlists you can set this option to true.
# base_playlist_disable = false
- # Spotify playlists usually have many artist, and if you don't want every
- # artist to be listed when artist browsing in Remote, you can set the
- # artist_override flag to true. This will use the compilation_artist as
- # album artist for spotify items that are not in the starred playlist.
+ # Spotify playlists usually have many artist, and if you don't want
+ # every artist to be listed when artist browsing in Remote, you can set
+ # the artist_override flag to true. This will use the compilation_artist
+ # as album artist for Spotify items.
# artist_override = false
- # Similar to the different artists in spotify playlists, the playlist items
- # belong to different albums, and if you do not want every album to be listed
- # when browsing in Remote, you can set the album_override flag to true. This
- # will use the playlist name as album name for spotify items that are not in
- # the starred playlist. Notice that if an item is in more than one playlist,
- # it will only appear in one album when browsing (in which album is random).
+ # Similar to the different artists in Spotify playlists, the playlist
+ # items belong to different albums, and if you do not want every album
+ # to be listed when browsing in Remote, you can set the album_override
+ # flag to true. This will use the playlist name as album name for
+ # Spotify items. Notice that if an item is in more than one playlist,
+ # it will only appear in one album when browsing (in which album is
+ # random).
# album_override = false
}
@@ -219,21 +239,22 @@ mpd {
# Default port is 6600, set to 0 to disable MPD support.
# port = 6600
- # HTTP port to listen for artwork requests (only supported by some MPD clients
- # and will need additional configuration in the MPD client to work).
- # Set to 0 to disable serving artwork over http.
+ # HTTP port to listen for artwork requests (only supported by some MPD
+ # clients and will need additional configuration in the MPD client to
+ # work). Set to 0 to disable serving artwork over http.
# http_port = 0
- # By default forked-daapd will - like iTunes - clear the playqueue if playback stops.
- # Setting clear_queue_on_stop_disable to true will keep the playlist like MPD does.
- # Note that some dacp clients do not show the playqueue if playback is stopped.
+ # By default forked-daapd will - like iTunes - clear the playqueue if
+ # playback stops. Setting clear_queue_on_stop_disable to true will keep
+ # the playlist like MPD does. Note that some dacp clients do not show
+ # the playqueue if playback is stopped.
# clear_queue_on_stop_disable = false
}
# SQLite configuration (allows to modify the operation of the SQLite databases)
-# Make sure to read the SQLite documentation for the corresponding PRAGMA statements as
-# changing them from the defaults may increase the possibility of database corruptions!
-# By default the SQLite default values are used.
+# Make sure to read the SQLite documentation for the corresponding PRAGMA
+# statements as changing them from the defaults may increase the possibility of
+# database corruptions! By default the SQLite default values are used.
sqlite {
# Cache size in number of db pages for the library database
# (SQLite default page size is 1024 bytes and cache size is 2000 pages)
diff --git a/forked-daapd.service b/forked-daapd.service
index 9ffac80..d2bccbc 100755
--- a/forked-daapd.service
+++ b/forked-daapd.service
@@ -1,6 +1,6 @@
[Unit]
Description=DAAP/DACP (iTunes), RSP and MPD server, supports AirPlay and Remote
-After=network.target
+After=network.target sound.target
[Service]
ExecStart=/usr/sbin/forked-daapd -f
diff --git a/scripts/freebsd_install_10.1.sh b/scripts/freebsd_install_10.1.sh
index a4bce20..c097fbb 100755
--- a/scripts/freebsd_install_10.1.sh
+++ b/scripts/freebsd_install_10.1.sh
@@ -11,7 +11,7 @@ fi
DEPS="gmake autoconf automake libtool gettext gperf glib pkgconf wget git \
ffmpeg libconfuse libevent2 mxml libgcrypt libunistring libiconv \
- libplist libinotify avahi sqlite3"
+ libplist libinotify avahi sqlite3 alsa-lib"
echo "The script can install the following dependency packages for you:"
echo $DEPS
read -p "Should the script install these packages? [y/N] " yn
diff --git a/src/Makefile.am b/src/Makefile.am
index 32fe601..06d46b4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -29,6 +29,10 @@ if COND_ALSA
ALSA_SRC=outputs/alsa.c
endif
+if COND_PULSEAUDIO
+PULSEAUDIO_SRC=outputs/pulse.c
+endif
+
GPERF_FILES = \
daap_query.gperf \
rsp_query.gperf \
@@ -62,19 +66,20 @@ forked_daapd_CPPFLAGS = -D_GNU_SOURCE \
forked_daapd_CFLAGS = \
@ZLIB_CFLAGS@ @AVAHI_CFLAGS@ @SQLITE3_CFLAGS@ @LIBAV_CFLAGS@ \
- @CONFUSE_CFLAGS@ @MINIXML_CFLAGS@ @LIBPLIST_CFLAGS@ \
- @LIBGCRYPT_CFLAGS@ @GPG_ERROR_CFLAGS@ @ALSA_CFLAGS@ @SPOTIFY_CFLAGS@ \
+ @CONFUSE_CFLAGS@ @MINIXML_CFLAGS@ @LIBPLIST_CFLAGS@ @SPOTIFY_CFLAGS@ \
+ @LIBGCRYPT_CFLAGS@ @GPG_ERROR_CFLAGS@ @ALSA_CFLAGS@ @LIBPULSE_CFLAGS@ \
@LIBCURL_CFLAGS@ @LIBPROTOBUF_C_CFLAGS@ @GNUTLS_CFLAGS@ @JSON_C_CFLAGS@
forked_daapd_LDADD = -lrt \
@ZLIB_LIBS@ @AVAHI_LIBS@ @SQLITE3_LIBS@ @LIBAV_LIBS@ \
- @CONFUSE_LIBS@ @LIBEVENT_LIBS@ \
- @MINIXML_LIBS@ @ANTLR3C_LIBS@ @LIBPLIST_LIBS@ \
- @LIBGCRYPT_LIBS@ @GPG_ERROR_LIBS@ @ALSA_LIBS@ @LIBUNISTRING@ @SPOTIFY_LIBS@ \
+ @CONFUSE_LIBS@ @LIBEVENT_LIBS@ @LIBUNISTRING@ \
+ @MINIXML_LIBS@ @ANTLR3C_LIBS@ @LIBPLIST_LIBS@ @SPOTIFY_LIBS@ \
+ @LIBGCRYPT_LIBS@ @GPG_ERROR_LIBS@ @ALSA_LIBS@ @LIBPULSE_LIBS@ \
@LIBCURL_LIBS@ @LIBPROTOBUF_C_LIBS@ @GNUTLS_LIBS@ @JSON_C_LIBS@
forked_daapd_SOURCES = main.c \
db.c db.h \
+ db_init.c db_init.h \
db_upgrade.c db_upgrade.h \
logger.c logger.h \
conffile.c conffile.h \
@@ -103,13 +108,14 @@ forked_daapd_SOURCES = main.c \
queue.c queue.h \
worker.c worker.h \
outputs.h outputs.c \
- outputs/raop.c outputs/streaming.c outputs/dummy.c \
- $(ALSA_SRC) $(CHROMECAST_SRC) \
+ outputs/raop.c outputs/streaming.c outputs/dummy.c outputs/fifo.c \
+ $(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \
evrtsp/rtsp.c evrtp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \
$(SPOTIFY_SRC) \
$(LASTFM_SRC) \
$(MPD_SRC) \
- listener.c listener.h
+ listener.c listener.h \
+ commands.c commands.h
nodist_forked_daapd_SOURCES = \
$(ANTLR_SOURCES)
diff --git a/src/SMARTPL.g b/src/SMARTPL.g
index 65b457a..1bbd179 100644
--- a/src/SMARTPL.g
+++ b/src/SMARTPL.g
@@ -71,6 +71,7 @@ INTTAG : 'play_count'
;
DATETAG : 'time_added'
+ | 'time_modified'
| 'time_played'
;
diff --git a/src/cache.c b/src/cache.c
index 79e2244..a42ce01 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -38,45 +38,33 @@
#include "conffile.h"
#include "logger.h"
+#include "httpd.h"
#include "httpd_daap.h"
#include "db.h"
#include "cache.h"
+#include "commands.h"
#define CACHE_VERSION 2
-struct cache_command;
-typedef int (*cmd_func)(struct cache_command *cmd);
-
-struct cache_command
+struct cache_arg
{
- pthread_mutex_t lck;
- pthread_cond_t cond;
-
- cmd_func func;
-
- int nonblock;
-
- struct {
- char *query; // daap query
- char *ua; // user agent
- int msec;
-
- char *path; // artwork path
- int type; // individual or group artwork
- int64_t persistentid;
- int max_w;
- int max_h;
- int format;
- time_t mtime;
- int cached;
- int del;
-
- struct evbuffer *evbuf;
- } arg;
+ char *query; // daap query
+ char *ua; // user agent
+ int msec;
+
+ char *path; // artwork path
+ int type; // individual or group artwork
+ int64_t persistentid;
+ int max_w;
+ int max_h;
+ int format;
+ time_t mtime;
+ int cached;
+ int del;
- int ret;
+ struct evbuffer *evbuf;
};
/* --- Globals --- */
@@ -85,11 +73,8 @@ static pthread_t tid_cache;
// Event base, pipes and events
struct event_base *evbase_cache;
-static int g_exit_pipe[2];
-static int g_cmd_pipe[2];
-static struct event *g_exitev;
-static struct event *g_cmdev;
static struct event *g_cacheev;
+static struct commands_base *cmdbase;
static int g_initialized;
@@ -136,90 +121,6 @@ remove_tag(char *in, const char *tag)
*(s - 1) = '\0';
}
-/* ---------------------------- COMMAND EXECUTION -------------------------- */
-
-static void
-command_init(struct cache_command *cmd)
-{
- memset(cmd, 0, sizeof(struct cache_command));
-
- pthread_mutex_init(&cmd->lck, NULL);
- pthread_cond_init(&cmd->cond, NULL);
-}
-
-static void
-command_deinit(struct cache_command *cmd)
-{
- pthread_cond_destroy(&cmd->cond);
- pthread_mutex_destroy(&cmd->lck);
-}
-
-static int
-send_command(struct cache_command *cmd)
-{
- int ret;
-
- if (!cmd->func)
- {
- DPRINTF(E_LOG, L_CACHE, "BUG: cmd->func is NULL!\n");
- return -1;
- }
-
- ret = write(g_cmd_pipe[1], &cmd, sizeof(cmd));
- if (ret != sizeof(cmd))
- {
- DPRINTF(E_LOG, L_CACHE, "Could not send command: %s\n", strerror(errno));
- return -1;
- }
-
- return 0;
-}
-
-static int
-sync_command(struct cache_command *cmd)
-{
- int ret;
-
- pthread_mutex_lock(&cmd->lck);
-
- ret = send_command(cmd);
- if (ret < 0)
- {
- pthread_mutex_unlock(&cmd->lck);
- return -1;
- }
-
- pthread_cond_wait(&cmd->cond, &cmd->lck);
- pthread_mutex_unlock(&cmd->lck);
-
- ret = cmd->ret;
-
- return ret;
-}
-
-static int
-nonblock_command(struct cache_command *cmd)
-{
- int ret;
-
- ret = send_command(cmd);
- if (ret < 0)
- return -1;
-
- return 0;
-}
-
-static void
-thread_exit(void)
-{
- int dummy = 42;
-
- DPRINTF(E_DBG, L_CACHE, "Killing cache thread\n");
-
- if (write(g_exit_pipe[1], &dummy, sizeof(dummy)) != sizeof(dummy))
- DPRINTF(E_LOG, L_CACHE, "Could not write to exit fd: %s\n", strerror(errno));
-}
-
/* --------------------------------- MAIN --------------------------------- */
/* Thread: cache */
@@ -701,16 +602,18 @@ cache_daap_reply_add(const char *query, struct evbuffer *evbuf)
}
/* Adds the query to the list of queries for which we will build and cache a reply */
-static int
-cache_daap_query_add(struct cache_command *cmd)
+static enum command_state
+cache_daap_query_add(void *arg, int *retval)
{
#define Q_TMPL "INSERT OR REPLACE INTO queries (user_agent, query, msec, timestamp) VALUES ('%q', '%q', %d, %" PRIi64 ");"
#define Q_CLEANUP "DELETE FROM queries WHERE id NOT IN (SELECT id FROM queries ORDER BY timestamp DESC LIMIT 20);"
+ struct cache_arg *cmdarg;
char *query;
char *errmsg;
int ret;
- if (!cmd->arg.ua)
+ cmdarg = arg;
+ if (!cmdarg->ua)
{
DPRINTF(E_LOG, L_CACHE, "Couldn't add slow query to cache, unknown user-agent\n");
@@ -718,16 +621,16 @@ cache_daap_query_add(struct cache_command *cmd)
}
// Currently we are only able to pre-build and cache these reply types
- if ( (strncmp(cmd->arg.query, "/databases/1/containers/", strlen("/databases/1/containers/")) != 0) &&
- (strncmp(cmd->arg.query, "/databases/1/groups?", strlen("/databases/1/groups?")) != 0) &&
- (strncmp(cmd->arg.query, "/databases/1/items?", strlen("/databases/1/items?")) != 0) &&
- (strncmp(cmd->arg.query, "/databases/1/browse/", strlen("/databases/1/browse/")) != 0) )
+ if ( (strncmp(cmdarg->query, "/databases/1/containers/", strlen("/databases/1/containers/")) != 0) &&
+ (strncmp(cmdarg->query, "/databases/1/groups?", strlen("/databases/1/groups?")) != 0) &&
+ (strncmp(cmdarg->query, "/databases/1/items?", strlen("/databases/1/items?")) != 0) &&
+ (strncmp(cmdarg->query, "/databases/1/browse/", strlen("/databases/1/browse/")) != 0) )
goto error_add;
- remove_tag(cmd->arg.query, "session-id");
- remove_tag(cmd->arg.query, "revision-number");
+ remove_tag(cmdarg->query, "session-id");
+ remove_tag(cmdarg->query, "revision-number");
- query = sqlite3_mprintf(Q_TMPL, cmd->arg.ua, cmd->arg.query, cmd->arg.msec, (int64_t)time(NULL));
+ query = sqlite3_mprintf(Q_TMPL, cmdarg->ua, cmdarg->query, cmdarg->msec, (int64_t)time(NULL));
if (!query)
{
DPRINTF(E_LOG, L_CACHE, "Out of memory making query string.\n");
@@ -745,10 +648,10 @@ cache_daap_query_add(struct cache_command *cmd)
goto error_add;
}
- DPRINTF(E_INFO, L_CACHE, "Slow query (%d ms) added to cache: '%s' (user-agent: '%s')\n", cmd->arg.msec, cmd->arg.query, cmd->arg.ua);
+ DPRINTF(E_INFO, L_CACHE, "Slow query (%d ms) added to cache: '%s' (user-agent: '%s')\n", cmdarg->msec, cmdarg->query, cmdarg->ua);
- free(cmd->arg.ua);
- free(cmd->arg.query);
+ free(cmdarg->ua);
+ free(cmdarg->query);
// Limits the size of the cache to only contain replies for 20 most recent queries
ret = sqlite3_exec(g_db_hdl, Q_CLEANUP, NULL, NULL, &errmsg);
@@ -756,36 +659,42 @@ cache_daap_query_add(struct cache_command *cmd)
{
DPRINTF(E_LOG, L_CACHE, "Error cleaning up query list before update: %s\n", errmsg);
sqlite3_free(errmsg);
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
cache_daap_trigger();
- return 0;
+ *retval = 0;
+ return COMMAND_END;
error_add:
- if (cmd->arg.ua)
- free(cmd->arg.ua);
+ if (cmdarg->ua)
+ free(cmdarg->ua);
- if (cmd->arg.query)
- free(cmd->arg.query);
+ if (cmdarg->query)
+ free(cmdarg->query);
- return -1;
+ *retval = -1;
+ return COMMAND_END;
#undef Q_CLEANUP
#undef Q_TMPL
}
-/* Gets a reply from the cache */
-static int
-cache_daap_query_get(struct cache_command *cmd)
+// Gets a reply from the cache.
+// cmdarg->evbuf will be filled with the reply (gzipped)
+static enum command_state
+cache_daap_query_get(void *arg, int *retval)
{
#define Q_TMPL "SELECT reply FROM replies WHERE query = ?;"
+ struct cache_arg *cmdarg;
sqlite3_stmt *stmt;
char *query;
int datalen;
int ret;
- query = cmd->arg.query;
+ cmdarg = arg;
+ query = cmdarg->query;
remove_tag(query, "session-id");
remove_tag(query, "revision-number");
@@ -795,7 +704,8 @@ cache_daap_query_get(struct cache_command *cmd)
{
DPRINTF(E_LOG, L_CACHE, "Error preparing query for cache update: %s\n", sqlite3_errmsg(g_db_hdl));
free(query);
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
sqlite3_bind_text(stmt, 1, query, -1, SQLITE_STATIC);
@@ -810,13 +720,13 @@ cache_daap_query_get(struct cache_command *cmd)
datalen = sqlite3_column_bytes(stmt, 0);
- if (!cmd->arg.evbuf)
+ if (!cmdarg->evbuf)
{
DPRINTF(E_LOG, L_CACHE, "Error: DAAP reply evbuffer is NULL\n");
goto error_get;
}
- ret = evbuffer_add(cmd->arg.evbuf, sqlite3_column_blob(stmt, 0), datalen);
+ ret = evbuffer_add(cmdarg->evbuf, sqlite3_column_blob(stmt, 0), datalen);
if (ret < 0)
{
DPRINTF(E_LOG, L_CACHE, "Out of memory for DAAP reply evbuffer\n");
@@ -831,12 +741,14 @@ cache_daap_query_get(struct cache_command *cmd)
free(query);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
error_get:
sqlite3_finalize(stmt);
free(query);
- return -1;
+ *retval = -1;
+ return COMMAND_END;
#undef Q_TMPL
}
@@ -873,6 +785,7 @@ cache_daap_update_cb(int fd, short what, void *arg)
{
sqlite3_stmt *stmt;
struct evbuffer *evbuf;
+ struct evbuffer *gzbuf;
char *errmsg;
char *query;
int ret;
@@ -908,10 +821,23 @@ cache_daap_update_cb(int fd, short what, void *arg)
continue;
}
- cache_daap_reply_add(query, evbuf);
+ gzbuf = httpd_gzip_deflate(evbuf);
+ if (!gzbuf)
+ {
+ DPRINTF(E_LOG, L_CACHE, "Error gzipping DAAP reply for query: %s\n", query);
+ cache_daap_query_delete(sqlite3_column_int(stmt, 0));
+ free(query);
+ evbuffer_free(evbuf);
+
+ continue;
+ }
- free(query);
evbuffer_free(evbuf);
+
+ cache_daap_reply_add(query, gzbuf);
+
+ free(query);
+ evbuffer_free(gzbuf);
}
if (ret != SQLITE_DONE)
@@ -926,40 +852,55 @@ cache_daap_update_cb(int fd, short what, void *arg)
* the actual cache update. The purpose is to avoid avoid cache updates when
* the database is busy, eg during a library scan.
*/
-static int
-cache_daap_update_timer(struct cache_command *cmd)
+static enum command_state
+cache_daap_update_timer(void *arg, int *ret)
{
if (!g_cacheev)
- return -1;
+ {
+ *ret = -1;
+ return COMMAND_END;
+ }
evtimer_add(g_cacheev, &g_wait);
- return 0;
+ *ret = 0;
+
+ return COMMAND_END;
}
-static int
-cache_daap_suspend_timer(struct cache_command *cmd)
+static enum command_state
+cache_daap_suspend_timer(void *arg, int *ret)
{
if (!g_cacheev)
- return -1;
+ {
+ *ret = -1;
+ return COMMAND_END;
+ }
g_suspended = evtimer_pending(g_cacheev, NULL);
if (g_suspended)
evtimer_del(g_cacheev);
- return 0;
+ *ret = 0;
+
+ return COMMAND_END;
}
-static int
-cache_daap_resume_timer(struct cache_command *cmd)
+static enum command_state
+cache_daap_resume_timer(void *arg, int *ret)
{
if (!g_cacheev)
- return -1;
+ {
+ *ret = -1;
+ return COMMAND_END;
+ }
if (g_suspended)
evtimer_add(g_cacheev, &g_wait);
- return 0;
+ *ret = 0;
+
+ return COMMAND_END;
}
/*
@@ -967,21 +908,23 @@ cache_daap_resume_timer(struct cache_command *cmd)
* after the cached timestamp. All cache entries for the given path are deleted, if the file was
* modified after the cached timestamp.
*
- * @param cmd->arg.path the full path to the artwork file (could be an jpg/png image or a media file with embedded artwork)
- * @param cmd->arg.mtime modified timestamp of the artwork file
+ * @param cmdarg->path the full path to the artwork file (could be an jpg/png image or a media file with embedded artwork)
+ * @param cmdarg->mtime modified timestamp of the artwork file
* @return 0 if successful, -1 if an error occurred
*/
-static int
-cache_artwork_ping_impl(struct cache_command *cmd)
+static enum command_state
+cache_artwork_ping_impl(void *arg, int *retval)
{
#define Q_TMPL_PING "UPDATE artwork SET db_timestamp = %" PRIi64 " WHERE filepath = '%q' AND db_timestamp >= %" PRIi64 ";"
#define Q_TMPL_DEL "DELETE FROM artwork WHERE filepath = '%q' AND db_timestamp < %" PRIi64 ";"
+ struct cache_arg *cmdarg;
char *query;
char *errmsg;
int ret;
- query = sqlite3_mprintf(Q_TMPL_PING, (int64_t)time(NULL), cmd->arg.path, (int64_t)cmd->arg.mtime);
+ cmdarg = arg;
+ query = sqlite3_mprintf(Q_TMPL_PING, (int64_t)time(NULL), cmdarg->path, (int64_t)cmdarg->mtime);
DPRINTF(E_DBG, L_CACHE, "Running query '%s'\n", query);
@@ -994,9 +937,9 @@ cache_artwork_ping_impl(struct cache_command *cmd)
goto error_ping;
}
- if (cmd->arg.del > 0)
+ if (cmdarg->del > 0)
{
- query = sqlite3_mprintf(Q_TMPL_DEL, cmd->arg.path, (int64_t)cmd->arg.mtime);
+ query = sqlite3_mprintf(Q_TMPL_DEL, cmdarg->path, (int64_t)cmdarg->mtime);
DPRINTF(E_DBG, L_CACHE, "Running query '%s'\n", query);
@@ -1010,15 +953,17 @@ cache_artwork_ping_impl(struct cache_command *cmd)
}
}
- free(cmd->arg.path);
+ free(cmdarg->path);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
error_ping:
sqlite3_free(errmsg);
- free(cmd->arg.path);
+ free(cmdarg->path);
- return -1;
+ *retval = -1;
+ return COMMAND_END;
#undef Q_TMPL_PING
#undef Q_TMPL_DEL
@@ -1027,19 +972,21 @@ cache_artwork_ping_impl(struct cache_command *cmd)
/*
* Removes all cache entries for the given path
*
- * @param cmd->arg.path the full path to the artwork file (could be an jpg/png image or a media file with embedded artwork)
+ * @param cmdarg->path the full path to the artwork file (could be an jpg/png image or a media file with embedded artwork)
* @return 0 if successful, -1 if an error occurred
*/
-static int
-cache_artwork_delete_by_path_impl(struct cache_command *cmd)
+static enum command_state
+cache_artwork_delete_by_path_impl(void *arg, int *retval)
{
#define Q_TMPL_DEL "DELETE FROM artwork WHERE filepath = '%q';"
+ struct cache_arg *cmdarg;
char *query;
char *errmsg;
int ret;
- query = sqlite3_mprintf(Q_TMPL_DEL, cmd->arg.path);
+ cmdarg = arg;
+ query = sqlite3_mprintf(Q_TMPL_DEL, cmdarg->path);
DPRINTF(E_DBG, L_CACHE, "Running query '%s'\n", query);
@@ -1050,12 +997,14 @@ cache_artwork_delete_by_path_impl(struct cache_command *cmd)
DPRINTF(E_LOG, L_CACHE, "Query error: %s\n", errmsg);
sqlite3_free(errmsg);
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
DPRINTF(E_DBG, L_CACHE, "Deleted %d rows\n", sqlite3_changes(g_db_hdl));
- return 0;
+ *retval = 0;
+ return COMMAND_END;
#undef Q_TMPL_DEL
}
@@ -1063,19 +1012,21 @@ cache_artwork_delete_by_path_impl(struct cache_command *cmd)
/*
* Removes all cache entries with cached timestamp older than the given reference timestamp
*
- * @param cmd->arg.mtime reference timestamp
+ * @param cmdarg->mtime reference timestamp
* @return 0 if successful, -1 if an error occurred
*/
-static int
-cache_artwork_purge_cruft_impl(struct cache_command *cmd)
+static enum command_state
+cache_artwork_purge_cruft_impl(void *arg, int *retval)
{
#define Q_TMPL "DELETE FROM artwork WHERE db_timestamp < %" PRIi64 ";"
+ struct cache_arg *cmdarg;
char *query;
char *errmsg;
int ret;
- query = sqlite3_mprintf(Q_TMPL, (int64_t)cmd->arg.mtime);
+ cmdarg = arg;
+ query = sqlite3_mprintf(Q_TMPL, (int64_t)cmdarg->mtime);
DPRINTF(E_DBG, L_CACHE, "Running purge query '%s'\n", query);
@@ -1086,12 +1037,14 @@ cache_artwork_purge_cruft_impl(struct cache_command *cmd)
DPRINTF(E_LOG, L_CACHE, "Query error: %s\n", errmsg);
sqlite3_free(errmsg);
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
DPRINTF(E_DBG, L_CACHE, "Purged %d rows\n", sqlite3_changes(g_db_hdl));
- return 0;
+ *retval = 0;
+ return COMMAND_END;
#undef Q_TMPL
}
@@ -1099,60 +1052,66 @@ cache_artwork_purge_cruft_impl(struct cache_command *cmd)
/*
* Adds the given (scaled) artwork image to the artwork cache
*
- * @param cmd->arg.persistentid persistent songalbumid or songartistid
- * @param cmd->arg.max_w maximum image width
- * @param cmd->arg.max_h maximum image height
- * @param cmd->arg.format ART_FMT_PNG for png, ART_FMT_JPEG for jpeg or 0 if no artwork available
- * @param cmd->arg.filename the full path to the artwork file (could be an jpg/png image or a media file with embedded artwork) or empty if no artwork available
- * @param cmd->arg.evbuf event buffer containing the (scaled) image
+ * @param cmdarg->persistentid persistent songalbumid or songartistid
+ * @param cmdarg->max_w maximum image width
+ * @param cmdarg->max_h maximum image height
+ * @param cmdarg->format ART_FMT_PNG for png, ART_FMT_JPEG for jpeg or 0 if no artwork available
+ * @param cmdarg->filename the full path to the artwork file (could be an jpg/png image or a media file with embedded artwork) or empty if no artwork available
+ * @param cmdarg->evbuf event buffer containing the (scaled) image
* @return 0 if successful, -1 if an error occurred
*/
-static int
-cache_artwork_add_impl(struct cache_command *cmd)
+static enum command_state
+cache_artwork_add_impl(void *arg, int *retval)
{
+ struct cache_arg *cmdarg;
sqlite3_stmt *stmt;
char *query;
uint8_t *data;
int datalen;
int ret;
+ cmdarg = arg;
query = "INSERT INTO artwork (id, persistentid, max_w, max_h, format, filepath, db_timestamp, data, type) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?);";
ret = sqlite3_prepare_v2(g_db_hdl, query, -1, &stmt, 0);
if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_CACHE, "Could not prepare statement: %s\n", sqlite3_errmsg(g_db_hdl));
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
- datalen = evbuffer_get_length(cmd->arg.evbuf);
- data = evbuffer_pullup(cmd->arg.evbuf, -1);
+ datalen = evbuffer_get_length(cmdarg->evbuf);
+ data = evbuffer_pullup(cmdarg->evbuf, -1);
- sqlite3_bind_int64(stmt, 1, cmd->arg.persistentid);
- sqlite3_bind_int(stmt, 2, cmd->arg.max_w);
- sqlite3_bind_int(stmt, 3, cmd->arg.max_h);
- sqlite3_bind_int(stmt, 4, cmd->arg.format);
- sqlite3_bind_text(stmt, 5, cmd->arg.path, -1, SQLITE_STATIC);
+ sqlite3_bind_int64(stmt, 1, cmdarg->persistentid);
+ sqlite3_bind_int(stmt, 2, cmdarg->max_w);
+ sqlite3_bind_int(stmt, 3, cmdarg->max_h);
+ sqlite3_bind_int(stmt, 4, cmdarg->format);
+ sqlite3_bind_text(stmt, 5, cmdarg->path, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 6, (uint64_t)time(NULL));
sqlite3_bind_blob(stmt, 7, data, datalen, SQLITE_STATIC);
- sqlite3_bind_int(stmt, 8, cmd->arg.type);
+ sqlite3_bind_int(stmt, 8, cmdarg->type);
ret = sqlite3_step(stmt);
if (ret != SQLITE_DONE)
{
DPRINTF(E_LOG, L_CACHE, "Error stepping query for artwork add: %s\n", sqlite3_errmsg(g_db_hdl));
sqlite3_finalize(stmt);
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
ret = sqlite3_finalize(stmt);
if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_CACHE, "Error finalizing query for artwork add: %s\n", sqlite3_errmsg(g_db_hdl));
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
/*
@@ -1161,29 +1120,32 @@ cache_artwork_add_impl(struct cache_command *cmd)
* If there is a cached entry for the given id and width/height, the parameter cached is set to 1.
* In this case format and data contain the cached values.
*
- * @param cmd->arg.type individual or group artwork
- * @param cmd->arg.persistentid persistent itemid, songalbumid or songartistid
- * @param cmd->arg.max_w maximum image width
- * @param cmd->arg.max_h maximum image height
- * @param cmd->arg.cached set by this function to 0 if no cache entry exists, otherwise 1
- * @param cmd->arg.format set by this function to the format of the cache entry
- * @param cmd->arg.evbuf event buffer filled by this function with the scaled image
+ * @param cmdarg->type individual or group artwork
+ * @param cmdarg->persistentid persistent itemid, songalbumid or songartistid
+ * @param cmdarg->max_w maximum image width
+ * @param cmdarg->max_h maximum image height
+ * @param cmdarg->cached set by this function to 0 if no cache entry exists, otherwise 1
+ * @param cmdarg->format set by this function to the format of the cache entry
+ * @param cmdarg->evbuf event buffer filled by this function with the scaled image
* @return 0 if successful, -1 if an error occurred
*/
-static int
-cache_artwork_get_impl(struct cache_command *cmd)
+static enum command_state
+cache_artwork_get_impl(void *arg, int *retval)
{
#define Q_TMPL "SELECT a.format, a.data FROM artwork a WHERE a.type = %d AND a.persistentid = %" PRIi64 " AND a.max_w = %d AND a.max_h = %d;"
+ struct cache_arg *cmdarg;
sqlite3_stmt *stmt;
char *query;
int datalen;
int ret;
- query = sqlite3_mprintf(Q_TMPL, cmd->arg.type, cmd->arg.persistentid, cmd->arg.max_w, cmd->arg.max_h);
+ cmdarg = arg;
+ query = sqlite3_mprintf(Q_TMPL, cmdarg->type, cmdarg->persistentid, cmdarg->max_w, cmdarg->max_h);
if (!query)
{
DPRINTF(E_LOG, L_CACHE, "Out of memory for query string\n");
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
DPRINTF(E_DBG, L_CACHE, "Running query '%s'\n", query);
@@ -1199,7 +1161,7 @@ cache_artwork_get_impl(struct cache_command *cmd)
ret = sqlite3_step(stmt);
if (ret != SQLITE_ROW)
{
- cmd->arg.cached = 0;
+ cmdarg->cached = 0;
if (ret == SQLITE_DONE)
{
@@ -1215,16 +1177,16 @@ cache_artwork_get_impl(struct cache_command *cmd)
goto error_get;
}
- cmd->arg.format = sqlite3_column_int(stmt, 0);
+ cmdarg->format = sqlite3_column_int(stmt, 0);
datalen = sqlite3_column_bytes(stmt, 1);
- if (!cmd->arg.evbuf)
+ if (!cmdarg->evbuf)
{
DPRINTF(E_LOG, L_CACHE, "Error: Artwork evbuffer is NULL\n");
ret = -1;
goto error_get;
}
- ret = evbuffer_add(cmd->arg.evbuf, sqlite3_column_blob(stmt, 1), datalen);
+ ret = evbuffer_add(cmdarg->evbuf, sqlite3_column_blob(stmt, 1), datalen);
if (ret < 0)
{
DPRINTF(E_LOG, L_CACHE, "Out of memory for artwork evbuffer\n");
@@ -1232,7 +1194,7 @@ cache_artwork_get_impl(struct cache_command *cmd)
goto error_get;
}
- cmd->arg.cached = 1;
+ cmdarg->cached = 1;
ret = sqlite3_finalize(stmt);
if (ret != SQLITE_OK)
@@ -1242,19 +1204,25 @@ cache_artwork_get_impl(struct cache_command *cmd)
sqlite3_free(query);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
error_get:
sqlite3_finalize(stmt);
sqlite3_free(query);
- return ret;
+ *retval = ret;
+ return COMMAND_END;
#undef Q_TMPL
}
-static int
-cache_artwork_stash_impl(struct cache_command *cmd)
+static enum command_state
+cache_artwork_stash_impl(void *arg, int *retval)
{
+ struct cache_arg *cmdarg;
+
+ cmdarg = arg;
+
/* Clear current stash */
if (g_stash.path)
{
@@ -1263,40 +1231,50 @@ cache_artwork_stash_impl(struct cache_command *cmd)
memset(&g_stash, 0, sizeof(struct stash));
}
- g_stash.size = evbuffer_get_length(cmd->arg.evbuf);
+ g_stash.size = evbuffer_get_length(cmdarg->evbuf);
g_stash.data = malloc(g_stash.size);
if (!g_stash.data)
{
DPRINTF(E_LOG, L_CACHE, "Out of memory for artwork stash data\n");
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
- g_stash.path = strdup(cmd->arg.path);
+ g_stash.path = strdup(cmdarg->path);
if (!g_stash.path)
{
DPRINTF(E_LOG, L_CACHE, "Out of memory for artwork stash path\n");
free(g_stash.data);
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
- g_stash.format = cmd->arg.format;
+ g_stash.format = cmdarg->format;
- return evbuffer_copyout(cmd->arg.evbuf, g_stash.data, g_stash.size);
+ *retval = evbuffer_copyout(cmdarg->evbuf, g_stash.data, g_stash.size);
+ return COMMAND_END;
}
-static int
-cache_artwork_read_impl(struct cache_command *cmd)
+static enum command_state
+cache_artwork_read_impl(void *arg, int *retval)
{
- cmd->arg.format = 0;
+ struct cache_arg *cmdarg;
- if (!g_stash.path || !g_stash.data || (strcmp(g_stash.path, cmd->arg.path) != 0))
- return -1;
+ cmdarg = arg;
+ cmdarg->format = 0;
- cmd->arg.format = g_stash.format;
+ if (!g_stash.path || !g_stash.data || (strcmp(g_stash.path, cmdarg->path) != 0))
+ {
+ *retval = -1;
+ return COMMAND_END;
+ }
+
+ cmdarg->format = g_stash.format;
DPRINTF(E_DBG, L_CACHE, "Stash hit (format %d, size %zu): %s\n", g_stash.format, g_stash.size, g_stash.path);
- return evbuffer_add(cmd->arg.evbuf, g_stash.data, g_stash.size);
+ *retval = evbuffer_add(cmdarg->evbuf, g_stash.data, g_stash.size);
+ return COMMAND_END;
}
static void *
@@ -1340,57 +1318,6 @@ cache(void *arg)
pthread_exit(NULL);
}
-static void
-exit_cb(int fd, short what, void *arg)
-{
- int dummy;
- int ret;
-
- ret = read(g_exit_pipe[0], &dummy, sizeof(dummy));
- if (ret != sizeof(dummy))
- DPRINTF(E_LOG, L_CACHE, "Error reading from exit pipe\n");
-
- event_base_loopbreak(evbase_cache);
-
- g_initialized = 0;
-
- event_add(g_exitev, NULL);
-}
-
-static void
-command_cb(int fd, short what, void *arg)
-{
- struct cache_command *cmd;
- int ret;
-
- ret = read(g_cmd_pipe[0], &cmd, sizeof(cmd));
- if (ret != sizeof(cmd))
- {
- DPRINTF(E_LOG, L_CACHE, "Could not read command! (read %d): %s\n", ret, (ret < 0) ? strerror(errno) : "-no error-");
- goto readd;
- }
-
- if (cmd->nonblock)
- {
- cmd->func(cmd);
-
- free(cmd);
- goto readd;
- }
-
- pthread_mutex_lock(&cmd->lck);
-
- ret = cmd->func(cmd);
- cmd->ret = ret;
-
- pthread_cond_signal(&cmd->cond);
- pthread_mutex_unlock(&cmd->lck);
-
- readd:
- event_add(g_cmdev, NULL);
-}
-
-
/* ---------------------------- DAAP cache API --------------------------- */
@@ -1404,122 +1331,66 @@ command_cb(int fd, short what, void *arg)
void
cache_daap_trigger(void)
{
- struct cache_command *cmd;
-
if (!g_initialized)
return;
- cmd = (struct cache_command *)malloc(sizeof(struct cache_command));
- if (!cmd)
- {
- DPRINTF(E_LOG, L_CACHE, "Could not allocate cache_command\n");
- return;
- }
-
- memset(cmd, 0, sizeof(struct cache_command));
-
- cmd->nonblock = 1;
-
- cmd->func = cache_daap_update_timer;
-
- nonblock_command(cmd);
+ commands_exec_async(cmdbase, cache_daap_update_timer, NULL);
}
void
cache_daap_suspend(void)
{
- struct cache_command *cmd;
-
if (!g_initialized)
return;
- cmd = (struct cache_command *)malloc(sizeof(struct cache_command));
- if (!cmd)
- {
- DPRINTF(E_LOG, L_CACHE, "Could not allocate cache_command\n");
- return;
- }
-
- memset(cmd, 0, sizeof(struct cache_command));
-
- cmd->nonblock = 1;
-
- cmd->func = cache_daap_suspend_timer;
-
- nonblock_command(cmd);
+ commands_exec_async(cmdbase, cache_daap_suspend_timer, NULL);
}
void
cache_daap_resume(void)
{
- struct cache_command *cmd;
-
if (!g_initialized)
return;
- cmd = (struct cache_command *)malloc(sizeof(struct cache_command));
- if (!cmd)
- {
- DPRINTF(E_LOG, L_CACHE, "Could not allocate cache_command\n");
- return;
- }
-
- memset(cmd, 0, sizeof(struct cache_command));
-
- cmd->nonblock = 1;
-
- cmd->func = cache_daap_resume_timer;
-
- nonblock_command(cmd);
+ commands_exec_async(cmdbase, cache_daap_resume_timer, NULL);
}
int
cache_daap_get(const char *query, struct evbuffer *evbuf)
{
- struct cache_command cmd;
- int ret;
+ struct cache_arg cmdarg;
if (!g_initialized)
return -1;
- command_init(&cmd);
-
- cmd.func = cache_daap_query_get;
- cmd.arg.query = strdup(query);
- cmd.arg.evbuf = evbuf;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.query = strdup(query);
+ cmdarg.evbuf = evbuf;
- return ret;
+ return commands_exec_sync(cmdbase, cache_daap_query_get, NULL, &cmdarg);
}
void
cache_daap_add(const char *query, const char *ua, int msec)
{
- struct cache_command *cmd;
+ struct cache_arg *cmdarg;
if (!g_initialized)
return;
- cmd = (struct cache_command *)malloc(sizeof(struct cache_command));
- if (!cmd)
+ cmdarg = (struct cache_arg *)malloc(sizeof(struct cache_arg));
+ if (!cmdarg)
{
- DPRINTF(E_LOG, L_CACHE, "Could not allocate cache_command\n");
+ DPRINTF(E_LOG, L_CACHE, "Could not allocate cache_arg\n");
return;
}
- memset(cmd, 0, sizeof(struct cache_command));
-
- cmd->nonblock = 1;
+ memset(cmdarg, 0, sizeof(struct cache_arg));
- cmd->func = cache_daap_query_add;
- cmd->arg.query = strdup(query);
- cmd->arg.ua = strdup(ua);
- cmd->arg.msec = msec;
+ cmdarg->query = strdup(query);
+ cmdarg->ua = strdup(ua);
+ cmdarg->msec = msec;
- nonblock_command(cmd);
+ commands_exec_async(cmdbase, cache_daap_query_add, cmdarg);
}
int
@@ -1546,28 +1417,25 @@ cache_daap_threshold(void)
void
cache_artwork_ping(char *path, time_t mtime, int del)
{
- struct cache_command *cmd;
+ struct cache_arg *cmdarg;
if (!g_initialized)
return;
- cmd = (struct cache_command *)malloc(sizeof(struct cache_command));
- if (!cmd)
+ cmdarg = (struct cache_arg *)malloc(sizeof(struct cache_arg));
+ if (!cmdarg)
{
- DPRINTF(E_LOG, L_CACHE, "Could not allocate cache_command\n");
+ DPRINTF(E_LOG, L_CACHE, "Could not allocate cache_arg\n");
return;
}
- memset(cmd, 0, sizeof(struct cache_command));
+ memset(cmdarg, 0, sizeof(struct cache_arg));
- cmd->nonblock = 1;
+ cmdarg->path = strdup(path);
+ cmdarg->mtime = mtime;
+ cmdarg->del = del;
- cmd->func = cache_artwork_ping_impl;
- cmd->arg.path = strdup(path);
- cmd->arg.mtime = mtime;
- cmd->arg.del = del;
-
- nonblock_command(cmd);
+ commands_exec_async(cmdbase, cache_artwork_ping_impl, cmdarg);
}
/*
@@ -1579,22 +1447,14 @@ cache_artwork_ping(char *path, time_t mtime, int del)
int
cache_artwork_delete_by_path(char *path)
{
- struct cache_command cmd;
- int ret;
+ struct cache_arg cmdarg;
if (!g_initialized)
return -1;
- command_init(&cmd);
-
- cmd.func = cache_artwork_delete_by_path_impl;
- cmd.arg.path = path;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.path = path;
- return ret;
+ return commands_exec_sync(cmdbase, cache_artwork_delete_by_path_impl, NULL, &cmdarg);
}
/*
@@ -1606,22 +1466,14 @@ cache_artwork_delete_by_path(char *path)
int
cache_artwork_purge_cruft(time_t ref)
{
- struct cache_command cmd;
- int ret;
+ struct cache_arg cmdarg;
if (!g_initialized)
return -1;
- command_init(&cmd);
-
- cmd.func = cache_artwork_purge_cruft_impl;
- cmd.arg.mtime = ref;
-
- ret = sync_command(&cmd);
+ cmdarg.mtime = ref;
- command_deinit(&cmd);
-
- return ret;
+ return commands_exec_sync(cmdbase, cache_artwork_purge_cruft_impl, NULL, &cmdarg);
}
/*
@@ -1639,28 +1491,20 @@ cache_artwork_purge_cruft(time_t ref)
int
cache_artwork_add(int type, int64_t persistentid, int max_w, int max_h, int format, char *filename, struct evbuffer *evbuf)
{
- struct cache_command cmd;
- int ret;
+ struct cache_arg cmdarg;
if (!g_initialized)
return -1;
- command_init(&cmd);
+ cmdarg.type = type;
+ cmdarg.persistentid = persistentid;
+ cmdarg.max_w = max_w;
+ cmdarg.max_h = max_h;
+ cmdarg.format = format;
+ cmdarg.path = filename;
+ cmdarg.evbuf = evbuf;
- cmd.func = cache_artwork_add_impl;
- cmd.arg.type = type;
- cmd.arg.persistentid = persistentid;
- cmd.arg.max_w = max_w;
- cmd.arg.max_h = max_h;
- cmd.arg.format = format;
- cmd.arg.path = filename;
- cmd.arg.evbuf = evbuf;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
-
- return ret;
+ return commands_exec_sync(cmdbase, cache_artwork_add_impl, NULL, &cmdarg);
}
/*
@@ -1680,7 +1524,7 @@ cache_artwork_add(int type, int64_t persistentid, int max_w, int max_h, int form
int
cache_artwork_get(int type, int64_t persistentid, int max_w, int max_h, int *cached, int *format, struct evbuffer *evbuf)
{
- struct cache_command cmd;
+ struct cache_arg cmdarg;
int ret;
if (!g_initialized)
@@ -1690,21 +1534,16 @@ cache_artwork_get(int type, int64_t persistentid, int max_w, int max_h, int *cac
return 0;
}
- command_init(&cmd);
+ cmdarg.type = type;
+ cmdarg.persistentid = persistentid;
+ cmdarg.max_w = max_w;
+ cmdarg.max_h = max_h;
+ cmdarg.evbuf = evbuf;
- cmd.func = cache_artwork_get_impl;
- cmd.arg.type = type;
- cmd.arg.persistentid = persistentid;
- cmd.arg.max_w = max_w;
- cmd.arg.max_h = max_h;
- cmd.arg.evbuf = evbuf;
+ ret = commands_exec_sync(cmdbase, cache_artwork_get_impl, NULL, &cmdarg);
- ret = sync_command(&cmd);
-
- *format = cmd.arg.format;
- *cached = cmd.arg.cached;
-
- command_deinit(&cmd);
+ *format = cmdarg.format;
+ *cached = cmdarg.cached;
return ret;
}
@@ -1720,24 +1559,16 @@ cache_artwork_get(int type, int64_t persistentid, int max_w, int max_h, int *cac
int
cache_artwork_stash(struct evbuffer *evbuf, char *path, int format)
{
- struct cache_command cmd;
- int ret;
+ struct cache_arg cmdarg;
if (!g_initialized)
return -1;
- command_init(&cmd);
+ cmdarg.evbuf = evbuf;
+ cmdarg.path = path;
+ cmdarg.format = format;
- cmd.func = cache_artwork_stash_impl;
- cmd.arg.evbuf = evbuf;
- cmd.arg.path = path;
- cmd.arg.format = format;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
-
- return ret;
+ return commands_exec_sync(cmdbase, cache_artwork_stash_impl, NULL, &cmdarg);
}
/*
@@ -1751,23 +1582,18 @@ cache_artwork_stash(struct evbuffer *evbuf, char *path, int format)
int
cache_artwork_read(struct evbuffer *evbuf, char *path, int *format)
{
- struct cache_command cmd;
+ struct cache_arg cmdarg;
int ret;
if (!g_initialized)
return -1;
- command_init(&cmd);
+ cmdarg.evbuf = evbuf;
+ cmdarg.path = path;
- cmd.func = cache_artwork_read_impl;
- cmd.arg.evbuf = evbuf;
- cmd.arg.path = path;
+ ret = commands_exec_sync(cmdbase, cache_artwork_read_impl, NULL, &cmdarg);
- ret = sync_command(&cmd);
-
- *format = cmd.arg.format;
-
- command_deinit(&cmd);
+ *format = cmdarg.format;
return ret;
}
@@ -1796,28 +1622,6 @@ cache_init(void)
return 0;
}
-#ifdef HAVE_PIPE2
- ret = pipe2(g_exit_pipe, O_CLOEXEC);
-#else
- ret = pipe(g_exit_pipe);
-#endif
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_CACHE, "Could not create exit pipe: %s\n", strerror(errno));
- goto exit_fail;
- }
-
-#ifdef HAVE_PIPE2
- ret = pipe2(g_cmd_pipe, O_CLOEXEC);
-#else
- ret = pipe(g_cmd_pipe);
-#endif
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_CACHE, "Could not create command pipe: %s\n", strerror(errno));
- goto cmd_fail;
- }
-
evbase_cache = event_base_new();
if (!evbase_cache)
{
@@ -1825,29 +1629,14 @@ cache_init(void)
goto evbase_fail;
}
- g_exitev = event_new(evbase_cache, g_exit_pipe[0], EV_READ, exit_cb, NULL);
- if (!g_exitev)
- {
- DPRINTF(E_LOG, L_CACHE, "Could not create exit event\n");
- goto evnew_fail;
- }
-
- g_cmdev = event_new(evbase_cache, g_cmd_pipe[0], EV_READ, command_cb, NULL);
- if (!g_cmdev)
- {
- DPRINTF(E_LOG, L_CACHE, "Could not create cmd event\n");
- goto evnew_fail;
- }
-
g_cacheev = evtimer_new(evbase_cache, cache_daap_update_cb, NULL);
- if (!g_cmdev)
+ if (!g_cacheev)
{
DPRINTF(E_LOG, L_CACHE, "Could not create cache event\n");
goto evnew_fail;
}
- event_add(g_exitev, NULL);
- event_add(g_cmdev, NULL);
+ cmdbase = commands_base_new(evbase_cache, NULL);
DPRINTF(E_INFO, L_CACHE, "cache thread init\n");
@@ -1868,19 +1657,12 @@ cache_init(void)
return 0;
thread_fail:
+ commands_base_free(cmdbase);
evnew_fail:
event_base_free(evbase_cache);
evbase_cache = NULL;
evbase_fail:
- close(g_cmd_pipe[0]);
- close(g_cmd_pipe[1]);
-
- cmd_fail:
- close(g_exit_pipe[0]);
- close(g_exit_pipe[1]);
-
- exit_fail:
return -1;
}
@@ -1892,7 +1674,8 @@ cache_deinit(void)
if (!g_initialized)
return;
- thread_exit();
+ g_initialized = 0;
+ commands_base_destroy(cmdbase);
ret = pthread_join(tid_cache, NULL);
if (ret != 0)
@@ -1903,10 +1686,4 @@ cache_deinit(void)
// Free event base (should free events too)
event_base_free(evbase_cache);
-
- // Close pipes
- close(g_cmd_pipe[0]);
- close(g_cmd_pipe[1]);
- close(g_exit_pipe[0]);
- close(g_exit_pipe[1]);
}
diff --git a/src/commands.c b/src/commands.c
new file mode 100644
index 0000000..8636afb
--- /dev/null
+++ b/src/commands.c
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2016 Christian Meffert <christian.meffert at googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "commands.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "logger.h"
+
+
+struct command
+{
+ pthread_mutex_t lck;
+ pthread_cond_t cond;
+
+ command_function func;
+ command_function func_bh;
+ void *arg;
+ int nonblock;
+ int ret;
+ int pending;
+};
+
+struct commands_base
+{
+ struct event_base *evbase;
+ command_exit_cb exit_cb;
+ int command_pipe[2];
+ struct event *command_event;
+ struct command *current_cmd;
+};
+
+/*
+ * Asynchronous execution of the command function
+ */
+static void
+command_cb_async(struct commands_base *cmdbase, struct command *cmd)
+{
+ enum command_state cmdstate;
+
+ // Command is executed asynchronously
+ cmdstate = cmd->func(cmd->arg, &cmd->ret);
+
+ // Only free arg if there are no pending events (used in worker.c)
+ if (cmdstate != COMMAND_PENDING && cmd->arg)
+ free(cmd->arg);
+
+ free(cmd);
+
+ event_add(cmdbase->command_event, NULL);
+}
+
+/*
+ * Synchronous execution of the command function
+ */
+static void
+command_cb_sync(struct commands_base *cmdbase, struct command *cmd)
+{
+ enum command_state cmdstate;
+
+ pthread_mutex_lock(&cmd->lck);
+
+ cmdstate = cmd->func(cmd->arg, &cmd->ret);
+ if (cmdstate == COMMAND_PENDING)
+ {
+ // Command execution is waiting for pending events before returning to the caller
+ cmdbase->current_cmd = cmd;
+ cmd->pending = cmd->ret;
+ }
+ else
+ {
+ // Command execution finished, execute the bottom half function
+ if (cmd->ret == 0 && cmd->func_bh)
+ {
+ cmdstate = cmd->func_bh(cmd->arg, &cmd->ret);
+ }
+
+ // Signal the calling thread that the command execution finished
+ pthread_cond_signal(&cmd->cond);
+ pthread_mutex_unlock(&cmd->lck);
+
+ event_add(cmdbase->command_event, NULL);
+ }
+}
+
+/*
+ * Event callback function
+ *
+ * Function is triggered by libevent if there is data to read on the command pipe (writing to the command pipe happens through
+ * the send_command function).
+ */
+static void
+command_cb(int fd, short what, void *arg)
+{
+ struct commands_base *cmdbase;
+ struct command *cmd;
+ int ret;
+
+ cmdbase = arg;
+
+ // Get the command to execute from the pipe
+ ret = read(cmdbase->command_pipe[0], &cmd, sizeof(cmd));
+ if (ret != sizeof(cmd))
+ {
+ DPRINTF(E_LOG, L_MAIN, "Error reading command from command pipe: expected %zu bytes, read %d bytes\n", sizeof(cmd), ret);
+
+ event_add(cmdbase->command_event, NULL);
+ return;
+ }
+
+ // Execute the command function
+ if (cmd->nonblock)
+ {
+ // Command is executed asynchronously
+ command_cb_async(cmdbase, cmd);
+ }
+ else
+ {
+ // Command is executed synchronously, caller is waiting until signaled that the execution finished
+ command_cb_sync(cmdbase, cmd);
+ }
+}
+
+/*
+ * Writes the given command to the command pipe
+ */
+static int
+send_command(struct commands_base *cmdbase, struct command *cmd)
+{
+ int ret;
+
+ if (!cmd->func)
+ {
+ DPRINTF(E_LOG, L_MAIN, "Programming error: send_command called with command->func NULL!\n");
+ return -1;
+ }
+
+ ret = write(cmdbase->command_pipe[1], &cmd, sizeof(cmd));
+ if (ret != sizeof(cmd))
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Creates a new command base, needs to be freed by commands_base_destroy or commands_base_free.
+ *
+ * @param evbase The libevent base to use for command handling
+ * @param exit_cb Optional callback function to be called during commands_base_destroy
+ */
+struct commands_base *
+commands_base_new(struct event_base *evbase, command_exit_cb exit_cb)
+{
+ struct commands_base *cmdbase;
+ int ret;
+
+ cmdbase = calloc(1, sizeof(struct commands_base));
+ if (!cmdbase)
+ {
+ DPRINTF(E_LOG, L_MAIN, "Out of memory for cmdbase\n");
+ return NULL;
+ }
+
+# if defined(__linux__)
+ ret = pipe2(cmdbase->command_pipe, O_CLOEXEC);
+# else
+ ret = pipe(cmdbase->command_pipe);
+# endif
+ if (ret < 0)
+ {
+ DPRINTF(E_LOG, L_MAIN, "Could not create command pipe: %s\n", strerror(errno));
+ free(cmdbase);
+ return NULL;
+ }
+
+ cmdbase->command_event = event_new(evbase, cmdbase->command_pipe[0], EV_READ, command_cb, cmdbase);
+ if (!cmdbase->command_event)
+ {
+ DPRINTF(E_LOG, L_MAIN, "Could not create cmd event\n");
+ close(cmdbase->command_pipe[0]);
+ close(cmdbase->command_pipe[1]);
+ free(cmdbase);
+ return NULL;
+ }
+
+ ret = event_add(cmdbase->command_event, NULL);
+ if (ret != 0)
+ {
+ DPRINTF(E_LOG, L_MAIN, "Could not add cmd event\n");
+ close(cmdbase->command_pipe[0]);
+ close(cmdbase->command_pipe[1]);
+ free(cmdbase);
+ return NULL;
+ }
+
+ cmdbase->evbase = evbase;
+ cmdbase->exit_cb = exit_cb;
+
+ return cmdbase;
+}
+
+/*
+ * Frees the command base and closes the (internally used) pipes
+ */
+int
+commands_base_free(struct commands_base *cmdbase)
+{
+ close(cmdbase->command_pipe[0]);
+ close(cmdbase->command_pipe[1]);
+ free(cmdbase);
+
+ return 0;
+}
+
+/*
+ * Gets the current return value for the current pending command.
+ *
+ * If a command has more than one pending event, each event can access the previous set return value
+ * if it depends on it.
+ *
+ * @param cmdbase The command base
+ * @return The current return value
+ */
+int
+commands_exec_returnvalue(struct commands_base *cmdbase)
+{
+ if (cmdbase->current_cmd == NULL)
+ return 0;
+
+ return cmdbase->current_cmd->ret;
+}
+
+/*
+ * If a command function returned COMMAND_PENDING, each event triggered by this command needs to
+ * call command_exec_end, passing it the return value of the event execution.
+ *
+ * If a command function is waiting for multiple events, each event needs to call command_exec_end.
+ * The command base keeps track of the number of still pending events and only returns to the caller
+ * if there are no pending events left.
+ *
+ * @param cmdbase The command base (holds the current pending command)
+ * @param retvalue The return value for the calling thread
+ */
+void
+commands_exec_end(struct commands_base *cmdbase, int retvalue)
+{
+ if (cmdbase->current_cmd == NULL)
+ return;
+
+ // A pending event finished, decrease the number of pending events and update the return value
+ cmdbase->current_cmd->pending--;
+ cmdbase->current_cmd->ret = retvalue;
+
+ DPRINTF(E_DBG, L_MAIN, "Command has %d pending events\n", cmdbase->current_cmd->pending);
+
+ // If there are still pending events return
+ if (cmdbase->current_cmd->pending > 0)
+ return;
+
+ // All pending events have finished, execute the bottom half and signal the caller that the command execution finished
+ if (cmdbase->current_cmd->func_bh)
+ {
+ cmdbase->current_cmd->func_bh(cmdbase->current_cmd->arg, &cmdbase->current_cmd->ret);
+ }
+ pthread_cond_signal(&cmdbase->current_cmd->cond);
+ pthread_mutex_unlock(&cmdbase->current_cmd->lck);
+
+ cmdbase->current_cmd = NULL;
+
+ /* Process commands again */
+ event_add(cmdbase->command_event, NULL);
+}
+
+/*
+ * Execute the function 'func' with the given argument 'arg' in the event loop thread.
+ * Blocks the caller (thread) until the function returned.
+ *
+ * If a function 'func_bh' ("bottom half") is given, it is executed after 'func' has successfully
+ * finished.
+ *
+ * @param cmdbase The command base
+ * @param func The function to be executed
+ * @param func_bh The bottom half function to be executed after all pending events from func are processed
+ * @param arg Argument passed to func (and func_bh)
+ * @return Return value of func (or func_bh if func_bh is not NULL)
+ */
+int
+commands_exec_sync(struct commands_base *cmdbase, command_function func, command_function func_bh, void *arg)
+{
+ struct command cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof(struct command));
+ cmd.func = func;
+ cmd.func_bh = func_bh;
+ cmd.arg = arg;
+ cmd.nonblock = 0;
+
+ pthread_mutex_lock(&cmd.lck);
+
+ ret = send_command(cmdbase, &cmd);
+ if (ret < 0)
+ {
+ DPRINTF(E_LOG, L_MAIN, "Error sending command\n");
+ pthread_mutex_unlock(&cmd.lck);
+ return -1;
+ }
+
+ pthread_cond_wait(&cmd.cond, &cmd.lck);
+ pthread_mutex_unlock(&cmd.lck);
+
+ return cmd.ret;
+}
+
+/*
+ * Execute the function 'func' with the given argument 'arg' in the event loop thread.
+ * Triggers the function execution and immediately returns (does not wait for func to finish).
+ *
+ * The pointer passed as argument is freed in the event loop thread after func returned.
+ *
+ * @param cmdbase The command base
+ * @param func The function to be executed
+ * @param arg Argument passed to func
+ * @return 0 if triggering the function execution succeeded, -1 on failure.
+ */
+int
+commands_exec_async(struct commands_base *cmdbase, command_function func, void *arg)
+{
+ struct command *cmd;
+ int ret;
+
+ cmd = calloc(1, sizeof(struct command));
+ cmd->func = func;
+ cmd->func_bh = NULL;
+ cmd->arg = arg;
+ cmd->nonblock = 1;
+
+ ret = send_command(cmdbase, cmd);
+ if (ret < 0)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * Command to break the libevent loop
+ *
+ * If the command base was created with an exit_cb function, exit_cb is called before breaking the
+ * libevent loop.
+ *
+ * @param arg The command base
+ * @param retval Always set to COMMAND_END
+ */
+static enum command_state
+cmdloop_exit(void *arg, int *retval)
+{
+ struct commands_base *cmdbase = arg;
+ *retval = 0;
+
+ if (cmdbase->exit_cb)
+ cmdbase->exit_cb();
+
+ event_base_loopbreak(cmdbase->evbase);
+
+ return COMMAND_END;
+}
+
+/*
+ * Break the libevent loop for the given command base, closes the internally used pipes
+ * and frees the command base.
+ *
+ * @param cmdbase The command base
+ */
+void
+commands_base_destroy(struct commands_base *cmdbase)
+{
+ commands_exec_sync(cmdbase, cmdloop_exit, NULL, cmdbase);
+ commands_base_free(cmdbase);
+}
+
diff --git a/src/commands.h b/src/commands.h
new file mode 100644
index 0000000..9076d0f
--- /dev/null
+++ b/src/commands.h
@@ -0,0 +1,56 @@
+
+#ifndef SRC_COMMANDS_H_
+#define SRC_COMMANDS_H_
+
+#include <event2/event.h>
+
+enum command_state {
+ COMMAND_END = 0,
+ COMMAND_PENDING = 1,
+};
+
+/*
+ * Function that will be executed in the event loop thread.
+ *
+ * If the function has pending events to complete, it needs to return
+ * COMMAND_PENDING with 'ret' set to the number of pending events to wait for.
+ *
+ * If the function returns with COMMAND_END, command execution will proceed
+ * with the "bottem half" function (if passed to the command_exec function) only
+ * if 'ret' is 0.
+ *
+ * @param arg Opaque pointer passed by command_exec_sync or command_exec_async
+ * @param ret Pointer to the return value for the caller of the command
+ * @return COMMAND_END if there are no pending events (function execution is
+ * complete) or COMMAND_PENDING if there are pending events
+ */
+typedef enum command_state (*command_function)(void *arg, int *ret);
+
+typedef void (*command_exit_cb)(void);
+
+
+struct commands_base;
+
+
+struct commands_base *
+commands_base_new(struct event_base *evbase, command_exit_cb exit_cb);
+
+int
+commands_base_free(struct commands_base *cmdbase);
+
+int
+commands_exec_returnvalue(struct commands_base *cmdbase);
+
+void
+commands_exec_end(struct commands_base *cmdbase, int retvalue);
+
+int
+commands_exec_sync(struct commands_base *cmdbase, command_function func, command_function func_bh, void *arg);
+
+int
+commands_exec_async(struct commands_base *cmdbase, command_function func, void *arg);
+
+void
+commands_base_destroy(struct commands_base *cmdbase);
+
+#endif /* SRC_COMMANDS_H_ */
diff --git a/src/conffile.c b/src/conffile.c
index c8571f3..8d01de0 100644
--- a/src/conffile.c
+++ b/src/conffile.c
@@ -55,6 +55,8 @@ static cfg_opt_t sec_general[] =
CFG_BOOL("ipv6", cfg_true, CFGF_NONE),
CFG_STR("cache_path", STATEDIR "/cache/" PACKAGE "/cache.db", CFGF_NONE),
CFG_INT("cache_daap_threshold", 1000, CFGF_NONE),
+ CFG_BOOL("speaker_autoselect", cfg_true, CFGF_NONE),
+ CFG_STR("allow_origin", "*", CFGF_NONE),
CFG_END()
};
@@ -84,6 +86,7 @@ static cfg_opt_t sec_library[] =
CFG_STR_LIST("filepath_ignore", NULL, CFGF_NONE),
CFG_BOOL("filescan_disable", cfg_false, CFGF_NONE),
CFG_BOOL("itunes_overrides", cfg_false, CFGF_NONE),
+ CFG_BOOL("itunes_smartpl", cfg_false, CFGF_NONE),
CFG_STR_LIST("no_decode", NULL, CFGF_NONE),
CFG_STR_LIST("force_decode", NULL, CFGF_NONE),
CFG_END()
@@ -96,6 +99,7 @@ static cfg_opt_t sec_audio[] =
CFG_STR("type", NULL, CFGF_NONE),
CFG_STR("card", "default", CFGF_NONE),
CFG_STR("mixer", NULL, CFGF_NONE),
+ CFG_STR("mixer_device", NULL, CFGF_NONE),
CFG_INT("offset", 0, CFGF_NONE),
CFG_END()
};
@@ -108,6 +112,14 @@ static cfg_opt_t sec_airplay[] =
CFG_END()
};
+/* FIFO section structure */
+static cfg_opt_t sec_fifo[] =
+ {
+ CFG_STR("nickname", "fifo", CFGF_NONE),
+ CFG_STR("path", NULL, CFGF_NONE),
+ CFG_END()
+ };
+
/* Spotify section structure */
static cfg_opt_t sec_spotify[] =
{
@@ -147,6 +159,7 @@ static cfg_opt_t toplvl_cfg[] =
CFG_SEC("library", sec_library, CFGF_NONE),
CFG_SEC("audio", sec_audio, CFGF_NONE),
CFG_SEC("airplay", sec_airplay, CFGF_MULTI | CFGF_TITLE),
+ CFG_SEC("fifo", sec_fifo, CFGF_NONE),
CFG_SEC("spotify", sec_spotify, CFGF_NONE),
CFG_SEC("sqlite", sec_sqlite, CFGF_NONE),
CFG_SEC("mpd", sec_mpd, CFGF_NONE),
diff --git a/src/db.c b/src/db.c
index 2c647d3..21e468d 100644
--- a/src/db.c
+++ b/src/db.c
@@ -44,6 +44,7 @@
#include "cache.h"
#include "misc.h"
#include "db.h"
+#include "db_init.h"
#include "db_upgrade.h"
@@ -267,6 +268,7 @@ static const ssize_t dbgri_cols_map[] =
dbgri_offsetof(groupalbumcount),
dbgri_offsetof(songalbumartist),
dbgri_offsetof(songartistid),
+ dbgri_offsetof(song_length),
};
/* This list must be kept in sync with
@@ -1209,13 +1211,13 @@ db_build_query_group_albums(struct query_params *qp, char **q)
sort = sort_clause[qp->sort];
if (idx && qp->filter)
- query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album, f.album_sort, COUNT(f.id), 1, f.album_artist, f.songartistid FROM files f JOIN groups g ON f.songalbumid = g.persistentid WHERE f.disabled = 0 AND %s GROUP BY f.songalbumid %s %s;", qp->filter, sort, idx);
+ query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album, f.album_sort, COUNT(f.id), 1, f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songalbumid = g.persistentid WHERE f.disabled = 0 AND %s GROUP BY f.songalbumid %s %s;", qp->filter, sort, idx);
else if (idx)
- query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album, f.album_sort, COUNT(f.id), 1, f.album_artist, f.songartistid FROM files f JOIN groups g ON f.songalbumid = g.persistentid WHERE f.disabled = 0 GROUP BY f.songalbumid %s %s;", sort, idx);
+ query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album, f.album_sort, COUNT(f.id), 1, f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songalbumid = g.persistentid WHERE f.disabled = 0 GROUP BY f.songalbumid %s %s;", sort, idx);
else if (qp->filter)
- query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album, f.album_sort, COUNT(f.id), 1, f.album_artist, f.songartistid FROM files f JOIN groups g ON f.songalbumid = g.persistentid WHERE f.disabled = 0 AND %s GROUP BY f.songalbumid %s;", qp->filter, sort);
+ query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album, f.album_sort, COUNT(f.id), 1, f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songalbumid = g.persistentid WHERE f.disabled = 0 AND %s GROUP BY f.songalbumid %s;", qp->filter, sort);
else
- query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album, f.album_sort, COUNT(f.id), 1, f.album_artist, f.songartistid FROM files f JOIN groups g ON f.songalbumid = g.persistentid WHERE f.disabled = 0 GROUP BY f.songalbumid %s;", sort);
+ query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album, f.album_sort, COUNT(f.id), 1, f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songalbumid = g.persistentid WHERE f.disabled = 0 GROUP BY f.songalbumid %s;", sort);
if (idx)
sqlite3_free(idx);
@@ -1251,13 +1253,13 @@ db_build_query_group_artists(struct query_params *qp, char **q)
sort = sort_clause[qp->sort];
if (idx && qp->filter)
- query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album_artist, f.album_artist_sort, COUNT(f.id), COUNT(DISTINCT f.songalbumid), f.album_artist, f.songartistid FROM files f JOIN groups g ON f.songartistid = g.persistentid WHERE f.disabled = 0 AND %s GROUP BY f.songartistid %s %s;", qp->filter, sort, idx);
+ query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album_artist, f.album_artist_sort, COUNT(f.id), COUNT(DISTINCT f.songalbumid), f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songartistid = g.persistentid WHERE f.disabled = 0 AND %s GROUP BY f.songartistid %s %s;", qp->filter, sort, idx);
else if (idx)
- query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album_artist, f.album_artist_sort, COUNT(f.id), COUNT(DISTINCT f.songalbumid), f.album_artist, f.songartistid FROM files f JOIN groups g ON f.songartistid = g.persistentid WHERE f.disabled = 0 GROUP BY f.songartistid %s %s;", sort, idx);
+ query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album_artist, f.album_artist_sort, COUNT(f.id), COUNT(DISTINCT f.songalbumid), f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songartistid = g.persistentid WHERE f.disabled = 0 GROUP BY f.songartistid %s %s;", sort, idx);
else if (qp->filter)
- query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album_artist, f.album_artist_sort, COUNT(f.id), COUNT(DISTINCT f.songalbumid), f.album_artist, f.songartistid FROM files f JOIN groups g ON f.songartistid = g.persistentid WHERE f.disabled = 0 AND %s GROUP BY f.songartistid %s;", qp->filter, sort);
+ query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album_artist, f.album_artist_sort, COUNT(f.id), COUNT(DISTINCT f.songalbumid), f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songartistid = g.persistentid WHERE f.disabled = 0 AND %s GROUP BY f.songartistid %s;", qp->filter, sort);
else
- query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album_artist, f.album_artist_sort, COUNT(f.id), COUNT(DISTINCT f.songalbumid), f.album_artist, f.songartistid FROM files f JOIN groups g ON f.songartistid = g.persistentid WHERE f.disabled = 0 GROUP BY f.songartistid %s;", sort);
+ query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album_artist, f.album_artist_sort, COUNT(f.id), COUNT(DISTINCT f.songalbumid), f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songartistid = g.persistentid WHERE f.disabled = 0 GROUP BY f.songartistid %s;", sort);
if (idx)
sqlite3_free(idx);
@@ -4807,398 +4809,6 @@ db_perthread_deinit(void)
}
-#define T_ADMIN \
- "CREATE TABLE IF NOT EXISTS admin(" \
- " key VARCHAR(32) NOT NULL," \
- " value VARCHAR(32) NOT NULL" \
- ");"
-
-#define T_FILES \
- "CREATE TABLE IF NOT EXISTS files (" \
- " id INTEGER PRIMARY KEY NOT NULL," \
- " path VARCHAR(4096) NOT NULL," \
- " fname VARCHAR(255) NOT NULL," \
- " title VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
- " artist VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
- " album VARCHAR(1024) NOT NULL COLLATE DAAP," \
- " genre VARCHAR(255) DEFAULT NULL COLLATE DAAP," \
- " comment VARCHAR(4096) DEFAULT NULL COLLATE DAAP," \
- " type VARCHAR(255) DEFAULT NULL COLLATE DAAP," \
- " composer VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
- " orchestra VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
- " conductor VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
- " grouping VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
- " url VARCHAR(1024) DEFAULT NULL," \
- " bitrate INTEGER DEFAULT 0," \
- " samplerate INTEGER DEFAULT 0," \
- " song_length INTEGER DEFAULT 0," \
- " file_size INTEGER DEFAULT 0," \
- " year INTEGER DEFAULT 0," \
- " track INTEGER DEFAULT 0," \
- " total_tracks INTEGER DEFAULT 0," \
- " disc INTEGER DEFAULT 0," \
- " total_discs INTEGER DEFAULT 0," \
- " bpm INTEGER DEFAULT 0," \
- " compilation INTEGER DEFAULT 0," \
- " artwork INTEGER DEFAULT 0," \
- " rating INTEGER DEFAULT 0," \
- " play_count INTEGER DEFAULT 0," \
- " seek INTEGER DEFAULT 0," \
- " data_kind INTEGER DEFAULT 0," \
- " item_kind INTEGER DEFAULT 0," \
- " description INTEGER DEFAULT 0," \
- " time_added INTEGER DEFAULT 0," \
- " time_modified INTEGER DEFAULT 0," \
- " time_played INTEGER DEFAULT 0," \
- " db_timestamp INTEGER DEFAULT 0," \
- " disabled INTEGER DEFAULT 0," \
- " sample_count INTEGER DEFAULT 0," \
- " codectype VARCHAR(5) DEFAULT NULL," \
- " idx INTEGER NOT NULL," \
- " has_video INTEGER DEFAULT 0," \
- " contentrating INTEGER DEFAULT 0," \
- " bits_per_sample INTEGER DEFAULT 0," \
- " album_artist VARCHAR(1024) NOT NULL COLLATE DAAP," \
- " media_kind INTEGER NOT NULL," \
- " tv_series_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
- " tv_episode_num_str VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
- " tv_network_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
- " tv_episode_sort INTEGER NOT NULL," \
- " tv_season_num INTEGER NOT NULL," \
- " songartistid INTEGER NOT NULL," \
- " songalbumid INTEGER NOT NULL," \
- " title_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
- " artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
- " album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
- " composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
- " album_artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
- " virtual_path VARCHAR(4096) DEFAULT NULL," \
- " directory_id INTEGER DEFAULT 0," \
- " date_released INTEGER DEFAULT 0" \
- ");"
-
-#define T_PL \
- "CREATE TABLE IF NOT EXISTS playlists (" \
- " id INTEGER PRIMARY KEY NOT NULL," \
- " title VARCHAR(255) NOT NULL COLLATE DAAP," \
- " type INTEGER NOT NULL," \
- " query VARCHAR(1024)," \
- " db_timestamp INTEGER NOT NULL," \
- " disabled INTEGER DEFAULT 0," \
- " path VARCHAR(4096)," \
- " idx INTEGER NOT NULL," \
- " special_id INTEGER DEFAULT 0," \
- " virtual_path VARCHAR(4096)," \
- " parent_id INTEGER DEFAULT 0," \
- " directory_id INTEGER DEFAULT 0" \
- ");"
-
-#define T_PLITEMS \
- "CREATE TABLE IF NOT EXISTS playlistitems (" \
- " id INTEGER PRIMARY KEY NOT NULL," \
- " playlistid INTEGER NOT NULL," \
- " filepath VARCHAR(4096) NOT NULL" \
- ");"
-
-#define T_GROUPS \
- "CREATE TABLE IF NOT EXISTS groups (" \
- " id INTEGER PRIMARY KEY NOT NULL," \
- " type INTEGER NOT NULL," \
- " name VARCHAR(1024) NOT NULL COLLATE DAAP," \
- " persistentid INTEGER NOT NULL," \
- "CONSTRAINT groups_type_unique_persistentid UNIQUE (type, persistentid)" \
- ");"
-
-#define T_PAIRINGS \
- "CREATE TABLE IF NOT EXISTS pairings(" \
- " remote VARCHAR(64) PRIMARY KEY NOT NULL," \
- " name VARCHAR(255) NOT NULL," \
- " guid VARCHAR(16) NOT NULL" \
- ");"
-
-#define T_SPEAKERS \
- "CREATE TABLE IF NOT EXISTS speakers(" \
- " id INTEGER PRIMARY KEY NOT NULL," \
- " selected INTEGER NOT NULL," \
- " volume INTEGER NOT NULL," \
- " name VARCHAR(255) DEFAULT NULL" \
- ");"
-
-#define T_INOTIFY \
- "CREATE TABLE IF NOT EXISTS inotify (" \
- " wd INTEGER PRIMARY KEY NOT NULL," \
- " cookie INTEGER NOT NULL," \
- " path VARCHAR(4096) NOT NULL" \
- ");"
-
-#define T_DIRECTORIES \
- "CREATE TABLE IF NOT EXISTS directories (" \
- " id INTEGER PRIMARY KEY NOT NULL," \
- " virtual_path VARCHAR(4096) NOT NULL," \
- " db_timestamp INTEGER DEFAULT 0," \
- " disabled INTEGER DEFAULT 0," \
- " parent_id INTEGER DEFAULT 0" \
- ");"
-
-#define TRG_GROUPS_INSERT_FILES \
- "CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \
- " BEGIN" \
- " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \
- " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (2, NEW.album_artist, NEW.songartistid);" \
- " END;"
-
-#define TRG_GROUPS_UPDATE_FILES \
- "CREATE TRIGGER update_groups_update_file AFTER UPDATE OF songalbumid ON files FOR EACH ROW" \
- " BEGIN" \
- " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \
- " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (2, NEW.album_artist, NEW.songartistid);" \
- " END;"
-
-#define Q_PL1 \
- "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
- " VALUES(1, 'Library', 0, '1 = 1', 0, '', 0, 0);"
-
-#define Q_PL2 \
- "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
- " VALUES(2, 'Music', 0, 'f.media_kind = 1', 0, '', 0, 6);"
-
-#define Q_PL3 \
- "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
- " VALUES(3, 'Movies', 0, 'f.media_kind = 2', 0, '', 0, 4);"
-
-#define Q_PL4 \
- "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
- " VALUES(4, 'TV Shows', 0, 'f.media_kind = 64', 0, '', 0, 5);"
-
-#define Q_PL5 \
- "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
- " VALUES(5, 'Podcasts', 0, 'f.media_kind = 4', 0, '', 0, 1);"
-
-#define Q_PL6 \
- "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
- " VALUES(6, 'Audiobooks', 0, 'f.media_kind = 8', 0, '', 0, 7);"
-
-/* These are the remaining automatically-created iTunes playlists, but
- * their query is unknown
- " VALUES(6, 'iTunes U', 0, 'media_kind = 256', 0, '', 0, 13);"
- " VALUES(8, 'Purchased', 0, 'media_kind = 1024', 0, '', 0, 8);"
- */
-
-
-#define Q_DIR1 \
- "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \
- " VALUES (1, '/', 0, 0, 0);"
-#define Q_DIR2 \
- "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \
- " VALUES (2, '/file:', 0, 0, 1);"
-#define Q_DIR3 \
- "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \
- " VALUES (3, '/http:', 0, 0, 1);"
-#define Q_DIR4 \
- "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \
- " VALUES (4, '/spotify:', 0, 4294967296, 1);"
-
-/* Rule of thumb: Will the current version of forked-daapd work with the new
- * version of the database? If yes, then it is a minor upgrade, if no, then it
- * is a major upgrade. In other words minor version upgrades permit downgrading
- * forked-daapd after the database was upgraded. */
-#define SCHEMA_VERSION_MAJOR 19
-#define SCHEMA_VERSION_MINOR 00
-#define Q_SCVER_MAJOR \
- "INSERT INTO admin (key, value) VALUES ('schema_version_major', '19');"
-#define Q_SCVER_MINOR \
- "INSERT INTO admin (key, value) VALUES ('schema_version_minor', '00');"
-
-struct db_init_query {
- char *query;
- char *desc;
-};
-
-static const struct db_init_query db_init_table_queries[] =
- {
- { T_ADMIN, "create table admin" },
- { T_FILES, "create table files" },
- { T_PL, "create table playlists" },
- { T_PLITEMS, "create table playlistitems" },
- { T_GROUPS, "create table groups" },
- { T_PAIRINGS, "create table pairings" },
- { T_SPEAKERS, "create table speakers" },
- { T_INOTIFY, "create table inotify" },
- { T_DIRECTORIES, "create table directories" },
-
- { TRG_GROUPS_INSERT_FILES, "create trigger update_groups_new_file" },
- { TRG_GROUPS_UPDATE_FILES, "create trigger update_groups_update_file" },
-
- { Q_PL1, "create default playlist" },
- { Q_PL2, "create default smart playlist 'Music'" },
- { Q_PL3, "create default smart playlist 'Movies'" },
- { Q_PL4, "create default smart playlist 'TV Shows'" },
- { Q_PL5, "create default smart playlist 'Podcasts'" },
- { Q_PL6, "create default smart playlist 'Audiobooks'" },
- { Q_DIR1, "create default root directory '/'" },
- { Q_DIR2, "create default base directory '/file:'" },
- { Q_DIR3, "create default base directory '/http:'" },
- { Q_DIR4, "create default base directory '/spotify:'" },
-
- { Q_SCVER_MAJOR, "set schema version major" },
- { Q_SCVER_MINOR, "set schema version minor" },
- };
-
-
-/* Indices must be prefixed with idx_ for db_drop_indices() to id them */
-
-#define I_RESCAN \
- "CREATE INDEX IF NOT EXISTS idx_rescan ON files(path, db_timestamp);"
-
-#define I_SONGARTISTID \
- "CREATE INDEX IF NOT EXISTS idx_sari ON files(songartistid);"
-
-/* Used by Q_GROUP_ALBUMS */
-#define I_SONGALBUMID \
- "CREATE INDEX IF NOT EXISTS idx_sali ON files(songalbumid, disabled, media_kind, album_sort, disc, track);"
-
-/* Used by Q_GROUP_ARTISTS */
-#define I_STATEMKINDSARI \
- "CREATE INDEX IF NOT EXISTS idx_state_mkind_sari ON files(disabled, media_kind, songartistid);"
-
-#define I_STATEMKINDSALI \
- "CREATE INDEX IF NOT EXISTS idx_state_mkind_sali ON files(disabled, media_kind, songalbumid);"
-
-#define I_ARTIST \
- "CREATE INDEX IF NOT EXISTS idx_artist ON files(artist, artist_sort);"
-
-#define I_ALBUMARTIST \
- "CREATE INDEX IF NOT EXISTS idx_albumartist ON files(album_artist, album_artist_sort);"
-
-/* Used by Q_BROWSE_COMPOSERS */
-#define I_COMPOSER \
- "CREATE INDEX IF NOT EXISTS idx_composer ON files(disabled, media_kind, composer_sort);"
-
-/* Used by Q_BROWSE_GENRES */
-#define I_GENRE \
- "CREATE INDEX IF NOT EXISTS idx_genre ON files(disabled, media_kind, genre);"
-
-/* Used by Q_PLITEMS for smart playlists */
-#define I_TITLE \
- "CREATE INDEX IF NOT EXISTS idx_title ON files(disabled, media_kind, title_sort);"
-
-#define I_ALBUM \
- "CREATE INDEX IF NOT EXISTS idx_album ON files(album, album_sort);"
-
-#define I_FILELIST \
- "CREATE INDEX IF NOT EXISTS idx_filelist ON files(disabled, virtual_path, time_modified);"
-
-#define I_FILE_DIR \
- "CREATE INDEX IF NOT EXISTS idx_file_dir ON files(disabled, directory_id);"
-
-#define I_PL_PATH \
- "CREATE INDEX IF NOT EXISTS idx_pl_path ON playlists(path);"
-
-#define I_PL_DISABLED \
- "CREATE INDEX IF NOT EXISTS idx_pl_disabled ON playlists(disabled, type, virtual_path, db_timestamp);"
-
-#define I_PL_DIR \
- "CREATE INDEX IF NOT EXISTS idx_pl_dir ON files(disabled, directory_id);"
-
-#define I_FILEPATH \
- "CREATE INDEX IF NOT EXISTS idx_filepath ON playlistitems(filepath ASC);"
-
-#define I_PLITEMID \
- "CREATE INDEX IF NOT EXISTS idx_playlistid ON playlistitems(playlistid, filepath);"
-
-#define I_GRP_PERSIST \
- "CREATE INDEX IF NOT EXISTS idx_grp_persist ON groups(persistentid);"
-
-#define I_PAIRING \
- "CREATE INDEX IF NOT EXISTS idx_pairingguid ON pairings(guid);"
-
-#define I_DIR_VPATH \
- "CREATE INDEX IF NOT EXISTS idx_dir_vpath ON directories(disabled, virtual_path);"
-
-#define I_DIR_PARENT \
- "CREATE INDEX IF NOT EXISTS idx_dir_parentid ON directories(parent_id);"
-
-static const struct db_init_query db_init_index_queries[] =
- {
- { I_RESCAN, "create rescan index" },
- { I_SONGARTISTID, "create songartistid index" },
- { I_SONGALBUMID, "create songalbumid index" },
- { I_STATEMKINDSARI, "create state/mkind/sari index" },
- { I_STATEMKINDSALI, "create state/mkind/sali index" },
-
- { I_ARTIST, "create artist index" },
- { I_ALBUMARTIST, "create album_artist index" },
- { I_COMPOSER, "create composer index" },
- { I_GENRE, "create genre index" },
- { I_TITLE, "create title index" },
- { I_ALBUM, "create album index" },
- { I_FILELIST, "create filelist index" },
- { I_FILE_DIR, "create file dir index" },
-
- { I_PL_PATH, "create playlist path index" },
- { I_PL_DISABLED, "create playlist state index" },
- { I_PL_DIR, "create playlist dir index" },
-
- { I_FILEPATH, "create file path index" },
- { I_PLITEMID, "create playlist id index" },
-
- { I_GRP_PERSIST, "create groups persistentid index" },
-
- { I_PAIRING, "create pairing guid index" },
-
- { I_DIR_VPATH, "create directories disabled_virtualpath index" },
- { I_DIR_PARENT, "create directories parentid index" },
- };
-
-static int
-db_create_indices(void)
-{
- char *errmsg;
- int i;
- int ret;
-
- for (i = 0; i < (sizeof(db_init_index_queries) / sizeof(db_init_index_queries[0])); i++)
- {
- DPRINTF(E_DBG, L_DB, "DB init index query: %s\n", db_init_index_queries[i].desc);
-
- ret = sqlite3_exec(hdl, db_init_index_queries[i].query, NULL, NULL, &errmsg);
- if (ret != SQLITE_OK)
- {
- DPRINTF(E_FATAL, L_DB, "DB init error: %s\n", errmsg);
-
- sqlite3_free(errmsg);
- return -1;
- }
- }
-
- return 0;
-}
-static int
-db_create_tables(void)
-{
- char *errmsg;
- int i;
- int ret;
-
- for (i = 0; i < (sizeof(db_init_table_queries) / sizeof(db_init_table_queries[0])); i++)
- {
- DPRINTF(E_DBG, L_DB, "DB init table query: %s\n", db_init_table_queries[i].desc);
-
- ret = sqlite3_exec(hdl, db_init_table_queries[i].query, NULL, NULL, &errmsg);
- if (ret != SQLITE_OK)
- {
- DPRINTF(E_FATAL, L_DB, "DB init error: %s\n", errmsg);
-
- sqlite3_free(errmsg);
- return -1;
- }
- }
-
- ret = db_create_indices();
-
- return ret;
-}
-
static int
@@ -5277,7 +4887,7 @@ db_check_version(void)
return -1;
}
- ret = db_create_indices();
+ ret = db_init_indices(hdl);
if (ret < 0)
{
DPRINTF(E_LOG, L_DB, "Database upgrade errored out, rolling back changes ...\n");
@@ -5372,7 +4982,7 @@ db_init(void)
{
DPRINTF(E_LOG, L_DB, "Could not check database version, trying DB init\n");
- ret = db_create_tables();
+ ret = db_init_tables(hdl);
if (ret < 0)
{
DPRINTF(E_FATAL, L_DB, "Could not create tables\n");
diff --git a/src/db.h b/src/db.h
index 65ae5ed..be4fc77 100644
--- a/src/db.h
+++ b/src/db.h
@@ -245,6 +245,7 @@ struct group_info {
uint32_t groupalbumcount; /* number of albums (agac) */
char *songalbumartist; /* song album artist (asaa) */
uint64_t songartistid; /* song artist id (asri) */
+ uint32_t song_length;
};
#define gri_offsetof(field) offsetof(struct group_info, field)
@@ -258,6 +259,7 @@ struct db_group_info {
char *groupalbumcount;
char *songalbumartist;
char *songartistid;
+ char *song_length;
};
#define dbgri_offsetof(field) offsetof(struct db_group_info, field)
diff --git a/src/db_init.c b/src/db_init.c
new file mode 100644
index 0000000..6eb5749
--- /dev/null
+++ b/src/db_init.c
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2009-2011 Julien BLACHE <jb at jblache.org>
+ * Copyright (C) 2010 Kai Elwert <elwertk at googlemail.com>
+ * Copyright (C) 2016 Christian Meffert <christian.meffert at googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <sqlite3.h>
+#include <stddef.h>
+
+#include "db_init.h"
+#include "logger.h"
+
+
+#define T_ADMIN \
+ "CREATE TABLE IF NOT EXISTS admin(" \
+ " key VARCHAR(32) NOT NULL," \
+ " value VARCHAR(32) NOT NULL" \
+ ");"
+
+#define T_FILES \
+ "CREATE TABLE IF NOT EXISTS files (" \
+ " id INTEGER PRIMARY KEY NOT NULL," \
+ " path VARCHAR(4096) NOT NULL," \
+ " fname VARCHAR(255) NOT NULL," \
+ " title VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
+ " artist VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
+ " album VARCHAR(1024) NOT NULL COLLATE DAAP," \
+ " genre VARCHAR(255) DEFAULT NULL COLLATE DAAP," \
+ " comment VARCHAR(4096) DEFAULT NULL COLLATE DAAP," \
+ " type VARCHAR(255) DEFAULT NULL COLLATE DAAP," \
+ " composer VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
+ " orchestra VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
+ " conductor VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
+ " grouping VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
+ " url VARCHAR(1024) DEFAULT NULL," \
+ " bitrate INTEGER DEFAULT 0," \
+ " samplerate INTEGER DEFAULT 0," \
+ " song_length INTEGER DEFAULT 0," \
+ " file_size INTEGER DEFAULT 0," \
+ " year INTEGER DEFAULT 0," \
+ " track INTEGER DEFAULT 0," \
+ " total_tracks INTEGER DEFAULT 0," \
+ " disc INTEGER DEFAULT 0," \
+ " total_discs INTEGER DEFAULT 0," \
+ " bpm INTEGER DEFAULT 0," \
+ " compilation INTEGER DEFAULT 0," \
+ " artwork INTEGER DEFAULT 0," \
+ " rating INTEGER DEFAULT 0," \
+ " play_count INTEGER DEFAULT 0," \
+ " seek INTEGER DEFAULT 0," \
+ " data_kind INTEGER DEFAULT 0," \
+ " item_kind INTEGER DEFAULT 0," \
+ " description INTEGER DEFAULT 0," \
+ " time_added INTEGER DEFAULT 0," \
+ " time_modified INTEGER DEFAULT 0," \
+ " time_played INTEGER DEFAULT 0," \
+ " db_timestamp INTEGER DEFAULT 0," \
+ " disabled INTEGER DEFAULT 0," \
+ " sample_count INTEGER DEFAULT 0," \
+ " codectype VARCHAR(5) DEFAULT NULL," \
+ " idx INTEGER NOT NULL," \
+ " has_video INTEGER DEFAULT 0," \
+ " contentrating INTEGER DEFAULT 0," \
+ " bits_per_sample INTEGER DEFAULT 0," \
+ " album_artist VARCHAR(1024) NOT NULL COLLATE DAAP," \
+ " media_kind INTEGER NOT NULL," \
+ " tv_series_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
+ " tv_episode_num_str VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
+ " tv_network_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
+ " tv_episode_sort INTEGER NOT NULL," \
+ " tv_season_num INTEGER NOT NULL," \
+ " songartistid INTEGER NOT NULL," \
+ " songalbumid INTEGER NOT NULL," \
+ " title_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
+ " artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
+ " album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
+ " composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
+ " album_artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
+ " virtual_path VARCHAR(4096) DEFAULT NULL," \
+ " directory_id INTEGER DEFAULT 0," \
+ " date_released INTEGER DEFAULT 0" \
+ ");"
+
+#define T_PL \
+ "CREATE TABLE IF NOT EXISTS playlists (" \
+ " id INTEGER PRIMARY KEY NOT NULL," \
+ " title VARCHAR(255) NOT NULL COLLATE DAAP," \
+ " type INTEGER NOT NULL," \
+ " query VARCHAR(1024)," \
+ " db_timestamp INTEGER NOT NULL," \
+ " disabled INTEGER DEFAULT 0," \
+ " path VARCHAR(4096)," \
+ " idx INTEGER NOT NULL," \
+ " special_id INTEGER DEFAULT 0," \
+ " virtual_path VARCHAR(4096)," \
+ " parent_id INTEGER DEFAULT 0," \
+ " directory_id INTEGER DEFAULT 0" \
+ ");"
+
+#define T_PLITEMS \
+ "CREATE TABLE IF NOT EXISTS playlistitems (" \
+ " id INTEGER PRIMARY KEY NOT NULL," \
+ " playlistid INTEGER NOT NULL," \
+ " filepath VARCHAR(4096) NOT NULL" \
+ ");"
+
+#define T_GROUPS \
+ "CREATE TABLE IF NOT EXISTS groups (" \
+ " id INTEGER PRIMARY KEY NOT NULL," \
+ " type INTEGER NOT NULL," \
+ " name VARCHAR(1024) NOT NULL COLLATE DAAP," \
+ " persistentid INTEGER NOT NULL," \
+ "CONSTRAINT groups_type_unique_persistentid UNIQUE (type, persistentid)" \
+ ");"
+
+#define T_PAIRINGS \
+ "CREATE TABLE IF NOT EXISTS pairings(" \
+ " remote VARCHAR(64) PRIMARY KEY NOT NULL," \
+ " name VARCHAR(255) NOT NULL," \
+ " guid VARCHAR(16) NOT NULL" \
+ ");"
+
+#define T_SPEAKERS \
+ "CREATE TABLE IF NOT EXISTS speakers(" \
+ " id INTEGER PRIMARY KEY NOT NULL," \
+ " selected INTEGER NOT NULL," \
+ " volume INTEGER NOT NULL," \
+ " name VARCHAR(255) DEFAULT NULL" \
+ ");"
+
+#define T_INOTIFY \
+ "CREATE TABLE IF NOT EXISTS inotify (" \
+ " wd INTEGER PRIMARY KEY NOT NULL," \
+ " cookie INTEGER NOT NULL," \
+ " path VARCHAR(4096) NOT NULL" \
+ ");"
+
+#define T_DIRECTORIES \
+ "CREATE TABLE IF NOT EXISTS directories (" \
+ " id INTEGER PRIMARY KEY NOT NULL," \
+ " virtual_path VARCHAR(4096) NOT NULL," \
+ " db_timestamp INTEGER DEFAULT 0," \
+ " disabled INTEGER DEFAULT 0," \
+ " parent_id INTEGER DEFAULT 0" \
+ ");"
+
+#define TRG_GROUPS_INSERT_FILES \
+ "CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \
+ " BEGIN" \
+ " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \
+ " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (2, NEW.album_artist, NEW.songartistid);" \
+ " END;"
+
+#define TRG_GROUPS_UPDATE_FILES \
+ "CREATE TRIGGER update_groups_update_file AFTER UPDATE OF songalbumid ON files FOR EACH ROW" \
+ " BEGIN" \
+ " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \
+ " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (2, NEW.album_artist, NEW.songartistid);" \
+ " END;"
+
+#define Q_PL1 \
+ "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
+ " VALUES(1, 'Library', 0, '1 = 1', 0, '', 0, 0);"
+
+#define Q_PL2 \
+ "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
+ " VALUES(2, 'Music', 0, 'f.media_kind = 1', 0, '', 0, 6);"
+
+#define Q_PL3 \
+ "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
+ " VALUES(3, 'Movies', 0, 'f.media_kind = 2', 0, '', 0, 4);"
+
+#define Q_PL4 \
+ "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
+ " VALUES(4, 'TV Shows', 0, 'f.media_kind = 64', 0, '', 0, 5);"
+
+#define Q_PL5 \
+ "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
+ " VALUES(5, 'Podcasts', 0, 'f.media_kind = 4', 0, '', 0, 1);"
+
+#define Q_PL6 \
+ "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
+ " VALUES(6, 'Audiobooks', 0, 'f.media_kind = 8', 0, '', 0, 7);"
+
+/* These are the remaining automatically-created iTunes playlists, but
+ * their query is unknown
+ " VALUES(6, 'iTunes U', 0, 'media_kind = 256', 0, '', 0, 13);"
+ " VALUES(8, 'Purchased', 0, 'media_kind = 1024', 0, '', 0, 8);"
+ */
+
+
+#define Q_DIR1 \
+ "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \
+ " VALUES (1, '/', 0, 0, 0);"
+#define Q_DIR2 \
+ "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \
+ " VALUES (2, '/file:', 0, 0, 1);"
+#define Q_DIR3 \
+ "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \
+ " VALUES (3, '/http:', 0, 0, 1);"
+#define Q_DIR4 \
+ "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \
+ " VALUES (4, '/spotify:', 0, 4294967296, 1);"
+
+#define Q_SCVER_MAJOR \
+ "INSERT INTO admin (key, value) VALUES ('schema_version_major', '%d');"
+#define Q_SCVER_MINOR \
+ "INSERT INTO admin (key, value) VALUES ('schema_version_minor', '%02d');"
+
+struct db_init_query {
+ char *query;
+ char *desc;
+};
+
+static const struct db_init_query db_init_table_queries[] =
+ {
+ { T_ADMIN, "create table admin" },
+ { T_FILES, "create table files" },
+ { T_PL, "create table playlists" },
+ { T_PLITEMS, "create table playlistitems" },
+ { T_GROUPS, "create table groups" },
+ { T_PAIRINGS, "create table pairings" },
+ { T_SPEAKERS, "create table speakers" },
+ { T_INOTIFY, "create table inotify" },
+ { T_DIRECTORIES, "create table directories" },
+
+ { TRG_GROUPS_INSERT_FILES, "create trigger update_groups_new_file" },
+ { TRG_GROUPS_UPDATE_FILES, "create trigger update_groups_update_file" },
+
+ { Q_PL1, "create default playlist" },
+ { Q_PL2, "create default smart playlist 'Music'" },
+ { Q_PL3, "create default smart playlist 'Movies'" },
+ { Q_PL4, "create default smart playlist 'TV Shows'" },
+ { Q_PL5, "create default smart playlist 'Podcasts'" },
+ { Q_PL6, "create default smart playlist 'Audiobooks'" },
+ { Q_DIR1, "create default root directory '/'" },
+ { Q_DIR2, "create default base directory '/file:'" },
+ { Q_DIR3, "create default base directory '/http:'" },
+ { Q_DIR4, "create default base directory '/spotify:'" },
+ };
+
+
+/* Indices must be prefixed with idx_ for db_drop_indices() to id them */
+
+#define I_RESCAN \
+ "CREATE INDEX IF NOT EXISTS idx_rescan ON files(path, db_timestamp);"
+
+#define I_SONGARTISTID \
+ "CREATE INDEX IF NOT EXISTS idx_sari ON files(songartistid);"
+
+/* Used by Q_GROUP_ALBUMS */
+#define I_SONGALBUMID \
+ "CREATE INDEX IF NOT EXISTS idx_sali ON files(songalbumid, disabled, media_kind, album_sort, disc, track);"
+
+/* Used by Q_GROUP_ARTISTS */
+#define I_STATEMKINDSARI \
+ "CREATE INDEX IF NOT EXISTS idx_state_mkind_sari ON files(disabled, media_kind, songartistid);"
+
+#define I_STATEMKINDSALI \
+ "CREATE INDEX IF NOT EXISTS idx_state_mkind_sali ON files(disabled, media_kind, songalbumid);"
+
+#define I_ARTIST \
+ "CREATE INDEX IF NOT EXISTS idx_artist ON files(artist, artist_sort);"
+
+#define I_ALBUMARTIST \
+ "CREATE INDEX IF NOT EXISTS idx_albumartist ON files(album_artist, album_artist_sort);"
+
+/* Used by Q_BROWSE_COMPOSERS */
+#define I_COMPOSER \
+ "CREATE INDEX IF NOT EXISTS idx_composer ON files(disabled, media_kind, composer_sort);"
+
+/* Used by Q_BROWSE_GENRES */
+#define I_GENRE \
+ "CREATE INDEX IF NOT EXISTS idx_genre ON files(disabled, media_kind, genre);"
+
+/* Used by Q_PLITEMS for smart playlists */
+#define I_TITLE \
+ "CREATE INDEX IF NOT EXISTS idx_title ON files(disabled, media_kind, title_sort);"
+
+#define I_ALBUM \
+ "CREATE INDEX IF NOT EXISTS idx_album ON files(album, album_sort);"
+
+#define I_FILELIST \
+ "CREATE INDEX IF NOT EXISTS idx_filelist ON files(disabled, virtual_path, time_modified);"
+
+#define I_FILE_DIR \
+ "CREATE INDEX IF NOT EXISTS idx_file_dir ON files(disabled, directory_id);"
+
+#define I_PL_PATH \
+ "CREATE INDEX IF NOT EXISTS idx_pl_path ON playlists(path);"
+
+#define I_PL_DISABLED \
+ "CREATE INDEX IF NOT EXISTS idx_pl_disabled ON playlists(disabled, type, virtual_path, db_timestamp);"
+
+#define I_PL_DIR \
+ "CREATE INDEX IF NOT EXISTS idx_pl_dir ON files(disabled, directory_id);"
+
+#define I_FILEPATH \
+ "CREATE INDEX IF NOT EXISTS idx_filepath ON playlistitems(filepath ASC);"
+
+#define I_PLITEMID \
+ "CREATE INDEX IF NOT EXISTS idx_playlistid ON playlistitems(playlistid, filepath);"
+
+#define I_GRP_PERSIST \
+ "CREATE INDEX IF NOT EXISTS idx_grp_persist ON groups(persistentid);"
+
+#define I_PAIRING \
+ "CREATE INDEX IF NOT EXISTS idx_pairingguid ON pairings(guid);"
+
+#define I_DIR_VPATH \
+ "CREATE INDEX IF NOT EXISTS idx_dir_vpath ON directories(disabled, virtual_path);"
+
+#define I_DIR_PARENT \
+ "CREATE INDEX IF NOT EXISTS idx_dir_parentid ON directories(parent_id);"
+
+static const struct db_init_query db_init_index_queries[] =
+ {
+ { I_RESCAN, "create rescan index" },
+ { I_SONGARTISTID, "create songartistid index" },
+ { I_SONGALBUMID, "create songalbumid index" },
+ { I_STATEMKINDSARI, "create state/mkind/sari index" },
+ { I_STATEMKINDSALI, "create state/mkind/sali index" },
+
+ { I_ARTIST, "create artist index" },
+ { I_ALBUMARTIST, "create album_artist index" },
+ { I_COMPOSER, "create composer index" },
+ { I_GENRE, "create genre index" },
+ { I_TITLE, "create title index" },
+ { I_ALBUM, "create album index" },
+ { I_FILELIST, "create filelist index" },
+ { I_FILE_DIR, "create file dir index" },
+
+ { I_PL_PATH, "create playlist path index" },
+ { I_PL_DISABLED, "create playlist state index" },
+ { I_PL_DIR, "create playlist dir index" },
+
+ { I_FILEPATH, "create file path index" },
+ { I_PLITEMID, "create playlist id index" },
+
+ { I_GRP_PERSIST, "create groups persistentid index" },
+
+ { I_PAIRING, "create pairing guid index" },
+
+ { I_DIR_VPATH, "create directories disabled_virtualpath index" },
+ { I_DIR_PARENT, "create directories parentid index" },
+ };
+
+int
+db_init_indices(sqlite3 *hdl)
+{
+ char *errmsg;
+ int i;
+ int ret;
+
+ for (i = 0; i < (sizeof(db_init_index_queries) / sizeof(db_init_index_queries[0])); i++)
+ {
+ DPRINTF(E_DBG, L_DB, "DB init index query: %s\n", db_init_index_queries[i].desc);
+
+ ret = sqlite3_exec(hdl, db_init_index_queries[i].query, NULL, NULL, &errmsg);
+ if (ret != SQLITE_OK)
+ {
+ DPRINTF(E_FATAL, L_DB, "DB init error: %s\n", errmsg);
+
+ sqlite3_free(errmsg);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int
+db_init_tables(sqlite3 *hdl)
+{
+ char *query;
+ char *errmsg;
+ int i;
+ int ret;
+
+ for (i = 0; i < (sizeof(db_init_table_queries) / sizeof(db_init_table_queries[0])); i++)
+ {
+ DPRINTF(E_DBG, L_DB, "DB init table query: %s\n", db_init_table_queries[i].desc);
+
+ ret = sqlite3_exec(hdl, db_init_table_queries[i].query, NULL, NULL, &errmsg);
+ if (ret != SQLITE_OK)
+ {
+ DPRINTF(E_FATAL, L_DB, "DB init error: %s\n", errmsg);
+
+ sqlite3_free(errmsg);
+ return -1;
+ }
+ }
+
+ query = sqlite3_mprintf(Q_SCVER_MAJOR, SCHEMA_VERSION_MAJOR);
+ DPRINTF(E_DBG, L_DB, "DB init table query: %s\n", query);
+
+ ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
+ sqlite3_free(query);
+ if (ret != SQLITE_OK)
+ {
+ DPRINTF(E_FATAL, L_DB, "DB init error: %s\n", errmsg);
+ sqlite3_free(errmsg);
+ return -1;
+ }
+
+ query = sqlite3_mprintf(Q_SCVER_MINOR, SCHEMA_VERSION_MINOR);
+ DPRINTF(E_DBG, L_DB, "DB init table query: %s\n", query);
+
+ ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
+ sqlite3_free(query);
+ if (ret != SQLITE_OK)
+ {
+ DPRINTF(E_FATAL, L_DB, "DB init error: %s\n", errmsg);
+ sqlite3_free(errmsg);
+ return -1;
+ }
+
+ ret = db_init_indices(hdl);
+
+ return ret;
+}
+
diff --git a/src/db_upgrade.h b/src/db_init.h
similarity index 62%
copy from src/db_upgrade.h
copy to src/db_init.h
index e75a4f1..befa677 100644
--- a/src/db_upgrade.h
+++ b/src/db_init.h
@@ -16,12 +16,22 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
-#ifndef SRC_DB_UPGRADE_H_
-#define SRC_DB_UPGRADE_H_
+#ifndef SRC_DB_INIT_H_
+#define SRC_DB_INIT_H_
#include <sqlite3.h>
+/* Rule of thumb: Will the current version of forked-daapd work with the new
+ * version of the database? If yes, then it is a minor upgrade, if no, then it
+ * is a major upgrade. In other words minor version upgrades permit downgrading
+ * forked-daapd after the database was upgraded. */
+#define SCHEMA_VERSION_MAJOR 19
+#define SCHEMA_VERSION_MINOR 00
+
+int
+db_init_indices(sqlite3 *hdl);
+
int
-db_upgrade(sqlite3 *hdl, int db_ver);
+db_init_tables(sqlite3 *hdl);
-#endif /* SRC_DB_UPGRADE_H_ */
+#endif /* SRC_DB_INIT_H_ */
diff --git a/src/dmap_common.c b/src/dmap_common.c
index 04dbcff..495a7bf 100644
--- a/src/dmap_common.c
+++ b/src/dmap_common.c
@@ -25,6 +25,7 @@
#include "db.h"
#include "misc.h"
+#include "httpd.h"
#include "logger.h"
#include "dmap_common.h"
@@ -355,7 +356,7 @@ dmap_send_error(struct evhttp_request *req, const char *container, const char *e
{
DPRINTF(E_LOG, L_DMAP, "Could not allocate evbuffer for DMAP error\n");
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
+ httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
return;
}
@@ -366,7 +367,7 @@ dmap_send_error(struct evhttp_request *req, const char *container, const char *e
{
DPRINTF(E_LOG, L_DMAP, "Could not expand evbuffer for DMAP error\n");
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
+ httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
evbuffer_free(evbuf);
return;
@@ -376,7 +377,7 @@ dmap_send_error(struct evhttp_request *req, const char *container, const char *e
dmap_add_int(evbuf, "mstt", 500);
dmap_add_string(evbuf, "msts", errmsg);
- evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP);
evbuffer_free(evbuf);
}
diff --git a/src/dmap_fields.gperf b/src/dmap_fields.gperf
index 7f0d87e..0119f02 100644
--- a/src/dmap_fields.gperf
+++ b/src/dmap_fields.gperf
@@ -92,7 +92,7 @@ static const struct dmap_field_map dfm_dmap_asst = { -1,
static const struct dmap_field_map dfm_dmap_assz = { dbmfi_offsetof(file_size), -1, -1 };
static const struct dmap_field_map dfm_dmap_asrv = { -1, -1, -1 };
static const struct dmap_field_map dfm_dmap_astc = { dbmfi_offsetof(total_tracks), -1, -1 };
-static const struct dmap_field_map dfm_dmap_astm = { dbmfi_offsetof(song_length), -1, -1 };
+static const struct dmap_field_map dfm_dmap_astm = { dbmfi_offsetof(song_length), -1, dbgri_offsetof(song_length) };
static const struct dmap_field_map dfm_dmap_astn = { dbmfi_offsetof(track), -1, -1 };
static const struct dmap_field_map dfm_dmap_asul = { dbmfi_offsetof(url), -1, -1 };
static const struct dmap_field_map dfm_dmap_asur = { dbmfi_offsetof(rating), -1, -1 };
diff --git a/src/filescanner.c b/src/filescanner.c
index b6324dc..d53e9f8 100644
--- a/src/filescanner.c
+++ b/src/filescanner.c
@@ -61,6 +61,7 @@
#include "player.h"
#include "cache.h"
#include "artwork.h"
+#include "commands.h"
#ifdef LASTFM
# include "lastfm.h"
@@ -69,21 +70,6 @@
# include "spotify.h"
#endif
-struct filescanner_command;
-
-typedef int (*cmd_func)(struct filescanner_command *cmd);
-
-struct filescanner_command
-{
- pthread_mutex_t lck;
- pthread_cond_t cond;
-
- cmd_func func;
-
- int nonblock;
-
- int ret;
-};
#define F_SCAN_BULK (1 << 0)
#define F_SCAN_RESCAN (1 << 1)
@@ -118,17 +104,14 @@ struct stacked_dir {
struct stacked_dir *next;
};
-static int cmd_pipe[2];
-static int exit_pipe[2];
static int scan_exit;
static int inofd;
static struct event_base *evbase_scan;
static struct event *inoev;
-static struct event *exitev;
-static struct event *cmdev;
static pthread_t tid_scan;
static struct deferred_pl *playlists;
static struct stacked_dir *dirstack;
+static struct commands_base *cmdbase;
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
struct deferred_file
@@ -167,46 +150,11 @@ static int
inofd_event_set(void);
static void
inofd_event_unset(void);
-static int
-filescanner_initscan(struct filescanner_command *cmd);
-static int
-filescanner_fullrescan(struct filescanner_command *cmd);
-
-
-/* ---------------------------- COMMAND EXECUTION -------------------------- */
-
-static int
-send_command(struct filescanner_command *cmd)
-{
- int ret;
-
- if (!cmd->func)
- {
- DPRINTF(E_LOG, L_SCAN, "BUG: cmd->func is NULL!\n");
- return -1;
- }
-
- ret = write(cmd_pipe[1], &cmd, sizeof(cmd));
- if (ret != sizeof(cmd))
- {
- DPRINTF(E_LOG, L_SCAN, "Could not send command: %s\n", strerror(errno));
- return -1;
- }
-
- return 0;
-}
-
-static int
-nonblock_command(struct filescanner_command *cmd)
-{
- int ret;
+static enum command_state
+filescanner_initscan(void *arg, int *retval);
+static enum command_state
+filescanner_fullrescan(void *arg, int *retval);
- ret = send_command(cmd);
- if (ret < 0)
- return -1;
-
- return 0;
-}
static int
push_dir(struct stacked_dir **s, char *path, int parent_id)
@@ -326,6 +274,9 @@ file_type_get(const char *path) {
if (!ext || (strlen(ext) == 1))
return FILE_REGULAR;
+ if (file_type_ignore(ext))
+ return FILE_IGNORE;
+
if ((strcasecmp(ext, ".m3u") == 0) || (strcasecmp(ext, ".pls") == 0))
return FILE_PLAYLIST;
@@ -364,9 +315,6 @@ file_type_get(const char *path) {
return FILE_IGNORE;
}
- if (file_type_ignore(ext))
- return FILE_IGNORE;
-
if ((filename[0] == '_') || (filename[0] == '.'))
return FILE_IGNORE;
@@ -714,7 +662,7 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct
ret = scan_metadata_ffmpeg(path, mfi);
if (ret < 0)
{
- DPRINTF(E_LOG, L_SCAN, "Playlist URL is unavailable for probe/metadata, assuming MP3 encoding\n");
+ DPRINTF(E_LOG, L_SCAN, "Playlist URL '%s' is unavailable for probe/metadata, assuming MP3 encoding\n", path);
mfi->type = strdup("mp3");
mfi->codectype = strdup("mpeg");
mfi->description = strdup("MPEG audio file");
@@ -736,13 +684,13 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct
}
else
{
- DPRINTF(E_LOG, L_SCAN, "Unknown scan type for %s, this error should not occur\n", path);
+ DPRINTF(E_LOG, L_SCAN, "Unknown scan type for '%s', this error should not occur\n", path);
ret = -1;
}
if (ret < 0)
{
- DPRINTF(E_INFO, L_SCAN, "Could not extract metadata for %s\n", path);
+ DPRINTF(E_INFO, L_SCAN, "Could not extract metadata for '%s'\n", path);
goto out;
}
@@ -855,6 +803,7 @@ static void
process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_id)
{
int is_bulkscan;
+ int ret;
is_bulkscan = (flags & F_SCAN_BULK);
@@ -924,7 +873,7 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_
DPRINTF(E_LOG, L_SCAN, "Startup rescan triggered, found init-rescan file: %s\n", file);
- filescanner_initscan(NULL);
+ filescanner_initscan(NULL, &ret);
break;
case FILE_CTRL_FULLSCAN:
@@ -933,7 +882,7 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_
DPRINTF(E_LOG, L_SCAN, "Full rescan triggered, found full-rescan file: %s\n", file);
- filescanner_fullrescan(NULL);
+ filescanner_fullrescan(NULL, &ret);
break;
default:
@@ -1143,6 +1092,12 @@ process_parent_directories(char *path)
ptr = path + 1;
while (ptr && (ptr = strchr(ptr, '/')))
{
+ if (strlen(ptr) <= 1)
+ {
+ // Do not process trailing '/'
+ break;
+ }
+
strncpy(buf, path, (ptr - path));
buf[(ptr - path)] = '\0';
@@ -1960,49 +1915,9 @@ inofd_event_unset(void)
}
/* Thread: scan */
-static void
-exit_cb(int fd, short event, void *arg)
-{
- event_base_loopbreak(evbase_scan);
-
- scan_exit = 1;
-}
-
-static void
-command_cb(int fd, short what, void *arg)
-{
- struct filescanner_command *cmd;
- int ret;
- ret = read(cmd_pipe[0], &cmd, sizeof(cmd));
- if (ret != sizeof(cmd))
- {
- DPRINTF(E_LOG, L_SCAN, "Could not read command! (read %d): %s\n", ret, (ret < 0) ? strerror(errno) : "-no error-");
- goto readd;
- }
-
- if (cmd->nonblock)
- {
- cmd->func(cmd);
-
- free(cmd);
- goto readd;
- }
-
- pthread_mutex_lock(&cmd->lck);
-
- ret = cmd->func(cmd);
- cmd->ret = ret;
-
- pthread_cond_signal(&cmd->cond);
- pthread_mutex_unlock(&cmd->lck);
-
- readd:
- event_add(cmdev, NULL);
-}
-
-static int
-filescanner_initscan(struct filescanner_command *cmd)
+static enum command_state
+filescanner_initscan(void *arg, int *retval)
{
DPRINTF(E_LOG, L_SCAN, "Startup rescan triggered\n");
@@ -2012,11 +1927,12 @@ filescanner_initscan(struct filescanner_command *cmd)
inofd_event_set();
bulk_scan(F_SCAN_BULK | F_SCAN_RESCAN);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-filescanner_fullrescan(struct filescanner_command *cmd)
+static enum command_state
+filescanner_fullrescan(void *arg, int *retval)
{
DPRINTF(E_LOG, L_SCAN, "Full rescan triggered\n");
@@ -2028,62 +1944,32 @@ filescanner_fullrescan(struct filescanner_command *cmd)
inofd_event_set();
bulk_scan(F_SCAN_BULK);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
void
filescanner_trigger_initscan(void)
{
- struct filescanner_command *cmd;
-
if (scanning)
{
DPRINTF(E_INFO, L_SCAN, "Scan already running, ignoring request to trigger a new init scan\n");
return;
}
-
- cmd = (struct filescanner_command *)malloc(sizeof(struct filescanner_command));
- if (!cmd)
- {
- DPRINTF(E_LOG, L_SCAN, "Could not allocate cache_command\n");
- return;
- }
-
- memset(cmd, 0, sizeof(struct filescanner_command));
-
- cmd->nonblock = 1;
-
- cmd->func = filescanner_initscan;
-
- nonblock_command(cmd);
+ commands_exec_async(cmdbase, filescanner_initscan, NULL);
}
void
filescanner_trigger_fullrescan(void)
{
- struct filescanner_command *cmd;
-
if (scanning)
{
DPRINTF(E_INFO, L_SCAN, "Scan already running, ignoring request to trigger a new init scan\n");
return;
}
- cmd = (struct filescanner_command *)malloc(sizeof(struct filescanner_command));
- if (!cmd)
- {
- DPRINTF(E_LOG, L_SCAN, "Could not allocate cache_command\n");
- return;
- }
-
- memset(cmd, 0, sizeof(struct filescanner_command));
-
- cmd->nonblock = 1;
-
- cmd->func = filescanner_fullrescan;
-
- nonblock_command(cmd);
+ commands_exec_async(cmdbase, filescanner_fullrescan, NULL);
}
/*
@@ -2113,48 +1999,13 @@ filescanner_init(void)
return -1;
}
-#ifdef HAVE_PIPE2
- ret = pipe2(exit_pipe, O_CLOEXEC);
-#else
- ret = pipe(exit_pipe);
-#endif
- if (ret < 0)
- {
- DPRINTF(E_FATAL, L_SCAN, "Could not create pipe: %s\n", strerror(errno));
-
- goto pipe_fail;
- }
-
- exitev = event_new(evbase_scan, exit_pipe[0], EV_READ, exit_cb, NULL);
- if (!exitev || (event_add(exitev, NULL) < 0))
- {
- DPRINTF(E_LOG, L_SCAN, "Could not create/add command event\n");
- goto exitev_fail;
- }
-
ret = inofd_event_set();
if (ret < 0)
{
goto ino_fail;
}
-#ifdef HAVE_PIPE2
- ret = pipe2(cmd_pipe, O_CLOEXEC);
-#else
- ret = pipe(cmd_pipe);
-#endif
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_SCAN, "Could not create command pipe: %s\n", strerror(errno));
- goto cmd_fail;
- }
-
- cmdev = event_new(evbase_scan, cmd_pipe[0], EV_READ, command_cb, NULL);
- if (!cmdev || (event_add(cmdev, NULL) < 0))
- {
- DPRINTF(E_LOG, L_SCAN, "Could not create/add command event\n");
- goto cmd_fail;
- }
+ cmdbase = commands_base_new(evbase_scan, NULL);
ret = pthread_create(&tid_scan, NULL, filescanner, NULL);
if (ret != 0)
@@ -2173,15 +2024,9 @@ filescanner_init(void)
return 0;
thread_fail:
- cmd_fail:
- close(cmd_pipe[0]);
- close(cmd_pipe[1]);
+ commands_base_free(cmdbase);
close(inofd);
- exitev_fail:
ino_fail:
- close(exit_pipe[0]);
- close(exit_pipe[1]);
- pipe_fail:
event_base_free(evbase_scan);
return -1;
@@ -2192,17 +2037,9 @@ void
filescanner_deinit(void)
{
int ret;
- int dummy = 42;
-
- ret = write(exit_pipe[1], &dummy, sizeof(dummy));
- if (ret != sizeof(dummy))
- {
- DPRINTF(E_FATAL, L_SCAN, "Could not write to exit fd: %s\n", strerror(errno));
-
- return;
- }
scan_exit = 1;
+ commands_base_destroy(cmdbase);
ret = pthread_join(tid_scan, NULL);
if (ret != 0)
@@ -2214,9 +2051,5 @@ filescanner_deinit(void)
inofd_event_unset();
- close(exit_pipe[0]);
- close(exit_pipe[1]);
- close(cmd_pipe[0]);
- close(cmd_pipe[1]);
event_base_free(evbase_scan);
}
diff --git a/src/filescanner_ffmpeg.c b/src/filescanner_ffmpeg.c
index 6548574..e5103aa 100644
--- a/src/filescanner_ffmpeg.c
+++ b/src/filescanner_ffmpeg.c
@@ -25,8 +25,6 @@
#include <string.h>
#include <time.h>
-#include <errno.h>
-
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
@@ -49,6 +47,16 @@ struct metadata_map {
int (*handler_function)(struct media_file_info *, char *);
};
+// Used for passing errors to DPRINTF (can't count on av_err2str being present)
+static char errbuf[64];
+
+static inline char *
+err2str(int errnum)
+{
+ av_strerror(errnum, errbuf, sizeof(errbuf));
+ return errbuf;
+}
+
static int
parse_slash_separated_ints(char *string, uint32_t *firstval, uint32_t *secondval)
{
@@ -401,7 +409,7 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
#endif
if (ret != 0)
{
- DPRINTF(E_WARN, L_SCAN, "Cannot open media file '%s': %s\n", path, strerror(AVUNERROR(ret)));
+ DPRINTF(E_WARN, L_SCAN, "Cannot open media file '%s': %s\n", path, err2str(ret));
free(path);
return -1;
@@ -416,7 +424,7 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
#endif
if (ret < 0)
{
- DPRINTF(E_WARN, L_SCAN, "Cannot get stream info: %s\n", strerror(AVUNERROR(ret)));
+ DPRINTF(E_WARN, L_SCAN, "Cannot get stream info of '%s': %s\n", path, err2str(ret));
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 21)
avformat_close_input(&ctx);
diff --git a/src/filescanner_itunes.c b/src/filescanner_itunes.c
index 8005f53..6abd842 100644
--- a/src/filescanner_itunes.c
+++ b/src/filescanner_itunes.c
@@ -668,9 +668,9 @@ ignore_pl(plist_t pl, char *name)
/* Special (builtin) playlists */
get_dictval_int_from_key(pl, "Distinguished Kind", &kind);
- /* If only we could recover the smart playlists ... */
- if (plist_dict_get_item(pl, "Smart Info")
- || plist_dict_get_item(pl, "Smart Criteria"))
+ /* Import smart playlists (optional) */
+ if (!cfg_getbool(cfg_getsec(cfg, "library"), "itunes_smartpl")
+ && (plist_dict_get_item(pl, "Smart Info") || plist_dict_get_item(pl, "Smart Criteria")))
smart = 1;
/* Not interested in the Master playlist */
diff --git a/src/httpd.c b/src/httpd.c
index be68364..800c5e1 100644
--- a/src/httpd.c
+++ b/src/httpd.c
@@ -54,12 +54,16 @@
#include "db.h"
#include "conffile.h"
#include "misc.h"
+#include "worker.h"
#include "httpd.h"
#include "httpd_rsp.h"
#include "httpd_daap.h"
#include "httpd_dacp.h"
#include "httpd_streaming.h"
#include "transcode.h"
+#ifdef LASTFM
+# include "lastfm.h"
+#endif
/*
* HTTP client quirks by User-Agent, from mt-daapd
@@ -82,6 +86,11 @@
#define STREAM_CHUNK_SIZE (64 * 1024)
#define WEBFACE_ROOT DATADIR "/webface/"
+#define ERR_PAGE "<html>\n<head>\n" \
+ "<title>%d %s</title>\n" \
+ "</head>\n<body>\n" \
+ "<h1>%s</h1>\n" \
+ "</body>\n</html>\n"
struct content_type_map {
char *ext;
@@ -130,6 +139,8 @@ static struct event *exitev;
static struct evhttp *evhttpd;
static pthread_t tid_httpd;
+static char *allow_origin;
+
#ifdef HAVE_LIBEVENT2_OLD
struct stream_ctx *g_st;
#endif
@@ -167,15 +178,38 @@ stream_end(struct stream_ctx *st, int failed)
free(st);
}
+/* Callback from the worker thread (async operation as it may block) */
+static void
+playcount_inc_cb(void *arg)
+{
+ int *id = arg;
+
+ db_file_inc_playcount(*id);
+}
+
+#ifdef LASTFM
+/* Callback from the worker thread (async operation as it may block) */
static void
-stream_up_playcount(struct stream_ctx *st)
+scrobble_cb(void *arg)
+{
+ int *id = arg;
+
+ lastfm_scrobble(*id);
+}
+#endif
+
+static void
+stream_end_register(struct stream_ctx *st)
{
if (!st->marked
&& (st->stream_size > ((st->size * 50) / 100))
&& (st->offset > ((st->size * 80) / 100)))
{
st->marked = 1;
- db_file_inc_playcount(st->id);
+ worker_execute(playcount_inc_cb, &st->id, sizeof(int), 0);
+#ifdef LASTFM
+ worker_execute(scrobble_cb, &st->id, sizeof(int), 1);
+#endif
}
}
@@ -269,7 +303,7 @@ stream_chunk_xcode_cb(int fd, short event, void *arg)
st->offset += ret;
- stream_up_playcount(st);
+ stream_end_register(st);
return;
@@ -335,7 +369,7 @@ stream_chunk_raw_cb(int fd, short event, void *arg)
st->offset += ret;
- stream_up_playcount(st);
+ stream_end_register(st);
}
static void
@@ -666,130 +700,141 @@ httpd_stream_file(struct evhttp_request *req, int id)
free_mfi(mfi, 0);
}
-/* Thread: httpd */
-void
-httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struct evbuffer *evbuf)
+struct evbuffer *
+httpd_gzip_deflate(struct evbuffer *in)
{
- unsigned char outbuf[128 * 1024];
+ struct evbuffer *out;
+ struct evbuffer_iovec iovec[1];
z_stream strm;
- struct evbuffer *gzbuf;
- struct evkeyvalq *headers;
- const char *param;
- int flush;
- int zret;
int ret;
- if (!req)
- return;
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
- if (!evbuf || (evbuffer_get_length(evbuf) == 0))
+ // Set up a gzip stream (the "+ 16" in 15 + 16), instead of a zlib stream (default)
+ ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
+ if (ret != Z_OK)
{
- DPRINTF(E_DBG, L_HTTPD, "Not gzipping body-less reply\n");
-
- goto no_gzip;
+ DPRINTF(E_LOG, L_HTTPD, "zlib setup failed: %s\n", zError(ret));
+ return NULL;
}
- headers = evhttp_request_get_input_headers(req);
-
- param = evhttp_find_header(headers, "Accept-Encoding");
- if (!param)
- {
- DPRINTF(E_DBG, L_HTTPD, "Not gzipping; no Accept-Encoding header\n");
+ strm.next_in = evbuffer_pullup(in, -1);
+ strm.avail_in = evbuffer_get_length(in);
- goto no_gzip;
- }
- else if (!strstr(param, "gzip") && !strstr(param, "*"))
+ out = evbuffer_new();
+ if (!out)
{
- DPRINTF(E_DBG, L_HTTPD, "Not gzipping; gzip not in Accept-Encoding (%s)\n", param);
-
- goto no_gzip;
+ DPRINTF(E_LOG, L_HTTPD, "Could not allocate evbuffer for gzipped reply\n");
+ goto out_deflate_end;
}
- gzbuf = evbuffer_new();
- if (!gzbuf)
+ // We use this to avoid a memcpy. The 512 is an arbitrary padding to make sure
+ // there is enough space, even if the compressed output should be slightly
+ // larger than input (could happen with small inputs).
+ ret = evbuffer_reserve_space(out, strm.avail_in + 512, iovec, 1);
+ if (ret < 0)
{
- DPRINTF(E_LOG, L_HTTPD, "Could not allocate evbuffer for gzipped reply\n");
-
- goto no_gzip;
+ DPRINTF(E_LOG, L_HTTPD, "Could not reserve memory for gzipped reply\n");
+ goto out_evbuf_free;
}
- strm.zalloc = Z_NULL;
- strm.zfree = Z_NULL;
- strm.opaque = Z_NULL;
+ strm.next_out = iovec[0].iov_base;
+ strm.avail_out = iovec[0].iov_len;
- /* Set up a gzip stream (the "+ 16" in 15 + 16), instead of a zlib stream (default) */
- zret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
- if (zret != Z_OK)
- {
- DPRINTF(E_DBG, L_HTTPD, "zlib setup failed: %s\n", zError(zret));
+ ret = deflate(&strm, Z_FINISH);
+ if (ret != Z_STREAM_END)
+ goto out_evbuf_free;
- goto out_fail_init;
- }
+ iovec[0].iov_len -= strm.avail_out;
- strm.next_in = evbuffer_pullup(evbuf, -1);
- strm.avail_in = evbuffer_get_length(evbuf);
+ evbuffer_commit_space(out, iovec, 1);
+ deflateEnd(&strm);
- flush = Z_NO_FLUSH;
+ return out;
- /* 2 iterations: Z_NO_FLUSH until input is consumed, then Z_FINISH */
- for (;;)
- {
- do
- {
- strm.next_out = outbuf;
- strm.avail_out = sizeof(outbuf);
+ out_evbuf_free:
+ evbuffer_free(out);
- zret = deflate(&strm, flush);
- if (zret == Z_STREAM_ERROR)
- {
- DPRINTF(E_LOG, L_HTTPD, "Could not deflate data: %s\n", strm.msg);
+ out_deflate_end:
+ deflateEnd(&strm);
- goto out_fail_gz;
- }
+ return NULL;
+}
- ret = evbuffer_add(gzbuf, outbuf, sizeof(outbuf) - strm.avail_out);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_HTTPD, "Out of memory adding gzipped data to evbuffer\n");
+void
+httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struct evbuffer *evbuf, enum httpd_send_flags flags)
+{
+ struct evbuffer *gzbuf;
+ struct evkeyvalq *input_headers;
+ struct evkeyvalq *output_headers;
+ const char *param;
+ int do_gzip;
- goto out_fail_gz;
- }
- }
- while (strm.avail_out == 0);
+ if (!req)
+ return;
- if (flush == Z_FINISH)
- break;
+ input_headers = evhttp_request_get_input_headers(req);
+ output_headers = evhttp_request_get_output_headers(req);
- flush = Z_FINISH;
- }
+ do_gzip = ( (!(flags & HTTPD_SEND_NO_GZIP)) &&
+ evbuf && (evbuffer_get_length(evbuf) > 512) &&
+ (param = evhttp_find_header(input_headers, "Accept-Encoding")) &&
+ (strstr(param, "gzip") || strstr(param, "*"))
+ );
- if (zret != Z_STREAM_END)
+ if (allow_origin)
+ evhttp_add_header(output_headers, "Access-Control-Allow-Origin", allow_origin);
+
+ if (do_gzip && (gzbuf = httpd_gzip_deflate(evbuf)))
{
- DPRINTF(E_LOG, L_HTTPD, "Compressed data not finalized!\n");
+ DPRINTF(E_DBG, L_HTTPD, "Gzipping response\n");
+
+ evhttp_add_header(output_headers, "Content-Encoding", "gzip");
+ evhttp_send_reply(req, code, reason, gzbuf);
+ evbuffer_free(gzbuf);
- goto out_fail_gz;
+ // Drain original buffer, as would be after evhttp_send_reply()
+ evbuffer_drain(evbuf, evbuffer_get_length(evbuf));
+ }
+ else
+ {
+ evhttp_send_reply(req, code, reason, evbuf);
}
+}
- deflateEnd(&strm);
+// This is a modified version of evhttp_send_error (credit libevent)
+void
+httpd_send_error(struct evhttp_request* req, int error, const char* reason)
+{
+ struct evkeyvalq *output_headers;
+ struct evbuffer *evbuf;
- headers = evhttp_request_get_output_headers(req);
+ if (!allow_origin)
+ {
+ evhttp_send_error(req, error, reason);
+ return;
+ }
- evhttp_add_header(headers, "Content-Encoding", "gzip");
- evhttp_send_reply(req, code, reason, gzbuf);
+ output_headers = evhttp_request_get_output_headers(req);
- evbuffer_free(gzbuf);
+ evhttp_clear_headers(output_headers);
- /* Drain original buffer, as would be after evhttp_send_reply() */
- evbuffer_drain(evbuf, evbuffer_get_length(evbuf));
+ evhttp_add_header(output_headers, "Access-Control-Allow-Origin", allow_origin);
+ evhttp_add_header(output_headers, "Content-Type", "text/html");
+ evhttp_add_header(output_headers, "Connection", "close");
- return;
+ evbuf = evbuffer_new();
+ if (!evbuf)
+ DPRINTF(E_LOG, L_HTTPD, "Could not allocate evbuffer for error page\n");
+ else
+ evbuffer_add_printf(evbuf, ERR_PAGE, error, reason, reason);
- out_fail_gz:
- deflateEnd(&strm);
- out_fail_init:
- evbuffer_free(gzbuf);
- no_gzip:
- evhttp_send_reply(req, code, reason, evbuf);
+ evhttp_send_reply(req, error, reason, evbuf);
+
+ if (evbuf)
+ evbuffer_free(evbuf);
}
/* Thread: httpd */
@@ -815,14 +860,14 @@ redirect_to_index(struct evhttp_request *req, char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Redirection URL exceeds buffer length\n");
- evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
+ httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
return;
}
headers = evhttp_request_get_output_headers(req);
-
evhttp_add_header(headers, "Location", buf);
- evhttp_send_reply(req, HTTP_MOVETEMP, "Moved", NULL);
+
+ httpd_send_reply(req, HTTP_MOVETEMP, "Moved", NULL, HTTPD_SEND_NO_GZIP);
}
/* Thread: httpd */
@@ -862,7 +907,7 @@ serve_file(struct evhttp_request *req, char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Remote web interface request denied; no password set\n");
- evhttp_send_error(req, 403, "Forbidden");
+ httpd_send_error(req, 403, "Forbidden");
return;
}
}
@@ -872,7 +917,7 @@ serve_file(struct evhttp_request *req, char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Request exceeds PATH_MAX: %s\n", uri);
- evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
+ httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
return;
}
@@ -882,7 +927,7 @@ serve_file(struct evhttp_request *req, char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Could not lstat() %s: %s\n", path, strerror(errno));
- evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
+ httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
return;
}
@@ -900,7 +945,7 @@ serve_file(struct evhttp_request *req, char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Could not dereference %s: %s\n", path, strerror(errno));
- evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
+ httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
return;
}
@@ -909,7 +954,7 @@ serve_file(struct evhttp_request *req, char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Dereferenced path exceeds PATH_MAX: %s\n", path);
- evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
+ httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
free(deref);
return;
@@ -923,7 +968,7 @@ serve_file(struct evhttp_request *req, char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", path, strerror(errno));
- evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
+ httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
return;
}
@@ -938,7 +983,7 @@ serve_file(struct evhttp_request *req, char *uri)
if (path_is_legal(path) != 0)
{
- evhttp_send_error(req, 403, "Forbidden");
+ httpd_send_error(req, 403, "Forbidden");
return;
}
@@ -948,7 +993,7 @@ serve_file(struct evhttp_request *req, char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Could not create evbuffer\n");
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal error");
+ httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal error");
return;
}
@@ -957,7 +1002,7 @@ serve_file(struct evhttp_request *req, char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", path, strerror(errno));
- evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
+ httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
return;
}
@@ -970,7 +1015,7 @@ serve_file(struct evhttp_request *req, char *uri)
{
DPRINTF(E_LOG, L_HTTPD, "Could not read file into evbuffer\n");
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal error");
+ httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal error");
return;
}
@@ -989,9 +1034,9 @@ serve_file(struct evhttp_request *req, char *uri)
}
headers = evhttp_request_get_output_headers(req);
-
evhttp_add_header(headers, "Content-Type", ctype);
- evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
+
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP);
evbuffer_free(evbuf);
}
@@ -1000,10 +1045,32 @@ serve_file(struct evhttp_request *req, char *uri)
static void
httpd_gen_cb(struct evhttp_request *req, void *arg)
{
+ struct evkeyvalq *input_headers;
+ struct evkeyvalq *output_headers;
const char *req_uri;
char *uri;
char *ptr;
+ // Did we get a CORS preflight request?
+ input_headers = evhttp_request_get_input_headers(req);
+ if ( input_headers && allow_origin &&
+ (evhttp_request_get_command(req) == EVHTTP_REQ_OPTIONS) &&
+ evhttp_find_header(input_headers, "Origin") &&
+ evhttp_find_header(input_headers, "Access-Control-Request-Method") )
+ {
+ output_headers = evhttp_request_get_output_headers(req);
+
+ evhttp_add_header(output_headers, "Access-Control-Allow-Origin", allow_origin);
+
+ // Allow only GET method and authorization header in cross origin requests
+ evhttp_add_header(output_headers, "Access-Control-Allow-Method", "GET");
+ evhttp_add_header(output_headers, "Access-Control-Allow-Headers", "authorization");
+
+ // In this case there is no reason to go through httpd_send_reply
+ evhttp_send_reply(req, HTTP_OK, "OK", NULL);
+ return;
+ }
+
req_uri = evhttp_request_get_uri(req);
if (!req_uri)
{
@@ -1254,28 +1321,30 @@ httpd_basic_auth(struct evhttp_request *req, char *user, char *passwd, char *rea
header = (char *)malloc(len);
if (!header)
{
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
+ httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
return -1;
}
ret = snprintf(header, len, "Basic realm=\"%s\"", realm);
if ((ret < 0) || (ret >= len))
{
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
+ httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
return -1;
}
evbuf = evbuffer_new();
if (!evbuf)
{
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
+ httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
return -1;
}
headers = evhttp_request_get_output_headers(req);
evhttp_add_header(headers, "WWW-Authenticate", header);
+
evbuffer_add(evbuf, http_reply_401, strlen(http_reply_401));
- evhttp_send_reply(req, 401, "Unauthorized", evbuf);
+
+ httpd_send_reply(req, 401, "Unauthorized", evbuf, HTTPD_SEND_NO_GZIP);
free(header);
evbuffer_free(evbuf);
@@ -1371,6 +1440,16 @@ httpd_init(void)
v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6");
port = cfg_getint(cfg_getsec(cfg, "library"), "port");
+ // For CORS headers
+ allow_origin = cfg_getstr(cfg_getsec(cfg, "general"), "allow_origin");
+ if (allow_origin)
+ {
+ if (strlen(allow_origin) != 0)
+ evhttp_set_allowed_methods(evhttpd, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_HEAD | EVHTTP_REQ_OPTIONS);
+ else
+ allow_origin = NULL;
+ }
+
if (v6enabled)
{
ret = evhttp_bind_socket(evhttpd, "::", port);
diff --git a/src/httpd.h b/src/httpd.h
index 8399818..7cd0f8f 100644
--- a/src/httpd.h
+++ b/src/httpd.h
@@ -5,11 +5,52 @@
#include <event2/http.h>
#include <event2/buffer.h>
+enum httpd_send_flags
+{
+ HTTPD_SEND_NO_GZIP = (1 << 0),
+};
+
void
httpd_stream_file(struct evhttp_request *req, int id);
+/*
+ * Gzips an evbuffer
+ *
+ * @in in Data to be compressed
+ * @return Compressed data - must be freed by caller
+ */
+struct evbuffer *
+httpd_gzip_deflate(struct evbuffer *in);
+
+/*
+ * This wrapper around evhttp_send_reply should be used whenever a request may
+ * come from a browser. It will automatically gzip if feasible, but the caller
+ * may direct it not to. It will set CORS headers as appropriate. Should be
+ * thread safe.
+ *
+ * @in req The evhttp request struct
+ * @in code HTTP code, e.g. 200
+ * @in reason A brief explanation of the error - if NULL the standard meaning
+ of the error code will be used
+ * @in evbuf Data for the response body
+ * @in flags See flags above
+ */
+void
+httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struct evbuffer *evbuf, enum httpd_send_flags flags);
+
+/*
+ * This is a substitute for evhttp_send_error that should be used whenever an
+ * error may be returned to a browser. It will set CORS headers as appropriate,
+ * which is not possible with evhttp_send_error, because it clears the headers.
+ * Should be thread safe.
+ *
+ * @in req The evhttp request struct
+ * @in error HTTP code, e.g. 200
+ * @in reason A brief explanation of the error - if NULL the standard meaning
+ of the error code will be used
+ */
void
-httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struct evbuffer *evbuf);
+httpd_send_error(struct evhttp_request *req, int error, const char *reason);
char *
httpd_fixup_uri(struct evhttp_request *req);
diff --git a/src/httpd_daap.c b/src/httpd_daap.c
index 5f2b0dc..22d69f4 100644
--- a/src/httpd_daap.c
+++ b/src/httpd_daap.c
@@ -282,7 +282,7 @@ daap_session_find(struct evhttp_request *req, struct evkeyvalq *query, struct ev
return s;
invalid:
- evhttp_send_error(req, 403, "Forbidden");
+ httpd_send_error(req, 403, "Forbidden");
return NULL;
}
@@ -355,7 +355,7 @@ update_refresh_cb(int fd, short event, void *arg)
evcon = evhttp_request_get_connection(ur->req);
evhttp_connection_set_closecb(evcon, NULL, NULL);
- httpd_send_reply(ur->req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(ur->req, HTTP_OK, "OK", evbuf, 0);
update_remove(ur);
}
@@ -882,7 +882,7 @@ daap_reply_server_info(struct evhttp_request *req, struct evbuffer *evbuf, char
evbuffer_add_buffer(evbuf, content);
evbuffer_free(content);
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
return 0;
}
@@ -924,7 +924,7 @@ daap_reply_content_codes(struct evhttp_request *req, struct evbuffer *evbuf, cha
dmap_add_short(evbuf, "mcty", dmap_fields[i].type); /* 10 */
}
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
return 0;
}
@@ -954,7 +954,7 @@ daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri,
{
DPRINTF(E_LOG, L_DAAP, "Login attempt with U-A: Remote and no pairing-guid\n");
- evhttp_send_error(req, 403, "Forbidden");
+ httpd_send_error(req, 403, "Forbidden");
return -1;
}
@@ -967,7 +967,7 @@ daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri,
DPRINTF(E_LOG, L_DAAP, "Login attempt with invalid pairing-guid\n");
free_pi(&pi, 1);
- evhttp_send_error(req, 403, "Forbidden");
+ httpd_send_error(req, 403, "Forbidden");
return -1;
}
@@ -999,7 +999,7 @@ daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri,
dmap_add_int(evbuf, "mstt", 200); /* 12 */
dmap_add_int(evbuf, "mlid", s->id); /* 12 */
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
return 0;
}
@@ -1015,7 +1015,7 @@ daap_reply_logout(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
daap_session_remove(s);
- httpd_send_reply(req, 204, "Logout Successful", evbuf);
+ httpd_send_reply(req, 204, "Logout Successful", evbuf, 0);
return 0;
}
@@ -1068,7 +1068,7 @@ daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
dmap_add_int(evbuf, "mstt", 200); /* 12 */
dmap_add_int(evbuf, "musr", current_rev); /* 12 */
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
return 0;
}
@@ -1122,7 +1122,7 @@ static int
daap_reply_activity(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
{
/* That's so nice, thanks for letting us know */
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -1222,7 +1222,7 @@ daap_reply_dblist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
evbuffer_add_buffer(evbuf, content);
evbuffer_free(content);
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
return 0;
}
@@ -1510,7 +1510,7 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
}
}
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
return 0;
@@ -1821,7 +1821,7 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char **
return -1;
}
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
return 0;
@@ -2132,7 +2132,7 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
}
}
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
return 0;
@@ -2338,7 +2338,7 @@ daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
}
}
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
ret = 0;
@@ -2378,7 +2378,7 @@ daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char *
ret = safe_atoi32(uri[3], &id);
if (ret < 0)
{
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return -1;
}
@@ -2390,7 +2390,7 @@ daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char *
{
DPRINTF(E_LOG, L_DAAP, "Could not convert mw parameter to integer\n");
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return -1;
}
@@ -2400,7 +2400,7 @@ daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char *
{
DPRINTF(E_LOG, L_DAAP, "Could not convert mh parameter to integer\n");
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return -1;
}
}
@@ -2442,12 +2442,11 @@ daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char *
snprintf(clen, sizeof(clen), "%ld", (long)len);
evhttp_add_header(headers, "Content-Length", clen);
- /* No gzip compression for artwork */
- evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP);
return 0;
no_artwork:
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
return -1;
}
@@ -2459,7 +2458,7 @@ daap_stream(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, stru
ret = safe_atoi32(uri[3], &id);
if (ret < 0)
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
else
httpd_stream_file(req, id);
@@ -2602,7 +2601,7 @@ daap_reply_dmap_test(struct evhttp_request *req, struct evbuffer *evbuf, char **
return -1;
}
- evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP);
return 0;
}
@@ -2711,7 +2710,7 @@ daap_request(struct evhttp_request *req)
full_uri = httpd_fixup_uri(req);
if (!full_uri)
{
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return;
}
@@ -2719,7 +2718,7 @@ daap_request(struct evhttp_request *req)
if (!ptr)
{
free(full_uri);
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return;
}
@@ -2730,7 +2729,7 @@ daap_request(struct evhttp_request *req)
if (!uri)
{
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return;
}
@@ -2741,7 +2740,7 @@ daap_request(struct evhttp_request *req)
if (!uri)
{
free(full_uri);
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return;
}
@@ -2762,7 +2761,7 @@ daap_request(struct evhttp_request *req)
{
DPRINTF(E_LOG, L_DAAP, "Unrecognized DAAP request\n");
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
free(uri);
free(full_uri);
@@ -2776,6 +2775,7 @@ daap_request(struct evhttp_request *req)
/* No authentication for these URIs */
if ((strcmp(uri, "/server-info") == 0)
|| (strcmp(uri, "/logout") == 0)
+ || (strcmp(uri, "/content-codes") == 0)
|| (strncmp(uri, "/databases/1/items/", strlen("/databases/1/items/")) == 0))
passwd = NULL;
@@ -2819,7 +2819,7 @@ daap_request(struct evhttp_request *req)
{
DPRINTF(E_LOG, L_DAAP, "DAAP URI has too many/few components (%d)\n", (uri_parts[0]) ? i : 0);
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
free(uri);
free(full_uri);
@@ -2841,7 +2841,7 @@ daap_request(struct evhttp_request *req)
{
DPRINTF(E_LOG, L_DAAP, "Could not allocate evbuffer for DAAP reply\n");
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
+ httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
free(uri);
free(full_uri);
@@ -2852,7 +2852,9 @@ daap_request(struct evhttp_request *req)
ret = cache_daap_get(full_uri, evbuf);
if (ret == 0)
{
- httpd_send_reply(req, HTTP_OK, "OK", evbuf); // TODO not all want this reply
+ // The cache will return the data gzipped, so httpd_send_reply won't need to do it
+ evhttp_add_header(headers, "Content-Encoding", "gzip");
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); // TODO not all want this reply
evbuffer_free(evbuf);
free(uri);
diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c
index f0f22fe..4d7b81a 100644
--- a/src/httpd_dacp.c
+++ b/src/httpd_dacp.c
@@ -368,10 +368,10 @@ playstatusupdate_cb(int fd, short what, void *arg)
{
buf = evbuffer_pullup(update, -1);
evbuffer_add(evbuf, buf, len);
- httpd_send_reply(ur->req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(ur->req, HTTP_OK, "OK", evbuf, 0);
}
else
- httpd_send_reply(ur->req, HTTP_OK, "OK", update);
+ httpd_send_reply(ur->req, HTTP_OK, "OK", update, 0);
free(ur);
}
@@ -774,7 +774,7 @@ dacp_reply_ctrlint(struct evhttp_request *req, struct evbuffer *evbuf, char **ur
dmap_add_char(evbuf, "cmrl", 1); /* 9, unknown */
dmap_add_long(evbuf, "ceSX", (1 << 1 | 1)); /* 16, unknown dacp - lowest bit announces support for playqueue-contents/-edit */
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
}
static int
@@ -796,7 +796,7 @@ find_first_song_id(const char *query)
qp.filter = daap_query_parse_sql(query);
if (!qp.filter)
{
- DPRINTF(E_LOG, L_PLAYER, "Improper DAAP query!\n");
+ DPRINTF(E_LOG, L_DACP, "Improper DAAP query!\n");
return -1;
}
@@ -804,7 +804,7 @@ find_first_song_id(const char *query)
ret = db_query_start(&qp);
if (ret < 0)
{
- DPRINTF(E_LOG, L_PLAYER, "Could not start query\n");
+ DPRINTF(E_LOG, L_DACP, "Could not start query\n");
goto no_query_start;
}
@@ -814,17 +814,17 @@ find_first_song_id(const char *query)
ret = safe_atoi32(dbmfi.id, &id);
if (ret < 0)
{
- DPRINTF(E_LOG, L_PLAYER, "Invalid song id in query result!\n");
+ DPRINTF(E_LOG, L_DACP, "Invalid song id in query result!\n");
goto no_result;
}
- DPRINTF(E_DBG, L_PLAYER, "Found index song (id %d)\n", id);
+ DPRINTF(E_DBG, L_DACP, "Found index song (id %d)\n", id);
ret = 1;
}
else
{
- DPRINTF(E_LOG, L_PLAYER, "No song matches query (results %d): %s\n", qp.results, qp.filter);
+ DPRINTF(E_LOG, L_DACP, "No song matches query (results %d): %s\n", qp.results, qp.filter);
goto no_result;
}
@@ -895,7 +895,7 @@ dacp_queueitem_make(struct queue_item **head, const char *query, const char *que
ret = safe_atoi64(strchr(queuefilter, ':') + 1, &albumid);
if (ret < 0)
{
- DPRINTF(E_LOG, L_PLAYER, "Invalid album id in queuefilter: %s\n", queuefilter);
+ DPRINTF(E_LOG, L_DACP, "Invalid album id in queuefilter: %s\n", queuefilter);
return -1;
}
@@ -908,7 +908,7 @@ dacp_queueitem_make(struct queue_item **head, const char *query, const char *que
ret = safe_atoi64(strchr(queuefilter, ':') + 1, &artistid);
if (ret < 0)
{
- DPRINTF(E_LOG, L_PLAYER, "Invalid artist id in queuefilter: %s\n", queuefilter);
+ DPRINTF(E_LOG, L_DACP, "Invalid artist id in queuefilter: %s\n", queuefilter);
return -1;
}
@@ -921,7 +921,7 @@ dacp_queueitem_make(struct queue_item **head, const char *query, const char *que
ret = safe_atoi32(strchr(queuefilter, ':') + 1, &plid);
if (ret < 0)
{
- DPRINTF(E_LOG, L_PLAYER, "Invalid playlist id in queuefilter: %s\n", queuefilter);
+ DPRINTF(E_LOG, L_DACP, "Invalid playlist id in queuefilter: %s\n", queuefilter);
return -1;
}
@@ -939,7 +939,7 @@ dacp_queueitem_make(struct queue_item **head, const char *query, const char *que
}
else
{
- DPRINTF(E_LOG, L_PLAYER, "Unknown queuefilter %s\n", queuefilter);
+ DPRINTF(E_LOG, L_DACP, "Unknown queuefilter %s\n", queuefilter);
// If the queuefilter is unkown, ignore it and use the query parameter instead to build the sql query
id = 0;
@@ -1103,7 +1103,7 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u
dmap_add_int(evbuf, "mstt", 200); /* 12 */
dmap_add_int(evbuf, "miid", id); /* 12 */
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
}
static void
@@ -1119,7 +1119,7 @@ dacp_reply_cue_clear(struct evhttp_request *req, struct evbuffer *evbuf, char **
dmap_add_int(evbuf, "mstt", 200); /* 12 */
dmap_add_int(evbuf, "miid", 0); /* 12 */
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
}
static void
@@ -1282,11 +1282,11 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u
}
/* 204 No Content is the canonical reply */
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
return;
out_fail:
- evhttp_send_error(req, 500, "Internal Server Error");
+ httpd_send_error(req, 500, "Internal Server Error");
}
static void
@@ -1301,7 +1301,7 @@ dacp_reply_pause(struct evhttp_request *req, struct evbuffer *evbuf, char **uri,
player_playback_pause();
/* 204 No Content is the canonical reply */
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
}
static void
@@ -1328,13 +1328,13 @@ dacp_reply_playpause(struct evhttp_request *req, struct evbuffer *evbuf, char **
{
DPRINTF(E_LOG, L_DACP, "Player returned an error for start after pause\n");
- evhttp_send_error(req, 500, "Internal Server Error");
+ httpd_send_error(req, 500, "Internal Server Error");
return;
}
}
/* 204 No Content is the canonical reply */
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
}
static void
@@ -1352,7 +1352,7 @@ dacp_reply_nextitem(struct evhttp_request *req, struct evbuffer *evbuf, char **u
{
DPRINTF(E_LOG, L_DACP, "Player returned an error for nextitem\n");
- evhttp_send_error(req, 500, "Internal Server Error");
+ httpd_send_error(req, 500, "Internal Server Error");
return;
}
@@ -1361,12 +1361,12 @@ dacp_reply_nextitem(struct evhttp_request *req, struct evbuffer *evbuf, char **u
{
DPRINTF(E_LOG, L_DACP, "Player returned an error for start after nextitem\n");
- evhttp_send_error(req, 500, "Internal Server Error");
+ httpd_send_error(req, 500, "Internal Server Error");
return;
}
/* 204 No Content is the canonical reply */
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
}
static void
@@ -1384,7 +1384,7 @@ dacp_reply_previtem(struct evhttp_request *req, struct evbuffer *evbuf, char **u
{
DPRINTF(E_LOG, L_DACP, "Player returned an error for previtem\n");
- evhttp_send_error(req, 500, "Internal Server Error");
+ httpd_send_error(req, 500, "Internal Server Error");
return;
}
@@ -1393,12 +1393,12 @@ dacp_reply_previtem(struct evhttp_request *req, struct evbuffer *evbuf, char **u
{
DPRINTF(E_LOG, L_DACP, "Player returned an error for start after previtem\n");
- evhttp_send_error(req, 500, "Internal Server Error");
+ httpd_send_error(req, 500, "Internal Server Error");
return;
}
/* 204 No Content is the canonical reply */
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
}
static void
@@ -1413,7 +1413,7 @@ dacp_reply_beginff(struct evhttp_request *req, struct evbuffer *evbuf, char **ur
/* TODO */
/* 204 No Content is the canonical reply */
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
}
static void
@@ -1428,7 +1428,7 @@ dacp_reply_beginrew(struct evhttp_request *req, struct evbuffer *evbuf, char **u
/* TODO */
/* 204 No Content is the canonical reply */
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
}
static void
@@ -1443,7 +1443,7 @@ dacp_reply_playresume(struct evhttp_request *req, struct evbuffer *evbuf, char *
/* TODO */
/* 204 No Content is the canonical reply */
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
}
static int
@@ -1673,7 +1673,7 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf,
dmap_add_char(evbuf, "apsm", status.shuffle); /* 9, daap.playlistshufflemode - not part of mlcl container */
dmap_add_char(evbuf, "aprm", status.repeat); /* 9, daap.playlistrepeatmode - not part of mlcl container */
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
}
static void
@@ -1697,7 +1697,7 @@ dacp_reply_playqueueedit_clear(struct evhttp_request *req, struct evbuffer *evbu
dmap_add_int(evbuf, "mstt", 200); /* 12 */
dmap_add_int(evbuf, "miid", 0); /* 12 */
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
}
static void
@@ -1826,7 +1826,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf,
}
/* 204 No Content is the canonical reply */
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
}
static void
@@ -1871,7 +1871,7 @@ dacp_reply_playqueueedit_move(struct evhttp_request *req, struct evbuffer *evbuf
}
/* 204 No Content is the canonical reply */
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
}
static void
@@ -1903,7 +1903,7 @@ dacp_reply_playqueueedit_remove(struct evhttp_request *req, struct evbuffer *evb
}
/* 204 No Content is the canonical reply */
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
}
static void
@@ -2023,9 +2023,9 @@ dacp_reply_playstatusupdate(struct evhttp_request *req, struct evbuffer *evbuf,
{
ret = make_playstatusupdate(evbuf);
if (ret < 0)
- evhttp_send_error(req, 500, "Internal Server Error");
+ httpd_send_error(req, 500, "Internal Server Error");
else
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
return;
}
@@ -2076,7 +2076,7 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf,
{
DPRINTF(E_LOG, L_DACP, "Request for artwork without mw parameter\n");
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return;
}
@@ -2085,7 +2085,7 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf,
{
DPRINTF(E_LOG, L_DACP, "Could not convert mw parameter to integer\n");
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return;
}
@@ -2094,7 +2094,7 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf,
{
DPRINTF(E_LOG, L_DACP, "Request for artwork without mh parameter\n");
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return;
}
@@ -2103,7 +2103,7 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf,
{
DPRINTF(E_LOG, L_DACP, "Could not convert mh parameter to integer\n");
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return;
}
@@ -2137,12 +2137,11 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf,
snprintf(clen, sizeof(clen), "%ld", (long)len);
evhttp_add_header(headers, "Content-Length", clen);
- /* No gzip compression for artwork */
- evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP);
return;
no_artwork:
- evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
+ httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
}
static void
@@ -2243,7 +2242,7 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char
return;
}
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
return;
@@ -2291,11 +2290,11 @@ dacp_reply_setproperty(struct evhttp_request *req, struct evbuffer *evbuf, char
}
/* 204 No Content is the canonical reply */
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
}
static void
-speaker_enum_cb(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg)
+speaker_enum_cb(uint64_t id, const char *name, int relvol, int absvol, struct spk_flags flags, void *arg)
{
struct evbuffer *evbuf;
int len;
@@ -2354,7 +2353,7 @@ dacp_reply_getspeakers(struct evhttp_request *req, struct evbuffer *evbuf, char
evbuffer_free(spklist);
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
}
static void
@@ -2377,7 +2376,7 @@ dacp_reply_setspeakers(struct evhttp_request *req, struct evbuffer *evbuf, char
{
DPRINTF(E_LOG, L_DACP, "Missing speaker-id parameter in DACP setspeakers request\n");
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return;
}
@@ -2397,7 +2396,7 @@ dacp_reply_setspeakers(struct evhttp_request *req, struct evbuffer *evbuf, char
{
DPRINTF(E_LOG, L_DACP, "Out of memory for speaker ids\n");
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
+ httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
return;
}
@@ -2437,15 +2436,15 @@ dacp_reply_setspeakers(struct evhttp_request *req, struct evbuffer *evbuf, char
/* Password problem */
if (ret == -2)
- evhttp_send_error(req, 902, "");
+ httpd_send_error(req, 902, "");
else
- evhttp_send_error(req, 500, "Internal Server Error");
+ httpd_send_error(req, 500, "Internal Server Error");
return;
}
/* 204 No Content is the canonical reply */
- evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
+ httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP);
}
@@ -2548,7 +2547,7 @@ dacp_request(struct evhttp_request *req)
full_uri = httpd_fixup_uri(req);
if (!full_uri)
{
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return;
}
@@ -2560,7 +2559,7 @@ dacp_request(struct evhttp_request *req)
if (!uri)
{
free(full_uri);
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
return;
}
@@ -2588,7 +2587,7 @@ dacp_request(struct evhttp_request *req)
{
DPRINTF(E_LOG, L_DACP, "Unrecognized DACP request\n");
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
free(uri);
free(full_uri);
@@ -2609,7 +2608,7 @@ dacp_request(struct evhttp_request *req)
{
DPRINTF(E_LOG, L_DACP, "DACP URI has too many/few components (%d)\n", (uri_parts[0]) ? i : 0);
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
free(uri);
free(full_uri);
@@ -2621,7 +2620,7 @@ dacp_request(struct evhttp_request *req)
{
DPRINTF(E_LOG, L_DACP, "Could not allocate evbuffer for DACP reply\n");
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
+ httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
free(uri);
free(full_uri);
diff --git a/src/httpd_rsp.c b/src/httpd_rsp.c
index 8e39374..77b9b5c 100644
--- a/src/httpd_rsp.c
+++ b/src/httpd_rsp.c
@@ -251,7 +251,7 @@ rsp_send_error(struct evhttp_request *req, char *errmsg)
if (!evbuf)
{
- evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
+ httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
return;
}
@@ -259,7 +259,8 @@ rsp_send_error(struct evhttp_request *req, char *errmsg)
headers = evhttp_request_get_output_headers(req);
evhttp_add_header(headers, "Content-Type", "text/xml; charset=utf-8");
evhttp_add_header(headers, "Connection", "close");
- evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
+
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP);
evbuffer_free(evbuf);
}
@@ -283,7 +284,8 @@ rsp_send_reply(struct evhttp_request *req, mxml_node_t *reply)
headers = evhttp_request_get_output_headers(req);
evhttp_add_header(headers, "Content-Type", "text/xml; charset=utf-8");
evhttp_add_header(headers, "Connection", "close");
- httpd_send_reply(req, HTTP_OK, "OK", evbuf);
+
+ httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
evbuffer_free(evbuf);
}
@@ -745,7 +747,7 @@ rsp_stream(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
ret = safe_atoi32(uri[2], &id);
if (ret < 0)
- evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
+ httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
else
httpd_stream_file(req, id);
}
diff --git a/src/laudio_alsa.c b/src/laudio_alsa.c
deleted file mode 100644
index 99daa52..0000000
--- a/src/laudio_alsa.c
+++ /dev/null
@@ -1,738 +0,0 @@
-/*
- * Copyright (C) 2010 Julien BLACHE <jb at jblache.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <stdint.h>
-#include <inttypes.h>
-
-#include <asoundlib.h>
-
-#include "conffile.h"
-#include "logger.h"
-#include "player.h"
-#include "laudio.h"
-
-
-struct pcm_packet
-{
- uint8_t samples[STOB(AIRTUNES_V2_PACKET_SAMPLES)];
-
- uint64_t rtptime;
-
- size_t offset;
-
- struct pcm_packet *next;
-};
-
-static uint64_t pcm_pos;
-static uint64_t pcm_start_pos;
-static int pcm_last_error;
-static int pcm_recovery;
-static int pcm_buf_threshold;
-static int pcm_period_size;
-
-static struct pcm_packet *pcm_pkt_head;
-static struct pcm_packet *pcm_pkt_tail;
-
-static char *card_name;
-static char *mixer_name;
-static snd_pcm_t *hdl;
-static snd_mixer_t *mixer_hdl;
-static snd_mixer_elem_t *vol_elem;
-static long vol_min;
-static long vol_max;
-
-static enum laudio_state pcm_status;
-static laudio_status_cb status_cb;
-
-
-static void
-update_status(enum laudio_state status)
-{
- pcm_status = status;
- status_cb(status);
-}
-
-static int
-laudio_alsa_xrun_recover(int err)
-{
- int ret;
-
- if (err != 0)
- pcm_last_error = err;
-
- /* Buffer underrun */
- if (err == -EPIPE)
- {
- DPRINTF(E_WARN, L_LAUDIO, "PCM buffer underrun\n");
-
- ret = snd_pcm_prepare(hdl);
- if (ret < 0)
- {
- DPRINTF(E_WARN, L_LAUDIO, "Couldn't recover from underrun: %s\n", snd_strerror(ret));
- return 1;
- }
-
- return 0;
- }
- /* Device suspended */
- else if (pcm_last_error == -ESTRPIPE)
- {
- ret = snd_pcm_resume(hdl);
- if (ret == -EAGAIN)
- {
- pcm_recovery++;
-
- return 2;
- }
- else if (ret < 0)
- {
- pcm_recovery = 0;
-
- ret = snd_pcm_prepare(hdl);
- if (ret < 0)
- {
- DPRINTF(E_WARN, L_LAUDIO, "Couldn't recover from suspend: %s\n", snd_strerror(ret));
- return 1;
- }
- }
-
- pcm_recovery = 0;
- return 0;
- }
-
- return err;
-}
-
-static int
-laudio_alsa_set_start_threshold(snd_pcm_uframes_t threshold)
-{
- snd_pcm_sw_params_t *sw_params;
- int ret;
-
- ret = snd_pcm_sw_params_malloc(&sw_params);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not allocate sw params: %s\n", snd_strerror(ret));
-
- goto out_fail;
- }
-
- ret = snd_pcm_sw_params_current(hdl, sw_params);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not retrieve current sw params: %s\n", snd_strerror(ret));
-
- goto out_fail;
- }
-
- ret = snd_pcm_sw_params_set_start_threshold(hdl, sw_params, threshold);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not set start threshold: %s\n", snd_strerror(ret));
-
- goto out_fail;
- }
-
- ret = snd_pcm_sw_params(hdl, sw_params);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not set sw params: %s\n", snd_strerror(ret));
-
- goto out_fail;
- }
-
- return 0;
-
- out_fail:
- snd_pcm_sw_params_free(sw_params);
-
- return -1;
-}
-
-static void
-laudio_alsa_write(uint8_t *buf, uint64_t rtptime)
-{
- struct pcm_packet *pkt;
- snd_pcm_sframes_t nsamp;
- int ret;
-
- pkt = (struct pcm_packet *)malloc(sizeof(struct pcm_packet));
- if (!pkt)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Out of memory for PCM pkt\n");
-
- update_status(LAUDIO_FAILED);
- return;
- }
-
- memcpy(pkt->samples, buf, sizeof(pkt->samples));
-
- pkt->rtptime = rtptime;
- pkt->offset = 0;
- pkt->next = NULL;
-
- if (pcm_pkt_tail)
- {
- pcm_pkt_tail->next = pkt;
- pcm_pkt_tail = pkt;
- }
- else
- {
- pcm_pkt_head = pkt;
- pcm_pkt_tail = pkt;
- }
-
- if (pcm_pos < pcm_pkt_head->rtptime)
- {
- pcm_pos += AIRTUNES_V2_PACKET_SAMPLES;
-
- return;
- }
- else if ((pcm_status != LAUDIO_RUNNING) && (pcm_pos >= pcm_start_pos))
- {
- update_status(LAUDIO_RUNNING);
- }
-
- pkt = pcm_pkt_head;
-
- while (pkt)
- {
- if (pcm_recovery)
- {
- ret = laudio_alsa_xrun_recover(0);
- if ((ret == 2) && (pcm_recovery < 10))
- return;
- else
- {
- if (ret == 2)
- DPRINTF(E_LOG, L_LAUDIO, "Couldn't recover PCM device after 10 tries, aborting\n");
-
- update_status(LAUDIO_FAILED);
- return;
- }
- }
-
- nsamp = snd_pcm_writei(hdl, pkt->samples + pkt->offset, BTOS(sizeof(pkt->samples) - pkt->offset));
- if ((nsamp == -EPIPE) || (nsamp == -ESTRPIPE))
- {
- ret = laudio_alsa_xrun_recover(nsamp);
- if ((ret < 0) || (ret == 1))
- {
- if (ret < 0)
- DPRINTF(E_LOG, L_LAUDIO, "PCM write error: %s\n", snd_strerror(ret));
-
- update_status(LAUDIO_FAILED);
- return;
- }
- else if (ret != 0)
- return;
-
- continue;
- }
- else if (nsamp < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "PCM write error: %s\n", snd_strerror(nsamp));
-
- update_status(LAUDIO_FAILED);
- return;
- }
-
- pcm_last_error = 0;
- pcm_pos += nsamp;
-
- pkt->offset += STOB(nsamp);
- if (pkt->offset == sizeof(pkt->samples))
- {
- pcm_pkt_head = pkt->next;
-
- if (pkt == pcm_pkt_tail)
- pcm_pkt_tail = NULL;
-
- free(pkt);
-
- pkt = pcm_pkt_head;
- }
-
- /* Don't let ALSA fill up the buffer too much */
- if (nsamp == AIRTUNES_V2_PACKET_SAMPLES)
- return;
- }
-}
-
-static uint64_t
-laudio_alsa_get_pos(void)
-{
- snd_pcm_sframes_t delay;
- int ret;
-
- if (pcm_pos == 0)
- return 0;
-
- if (pcm_last_error != 0)
- return pcm_pos;
-
- if (snd_pcm_state(hdl) != SND_PCM_STATE_RUNNING)
- return pcm_pos;
-
- ret = snd_pcm_delay(hdl, &delay);
- if (ret < 0)
- {
- DPRINTF(E_WARN, L_LAUDIO, "Could not obtain PCM delay: %s\n", snd_strerror(ret));
-
- return pcm_pos;
- }
-
- return pcm_pos - delay;
-}
-
-static void
-laudio_alsa_set_volume(int vol)
-{
- int pcm_vol;
-
- if (!mixer_hdl || !vol_elem)
- return;
-
- snd_mixer_handle_events(mixer_hdl);
-
- if (!snd_mixer_selem_is_active(vol_elem))
- return;
-
- switch (vol)
- {
- case 0:
- pcm_vol = vol_min;
- break;
-
- case 100:
- pcm_vol = vol_max;
- break;
-
- default:
- pcm_vol = vol_min + (vol * (vol_max - vol_min)) / 100;
- break;
- }
-
- DPRINTF(E_DBG, L_LAUDIO, "Setting PCM volume to %d (%d)\n", pcm_vol, vol);
-
- snd_mixer_selem_set_playback_volume_all(vol_elem, pcm_vol);
-}
-
-static int
-laudio_alsa_start(uint64_t cur_pos, uint64_t next_pkt)
-{
- snd_output_t *output;
- char *debug_pcm_cfg;
- int ret;
-
- ret = snd_pcm_prepare(hdl);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not prepare PCM device: %s\n", snd_strerror(ret));
-
- return -1;
- }
-
- DPRINTF(E_DBG, L_LAUDIO, "Start local audio curpos %" PRIu64 ", next_pkt %" PRIu64 "\n", cur_pos, next_pkt);
-
- /* Make pcm_pos the rtptime of the packet containing cur_pos */
- pcm_pos = next_pkt;
- while (pcm_pos > cur_pos)
- pcm_pos -= AIRTUNES_V2_PACKET_SAMPLES;
-
- pcm_start_pos = next_pkt + pcm_period_size;
-
- /* Compensate period size, otherwise get_pos won't be correct */
- pcm_pos += pcm_period_size;
-
- DPRINTF(E_DBG, L_LAUDIO, "PCM pos %" PRIu64 ", start pos %" PRIu64 "\n", pcm_pos, pcm_start_pos);
-
- pcm_pkt_head = NULL;
- pcm_pkt_tail = NULL;
-
- pcm_last_error = 0;
- pcm_recovery = 0;
-
- // alsa doesn't actually seem to wait for this threshold?
- ret = laudio_alsa_set_start_threshold(pcm_buf_threshold);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not set PCM start threshold for local audio start\n");
-
- return -1;
- }
-
- // Dump PCM config data for E_DBG logging
- ret = snd_output_buffer_open(&output);
- if (ret == 0)
- {
- if (snd_pcm_dump_setup(hdl, output) == 0)
- {
- snd_output_buffer_string(output, &debug_pcm_cfg);
- DPRINTF(E_DBG, L_LAUDIO, "Dump of sound device config:\n%s\n", debug_pcm_cfg);
- }
-
- snd_output_close(output);
- }
-
- update_status(LAUDIO_STARTED);
-
- return 0;
-}
-
-static void
-laudio_alsa_stop(void)
-{
- struct pcm_packet *pkt;
-
- update_status(LAUDIO_STOPPING);
-
- snd_pcm_drop(hdl);
-
- for (pkt = pcm_pkt_head; pcm_pkt_head; pkt = pcm_pkt_head)
- {
- pcm_pkt_head = pkt->next;
-
- free(pkt);
- }
-
- pcm_pkt_head = NULL;
- pcm_pkt_tail = NULL;
-
- update_status(LAUDIO_OPEN);
-}
-
-static int
-mixer_open(void)
-{
- snd_mixer_elem_t *elem;
- snd_mixer_elem_t *master;
- snd_mixer_elem_t *pcm;
- snd_mixer_elem_t *custom;
- snd_mixer_selem_id_t *sid;
- int ret;
-
- ret = snd_mixer_open(&mixer_hdl, 0);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Failed to open mixer: %s\n", snd_strerror(ret));
-
- mixer_hdl = NULL;
- return -1;
- }
-
- ret = snd_mixer_attach(mixer_hdl, card_name);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Failed to attach mixer: %s\n", snd_strerror(ret));
-
- goto out_close;
- }
-
- ret = snd_mixer_selem_register(mixer_hdl, NULL, NULL);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Failed to register mixer: %s\n", snd_strerror(ret));
-
- goto out_detach;
- }
-
- ret = snd_mixer_load(mixer_hdl);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Failed to load mixer: %s\n", snd_strerror(ret));
-
- goto out_detach;
- }
-
- /* Grab interesting elements */
- snd_mixer_selem_id_alloca(&sid);
-
- pcm = NULL;
- master = NULL;
- custom = NULL;
- for (elem = snd_mixer_first_elem(mixer_hdl); elem; elem = snd_mixer_elem_next(elem))
- {
- snd_mixer_selem_get_id(elem, sid);
-
- if (mixer_name && (strcmp(snd_mixer_selem_id_get_name(sid), mixer_name) == 0))
- {
- custom = elem;
- break;
- }
- else if (strcmp(snd_mixer_selem_id_get_name(sid), "PCM") == 0)
- pcm = elem;
- else if (strcmp(snd_mixer_selem_id_get_name(sid), "Master") == 0)
- master = elem;
- }
-
- if (mixer_name)
- {
- if (custom)
- vol_elem = custom;
- else
- {
- DPRINTF(E_LOG, L_LAUDIO, "Failed to open configured mixer element '%s'\n", mixer_name);
-
- goto out_detach;
- }
- }
- else if (pcm)
- vol_elem = pcm;
- else if (master)
- vol_elem = master;
- else
- {
- DPRINTF(E_LOG, L_LAUDIO, "Failed to open PCM or Master mixer element\n");
-
- goto out_detach;
- }
-
- /* Get min & max volume */
- snd_mixer_selem_get_playback_volume_range(vol_elem, &vol_min, &vol_max);
-
- return 0;
-
- out_detach:
- snd_mixer_detach(mixer_hdl, card_name);
- out_close:
- snd_mixer_close(mixer_hdl);
- mixer_hdl = NULL;
- vol_elem = NULL;
-
- return -1;
-}
-
-static int
-laudio_alsa_open(void)
-{
- snd_pcm_hw_params_t *hw_params;
- snd_pcm_uframes_t bufsize;
- snd_pcm_uframes_t period_size;
- int ret;
-
- hw_params = NULL;
-
- ret = snd_pcm_open(&hdl, card_name, SND_PCM_STREAM_PLAYBACK, 0);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not open playback device: %s\n", snd_strerror(ret));
-
- return -1;
- }
-
- /* HW params */
- ret = snd_pcm_hw_params_malloc(&hw_params);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not allocate hw params: %s\n", snd_strerror(ret));
-
- goto out_fail;
- }
-
- ret = snd_pcm_hw_params_any(hdl, hw_params);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not retrieve hw params: %s\n", snd_strerror(ret));
-
- goto out_fail;
- }
-
- ret = snd_pcm_hw_params_set_access(hdl, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not set access method: %s\n", snd_strerror(ret));
-
- goto out_fail;
- }
-
- ret = snd_pcm_hw_params_set_format(hdl, hw_params, SND_PCM_FORMAT_S16_LE);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not set S16LE format: %s\n", snd_strerror(ret));
-
- goto out_fail;
- }
-
- ret = snd_pcm_hw_params_set_channels(hdl, hw_params, 2);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not set stereo output: %s\n", snd_strerror(ret));
-
- goto out_fail;
- }
-
- ret = snd_pcm_hw_params_set_rate(hdl, hw_params, 44100, 0);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Hardware doesn't support 44.1 kHz: %s\n", snd_strerror(ret));
-
- goto out_fail;
- }
-
- ret = snd_pcm_hw_params_get_buffer_size_max(hw_params, &bufsize);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not get max buffer size: %s\n", snd_strerror(ret));
-
- goto out_fail;
- }
-
- DPRINTF(E_DBG, L_LAUDIO, "Max buffer size is %lu samples\n", bufsize);
-
- ret = snd_pcm_hw_params_set_buffer_size_max(hdl, hw_params, &bufsize);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not set buffer size to max: %s\n", snd_strerror(ret));
-
- goto out_fail;
- }
-
- // With a small period size we seem to get underruns because the period time
- // passes before we manage to feed with samples (if the player is slightly
- // behind - especially critical during startup when the buffer is low)
- // Internet suggests period_size should be /2 bufsize, but default seems to be
- // much lower, so compromise on /4 (but not more than 65536 frames = almost 2 sec).
- period_size = bufsize / 4;
- if (period_size > 65536)
- period_size = 65536;
-
- ret = snd_pcm_hw_params_set_period_size_near(hdl, hw_params, &period_size, NULL);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not set period size: %s\n", snd_strerror(ret));
-
- goto out_fail;
- }
-
- DPRINTF(E_DBG, L_LAUDIO, "Buffer size is %lu samples, period size is %lu samples\n", bufsize, period_size);
-
- ret = snd_pcm_hw_params(hdl, hw_params);
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not set hw params: %s\n", snd_strerror(ret));
-
- goto out_fail;
- }
-
- snd_pcm_hw_params_free(hw_params);
- hw_params = NULL;
-
- pcm_pos = 0;
- pcm_last_error = 0;
- pcm_recovery = 0;
- pcm_buf_threshold = ((bufsize - period_size) / AIRTUNES_V2_PACKET_SAMPLES) * AIRTUNES_V2_PACKET_SAMPLES;
- pcm_period_size = period_size;
-
- ret = mixer_open();
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_LAUDIO, "Could not open mixer\n");
-
- goto out_fail;
- }
-
- update_status(LAUDIO_OPEN);
-
- return 0;
-
- out_fail:
- if (hw_params)
- snd_pcm_hw_params_free(hw_params);
-
- snd_pcm_close(hdl);
- hdl = NULL;
-
- return -1;
-}
-
-static void
-laudio_alsa_close(void)
-{
- struct pcm_packet *pkt;
-
- snd_pcm_close(hdl);
- hdl = NULL;
-
- if (mixer_hdl)
- {
- snd_mixer_detach(mixer_hdl, card_name);
- snd_mixer_close(mixer_hdl);
-
- mixer_hdl = NULL;
- vol_elem = NULL;
- }
-
- for (pkt = pcm_pkt_head; pcm_pkt_head; pkt = pcm_pkt_head)
- {
- pcm_pkt_head = pkt->next;
-
- free(pkt);
- }
-
- pcm_pkt_head = NULL;
- pcm_pkt_tail = NULL;
-
- update_status(LAUDIO_CLOSED);
-}
-
-
-static int
-laudio_alsa_init(laudio_status_cb cb, cfg_t *cfg_audio)
-{
- snd_lib_error_set_handler(logger_alsa);
-
- status_cb = cb;
-
- card_name = cfg_getstr(cfg_audio, "card");
- mixer_name = cfg_getstr(cfg_audio, "mixer");
-
- hdl = NULL;
- mixer_hdl = NULL;
- vol_elem = NULL;
-
- return 0;
-}
-
-static void
-laudio_alsa_deinit(void)
-{
- snd_lib_error_set_handler(NULL);
-}
-
-audio_output audio_alsa = {
- .name = "alsa",
- .init = &laudio_alsa_init,
- .deinit = &laudio_alsa_deinit,
- .start = &laudio_alsa_start,
- .stop = &laudio_alsa_stop,
- .open = &laudio_alsa_open,
- .close = &laudio_alsa_close,
- .pos = &laudio_alsa_get_pos,
- .write = &laudio_alsa_write,
- .volume = &laudio_alsa_set_volume,
- };
diff --git a/src/logger.c b/src/logger.c
index a293503..b84dd36 100644
--- a/src/logger.c
+++ b/src/logger.c
@@ -43,7 +43,7 @@ static int threshold;
static int console;
static char *logfilename;
static FILE *logfile;
-static char *labels[] = { "config", "daap", "db", "httpd", "http", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify", "lastfm", "cache", "mpd", "stream", "cast" };
+static char *labels[] = { "config", "daap", "db", "httpd", "http", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify", "lastfm", "cache", "mpd", "stream", "cast", "fifo" };
static char *severities[] = { "FATAL", "LOG", "WARN", "INFO", "DEBUG", "SPAM" };
diff --git a/src/logger.h b/src/logger.h
index d91a404..7e4f082 100644
--- a/src/logger.h
+++ b/src/logger.h
@@ -33,8 +33,9 @@
#define L_MPD 24
#define L_STREAMING 25
#define L_CAST 26
+#define L_FIFO 27
-#define N_LOGDOMAINS 27
+#define N_LOGDOMAINS 28
/* Severities */
#define E_FATAL 0
diff --git a/src/main.c b/src/main.c
index e64b5c7..3c0f07d 100644
--- a/src/main.c
+++ b/src/main.c
@@ -346,9 +346,9 @@ signal_signalfd_cb(int fd, short event, void *arg)
switch (info.ssi_signo)
{
case SIGCHLD:
- DPRINTF(E_LOG, L_MAIN, "Got SIGCHLD, reaping children\n");
+ DPRINTF(E_LOG, L_MAIN, "Got SIGCHLD\n");
- while (wait3(&status, WNOHANG, NULL) > 0)
+ while (waitpid(-1, &status, WNOHANG) > 0)
/* Nothing. */ ;
break;
@@ -391,9 +391,9 @@ signal_kqueue_cb(int fd, short event, void *arg)
switch (ke.ident)
{
case SIGCHLD:
- DPRINTF(E_LOG, L_MAIN, "Got SIGCHLD, reaping children\n");
+ DPRINTF(E_LOG, L_MAIN, "Got SIGCHLD\n");
- while (wait3(&status, WNOHANG, NULL) > 0)
+ while (waitpid(-1, &status, WNOHANG) > 0)
/* Nothing. */ ;
break;
@@ -464,6 +464,7 @@ main(int argc, char **argv)
char *logfile;
char *ffid;
char *pidfile;
+ char buildopts[256];
const char *gcry_version;
sigset_t sigs;
int sigfd;
@@ -590,6 +591,32 @@ main(int argc, char **argv)
DPRINTF(E_LOG, L_MAIN, "Forked Media Server Version %s taking off\n", VERSION);
+ /* Remember to check the size of buildopts when adding new opts */
+ strcpy(buildopts, "");
+#ifdef ITUNES
+ strcat(buildopts, " --enable-itunes");
+#endif
+#ifdef SPOTIFY
+ strcat(buildopts, " --enable-spotify");
+#endif
+#ifdef LASTFM
+ strcat(buildopts, " --enable-lastfm");
+#endif
+#ifdef CHROMECAST
+ strcat(buildopts, " --enable-chromecast");
+#endif
+#ifdef MPD
+ strcat(buildopts, " --enable-mpd");
+#endif
+#ifdef ALSA
+ strcat(buildopts, " --with-alsa");
+#endif
+#ifdef PULSEAUDIO
+ strcat(buildopts, " --with-pulseaudio");
+#endif
+
+ DPRINTF(E_LOG, L_MAIN, "Built %s with:%s\n", BUILDDATE, buildopts);
+
ret = av_lockmgr_register(ffmpeg_lockmgr);
if (ret < 0)
{
diff --git a/src/mdns.h b/src/mdns.h
index f054aca..656968b 100644
--- a/src/mdns.h
+++ b/src/mdns.h
@@ -4,27 +4,50 @@
#include "misc.h"
-#define MDNS_WANT_V4 (1 << 0)
-#define MDNS_WANT_V4LL (1 << 1)
-#define MDNS_WANT_V6 (1 << 2)
-#define MDNS_WANT_V6LL (1 << 3)
-
-#define MDNS_WANT_DEFAULT (MDNS_WANT_V4 | MDNS_WANT_V6 | MDNS_WANT_V6LL)
-
typedef void (* mdns_browse_cb)(const char *name, const char *type, const char *domain, const char *hostname, int family, const char *address, int port, struct keyval *txt);
-/* mDNS interface functions */
-/* Call only from the main thread */
+/*
+ * Start a mDNS client
+ * Call only from the main thread!
+ *
+ * @return 0 on success, -1 on error
+ */
int
mdns_init(void);
+/*
+ * Removes registered services, stops service browsers and stop the mDNS client
+ * Call only from the main thread!
+ *
+ */
void
mdns_deinit(void);
+/*
+ * Register (announce) a service with mDNS
+ * Call only from the main thread!
+ *
+ * @in name Name of service, e.g. "My Music on Debian"
+ * @in type Type of service to announce, e.g. "_daap._tcp"
+ * @in port Port of the service
+ * @in txt Pointer to array of strings with txt key/values ("Version=1")
+ * for DNS-SD TXT. The array must be terminated by a NULL pointer.
+ * @return 0 on success, -1 on error
+ */
int
mdns_register(char *name, char *type, int port, char **txt);
+/*
+ * Start a service browser, a callback will be made when the service changes state
+ * Call only from the main thread!
+ *
+ * @in type Type of service to look for, e.g. "_raop._tcp"
+ * @in family AF_INET to browse for ipv4 services, AF_INET6 for ipv6,
+ AF_UNSPEC for both
+ * @in cb Callback when service state changes (e.g. appears/disappears)
+ * @return 0 on success, -1 on error
+ */
int
-mdns_browse(char *type, int flags, mdns_browse_cb cb);
+mdns_browse(char *type, int family, mdns_browse_cb cb);
#endif /* !__MDNS_H__ */
diff --git a/src/mdns_avahi.c b/src/mdns_avahi.c
index 9939c4c..b1cd1cb 100644
--- a/src/mdns_avahi.c
+++ b/src/mdns_avahi.c
@@ -46,6 +46,7 @@
#include "logger.h"
#include "mdns.h"
+#define MDNSERR avahi_strerror(avahi_client_errno(mdns_client))
/* Main event base, from main.c */
extern struct event_base *evbase_main;
@@ -140,12 +141,10 @@ ev_watch_new(const AvahiPoll *api, int fd, AvahiWatchEvent a_events, AvahiWatchC
AvahiWatch *w;
int ret;
- w = (AvahiWatch *)malloc(sizeof(AvahiWatch));
+ w = calloc(1, sizeof(AvahiWatch));
if (!w)
return NULL;
- memset(w, 0, sizeof(AvahiWatch));
-
w->cb = cb;
w->userdata = userdata;
@@ -255,12 +254,10 @@ ev_timeout_new(const AvahiPoll *api, const struct timeval *tv, AvahiTimeoutCallb
AvahiTimeout *t;
int ret;
- t = (AvahiTimeout *)malloc(sizeof(AvahiTimeout));
+ t = calloc(1, sizeof(AvahiTimeout));
if (!t)
return NULL;
- memset(t, 0, sizeof(AvahiTimeout));
-
t->cb = cb;
t->userdata = userdata;
@@ -338,10 +335,9 @@ static struct AvahiPoll ev_poll_api =
struct mdns_browser
{
char *type;
+ AvahiProtocol protocol;
mdns_browse_cb cb;
- int flags;
-
struct mdns_browser *next;
};
@@ -352,7 +348,7 @@ struct mdns_record_browser {
char *domain;
struct keyval txt_kv;
- unsigned short port;
+ int port;
};
struct mdns_group_entry
@@ -368,192 +364,107 @@ struct mdns_group_entry
static struct mdns_browser *browser_list;
static struct mdns_group_entry *group_entries;
-
#define IPV4LL_NETWORK 0xA9FE0000
#define IPV4LL_NETMASK 0xFFFF0000
#define IPV6LL_NETWORK 0xFE80
#define IPV6LL_NETMASK 0xFFC0
static int
-is_v4ll(struct in_addr *addr)
+is_v4ll(const AvahiIPv4Address *addr)
{
- return ((ntohl(addr->s_addr) & IPV4LL_NETMASK) == IPV4LL_NETWORK);
+ return ((ntohl(addr->address) & IPV4LL_NETMASK) == IPV4LL_NETWORK);
}
static int
-is_v6ll(struct in6_addr *addr)
+is_v6ll(const AvahiIPv6Address *addr)
{
- return ((((addr->s6_addr[0] << 8) | addr->s6_addr[1]) & IPV6LL_NETMASK) == IPV6LL_NETWORK);
+ return ((((addr->address[0] << 8) | addr->address[1]) & IPV6LL_NETMASK) == IPV6LL_NETWORK);
}
-static void
-browse_record_callback_v4(AvahiRecordBrowser *b, AvahiIfIndex intf, AvahiProtocol proto,
- AvahiBrowserEvent event, const char *hostname, uint16_t clazz, uint16_t type,
- const void *rdata, size_t size, AvahiLookupResultFlags flags, void *userdata)
+static int
+avahi_address_make(AvahiAddress *addr, AvahiProtocol proto, const void *rdata, size_t size)
{
- char address[INET_ADDRSTRLEN];
- struct in_addr addr;
- struct mdns_record_browser *rb_data;
- int ll;
+ memset(addr, 0, sizeof(AvahiAddress));
- rb_data = (struct mdns_record_browser *)userdata;
+ addr->proto = proto;
- switch (event)
+ if (proto == AVAHI_PROTO_INET)
{
- case AVAHI_BROWSER_NEW:
- if (size != sizeof(addr.s_addr))
- {
- DPRINTF(E_WARN, L_MDNS, "Got RR type A size %ld (should be %ld)\n", (long)size, (long)sizeof(addr.s_addr));
-
- return;
- }
-
- memcpy(&addr.s_addr, rdata, sizeof(addr.s_addr));
-
- ll = is_v4ll(&addr);
- if (ll && !(rb_data->mb->flags & MDNS_WANT_V4LL))
- {
- DPRINTF(E_DBG, L_MDNS, "Discarding IPv4 LL, not interested (service %s)\n", rb_data->name);
- return;
- }
- else if (!ll && !(rb_data->mb->flags & MDNS_WANT_V4))
- {
- DPRINTF(E_DBG, L_MDNS, "Discarding IPv4, not interested (service %s)\n", rb_data->name);
- return;
- }
-
- if (!inet_ntop(AF_INET, &addr.s_addr, address, sizeof(address)))
- {
- DPRINTF(E_LOG, L_MDNS, "Could not print IPv4 address: %s\n", strerror(errno));
-
- return;
- }
-
- DPRINTF(E_DBG, L_MDNS, "Service %s, hostname %s resolved to %s\n", rb_data->name, hostname, address);
-
- /* Execute callback (mb->cb) with all the data */
- rb_data->mb->cb(rb_data->name, rb_data->mb->type, rb_data->domain, hostname, AF_INET, address, rb_data->port, &rb_data->txt_kv);
- /* Got a suitable address, stop record browser */
- break;
-
- case AVAHI_BROWSER_REMOVE:
- /* Not handled - record browser lifetime too short for this to happen */
- return;
-
- case AVAHI_BROWSER_CACHE_EXHAUSTED:
- case AVAHI_BROWSER_ALL_FOR_NOW:
- DPRINTF(E_DBG, L_MDNS, "Avahi Record Browser (%s v4): no more results (%s)\n", hostname,
- (event == AVAHI_BROWSER_CACHE_EXHAUSTED) ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
+ if (size != sizeof(AvahiIPv4Address))
+ {
+ DPRINTF(E_LOG, L_MDNS, "Got RR type A size %zu (should be %zu)\n", size, sizeof(AvahiIPv4Address));
+ return -1;
+ }
- break;
+ memcpy(&addr->data.ipv4.address, rdata, size);
+ return 0;
+ }
- case AVAHI_BROWSER_FAILURE:
- DPRINTF(E_LOG, L_MDNS, "Avahi Record Browser (%s v4) failure: %s\n", hostname,
- avahi_strerror(avahi_client_errno(avahi_record_browser_get_client(b))));
+ if (proto == AVAHI_PROTO_INET6)
+ {
+ if (size != sizeof(AvahiIPv6Address))
+ {
+ DPRINTF(E_LOG, L_MDNS, "Got RR type AAAA size %zu (should be %zu)\n", size, sizeof(AvahiIPv6Address));
+ return -1;
+ }
- break;
+ memcpy(&addr->data.ipv6.address, rdata, size);
+ return 0;
}
- keyval_clear(&rb_data->txt_kv);
- free(rb_data->name);
- free(rb_data->domain);
- free(rb_data);
-
- avahi_record_browser_free(b);
+ DPRINTF(E_LOG, L_MDNS, "Error: Unknown protocol\n");
+ return -1;
}
static void
-browse_record_callback_v6(AvahiRecordBrowser *b, AvahiIfIndex intf, AvahiProtocol proto,
- AvahiBrowserEvent event, const char *hostname, uint16_t clazz, uint16_t type,
- const void *rdata, size_t size, AvahiLookupResultFlags flags, void *userdata)
+browse_record_callback(AvahiRecordBrowser *b, AvahiIfIndex intf, AvahiProtocol proto,
+ AvahiBrowserEvent event, const char *hostname, uint16_t clazz, uint16_t type,
+ const void *rdata, size_t size, AvahiLookupResultFlags flags, void *userdata)
{
- char address[INET6_ADDRSTRLEN + IF_NAMESIZE + 1];
- char ifname[IF_NAMESIZE];
- struct in6_addr addr;
struct mdns_record_browser *rb_data;
- int ll;
- int len;
+ AvahiAddress addr;
+ char address[AVAHI_ADDRESS_STR_MAX];
+ int family;
int ret;
rb_data = (struct mdns_record_browser *)userdata;
- switch (event)
- {
- case AVAHI_BROWSER_NEW:
- if (size != sizeof(addr.s6_addr))
- {
- DPRINTF(E_WARN, L_MDNS, "Got RR type AAAA size %ld (should be %ld)\n", (long)size, (long)sizeof(addr.s6_addr));
-
- return;
- }
-
- memcpy(&addr.s6_addr, rdata, sizeof(addr.s6_addr));
-
- ll = is_v6ll(&addr);
- if (ll && !(rb_data->mb->flags & MDNS_WANT_V6LL))
- {
- DPRINTF(E_DBG, L_MDNS, "Discarding IPv6 LL, not interested (service %s)\n", rb_data->name);
- return;
- }
- else if (!ll && !(rb_data->mb->flags & MDNS_WANT_V6))
- {
- DPRINTF(E_DBG, L_MDNS, "Discarding IPv6, not interested (service %s)\n", rb_data->name);
- return;
- }
-
- if (!inet_ntop(AF_INET6, &addr.s6_addr, address, sizeof(address)))
- {
- DPRINTF(E_LOG, L_MDNS, "Could not print IPv6 address: %s\n", strerror(errno));
+ if (event == AVAHI_BROWSER_CACHE_EXHAUSTED)
+ DPRINTF(E_DBG, L_MDNS, "Avahi Record Browser (%s, proto %d): no more results (CACHE_EXHAUSTED)\n", hostname, proto);
+ else if (event == AVAHI_BROWSER_ALL_FOR_NOW)
+ DPRINTF(E_DBG, L_MDNS, "Avahi Record Browser (%s, proto %d): no more results (ALL_FOR_NOW)\n", hostname, proto);
+ else if (event == AVAHI_BROWSER_FAILURE)
+ DPRINTF(E_LOG, L_MDNS, "Avahi Record Browser (%s, proto %d) failure: %s\n", hostname, proto, MDNSERR);
+ else if (event == AVAHI_BROWSER_REMOVE)
+ return; // Not handled - record browser lifetime too short for this to happen
- return;
- }
+ if (event != AVAHI_BROWSER_NEW)
+ goto out_free_record_browser;
- if (ll)
- {
- if (!if_indextoname(intf, ifname))
- {
- DPRINTF(E_LOG, L_MDNS, "Could not map interface index %d to a name\n", intf);
-
- return;
- }
-
- len = strlen(address);
- ret = snprintf(address + len, sizeof(address) - len, "%%%s", ifname);
- if ((ret < 0) || (ret > sizeof(address) - len))
- {
- DPRINTF(E_LOG, L_MDNS, "Buffer too short for scoped IPv6 LL\n");
-
- return;
- }
- }
-
- DPRINTF(E_DBG, L_MDNS, "Service %s, hostname %s resolved to %s\n", rb_data->name, hostname, address);
-
- /* Execute callback (mb->cb) with all the data */
- rb_data->mb->cb(rb_data->name, rb_data->mb->type, rb_data->domain, hostname, AF_INET6, address, rb_data->port, &rb_data->txt_kv);
- /* Got a suitable address, stop record browser */
- break;
+ ret = avahi_address_make(&addr, proto, rdata, size); // Not an avahi function despite the name
+ if (ret < 0)
+ return;
- case AVAHI_BROWSER_REMOVE:
- /* Not handled - record browser lifetime too short for this to happen */
- return;
+ family = avahi_proto_to_af(proto);
+ avahi_address_snprint(address, sizeof(address), &addr);
- case AVAHI_BROWSER_CACHE_EXHAUSTED:
- case AVAHI_BROWSER_ALL_FOR_NOW:
- DPRINTF(E_DBG, L_MDNS, "Avahi Record Browser (%s v6): no more results (%s)\n", hostname,
- (event == AVAHI_BROWSER_CACHE_EXHAUSTED) ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
+ // Avahi will sometimes give us link-local addresses in 169.254.0.0/16 or
+ // fe80::/10, which (most of the time) are useless
+ // - see also https://lists.freedesktop.org/archives/avahi/2012-September/002183.html
+ if ((proto == AVAHI_PROTO_INET && is_v4ll(&addr.data.ipv4)) || (proto == AVAHI_PROTO_INET6 && is_v6ll(&addr.data.ipv6)))
+ {
+ DPRINTF(E_WARN, L_MDNS, "Ignoring announcement from %s, address %s is link-local\n", hostname, address);
+ return;
+ }
- break;
+ DPRINTF(E_DBG, L_MDNS, "Avahi Record Browser (%s, proto %d): NEW record %s for service type '%s'\n", hostname, proto, address, rb_data->mb->type);
- case AVAHI_BROWSER_FAILURE:
- DPRINTF(E_LOG, L_MDNS, "Avahi Record Browser (%s v6) failure: %s\n", hostname,
- avahi_strerror(avahi_client_errno(avahi_record_browser_get_client(b))));
+ // Execute callback (mb->cb) with all the data
+ rb_data->mb->cb(rb_data->name, rb_data->mb->type, rb_data->domain, hostname, family, address, rb_data->port, &rb_data->txt_kv);
- break;
- }
-
- /* Cleanup when done/error */
- keyval_clear(&rb_data->txt_kv);
+ // Stop record browser
+ out_free_record_browser:
+ keyval_clear(&rb_data->txt_kv);
free(rb_data->name);
free(rb_data->domain);
free(rb_data);
@@ -561,165 +472,73 @@ browse_record_callback_v6(AvahiRecordBrowser *b, AvahiIfIndex intf, AvahiProtoco
avahi_record_browser_free(b);
}
-static int
-spawn_record_browser(AvahiClient *c, AvahiIfIndex intf, AvahiProtocol proto, const char *hostname, const char *domain,
- uint16_t type, struct mdns_browser *mb, const char *name, uint16_t port, AvahiStringList *txt)
+static void
+browse_resolve_callback(AvahiServiceResolver *r, AvahiIfIndex intf, AvahiProtocol proto, AvahiResolverEvent event,
+ const char *name, const char *type, const char *domain, const char *hostname, const AvahiAddress *addr,
+ uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata)
{
AvahiRecordBrowser *rb;
struct mdns_record_browser *rb_data;
char *key;
char *value;
- char *ptr;
- size_t len;
+ uint16_t dns_type;
int ret;
- rb_data = (struct mdns_record_browser *)malloc(sizeof(struct mdns_record_browser));
- if (!rb_data)
+ if (event == AVAHI_RESOLVER_FAILURE)
{
- DPRINTF(E_LOG, L_MDNS, "Out of memory for record browser data\n");
-
- return -1;
+ DPRINTF(E_LOG, L_MDNS, "Avahi Resolver failure: service '%s' type '%s': %s\n", name, type, MDNSERR);
+ goto out_free_resolver;
}
-
- memset(rb_data, 0, sizeof(struct mdns_record_browser));
-
- rb_data->mb = mb;
- rb_data->port = port;
-
- rb_data->name = strdup(name);
- if (!rb_data->name)
+ else if (event != AVAHI_RESOLVER_FOUND)
{
- DPRINTF(E_LOG, L_MDNS, "Out of memory for service name\n");
-
- goto out_free_rb;
+ DPRINTF(E_LOG, L_MDNS, "Avahi Resolver empty callback\n");
+ goto out_free_resolver;
}
- rb_data->domain = strdup(domain);
- if (!rb_data->domain)
- {
- DPRINTF(E_LOG, L_MDNS, "Out of memory for service domain\n");
+ DPRINTF(E_DBG, L_MDNS, "Avahi Resolver: resolved service '%s' type '%s' proto %d, host %s\n", name, type, proto, hostname);
- goto out_free_name;
+ rb_data = calloc(1, sizeof(struct mdns_record_browser));
+ if (! (rb_data && (rb_data->name = strdup(name)) && (rb_data->domain = strdup(domain))) )
+ {
+ DPRINTF(E_LOG, L_MDNS, "Out of memory\n");
+ goto out_free_resolver;
}
+ rb_data->mb = (struct mdns_browser *)userdata;
+ rb_data->port = port;
+
while (txt)
{
- len = avahi_string_list_get_size(txt);
- key = (char *)avahi_string_list_get_text(txt);
-
- ptr = memchr(key, '=', len);
- if (!ptr)
- {
- value = "";
- len = 0;
- }
- else
- {
- *ptr = '\0';
- value = ptr + 1;
-
- len -= strlen(key) + 1;
- }
-
- ret = keyval_add_size(&rb_data->txt_kv, key, value, len);
-
- if (ptr)
- *ptr = '=';
+ ret = avahi_string_list_get_pair(txt, &key, &value, NULL);
+ txt = avahi_string_list_get_next(txt);
if (ret < 0)
- {
- DPRINTF(E_LOG, L_MDNS, "Could not build TXT record keyval\n");
+ continue;
- goto out_free_keyval;
+ if (value)
+ {
+ keyval_add(&rb_data->txt_kv, key, value);
+ avahi_free(value);
}
- txt = avahi_string_list_get_next(txt);
+ avahi_free(key);
}
- rb = NULL;
- switch (type)
- {
- case AVAHI_DNS_TYPE_A:
- rb = avahi_record_browser_new(c, intf, proto, hostname,
- AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_A, 0,
- browse_record_callback_v4, rb_data);
- if (!rb)
- DPRINTF(E_LOG, L_MDNS, "Could not create v4 record browser for host %s: %s\n",
- hostname, avahi_strerror(avahi_client_errno(c)));
- break;
-
- case AVAHI_DNS_TYPE_AAAA:
- rb = avahi_record_browser_new(c, intf, proto, hostname,
- AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_AAAA, 0,
- browse_record_callback_v6, rb_data);
- if (!rb)
- DPRINTF(E_LOG, L_MDNS, "Could not create v4 record browser for host %s: %s\n",
- hostname, avahi_strerror(avahi_client_errno(c)));
- break;
- }
+ if (proto == AVAHI_PROTO_INET6)
+ dns_type = AVAHI_DNS_TYPE_AAAA;
+ else
+ dns_type = AVAHI_DNS_TYPE_A;
+ // We need to implement a record browser because the announcement from some
+ // devices (e.g. ApEx 1 gen) will include multiple records, and we need to
+ // filter out those records that won't work (notably link-local). The value of
+ // *addr given by browse_resolve_callback is just the first record.
+ rb = avahi_record_browser_new(mdns_client, intf, proto, hostname, AVAHI_DNS_CLASS_IN, dns_type, 0, browse_record_callback, rb_data);
if (!rb)
- goto out_free_keyval;
-
- return 0;
-
- out_free_keyval:
- keyval_clear(&rb_data->txt_kv);
- free(rb_data->domain);
- out_free_name:
- free(rb_data->name);
- out_free_rb:
- free(rb_data);
-
- return -1;
-}
-
-static void
-browse_resolve_callback(AvahiServiceResolver *r, AvahiIfIndex intf, AvahiProtocol proto, AvahiResolverEvent event,
- const char *name, const char *type, const char *domain, const char *hostname, const AvahiAddress *addr,
- uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata)
-{
- AvahiClient *c;
- struct mdns_browser *mb;
- int ret;
-
- mb = (struct mdns_browser *)userdata;
-
- switch (event)
- {
- case AVAHI_RESOLVER_FAILURE:
- DPRINTF(E_LOG, L_MDNS, "Avahi Resolver failure: service '%s' type '%s': %s\n", name, type,
- avahi_strerror(avahi_client_errno(mdns_client)));
- break;
-
- case AVAHI_RESOLVER_FOUND:
- DPRINTF(E_DBG, L_MDNS, "Avahi Resolver: resolved service '%s' type '%s' proto %d\n", name, type, proto);
-
- c = avahi_service_resolver_get_client(r);
-
- if (mb->flags & (MDNS_WANT_V4 | MDNS_WANT_V4LL))
- {
- ret = spawn_record_browser(c, intf, proto, hostname, domain,
- AVAHI_DNS_TYPE_A, mb, name, port, txt);
- if (ret < 0)
- DPRINTF(E_LOG, L_MDNS, "Failed to create record browser for type A\n");
- }
-
- if (mb->flags & (MDNS_WANT_V6 | MDNS_WANT_V6LL))
- {
- ret = spawn_record_browser(c, intf, proto, hostname, domain,
- AVAHI_DNS_TYPE_AAAA, mb, name, port, txt);
- if (ret < 0)
- DPRINTF(E_LOG, L_MDNS, "Failed to create record browser for type A\n");
- }
-
-
- break;
- }
+ DPRINTF(E_LOG, L_MDNS, "Could not create record browser for host %s: %s\n", hostname, MDNSERR);
+ out_free_resolver:
avahi_service_resolver_free(r);
-
- return;
}
static void
@@ -735,17 +554,14 @@ browse_callback(AvahiServiceBrowser *b, AvahiIfIndex intf, AvahiProtocol proto,
switch (event)
{
case AVAHI_BROWSER_FAILURE:
- DPRINTF(E_LOG, L_MDNS, "Avahi Browser failure: %s\n",
- avahi_strerror(avahi_client_errno(mdns_client)));
+ DPRINTF(E_LOG, L_MDNS, "Avahi Browser failure: %s\n", MDNSERR);
avahi_service_browser_free(b);
- b = avahi_service_browser_new(mdns_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, mb->type, NULL, 0, browse_callback, mb);
+ b = avahi_service_browser_new(mdns_client, AVAHI_IF_UNSPEC, mb->protocol, mb->type, NULL, 0, browse_callback, mb);
if (!b)
- {
- DPRINTF(E_LOG, L_MDNS, "Failed to recreate service browser (service type %s): %s\n", mb->type,
- avahi_strerror(avahi_client_errno(mdns_client)));
- }
+ DPRINTF(E_LOG, L_MDNS, "Failed to recreate service browser (service type %s): %s\n", mb->type, MDNSERR);
+
return;
case AVAHI_BROWSER_NEW:
@@ -753,30 +569,14 @@ browse_callback(AvahiServiceBrowser *b, AvahiIfIndex intf, AvahiProtocol proto,
res = avahi_service_resolver_new(mdns_client, intf, proto, name, type, domain, proto, 0, browse_resolve_callback, mb);
if (!res)
- DPRINTF(E_LOG, L_MDNS, "Failed to create service resolver: %s\n",
- avahi_strerror(avahi_client_errno(mdns_client)));
+ DPRINTF(E_LOG, L_MDNS, "Failed to create service resolver: %s\n", MDNSERR);
+
break;
case AVAHI_BROWSER_REMOVE:
DPRINTF(E_DBG, L_MDNS, "Avahi Browser: REMOVE service '%s' type '%s' proto %d\n", name, type, proto);
- switch (proto)
- {
- case AVAHI_PROTO_INET:
- family = AF_INET;
- break;
-
- case AVAHI_PROTO_INET6:
- family = AF_INET6;
- break;
-
- default:
- DPRINTF(E_INFO, L_MDNS, "Avahi Browser: unknown protocol %d\n", proto);
-
- family = AF_UNSPEC;
- break;
- }
-
+ family = avahi_proto_to_af(proto);
if (family != AF_UNSPEC)
mb->cb(name, type, domain, NULL, family, NULL, -1, NULL);
break;
@@ -831,7 +631,6 @@ _create_services(void)
if (!group_entries)
{
DPRINTF(E_DBG, L_MDNS, "No entries yet... skipping service create\n");
-
return;
}
@@ -840,9 +639,7 @@ _create_services(void)
mdns_group = avahi_entry_group_new(mdns_client, entry_group_callback, NULL);
if (!mdns_group)
{
- DPRINTF(E_WARN, L_MDNS, "Could not create Avahi EntryGroup: %s\n",
- avahi_strerror(avahi_client_errno(mdns_client)));
-
+ DPRINTF(E_WARN, L_MDNS, "Could not create Avahi EntryGroup: %s\n", MDNSERR);
return;
}
}
@@ -858,7 +655,6 @@ _create_services(void)
if (ret < 0)
{
DPRINTF(E_WARN, L_MDNS, "Could not add mDNS services: %s\n", avahi_strerror(ret));
-
return;
}
@@ -867,10 +663,7 @@ _create_services(void)
ret = avahi_entry_group_commit(mdns_group);
if (ret < 0)
- {
- DPRINTF(E_WARN, L_MDNS, "Could not commit mDNS services: %s\n",
- avahi_strerror(avahi_client_errno(mdns_client)));
- }
+ DPRINTF(E_WARN, L_MDNS, "Could not commit mDNS services: %s\n", MDNSERR);
}
static void
@@ -889,12 +682,9 @@ client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void *
for (mb = browser_list; mb; mb = mb->next)
{
- b = avahi_service_browser_new(mdns_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, mb->type, NULL, 0, browse_callback, mb);
+ b = avahi_service_browser_new(mdns_client, AVAHI_IF_UNSPEC, mb->protocol, mb->type, NULL, 0, browse_callback, mb);
if (!b)
- {
- DPRINTF(E_LOG, L_MDNS, "Failed to recreate service browser (service type %s): %s\n", mb->type,
- avahi_strerror(avahi_client_errno(mdns_client)));
- }
+ DPRINTF(E_LOG, L_MDNS, "Failed to recreate service browser (service type %s): %s\n", mb->type, MDNSERR);
}
break;
@@ -915,13 +705,9 @@ client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void *
avahi_client_free(mdns_client);
mdns_group = NULL;
- mdns_client = avahi_client_new(&ev_poll_api, AVAHI_CLIENT_NO_FAIL,
- client_callback, NULL, &error);
+ mdns_client = avahi_client_new(&ev_poll_api, AVAHI_CLIENT_NO_FAIL, client_callback, NULL, &error);
if (!mdns_client)
- {
- DPRINTF(E_LOG, L_MDNS, "Failed to create new Avahi client: %s\n",
- avahi_strerror(error));
- }
+ DPRINTF(E_LOG, L_MDNS, "Failed to create new Avahi client: %s\n", avahi_strerror(error));
}
else
{
@@ -960,8 +746,7 @@ mdns_init(void)
client_callback, NULL, &error);
if (!mdns_client)
{
- DPRINTF(E_WARN, L_MDNS, "mdns_init: Could not create Avahi client: %s\n",
- avahi_strerror(avahi_client_errno(mdns_client)));
+ DPRINTF(E_WARN, L_MDNS, "mdns_init: Could not create Avahi client: %s\n", MDNSERR);
return -1;
}
@@ -1023,9 +808,12 @@ mdns_register(char *name, char *type, int port, char **txt)
DPRINTF(E_DBG, L_MDNS, "Adding mDNS service %s/%s\n", name, type);
- ge = (struct mdns_group_entry *)malloc(sizeof(struct mdns_group_entry));
+ ge = calloc(1, sizeof(struct mdns_group_entry));
if (!ge)
- return -1;
+ {
+ DPRINTF(E_LOG, L_MDNS, "Out of memory for mdns register\n");
+ return -1;
+ }
ge->name = strdup(name);
ge->type = strdup(type);
@@ -1060,30 +848,31 @@ mdns_register(char *name, char *type, int port, char **txt)
}
int
-mdns_browse(char *type, int flags, mdns_browse_cb cb)
+mdns_browse(char *type, int family, mdns_browse_cb cb)
{
struct mdns_browser *mb;
AvahiServiceBrowser *b;
DPRINTF(E_DBG, L_MDNS, "Adding service browser for type %s\n", type);
- mb = (struct mdns_browser *)malloc(sizeof(struct mdns_browser));
+ mb = calloc(1, sizeof(struct mdns_browser));
if (!mb)
- return -1;
+ {
+ DPRINTF(E_LOG, L_MDNS, "Out of memory for new mdns browser\n");
+ return -1;
+ }
+ mb->protocol = avahi_af_to_proto(family);
mb->type = strdup(type);
mb->cb = cb;
- mb->flags = (flags) ? flags : MDNS_WANT_DEFAULT;
-
mb->next = browser_list;
browser_list = mb;
- b = avahi_service_browser_new(mdns_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, type, NULL, 0, browse_callback, mb);
+ b = avahi_service_browser_new(mdns_client, AVAHI_IF_UNSPEC, mb->protocol, mb->type, NULL, 0, browse_callback, mb);
if (!b)
{
- DPRINTF(E_LOG, L_MDNS, "Failed to create service browser: %s\n",
- avahi_strerror(avahi_client_errno(mdns_client)));
+ DPRINTF(E_LOG, L_MDNS, "Failed to create service browser: %s\n", MDNSERR);
browser_list = mb->next;
free(mb->type);
diff --git a/src/misc.c b/src/misc.c
index d4fd95c..25f767f 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -668,7 +668,7 @@ b64_decode(const char *b64)
static const char b64_encode_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static void
-b64_encode_block(uint8_t *in, char *out, int len)
+b64_encode_block(const uint8_t *in, char *out, int len)
{
out[0] = b64_encode_table[in[0] >> 2];
@@ -691,7 +691,7 @@ b64_encode_block(uint8_t *in, char *out, int len)
}
static void
-b64_encode_full_block(uint8_t *in, char *out)
+b64_encode_full_block(const uint8_t *in, char *out)
{
out[0] = b64_encode_table[in[0] >> 2];
out[1] = b64_encode_table[((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4)];
@@ -700,7 +700,7 @@ b64_encode_full_block(uint8_t *in, char *out)
}
char *
-b64_encode(uint8_t *in, size_t len)
+b64_encode(const uint8_t *in, size_t len)
{
char *encoded;
char *out;
diff --git a/src/misc.h b/src/misc.h
index f7a1e83..8a8d2ee 100644
--- a/src/misc.h
+++ b/src/misc.h
@@ -77,7 +77,7 @@ char *
b64_decode(const char *b64);
char *
-b64_encode(uint8_t *in, size_t len);
+b64_encode(const uint8_t *in, size_t len);
uint64_t
murmur_hash64(const void *key, int len, uint32_t seed);
diff --git a/src/mpd.c b/src/mpd.c
index cad44b1..5da1e20 100644
--- a/src/mpd.c
+++ b/src/mpd.c
@@ -55,6 +55,7 @@
#include "logger.h"
#include "db.h"
#include "conffile.h"
+#include "httpd.h"
#include "misc.h"
#include "listener.h"
#include "artwork.h"
@@ -62,37 +63,19 @@
#include "player.h"
#include "queue.h"
#include "filescanner.h"
+#include "commands.h"
static pthread_t tid_mpd;
static struct event_base *evbase_mpd;
-static int g_exit_pipe[2];
-static struct event *g_exitev;
-static int g_cmd_pipe[2];
-static struct event *g_cmdev;
+static struct commands_base *cmdbase;
static struct evhttp *evhttpd;
struct evconnlistener *listener;
-struct mpd_command;
-
-typedef int (*cmd_func)(struct mpd_command *cmd);
-
-struct mpd_command
-{
- pthread_mutex_t lck;
- pthread_cond_t cond;
-
- cmd_func func;
-
- enum listener_event_type arg_evtype;
- int nonblock;
-
- int ret;
-};
#define COMMAND_ARGV_MAX 37
@@ -207,51 +190,6 @@ struct idle_client
struct idle_client *idle_clients;
-/* ---------------------------- COMMAND EXECUTION -------------------------- */
-
-static int
-send_command(struct mpd_command *cmd)
-{
- int ret;
-
- if (!cmd->func)
- {
- DPRINTF(E_LOG, L_MPD, "BUG: cmd->func is NULL!\n");
- return -1;
- }
-
- ret = write(g_cmd_pipe[1], &cmd, sizeof(cmd));
- if (ret != sizeof(cmd))
- {
- DPRINTF(E_LOG, L_MPD, "Could not send command: %s\n", strerror(errno));
- return -1;
- }
-
- return 0;
-}
-
-static int
-nonblock_command(struct mpd_command *cmd)
-{
- int ret;
-
- ret = send_command(cmd);
- if (ret < 0)
- return -1;
-
- return 0;
-}
-
-static void
-thread_exit(void)
-{
- int dummy = 42;
-
- DPRINTF(E_DBG, L_MPD, "Killing mpd thread\n");
-
- if (write(g_exit_pipe[1], &dummy, sizeof(dummy)) != sizeof(dummy))
- DPRINTF(E_LOG, L_MPD, "Could not write to exit fd: %s\n", strerror(errno));
-}
/* Thread: mpd */
@@ -276,21 +214,6 @@ mpd(void *arg)
}
static void
-exit_cb(int fd, short what, void *arg)
-{
- int dummy;
- int ret;
-
- ret = read(g_exit_pipe[0], &dummy, sizeof(dummy));
- if (ret != sizeof(dummy))
- DPRINTF(E_LOG, L_MPD, "Error reading from exit pipe\n");
-
- event_base_loopbreak(evbase_mpd);
-
- event_add(g_exitev, NULL);
-}
-
-static void
mpd_time(char *buffer, size_t bufferlen, time_t t)
{
struct tm tm;
@@ -1199,7 +1122,7 @@ mpd_command_pause(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
else
ret = player_playback_start(NULL);
- if (ret != 0)
+ if (ret < 0)
{
ret = asprintf(errmsg, "Failed to pause playback");
if (ret < 0)
@@ -1224,16 +1147,6 @@ mpd_command_play(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
player_get_status(&status);
- //TODO verfiy handling of play with parameter if already playing
- if (status.status == PLAY_PLAYING)
- {
- ret = player_playback_pause();
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_MPD, "Error pausing playback\n");
- }
- }
-
songpos = 0;
if (argc > 1)
{
@@ -1247,12 +1160,24 @@ mpd_command_play(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
}
}
+ if (status.status == PLAY_PLAYING && songpos < 0)
+ {
+ DPRINTF(E_DBG, L_MPD, "Ignoring play command with parameter '%s', player is already playing.\n", argv[1]);
+ return 0;
+ }
+
+ if (status.status == PLAY_PLAYING)
+ {
+ // Stop playback, if player is already playing and a valid song position is given (it will be restarted for the given song position)
+ player_playback_stop();
+ }
+
if (songpos > 0)
ret = player_playback_start_byindex(songpos, NULL);
else
ret = player_playback_start(NULL);
- if (ret != 0)
+ if (ret < 0)
{
ret = asprintf(errmsg, "Failed to start playback");
if (ret < 0)
@@ -1277,19 +1202,10 @@ mpd_command_playid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
player_get_status(&status);
- //TODO verfiy handling of play with parameter if already playing
- if (status.status == PLAY_PLAYING)
- {
- ret = player_playback_pause();
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_MPD, "Error pausing playback\n");
- }
- }
-
id = 0;
if (argc > 1)
{
+ //TODO [mpd] mpd allows passing "-1" as argument and simply ignores it, forked-daapd fails to convert "-1" to an unsigned int
ret = safe_atou32(argv[1], &id);
if (ret < 0)
{
@@ -1300,12 +1216,18 @@ mpd_command_playid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
}
}
+ if (status.status == PLAY_PLAYING)
+ {
+ // Stop playback, if player is already playing and a valid item id is given (it will be restarted for the given song)
+ player_playback_stop();
+ }
+
if (id > 0)
ret = player_playback_start_byitemid(id, NULL);
else
ret = player_playback_start(NULL);
- if (ret != 0)
+ if (ret < 0)
{
ret = asprintf(errmsg, "Failed to start playback");
if (ret < 0)
@@ -2110,7 +2032,7 @@ mpd_command_listplaylist(struct evbuffer *evbuf, int argc, char **argv, char **e
if (argc < 2)
{
- ret = asprintf(errmsg, "Missing argument for command 'load'");
+ ret = asprintf(errmsg, "Missing argument for command 'listplaylist'");
if (ret < 0)
DPRINTF(E_LOG, L_MPD, "Out of memory\n");
return ACK_ERROR_ARG;
@@ -2182,7 +2104,7 @@ mpd_command_listplaylistinfo(struct evbuffer *evbuf, int argc, char **argv, char
if (argc < 2)
{
- ret = asprintf(errmsg, "Missing argument for command 'load'");
+ ret = asprintf(errmsg, "Missing argument for command 'listplaylistinfo'");
if (ret < 0)
DPRINTF(E_LOG, L_MPD, "Out of memory\n");
return ACK_ERROR_ARG;
@@ -3296,16 +3218,13 @@ mpd_command_rescan(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
* Adds a new struct output to the given struct outputs in *arg for the given speaker (id, name, etc.).
*/
static void
-outputs_enum_cb(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg)
+outputs_enum_cb(uint64_t id, const char *name, int relvol, int absvol, struct spk_flags flags, void *arg)
{
struct outputs *outputs;
struct output *output;
outputs = (struct outputs *)arg;
- DPRINTF(E_DBG, L_MPD, "outputid: %" PRIu64 ", outputname: %s, outputenabled: %d\n",
- id, name, flags.selected);
-
output = (struct output*)malloc(sizeof(struct output));
output->id = id;
@@ -3318,6 +3237,9 @@ outputs_enum_cb(uint64_t id, const char *name, int relvol, struct spk_flags flag
outputs->count++;
if (flags.selected)
outputs->active++;
+
+ DPRINTF(E_DBG, L_MPD, "Output enum: shortid %d, id %" PRIu64 ", name '%s', selected %d\n",
+ output->shortid, output->id, output->name, output->selected);
}
/*
@@ -3427,7 +3349,7 @@ mpd_command_enableoutput(struct evbuffer *evbuf, int argc, char **argv, char **e
if (argc < 2)
{
- ret = asprintf(errmsg, "Missing argument for command 'disableoutput'");
+ ret = asprintf(errmsg, "Missing argument for command 'enableoutput'");
if (ret < 0)
DPRINTF(E_LOG, L_MPD, "Out of memory\n");
return ACK_ERROR_ARG;
@@ -3518,7 +3440,7 @@ mpd_command_toggleoutput(struct evbuffer *evbuf, int argc, char **argv, char **e
if (argc < 2)
{
- ret = asprintf(errmsg, "Missing argument for command 'disableoutput'");
+ ret = asprintf(errmsg, "Missing argument for command 'toggleoutput'");
if (ret < 0)
DPRINTF(E_LOG, L_MPD, "Out of memory\n");
return ACK_ERROR_ARG;
@@ -3606,21 +3528,24 @@ mpd_command_toggleoutput(struct evbuffer *evbuf, int argc, char **argv, char **e
* outputid: 0
* outputname: Computer
* outputenabled: 1
+ * outputvolume: 50
*/
static void
-speaker_enum_cb(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg)
+speaker_enum_cb(uint64_t id, const char *name, int relvol, int absvol, struct spk_flags flags, void *arg)
{
struct evbuffer *evbuf;
evbuf = (struct evbuffer *)arg;
evbuffer_add_printf(evbuf,
- "outputid: %d\n"
- "outputname: %s\n"
- "outputenabled: %d\n",
- (unsigned short) id,
- name,
- flags.selected);
+ "outputid: %d\n"
+ "outputname: %s\n"
+ "outputenabled: %d\n"
+ "outputvolume: %d\n",
+ (unsigned short) id,
+ name,
+ flags.selected,
+ absvol);
}
/*
@@ -3635,6 +3560,82 @@ mpd_command_outputs(struct evbuffer *evbuf, int argc, char **argv, char **errmsg
return 0;
}
+static int
+mpd_command_outputvolume(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
+{
+ uint32_t shortid;
+ int volume;
+ struct outputs outputs;
+ struct output *output;
+ int ret;
+
+ if (argc < 3)
+ {
+ ret = asprintf(errmsg, "Missing argument for command 'outputvolume'");
+ if (ret < 0)
+ DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+ return ACK_ERROR_ARG;
+ }
+
+ ret = safe_atou32(argv[1], &shortid);
+ if (ret < 0)
+ {
+ ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
+ if (ret < 0)
+ DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+ return ACK_ERROR_ARG;
+ }
+
+ ret = safe_atoi32(argv[2], &volume);
+ if (ret < 0)
+ {
+ ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[2]);
+ if (ret < 0)
+ DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+ return ACK_ERROR_ARG;
+ }
+
+ outputs.count = 0;
+ outputs.active = 0;
+ outputs.outputs = NULL;
+
+ player_speaker_enumerate(outputs_enum_cb, &outputs);
+
+ output = outputs.outputs;
+ while (output)
+ {
+ if (output->shortid == shortid)
+ {
+ break;
+ }
+ output = output->next;
+ }
+
+ if (!output)
+ {
+ free_outputs(outputs.outputs);
+ ret = asprintf(errmsg, "No speaker found for short id: %d", shortid);
+ if (ret < 0)
+ DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+ return ACK_ERROR_UNKNOWN;
+ }
+
+ ret = player_volume_setabs_speaker(output->id, volume);
+
+ if (ret < 0)
+ {
+ free_outputs(outputs.outputs);
+ ret = asprintf(errmsg, "Setting volume to %d for speaker with short-id %d failed", volume, shortid);
+ if (ret < 0)
+ DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+ return ACK_ERROR_UNKNOWN;
+ }
+
+ free_outputs(outputs.outputs);
+
+ return 0;
+}
+
/*
* Dummy function to handle commands that are not supported by forked-daapd and should
* not raise an error.
@@ -3699,7 +3700,7 @@ mpd_command_decoders(struct evbuffer *evbuf, int argc, char **argv, char **errms
return 0;
}
-struct command
+struct mpd_command
{
/* The command name */
const char *mpdcommand;
@@ -3716,7 +3717,7 @@ struct command
int (*handler)(struct evbuffer *evbuf, int argc, char **argv, char **errmsg);
};
-static struct command mpd_handlers[] =
+static struct mpd_command mpd_handlers[] =
{
/*
* Commands for querying status
@@ -4178,6 +4179,14 @@ static struct command mpd_handlers[] =
},
/*
+ * Forked-daapd commands (not supported by mpd)
+ */
+ {
+ .mpdcommand = "outputvolume",
+ .handler = mpd_command_outputvolume
+ },
+
+ /*
* NULL command to terminate loop
*/
{
@@ -4192,7 +4201,7 @@ static struct command mpd_handlers[] =
* @param name the name of the command
* @return the command or NULL if it is an unknown/unsupported command
*/
-static struct command*
+static struct mpd_command*
mpd_find_command(const char *name)
{
int i;
@@ -4240,7 +4249,7 @@ mpd_read_cb(struct bufferevent *bev, void *ctx)
int ncmd;
char *line;
char *errmsg;
- struct command *command;
+ struct mpd_command *command;
enum command_list_type listtype;
int idle_cmd;
int close_cmd;
@@ -4525,16 +4534,18 @@ mpd_notify_idle_client(struct idle_client *client, enum listener_event_type type
return 0;
}
-static int
-mpd_notify_idle(struct mpd_command *cmd)
+static enum command_state
+mpd_notify_idle(void *arg, int *retval)
{
+ enum listener_event_type type;
struct idle_client *client;
struct idle_client *prev;
struct idle_client *next;
int i;
int ret;
- DPRINTF(E_DBG, L_MPD, "Notify clients waiting for idle results: %d\n", cmd->arg_evtype);
+ type = *(enum listener_event_type *)arg;
+ DPRINTF(E_DBG, L_MPD, "Notify clients waiting for idle results: %d\n", type);
prev = NULL;
next = NULL;
@@ -4546,7 +4557,7 @@ mpd_notify_idle(struct mpd_command *cmd)
next = client->next;
- ret = mpd_notify_idle_client(client, cmd->arg_evtype);
+ ret = mpd_notify_idle_client(client, type);
if (ret == 0)
{
@@ -4566,63 +4577,20 @@ mpd_notify_idle(struct mpd_command *cmd)
i++;
}
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
static void
mpd_listener_cb(enum listener_event_type type)
{
- DPRINTF(E_DBG, L_MPD, "Listener callback called with event type %d.\n", type);
- struct mpd_command *cmd;
-
- cmd = (struct mpd_command *)malloc(sizeof(struct mpd_command));
- if (!cmd)
- {
- DPRINTF(E_LOG, L_MPD, "Could not allocate cache_command\n");
- return;
- }
-
- memset(cmd, 0, sizeof(struct mpd_command));
-
- cmd->nonblock = 1;
-
- cmd->func = mpd_notify_idle;
- cmd->arg_evtype = type;
-
- nonblock_command(cmd);
-}
-
-static void
-command_cb(int fd, short what, void *arg)
-{
- struct mpd_command *cmd;
- int ret;
-
- ret = read(g_cmd_pipe[0], &cmd, sizeof(cmd));
- if (ret != sizeof(cmd))
- {
- DPRINTF(E_LOG, L_MPD, "Could not read command! (read %d): %s\n", ret, (ret < 0) ? strerror(errno) : "-no error-");
- goto readd;
- }
-
- if (cmd->nonblock)
- {
- cmd->func(cmd);
-
- free(cmd);
- goto readd;
- }
+ enum listener_event_type *ptr;
- pthread_mutex_lock(&cmd->lck);
+ ptr = (enum listener_event_type *)malloc(sizeof(enum listener_event_type));
+ *ptr = type;
- ret = cmd->func(cmd);
- cmd->ret = ret;
-
- pthread_cond_signal(&cmd->cond);
- pthread_mutex_unlock(&cmd->lck);
-
- readd:
- event_add(g_cmdev, NULL);
+ DPRINTF(E_DBG, L_MPD, "Listener callback called with event type %d.\n", type);
+ commands_exec_async(cmdbase, mpd_notify_idle, ptr);
}
/*
@@ -4654,7 +4622,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
if (evhttp_request_get_command(req) != EVHTTP_REQ_GET)
{
DPRINTF(E_LOG, L_MPD, "Unsupported request type for artwork\n");
- evhttp_send_error(req, HTTP_BADMETHOD, "Method not allowed");
+ httpd_send_error(req, HTTP_BADMETHOD, "Method not allowed");
return;
}
@@ -4665,7 +4633,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
if (!decoded)
{
DPRINTF(E_LOG, L_MPD, "Bad artwork request with uri '%s'\n", uri);
- evhttp_send_error(req, HTTP_BADREQUEST, 0);
+ httpd_send_error(req, HTTP_BADREQUEST, 0);
return;
}
@@ -4673,7 +4641,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
if (!path)
{
DPRINTF(E_LOG, L_MPD, "Invalid path from artwork request with uri '%s'\n", uri);
- evhttp_send_error(req, HTTP_BADREQUEST, 0);
+ httpd_send_error(req, HTTP_BADREQUEST, 0);
evhttp_uri_free(decoded);
return;
}
@@ -4682,7 +4650,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
if (!decoded_path)
{
DPRINTF(E_LOG, L_MPD, "Error decoding path from artwork request with uri '%s'\n", uri);
- evhttp_send_error(req, HTTP_BADREQUEST, 0);
+ httpd_send_error(req, HTTP_BADREQUEST, 0);
evhttp_uri_free(decoded);
return;
}
@@ -4697,7 +4665,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
if (!itemid)
{
DPRINTF(E_WARN, L_MPD, "No item found for path '%s' from request uri '%s'\n", decoded_path, uri);
- evhttp_send_error(req, HTTP_NOTFOUND, "Document was not found");
+ httpd_send_error(req, HTTP_NOTFOUND, "Document was not found");
evhttp_uri_free(decoded);
free(decoded_path);
return;
@@ -4707,7 +4675,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
if (!evbuffer)
{
DPRINTF(E_LOG, L_MPD, "Could not allocate an evbuffer for artwork request\n");
- evhttp_send_error(req, HTTP_INTERNAL, "Document was not found");
+ httpd_send_error(req, HTTP_INTERNAL, "Document was not found");
evhttp_uri_free(decoded);
free(decoded_path);
return;
@@ -4716,7 +4684,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
format = artwork_get_item(evbuffer, itemid, 600, 600);
if (format < 0)
{
- evhttp_send_error(req, HTTP_NOTFOUND, "Document was not found");
+ httpd_send_error(req, HTTP_NOTFOUND, "Document was not found");
}
else
{
@@ -4730,7 +4698,8 @@ artwork_cb(struct evhttp_request *req, void *arg)
evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "image/jpeg");
break;
}
- evhttp_send_reply(req, HTTP_OK, "OK", evbuffer);
+
+ httpd_send_reply(req, HTTP_OK, "OK", evbuffer, HTTPD_SEND_NO_GZIP);
}
evbuffer_free(evbuffer);
@@ -4761,28 +4730,6 @@ int mpd_init(void)
v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6");
-#ifdef HAVE_PIPE2
- ret = pipe2(g_exit_pipe, O_CLOEXEC);
-#else
- ret = pipe(g_exit_pipe);
-#endif
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_MPD, "Could not create pipe: %s\n", strerror(errno));
- goto exit_fail;
- }
-
-#ifdef HAVE_PIPE2
- ret = pipe2(g_cmd_pipe, O_CLOEXEC);
-#else
- ret = pipe(g_cmd_pipe);
-#endif
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_MPD, "Could not create command pipe: %s\n", strerror(errno));
- goto cmd_fail;
- }
-
evbase_mpd = event_base_new();
if (!evbase_mpd)
{
@@ -4790,24 +4737,7 @@ int mpd_init(void)
goto evbase_fail;
}
- g_exitev = event_new(evbase_mpd, g_exit_pipe[0], EV_READ, exit_cb, NULL);
- if (!g_exitev)
- {
- DPRINTF(E_LOG, L_MPD, "Could not create exit event\n");
- goto evnew_fail;
- }
-
- event_add(g_exitev, NULL);
-
-
- g_cmdev = event_new(evbase_mpd, g_cmd_pipe[0], EV_READ, command_cb, NULL);
- if (!g_cmdev)
- {
- DPRINTF(E_LOG, L_MPD, "Could not create cmd event\n");
- goto evnew_fail;
- }
-
- event_add(g_cmdev, NULL);
+ cmdbase = commands_base_new(evbase_mpd, NULL);
if (v6enabled)
{
@@ -4917,19 +4847,11 @@ int mpd_init(void)
evhttp_fail:
evconnlistener_free(listener);
connew_fail:
- evnew_fail:
+ commands_base_free(cmdbase);
event_base_free(evbase_mpd);
evbase_mpd = NULL;
evbase_fail:
- close(g_cmd_pipe[0]);
- close(g_cmd_pipe[1]);
-
- cmd_fail:
- close(g_exit_pipe[0]);
- close(g_exit_pipe[1]);
-
- exit_fail:
return -1;
}
@@ -4948,7 +4870,7 @@ void mpd_deinit(void)
return;
}
- thread_exit();
+ commands_base_destroy(cmdbase);
ret = pthread_join(tid_mpd, NULL);
if (ret != 0)
@@ -4974,10 +4896,4 @@ void mpd_deinit(void)
// Free event base (should free events too)
event_base_free(evbase_mpd);
-
- // Close pipes
- close(g_exit_pipe[0]);
- close(g_exit_pipe[1]);
- close(g_cmd_pipe[0]);
- close(g_cmd_pipe[1]);
}
diff --git a/src/outputs.c b/src/outputs.c
index 0ca17fb..c6cd1bb 100644
--- a/src/outputs.c
+++ b/src/outputs.c
@@ -34,9 +34,13 @@
extern struct output_definition output_raop;
extern struct output_definition output_streaming;
extern struct output_definition output_dummy;
+extern struct output_definition output_fifo;
#ifdef ALSA
extern struct output_definition output_alsa;
#endif
+#ifdef PULSEAUDIO
+extern struct output_definition output_pulse;
+#endif
#ifdef CHROMECAST
extern struct output_definition output_cast;
#endif
@@ -46,9 +50,13 @@ static struct output_definition *outputs[] = {
&output_raop,
&output_streaming,
&output_dummy,
+ &output_fifo,
#ifdef ALSA
&output_alsa,
#endif
+#ifdef PULSEAUDIO
+ &output_pulse,
+#endif
#ifdef CHROMECAST
&output_cast,
#endif
diff --git a/src/outputs.h b/src/outputs.h
index cec7b9d..52b16a6 100644
--- a/src/outputs.h
+++ b/src/outputs.h
@@ -52,9 +52,13 @@ enum output_types
OUTPUT_TYPE_RAOP,
OUTPUT_TYPE_STREAMING,
OUTPUT_TYPE_DUMMY,
+ OUTPUT_TYPE_FIFO,
#ifdef ALSA
OUTPUT_TYPE_ALSA,
#endif
+#ifdef PULSEAUDIO
+ OUTPUT_TYPE_PULSE,
+#endif
#ifdef CHROMECAST
OUTPUT_TYPE_CAST,
#endif
diff --git a/src/outputs/alsa.c b/src/outputs/alsa.c
index ecec35c..ee3aa3d 100644
--- a/src/outputs/alsa.c
+++ b/src/outputs/alsa.c
@@ -49,6 +49,7 @@
// TODO Unglobalise these and add support for multiple sound cards
static char *card_name;
static char *mixer_name;
+static char *mixer_device_name;
static snd_pcm_t *hdl;
static snd_mixer_t *mixer_hdl;
static snd_mixer_elem_t *vol_elem;
@@ -60,11 +61,10 @@ static int offset;
enum alsa_state
{
- ALSA_STATE_STOPPED = 0,
+ ALSA_STATE_FAILED = 0,
+ ALSA_STATE_STOPPED = 1,
ALSA_STATE_STARTED = ALSA_F_STARTED,
ALSA_STATE_STREAMING = ALSA_F_STARTED | 0x01,
-
- ALSA_STATE_FAILED = -1,
};
enum alsa_sync_state
@@ -317,7 +317,7 @@ mixer_open(void)
return -1;
}
- ret = snd_mixer_attach(mixer_hdl, card_name);
+ ret = snd_mixer_attach(mixer_hdl, mixer_device_name);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Failed to attach mixer: %s\n", snd_strerror(ret));
@@ -990,6 +990,9 @@ alsa_init(void)
card_name = cfg_getstr(cfg_audio, "card");
mixer_name = cfg_getstr(cfg_audio, "mixer");
+ mixer_device_name = cfg_getstr(cfg_audio, "mixer_device");
+ if (mixer_device_name == NULL || strlen(mixer_device_name) == 0)
+ mixer_device_name = card_name;
nickname = cfg_getstr(cfg_audio, "nickname");
offset = cfg_getint(cfg_audio, "offset");
if (abs(offset) > 44100)
@@ -1012,7 +1015,7 @@ alsa_init(void)
device->advertised = 1;
device->has_video = 0;
- DPRINTF(E_INFO, L_LAUDIO, "Adding ALSA device '%s' using friendly name '%s'\n", card_name, nickname);
+ DPRINTF(E_INFO, L_LAUDIO, "Adding ALSA device '%s' with name '%s'\n", card_name, nickname);
player_device_add(device);
diff --git a/src/outputs/cast.c b/src/outputs/cast.c
index e5b19d8..208fb7c 100644
--- a/src/outputs/cast.c
+++ b/src/outputs/cast.c
@@ -110,9 +110,9 @@ typedef void (*cast_reply_cb)(struct cast_session *cs, struct cast_msg_payload *
enum cast_state
{
// Something bad happened during a session
- CAST_STATE_FAILED = -1,
+ CAST_STATE_FAILED = 0,
// No session allocated
- CAST_STATE_NULL = 0,
+ CAST_STATE_NONE = 1,
// Session allocated, but no connection
CAST_STATE_DISCONNECTED = CAST_STATE_F_STARTUP | 0x01,
// TCP connect, TLS handshake, CONNECT and GET_STATUS request
@@ -162,6 +162,11 @@ struct cast_session
cast_reply_cb callback_register[CALLBACK_REGISTER_SIZE];
struct event *reply_timeout;
+ // This is used to work around a bug where no response is given by the device.
+ // For certain requests, we will then retry, e.g. by checking status. We
+ // register our retry so that we on only retry once.
+ int retry;
+
// Session info from the ChromeCast
char *transport_id;
char *session_id;
@@ -604,7 +609,7 @@ cast_msg_send(struct cast_session *cs, enum cast_msg_types type, cast_reply_cb r
len = extensions__core_api__cast_channel__cast_message__get_packed_size(&msg);
if (len <= 0)
{
- DPRINTF(E_LOG, L_CAST, "Could not send message (type %d), invalid length: %d\n", type, len);
+ DPRINTF(E_LOG, L_CAST, "Could not send message (type %d), invalid length: %zu\n", type, len);
return -1;
}
@@ -628,7 +633,7 @@ cast_msg_send(struct cast_session *cs, enum cast_msg_types type, cast_reply_cb r
}
if (type != PONG)
- DPRINTF(E_DBG, L_CAST, "TX %d %s %s %s %s\n", len, msg.source_id, msg.destination_id, msg.namespace_, msg.payload_utf8);
+ DPRINTF(E_DBG, L_CAST, "TX %zu %s %s %s %s\n", len, msg.source_id, msg.destination_id, msg.namespace_, msg.payload_utf8);
return 0;
}
@@ -728,6 +733,15 @@ cast_msg_process(struct cast_session *cs, const uint8_t *data, size_t len)
int unknown_session_id;
int i;
+#ifdef DEBUG_CONNECTION
+ char *b64 = b64_encode(data, len);
+ if (b64)
+ {
+ DPRINTF(E_DBG, L_CAST, "Reply dump (len %zu): %s\n", len, b64);
+ free(b64);
+ }
+#endif
+
reply = extensions__core_api__cast_channel__cast_message__unpack(NULL, len, data);
if (!reply)
{
@@ -748,7 +762,7 @@ cast_msg_process(struct cast_session *cs, const uint8_t *data, size_t len)
goto out_free_parsed;
}
- DPRINTF(E_DBG, L_CAST, "RX %d %s %s %s %s\n", len, reply->source_id, reply->destination_id, reply->namespace_, reply->payload_utf8);
+ DPRINTF(E_DBG, L_CAST, "RX %zu %s %s %s %s\n", len, reply->source_id, reply->destination_id, reply->namespace_, reply->payload_utf8);
if (payload.type == UNKNOWN)
goto out_free_parsed;
@@ -795,7 +809,7 @@ cast_msg_process(struct cast_session *cs, const uint8_t *data, size_t len)
/* cs->state = CAST_STATE_MEDIA_CONNECTED;
// Kill the session, the player will need to restart it
- cast_session_shutdown(cs, CAST_STATE_NULL);
+ cast_session_shutdown(cs, CAST_STATE_NONE);
goto out_free_parsed;
*/ }
}
@@ -823,7 +837,7 @@ cast_status(struct cast_session *cs)
case CAST_STATE_FAILED:
state = OUTPUT_STATE_FAILED;
break;
- case CAST_STATE_NULL:
+ case CAST_STATE_NONE:
state = OUTPUT_STATE_STOPPED;
break;
case CAST_STATE_DISCONNECTED ... CAST_STATE_MEDIA_LAUNCHED:
@@ -925,17 +939,34 @@ cast_cb_startup_launch(struct cast_session *cs, struct cast_msg_payload *payload
{
int ret;
+ // Sometimes the response to a LAUNCH is just a broadcast RECEIVER_STATUS
+ // without our requestId. That won't be registered by our response handler,
+ // and we get an empty callback due to timeout. In this case we send a
+ // GET_STATUS to see if we are good to go anyway.
+ if (!payload && !cs->retry)
+ {
+ DPRINTF(E_LOG, L_CAST, "No RECEIVER_STATUS reply to our LAUNCH - trying GET_STATUS instead\n");
+ cs->retry++;
+ ret = cast_msg_send(cs, GET_STATUS, cast_cb_startup_launch);
+ if (ret != 0)
+ goto error;
+
+ return;
+ }
+
if (!payload)
{
DPRINTF(E_LOG, L_CAST, "No RECEIVER_STATUS reply to our LAUNCH - aborting\n");
goto error;
}
- else if (payload->type != RECEIVER_STATUS)
+
+ if (payload->type != RECEIVER_STATUS)
{
DPRINTF(E_LOG, L_CAST, "No RECEIVER_STATUS reply to our LAUNCH (got type: %d) - aborting\n", payload->type);
goto error;
}
- else if (!payload->transport_id || !payload->session_id)
+
+ if (!payload->transport_id || !payload->session_id)
{
DPRINTF(E_LOG, L_CAST, "Missing session id or transport id in RECEIVER_STATUS - aborting\n");
goto error;
@@ -947,6 +978,8 @@ cast_cb_startup_launch(struct cast_session *cs, struct cast_msg_payload *payload
cs->session_id = strdup(payload->session_id);
cs->transport_id = strdup(payload->transport_id);
+ cs->retry = 0;
+
ret = cast_msg_send(cs, MEDIA_CONNECT, NULL);
if (ret == 0)
ret = cast_msg_send(cs, MEDIA_GET_STATUS, cast_cb_startup_media);
@@ -1009,7 +1042,7 @@ cast_cb_probe(struct cast_session *cs, struct cast_msg_payload *payload)
cast_status(cs);
- cast_session_shutdown(cs, CAST_STATE_NULL);
+ cast_session_shutdown(cs, CAST_STATE_NONE);
return;
@@ -1078,66 +1111,91 @@ cast_listen_cb(int fd, short what, void *arg)
{
struct cast_session *cs;
uint8_t buffer[MAX_BUF + 1]; // Not sure about the +1, but is copied from gnutls examples
+ uint32_t be;
+ size_t len;
int received;
int ret;
- cs = (struct cast_session *)arg;
+ for (cs = sessions; cs; cs = cs->next)
+ {
+ if (cs == (struct cast_session *)arg)
+ break;
+ }
+
+ if (!cs)
+ {
+ DPRINTF(E_INFO, L_CAST, "Callback on dead session, ignoring\n");
+ return;
+ }
if (what == EV_TIMEOUT)
{
DPRINTF(E_LOG, L_CAST, "No heartbeat from '%s', shutting down\n", cs->devname);
- cs->state = CAST_STATE_CONNECTED;
- cast_session_shutdown(cs, CAST_STATE_FAILED);
- return;
+ goto fail;
}
#ifdef DEBUG_CONNECTION
DPRINTF(E_DBG, L_CAST, "New data from '%s'\n", cs->devname);
#endif
+ // We first read the 4 byte header and then the actual message. The header
+ // will be the length of the message.
+ ret = gnutls_record_recv(cs->tls_session, buffer, 4);
+ if (ret != 4)
+ goto no_read;
+
+ memcpy(&be, buffer, 4);
+ len = be32toh(be);
+ if ((len == 0) || (len > MAX_BUF))
+ {
+ DPRINTF(E_LOG, L_CAST, "Bad length of incoming message, aborting (len=%zu, size=%d)\n", len, MAX_BUF);
+ goto fail;
+ }
+
received = 0;
- while ((ret = gnutls_record_recv(cs->tls_session, buffer + received, MAX_BUF - received)) > 0)
+ while (received < len)
{
-#ifdef DEBUG_CONNECTION
- DPRINTF(E_DBG, L_CAST, "Received %d bytes\n", ret);
-#endif
+ ret = gnutls_record_recv(cs->tls_session, buffer + received, len - received);
+ if (ret <= 0)
+ goto no_read;
+
+ received += ret;
- if (ret == 4)
- {
#ifdef DEBUG_CONNECTION
- uint32_t be;
- size_t len;
- memcpy(&be, buffer, 4);
- len = be32toh(be);
- DPRINTF(E_DBG, L_CAST, "Incoming %d bytes\n", len);
+ DPRINTF(E_DBG, L_CAST, "Received %d bytes out of expected %zu bytes\n", received, len);
#endif
- }
- else
- {
- received += ret;
- }
+ }
- if (received >= MAX_BUF)
- {
- DPRINTF(E_LOG, L_CAST, "Receive buffer exhausted!\n");
- cast_session_shutdown(cs, CAST_STATE_FAILED);
- return;
- }
+ ret = gnutls_record_check_pending(cs->tls_session);
+
+ // Process the message - note that this may result in cs being invalidated
+ cast_msg_process(cs, buffer, len);
+
+ // In the event there was more data waiting for us we go again
+ if (ret > 0)
+ {
+ DPRINTF(E_INFO, L_CAST, "More data pending from device (%d bytes)\n", ret);
+ cast_listen_cb(fd, what, arg);
}
+ return;
+
+ no_read:
if ((ret != GNUTLS_E_INTERRUPTED) && (ret != GNUTLS_E_AGAIN))
{
DPRINTF(E_LOG, L_CAST, "Session error: %s\n", gnutls_strerror(ret));
-
- // Downgrade state to make cast_session_shutdown perform an exit which is
- // quick and won't require a reponse from the device
- cs->state = CAST_STATE_CONNECTED;
- cast_session_shutdown(cs, CAST_STATE_FAILED);
- return;
+ goto fail;
}
- if (received)
- cast_msg_process(cs, buffer, received);
+ DPRINTF(E_DBG, L_CAST, "Return value from tls is %d (GNUTLS_E_AGAIN is %d)\n", ret, GNUTLS_E_AGAIN);
+
+ return;
+
+ fail:
+ // Downgrade state to make cast_session_shutdown perform an exit which is
+ // quick and won't require a reponse from the device
+ cs->state = CAST_STATE_CONNECTED;
+ cast_session_shutdown(cs, CAST_STATE_FAILED);
}
static void
@@ -1147,15 +1205,15 @@ cast_reply_timeout_cb(int fd, short what, void *arg)
int i;
cs = (struct cast_session *)arg;
+ i = cs->request_id % CALLBACK_REGISTER_SIZE;
- DPRINTF(E_WARN, L_CAST, "Request timeout, will run empty callbacks\n");
+ DPRINTF(E_LOG, L_CAST, "Request %d timed out, will run empty callback\n", i);
- for (i = 0; i < CALLBACK_REGISTER_SIZE; i++)
- if (cs->callback_register[i])
- {
- cs->callback_register[i](cs, NULL);
- cs->callback_register[i] = NULL;
- }
+ if (cs->callback_register[i])
+ {
+ cs->callback_register[i](cs, NULL);
+ cs->callback_register[i] = NULL;
+ }
}
static void
@@ -1358,7 +1416,7 @@ cast_session_make(struct output_device *device, int family, output_status_cb cb)
}
// Attempts to "nicely" bring down a session to wanted_state, and then issues
-// the callback. If wanted_state is CAST_STATE_NULL/FAILED then the session is purged.
+// the callback. If wanted_state is CAST_STATE_NONE/FAILED then the session is purged.
static void
cast_session_shutdown(struct cast_session *cs, enum cast_state wanted_state)
{
@@ -1372,7 +1430,7 @@ cast_session_shutdown(struct cast_session *cs, enum cast_state wanted_state)
}
else if (cs->state < wanted_state)
{
- DPRINTF(E_LOG, L_CAST, "Bug! Shutdown request got wanted_state that is higher than current state\n");
+ DPRINTF(E_LOG, L_CAST, "Bug! Shutdown request got wanted_state (%d) that is higher than current state (%d)\n", wanted_state, cs->state);
return;
}
@@ -1429,7 +1487,7 @@ cast_session_shutdown(struct cast_session *cs, enum cast_state wanted_state)
return;
// Asked to destroy the session
- if (wanted_state == CAST_STATE_NULL || wanted_state == CAST_STATE_FAILED)
+ if (wanted_state == CAST_STATE_NONE || wanted_state == CAST_STATE_FAILED)
{
cs->state = wanted_state;
cast_status(cs);
@@ -1488,7 +1546,7 @@ cast_device_stop(struct output_session *session)
{
struct cast_session *cs = session->session;
- cast_session_shutdown(cs, CAST_STATE_NULL);
+ cast_session_shutdown(cs, CAST_STATE_NONE);
}
static int
@@ -1580,9 +1638,14 @@ static void
cast_playback_stop(void)
{
struct cast_session *cs;
+ struct cast_session *next;
- for (cs = sessions; cs; cs = cs->next)
- cast_session_shutdown(cs, CAST_STATE_NULL);
+ for (cs = sessions; cs; cs = next)
+ {
+ next = cs->next;
+ if (cs->state & CAST_STATE_F_MEDIA_CONNECTED)
+ cast_session_shutdown(cs, CAST_STATE_NONE);
+ }
}
static void
@@ -1637,7 +1700,7 @@ cast_set_status_cb(struct output_session *session, output_status_cb cb)
static int
cast_init(void)
{
- int mdns_flags;
+ int family;
int i;
int ret;
@@ -1668,9 +1731,12 @@ cast_init(void)
goto out_tls_deinit;
}
- mdns_flags = MDNS_WANT_V4 | MDNS_WANT_V6 | MDNS_WANT_V6LL;
+ if (cfg_getbool(cfg_getsec(cfg, "general"), "ipv6"))
+ family = AF_UNSPEC;
+ else
+ family = AF_INET;
- ret = mdns_browse("_googlecast._tcp", mdns_flags, cast_device_cb);
+ ret = mdns_browse("_googlecast._tcp", family, cast_device_cb);
if (ret < 0)
{
DPRINTF(E_LOG, L_CAST, "Could not add mDNS browser for Chromecast devices\n");
diff --git a/src/outputs/fifo.c b/src/outputs/fifo.c
new file mode 100644
index 0000000..6fb81f1
--- /dev/null
+++ b/src/outputs/fifo.c
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2016 Christian Meffert <christian.meffert at googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <event2/event.h>
+
+#include "conffile.h"
+#include "logger.h"
+#include "player.h"
+#include "outputs.h"
+
+#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
+
+struct fifo_session
+{
+ enum output_device_state state;
+
+ char *path;
+
+ int input_fd;
+ int output_fd;
+
+ int created;
+
+ struct event *deferredev;
+ output_status_cb defer_cb;
+
+ /* Do not dereference - only passed to the status cb */
+ struct output_device *device;
+ struct output_session *output_session;
+ output_status_cb status_cb;
+};
+
+/* From player.c */
+extern struct event_base *evbase_player;
+
+static struct fifo_session *sessions;
+
+/* Forwards */
+static void
+defer_cb(int fd, short what, void *arg);
+
+
+/* ---------------------------- FIFO HANDLING ---------------------------- */
+
+static void
+fifo_delete(struct fifo_session *fifo_session)
+{
+ DPRINTF(E_DBG, L_FIFO, "Removing FIFO \"%s\"\n", fifo_session->path);
+
+ if (unlink(fifo_session->path) < 0)
+ {
+ DPRINTF(E_WARN, L_FIFO, "Could not remove FIFO \"%s\": %d\n", fifo_session->path, errno);
+ return;
+ }
+
+ fifo_session->created = 0;
+}
+
+static void
+fifo_close(struct fifo_session *fifo_session)
+{
+ struct stat st;
+
+ if (fifo_session->input_fd > 0)
+ {
+ close(fifo_session->input_fd);
+ fifo_session->input_fd = -1;
+ }
+
+ if (fifo_session->output_fd > 0)
+ {
+ close(fifo_session->output_fd);
+ fifo_session->output_fd = -1;
+ }
+
+ if (fifo_session->created && (stat(fifo_session->path, &st) == 0))
+ fifo_delete(fifo_session);
+}
+
+static int
+fifo_make(struct fifo_session *fifo_session)
+{
+ DPRINTF(E_DBG, L_FIFO, "Creating FIFO \"%s\"\n", fifo_session->path);
+
+ if (mkfifo(fifo_session->path, 0666) < 0)
+ {
+ DPRINTF(E_LOG, L_FIFO, "Could not create FIFO \"%s\": %d\n", fifo_session->path, errno);
+ return -1;
+ }
+
+ fifo_session->created = 1;
+
+ return 0;
+}
+
+static int
+fifo_check(struct fifo_session *fifo_session)
+{
+ struct stat st;
+
+ if (stat(fifo_session->path, &st) < 0)
+ {
+ if (errno == ENOENT)
+ {
+ /* Path doesn't exist */
+ return fifo_make(fifo_session);
+ }
+
+ DPRINTF(E_LOG, L_FIFO, "Failed to stat FIFO \"%s\": %d\n", fifo_session->path, errno);
+ return -1;
+ }
+
+ if (!S_ISFIFO(st.st_mode))
+ {
+ DPRINTF(E_LOG, L_FIFO, "\"%s\" already exists, but is not a FIFO\n", fifo_session->path);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+fifo_open(struct fifo_session *fifo_session)
+{
+ int ret;
+
+ ret = fifo_check(fifo_session);
+ if (ret < 0)
+ return -1;
+
+ fifo_session->input_fd = open(fifo_session->path, O_RDONLY | O_NONBLOCK, 0);
+ if (fifo_session->input_fd < 0)
+ {
+ DPRINTF(E_LOG, L_FIFO, "Could not open FIFO \"%s\" for reading: %d\n", fifo_session->path, errno);
+ fifo_close(fifo_session);
+ return -1;
+ }
+
+ fifo_session->output_fd = open(fifo_session->path, O_WRONLY | O_NONBLOCK, 0);
+ if (fifo_session->output_fd < 0)
+ {
+ DPRINTF(E_LOG, L_FIFO, "Could not open FIFO \"%s\" for writing: %d\n", fifo_session->path, errno);
+ fifo_close(fifo_session);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+fifo_empty(struct fifo_session *fifo_session)
+{
+ char buf[FIFO_BUFFER_SIZE];
+ int bytes = 1;
+
+ while (bytes > 0 && errno != EINTR)
+ bytes = read(fifo_session->input_fd, buf, FIFO_BUFFER_SIZE);
+
+ if (bytes < 0 && errno != EAGAIN)
+ {
+ DPRINTF(E_LOG, L_FIFO, "Flush of FIFO \"%s\" failed: %d\n", fifo_session->path, errno);
+ }
+}
+
+/* ---------------------------- SESSION HANDLING ---------------------------- */
+
+static void
+fifo_session_free(struct fifo_session *fifo_session)
+{
+ event_free(fifo_session->deferredev);
+
+ free(fifo_session->output_session);
+ free(fifo_session);
+
+ fifo_session = NULL;
+}
+
+static void
+fifo_session_cleanup(struct fifo_session *fifo_session)
+{
+ // Normally some here code to remove from linked list - here we just say:
+ sessions = NULL;
+
+ fifo_session_free(fifo_session);
+}
+
+static struct fifo_session *
+fifo_session_make(struct output_device *device, output_status_cb cb)
+{
+ struct output_session *output_session;
+ struct fifo_session *fifo_session;
+
+ output_session = calloc(1, sizeof(struct output_session));
+ fifo_session = calloc(1, sizeof(struct fifo_session));
+ if (!output_session || !fifo_session)
+ {
+ DPRINTF(E_LOG, L_FIFO, "Out of memory for fifo session\n");
+ return NULL;
+ }
+
+ fifo_session->deferredev = evtimer_new(evbase_player, defer_cb, fifo_session);
+ if (!fifo_session->deferredev)
+ {
+ DPRINTF(E_LOG, L_FIFO, "Out of memory for fifo deferred event\n");
+ free(output_session);
+ free(fifo_session);
+ return NULL;
+ }
+
+ output_session->session = fifo_session;
+ output_session->type = device->type;
+
+ fifo_session->output_session = output_session;
+ fifo_session->state = OUTPUT_STATE_CONNECTED;
+ fifo_session->device = device;
+ fifo_session->status_cb = cb;
+
+ fifo_session->created = 0;
+ fifo_session->path = device->extra_device_info;
+ fifo_session->input_fd = -1;
+ fifo_session->output_fd = -1;
+
+ sessions = fifo_session;
+
+ return fifo_session;
+}
+
+
+/* ---------------------------- STATUS HANDLERS ----------------------------- */
+
+// Maps our internal state to the generic output state and then makes a callback
+// to the player to tell that state
+static void
+defer_cb(int fd, short what, void *arg)
+{
+ struct fifo_session *ds = arg;
+
+ if (ds->defer_cb)
+ ds->defer_cb(ds->device, ds->output_session, ds->state);
+
+ if (ds->state == OUTPUT_STATE_STOPPED)
+ fifo_session_cleanup(ds);
+}
+
+static void
+fifo_status(struct fifo_session *fifo_session)
+{
+ fifo_session->defer_cb = fifo_session->status_cb;
+ event_active(fifo_session->deferredev, 0, 0);
+ fifo_session->status_cb = NULL;
+}
+
+
+/* ------------------ INTERFACE FUNCTIONS CALLED BY OUTPUTS.C --------------- */
+
+static int
+fifo_device_start(struct output_device *device, output_status_cb cb, uint64_t rtptime)
+{
+ struct fifo_session *fifo_session;
+ int ret;
+
+ fifo_session = fifo_session_make(device, cb);
+ if (!fifo_session)
+ return -1;
+
+ ret = fifo_open(fifo_session);
+ if (ret < 0)
+ return -1;
+
+ fifo_status(fifo_session);
+
+ return 0;
+}
+
+static void
+fifo_device_stop(struct output_session *output_session)
+{
+ struct fifo_session *fifo_session = output_session->session;
+
+ fifo_close(fifo_session);
+
+ fifo_session->state = OUTPUT_STATE_STOPPED;
+ fifo_status(fifo_session);
+}
+
+static int
+fifo_device_probe(struct output_device *device, output_status_cb cb)
+{
+ struct fifo_session *fifo_session;
+ int ret;
+
+ fifo_session = fifo_session_make(device, cb);
+ if (!fifo_session)
+ return -1;
+
+ ret = fifo_open(fifo_session);
+ if (ret < 0)
+ {
+ fifo_session_cleanup(fifo_session);
+ return -1;
+ }
+
+ fifo_close(fifo_session);
+
+ fifo_session->status_cb = cb;
+ fifo_session->state = OUTPUT_STATE_STOPPED;
+
+ fifo_status(fifo_session);
+
+ return 0;
+}
+
+static int
+fifo_device_volume_set(struct output_device *device, output_status_cb cb)
+{
+ struct fifo_session *fifo_session;
+
+ if (!device->session || !device->session->session)
+ return 0;
+
+ fifo_session = device->session->session;
+
+ fifo_session->status_cb = cb;
+ fifo_status(fifo_session);
+
+ return 1;
+}
+
+static void
+fifo_playback_start(uint64_t next_pkt, struct timespec *ts)
+{
+ struct fifo_session *fifo_session = sessions;
+
+ if (!fifo_session)
+ return;
+
+ fifo_session->state = OUTPUT_STATE_STREAMING;
+ fifo_status(fifo_session);
+}
+
+static void
+fifo_playback_stop(void)
+{
+ struct fifo_session *fifo_session = sessions;
+
+ if (!fifo_session)
+ return;
+
+ fifo_session->state = OUTPUT_STATE_CONNECTED;
+ fifo_status(fifo_session);
+}
+
+static int
+fifo_flush(output_status_cb cb, uint64_t rtptime)
+{
+ struct fifo_session *fifo_session = sessions;
+
+ if (!fifo_session)
+ return 0;
+
+ fifo_empty(fifo_session);
+
+ fifo_session->status_cb = cb;
+ fifo_session->state = OUTPUT_STATE_CONNECTED;
+ fifo_status(fifo_session);
+ return 0;
+}
+
+static void
+fifo_write(uint8_t *buf, uint64_t rtptime)
+{
+ struct fifo_session *fifo_session = sessions;
+ size_t length = STOB(AIRTUNES_V2_PACKET_SAMPLES);
+ ssize_t bytes;
+
+ if (!fifo_session || !fifo_session->device->selected)
+ return;
+
+ while (1)
+ {
+ bytes = write(fifo_session->output_fd, buf, length);
+ if (bytes > 0)
+ return;
+
+ if (bytes < 0)
+ {
+ switch (errno)
+ {
+ case EAGAIN:
+ /* The pipe is full, so empty it */
+ fifo_empty(fifo_session);
+ continue;
+ case EINTR:
+ continue;
+ }
+
+ DPRINTF(E_LOG, L_FIFO, "Failed to write to FIFO %s: %d\n", fifo_session->path, errno);
+ return;
+ }
+ }
+}
+
+static void
+fifo_set_status_cb(struct output_session *session, output_status_cb cb)
+{
+ struct fifo_session *fifo_session = session->session;
+
+ fifo_session->status_cb = cb;
+}
+
+static int
+fifo_init(void)
+{
+ struct output_device *device;
+ cfg_t *cfg_fifo;
+ char *nickname;
+ char *path;
+
+ cfg_fifo = cfg_getsec(cfg, "fifo");
+ if (!cfg_fifo)
+ return -1;
+
+ path = cfg_getstr(cfg_fifo, "path");
+ if (!path)
+ return -1;
+
+ nickname = cfg_getstr(cfg_fifo, "nickname");
+
+ device = calloc(1, sizeof(struct output_device));
+ if (!device)
+ {
+ DPRINTF(E_LOG, L_FIFO, "Out of memory for fifo device\n");
+ return -1;
+ }
+
+ device->id = 100;
+ device->name = strdup(nickname);
+ device->type = OUTPUT_TYPE_FIFO;
+ device->type_name = outputs_name(device->type);
+ device->advertised = 1;
+ device->has_video = 0;
+ device->extra_device_info = path;
+ DPRINTF(E_INFO, L_FIFO, "Adding fifo output device '%s' with path '%s'\n", nickname, path);
+
+ player_device_add(device);
+
+ return 0;
+}
+
+static void
+fifo_deinit(void)
+{
+ return;
+}
+
+struct output_definition output_fifo =
+{
+ .name = "fifo",
+ .type = OUTPUT_TYPE_FIFO,
+ .priority = 98,
+ .disabled = 0,
+ .init = fifo_init,
+ .deinit = fifo_deinit,
+ .device_start = fifo_device_start,
+ .device_stop = fifo_device_stop,
+ .device_probe = fifo_device_probe,
+ .device_volume_set = fifo_device_volume_set,
+ .playback_start = fifo_playback_start,
+ .playback_stop = fifo_playback_stop,
+ .write = fifo_write,
+ .flush = fifo_flush,
+ .status_cb = fifo_set_status_cb,
+};
diff --git a/src/outputs/pulse.c b/src/outputs/pulse.c
new file mode 100644
index 0000000..ae7a4a6
--- /dev/null
+++ b/src/outputs/pulse.c
@@ -0,0 +1,976 @@
+/*
+ * Copyright (C) 2016- Espen Jürgensen <espenjurgensen at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <event2/event.h>
+#include <pulse/pulseaudio.h>
+
+#include "misc.h"
+#include "conffile.h"
+#include "logger.h"
+#include "player.h"
+#include "outputs.h"
+#include "commands.h"
+
+#define PULSE_MAX_DEVICES 64
+#define PULSE_LOG_MAX 10
+
+/* TODO for Pulseaudio
+ - Add real sync with AirPlay
+ - Allow per-sink latency config
+*/
+
+struct pulse
+{
+ pa_threaded_mainloop *mainloop;
+ pa_context *context;
+
+ struct commands_base *cmdbase;
+
+ int operation_success;
+} pulse;
+
+struct pulse_session
+{
+ pa_stream_state_t state;
+ pa_stream *stream;
+
+ pa_buffer_attr attr;
+ pa_volume_t volume;
+
+ int logcount;
+
+ char *devname;
+
+ /* Do not dereference - only passed to the status cb */
+ struct output_device *device;
+ struct output_session *output_session;
+ output_status_cb status_cb;
+
+ struct pulse_session *next;
+};
+
+// From player.c
+extern struct event_base *evbase_player;
+
+// Globals
+static struct pulse_session *sessions;
+
+// Internal list with indeces of the Pulseaudio devices (sinks) we have registered
+static uint32_t pulse_known_devices[PULSE_MAX_DEVICES];
+
+// Converts from 0 - 100 to Pulseaudio's scale
+static inline pa_volume_t
+pulse_from_device_volume(int device_volume)
+{
+ return (PA_VOLUME_MUTED + (device_volume * (PA_VOLUME_NORM - PA_VOLUME_MUTED)) / 100);
+}
+
+/* ---------------------------- SESSION HANDLING ---------------------------- */
+
+static void
+pulse_session_free(struct pulse_session *ps)
+{
+ if (ps->stream)
+ {
+ pa_threaded_mainloop_lock(pulse.mainloop);
+
+ pa_stream_set_underflow_callback(ps->stream, NULL, NULL);
+ pa_stream_set_overflow_callback(ps->stream, NULL, NULL);
+ pa_stream_set_state_callback(ps->stream, NULL, NULL);
+ pa_stream_disconnect(ps->stream);
+ pa_stream_unref(ps->stream);
+
+ pa_threaded_mainloop_unlock(pulse.mainloop);
+ }
+
+ if (ps->devname)
+ free(ps->devname);
+
+ free(ps->output_session);
+
+ free(ps);
+}
+
+static void
+pulse_session_cleanup(struct pulse_session *ps)
+{
+ struct pulse_session *p;
+
+ if (ps == sessions)
+ sessions = sessions->next;
+ else
+ {
+ for (p = sessions; p && (p->next != ps); p = p->next)
+ ; /* EMPTY */
+
+ if (!p)
+ DPRINTF(E_WARN, L_LAUDIO, "WARNING: struct pulse_session not found in list; BUG!\n");
+ else
+ p->next = ps->next;
+ }
+
+ pulse_session_free(ps);
+}
+
+static struct pulse_session *
+pulse_session_make(struct output_device *device, output_status_cb cb)
+{
+ struct output_session *os;
+ struct pulse_session *ps;
+
+ os = calloc(1, sizeof(struct output_session));
+ ps = calloc(1, sizeof(struct pulse_session));
+ if (!os || !ps)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Out of memory for Pulseaudio session\n");
+ return NULL;
+ }
+
+ os->session = ps;
+ os->type = device->type;
+
+ ps->output_session = os;
+ ps->state = PA_STREAM_UNCONNECTED;
+ ps->device = device;
+ ps->status_cb = cb;
+ ps->volume = pulse_from_device_volume(device->volume);
+ ps->devname = strdup(device->extra_device_info);
+
+ ps->next = sessions;
+ sessions = ps;
+
+ return ps;
+}
+
+/* ---------------------------- COMMAND HANDLERS ---------------------------- */
+
+// Maps our internal state to the generic output state and then makes a callback
+// to the player to tell that state. Should always be called deferred.
+static enum command_state
+send_status(void *arg, int *ptr)
+{
+ struct pulse_session *ps = arg;
+ output_status_cb status_cb;
+ enum output_device_state state;
+
+ switch (ps->state)
+ {
+ case PA_STREAM_FAILED:
+ state = OUTPUT_STATE_FAILED;
+ break;
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_TERMINATED:
+ state = OUTPUT_STATE_STOPPED;
+ break;
+ case PA_STREAM_READY:
+ state = OUTPUT_STATE_CONNECTED;
+ break;
+ case PA_STREAM_CREATING:
+ state = OUTPUT_STATE_STARTUP;
+ break;
+ default:
+ DPRINTF(E_LOG, L_LAUDIO, "Bug! Unhandled state in send_status()\n");
+ state = OUTPUT_STATE_FAILED;
+ }
+
+ status_cb = ps->status_cb;
+ ps->status_cb = NULL;
+ if (status_cb)
+ status_cb(ps->device, ps->output_session, state);
+
+ return COMMAND_PENDING; // Don't want the command module to clean up ps
+}
+
+static enum command_state
+session_shutdown(void *arg, int *ptr)
+{
+ struct pulse_session *ps = arg;
+
+ send_status(ps, ptr);
+ pulse_session_cleanup(ps);
+
+ return COMMAND_PENDING; // Don't want the command module to clean up ps
+}
+
+/* ---------------------- EXECUTED IN PULSEAUDIO THREAD --------------------- */
+
+static void
+pulse_status(struct pulse_session *ps)
+{
+ // async to avoid risk of deadlock if the player should make calls back to Pulseaudio
+ commands_exec_async(pulse.cmdbase, send_status, ps);
+}
+
+static void
+pulse_session_shutdown(struct pulse_session *ps)
+{
+ // async to avoid risk of deadlock if the player should make calls back to Pulseaudio
+ commands_exec_async(pulse.cmdbase, session_shutdown, ps);
+}
+
+static void
+pulse_session_shutdown_all(pa_stream_state_t state)
+{
+ struct pulse_session *ps;
+ struct pulse_session *next;
+
+ for (ps = sessions; ps; ps = next)
+ {
+ next = ps->next;
+ ps->state = state;
+ pulse_session_shutdown(ps);
+ }
+}
+
+/* --------------------- CALLBACKS FROM PULSEAUDIO THREAD ------------------- */
+
+
+// This will be called if something happens to the stream after it was opened
+static void
+stream_state_cb(pa_stream *s, void *userdata)
+{
+ struct pulse_session *ps = userdata;
+
+ DPRINTF(E_DBG, L_LAUDIO, "Pulseaudio stream to '%s' changed state (%d)\n", ps->devname, ps->state);
+
+ ps->state = pa_stream_get_state(s);
+ if (!PA_STREAM_IS_GOOD(ps->state))
+ {
+ if (ps->state == PA_STREAM_FAILED)
+ {
+ errno = pa_context_errno(pulse.context);
+ DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio stream to '%s' failed with error: %s\n", ps->devname, pa_strerror(errno));
+ }
+ else
+ DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio stream to '%s' aborted (%d)\n", ps->devname, ps->state);
+
+ pulse_session_shutdown(ps);
+ return;
+ }
+}
+
+static void
+underrun_cb(pa_stream *s, void *userdata)
+{
+ struct pulse_session *ps = userdata;
+
+ if (ps->logcount > PULSE_LOG_MAX)
+ return;
+
+ ps->logcount++;
+
+ if (ps->logcount < PULSE_LOG_MAX)
+ DPRINTF(E_WARN, L_LAUDIO, "Pulseaudio reports buffer underrun on '%s'\n", ps->devname);
+ else if (ps->logcount == PULSE_LOG_MAX)
+ DPRINTF(E_WARN, L_LAUDIO, "Pulseaudio reports buffer underrun on '%s' (no further logging)\n", ps->devname);
+}
+
+static void
+overrun_cb(pa_stream *s, void *userdata)
+{
+ struct pulse_session *ps = userdata;
+
+ if (ps->logcount > PULSE_LOG_MAX)
+ return;
+
+ ps->logcount++;
+
+ if (ps->logcount < PULSE_LOG_MAX)
+ DPRINTF(E_WARN, L_LAUDIO, "Pulseaudio reports buffer overrun on '%s'\n", ps->devname);
+ else if (ps->logcount == PULSE_LOG_MAX)
+ DPRINTF(E_WARN, L_LAUDIO, "Pulseaudio reports buffer overrun on '%s' (no further logging)\n", ps->devname);
+}
+
+// This will be called our request to open the stream has completed
+static void
+start_cb(pa_stream *s, void *userdata)
+{
+ struct pulse_session *ps = userdata;
+
+ ps->state = pa_stream_get_state(s);
+ if (ps->state == PA_STREAM_CREATING)
+ return;
+
+ if (ps->state != PA_STREAM_READY)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Error starting Pulseaudio stream to '%s' (%d)\n", ps->devname, ps->state);
+ pulse_session_shutdown(ps);
+ return;
+ }
+
+ pa_stream_set_underflow_callback(ps->stream, underrun_cb, ps);
+ pa_stream_set_overflow_callback(ps->stream, overrun_cb, ps);
+ pa_stream_set_state_callback(ps->stream, stream_state_cb, ps);
+
+ pulse_status(ps);
+}
+
+static void
+close_cb(pa_stream *s, void *userdata)
+{
+ struct pulse_session *ps = userdata;
+
+ pulse_session_shutdown(ps);
+}
+
+// This will be called our request to probe the stream has completed
+static void
+probe_cb(pa_stream *s, void *userdata)
+{
+ struct pulse_session *ps = userdata;
+
+ ps->state = pa_stream_get_state(s);
+ if (ps->state == PA_STREAM_CREATING)
+ return;
+
+ if (ps->state != PA_STREAM_READY)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Error probing Pulseaudio stream to '%s' (%d)\n", ps->devname, ps->state);
+ pulse_session_shutdown(ps);
+ return;
+ }
+
+ // This will callback to the player with succes and then remove the session
+ pulse_session_shutdown(ps);
+}
+
+static void
+flush_cb(pa_stream *s, int success, void *userdata)
+{
+ struct pulse_session *ps = userdata;
+
+ pulse_status(ps);
+}
+
+static void
+volume_cb(pa_context *c, int success, void *userdata)
+{
+ struct pulse_session *ps = userdata;
+
+ pulse_status(ps);
+}
+
+static void
+sinklist_cb(pa_context *ctx, const pa_sink_info *info, int eol, void *userdata)
+{
+ struct output_device *device;
+ const char *name;
+ int i;
+ int pos;
+
+ if (eol > 0)
+ return;
+
+ DPRINTF(E_DBG, L_LAUDIO, "Callback for Pulseaudio sink '%s' (id %" PRIu32 ")\n", info->name, info->index);
+
+ pos = -1;
+ for (i = 0; i < PULSE_MAX_DEVICES; i++)
+ {
+ if (pulse_known_devices[i] == (info->index + 1))
+ return;
+
+ if (pulse_known_devices[i] == 0)
+ pos = i;
+ }
+
+ if (pos == -1)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Maximum number of Pulseaudio devices reached (%d), cannot add '%s'\n", PULSE_MAX_DEVICES, info->name);
+ return;
+ }
+
+ device = calloc(1, sizeof(struct output_device));
+ if (!device)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Out of memory for new Pulseaudio sink\n");
+ return;
+ }
+
+ if (info->index == 0)
+ {
+ name = cfg_getstr(cfg_getsec(cfg, "audio"), "nickname");
+
+ DPRINTF(E_LOG, L_LAUDIO, "Adding Pulseaudio sink '%s' (%s) with name '%s'\n", info->description, info->name, name);
+ }
+ else
+ {
+ name = info->description;
+
+ DPRINTF(E_LOG, L_LAUDIO, "Adding Pulseaudio sink '%s' (%s)\n", info->description, info->name);
+ }
+
+ pulse_known_devices[pos] = info->index + 1; // Array values of 0 mean no device, so we add 1 to make sure the value is > 0
+
+ device->id = info->index;
+ device->name = strdup(name);
+ device->type = OUTPUT_TYPE_PULSE;
+ device->type_name = outputs_name(device->type);
+ device->advertised = 1;
+ device->extra_device_info = strdup(info->name);
+
+ player_device_add(device);
+}
+
+static void
+subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata)
+{
+ struct output_device *device;
+ pa_operation *o;
+ int i;
+
+ DPRINTF(E_DBG, L_LAUDIO, "Callback for Pulseaudio subscribe (id %" PRIu32 ", event %d)\n", index, t);
+
+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) != PA_SUBSCRIPTION_EVENT_SINK)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio subscribe called back with unknown event\n");
+ return;
+ }
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
+ {
+ device = calloc(1, sizeof(struct output_device));
+ if (!device)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Out of memory for temp Pulseaudio device\n");
+ return;
+ }
+
+ device->id = index;
+
+ DPRINTF(E_LOG, L_LAUDIO, "Removing Pulseaudio sink with id %" PRIu32 "\n", index);
+
+ for (i = 0; i < PULSE_MAX_DEVICES; i++)
+ {
+ if (pulse_known_devices[i] == index)
+ pulse_known_devices[i] = 0;
+ }
+
+ player_device_remove(device);
+ return;
+ }
+
+ o = pa_context_get_sink_info_by_index(c, index, sinklist_cb, NULL);
+ if (!o)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio error getting sink info for id %" PRIu32 "\n", index);
+ return;
+ }
+ pa_operation_unref(o);
+}
+
+static void
+context_state_cb(pa_context *c, void *userdata)
+{
+ pa_context_state_t state;
+ pa_operation *o;
+
+ state = pa_context_get_state(c);
+
+ switch (state)
+ {
+ case PA_CONTEXT_READY:
+ DPRINTF(E_DBG, L_LAUDIO, "Pulseaudio context state changed to ready\n");
+
+ o = pa_context_get_sink_info_list(c, sinklist_cb, NULL);
+ if (!o)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Could not list Pulseaudio sink info\n");
+ return;
+ }
+ pa_operation_unref(o);
+
+ pa_context_set_subscribe_callback(c, subscribe_cb, NULL);
+ o = pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK, NULL, NULL);
+ if (!o)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Could not subscribe to Pulseaudio sink info\n");
+ return;
+ }
+ pa_operation_unref(o);
+
+ pa_threaded_mainloop_signal(pulse.mainloop, 0);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio failed with error: %s\n", pa_strerror(pa_context_errno(c)));
+ pulse_session_shutdown_all(PA_STREAM_FAILED);
+ pa_threaded_mainloop_signal(pulse.mainloop, 0);
+ break;
+
+ case PA_CONTEXT_TERMINATED:
+ DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio terminated\n");
+ pulse_session_shutdown_all(PA_STREAM_UNCONNECTED);
+ pa_threaded_mainloop_signal(pulse.mainloop, 0);
+ break;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+}
+
+
+/* ------------------------------- MISC HELPERS ----------------------------- */
+
+// Used by init and deinit to stop main thread
+static void
+pulse_free(void)
+{
+ if (pulse.mainloop)
+ pa_threaded_mainloop_stop(pulse.mainloop);
+
+ if (pulse.context)
+ {
+ pa_context_disconnect(pulse.context);
+ pa_context_unref(pulse.context);
+ }
+
+ if (pulse.cmdbase)
+ commands_base_free(pulse.cmdbase);
+
+ if (pulse.mainloop)
+ pa_threaded_mainloop_free(pulse.mainloop);
+}
+
+static int
+stream_open(struct pulse_session *ps, pa_stream_notify_cb_t cb)
+{
+ pa_stream_flags_t flags;
+ pa_sample_spec ss;
+ pa_cvolume cvol;
+ int offset;
+ int ret;
+
+ DPRINTF(E_DBG, L_LAUDIO, "Opening Pulseaudio stream to '%s'\n", ps->devname);
+
+ ss.format = PA_SAMPLE_S16LE;
+ ss.channels = 2;
+ ss.rate = 44100;
+
+ offset = cfg_getint(cfg_getsec(cfg, "audio"), "offset");
+ if (abs(offset) > 44100)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "The audio offset (%d) set in the configuration is out of bounds\n", offset);
+ offset = 44100 * (offset/abs(offset));
+ }
+
+ pa_threaded_mainloop_lock(pulse.mainloop);
+
+ if (!(ps->stream = pa_stream_new(pulse.context, "forked-daapd audio", &ss, NULL)))
+ goto unlock_and_fail;
+
+ pa_stream_set_state_callback(ps->stream, cb, ps);
+
+ flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
+
+ ps->attr.tlength = STOB(2 * ss.rate + AIRTUNES_V2_PACKET_SAMPLES - offset); // 2 second latency
+ ps->attr.maxlength = 2 * ps->attr.tlength;
+ ps->attr.prebuf = (uint32_t)-1;
+ ps->attr.minreq = (uint32_t)-1;
+ ps->attr.fragsize = (uint32_t)-1;
+
+ pa_cvolume_set(&cvol, 2, ps->volume);
+
+ ret = pa_stream_connect_playback(ps->stream, ps->devname, &ps->attr, flags, &cvol, NULL);
+ if (ret < 0)
+ goto unlock_and_fail;
+
+ ps->state = pa_stream_get_state(ps->stream);
+ if (!PA_STREAM_IS_GOOD(ps->state))
+ goto unlock_and_fail;
+
+ pa_threaded_mainloop_unlock(pulse.mainloop);
+
+ return 0;
+
+ unlock_and_fail:
+ ret = pa_context_errno(pulse.context);
+
+ DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not start '%s': %s\n", ps->devname, pa_strerror(ret));
+
+ pa_threaded_mainloop_unlock(pulse.mainloop);
+
+ return -1;
+}
+
+static void
+stream_close(struct pulse_session *ps, pa_stream_notify_cb_t cb)
+{
+ pa_threaded_mainloop_lock(pulse.mainloop);
+
+ pa_stream_set_underflow_callback(ps->stream, NULL, NULL);
+ pa_stream_set_overflow_callback(ps->stream, NULL, NULL);
+ pa_stream_set_state_callback(ps->stream, cb, ps);
+ pa_stream_disconnect(ps->stream);
+ pa_stream_unref(ps->stream);
+
+ ps->state = PA_STREAM_TERMINATED;
+ ps->stream = NULL;
+
+ pa_threaded_mainloop_unlock(pulse.mainloop);
+}
+
+
+/* ------------------ INTERFACE FUNCTIONS CALLED BY OUTPUTS.C --------------- */
+
+static int
+pulse_device_start(struct output_device *device, output_status_cb cb, uint64_t rtptime)
+{
+ struct pulse_session *ps;
+ int ret;
+
+ DPRINTF(E_DBG, L_LAUDIO, "Pulseaudio starting '%s'\n", device->name);
+
+ ps = pulse_session_make(device, cb);
+ if (!ps)
+ return -1;
+
+ ret = stream_open(ps, start_cb);
+ if (ret < 0)
+ {
+ pulse_session_cleanup(ps);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+pulse_device_stop(struct output_session *session)
+{
+ struct pulse_session *ps = session->session;
+
+ DPRINTF(E_DBG, L_LAUDIO, "Pulseaudio stopping '%s'\n", ps->devname);
+
+ stream_close(ps, close_cb);
+}
+
+static int
+pulse_device_probe(struct output_device *device, output_status_cb cb)
+{
+ struct pulse_session *ps;
+ int ret;
+
+ DPRINTF(E_DBG, L_LAUDIO, "Pulseaudio probing '%s'\n", device->name);
+
+ ps = pulse_session_make(device, cb);
+ if (!ps)
+ return -1;
+
+ ret = stream_open(ps, probe_cb);
+ if (ret < 0)
+ {
+ pulse_session_cleanup(ps);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+pulse_device_free_extra(struct output_device *device)
+{
+ free(device->extra_device_info);
+}
+
+static int
+pulse_device_volume_set(struct output_device *device, output_status_cb cb)
+{
+ struct pulse_session *ps;
+ uint32_t idx;
+ pa_operation* o;
+ pa_cvolume cvol;
+
+ if (!sessions || !device->session || !device->session->session)
+ return 0;
+
+ ps = device->session->session;
+ idx = pa_stream_get_index(ps->stream);
+
+ ps->volume = pulse_from_device_volume(device->volume);
+ pa_cvolume_set(&cvol, 2, ps->volume);
+
+ DPRINTF(E_DBG, L_LAUDIO, "Setting Pulseaudio volume for stream %" PRIu32 " to %d (%d)\n", idx, (int)ps->volume, device->volume);
+
+ pa_threaded_mainloop_lock(pulse.mainloop);
+
+ ps->status_cb = cb;
+
+ o = pa_context_set_sink_input_volume(pulse.context, idx, &cvol, volume_cb, ps);
+ if (!o)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not set volume: %s\n", pa_strerror(pa_context_errno(pulse.context)));
+ pa_threaded_mainloop_unlock(pulse.mainloop);
+ return 0;
+ }
+ pa_operation_unref(o);
+
+ pa_threaded_mainloop_unlock(pulse.mainloop);
+
+ return 1;
+}
+
+static void
+pulse_write(uint8_t *buf, uint64_t rtptime)
+{
+ struct pulse_session *ps;
+ struct pulse_session *next;
+ size_t length;
+ int ret;
+
+ if (!sessions)
+ return;
+
+ length = STOB(AIRTUNES_V2_PACKET_SAMPLES);
+
+ pa_threaded_mainloop_lock(pulse.mainloop);
+
+ for (ps = sessions; ps; ps = next)
+ {
+ next = ps->next;
+
+ if (ps->state != PA_STREAM_READY)
+ continue;
+
+ ret = pa_stream_write(ps->stream, buf, length, NULL, 0LL, PA_SEEK_RELATIVE);
+ if (ret < 0)
+ {
+ ret = pa_context_errno(pulse.context);
+ DPRINTF(E_LOG, L_LAUDIO, "Error writing Pulseaudio stream data to '%s': %s\n", ps->devname, pa_strerror(ret));
+
+ ps->state = PA_STREAM_FAILED;
+ pulse_session_shutdown(ps);
+
+ continue;
+ }
+ }
+
+ pa_threaded_mainloop_unlock(pulse.mainloop);
+}
+
+static void
+pulse_playback_start(uint64_t next_pkt, struct timespec *ts)
+{
+ struct pulse_session *ps;
+ pa_operation* o;
+
+ pa_threaded_mainloop_lock(pulse.mainloop);
+
+ for (ps = sessions; ps; ps = ps->next)
+ {
+ o = pa_stream_cork(ps->stream, 0, NULL, NULL);
+ if (!o)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not resume '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
+ continue;
+ }
+ pa_operation_unref(o);
+ }
+
+ pa_threaded_mainloop_unlock(pulse.mainloop);
+}
+
+static void
+pulse_playback_stop(void)
+{
+ struct pulse_session *ps;
+ pa_operation* o;
+
+ pa_threaded_mainloop_lock(pulse.mainloop);
+
+ for (ps = sessions; ps; ps = ps->next)
+ {
+ o = pa_stream_cork(ps->stream, 1, NULL, NULL);
+ if (!o)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not pause '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
+ continue;
+ }
+ pa_operation_unref(o);
+
+ o = pa_stream_flush(ps->stream, NULL, NULL);
+ if (!o)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not flush '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
+ continue;
+ }
+ pa_operation_unref(o);
+ }
+
+ pa_threaded_mainloop_unlock(pulse.mainloop);
+}
+
+static int
+pulse_flush(output_status_cb cb, uint64_t rtptime)
+{
+ struct pulse_session *ps;
+ pa_operation* o;
+ int i;
+
+ DPRINTF(E_DBG, L_LAUDIO, "Pulseaudio flush\n");
+
+ pa_threaded_mainloop_lock(pulse.mainloop);
+
+ i = 0;
+ for (ps = sessions; ps; ps = ps->next)
+ {
+ i++;
+
+ ps->status_cb = cb;
+
+ o = pa_stream_cork(ps->stream, 1, NULL, NULL);
+ if (!o)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not pause '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
+ continue;
+ }
+ pa_operation_unref(o);
+
+ o = pa_stream_flush(ps->stream, flush_cb, ps);
+ if (!o)
+ {
+ DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not flush '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
+ continue;
+ }
+ pa_operation_unref(o);
+ }
+
+ pa_threaded_mainloop_unlock(pulse.mainloop);
+
+ return i;
+}
+
+static void
+pulse_set_status_cb(struct output_session *session, output_status_cb cb)
+{
+ struct pulse_session *ps = session->session;
+
+ ps->status_cb = cb;
+}
+
+static int
+pulse_init(void)
+{
+ char *type;
+ int state;
+ int ret;
+
+ type = cfg_getstr(cfg_getsec(cfg, "audio"), "type");
+ if (type && (strcasecmp(type, "pulseaudio") != 0))
+ return -1;
+
+ ret = 0;
+
+ if (!(pulse.mainloop = pa_threaded_mainloop_new()))
+ goto fail;
+
+ if (!(pulse.cmdbase = commands_base_new(evbase_player, NULL)))
+ goto fail;
+
+#ifdef HAVE_PULSE_MAINLOOP_SET_NAME
+ pa_threaded_mainloop_set_name(pulse.mainloop, "pulseaudio");
+#endif
+
+ if (!(pulse.context = pa_context_new(pa_threaded_mainloop_get_api(pulse.mainloop), "forked-daapd")))
+ goto fail;
+
+ pa_context_set_state_callback(pulse.context, context_state_cb, NULL);
+
+ if (pa_context_connect(pulse.context, NULL, 0, NULL) < 0)
+ {
+ ret = pa_context_errno(pulse.context);
+ goto fail;
+ }
+
+ pa_threaded_mainloop_lock(pulse.mainloop);
+
+ if (pa_threaded_mainloop_start(pulse.mainloop) < 0)
+ goto unlock_and_fail;
+
+ for (;;)
+ {
+ state = pa_context_get_state(pulse.context);
+
+ if (state == PA_CONTEXT_READY)
+ break;
+
+ if (!PA_CONTEXT_IS_GOOD(state))
+ {
+ ret = pa_context_errno(pulse.context);
+ goto unlock_and_fail;
+ }
+
+ /* Wait until the context is ready */
+ pa_threaded_mainloop_wait(pulse.mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(pulse.mainloop);
+
+ return 0;
+
+ unlock_and_fail:
+ pa_threaded_mainloop_unlock(pulse.mainloop);
+
+ fail:
+ if (ret)
+ DPRINTF(E_LOG, L_LAUDIO, "Error initializing Pulseaudio: %s\n", pa_strerror(ret));
+
+ pulse_free();
+ return -1;
+}
+
+static void
+pulse_deinit(void)
+{
+ pulse_free();
+}
+
+struct output_definition output_pulse =
+{
+ .name = "Pulseaudio",
+ .type = OUTPUT_TYPE_PULSE,
+ .priority = 3,
+ .disabled = 0,
+ .init = pulse_init,
+ .deinit = pulse_deinit,
+ .device_start = pulse_device_start,
+ .device_stop = pulse_device_stop,
+ .device_probe = pulse_device_probe,
+ .device_free_extra = pulse_device_free_extra,
+ .device_volume_set = pulse_device_volume_set,
+ .playback_start = pulse_playback_start,
+ .playback_stop = pulse_playback_stop,
+ .write = pulse_write,
+ .flush = pulse_flush,
+ .status_cb = pulse_set_status_cb,
+};
+
diff --git a/src/outputs/raop.c b/src/outputs/raop.c
index a36f277..ec98cbe 100644
--- a/src/outputs/raop.c
+++ b/src/outputs/raop.c
@@ -114,6 +114,7 @@ enum raop_devtype {
RAOP_DEV_APEX2_80211N,
RAOP_DEV_APEX3_80211N,
RAOP_DEV_APPLETV,
+ RAOP_DEV_APPLETV4,
RAOP_DEV_OTHER,
};
@@ -161,6 +162,7 @@ struct raop_session
unsigned encrypt:1;
unsigned auth_quirk_itunes:1;
unsigned wants_metadata:1;
+ unsigned keep_alive:1;
struct event *deferredev;
@@ -263,6 +265,7 @@ static const char *raop_devtype[] =
"AirPort Express 2 - 802.11n",
"AirPort Express 3 - 802.11n",
"AppleTV",
+ "AppleTV4",
"Other",
};
@@ -303,6 +306,10 @@ static struct raop_metadata *metadata_tail;
/* FLUSH timer */
static struct event *flush_timer;
+/* Keep-alive timer - hack for ATV's with tvOS 10 */
+static struct event *keep_alive_timer;
+static struct timeval keep_alive_tv = { 60, 0 };
+
/* Sessions */
static struct raop_session *sessions;
@@ -964,7 +971,7 @@ raop_add_auth(struct raop_session *rs, struct evrtsp_request *req, const char *m
if (!rs->password)
{
- DPRINTF(E_LOG, L_RAOP, "Authentication required but no password found for device %s\n", rs->devname);
+ DPRINTF(E_LOG, L_RAOP, "Authentication required but no password found for device '%s'\n", rs->devname);
return -2;
}
@@ -1180,7 +1187,7 @@ raop_add_headers(struct raop_session *rs, struct evrtsp_request *req, enum evrts
method = evrtsp_method(req_method);
- DPRINTF(E_DBG, L_RAOP, "Building %s for %s\n", method, rs->devname);
+ DPRINTF(E_DBG, L_RAOP, "Building %s for '%s'\n", method, rs->devname);
snprintf(buf, sizeof(buf), "%d", rs->cseq);
evrtsp_add_header(req->output_headers, "CSeq", buf);
@@ -1820,7 +1827,7 @@ raop_deferredev_cb(int fd, short what, void *arg)
rs = (struct raop_session *)arg;
- DPRINTF(E_DBG, L_RAOP, "Cleaning up failed session (deferred) on device %s\n", rs->devname);
+ DPRINTF(E_DBG, L_RAOP, "Cleaning up failed session (deferred) on device '%s'\n", rs->devname);
raop_session_failure(rs);
}
@@ -1833,7 +1840,7 @@ raop_rtsp_close_cb(struct evrtsp_connection *evcon, void *arg)
rs = (struct raop_session *)arg;
- DPRINTF(E_LOG, L_RAOP, "ApEx %s closed RTSP connection\n", rs->devname);
+ DPRINTF(E_LOG, L_RAOP, "Device '%s' closed RTSP connection\n", rs->devname);
rs->state = RAOP_STATE_FAILED;
@@ -1905,26 +1912,37 @@ raop_session_make(struct output_device *rd, int family, output_status_cb cb)
case RAOP_DEV_APEX1_80211G:
rs->encrypt = 1;
rs->auth_quirk_itunes = 1;
+ rs->keep_alive = 0;
break;
case RAOP_DEV_APEX2_80211N:
rs->encrypt = 1;
rs->auth_quirk_itunes = 0;
+ rs->keep_alive = 0;
break;
case RAOP_DEV_APEX3_80211N:
rs->encrypt = 0;
rs->auth_quirk_itunes = 0;
+ rs->keep_alive = 0;
break;
case RAOP_DEV_APPLETV:
rs->encrypt = 0;
rs->auth_quirk_itunes = 0;
+ rs->keep_alive = 0;
+ break;
+
+ case RAOP_DEV_APPLETV4:
+ rs->encrypt = 0;
+ rs->auth_quirk_itunes = 0;
+ rs->keep_alive = 1;
break;
case RAOP_DEV_OTHER:
rs->encrypt = re->encrypt;
rs->auth_quirk_itunes = 0;
+ rs->keep_alive = 0;
break;
}
@@ -1939,7 +1957,7 @@ raop_session_make(struct output_device *rd, int family, output_status_cb cb)
rs->ctrl = evrtsp_connection_new(address, port);
if (!rs->ctrl)
{
- DPRINTF(E_LOG, L_RAOP, "Could not create control connection to %s\n", address);
+ DPRINTF(E_LOG, L_RAOP, "Could not create control connection to '%s' (%s)\n", rd->name, address);
goto out_free_event;
}
@@ -1991,7 +2009,7 @@ raop_session_make(struct output_device *rd, int family, output_status_cb cb)
if (ret <= 0)
{
- DPRINTF(E_LOG, L_RAOP, "Device address not valid (%s)\n", address);
+ DPRINTF(E_LOG, L_RAOP, "Device '%s' has invalid address (%s) for %s\n", rd->name, address, (family == AF_INET) ? "ipv4" : "ipv6");
goto out_free_evcon;
}
@@ -2194,7 +2212,7 @@ raop_metadata_send_internal(struct raop_session *rs, struct raop_metadata *rmd,
ret = raop_metadata_send_metadata(rs, evbuf, rmd, rtptime);
if (ret < 0)
{
- DPRINTF(E_LOG, L_RAOP, "Could not send metadata to %s\n", rs->devname);
+ DPRINTF(E_LOG, L_RAOP, "Could not send metadata to '%s'\n", rs->devname);
ret = -1;
goto out;
@@ -2206,7 +2224,7 @@ raop_metadata_send_internal(struct raop_session *rs, struct raop_metadata *rmd,
ret = raop_metadata_send_artwork(rs, evbuf, rmd, rtptime);
if (ret < 0)
{
- DPRINTF(E_LOG, L_RAOP, "Could not send artwork to %s\n", rs->devname);
+ DPRINTF(E_LOG, L_RAOP, "Could not send artwork to '%s'\n", rs->devname);
ret = -1;
goto out;
@@ -2216,7 +2234,7 @@ raop_metadata_send_internal(struct raop_session *rs, struct raop_metadata *rmd,
ret = raop_metadata_send_progress(rs, evbuf, rmd, offset, delay);
if (ret < 0)
{
- DPRINTF(E_LOG, L_RAOP, "Could not send progress to %s\n", rs->devname);
+ DPRINTF(E_LOG, L_RAOP, "Could not send progress to '%s'\n", rs->devname);
ret = -1;
goto out;
@@ -2335,8 +2353,6 @@ raop_volume_convert(int volume, char *name)
max_volume = RAOP_CONFIG_MAX_VOLUME;
}
- DPRINTF(E_DBG, L_RAOP, "Setting max_volume for device %s to %d\n", name, max_volume);
-
/* RAOP volume
* -144.0 is off
* 0 - 100 maps to -30.0 - 0
@@ -2488,6 +2504,31 @@ raop_cb_flush(struct evrtsp_request *req, void *arg)
raop_session_failure(rs);
}
+static void
+raop_cb_keep_alive(struct evrtsp_request *req, void *arg)
+{
+ struct raop_session *rs = arg;
+
+ rs->reqs_in_flight--;
+
+ if (!req)
+ goto error;
+
+ if (req->response_code != RTSP_OK)
+ {
+ DPRINTF(E_LOG, L_RAOP, "SET_PARAMETER request failed for keep alive: %d %s\n", req->response_code, req->response_code_line);
+ goto error;
+ }
+
+ if (!rs->reqs_in_flight)
+ evrtsp_connection_set_closecb(rs->ctrl, raop_rtsp_close_cb, rs);
+
+ return;
+
+ error:
+ raop_session_failure(rs);
+}
+
// Forward
static void
raop_device_stop(struct output_session *session);
@@ -2508,6 +2549,25 @@ raop_flush_timer_cb(int fd, short what, void *arg)
}
}
+static void
+raop_keep_alive_timer_cb(int fd, short what, void *arg)
+{
+ struct raop_session *rs;
+
+ for (rs = sessions; rs; rs = rs->next)
+ {
+ if (!rs->keep_alive)
+ continue;
+
+ if (!(rs->state & RAOP_STATE_F_CONNECTED))
+ continue;
+
+ raop_set_volume_internal(rs, rs->volume, raop_cb_keep_alive);
+ }
+
+ evtimer_add(keep_alive_timer, &keep_alive_tv);
+}
+
static int
raop_flush(output_status_cb cb, uint64_t rtptime)
{
@@ -2865,7 +2925,7 @@ raop_v2_control_send_sync(uint64_t next_pkt, struct timespec *init)
ret = sendto(rs->control_svc->fd, msg, sizeof(msg), 0, &rs->sa.sa, len);
if (ret < 0)
- DPRINTF(E_LOG, L_RAOP, "Could not send playback sync to device %s: %s\n", rs->devname, strerror(errno));
+ DPRINTF(E_LOG, L_RAOP, "Could not send playback sync to device '%s': %s\n", rs->devname, strerror(errno));
}
}
@@ -3263,14 +3323,14 @@ raop_v2_send_packet(struct raop_session *rs, struct raop_v2_packet *pkt)
ret = send(rs->server_fd, data, AIRTUNES_V2_PKT_LEN, 0);
if (ret < 0)
{
- DPRINTF(E_LOG, L_RAOP, "Send error for %s: %s\n", rs->devname, strerror(errno));
+ DPRINTF(E_LOG, L_RAOP, "Send error for '%s': %s\n", rs->devname, strerror(errno));
raop_session_failure(rs);
return -1;
}
else if (ret != AIRTUNES_V2_PKT_LEN)
{
- DPRINTF(E_WARN, L_RAOP, "Partial send (%d) for %s\n", ret, rs->devname);
+ DPRINTF(E_WARN, L_RAOP, "Partial send (%d) for '%s'\n", ret, rs->devname);
return -1;
}
@@ -3329,7 +3389,7 @@ raop_v2_resend_range(struct raop_session *rs, uint16_t seqnum, uint16_t len)
/* Check that seqnum is in the retransmit buffer */
if ((seqnum > pktbuf_head->seqnum) || (seqnum < pktbuf_tail->seqnum))
{
- DPRINTF(E_WARN, L_RAOP, "RAOP device %s asking for seqnum %u; not in buffer (h %u t %u)\n", rs->devname, seqnum, pktbuf_head->seqnum, pktbuf_tail->seqnum);
+ DPRINTF(E_WARN, L_RAOP, "Device '%s' asking for seqnum %u; not in buffer (h %u t %u)\n", rs->devname, seqnum, pktbuf_head->seqnum, pktbuf_tail->seqnum);
return;
}
@@ -3526,7 +3586,7 @@ raop_cb_startup_record(struct evrtsp_request *req, void *arg)
/* Audio latency */
param = evrtsp_find_header(req->input_headers, "Audio-Latency");
if (!param)
- DPRINTF(E_INFO, L_RAOP, "RECORD reply from %s did not have an Audio-Latency header\n", rs->devname);
+ DPRINTF(E_INFO, L_RAOP, "RECORD reply from '%s' did not have an Audio-Latency header\n", rs->devname);
else
DPRINTF(E_DBG, L_RAOP, "RAOP audio latency is %s\n", param);
@@ -3738,12 +3798,24 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg)
rs->reqs_in_flight--;
- if (!req)
- goto cleanup;
+ if (!req || !req->response_code)
+ {
+ DPRINTF(E_LOG, L_RAOP, "No response from '%s' (%s) to OPTIONS request\n", rs->devname, rs->address);
+
+ if (rs->device->v4_address && (rs->sa.ss.ss_family == AF_INET6))
+ {
+ DPRINTF(E_LOG, L_RAOP, "Falling back to ipv4, the ipv6 address is not responding\n");
+
+ free(rs->device->v6_address);
+ rs->device->v6_address = NULL;
+ }
+
+ goto cleanup;
+ }
if ((req->response_code != RTSP_OK) && (req->response_code != RTSP_UNAUTHORIZED))
{
- DPRINTF(E_LOG, L_RAOP, "OPTIONS request failed in session startup: %d %s\n", req->response_code, req->response_code_line);
+ DPRINTF(E_LOG, L_RAOP, "OPTIONS request failed starting '%s': %d %s\n", rs->devname, req->response_code, req->response_code_line);
goto cleanup;
}
@@ -3756,7 +3828,7 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg)
{
if (rs->req_has_auth)
{
- DPRINTF(E_LOG, L_RAOP, "Bad password for device %s\n", rs->devname);
+ DPRINTF(E_LOG, L_RAOP, "Bad password for device '%s'\n", rs->devname);
rs->state = RAOP_STATE_PASSWORD;
goto cleanup;
@@ -3838,12 +3910,24 @@ raop_cb_probe_options(struct evrtsp_request *req, void *arg)
rs->reqs_in_flight--;
- if (!req)
- goto cleanup;
+ if (!req || !req->response_code)
+ {
+ DPRINTF(E_LOG, L_RAOP, "No response from '%s' (%s) to OPTIONS request\n", rs->devname, rs->address);
+
+ if (rs->device->v4_address && (rs->sa.ss.ss_family == AF_INET6))
+ {
+ DPRINTF(E_LOG, L_RAOP, "Falling back to ipv4, the ipv6 address is not responding\n");
+
+ free(rs->device->v6_address);
+ rs->device->v6_address = NULL;
+ }
+
+ goto cleanup;
+ }
if ((req->response_code != RTSP_OK) && (req->response_code != RTSP_UNAUTHORIZED))
{
- DPRINTF(E_LOG, L_RAOP, "OPTIONS request failed in device probe: %d %s\n", req->response_code, req->response_code_line);
+ DPRINTF(E_LOG, L_RAOP, "OPTIONS request failed probing '%s': %d %s\n", rs->devname, req->response_code, req->response_code_line);
goto cleanup;
}
@@ -3856,7 +3940,7 @@ raop_cb_probe_options(struct evrtsp_request *req, void *arg)
{
if (rs->req_has_auth)
{
- DPRINTF(E_LOG, L_RAOP, "Bad password for device %s\n", rs->devname);
+ DPRINTF(E_LOG, L_RAOP, "Bad password for device '%s'\n", rs->devname);
rs->state = RAOP_STATE_PASSWORD;
goto cleanup;
@@ -3900,6 +3984,8 @@ raop_cb_probe_options(struct evrtsp_request *req, void *arg)
["sf=0x4" "am=AppleTV2,1" "vs=105.5" "md=0,1,2" "tp=TCP,UDP" "vn=65537" "pw=false" "ss=16" "sr=44100" "da=true" "sv=false" "et=0,3" "cn=0,1" "ch=2" "txtvers=1"]
* Apple TV 3:
["vv=2" "vs=200.54" "vn=65537" "tp=UDP" "sf=0x44" "pk=8...f" "am=AppleTV3,1" "md=0,1,2" "ft=0x5A7FFFF7,0xE" "et=0,3,5" "da=true" "cn=0,1,2,3"]
+ * Apple TV 4:
+ ["vv=2" "vs=301.44.3" "vn=65537" "tp=UDP" "pk=9...f" "am=AppleTV5,3" "md=0,1,2" "sf=0x44" "ft=0x5A7FFFF7,0x4DE" "et=0,3,5" "da=true" "cn=0,1,2,3"]
* Sony STR-DN1040:
["fv=s9327.1090.0" "am=STR-DN1040" "vs=141.9" "vn=65537" "tp=UDP" "ss=16" "sr=44100" "sv=false" "pw=false" "md=0,2" "ft=0x44F0A00" "et=0,4" "da=true" "cn=0,1" "ch=2" "txtvers=1"]
* AirFoil:
@@ -4050,6 +4136,8 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
re->devtype = RAOP_DEV_APEX2_80211N; // Second generation
else if (strncmp(p, "AirPort", strlen("AirPort")) == 0)
re->devtype = RAOP_DEV_APEX3_80211N; // Third generation and newer
+ else if (strncmp(p, "AppleTV5,3", strlen("AppleTV5,3")) == 0)
+ re->devtype = RAOP_DEV_APPLETV4; // Stream to ATV with tvOS 10 needs to be kept alive
else if (strncmp(p, "AppleTV", strlen("AppleTV")) == 0)
re->devtype = RAOP_DEV_APPLETV;
else if (*p == '\0')
@@ -4069,9 +4157,6 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
else
re->wants_metadata = 0;
- DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device %s: password: %u, encrypt: %u, metadata: %u, type %s\n",
- name, rd->has_password, re->encrypt, re->wants_metadata, raop_devtype[re->devtype]);
-
rd->advertised = 1;
switch (family)
@@ -4079,12 +4164,20 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
case AF_INET:
rd->v4_address = strdup(address);
rd->v4_port = port;
+ DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device %s: password: %u, encrypt: %u, metadata: %u, type %s, address %s:%d\n",
+ name, rd->has_password, re->encrypt, re->wants_metadata, raop_devtype[re->devtype], address, port);
break;
case AF_INET6:
rd->v6_address = strdup(address);
rd->v6_port = port;
+ DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device %s: password: %u, encrypt: %u, metadata: %u, type %s, address [%s]:%d\n",
+ name, rd->has_password, re->encrypt, re->wants_metadata, raop_devtype[re->devtype], address, port);
break;
+
+ default:
+ DPRINTF(E_LOG, L_RAOP, "Error: AirPlay device %s has neither ipv4 og ipv6 address\n", name);
+ goto free_rd;
}
ret = player_device_add(rd);
@@ -4206,6 +4299,7 @@ raop_playback_start(uint64_t next_pkt, struct timespec *ts)
struct raop_session *rs;
event_del(flush_timer);
+ evtimer_add(keep_alive_timer, &keep_alive_tv);
sync_counter = 0;
@@ -4225,6 +4319,8 @@ raop_playback_stop(void)
struct raop_session *rs;
int ret;
+ evtimer_del(keep_alive_timer);
+
for (rs = sessions; rs; rs = rs->next)
{
ret = raop_send_req_teardown(rs, raop_cb_shutdown_teardown);
@@ -4249,7 +4345,7 @@ raop_init(void)
char *libname;
gpg_error_t gc_err;
int v6enabled;
- int mdns_flags;
+ int family;
int ret;
timing_4svc.fd = -1;
@@ -4331,9 +4427,10 @@ raop_init(void)
*ptr = '\0';
flush_timer = evtimer_new(evbase_player, raop_flush_timer_cb, NULL);
- if (!flush_timer)
+ keep_alive_timer = evtimer_new(evbase_player, raop_keep_alive_timer_cb, NULL);
+ if (!flush_timer || !keep_alive_timer)
{
- DPRINTF(E_LOG, L_RAOP, "Out of memory for flush timer\n");
+ DPRINTF(E_LOG, L_RAOP, "Out of memory for flush timer or keep alive timer\n");
goto out_free_b64_iv;
}
@@ -4345,13 +4442,13 @@ raop_init(void)
{
DPRINTF(E_LOG, L_RAOP, "AirPlay time synchronization failed to start\n");
- goto out_free_flush_timer;
+ goto out_free_timers;
}
ret = raop_v2_control_start(v6enabled);
if (ret < 0)
{
- DPRINTF(E_LOG, L_RAOP, "AirPlay playback synchronization failed to start\n");
+ DPRINTF(E_LOG, L_RAOP, "AirPlay playback control failed to start\n");
goto out_stop_timing;
}
@@ -4360,11 +4457,11 @@ raop_init(void)
v6enabled = !((timing_6svc.fd < 0) || (control_6svc.fd < 0));
if (v6enabled)
- mdns_flags = MDNS_WANT_V4 | MDNS_WANT_V6 | MDNS_WANT_V6LL;
+ family = AF_UNSPEC;
else
- mdns_flags = MDNS_WANT_V4;
+ family = AF_INET;
- ret = mdns_browse("_raop._tcp", mdns_flags, raop_device_cb);
+ ret = mdns_browse("_raop._tcp", family, raop_device_cb);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not add mDNS browser for AirPlay devices\n");
@@ -4379,8 +4476,9 @@ raop_init(void)
raop_v2_control_stop();
out_stop_timing:
raop_v2_timing_stop();
- out_free_flush_timer:
+ out_free_timers:
event_free(flush_timer);
+ event_free(keep_alive_timer);
out_free_b64_iv:
free(raop_aes_iv_b64);
out_free_b64_key:
@@ -4407,6 +4505,7 @@ raop_deinit(void)
raop_v2_timing_stop();
event_free(flush_timer);
+ event_free(keep_alive_timer);
gcry_cipher_close(raop_aes_ctx);
diff --git a/src/player.c b/src/player.c
index fd1dbeb..d6423b9 100644
--- a/src/player.c
+++ b/src/player.c
@@ -53,6 +53,7 @@
#include "player.h"
#include "worker.h"
#include "listener.h"
+#include "commands.h"
/* Audio outputs */
#include "outputs.h"
@@ -124,20 +125,11 @@ struct player_source
struct player_source *play_next;
};
-enum player_sync_source
- {
- PLAYER_SYNC_CLOCK,
- PLAYER_SYNC_LAUDIO,
- };
-
struct volume_param {
int volume;
uint64_t spk_id;
};
-struct player_command;
-typedef int (*cmd_func)(struct player_command *cmd);
-
struct spk_enum
{
spk_enum_cb cb;
@@ -198,52 +190,42 @@ struct player_metadata
struct output_metadata *omd;
};
-struct player_command
-{
- pthread_mutex_t lck;
- pthread_cond_t cond;
-
- cmd_func func;
- cmd_func func_bh;
-
- int nonblock;
-
- union {
- struct volume_param vol_param;
- void *noarg;
- struct spk_enum *spk_enum;
- struct output_device *device;
- struct player_status *status;
- struct player_source *ps;
- struct player_metadata *pmd;
- uint32_t *id_ptr;
- uint64_t *device_ids;
- enum repeat_mode mode;
- uint32_t id;
- int intval;
- struct icy_artwork icy;
- struct playback_start_param playback_start_param;
- struct playerqueue_get_param queue_get_param;
- struct playerqueue_add_param queue_add_param;
- struct playerqueue_move_param queue_move_param;
- struct playerqueue_remove_param queue_remove_param;
- } arg;
-
- int ret;
+struct speaker_set_param
+{
+ uint64_t *device_ids;
+ int intval;
+};
- int output_requests_pending;
+union player_arg
+{
+ struct volume_param vol_param;
+ void *noarg;
+ struct spk_enum *spk_enum;
+ struct output_device *device;
+ struct player_status *status;
+ struct player_source *ps;
+ struct player_metadata *pmd;
+ uint32_t *id_ptr;
+ struct speaker_set_param speaker_set_param;
+ enum repeat_mode mode;
+ uint32_t id;
+ int intval;
+ struct icy_artwork icy;
+ struct playback_start_param playback_start_param;
+ struct playerqueue_get_param queue_get_param;
+ struct playerqueue_add_param queue_add_param;
+ struct playerqueue_move_param queue_move_param;
+ struct playerqueue_remove_param queue_remove_param;
};
struct event_base *evbase_player;
-static int exit_pipe[2];
-static int cmd_pipe[2];
static int player_exit;
-static struct event *exitev;
-static struct event *cmdev;
static pthread_t tid_player;
+static struct commands_base *cmdbase;
/* Config values */
+static int speaker_autoselect;
static int clear_queue_on_stop_disabled;
/* Player status */
@@ -270,9 +252,6 @@ static struct timespec packet_time = { 0, AIRTUNES_V2_STREAM_PERIOD };
// Will be positive if we need to skip some source reads (see below)
static int ticks_skip;
-/* Sync source */
-static enum player_sync_source pb_sync_source;
-
/* Sync values */
static struct timespec pb_pos_stamp;
static uint64_t pb_pos;
@@ -286,9 +265,6 @@ static struct output_device *dev_list;
/* Output status */
static int output_sessions;
-/* Commands */
-static struct player_command *cur_cmd;
-
/* Last commanded volume */
static int master_volume;
@@ -308,35 +284,6 @@ static struct queue *queue;
/* Play history */
static struct player_history *history;
-/* Command helpers */
-static void
-command_async_end(struct player_command *cmd)
-{
- cur_cmd = NULL;
-
- pthread_cond_signal(&cmd->cond);
- pthread_mutex_unlock(&cmd->lck);
-
- /* Process commands again */
- event_add(cmdev, NULL);
-}
-
-static void
-command_init(struct player_command *cmd)
-{
- memset(cmd, 0, sizeof(struct player_command));
-
- pthread_mutex_init(&cmd->lck, NULL);
- pthread_cond_init(&cmd->cond, NULL);
-}
-
-static void
-command_deinit(struct player_command *cmd)
-{
- pthread_cond_destroy(&cmd->cond);
- pthread_mutex_destroy(&cmd->lck);
-}
-
static void
status_update(enum play_status status)
@@ -434,8 +381,8 @@ speaker_deselect_output(struct output_device *device)
}
-static int
-player_get_current_pos_clock(uint64_t *pos, struct timespec *ts, int commit)
+int
+player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit)
{
uint64_t delta;
int ret;
@@ -477,21 +424,6 @@ player_get_current_pos_clock(uint64_t *pos, struct timespec *ts, int commit)
return 0;
}
-int
-player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit)
-{
- switch (pb_sync_source)
- {
- case PLAYER_SYNC_CLOCK:
- return player_get_current_pos_clock(pos, ts, commit);
-
- default:
- DPRINTF(E_LOG, L_PLAYER, "Bug! player_get_current_pos called with unknown source\n");
- }
-
- return -1;
-}
-
static int
pb_timer_start(void)
{
@@ -543,8 +475,8 @@ pb_timer_stop(void)
static void
playback_abort(void);
-static int
-playerqueue_clear(struct player_command *cmd);
+static enum command_state
+playerqueue_clear(void *arg, int *retval);
static void
player_metadata_send(struct player_metadata *pmd);
@@ -1520,14 +1452,6 @@ player_playback_cb(int fd, short what, void *arg)
overrun = ret;
#endif /* __linux__ */
-/*debug_counter++;
-if (debug_counter % 2000 == 0)
-{
- DPRINTF(E_LOG, L_PLAYER, "Sleep a bit!!\n");
- usleep(5 * AIRTUNES_V2_STREAM_PERIOD / 1000);
- DPRINTF(E_LOG, L_PLAYER, "Wake again\n");
-}*/
-
// The reason we get behind the playback timer may be that we are playing a
// network stream OR that the source is slow to open OR some interruption.
// For streams, we might be consuming faster than the stream delivers, so
@@ -1546,11 +1470,14 @@ if (debug_counter % 2000 == 0)
skip_first = 0;
if (overrun > PLAYER_TICKS_MAX_OVERRUN)
{
- DPRINTF(E_WARN, L_PLAYER, "Behind the playback timer with %" PRIu64 " ticks, initiating catch up\n", overrun);
+ DPRINTF(E_WARN, L_PLAYER, "Behind the playback timer with %" PRIu64 " ticks\n", overrun);
- if (cur_streaming->data_kind == DATA_KIND_HTTP || cur_streaming->data_kind == DATA_KIND_PIPE)
+ if (cur_streaming && (cur_streaming->data_kind == DATA_KIND_HTTP || cur_streaming->data_kind == DATA_KIND_PIPE))
{
ticks_skip = 3 * overrun;
+
+ DPRINTF(E_WARN, L_PLAYER, "Will skip reading for a total of %d ticks to catch up\n", ticks_skip);
+
// We always skip after a timer overrun, since another read will
// probably just give another time overrun
skip_first = 1;
@@ -1672,15 +1599,17 @@ device_check(struct output_device *check)
return (device) ? 0 : -1;
}
-static int
-device_add(struct player_command *cmd)
+static enum command_state
+device_add(void *arg, int *retval)
{
+ union player_arg *cmdarg;
struct output_device *add;
struct output_device *device;
int selected;
int ret;
- add = cmd->arg.device;
+ cmdarg = arg;
+ add = cmdarg->device;
for (device = dev_list; device; device = device->next)
{
@@ -1748,16 +1677,19 @@ device_add(struct player_command *cmd)
device_list_sort();
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-device_remove_family(struct player_command *cmd)
+static enum command_state
+device_remove_family(void *arg, int *retval)
{
+ union player_arg *cmdarg;
struct output_device *remove;
struct output_device *device;
- remove = cmd->arg.device;
+ cmdarg = arg;
+ remove = cmdarg->device;
for (device = dev_list; device; device = device->next)
{
@@ -1770,7 +1702,8 @@ device_remove_family(struct player_command *cmd)
DPRINTF(E_WARN, L_PLAYER, "The %s device '%s' stopped advertising, but not in our list\n", remove->type_name, remove->name);
outputs_device_free(remove);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
/* v{4,6}_port non-zero indicates the address family stopped advertising */
@@ -1798,15 +1731,18 @@ device_remove_family(struct player_command *cmd)
outputs_device_free(remove);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-metadata_send(struct player_command *cmd)
+static enum command_state
+metadata_send(void *arg, int *retval)
{
+ union player_arg *cmdarg;
struct player_metadata *pmd;
- pmd = cmd->arg.pmd;
+ cmdarg = arg;
+ pmd = cmdarg->pmd;
/* Do the setting of rtptime which was deferred in metadata_trigger because we
* wanted to wait until we had the actual last_rtptime
@@ -1816,7 +1752,8 @@ metadata_send(struct player_command *cmd)
outputs_metadata_send(pmd->omd, pmd->rtptime, pmd->offset, pmd->startup);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
/* Output device callbacks executed in the player thread */
@@ -1873,43 +1810,33 @@ device_command_cb(struct output_device *device, struct output_session *session,
{
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_command_cb\n", outputs_name(device->type));
- cur_cmd->output_requests_pending--;
-
outputs_status_cb(session, device_streaming_cb);
if (status == OUTPUT_STATE_FAILED)
device_streaming_cb(device, session, status);
- if (cur_cmd->output_requests_pending == 0)
- {
- if (cur_cmd->func_bh)
- cur_cmd->ret = cur_cmd->func_bh(cur_cmd);
- else
- cur_cmd->ret = 0;
-
- command_async_end(cur_cmd);
- }
+ commands_exec_end(cmdbase, 0);
}
static void
device_shutdown_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
{
+ int retval;
int ret;
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_shutdown_cb\n", outputs_name(device->type));
- cur_cmd->output_requests_pending--;
-
if (output_sessions)
output_sessions--;
+ retval = commands_exec_returnvalue(cmdbase);
ret = device_check(device);
if (ret < 0)
{
DPRINTF(E_WARN, L_PLAYER, "Output device disappeared before shutdown completion!\n");
- if (cur_cmd->ret != -2)
- cur_cmd->ret = -1;
+ if (retval != -2)
+ retval = -1;
goto out;
}
@@ -1919,14 +1846,11 @@ device_shutdown_cb(struct output_device *device, struct output_session *session,
device_remove(device);
out:
- if (cur_cmd->output_requests_pending == 0)
- {
- /* cur_cmd->ret already set
- * - to 0 (or -2 if password issue) in speaker_set()
- * - to -1 above on error
- */
- command_async_end(cur_cmd);
- }
+ /* cur_cmd->ret already set
+ * - to 0 (or -2 if password issue) in speaker_set()
+ * - to -1 above on error
+ */
+ commands_exec_end(cmdbase, retval);
}
static void
@@ -1945,12 +1869,12 @@ static void
device_activate_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
{
struct timespec ts;
+ int retval;
int ret;
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_activate_cb\n", outputs_name(device->type));
- cur_cmd->output_requests_pending--;
-
+ retval = commands_exec_returnvalue(cmdbase);
ret = device_check(device);
if (ret < 0)
{
@@ -1959,15 +1883,15 @@ device_activate_cb(struct output_device *device, struct output_session *session,
outputs_status_cb(session, device_lost_cb);
outputs_device_stop(session);
- if (cur_cmd->ret != -2)
- cur_cmd->ret = -1;
+ if (retval != -2)
+ retval = -1;
goto out;
}
if (status == OUTPUT_STATE_PASSWORD)
{
status = OUTPUT_STATE_FAILED;
- cur_cmd->ret = -2;
+ retval = -2;
}
if (status == OUTPUT_STATE_FAILED)
@@ -1977,8 +1901,8 @@ device_activate_cb(struct output_device *device, struct output_session *session,
if (!device->advertised)
device_remove(device);
- if (cur_cmd->ret != -2)
- cur_cmd->ret = -1;
+ if (retval != -2)
+ retval = -1;
goto out;
}
@@ -2004,40 +1928,37 @@ device_activate_cb(struct output_device *device, struct output_session *session,
outputs_status_cb(session, device_streaming_cb);
out:
- if (cur_cmd->output_requests_pending == 0)
- {
- /* cur_cmd->ret already set
- * - to 0 in speaker_set() (default)
- * - to -2 above if password issue
- * - to -1 above on error
- */
- command_async_end(cur_cmd);
- }
+ /* cur_cmd->ret already set
+ * - to 0 in speaker_set() (default)
+ * - to -2 above if password issue
+ * - to -1 above on error
+ */
+ commands_exec_end(cmdbase, retval);
}
static void
device_probe_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
{
+ int retval;
int ret;
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_probe_cb\n", outputs_name(device->type));
- cur_cmd->output_requests_pending--;
-
+ retval = commands_exec_returnvalue(cmdbase);
ret = device_check(device);
if (ret < 0)
{
DPRINTF(E_WARN, L_PLAYER, "Output device disappeared during probe!\n");
- if (cur_cmd->ret != -2)
- cur_cmd->ret = -1;
+ if (retval != -2)
+ retval = -1;
goto out;
}
if (status == OUTPUT_STATE_PASSWORD)
{
status = OUTPUT_STATE_FAILED;
- cur_cmd->ret = -2;
+ retval = -2;
}
if (status == OUTPUT_STATE_FAILED)
@@ -2047,21 +1968,18 @@ device_probe_cb(struct output_device *device, struct output_session *session, en
if (!device->advertised)
device_remove(device);
- if (cur_cmd->ret != -2)
- cur_cmd->ret = -1;
+ if (retval != -2)
+ retval = -1;
goto out;
}
out:
- if (cur_cmd->output_requests_pending == 0)
- {
- /* cur_cmd->ret already set
- * - to 0 in speaker_set() (default)
- * - to -2 above if password issue
- * - to -1 above on error
- */
- command_async_end(cur_cmd);
- }
+ /* cur_cmd->ret already set
+ * - to 0 in speaker_set() (default)
+ * - to -2 above if password issue
+ * - to -1 above on error
+ */
+ commands_exec_end(cmdbase, retval);
}
static void
@@ -2071,8 +1989,6 @@ device_restart_cb(struct output_device *device, struct output_session *session,
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_restart_cb\n", outputs_name(device->type));
- cur_cmd->output_requests_pending--;
-
ret = device_check(device);
if (ret < 0)
{
@@ -2100,18 +2016,15 @@ device_restart_cb(struct output_device *device, struct output_session *session,
outputs_status_cb(session, device_streaming_cb);
out:
- if (cur_cmd->output_requests_pending == 0)
- {
- cur_cmd->ret = cur_cmd->func_bh(cur_cmd);
-
- command_async_end(cur_cmd);
- }
+ commands_exec_end(cmdbase, 0);
}
/* Internal abort routine */
static void
playback_abort(void)
{
+ int ret;
+
outputs_playback_stop();
pb_timer_stop();
@@ -2121,7 +2034,7 @@ playback_abort(void)
evbuffer_drain(audio_buf, evbuffer_get_length(audio_buf));
if (!clear_queue_on_stop_disabled)
- playerqueue_clear(NULL);
+ playerqueue_clear(NULL, &ret);
status_update(PLAY_STOPPED);
@@ -2129,9 +2042,10 @@ playback_abort(void)
}
/* Actual commands, executed in the player thread */
-static int
-get_status(struct player_command *cmd)
+static enum command_state
+get_status(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
struct timespec ts;
struct player_source *ps;
struct player_status *status;
@@ -2139,7 +2053,7 @@ get_status(struct player_command *cmd)
uint64_t pos;
int ret;
- status = cmd->arg.status;
+ status = cmdarg->status;
memset(status, 0, sizeof(struct player_status));
@@ -2231,52 +2145,66 @@ get_status(struct player_command *cmd)
break;
}
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-now_playing(struct player_command *cmd)
+static enum command_state
+now_playing(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
uint32_t *id;
struct player_source *ps_playing;
- id = cmd->arg.id_ptr;
+ id = cmdarg->id_ptr;
ps_playing = source_now_playing();
if (ps_playing)
*id = ps_playing->id;
else
- return -1;
+ {
+ *retval = -1;
+ return COMMAND_END;
+ }
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-artwork_url_get(struct player_command *cmd)
+static enum command_state
+artwork_url_get(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
struct player_source *ps;
- cmd->arg.icy.artwork_url = NULL;
+ cmdarg->icy.artwork_url = NULL;
if (cur_playing)
ps = cur_playing;
else if (cur_streaming)
ps = cur_streaming;
else
- return -1;
+ {
+ *retval = -1;
+ return COMMAND_END;
+ }
/* Check that we are playing a viable stream, and that it has the requested id */
- if (!ps->xcode || ps->data_kind != DATA_KIND_HTTP || ps->id != cmd->arg.icy.id)
- return -1;
+ if (!ps->xcode || ps->data_kind != DATA_KIND_HTTP || ps->id != cmdarg->icy.id)
+ {
+ *retval = -1;
+ return COMMAND_END;
+ }
- cmd->arg.icy.artwork_url = transcode_metadata_artwork_url(ps->xcode);
+ cmdarg->icy.artwork_url = transcode_metadata_artwork_url(ps->xcode);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playback_stop(struct player_command *cmd)
+static enum command_state
+playback_stop(void *arg, int *retval)
{
struct player_source *ps_playing;
@@ -2284,7 +2212,7 @@ playback_stop(struct player_command *cmd)
* full stop just yet; this saves time when restarting, which is nicer
* for the user.
*/
- cmd->output_requests_pending = outputs_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);
+ *retval = outputs_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);
pb_timer_stop();
@@ -2303,15 +2231,15 @@ playback_stop(struct player_command *cmd)
metadata_purge();
/* We're async if we need to flush devices */
- if (cmd->output_requests_pending > 0)
- return 1; /* async */
+ if (*retval > 0)
+ return COMMAND_PENDING; /* async */
- return 0;
+ return COMMAND_END;
}
/* Playback startup bottom half */
-static int
-playback_start_bh(struct player_command *cmd)
+static enum command_state
+playback_start_bh(void *arg, int *retval)
{
int ret;
@@ -2351,16 +2279,18 @@ playback_start_bh(struct player_command *cmd)
status_update(PLAY_PLAYING);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
out_fail:
playback_abort();
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
-static int
-playback_start_item(struct player_command *cmd, struct queue_item *qii)
+static enum command_state
+playback_start_item(union player_arg *cmdarg, int *retval, struct queue_item *qii)
{
uint32_t *dbmfi_id;
struct output_device *device;
@@ -2368,7 +2298,7 @@ playback_start_item(struct player_command *cmd, struct queue_item *qii)
struct queue_item *item;
int ret;
- dbmfi_id = cmd->arg.playback_start_param.id_ptr;
+ dbmfi_id = cmdarg->playback_start_param.id_ptr;
ps_playing = source_now_playing();
@@ -2386,7 +2316,8 @@ playback_start_item(struct player_command *cmd, struct queue_item *qii)
status_update(player_state);
- return 0;
+ *retval = 1; // Value greater 0 will prevent execution of the bottom half function
+ return COMMAND_END;
}
// Update global playback position
@@ -2411,7 +2342,8 @@ playback_start_item(struct player_command *cmd, struct queue_item *qii)
if (ret < 0)
{
playback_abort();
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
}
@@ -2419,7 +2351,8 @@ playback_start_item(struct player_command *cmd, struct queue_item *qii)
if (ret < 0)
{
playback_abort();
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
@@ -2429,7 +2362,7 @@ playback_start_item(struct player_command *cmd, struct queue_item *qii)
metadata_trigger(1);
/* Start sessions on selected devices */
- cmd->output_requests_pending = 0;
+ *retval = 0;
for (device = dev_list; device; device = device->next)
{
@@ -2443,12 +2376,12 @@ playback_start_item(struct player_command *cmd, struct queue_item *qii)
}
DPRINTF(E_INFO, L_PLAYER, "Using selected %s device '%s'\n", device->type_name, device->name);
- cmd->output_requests_pending++;
+ (*retval)++;
}
}
- /* Try to autoselect a non-selected device if the above failed */
- if ((cmd->output_requests_pending == 0) && (output_sessions == 0))
+ /* If autoselecting is enabled, try to autoselect a non-selected device if the above failed */
+ if (speaker_autoselect && (*retval == 0) && (output_sessions == 0))
for (device = dev_list; device; device = device->next)
{
if ((outputs_priority(device) == 0) || device->session)
@@ -2464,67 +2397,72 @@ playback_start_item(struct player_command *cmd, struct queue_item *qii)
}
DPRINTF(E_INFO, L_PLAYER, "Autoselecting %s device '%s'\n", device->type_name, device->name);
- cmd->output_requests_pending++;
+ (*retval)++;
break;
}
/* No luck finding valid output */
- if ((cmd->output_requests_pending == 0) && (output_sessions == 0))
+ if ((*retval == 0) && (output_sessions == 0))
{
DPRINTF(E_LOG, L_PLAYER, "Could not start playback: no output selected or couldn't start any output\n");
playback_abort();
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
/* We're async if we need to start devices */
- if (cmd->output_requests_pending > 0)
- return 1; /* async */
+ if (*retval > 0)
+ return COMMAND_PENDING; /* async */
/* Otherwise, just run the bottom half */
- return playback_start_bh(cmd);
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playback_start(struct player_command *cmd)
+static enum command_state
+playback_start(void *arg, int *retval)
{
- return playback_start_item(cmd, NULL);
+ return playback_start_item(arg, retval, NULL);
}
-static int
-playback_start_byitemid(struct player_command *cmd)
+static enum command_state
+playback_start_byitemid(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
int item_id;
struct queue_item *qii;
- item_id = cmd->arg.playback_start_param.id;
+ item_id = cmdarg->playback_start_param.id;
qii = queue_get_byitemid(queue, item_id);
- return playback_start_item(cmd, qii);
+ return playback_start_item(cmdarg, retval, qii);
}
-static int
-playback_start_byindex(struct player_command *cmd)
+static enum command_state
+playback_start_byindex(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
int pos;
struct queue_item *qii;
- pos = cmd->arg.playback_start_param.pos;
+ pos = cmdarg->playback_start_param.pos;
qii = queue_get_byindex(queue, pos, 0);
- return playback_start_item(cmd, qii);
+ return playback_start_item(cmdarg, retval, qii);
}
-static int
-playback_start_bypos(struct player_command *cmd)
+static enum command_state
+playback_start_bypos(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
int offset;
struct player_source *ps_playing;
struct queue_item *qii;
- offset = cmd->arg.playback_start_param.pos;
+ offset = cmdarg->playback_start_param.pos;
ps_playing = source_now_playing();
@@ -2537,11 +2475,11 @@ playback_start_bypos(struct player_command *cmd)
qii = queue_get_byindex(queue, offset, shuffle);
}
- return playback_start_item(cmd, qii);
+ return playback_start_item(cmdarg, retval, qii);
}
-static int
-playback_prev_bh(struct player_command *cmd)
+static enum command_state
+playback_prev_bh(void *arg, int *retval)
{
int ret;
int pos_sec;
@@ -2554,7 +2492,8 @@ playback_prev_bh(struct player_command *cmd)
if (!cur_streaming)
{
DPRINTF(E_LOG, L_PLAYER, "Could not get current stream source\n");
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
/* Only add to history if playback started. */
@@ -2576,7 +2515,8 @@ playback_prev_bh(struct player_command *cmd)
if (!item)
{
playback_abort();
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
source_stop();
@@ -2586,7 +2526,8 @@ playback_prev_bh(struct player_command *cmd)
{
playback_abort();
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
}
else
@@ -2596,24 +2537,29 @@ playback_prev_bh(struct player_command *cmd)
{
playback_abort();
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
}
if (player_state == PLAY_STOPPED)
- return -1;
+ {
+ *retval = -1;
+ return COMMAND_END;
+ }
/* Silent status change - playback_start() sends the real status update */
player_state = PLAY_PAUSED;
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
/*
* The bottom half of the next command
*/
-static int
-playback_next_bh(struct player_command *cmd)
+static enum command_state
+playback_next_bh(void *arg, int *retval)
{
int ret;
struct queue_item *item;
@@ -2625,7 +2571,8 @@ playback_next_bh(struct player_command *cmd)
if (!cur_streaming)
{
DPRINTF(E_LOG, L_PLAYER, "Could not get current stream source\n");
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
/* Only add to history if playback started. */
@@ -2636,7 +2583,8 @@ playback_next_bh(struct player_command *cmd)
if (!item)
{
playback_abort();
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
source_stop();
@@ -2645,25 +2593,31 @@ playback_next_bh(struct player_command *cmd)
if (ret < 0)
{
playback_abort();
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
if (player_state == PLAY_STOPPED)
- return -1;
+ {
+ *retval = -1;
+ return COMMAND_END;
+ }
/* Silent status change - playback_start() sends the real status update */
player_state = PLAY_PAUSED;
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playback_seek_bh(struct player_command *cmd)
+static enum command_state
+playback_seek_bh(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
int ms;
int ret;
- ms = cmd->arg.intval;
+ ms = cmdarg->intval;
ret = source_seek(ms);
@@ -2671,17 +2625,19 @@ playback_seek_bh(struct player_command *cmd)
{
playback_abort();
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
/* Silent status change - playback_start() sends the real status update */
player_state = PLAY_PAUSED;
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playback_pause_bh(struct player_command *cmd)
+static enum command_state
+playback_pause_bh(void *arg, int *retval)
{
int ret;
@@ -2691,7 +2647,8 @@ playback_pause_bh(struct player_command *cmd)
DPRINTF(E_DBG, L_PLAYER, "Source is not pausable, abort playback\n");
playback_abort();
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
status_update(PLAY_PAUSED);
@@ -2701,11 +2658,12 @@ playback_pause_bh(struct player_command *cmd)
db_file_save_seek(cur_streaming->id, ret);
}
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playback_pause(struct player_command *cmd)
+static enum command_state
+playback_pause(void *arg, int *retval)
{
uint64_t pos;
@@ -2715,14 +2673,18 @@ playback_pause(struct player_command *cmd)
DPRINTF(E_LOG, L_PLAYER, "Could not retrieve current position for pause\n");
playback_abort();
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
/* Make sure playback is still running after source_check() */
if (player_state == PLAY_STOPPED)
- return -1;
+ {
+ *retval = -1;
+ return COMMAND_END;
+ }
- cmd->output_requests_pending = outputs_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);
+ *retval = outputs_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);
pb_timer_stop();
@@ -2733,21 +2695,22 @@ playback_pause(struct player_command *cmd)
metadata_purge();
/* We're async if we need to flush devices */
- if (cmd->output_requests_pending > 0)
- return 1; /* async */
+ if (*retval > 0)
+ return COMMAND_PENDING; /* async */
/* Otherwise, just run the bottom half */
- return cmd->func_bh(cmd);
+ return COMMAND_END;
}
-static int
-speaker_enumerate(struct player_command *cmd)
+static enum command_state
+speaker_enumerate(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
struct output_device *device;
struct spk_enum *spk_enum;
struct spk_flags flags;
- spk_enum = cmd->arg.spk_enum;
+ spk_enum = cmdarg->spk_enum;
#ifdef DEBUG_RELVOL
DPRINTF(E_DBG, L_PLAYER, "*** master: %d\n", master_volume);
@@ -2761,7 +2724,7 @@ speaker_enumerate(struct player_command *cmd)
flags.has_password = device->has_password;
flags.has_video = device->has_video;
- spk_enum->cb(device->id, device->name, device->relvol, flags, spk_enum->arg);
+ spk_enum->cb(device->id, device->name, device->relvol, device->volume, flags, spk_enum->arg);
#ifdef DEBUG_RELVOL
DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol);
@@ -2769,7 +2732,8 @@ speaker_enumerate(struct player_command *cmd)
}
}
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
static int
@@ -2820,16 +2784,18 @@ speaker_deactivate(struct output_device *device)
return 0;
}
-static int
-speaker_set(struct player_command *cmd)
+static enum command_state
+speaker_set(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
struct output_device *device;
uint64_t *ids;
int nspk;
int i;
int ret;
- ids = cmd->arg.device_ids;
+ *retval = 0;
+ ids = cmdarg->speaker_set_param.device_ids;
if (ids)
nspk = ids[0];
@@ -2838,8 +2804,7 @@ speaker_set(struct player_command *cmd)
DPRINTF(E_DBG, L_PLAYER, "Speaker set: %d speakers\n", nspk);
- cmd->output_requests_pending = 0;
- cmd->ret = 0;
+ *retval = 0;
for (device = dev_list; device; device = device->next)
{
@@ -2857,7 +2822,7 @@ speaker_set(struct player_command *cmd)
{
DPRINTF(E_INFO, L_PLAYER, "The %s device '%s' is password-protected, but we don't have it\n", device->type_name, device->name);
- cmd->ret = -2;
+ cmdarg->speaker_set_param.intval = -2;
continue;
}
@@ -2875,11 +2840,11 @@ speaker_set(struct player_command *cmd)
speaker_deselect_output(device);
- if (cmd->ret != -2)
- cmd->ret = -1;
+ if (cmdarg->speaker_set_param.intval != -2)
+ cmdarg->speaker_set_param.intval = -1;
}
else
- cmd->output_requests_pending++;
+ (*retval)++;
}
}
else
@@ -2896,38 +2861,39 @@ speaker_set(struct player_command *cmd)
{
DPRINTF(E_LOG, L_PLAYER, "Could not deactivate %s device '%s'\n", device->type_name, device->name);
- if (cmd->ret != -2)
- cmd->ret = -1;
+ if (cmdarg->speaker_set_param.intval != -2)
+ cmdarg->speaker_set_param.intval = -1;
}
else
- cmd->output_requests_pending++;
+ (*retval)++;
}
}
}
listener_notify(LISTENER_SPEAKER);
- if (cmd->output_requests_pending > 0)
- return 1; /* async */
+ if (*retval > 0)
+ return COMMAND_PENDING; /* async */
- return cmd->ret;
+ *retval = cmdarg->speaker_set_param.intval;
+ return COMMAND_END;
}
-static int
-volume_set(struct player_command *cmd)
+static enum command_state
+volume_set(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
struct output_device *device;
int volume;
- volume = cmd->arg.intval;
+ *retval = 0;
+ volume = cmdarg->intval;
if (master_volume == volume)
- return 0;
+ return COMMAND_END;
master_volume = volume;
- cmd->output_requests_pending = 0;
-
for (device = dev_list; device; device = device->next)
{
if (!device->selected)
@@ -2940,26 +2906,28 @@ volume_set(struct player_command *cmd)
#endif
if (device->session)
- cmd->output_requests_pending += outputs_device_volume_set(device, device_command_cb);
+ *retval += outputs_device_volume_set(device, device_command_cb);
}
listener_notify(LISTENER_VOLUME);
- if (cmd->output_requests_pending > 0)
- return 1; /* async */
+ if (*retval > 0)
+ return COMMAND_PENDING; /* async */
- return 0;
+ return COMMAND_END;
}
-static int
-volume_setrel_speaker(struct player_command *cmd)
+static enum command_state
+volume_setrel_speaker(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
struct output_device *device;
uint64_t id;
int relvol;
- id = cmd->arg.vol_param.spk_id;
- relvol = cmd->arg.vol_param.volume;
+ *retval = 0;
+ id = cmdarg->vol_param.spk_id;
+ relvol = cmdarg->vol_param.volume;
for (device = dev_list; device; device = device->next)
{
@@ -2967,7 +2935,10 @@ volume_setrel_speaker(struct player_command *cmd)
continue;
if (!device->selected)
- return 0;
+ {
+ *retval = 0;
+ return COMMAND_END;
+ }
device->relvol = relvol;
device->volume = rel_to_vol(relvol);
@@ -2977,28 +2948,30 @@ volume_setrel_speaker(struct player_command *cmd)
#endif
if (device->session)
- cmd->output_requests_pending = outputs_device_volume_set(device, device_command_cb);
+ *retval = outputs_device_volume_set(device, device_command_cb);
break;
}
listener_notify(LISTENER_VOLUME);
- if (cmd->output_requests_pending > 0)
- return 1; /* async */
+ if (*retval > 0)
+ return COMMAND_PENDING; /* async */
- return 0;
+ return COMMAND_END;
}
-static int
-volume_setabs_speaker(struct player_command *cmd)
+static enum command_state
+volume_setabs_speaker(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
struct output_device *device;
uint64_t id;
int volume;
- id = cmd->arg.vol_param.spk_id;
- volume = cmd->arg.vol_param.volume;
+ *retval = 0;
+ id = cmdarg->vol_param.spk_id;
+ volume = cmdarg->vol_param.volume;
master_volume = volume;
@@ -3026,48 +2999,56 @@ volume_setabs_speaker(struct player_command *cmd)
#endif
if (device->session)
- cmd->output_requests_pending = outputs_device_volume_set(device, device_command_cb);
+ *retval = outputs_device_volume_set(device, device_command_cb);//FIXME Does this need to be += ?
}
}
listener_notify(LISTENER_VOLUME);
- if (cmd->output_requests_pending > 0)
- return 1; /* async */
+ if (*retval > 0)
+ return COMMAND_PENDING; /* async */
- return 0;
+ return COMMAND_END;
}
-static int
-repeat_set(struct player_command *cmd)
+static enum command_state
+repeat_set(void *arg, int *retval)
{
- if (cmd->arg.mode == repeat)
- return 0;
+ union player_arg *cmdarg = arg;
- switch (cmd->arg.mode)
+ if (cmdarg->mode == repeat)
+ {
+ *retval = 0;
+ return COMMAND_END;
+ }
+
+ switch (cmdarg->mode)
{
case REPEAT_OFF:
case REPEAT_SONG:
case REPEAT_ALL:
- repeat = cmd->arg.mode;
+ repeat = cmdarg->mode;
break;
default:
- DPRINTF(E_LOG, L_PLAYER, "Invalid repeat mode: %d\n", cmd->arg.mode);
- return -1;
+ DPRINTF(E_LOG, L_PLAYER, "Invalid repeat mode: %d\n", cmdarg->mode);
+ *retval = -1;
+ return COMMAND_END;
}
listener_notify(LISTENER_OPTIONS);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-shuffle_set(struct player_command *cmd)
+static enum command_state
+shuffle_set(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
uint32_t cur_id;
- switch (cmd->arg.intval)
+ switch (cmdarg->intval)
{
case 1:
if (!shuffle)
@@ -3077,28 +3058,31 @@ shuffle_set(struct player_command *cmd)
}
/* FALLTHROUGH*/
case 0:
- shuffle = cmd->arg.intval;
+ shuffle = cmdarg->intval;
break;
default:
- DPRINTF(E_LOG, L_PLAYER, "Invalid shuffle mode: %d\n", cmd->arg.intval);
- return -1;
+ DPRINTF(E_LOG, L_PLAYER, "Invalid shuffle mode: %d\n", cmdarg->intval);
+ *retval = -1;
+ return COMMAND_END;
}
listener_notify(LISTENER_OPTIONS);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playerqueue_get_bypos(struct player_command *cmd)
+static enum command_state
+playerqueue_get_bypos(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
int count;
struct queue *qi;
struct player_source *ps;
int item_id;
- count = cmd->arg.queue_get_param.count;
+ count = cmdarg->queue_get_param.count;
ps = source_now_playing();
@@ -3110,36 +3094,40 @@ playerqueue_get_bypos(struct player_command *cmd)
qi = queue_new_bypos(queue, item_id, count, shuffle);
- cmd->arg.queue_get_param.queue = qi;
+ cmdarg->queue_get_param.queue = qi;
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playerqueue_get_byindex(struct player_command *cmd)
+static enum command_state
+playerqueue_get_byindex(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
int pos;
int count;
struct queue *qi;
- pos = cmd->arg.queue_get_param.pos;
- count = cmd->arg.queue_get_param.count;
+ pos = cmdarg->queue_get_param.pos;
+ count = cmdarg->queue_get_param.count;
qi = queue_new_byindex(queue, pos, count, 0);
- cmd->arg.queue_get_param.queue = qi;
+ cmdarg->queue_get_param.queue = qi;
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playerqueue_add(struct player_command *cmd)
+static enum command_state
+playerqueue_add(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
struct queue_item *items;
uint32_t cur_id;
uint32_t *item_id;
- items = cmd->arg.queue_add_param.items;
- item_id = cmd->arg.queue_add_param.item_id_ptr;
+ items = cmdarg->queue_add_param.items;
+ item_id = cmdarg->queue_add_param.item_id_ptr;
queue_add(queue, items);
@@ -3157,16 +3145,18 @@ playerqueue_add(struct player_command *cmd)
listener_notify(LISTENER_PLAYLIST);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playerqueue_add_next(struct player_command *cmd)
+static enum command_state
+playerqueue_add_next(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
struct queue_item *items;
uint32_t cur_id;
- items = cmd->arg.queue_add_param.items;
+ items = cmdarg->queue_add_param.items;
cur_id = cur_streaming ? cur_streaming->item_id : 0;
@@ -3180,17 +3170,19 @@ playerqueue_add_next(struct player_command *cmd)
listener_notify(LISTENER_PLAYLIST);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playerqueue_move_bypos(struct player_command *cmd)
+static enum command_state
+playerqueue_move_bypos(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
struct player_source *ps_playing;
uint32_t item_id;
DPRINTF(E_DBG, L_PLAYER, "Moving song from position %d to be the next song after %d\n",
- cmd->arg.queue_move_param.from_pos, cmd->arg.queue_move_param.to_pos);
+ cmdarg->queue_move_param.from_pos, cmdarg->queue_move_param.to_pos);
ps_playing = source_now_playing();
@@ -3202,57 +3194,66 @@ playerqueue_move_bypos(struct player_command *cmd)
else
item_id = ps_playing->item_id;
- queue_move_bypos(queue, item_id, cmd->arg.queue_move_param.from_pos, cmd->arg.queue_move_param.to_pos, shuffle);
+ queue_move_bypos(queue, item_id, cmdarg->queue_move_param.from_pos, cmdarg->queue_move_param.to_pos, shuffle);
cur_plversion++;
listener_notify(LISTENER_PLAYLIST);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playerqueue_move_byindex(struct player_command *cmd)
+static enum command_state
+playerqueue_move_byindex(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
+
DPRINTF(E_DBG, L_PLAYER, "Moving song from index %d to be the next song after %d\n",
- cmd->arg.queue_move_param.from_pos, cmd->arg.queue_move_param.to_pos);
+ cmdarg->queue_move_param.from_pos, cmdarg->queue_move_param.to_pos);
- queue_move_byindex(queue, cmd->arg.queue_move_param.from_pos, cmd->arg.queue_move_param.to_pos, 0);
+ queue_move_byindex(queue, cmdarg->queue_move_param.from_pos, cmdarg->queue_move_param.to_pos, 0);
cur_plversion++;
listener_notify(LISTENER_PLAYLIST);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playerqueue_move_byitemid(struct player_command *cmd)
+static enum command_state
+playerqueue_move_byitemid(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
+
DPRINTF(E_DBG, L_PLAYER, "Moving song with item-id %d to be the next song after index %d\n",
- cmd->arg.queue_move_param.item_id, cmd->arg.queue_move_param.to_pos);
+ cmdarg->queue_move_param.item_id, cmdarg->queue_move_param.to_pos);
- queue_move_byitemid(queue, cmd->arg.queue_move_param.item_id, cmd->arg.queue_move_param.to_pos, 0);
+ queue_move_byitemid(queue, cmdarg->queue_move_param.item_id, cmdarg->queue_move_param.to_pos, 0);
cur_plversion++;
listener_notify(LISTENER_PLAYLIST);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playerqueue_remove_bypos(struct player_command *cmd)
+static enum command_state
+playerqueue_remove_bypos(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
int pos;
struct player_source *ps_playing;
uint32_t item_id;
- pos = cmd->arg.intval;
+ pos = cmdarg->intval;
if (pos < 1)
{
DPRINTF(E_LOG, L_PLAYER, "Can't remove item, invalid position %d\n", pos);
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
ps_playing = source_now_playing();
@@ -3272,18 +3273,20 @@ playerqueue_remove_bypos(struct player_command *cmd)
listener_notify(LISTENER_PLAYLIST);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playerqueue_remove_byindex(struct player_command *cmd)
+static enum command_state
+playerqueue_remove_byindex(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
int pos;
int count;
int i;
- pos = cmd->arg.queue_remove_param.from_pos;
- count = cmd->arg.queue_remove_param.count;
+ pos = cmdarg->queue_remove_param.from_pos;
+ count = cmdarg->queue_remove_param.count;
DPRINTF(E_DBG, L_PLAYER, "Removing %d items starting from position %d\n", count, pos);
@@ -3294,19 +3297,22 @@ playerqueue_remove_byindex(struct player_command *cmd)
listener_notify(LISTENER_PLAYLIST);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playerqueue_remove_byitemid(struct player_command *cmd)
+static enum command_state
+playerqueue_remove_byitemid(void *arg, int *retval)
{
+ union player_arg *cmdarg = arg;
uint32_t id;
- id = cmd->arg.id;
+ id = cmdarg->id;
if (id < 1)
{
DPRINTF(E_LOG, L_PLAYER, "Can't remove item, invalid id %d\n", id);
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
DPRINTF(E_DBG, L_PLAYER, "Removing item with id %d\n", id);
@@ -3316,14 +3322,15 @@ playerqueue_remove_byitemid(struct player_command *cmd)
listener_notify(LISTENER_PLAYLIST);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
/*
* Removes all media items from the queue
*/
-static int
-playerqueue_clear(struct player_command *cmd)
+static enum command_state
+playerqueue_clear(void *arg, int *retval)
{
queue_clear(queue);
@@ -3332,14 +3339,15 @@ playerqueue_clear(struct player_command *cmd)
listener_notify(LISTENER_PLAYLIST);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
/*
* Removes all items from the history
*/
-static int
-playerqueue_clear_history(struct player_command *cmd)
+static enum command_state
+playerqueue_clear_history(void *arg, int *retval)
{
memset(history, 0, sizeof(struct player_history));
@@ -3347,149 +3355,31 @@ playerqueue_clear_history(struct player_command *cmd)
listener_notify(LISTENER_PLAYLIST);
- return 0;
-}
-
-static int
-playerqueue_plid(struct player_command *cmd)
-{
- cur_plid = cmd->arg.id;
-
- return 0;
-}
-
-/* Command processing */
-/* Thread: player */
-static void
-command_cb(int fd, short what, void *arg)
-{
- struct player_command *cmd;
- int ret;
-
- ret = read(cmd_pipe[0], &cmd, sizeof(cmd));
- if (ret != sizeof(cmd))
- {
- DPRINTF(E_LOG, L_PLAYER, "Could not read command! (read %d): %s\n", ret, (ret < 0) ? strerror(errno) : "-no error-");
-
- goto readd;
- }
-
- if (cmd->nonblock)
- {
- cmd->func(cmd);
-
- free(cmd);
- goto readd;
- }
-
- pthread_mutex_lock(&cmd->lck);
-
- cur_cmd = cmd;
-
- ret = cmd->func(cmd);
-
- if (ret <= 0)
- {
- cmd->ret = ret;
-
- cur_cmd = NULL;
-
- pthread_cond_signal(&cmd->cond);
- pthread_mutex_unlock(&cmd->lck);
- }
- else
- {
- /* Command is asynchronous, we don't want to process another command
- * before we're done with this one. See command_async_end().
- */
-
- return;
- }
-
- readd:
- event_add(cmdev, NULL);
-}
-
-
-/* Thread: httpd (DACP) - mDNS */
-static int
-send_command(struct player_command *cmd)
-{
- int ret;
-
- if (!cmd->func)
- {
- DPRINTF(E_LOG, L_PLAYER, "BUG: cmd->func is NULL!\n");
-
- return -1;
- }
-
- ret = write(cmd_pipe[1], &cmd, sizeof(cmd));
- if (ret != sizeof(cmd))
- {
- DPRINTF(E_LOG, L_PLAYER, "Could not send command: %s\n", strerror(errno));
-
- return -1;
- }
-
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-/* Thread: mDNS */
-static int
-nonblock_command(struct player_command *cmd)
+static enum command_state
+playerqueue_plid(void *arg, int *retval)
{
- int ret;
-
- ret = send_command(cmd);
- if (ret < 0)
- return -1;
+ union player_arg *cmdarg = arg;
+ cur_plid = cmdarg->id;
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-/* Thread: httpd (DACP) */
-static int
-sync_command(struct player_command *cmd)
-{
- int ret;
-
- pthread_mutex_lock(&cmd->lck);
-
- ret = send_command(cmd);
- if (ret < 0)
- {
- pthread_mutex_unlock(&cmd->lck);
-
- return -1;
- }
-
- pthread_cond_wait(&cmd->cond, &cmd->lck);
-
- pthread_mutex_unlock(&cmd->lck);
-
- ret = cmd->ret;
-
- return ret;
-}
/* Player API executed in the httpd (DACP) thread */
int
player_get_status(struct player_status *status)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = get_status;
- cmd.func_bh = NULL;
- cmd.arg.status = status;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.status = status;
+ ret = commands_exec_sync(cmdbase, get_status, NULL, &cmdarg);
return ret;
}
@@ -3502,45 +3392,32 @@ player_get_status(struct player_status *status)
int
player_now_playing(uint32_t *id)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = now_playing;
- cmd.func_bh = NULL;
- cmd.arg.id_ptr = id;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.id_ptr = id;
+ ret = commands_exec_sync(cmdbase, now_playing, NULL, &cmdarg);
return ret;
}
char *
player_get_icy_artwork_url(uint32_t id)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = artwork_url_get;
- cmd.func_bh = NULL;
- cmd.arg.icy.id = id;
+ cmdarg.icy.id = id;
if (pthread_self() != tid_player)
- ret = sync_command(&cmd);
+ ret = commands_exec_sync(cmdbase, artwork_url_get, NULL, &cmdarg);
else
- ret = artwork_url_get(&cmd);
-
- command_deinit(&cmd);
+ artwork_url_get(&cmdarg, &ret);
if (ret < 0)
return NULL;
else
- return cmd.arg.icy.artwork_url;
+ return cmdarg.icy.artwork_url;
}
/*
@@ -3559,19 +3436,12 @@ player_get_icy_artwork_url(uint32_t id)
int
player_playback_start(uint32_t *id)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playback_start;
- cmd.func_bh = playback_start_bh;
- cmd.arg.playback_start_param.id_ptr = id;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.playback_start_param.id_ptr = id;
+ ret = commands_exec_sync(cmdbase, playback_start, playback_start_bh, &cmdarg);
return ret;
}
@@ -3589,19 +3459,13 @@ player_playback_start(uint32_t *id)
int
player_playback_start_byindex(int index, uint32_t *id)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playback_start_byindex;
- cmd.func_bh = playback_start_bh;
- cmd.arg.playback_start_param.pos = index;
- cmd.arg.playback_start_param.id_ptr = id;
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.playback_start_param.pos = index;
+ cmdarg.playback_start_param.id_ptr = id;
+ ret = commands_exec_sync(cmdbase, playback_start_byindex, playback_start_bh, &cmdarg);
return ret;
}
@@ -3621,19 +3485,13 @@ player_playback_start_byindex(int index, uint32_t *id)
int
player_playback_start_bypos(int pos, uint32_t *id)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playback_start_bypos;
- cmd.func_bh = playback_start_bh;
- cmd.arg.playback_start_param.pos = pos;
- cmd.arg.playback_start_param.id_ptr = id;
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.playback_start_param.pos = pos;
+ cmdarg.playback_start_param.id_ptr = id;
+ ret = commands_exec_sync(cmdbase, playback_start_bypos, playback_start_bh, &cmdarg);
return ret;
}
@@ -3651,18 +3509,13 @@ player_playback_start_bypos(int pos, uint32_t *id)
int
player_playback_start_byitemid(uint32_t item_id, uint32_t *id)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playback_start_byitemid;
- cmd.func_bh = playback_start_bh;
- cmd.arg.playback_start_param.id = item_id;
- cmd.arg.playback_start_param.id_ptr = id;
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.playback_start_param.id = item_id;
+ cmdarg.playback_start_param.id_ptr = id;
+ ret = commands_exec_sync(cmdbase, playback_start_byitemid, playback_start_bh, &cmdarg);
+ return ret;
return ret;
}
@@ -3670,94 +3523,48 @@ player_playback_start_byitemid(uint32_t item_id, uint32_t *id)
int
player_playback_stop(void)
{
- struct player_command cmd;
int ret;
- command_init(&cmd);
-
- cmd.func = playback_stop;
- cmd.arg.noarg = NULL;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
-
+ ret = commands_exec_sync(cmdbase, playback_stop, NULL, NULL);
return ret;
}
int
player_playback_pause(void)
{
- struct player_command cmd;
int ret;
- command_init(&cmd);
-
- cmd.func = playback_pause;
- cmd.func_bh = playback_pause_bh;
- cmd.arg.noarg = NULL;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
-
+ ret = commands_exec_sync(cmdbase, playback_pause, playback_pause_bh, NULL);
return ret;
}
int
player_playback_seek(int ms)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playback_pause;
- cmd.func_bh = playback_seek_bh;
- cmd.arg.intval = ms;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.intval = ms;
+ ret = commands_exec_sync(cmdbase, playback_pause, playback_seek_bh, &cmdarg);
return ret;
}
int
player_playback_next(void)
{
- struct player_command cmd;
int ret;
- command_init(&cmd);
-
- cmd.func = playback_pause;
- cmd.func_bh = playback_next_bh;
- cmd.arg.noarg = NULL;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
-
+ ret = commands_exec_sync(cmdbase, playback_pause, playback_next_bh, NULL);
return ret;
}
int
player_playback_prev(void)
{
- struct player_command cmd;
int ret;
- command_init(&cmd);
-
- cmd.func = playback_pause;
- cmd.func_bh = playback_prev_bh;
- cmd.arg.noarg = NULL;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
-
+ ret = commands_exec_sync(cmdbase, playback_pause, playback_prev_bh, NULL);
return ret;
}
@@ -3765,136 +3572,89 @@ player_playback_prev(void)
void
player_speaker_enumerate(spk_enum_cb cb, void *arg)
{
- struct player_command cmd;
+ union player_arg cmdarg;
struct spk_enum spk_enum;
- command_init(&cmd);
-
spk_enum.cb = cb;
spk_enum.arg = arg;
- cmd.func = speaker_enumerate;
- cmd.func_bh = NULL;
- cmd.arg.spk_enum = &spk_enum;
-
- sync_command(&cmd);
+ cmdarg.spk_enum = &spk_enum;
- command_deinit(&cmd);
+ commands_exec_sync(cmdbase, speaker_enumerate, NULL, &cmdarg);
}
int
player_speaker_set(uint64_t *ids)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = speaker_set;
- cmd.func_bh = NULL;
- cmd.arg.device_ids = ids;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.speaker_set_param.device_ids = ids;
+ cmdarg.speaker_set_param.intval = 0;
+ ret = commands_exec_sync(cmdbase, speaker_set, NULL, &cmdarg);
return ret;
}
int
player_volume_set(int vol)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = volume_set;
- cmd.func_bh = NULL;
- cmd.arg.intval = vol;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.intval = vol;
+ ret = commands_exec_sync(cmdbase, volume_set, NULL, &cmdarg);
return ret;
}
int
player_volume_setrel_speaker(uint64_t id, int relvol)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = volume_setrel_speaker;
- cmd.func_bh = NULL;
- cmd.arg.vol_param.spk_id = id;
- cmd.arg.vol_param.volume = relvol;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.vol_param.spk_id = id;
+ cmdarg.vol_param.volume = relvol;
+ ret = commands_exec_sync(cmdbase, volume_setrel_speaker, NULL, &cmdarg);
return ret;
}
int
player_volume_setabs_speaker(uint64_t id, int vol)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = volume_setabs_speaker;
- cmd.func_bh = NULL;
- cmd.arg.vol_param.spk_id = id;
- cmd.arg.vol_param.volume = vol;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.vol_param.spk_id = id;
+ cmdarg.vol_param.volume = vol;
+ ret = commands_exec_sync(cmdbase, volume_setabs_speaker, NULL, &cmdarg);
return ret;
}
int
player_repeat_set(enum repeat_mode mode)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = repeat_set;
- cmd.func_bh = NULL;
- cmd.arg.mode = mode;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.mode = mode;
+ ret = commands_exec_sync(cmdbase, repeat_set, NULL, &cmdarg);
return ret;
}
int
player_shuffle_set(int enable)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = shuffle_set;
- cmd.func_bh = NULL;
- cmd.arg.intval = enable;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.intval = enable;
+ ret = commands_exec_sync(cmdbase, shuffle_set, NULL, &cmdarg);
return ret;
}
@@ -3910,25 +3670,19 @@ player_shuffle_set(int enable)
struct queue *
player_queue_get_bypos(int count)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playerqueue_get_bypos;
- cmd.func_bh = NULL;
- cmd.arg.queue_get_param.pos = -1;
- cmd.arg.queue_get_param.count = count;
- cmd.arg.queue_get_param.queue = NULL;
+ cmdarg.queue_get_param.pos = -1;
+ cmdarg.queue_get_param.count = count;
+ cmdarg.queue_get_param.queue = NULL;
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ ret = commands_exec_sync(cmdbase, playerqueue_get_bypos, NULL, &cmdarg);
if (ret != 0)
return NULL;
- return cmd.arg.queue_get_param.queue;
+ return cmdarg.queue_get_param.queue;
}
/*
@@ -3942,25 +3696,19 @@ player_queue_get_bypos(int count)
struct queue *
player_queue_get_byindex(int index, int count)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playerqueue_get_byindex;
- cmd.func_bh = NULL;
- cmd.arg.queue_get_param.pos = index;
- cmd.arg.queue_get_param.count = count;
- cmd.arg.queue_get_param.queue = NULL;
+ cmdarg.queue_get_param.pos = index;
+ cmdarg.queue_get_param.count = count;
+ cmdarg.queue_get_param.queue = NULL;
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ ret = commands_exec_sync(cmdbase, playerqueue_get_byindex, NULL, &cmdarg);
if (ret != 0)
return NULL;
- return cmd.arg.queue_get_param.queue;
+ return cmdarg.queue_get_param.queue;
}
/*
@@ -3969,20 +3717,13 @@ player_queue_get_byindex(int index, int count)
int
player_queue_add(struct queue_item *items, uint32_t *item_id)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playerqueue_add;
- cmd.func_bh = NULL;
- cmd.arg.queue_add_param.items = items;
- cmd.arg.queue_add_param.item_id_ptr = item_id;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.queue_add_param.items = items;
+ cmdarg.queue_add_param.item_id_ptr = item_id;
+ ret = commands_exec_sync(cmdbase, playerqueue_add, NULL, &cmdarg);
return ret;
}
@@ -3992,19 +3733,12 @@ player_queue_add(struct queue_item *items, uint32_t *item_id)
int
player_queue_add_next(struct queue_item *items)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playerqueue_add_next;
- cmd.func_bh = NULL;
- cmd.arg.queue_add_param.items = items;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.queue_add_param.items = items;
+ ret = commands_exec_sync(cmdbase, playerqueue_add_next, NULL, &cmdarg);
return ret;
}
@@ -4017,60 +3751,39 @@ player_queue_add_next(struct queue_item *items)
int
player_queue_move_bypos(int pos_from, int pos_to)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playerqueue_move_bypos;
- cmd.func_bh = NULL;
- cmd.arg.queue_move_param.from_pos = pos_from;
- cmd.arg.queue_move_param.to_pos = pos_to;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.queue_move_param.from_pos = pos_from;
+ cmdarg.queue_move_param.to_pos = pos_to;
+ ret = commands_exec_sync(cmdbase, playerqueue_move_bypos, NULL, &cmdarg);
return ret;
}
int
player_queue_move_byindex(int pos_from, int pos_to)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playerqueue_move_byindex;
- cmd.func_bh = NULL;
- cmd.arg.queue_move_param.from_pos = pos_from;
- cmd.arg.queue_move_param.to_pos = pos_to;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.queue_move_param.from_pos = pos_from;
+ cmdarg.queue_move_param.to_pos = pos_to;
+ ret = commands_exec_sync(cmdbase, playerqueue_move_byindex, NULL, &cmdarg);
return ret;
}
int
player_queue_move_byitemid(uint32_t item_id, int pos_to)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playerqueue_move_byitemid;
- cmd.func_bh = NULL;
- cmd.arg.queue_move_param.item_id = item_id;
- cmd.arg.queue_move_param.to_pos = pos_to;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.queue_move_param.item_id = item_id;
+ cmdarg.queue_move_param.to_pos = pos_to;
+ ret = commands_exec_sync(cmdbase, playerqueue_move_byitemid, NULL, &cmdarg);
return ret;
}
@@ -4086,19 +3799,12 @@ player_queue_move_byitemid(uint32_t item_id, int pos_to)
int
player_queue_remove_bypos(int pos)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playerqueue_remove_bypos;
- cmd.func_bh = NULL;
- cmd.arg.intval = pos;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.intval = pos;
+ ret = commands_exec_sync(cmdbase, playerqueue_remove_bypos, NULL, &cmdarg);
return ret;
}
@@ -4114,20 +3820,13 @@ player_queue_remove_bypos(int pos)
int
player_queue_remove_byindex(int pos, int count)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playerqueue_remove_byindex;
- cmd.func_bh = NULL;
- cmd.arg.queue_remove_param.from_pos = pos;
- cmd.arg.queue_remove_param.count = count;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.queue_remove_param.from_pos = pos;
+ cmdarg.queue_remove_param.count = count;
+ ret = commands_exec_sync(cmdbase, playerqueue_remove_byindex, NULL, &cmdarg);
return ret;
}
@@ -4140,141 +3839,85 @@ player_queue_remove_byindex(int pos, int count)
int
player_queue_remove_byitemid(uint32_t id)
{
- struct player_command cmd;
+ union player_arg cmdarg;
int ret;
- command_init(&cmd);
-
- cmd.func = playerqueue_remove_byitemid;
- cmd.func_bh = NULL;
- cmd.arg.id = id;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ cmdarg.id = id;
+ ret = commands_exec_sync(cmdbase, playerqueue_remove_byitemid, NULL, &cmdarg);
return ret;
}
void
player_queue_clear(void)
{
- struct player_command cmd;
-
- command_init(&cmd);
-
- cmd.func = playerqueue_clear;
- cmd.func_bh = NULL;
- cmd.arg.noarg = NULL;
-
- sync_command(&cmd);
-
- command_deinit(&cmd);
+ commands_exec_sync(cmdbase, playerqueue_clear, NULL, NULL);
}
void
player_queue_clear_history()
{
- struct player_command cmd;
-
- command_init(&cmd);
-
- cmd.func = playerqueue_clear_history;
- cmd.func_bh = NULL;
-
- sync_command(&cmd);
-
- command_deinit(&cmd);
+ commands_exec_sync(cmdbase, playerqueue_clear_history, NULL, NULL);
}
void
player_queue_plid(uint32_t plid)
{
- struct player_command cmd;
-
- command_init(&cmd);
+ union player_arg cmdarg;
- cmd.func = playerqueue_plid;
- cmd.func_bh = NULL;
- cmd.arg.id = plid;
+ cmdarg.id = plid;
- sync_command(&cmd);
-
- command_deinit(&cmd);
+ commands_exec_sync(cmdbase, playerqueue_plid, NULL, &cmdarg);
}
/* Non-blocking commands used by mDNS */
int
player_device_add(void *device)
{
- struct player_command *cmd;
+ union player_arg *cmdarg;
int ret;
- cmd = calloc(1, sizeof(struct player_command));
- if (!cmd)
+ cmdarg = calloc(1, sizeof(union player_arg));
+ if (!cmdarg)
{
DPRINTF(E_LOG, L_PLAYER, "Could not allocate player_command\n");
return -1;
}
- cmd->nonblock = 1;
-
- cmd->func = device_add;
- cmd->arg.device = device;
+ cmdarg->device = device;
- ret = nonblock_command(cmd);
- if (ret < 0)
- {
- free(cmd);
- return -1;
- }
-
- return 0;
+ ret = commands_exec_async(cmdbase, device_add, cmdarg);
+ return ret;
}
int
player_device_remove(void *device)
{
- struct player_command *cmd;
+ union player_arg *cmdarg;
int ret;
- cmd = calloc(1, sizeof(struct player_command));
- if (!cmd)
+ cmdarg = calloc(1, sizeof(union player_arg));
+ if (!cmdarg)
{
DPRINTF(E_LOG, L_PLAYER, "Could not allocate player_command\n");
return -1;
}
- cmd->nonblock = 1;
-
- cmd->func = device_remove_family;
- cmd->arg.device = device;
+ cmdarg->device = device;
- ret = nonblock_command(cmd);
- if (ret < 0)
- {
- free(cmd);
- return -1;
- }
-
- return 0;
+ ret = commands_exec_async(cmdbase, device_remove_family, cmdarg);
+ return ret;
}
/* Thread: worker */
static void
player_metadata_send(struct player_metadata *pmd)
{
- struct player_command cmd;
-
- command_init(&cmd);
+ union player_arg cmdarg;
- cmd.func = metadata_send;
- cmd.func_bh = NULL;
- cmd.arg.pmd = pmd;
+ cmdarg.pmd = pmd;
- sync_command(&cmd);
-
- command_deinit(&cmd);
+ commands_exec_sync(cmdbase, metadata_send, NULL, &cmdarg);
}
/* Thread: player */
@@ -4312,14 +3955,6 @@ player(void *arg)
pthread_exit(NULL);
}
-/* Thread: player */
-static void
-exit_cb(int fd, short what, void *arg)
-{
- event_base_loopbreak(evbase_player);
-
- player_exit = 1;
-}
/* Thread: main */
int
@@ -4331,6 +3966,7 @@ player_init(void)
player_exit = 0;
+ speaker_autoselect = cfg_getbool(cfg_getsec(cfg, "general"), "speaker_autoselect");
clear_queue_on_stop_disabled = cfg_getbool(cfg_getsec(cfg, "mpd"), "clear_queue_on_stop_disable");
dev_list = NULL;
@@ -4339,8 +3975,6 @@ player_init(void)
output_sessions = 0;
- cur_cmd = NULL;
-
cur_playing = NULL;
cur_streaming = NULL;
cur_plid = 0;
@@ -4403,30 +4037,6 @@ player_init(void)
goto audio_fail;
}
-#ifdef HAVE_PIPE2
- ret = pipe2(exit_pipe, O_CLOEXEC);
-#else
- ret = pipe(exit_pipe);
-#endif
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_PLAYER, "Could not create pipe: %s\n", strerror(errno));
-
- goto exit_fail;
- }
-
-#ifdef HAVE_PIPE2
- ret = pipe2(cmd_pipe, O_CLOEXEC);
-#else
- ret = pipe(cmd_pipe);
-#endif
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_PLAYER, "Could not create command pipe: %s\n", strerror(errno));
-
- goto cmd_fail;
- }
-
evbase_player = event_base_new();
if (!evbase_player)
{
@@ -4435,20 +4045,6 @@ player_init(void)
goto evbase_fail;
}
- exitev = event_new(evbase_player, exit_pipe[0], EV_READ, exit_cb, NULL);
- if (!exitev)
- {
- DPRINTF(E_LOG, L_PLAYER, "Could not create exit event\n");
- goto evnew_fail;
- }
-
- cmdev = event_new(evbase_player, cmd_pipe[0], EV_READ, command_cb, NULL);
- if (!cmdev)
- {
- DPRINTF(E_LOG, L_PLAYER, "Could not create cmd event\n");
- goto evnew_fail;
- }
-
#if defined(__linux__)
pb_timer_ev = event_new(evbase_player, pb_timer_fd, EV_READ | EV_PERSIST, player_playback_cb, NULL);
#else
@@ -4460,10 +4056,10 @@ player_init(void)
goto evnew_fail;
}
- event_add(exitev, NULL);
- event_add(cmdev, NULL);
event_add(pb_timer_ev, NULL);
+ cmdbase = commands_base_new(evbase_player, NULL);
+
ret = outputs_init();
if (ret < 0)
{
@@ -4488,15 +4084,10 @@ player_init(void)
thread_fail:
outputs_deinit();
outputs_fail:
+ commands_base_free(cmdbase);
evnew_fail:
event_base_free(evbase_player);
evbase_fail:
- close(cmd_pipe[0]);
- close(cmd_pipe[1]);
- cmd_fail:
- close(exit_pipe[0]);
- close(exit_pipe[1]);
- exit_fail:
evbuffer_free(audio_buf);
audio_fail:
#if defined(__linux__)
@@ -4513,20 +4104,14 @@ void
player_deinit(void)
{
int ret;
- int dummy = 42;
-
- ret = write(exit_pipe[1], &dummy, sizeof(dummy));
- if (ret != sizeof(dummy))
- {
- DPRINTF(E_LOG, L_PLAYER, "Could not write to exit pipe: %s\n", strerror(errno));
- return;
- }
+ player_exit = 1;
+ commands_base_destroy(cmdbase);
ret = pthread_join(tid_player, NULL);
if (ret != 0)
{
- DPRINTF(E_LOG, L_PLAYER, "Could not join HTTPd thread: %s\n", strerror(errno));
+ DPRINTF(E_LOG, L_PLAYER, "Could not join player thread: %s\n", strerror(errno));
return;
}
@@ -4545,11 +4130,5 @@ player_deinit(void)
outputs_deinit();
- close(exit_pipe[0]);
- close(exit_pipe[1]);
- close(cmd_pipe[0]);
- close(cmd_pipe[1]);
- cmd_pipe[0] = -1;
- cmd_pipe[1] = -1;
event_base_free(evbase_player);
}
diff --git a/src/player.h b/src/player.h
index c69a69f..d0b4040 100644
--- a/src/player.h
+++ b/src/player.h
@@ -69,7 +69,7 @@ struct player_status {
int next_pos_pl;
};
-typedef void (*spk_enum_cb)(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg);
+typedef void (*spk_enum_cb)(uint64_t id, const char *name, int relvol, int absvol, struct spk_flags flags, void *arg);
struct player_history
{
diff --git a/src/remote_pairing.c b/src/remote_pairing.c
index f3f935e..49ca5c0 100644
--- a/src/remote_pairing.c
+++ b/src/remote_pairing.c
@@ -492,7 +492,7 @@ pairing_request_cb(struct evhttp_request *req, void *arg)
ri->pi.guid = strdup(guid);
- DPRINTF(E_INFO, L_REMOTE, "Pairing succeeded with Remote '%s' (id %s), GUID: %s\n", ri->pi.name, ri->pi.remote_id, guid);
+ DPRINTF(E_LOG, L_REMOTE, "Pairing succeeded with Remote '%s' (id %s), GUID: %s\n", ri->pi.name, ri->pi.remote_id, guid);
ret = db_pairing_add(&ri->pi);
if (ret < 0)
@@ -935,7 +935,8 @@ remote_pairing_init(void)
}
#endif /* USE_EVENTFD */
- ret = mdns_browse("_touch-remote._tcp", MDNS_WANT_V4, touch_remote_cb);
+ // No ipv6 for remote at the moment
+ ret = mdns_browse("_touch-remote._tcp", AF_INET, touch_remote_cb);
if (ret < 0)
{
DPRINTF(E_FATAL, L_REMOTE, "Could not browse for Remote services\n");
diff --git a/src/spotify.c b/src/spotify.c
index 61870a1..a75be67 100644
--- a/src/spotify.c
+++ b/src/spotify.c
@@ -49,6 +49,7 @@
#include "conffile.h"
#include "filescanner.h"
#include "cache.h"
+#include "commands.h"
/* How long to wait for audio (in sec) before giving up */
@@ -102,30 +103,6 @@ struct artwork_get_param
int is_loaded;
};
-struct spotify_command;
-
-typedef int (*cmd_func)(struct spotify_command *cmd);
-
-struct spotify_command
-{
- pthread_mutex_t lck;
- pthread_cond_t cond;
-
- cmd_func func;
- cmd_func func_bh;
-
- int nonblock;
-
- union {
- void *noarg;
- sp_link *link;
- int seek_ms;
- struct audio_get_param audio;
- struct artwork_get_param artwork;
- } arg;
-
- int ret;
-};
/* --- Globals --- */
// Spotify thread
@@ -137,21 +114,17 @@ static pthread_cond_t login_cond;
// Event base, pipes and events
struct event_base *evbase_spotify;
-static int g_exit_pipe[2];
-static int g_cmd_pipe[2];
static int g_notify_pipe[2];
-static struct event *g_exitev;
-static struct event *g_cmdev;
static struct event *g_notifyev;
+static struct commands_base *cmdbase;
+
// The global session handle
static sp_session *g_sess;
// The global library handle
static void *g_libhandle;
// The global state telling us what the thread is currently doing
static enum spotify_state g_state;
-/* (not used) Tells which commmand is currently being processed */
-static struct spotify_command *g_cmd;
// The global base playlist id (parent of all Spotify playlists in the db)
static int g_base_plid;
@@ -409,92 +382,6 @@ fptr_assign_all()
// End of ugly part
-/* ---------------------------- COMMAND EXECUTION -------------------------- */
-
-static void
-command_init(struct spotify_command *cmd)
-{
- memset(cmd, 0, sizeof(struct spotify_command));
-
- pthread_mutex_init(&cmd->lck, NULL);
- pthread_cond_init(&cmd->cond, NULL);
-}
-
-static void
-command_deinit(struct spotify_command *cmd)
-{
- pthread_cond_destroy(&cmd->cond);
- pthread_mutex_destroy(&cmd->lck);
-}
-
-static int
-send_command(struct spotify_command *cmd)
-{
- int ret;
-
- if (!cmd->func)
- {
- DPRINTF(E_LOG, L_SPOTIFY, "BUG: cmd->func is NULL!\n");
- return -1;
- }
-
- ret = write(g_cmd_pipe[1], &cmd, sizeof(cmd));
- if (ret != sizeof(cmd))
- {
- DPRINTF(E_LOG, L_SPOTIFY, "Could not send command: %s\n", strerror(errno));
- return -1;
- }
-
- return 0;
-}
-
-static int
-sync_command(struct spotify_command *cmd)
-{
- int ret;
-
- pthread_mutex_lock(&cmd->lck);
-
- ret = send_command(cmd);
- if (ret < 0)
- {
- pthread_mutex_unlock(&cmd->lck);
- return -1;
- }
-
- pthread_cond_wait(&cmd->cond, &cmd->lck);
- pthread_mutex_unlock(&cmd->lck);
-
- ret = cmd->ret;
-
- return ret;
-}
-
-static int
-nonblock_command(struct spotify_command *cmd)
-{
- int ret;
-
- ret = send_command(cmd);
- if (ret < 0)
- return -1;
-
- return 0;
-}
-
-/* Thread: main and filescanner */
-static void
-thread_exit(void)
-{
- int dummy = 42;
-
- DPRINTF(E_DBG, L_SPOTIFY, "Killing Spotify thread\n");
-
- if (write(g_exit_pipe[1], &dummy, sizeof(dummy)) != sizeof(dummy))
- DPRINTF(E_LOG, L_SPOTIFY, "Could not write to exit fd: %s\n", strerror(errno));
-}
-
-
/* -------------------------- PLAYLIST HELPERS ------------------------- */
/* Should only be called from within the spotify thread */
@@ -1033,47 +920,55 @@ audio_fifo_flush(void)
pthread_mutex_unlock(&g_audio_fifo->mutex);
}
-static int
-playback_setup(struct spotify_command *cmd)
+static enum command_state
+playback_setup(void *arg, int *retval)
{
+ sp_link *link;
sp_track *track;
sp_error err;
DPRINTF(E_DBG, L_SPOTIFY, "Setting up for playback\n");
+ link = (sp_link *) arg;
+
if (SP_CONNECTION_STATE_LOGGED_IN != fptr_sp_session_connectionstate(g_sess))
{
DPRINTF(E_LOG, L_SPOTIFY, "Can't play music, not connected and logged in to Spotify\n");
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
- if (!cmd->arg.link)
+ if (!link)
{
DPRINTF(E_LOG, L_SPOTIFY, "Playback setup failed, no Spotify link\n");
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
- track = fptr_sp_link_as_track(cmd->arg.link);
+ track = fptr_sp_link_as_track(link);
if (!track)
{
DPRINTF(E_LOG, L_SPOTIFY, "Playback setup failed, invalid Spotify track\n");
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
err = fptr_sp_session_player_load(g_sess, track);
if (SP_ERROR_OK != err)
{
DPRINTF(E_LOG, L_SPOTIFY, "Playback setup failed: %s\n", fptr_sp_error_message(err));
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
audio_fifo_flush();
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playback_play(struct spotify_command *cmd)
+static enum command_state
+playback_play(void *arg, int *retval)
{
sp_error err;
@@ -1083,16 +978,18 @@ playback_play(struct spotify_command *cmd)
if (SP_ERROR_OK != err)
{
DPRINTF(E_LOG, L_SPOTIFY, "Playback failed: %s\n", fptr_sp_error_message(err));
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
g_state = SPOTIFY_STATE_PLAYING;
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playback_pause(struct spotify_command *cmd)
+static enum command_state
+playback_pause(void *arg, int *retval)
{
sp_error err;
@@ -1104,16 +1001,18 @@ playback_pause(struct spotify_command *cmd)
if (SP_ERROR_OK != err)
{
DPRINTF(E_LOG, L_SPOTIFY, "Playback pause failed: %s\n", fptr_sp_error_message(err));
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
g_state = SPOTIFY_STATE_PAUSED;
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playback_stop(struct spotify_command *cmd)
+static enum command_state
+playback_stop(void *arg, int *retval)
{
sp_error err;
@@ -1123,35 +1022,43 @@ playback_stop(struct spotify_command *cmd)
if (SP_ERROR_OK != err)
{
DPRINTF(E_LOG, L_SPOTIFY, "Playback stop failed: %s\n", fptr_sp_error_message(err));
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
g_state = SPOTIFY_STATE_STOPPED;
- return 0;
+
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playback_seek(struct spotify_command *cmd)
+static enum command_state
+playback_seek(void *arg, int *retval)
{
+ int seek_ms;
sp_error err;
DPRINTF(E_DBG, L_SPOTIFY, "Playback seek\n");
- err = fptr_sp_session_player_seek(g_sess, cmd->arg.seek_ms);
+ seek_ms = *((int *) arg);
+
+ err = fptr_sp_session_player_seek(g_sess, seek_ms);
if (SP_ERROR_OK != err)
{
DPRINTF(E_LOG, L_SPOTIFY, "Could not seek: %s\n", fptr_sp_error_message(err));
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
audio_fifo_flush();
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-playback_eot(struct spotify_command *cmd)
+static enum command_state
+playback_eot(void *arg, int *retval)
{
sp_error err;
@@ -1161,17 +1068,20 @@ playback_eot(struct spotify_command *cmd)
if (SP_ERROR_OK != err)
{
DPRINTF(E_LOG, L_SPOTIFY, "Playback end of track failed: %s\n", fptr_sp_error_message(err));
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
g_state = SPOTIFY_STATE_STOPPING;
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-static int
-audio_get(struct spotify_command *cmd)
+static enum command_state
+audio_get(void *arg, int *retval)
{
+ struct audio_get_param *audio;
struct timespec ts;
audio_fifo_data_t *afd;
int processed;
@@ -1179,16 +1089,17 @@ audio_get(struct spotify_command *cmd)
int ret;
int s;
+ audio = (struct audio_get_param *) arg;
afd = NULL;
processed = 0;
// If spotify was paused begin by resuming playback
if (g_state == SPOTIFY_STATE_PAUSED)
- playback_play(NULL);
+ playback_play(NULL, retval);
pthread_mutex_lock(&g_audio_fifo->mutex);
- while ((processed < cmd->arg.audio.wanted) && (g_state != SPOTIFY_STATE_STOPPED))
+ while ((processed < audio->wanted) && (g_state != SPOTIFY_STATE_STOPPED))
{
// If track has ended and buffer is empty
if ((g_state == SPOTIFY_STATE_STOPPING) && (g_audio_fifo->qlen <= 0))
@@ -1227,14 +1138,15 @@ audio_get(struct spotify_command *cmd)
s = afd->nsamples * sizeof(int16_t) * 2;
- ret = evbuffer_add(cmd->arg.audio.evbuf, afd->samples, s);
+ ret = evbuffer_add(audio->evbuf, afd->samples, s);
free(afd);
afd = NULL;
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for evbuffer (tried to add %d bytes)\n", s);
pthread_mutex_unlock(&g_audio_fifo->mutex);
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
processed += s;
@@ -1242,41 +1154,39 @@ audio_get(struct spotify_command *cmd)
pthread_mutex_unlock(&g_audio_fifo->mutex);
- return processed;
+
+ *retval = processed;
+ return COMMAND_END;
}
static void
artwork_loaded_cb(sp_image *image, void *userdata)
{
- struct spotify_command *cmd = userdata;
-
- pthread_mutex_lock(&cmd->arg.artwork.mutex);
+ struct artwork_get_param *artwork;
+
+ artwork = userdata;
+
+ pthread_mutex_lock(&artwork->mutex);
- cmd->arg.artwork.is_loaded = 1;
+ artwork->is_loaded = 1;
- pthread_cond_signal(&cmd->arg.artwork.cond);
- pthread_mutex_unlock(&cmd->arg.artwork.mutex);
+ pthread_cond_signal(&artwork->cond);
+ pthread_mutex_unlock(&artwork->mutex);
}
-static int
-artwork_get_bh(struct spotify_command *cmd)
+static enum command_state
+artwork_get_bh(void *arg, int *retval)
{
+ struct artwork_get_param *artwork;
sp_imageformat imageformat;
sp_error err;
const void *data;
size_t data_size;
int ret;
- sp_image *image = cmd->arg.artwork.image;
- char *path = cmd->arg.artwork.path;
-
- if (!cmd->arg.artwork.is_loaded)
- {
- DPRINTF(E_DBG, L_SPOTIFY, "Request for artwork timed out: %s\n", path);
-
- fptr_sp_image_remove_load_callback(image, artwork_loaded_cb, cmd);
- goto fail;
- }
+ artwork = arg;
+ sp_image *image = artwork->image;
+ char *path = artwork->path;
err = fptr_sp_image_error(image);
if (err != SP_ERROR_OK)
@@ -1305,14 +1215,14 @@ artwork_get_bh(struct spotify_command *cmd)
goto fail;
}
- ret = evbuffer_expand(cmd->arg.artwork.evbuf, data_size);
+ ret = evbuffer_expand(artwork->evbuf, data_size);
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for artwork\n");
goto fail;
}
- ret = evbuffer_add(cmd->arg.artwork.evbuf, data, data_size);
+ ret = evbuffer_add(artwork->evbuf, data, data_size);
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Could not add Spotify image to event buffer\n");
@@ -1323,17 +1233,20 @@ artwork_get_bh(struct spotify_command *cmd)
fptr_sp_image_release(image);
- return data_size;
+ *retval = 0;
+ return COMMAND_END;
fail:
fptr_sp_image_release(image);
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
-static int
-artwork_get(struct spotify_command *cmd)
+static enum command_state
+artwork_get(void *arg, int *retval)
{
+ struct artwork_get_param *artwork;
char *path;
sp_link *link;
sp_track *track;
@@ -1343,7 +1256,8 @@ artwork_get(struct spotify_command *cmd)
sp_image_size image_size;
sp_error err;
- path = cmd->arg.artwork.path;
+ artwork = arg;
+ path = artwork->path;
// Now begins: path -> link -> track -> album -> image_id -> image -> format -> data
link = fptr_sp_link_create_from_string(path);
@@ -1369,9 +1283,9 @@ artwork_get(struct spotify_command *cmd)
// Get an image at least the same size as requested
image_size = SP_IMAGE_SIZE_SMALL; // 64x64
- if ((cmd->arg.artwork.max_w > 64) || (cmd->arg.artwork.max_h > 64))
+ if ((artwork->max_w > 64) || (artwork->max_h > 64))
image_size = SP_IMAGE_SIZE_NORMAL; // 300x300
- if ((cmd->arg.artwork.max_w > 300) || (cmd->arg.artwork.max_h > 300))
+ if ((artwork->max_w > 300) || (artwork->max_h > 300))
image_size = SP_IMAGE_SIZE_LARGE; // 640x640
image_id = fptr_sp_album_cover(album, image_size);
@@ -1390,31 +1304,35 @@ artwork_get(struct spotify_command *cmd)
fptr_sp_link_release(link);
- cmd->arg.artwork.image = image;
+ artwork->image = image;
+ artwork->is_loaded = fptr_sp_image_is_loaded(image);
/* If the image is ready we can return it straight away, otherwise we will
* let the calling thread wait, since the Spotify thread should not wait
*/
- if ( (cmd->arg.artwork.is_loaded = fptr_sp_image_is_loaded(image)) )
- return artwork_get_bh(cmd);
+ if (artwork->is_loaded)
+ return artwork_get_bh(artwork, retval);
DPRINTF(E_SPAM, L_SPOTIFY, "Will wait for Spotify to call artwork_loaded_cb\n");
/* Async - we will return to spotify_artwork_get which will wait for callback */
- err = fptr_sp_image_add_load_callback(image, artwork_loaded_cb, cmd);
+ err = fptr_sp_image_add_load_callback(image, artwork_loaded_cb, artwork);
if (err != SP_ERROR_OK)
{
DPRINTF(E_WARN, L_SPOTIFY, "Adding artwork cb failed, Spotify error: %s\n", fptr_sp_error_message(err));
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
- return 0;
+ *retval = 0;
+ return COMMAND_END;
level2_exit:
fptr_sp_link_release(link);
level1_exit:
- return -1;
+ *retval = -1;
+ return COMMAND_END;
}
@@ -1615,24 +1533,9 @@ static void connectionstate_updated(sp_session *session)
*/
static void end_of_track(sp_session *sess)
{
- struct spotify_command *cmd;
-
DPRINTF(E_DBG, L_SPOTIFY, "End of track\n");
- cmd = (struct spotify_command *)malloc(sizeof(struct spotify_command));
- if (!cmd)
- {
- DPRINTF(E_LOG, L_SPOTIFY, "Could not allocate spotify_command\n");
- return;
- }
- memset(cmd, 0, sizeof(struct spotify_command));
-
- cmd->nonblock = 1;
-
- cmd->func = playback_eot;
- cmd->arg.noarg = NULL;
-
- nonblock_command(cmd);
+ commands_exec_async(cmdbase, playback_eot, NULL);
}
/**
@@ -1700,58 +1603,11 @@ spotify(void *arg)
}
static void
-exit_cb(int fd, short what, void *arg)
+exit_cb()
{
- int dummy;
- int ret;
-
- ret = read(g_exit_pipe[0], &dummy, sizeof(dummy));
- if (ret != sizeof(dummy))
- DPRINTF(E_LOG, L_SPOTIFY, "Error reading from exit pipe\n");
-
fptr_sp_session_player_unload(g_sess);
fptr_sp_session_logout(g_sess);
-
- event_base_loopbreak(evbase_spotify);
-
g_state = SPOTIFY_STATE_INACTIVE;
-
- event_add(g_exitev, NULL);
-}
-
-static void
-command_cb(int fd, short what, void *arg)
-{
- struct spotify_command *cmd;
- int ret;
-
- ret = read(g_cmd_pipe[0], &cmd, sizeof(cmd));
- if (ret != sizeof(cmd))
- {
- DPRINTF(E_LOG, L_SPOTIFY, "Could not read command! (read %d): %s\n", ret, (ret < 0) ? strerror(errno) : "-no error-");
- goto readd;
- }
-
- if (cmd->nonblock)
- {
- cmd->func(cmd);
-
- free(cmd);
- goto readd;
- }
-
- pthread_mutex_lock(&cmd->lck);
-
- g_cmd = cmd;
- ret = cmd->func(cmd);
- cmd->ret = ret;
- g_cmd = NULL;
-
- pthread_cond_signal(&cmd->cond);
- pthread_mutex_unlock(&cmd->lck);
-
- readd:
- event_add(g_cmdev, NULL);
}
/* Process events when timeout expires or triggered by libspotify's notify_main_thread */
@@ -1789,9 +1645,7 @@ notify_cb(int fd, short what, void *arg)
int
spotify_playback_setup(struct media_file_info *mfi)
{
- struct spotify_command cmd;
sp_link *link;
- int ret;
DPRINTF(E_DBG, L_SPOTIFY, "Playback setup request\n");
@@ -1802,144 +1656,59 @@ spotify_playback_setup(struct media_file_info *mfi)
return -1;
}
- command_init(&cmd);
-
- cmd.func = playback_setup;
- cmd.arg.link = link;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
-
- return ret;
+ return commands_exec_sync(cmdbase, playback_setup, NULL, link);
}
int
spotify_playback_play()
{
- struct spotify_command cmd;
- int ret;
-
DPRINTF(E_DBG, L_SPOTIFY, "Playback request\n");
- command_init(&cmd);
-
- cmd.func = playback_play;
- cmd.arg.noarg = NULL;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
-
- return ret;
+ return commands_exec_sync(cmdbase, playback_play, NULL, NULL);
}
int
spotify_playback_pause()
{
- struct spotify_command cmd;
- int ret;
-
DPRINTF(E_DBG, L_SPOTIFY, "Pause request\n");
- command_init(&cmd);
-
- cmd.func = playback_pause;
- cmd.arg.noarg = NULL;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
-
- return ret;
+ return commands_exec_sync(cmdbase, playback_pause, NULL, NULL);
}
/* Thread: libspotify */
void
spotify_playback_pause_nonblock(void)
{
- struct spotify_command *cmd;
-
DPRINTF(E_DBG, L_SPOTIFY, "Nonblock pause request\n");
- cmd = (struct spotify_command *)malloc(sizeof(struct spotify_command));
- if (!cmd)
- {
- DPRINTF(E_LOG, L_SPOTIFY, "Could not allocate spotify_command\n");
- return;
- }
-
- memset(cmd, 0, sizeof(struct spotify_command));
-
- cmd->nonblock = 1;
-
- cmd->func = playback_pause;
- cmd->arg.noarg = NULL;
-
- nonblock_command(cmd);
+ commands_exec_async(cmdbase, playback_pause, NULL);
}
/* Thread: player and libspotify */
int
spotify_playback_stop(void)
{
- struct spotify_command cmd;
- int ret;
-
DPRINTF(E_DBG, L_SPOTIFY, "Stop request\n");
- command_init(&cmd);
-
- cmd.func = playback_stop;
- cmd.arg.noarg = NULL;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
-
- return ret;
+ return commands_exec_sync(cmdbase, playback_stop, NULL, NULL);
}
/* Thread: player and libspotify */
void
spotify_playback_stop_nonblock(void)
{
- struct spotify_command *cmd;
-
DPRINTF(E_DBG, L_SPOTIFY, "Nonblock stop request\n");
- cmd = (struct spotify_command *)malloc(sizeof(struct spotify_command));
- if (!cmd)
- {
- DPRINTF(E_LOG, L_SPOTIFY, "Could not allocate spotify_command\n");
- return;
- }
-
- memset(cmd, 0, sizeof(struct spotify_command));
-
- cmd->nonblock = 1;
-
- cmd->func = playback_stop;
- cmd->arg.noarg = NULL;
-
- nonblock_command(cmd);
+ commands_exec_async(cmdbase, playback_stop, NULL);
}
/* Thread: player */
int
spotify_playback_seek(int ms)
{
- struct spotify_command cmd;
int ret;
- command_init(&cmd);
-
- cmd.func = playback_seek;
- cmd.arg.seek_ms = ms;
-
- ret = sync_command(&cmd);
-
- command_deinit(&cmd);
+ ret = commands_exec_sync(cmdbase, playback_seek, NULL, &ms);
if (ret == 0)
return ms;
@@ -1951,58 +1720,44 @@ spotify_playback_seek(int ms)
int
spotify_audio_get(struct evbuffer *evbuf, int wanted)
{
- struct spotify_command cmd;
- int ret;
-
- command_init(&cmd);
-
- cmd.func = audio_get;
- cmd.arg.audio.evbuf = evbuf;
- cmd.arg.audio.wanted = wanted;
-
- ret = sync_command(&cmd);
+ struct audio_get_param audio;
- command_deinit(&cmd);
+ audio.evbuf = evbuf;
+ audio.wanted = wanted;
- return ret;
+ return commands_exec_sync(cmdbase, audio_get, NULL, &audio);
}
/* Thread: httpd (artwork) and worker */
int
spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h)
{
- struct spotify_command cmd;
+ struct artwork_get_param artwork;
struct timespec ts;
int ret;
- command_init(&cmd);
-
- cmd.func = artwork_get;
- cmd.arg.artwork.evbuf = evbuf;
- cmd.arg.artwork.path = path;
- cmd.arg.artwork.max_w = max_w;
- cmd.arg.artwork.max_h = max_h;
-
- pthread_mutex_init(&cmd.arg.artwork.mutex, NULL);
- pthread_cond_init(&cmd.arg.artwork.cond, NULL);
+ artwork.evbuf = evbuf;
+ artwork.path = path;
+ artwork.max_w = max_w;
+ artwork.max_h = max_h;
- ret = sync_command(&cmd);
+ pthread_mutex_init(&artwork.mutex, NULL);
+ pthread_cond_init(&artwork.cond, NULL);
+ ret = commands_exec_sync(cmdbase, artwork_get, NULL, &artwork);
+
// Artwork was not ready, wait for callback from libspotify
if (ret == 0)
{
- pthread_mutex_lock(&cmd.arg.artwork.mutex);
+ pthread_mutex_lock(&artwork.mutex);
mk_reltime(&ts, SPOTIFY_ARTWORK_TIMEOUT);
- if (!cmd.arg.artwork.is_loaded)
- pthread_cond_timedwait(&cmd.arg.artwork.cond, &cmd.arg.artwork.mutex, &ts);
- pthread_mutex_unlock(&cmd.arg.artwork.mutex);
+ if (!artwork.is_loaded)
+ pthread_cond_timedwait(&artwork.cond, &artwork.mutex, &ts);
+ pthread_mutex_unlock(&artwork.mutex);
- cmd.func = artwork_get_bh;
- ret = sync_command(&cmd);
+ ret = commands_exec_sync(cmdbase, artwork_get_bh, NULL, &artwork);
}
-
- command_deinit(&cmd);
-
+
return ret;
}
@@ -2194,28 +1949,6 @@ spotify_init(void)
goto assign_fail;
#ifdef HAVE_PIPE2
- ret = pipe2(g_exit_pipe, O_CLOEXEC);
-#else
- ret = pipe(g_exit_pipe);
-#endif
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_SPOTIFY, "Could not create pipe: %s\n", strerror(errno));
- goto exit_fail;
- }
-
-#ifdef HAVE_PIPE2
- ret = pipe2(g_cmd_pipe, O_CLOEXEC);
-#else
- ret = pipe(g_cmd_pipe);
-#endif
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_SPOTIFY, "Could not create command pipe: %s\n", strerror(errno));
- goto cmd_fail;
- }
-
-#ifdef HAVE_PIPE2
ret = pipe2(g_notify_pipe, O_CLOEXEC);
#else
ret = pipe(g_notify_pipe);
@@ -2233,20 +1966,6 @@ spotify_init(void)
goto evbase_fail;
}
- g_exitev = event_new(evbase_spotify, g_exit_pipe[0], EV_READ, exit_cb, NULL);
- if (!g_exitev)
- {
- DPRINTF(E_LOG, L_SPOTIFY, "Could not create exit event\n");
- goto evnew_fail;
- }
-
- g_cmdev = event_new(evbase_spotify, g_cmd_pipe[0], EV_READ, command_cb, NULL);
- if (!g_cmdev)
- {
- DPRINTF(E_LOG, L_SPOTIFY, "Could not create cmd event\n");
- goto evnew_fail;
- }
-
g_notifyev = event_new(evbase_spotify, g_notify_pipe[0], EV_READ | EV_TIMEOUT, notify_cb, NULL);
if (!g_notifyev)
{
@@ -2254,10 +1973,16 @@ spotify_init(void)
goto evnew_fail;
}
- event_add(g_exitev, NULL);
- event_add(g_cmdev, NULL);
event_add(g_notifyev, NULL);
+
+ cmdbase = commands_base_new(evbase_spotify, exit_cb);
+ if (!cmdbase)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Could not create command base\n");
+ goto cmd_fail;
+ }
+
DPRINTF(E_INFO, L_SPOTIFY, "Spotify session init\n");
spotify_cfg = cfg_getsec(cfg, "spotify");
@@ -2333,7 +2058,9 @@ spotify_init(void)
g_sess = NULL;
session_fail:
+ cmd_fail:
evnew_fail:
+ commands_base_free(cmdbase);
event_base_free(evbase_spotify);
evbase_spotify = NULL;
@@ -2342,14 +2069,6 @@ spotify_init(void)
close(g_notify_pipe[1]);
notify_fail:
- close(g_cmd_pipe[0]);
- close(g_cmd_pipe[1]);
-
- cmd_fail:
- close(g_exit_pipe[0]);
- close(g_exit_pipe[1]);
-
- exit_fail:
assign_fail:
dlclose(g_libhandle);
g_libhandle = NULL;
@@ -2369,7 +2088,8 @@ spotify_deinit(void)
/* Send exit signal to thread (if active) */
if (g_state != SPOTIFY_STATE_INACTIVE)
{
- thread_exit();
+ commands_base_destroy(cmdbase);
+ g_state = SPOTIFY_STATE_INACTIVE;
ret = pthread_join(tid_spotify, NULL);
if (ret != 0)
@@ -2388,10 +2108,6 @@ spotify_deinit(void)
/* Close pipes */
close(g_notify_pipe[0]);
close(g_notify_pipe[1]);
- close(g_cmd_pipe[0]);
- close(g_cmd_pipe[1]);
- close(g_exit_pipe[0]);
- close(g_exit_pipe[1]);
/* Destroy locks */
pthread_cond_destroy(&login_cond);
diff --git a/src/transcode.c b/src/transcode.c
index 776393d..b6ee21b 100644
--- a/src/transcode.c
+++ b/src/transcode.c
@@ -54,9 +54,12 @@
// How long to wait (in microsec) before interrupting av_read_frame
#define READ_TIMEOUT 15000000
-static char *default_codecs = "mpeg,wav";
-static char *roku_codecs = "mpeg,mp4a,wma,wav";
-static char *itunes_codecs = "mpeg,mp4a,mp4v,alac,wav";
+static const char *default_codecs = "mpeg,wav";
+static const char *roku_codecs = "mpeg,mp4a,wma,wav";
+static const char *itunes_codecs = "mpeg,mp4a,mp4v,alac,wav";
+
+// Used for passing errors to DPRINTF (can't count on av_err2str being present)
+static char errbuf[64];
struct filter_ctx {
AVFilterContext *buffersink_ctx;
@@ -193,6 +196,13 @@ init_profile(struct encode_ctx *ctx, enum transcode_profile profile)
/* -------------------------------- HELPERS -------------------------------- */
+static inline char *
+err2str(int errnum)
+{
+ av_strerror(errnum, errbuf, sizeof(errbuf));
+ return errbuf;
+}
+
static inline void
add_le16(uint8_t *dst, uint16_t val)
{
@@ -296,7 +306,6 @@ static int
read_packet(AVPacket *packet, AVStream **stream, unsigned int *stream_index, struct decode_ctx *ctx)
{
AVStream *in_stream;
- char *errmsg;
int ret;
do
@@ -322,10 +331,7 @@ read_packet(AVPacket *packet, AVStream **stream, unsigned int *stream_index, str
ret = av_read_frame(ctx->ifmt_ctx, &ctx->packet);
if (ret < 0)
{
- errmsg = malloc(128);
- av_strerror(ret, errmsg, 128);
- DPRINTF(E_WARN, L_XCODE, "Could not read frame: %s\n", errmsg);
- free(errmsg);
+ DPRINTF(E_WARN, L_XCODE, "Could not read frame: %s\n", err2str(ret));
return ret;
}
@@ -409,7 +415,7 @@ filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int s
ret = av_buffersrc_add_frame_flags(ctx->filter_ctx[stream_index].buffersrc_ctx, frame, 0);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Error while feeding the filtergraph\n");
+ DPRINTF(E_LOG, L_XCODE, "Error while feeding the filtergraph: %s\n", err2str(ret));
return -1;
}
}
@@ -463,7 +469,7 @@ filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int s
ret = av_buffersrc_write_frame(ctx->filter_ctx[stream_index].buffersrc_ctx, frame);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Error while feeding the filtergraph\n");
+ DPRINTF(E_LOG, L_XCODE, "Error while feeding the filtergraph: %s\n", err2str(ret));
return -1;
}
}
@@ -609,14 +615,14 @@ open_input(struct decode_ctx *ctx, struct media_file_info *mfi, int decode_video
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot open input path\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot open '%s': %s\n", mfi->path, err2str(ret));
return -1;
}
ret = avformat_find_stream_info(ctx->ifmt_ctx, NULL);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot find stream information\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot find stream information: %s\n", err2str(ret));
goto out_fail;
}
@@ -644,7 +650,7 @@ open_input(struct decode_ctx *ctx, struct media_file_info *mfi, int decode_video
ret = avcodec_open2(ctx->ifmt_ctx->streams[stream_index]->codec, decoder, NULL);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Failed to open decoder for stream #%d\n", stream_index);
+ DPRINTF(E_LOG, L_XCODE, "Failed to open decoder for stream #%d: %s\n", stream_index, err2str(ret));
goto out_fail;
}
@@ -658,14 +664,14 @@ open_input(struct decode_ctx *ctx, struct media_file_info *mfi, int decode_video
stream_index = av_find_best_stream(ctx->ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);
if ((stream_index < 0) || (!decoder))
{
- DPRINTF(E_LOG, L_XCODE, "Did not find video stream or suitable decoder for %s\n", mfi->path);
+ DPRINTF(E_LOG, L_XCODE, "Did not find video stream or suitable decoder for '%s': %s\n", mfi->path, err2str(ret));
return 0;
}
ret = avcodec_open2(ctx->ifmt_ctx->streams[stream_index]->codec, decoder, NULL);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Failed to open decoder for stream #%d\n", stream_index);
+ DPRINTF(E_LOG, L_XCODE, "Failed to open decoder for stream #%d: %s\n", stream_index, err2str(ret));
return 0;
}
@@ -799,7 +805,7 @@ open_output(struct encode_ctx *ctx, struct decode_ctx *src_ctx)
ret = avcodec_open2(enc_ctx, encoder, NULL);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot open encoder (%s) for input stream #%u\n", codec_desc->name, i);
+ DPRINTF(E_LOG, L_XCODE, "Cannot open encoder (%s) for input stream #%u: %s\n", codec_desc->name, i, err2str(ret));
goto out_fail_codec;
}
@@ -811,7 +817,7 @@ open_output(struct encode_ctx *ctx, struct decode_ctx *src_ctx)
ret = avformat_write_header(ctx->ofmt_ctx, NULL);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Error occurred when writing header to output buffer\n");
+ DPRINTF(E_LOG, L_XCODE, "Error writing header to output buffer: %s\n", err2str(ret));
goto out_fail_write;
}
@@ -891,21 +897,21 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot create buffer source\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot create buffer source: %s\n", err2str(ret));
goto out_fail;
}
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot create buffer sink\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot create buffer sink: %s\n", err2str(ret));
goto out_fail;
}
ret = av_opt_set_bin(buffersink_ctx, "pix_fmts", (uint8_t*)&enc_ctx->pix_fmt, sizeof(enc_ctx->pix_fmt), AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot set output pixel format\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot set output pixel format: %s\n", err2str(ret));
goto out_fail;
}
}
@@ -932,14 +938,14 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer source\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer source: %s\n", err2str(ret));
goto out_fail;
}
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer sink\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer sink: %s\n", err2str(ret));
goto out_fail;
}
@@ -947,7 +953,7 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte
(uint8_t*)&enc_ctx->sample_fmt, sizeof(enc_ctx->sample_fmt), AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot set output sample format\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot set output sample format: %s\n", err2str(ret));
goto out_fail;
}
@@ -955,7 +961,7 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte
(uint8_t*)&enc_ctx->channel_layout, sizeof(enc_ctx->channel_layout), AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot set output channel layout\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot set output channel layout: %s\n", err2str(ret));
goto out_fail;
}
@@ -963,7 +969,7 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte
(uint8_t*)&enc_ctx->sample_rate, sizeof(enc_ctx->sample_rate), AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot set output sample rate\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot set output sample rate: %s\n", err2str(ret));
goto out_fail;
}
}
@@ -1055,7 +1061,7 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot create buffer source\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot create buffer source: %s\n", err2str(ret));
goto out_fail;
}
@@ -1066,14 +1072,14 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte
ret = avfilter_graph_create_filter(&format_ctx, format, "format", args, NULL, filter_graph);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot create format filter\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot create format filter: %s\n", err2str(ret));
goto out_fail;
}
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot create buffer sink\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot create buffer sink: %s\n", err2str(ret));
goto out_fail;
}
}
@@ -1101,7 +1107,7 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer source\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer source: %s\n", err2str(ret));
goto out_fail;
}
@@ -1113,14 +1119,14 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte
ret = avfilter_graph_create_filter(&format_ctx, format, "format", args, NULL, filter_graph);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot create audio format filter\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot create audio format filter: %s\n", err2str(ret));
goto out_fail;
}
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph);
if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer sink\n");
+ DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer sink: %s\n", err2str(ret));
goto out_fail;
}
}
@@ -1134,7 +1140,7 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte
if (ret >= 0)
ret = avfilter_link(format_ctx, 0, buffersink_ctx, 0);
if (ret < 0)
- DPRINTF(E_LOG, L_XCODE, "Error connecting filters\n");
+ DPRINTF(E_LOG, L_XCODE, "Error connecting filters: %s\n", err2str(ret));
ret = avfilter_graph_config(filter_graph, NULL);
if (ret < 0)
@@ -1587,7 +1593,6 @@ transcode_decode(struct decoded_frame **decoded, struct decode_ctx *ctx)
int
transcode_encode(struct evbuffer *evbuf, struct decoded_frame *decoded, struct encode_ctx *ctx)
{
- char *errmsg;
int stream_index;
int encoded_length;
int ret;
@@ -1608,10 +1613,7 @@ transcode_encode(struct evbuffer *evbuf, struct decoded_frame *decoded, struct e
ret = filter_encode_write_frame(ctx, decoded->frame, stream_index);
if (ret < 0)
{
- errmsg = malloc(128);
- av_strerror(ret, errmsg, 128);
- DPRINTF(E_LOG, L_XCODE, "Error occurred: %s\n", errmsg);
- free(errmsg);
+ DPRINTF(E_LOG, L_XCODE, "Error occurred: %s\n", err2str(ret));
return ret;
}
@@ -1654,6 +1656,7 @@ transcode_raw2frame(uint8_t *data, size_t size)
{
struct decoded_frame *decoded;
AVFrame *frame;
+ int ret;
decoded = malloc(sizeof(struct decoded_frame));
frame = av_frame_alloc();
@@ -1675,9 +1678,10 @@ transcode_raw2frame(uint8_t *data, size_t size)
frame->pts = AV_NOPTS_VALUE;
frame->sample_rate = 44100;
- if (avcodec_fill_audio_frame(frame, 2, frame->format, data, size, 0) < 0)
+ ret = avcodec_fill_audio_frame(frame, 2, frame->format, data, size, 0);
+ if (ret < 0)
{
- DPRINTF(E_LOG, L_XCODE, "Error filling frame with rawbuf\n");
+ DPRINTF(E_LOG, L_XCODE, "Error filling frame with rawbuf: %s\n", err2str(ret));
return NULL;
}
@@ -1721,7 +1725,7 @@ transcode_seek(struct transcode_ctx *ctx, int ms)
ret = av_seek_frame(decode_ctx->ifmt_ctx, in_stream->index, target_pts, AVSEEK_FLAG_BACKWARD);
if (ret < 0)
{
- DPRINTF(E_WARN, L_XCODE, "Could not seek into stream: %s\n", strerror(AVUNERROR(ret)));
+ DPRINTF(E_WARN, L_XCODE, "Could not seek into stream: %s\n", err2str(ret));
return -1;
}
@@ -1743,7 +1747,7 @@ transcode_seek(struct transcode_ctx *ctx, int ms)
ret = av_read_frame(decode_ctx->ifmt_ctx, &decode_ctx->packet);
if (ret < 0)
{
- DPRINTF(E_WARN, L_XCODE, "Could not read more data while seeking\n");
+ DPRINTF(E_WARN, L_XCODE, "Could not read more data while seeking: %s\n", err2str(ret));
in_stream->codec->skip_frame = AVDISCARD_DEFAULT;
return -1;
}
diff --git a/src/worker.c b/src/worker.c
index da6ba09..a7cc9ee 100644
--- a/src/worker.c
+++ b/src/worker.c
@@ -39,29 +39,15 @@
#include "db.h"
#include "logger.h"
#include "worker.h"
+#include "commands.h"
-struct worker_command;
-
-typedef int (*cmd_func)(struct worker_command *cmd);
-
-struct worker_command
+struct worker_arg
{
- pthread_mutex_t lck;
- pthread_cond_t cond;
-
- cmd_func func;
-
- int nonblock;
-
- struct {
- void (*cb)(void *);
- void *cb_arg;
- int delay;
- struct event *timer;
- } arg;
-
- int ret;
+ void (*cb)(void *);
+ void *cb_arg;
+ int delay;
+ struct event *timer;
};
@@ -72,10 +58,8 @@ static pthread_t tid_worker;
// Event base, pipes and events
struct event_base *evbase_worker;
static int g_initialized;
-static int g_exit_pipe[2];
-static int g_cmd_pipe[2];
-static struct event *g_exitev;
-static struct event *g_cmdev;
+static struct commands_base *cmdbase;
+
/* ---------------------------- CALLBACK EXECUTION ------------------------- */
/* Thread: worker */
@@ -83,85 +67,39 @@ static struct event *g_cmdev;
static void
execute_cb(int fd, short what, void *arg)
{
- struct worker_command *cmd = arg;
+ struct worker_arg *cmdarg = arg;
- cmd->arg.cb(cmd->arg.cb_arg);
+ cmdarg->cb(cmdarg->cb_arg);
- event_free(cmd->arg.timer);
- free(cmd->arg.cb_arg);
- free(cmd);
+ event_free(cmdarg->timer);
+ free(cmdarg->cb_arg);
+ free(cmdarg);
}
-static int
-execute(struct worker_command *cmd)
+static enum command_state
+execute(void *arg, int *retval)
{
- struct timeval tv = { cmd->arg.delay, 0 };
+ struct worker_arg *cmdarg = arg;
+ struct timeval tv = { cmdarg->delay, 0 };
- if (cmd->arg.delay)
+ if (cmdarg->delay)
{
- cmd->arg.timer = evtimer_new(evbase_worker, execute_cb, cmd);
- evtimer_add(cmd->arg.timer, &tv);
+ cmdarg->timer = evtimer_new(evbase_worker, execute_cb, cmdarg);
+ evtimer_add(cmdarg->timer, &tv);
- return 1; // Not done yet, ask caller not to free cmd
+ *retval = 0;
+ return COMMAND_PENDING; // Not done yet, ask caller not to free cmd
}
- cmd->arg.cb(cmd->arg.cb_arg);
- free(cmd->arg.cb_arg);
+ cmdarg->cb(cmdarg->cb_arg);
+ free(cmdarg->cb_arg);
- return 0;
+ *retval = 0;
+ return COMMAND_END;
}
-/* ---------------------------- COMMAND EXECUTION -------------------------- */
-
-static int
-send_command(struct worker_command *cmd)
-{
- int ret;
-
- if (!cmd->func)
- {
- DPRINTF(E_LOG, L_MAIN, "BUG: cmd->func is NULL!\n");
- return -1;
- }
-
- ret = write(g_cmd_pipe[1], &cmd, sizeof(cmd));
- if (ret != sizeof(cmd))
- {
- DPRINTF(E_LOG, L_MAIN, "Could not send command: %s\n", strerror(errno));
- return -1;
- }
-
- return 0;
-}
-
-static int
-nonblock_command(struct worker_command *cmd)
-{
- int ret;
-
- ret = send_command(cmd);
- if (ret < 0)
- return -1;
-
- return 0;
-}
-
-/* Thread: main */
-static void
-thread_exit(void)
-{
- int dummy = 42;
-
- DPRINTF(E_DBG, L_MAIN, "Killing worker thread\n");
-
- if (write(g_exit_pipe[1], &dummy, sizeof(dummy)) != sizeof(dummy))
- DPRINTF(E_LOG, L_MAIN, "Could not write to exit fd: %s\n", strerror(errno));
-}
-
-
-
/* --------------------------------- MAIN --------------------------------- */
/* Thread: worker */
@@ -192,57 +130,6 @@ worker(void *arg)
pthread_exit(NULL);
}
-static void
-exit_cb(int fd, short what, void *arg)
-{
- int dummy;
- int ret;
-
- ret = read(g_exit_pipe[0], &dummy, sizeof(dummy));
- if (ret != sizeof(dummy))
- DPRINTF(E_LOG, L_MAIN, "Error reading from exit pipe\n");
-
- event_base_loopbreak(evbase_worker);
-
- g_initialized = 0;
-
- event_add(g_exitev, NULL);
-}
-
-static void
-command_cb(int fd, short what, void *arg)
-{
- struct worker_command *cmd;
- int ret;
-
- ret = read(g_cmd_pipe[0], &cmd, sizeof(cmd));
- if (ret != sizeof(cmd))
- {
- DPRINTF(E_LOG, L_MAIN, "Could not read command! (read %d): %s\n", ret, (ret < 0) ? strerror(errno) : "-no error-");
- goto readd;
- }
-
- if (cmd->nonblock)
- {
- ret = cmd->func(cmd);
-
- if (ret == 0)
- free(cmd);
- goto readd;
- }
-
- pthread_mutex_lock(&cmd->lck);
-
- ret = cmd->func(cmd);
- cmd->ret = ret;
-
- pthread_cond_signal(&cmd->cond);
- pthread_mutex_unlock(&cmd->lck);
-
- readd:
- event_add(g_cmdev, NULL);
-}
-
/* ---------------------------- Our worker API --------------------------- */
@@ -250,19 +137,19 @@ command_cb(int fd, short what, void *arg)
void
worker_execute(void (*cb)(void *), void *cb_arg, size_t arg_size, int delay)
{
- struct worker_command *cmd;
+ struct worker_arg *cmdarg;
void *argcpy;
DPRINTF(E_DBG, L_MAIN, "Got worker execute request\n");
- cmd = (struct worker_command *)malloc(sizeof(struct worker_command));
- if (!cmd)
+ cmdarg = (struct worker_arg *)malloc(sizeof(struct worker_arg));
+ if (!cmdarg)
{
- DPRINTF(E_LOG, L_MAIN, "Could not allocate worker_command\n");
+ DPRINTF(E_LOG, L_MAIN, "Could not allocate worker_arg\n");
return;
}
- memset(cmd, 0, sizeof(struct worker_command));
+ memset(cmdarg, 0, sizeof(struct worker_arg));
argcpy = malloc(arg_size);
if (!argcpy)
@@ -273,15 +160,11 @@ worker_execute(void (*cb)(void *), void *cb_arg, size_t arg_size, int delay)
memcpy(argcpy, cb_arg, arg_size);
- cmd->nonblock = 1;
- cmd->func = execute;
- cmd->arg.cb = cb;
- cmd->arg.cb_arg = argcpy;
- cmd->arg.delay = delay;
+ cmdarg->cb = cb;
+ cmdarg->cb_arg = argcpy;
+ cmdarg->delay = delay;
- nonblock_command(cmd);
-
- return;
+ commands_exec_async(cmdbase, execute, cmdarg);
}
int
@@ -289,28 +172,6 @@ worker_init(void)
{
int ret;
-#ifdef HAVE_PIPE2
- ret = pipe2(g_exit_pipe, O_CLOEXEC);
-#else
- ret = pipe(g_exit_pipe);
-#endif
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_MAIN, "Could not create pipe: %s\n", strerror(errno));
- goto exit_fail;
- }
-
-#ifdef HAVE_PIPE2
- ret = pipe2(g_cmd_pipe, O_CLOEXEC);
-#else
- ret = pipe(g_cmd_pipe);
-#endif
- if (ret < 0)
- {
- DPRINTF(E_LOG, L_MAIN, "Could not create command pipe: %s\n", strerror(errno));
- goto cmd_fail;
- }
-
evbase_worker = event_base_new();
if (!evbase_worker)
{
@@ -318,22 +179,7 @@ worker_init(void)
goto evbase_fail;
}
- g_exitev = event_new(evbase_worker, g_exit_pipe[0], EV_READ, exit_cb, NULL);
- if (!g_exitev)
- {
- DPRINTF(E_LOG, L_MAIN, "Could not create exit event\n");
- goto evnew_fail;
- }
-
- g_cmdev = event_new(evbase_worker, g_cmd_pipe[0], EV_READ, command_cb, NULL);
- if (!g_cmdev)
- {
- DPRINTF(E_LOG, L_MAIN, "Could not create cmd event\n");
- goto evnew_fail;
- }
-
- event_add(g_exitev, NULL);
- event_add(g_cmdev, NULL);
+ cmdbase = commands_base_new(evbase_worker, NULL);
ret = pthread_create(&tid_worker, NULL, worker, NULL);
if (ret < 0)
@@ -352,19 +198,11 @@ worker_init(void)
return 0;
thread_fail:
- evnew_fail:
+ commands_base_free(cmdbase);
event_base_free(evbase_worker);
evbase_worker = NULL;
evbase_fail:
- close(g_cmd_pipe[0]);
- close(g_cmd_pipe[1]);
-
- cmd_fail:
- close(g_exit_pipe[0]);
- close(g_exit_pipe[1]);
-
- exit_fail:
return -1;
}
@@ -373,7 +211,8 @@ worker_deinit(void)
{
int ret;
- thread_exit();
+ g_initialized = 0;
+ commands_base_destroy(cmdbase);
ret = pthread_join(tid_worker, NULL);
if (ret != 0)
@@ -384,10 +223,4 @@ worker_deinit(void)
// Free event base (should free events too)
event_base_free(evbase_worker);
-
- // Close pipes
- close(g_cmd_pipe[0]);
- close(g_cmd_pipe[1]);
- close(g_exit_pipe[0]);
- close(g_exit_pipe[1]);
}
--
forked-daapd packaging
More information about the pkg-multimedia-commits
mailing list