mirror of
https://github.com/owntone/owntone-server.git
synced 2025-02-10 05:08:11 -05:00
Merge branch 'atv_verification1'
This commit is contained in:
commit
44fad55b0b
11
.travis.yml
11
.travis.yml
@ -3,12 +3,11 @@ sudo: required
|
|||||||
dist: trusty
|
dist: trusty
|
||||||
env:
|
env:
|
||||||
matrix:
|
matrix:
|
||||||
- CFG=""
|
- CFG="--disable-verification"
|
||||||
- CFG="--enable-lastfm"
|
- CFG="--enable-lastfm --disable-verification"
|
||||||
- CFG="--enable-itunes"
|
- CFG="--enable-spotify --disable-verification"
|
||||||
- CFG="--enable-spotify"
|
- CFG="--enable-chromecast --disable-verification"
|
||||||
- CFG="--enable-chromecast"
|
- CFG="--with-pulseaudio --disable-verification"
|
||||||
- CFG="--with-pulseaudio"
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- autoreconf -fi
|
- autoreconf -fi
|
||||||
|
42
INSTALL
42
INSTALL
@ -31,16 +31,17 @@ libraries:
|
|||||||
antlr3 libantlr3c-dev libconfuse-dev libunistring-dev libsqlite3-dev \
|
antlr3 libantlr3c-dev libconfuse-dev libunistring-dev libsqlite3-dev \
|
||||||
libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \
|
libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \
|
||||||
libasound2-dev libmxml-dev libgcrypt11-dev libavahi-client-dev zlib1g-dev \
|
libasound2-dev libmxml-dev libgcrypt11-dev libavahi-client-dev zlib1g-dev \
|
||||||
libevent-dev
|
libevent-dev libplist-dev libsodium-dev
|
||||||
|
|
||||||
Optional packages:
|
Optional packages:
|
||||||
|
|
||||||
Feature | Configure argument | Packages
|
Feature | Configure argument | Packages
|
||||||
-----------|---------------------|---------------------------------------------
|
--------------------|------------------------|---------------------------------------------
|
||||||
Chromecast | --enable-chromecast | libjson-c-dev libgnutls-dev libprotobuf-c-dev
|
Chromecast | --enable-chromecast | libjson-c-dev libgnutls-dev libprotobuf-c-dev
|
||||||
LastFM | --enable-lastfm | libcurl4-gnutls-dev OR libcurl4-openssl-dev
|
LastFM | --enable-lastfm | libcurl4-gnutls-dev OR libcurl4-openssl-dev
|
||||||
iTunes XML | --enable-itunes | libplist-dev
|
iTunes XML | --disable-itunes | libplist-dev
|
||||||
Pulseaudio | --with-pulseaudio | libpulse-dev
|
Device verification | --disable-verification | libplist-dev libsodium-dev
|
||||||
|
Pulseaudio | --with-pulseaudio | libpulse-dev
|
||||||
|
|
||||||
Note that while forked-daapd will work with versions of libevent between 2.0.0
|
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
|
and 2.1.3, it is recommended to use 2.1.4+. Otherwise you may not have support
|
||||||
@ -68,7 +69,8 @@ will need ffmpeg. You can google how to do that. Then run:
|
|||||||
sudo yum install \
|
sudo yum install \
|
||||||
git automake autoconf gettext-devel gperf gawk libtool \
|
git automake autoconf gettext-devel gperf gawk libtool \
|
||||||
sqlite-devel libconfuse-devel libunistring-devel mxml-devel libevent-devel \
|
sqlite-devel libconfuse-devel libunistring-devel mxml-devel libevent-devel \
|
||||||
avahi-devel libgcrypt-devel zlib-devel alsa-lib-devel ffmpeg-devel
|
avahi-devel libgcrypt-devel zlib-devel alsa-lib-devel ffmpeg-devel \
|
||||||
|
libplist-devel libsodium-devel
|
||||||
|
|
||||||
Clone the forked-daapd repo:
|
Clone the forked-daapd repo:
|
||||||
|
|
||||||
@ -137,12 +139,13 @@ Add the following to .bashrc:
|
|||||||
|
|
||||||
Optional features require the following additional ports:
|
Optional features require the following additional ports:
|
||||||
|
|
||||||
Feature | Configure argument | Ports
|
Feature | Configure argument | Ports
|
||||||
-----------|---------------------|--------------------------------------------
|
--------------------|------------------------|--------------------------------------------
|
||||||
Chromecast | --enable-chromecast | json-c gnutls protobuf-c
|
Chromecast | --enable-chromecast | json-c gnutls protobuf-c
|
||||||
LastFM | --enable-lastfm | curl
|
LastFM | --enable-lastfm | curl
|
||||||
iTunes XML | --enable-itunes | libplist
|
iTunes XML | --disable-itunes | libplist
|
||||||
Pulseaudio | --with-pulseaudio | pulseaudio
|
Device verification | --disable-verification | libplist libsodium
|
||||||
|
Pulseaudio | --with-pulseaudio | pulseaudio
|
||||||
|
|
||||||
Clone the forked-daapd repo:
|
Clone the forked-daapd repo:
|
||||||
git clone https://github.com/ejurgensen/forked-daapd.git
|
git clone https://github.com/ejurgensen/forked-daapd.git
|
||||||
@ -219,8 +222,10 @@ Libraries:
|
|||||||
often already installed as part of your distro
|
often already installed as part of your distro
|
||||||
- libpulse (optional - Pulseaudio local audio)
|
- libpulse (optional - Pulseaudio local audio)
|
||||||
from <https://www.freedesktop.org/wiki/Software/PulseAudio/Download/>
|
from <https://www.freedesktop.org/wiki/Software/PulseAudio/Download/>
|
||||||
- libplist 0.16+ (optional - iTunes XML support)
|
- libplist 0.16+ (optional - iTunes XML support and Apple TV device verification)
|
||||||
from <http://github.com/JonathanBeck/libplist/downloads>
|
from <http://github.com/JonathanBeck/libplist/downloads>
|
||||||
|
- libsodium (optional - Apple TV device verification)
|
||||||
|
from <https://download.libsodium.org/doc/>
|
||||||
- libspotify (optional - Spotify support)
|
- libspotify (optional - Spotify support)
|
||||||
from <https://developer.spotify.com>
|
from <https://developer.spotify.com>
|
||||||
- libcurl (optional - LastFM support)
|
- libcurl (optional - LastFM support)
|
||||||
@ -278,12 +283,15 @@ disabled).
|
|||||||
Support for LastFM scrobbling is optional. Use --enable-lastfm to enable this
|
Support for LastFM scrobbling is optional. Use --enable-lastfm to enable this
|
||||||
feature.
|
feature.
|
||||||
|
|
||||||
Support for iTunes Music Library XML format is optional. Use --enable-itunes
|
Support for iTunes Music Library XML format is optional. Use --disable-itunes
|
||||||
to enable this feature.
|
to disable this feature.
|
||||||
|
|
||||||
Support for the MPD protocol is optional. Use --disable-mpd to disable this
|
Support for the MPD protocol is optional. Use --disable-mpd to disable this
|
||||||
feature.
|
feature.
|
||||||
|
|
||||||
|
Support for Apple TV device verification is optional. Use --disable-verification
|
||||||
|
to disable this feature.
|
||||||
|
|
||||||
Support for Chromecast devices is optional. Use --enable-chromecast to enable
|
Support for Chromecast devices is optional. Use --enable-chromecast to enable
|
||||||
this feature.
|
this feature.
|
||||||
|
|
||||||
|
39
README.md
39
README.md
@ -8,9 +8,6 @@ MPD clients, Chromecast, network streaming, internet radio, Spotify and LastFM.
|
|||||||
|
|
||||||
It does not support streaming video by AirPlay nor Chromecast.
|
It does not support streaming video by AirPlay nor Chromecast.
|
||||||
|
|
||||||
**Note: Airplay to Apple TV's that are updated to tvOS 10.2 is currently broken,
|
|
||||||
because these Apple TV's require device verification.**
|
|
||||||
|
|
||||||
DAAP stands for Digital Audio Access Protocol, and is the protocol used
|
DAAP stands for Digital Audio Access Protocol, and is the protocol used
|
||||||
by iTunes and friends to share/stream media libraries over the network.
|
by iTunes and friends to share/stream media libraries over the network.
|
||||||
|
|
||||||
@ -128,16 +125,8 @@ Or, if that doesn't work:
|
|||||||
doesn't work properly on your network.
|
doesn't work properly on your network.
|
||||||
|
|
||||||
4. Prepare a text file with a filename ending with .remote; the filename
|
4. Prepare a text file with a filename ending with .remote; the filename
|
||||||
doesn't matter, only the .remote ending does. This file must contain
|
doesn't matter, only the .remote ending does. This first line in the file
|
||||||
two lines: the first line is the name of your iPod/iPhone/iPad, the second
|
must contain the 4-digit pairing code displayed by Remote.
|
||||||
is the 4-digit pairing code displayed by Remote.
|
|
||||||
|
|
||||||
If your iPod/iPhone/iPad is named "Foobar" and Remote gives you the pairing
|
|
||||||
code 5387, the file content must be:
|
|
||||||
```
|
|
||||||
Foobar
|
|
||||||
5387
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Move this file somewhere in your library
|
5. Move this file somewhere in your library
|
||||||
|
|
||||||
@ -154,12 +143,11 @@ forked-daapd does not get notified about new files on network mounts, so the
|
|||||||
Solution: Set two library paths in the config, and add the .remote file to the
|
Solution: Set two library paths in the config, and add the .remote file to the
|
||||||
local path. See [Libraries on network mounts](#libraries-on-network-mounts).
|
local path. See [Libraries on network mounts](#libraries-on-network-mounts).
|
||||||
|
|
||||||
#### You did not enter the correct name or pairing code
|
#### You did not enter the correct pairing code
|
||||||
You will see an error in the log about pairing failure with a HTTP response code
|
You will see an error in the log about pairing failure with a HTTP response code
|
||||||
that is *not* 0.
|
that is *not* 0.
|
||||||
Solution: Copy-paste the name to be sure to get specials characters right. You
|
Solution: Try again. You can also try the pairinghelper script located in the
|
||||||
can also try the pairinghelper script located in the scripts-folder of the
|
scripts-folder of the source or the mpc method described above.
|
||||||
source.
|
|
||||||
|
|
||||||
#### No response from Remote, possibly a network issue
|
#### No response from Remote, possibly a network issue
|
||||||
If you see an error in the log with either:
|
If you see an error in the log with either:
|
||||||
@ -191,9 +179,8 @@ Otherwise try using avahi-browse for troubleshooting:
|
|||||||
|
|
||||||
Hit Ctrl-C to terminate avahi-browse.
|
Hit Ctrl-C to terminate avahi-browse.
|
||||||
|
|
||||||
The name of your iPod/iPhone/iPad is the value of the DvNm field above. In this
|
To check for network issues you can try to connect to address and port with
|
||||||
example, the correct value is Foobar. To check for network issues you can try to
|
telnet.
|
||||||
connect to address and port with telnet.
|
|
||||||
|
|
||||||
### Selecting output devices
|
### Selecting output devices
|
||||||
|
|
||||||
@ -217,6 +204,18 @@ devices that are password-protected, the device's AirPlay name and password
|
|||||||
must be given in the configuration file. See the sample configuration file
|
must be given in the configuration file. See the sample configuration file
|
||||||
for the syntax.
|
for the syntax.
|
||||||
|
|
||||||
|
If your Apple TV requires device verification (always required by Apple TV4 with
|
||||||
|
tvOS 10.2) then you must select the device for playback, whereafter a PIN will
|
||||||
|
be displayed by the Apple TV. The do either of the following:
|
||||||
|
|
||||||
|
Alternative 1: Create a file ending with .verification in your music library,
|
||||||
|
input the PIN, and save the file. Forked-daapd will now pair with the device,
|
||||||
|
and if you select the device again, playback should start.
|
||||||
|
Alternative 2: Run "mpc sendmessage verification [PIN]" (requires the mpc tool),
|
||||||
|
and then select the device again. Playback should start.
|
||||||
|
|
||||||
|
For troubleshooting, see [using Remote](#using-remote).
|
||||||
|
|
||||||
|
|
||||||
## Chromecast
|
## Chromecast
|
||||||
|
|
||||||
|
31
configure.ac
31
configure.ac
@ -273,6 +273,14 @@ dnl Build with libcurl
|
|||||||
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libcurl support], [libcurl], [LIBCURL],
|
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libcurl support], [libcurl], [LIBCURL],
|
||||||
[libcurl], [curl_global_init], [curl/curl.h])
|
[libcurl], [curl_global_init], [curl/curl.h])
|
||||||
|
|
||||||
|
dnl Build with libsodium
|
||||||
|
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libsodium support], [libsodium], [LIBSODIUM],
|
||||||
|
[libsodium], [sodium_init], [sodium.h])
|
||||||
|
|
||||||
|
dnl Build with libplist
|
||||||
|
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libplist support], [libplist], [LIBPLIST],
|
||||||
|
[libplist >= 0.16], [plist_dict_get_item], [plist/plist.h])
|
||||||
|
|
||||||
dnl Build with libevent_pthreads
|
dnl Build with libevent_pthreads
|
||||||
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libevent_pthreads support],
|
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libevent_pthreads support],
|
||||||
[libevent_pthreads], [LIBEVENT_PTHREADS], [libevent_pthreads],
|
[libevent_pthreads], [LIBEVENT_PTHREADS], [libevent_pthreads],
|
||||||
@ -300,12 +308,6 @@ AS_IF([[test "x$with_avahi" = "xno"]],
|
|||||||
[AC_MSG_ERROR([[Avahi client or Bonjour DNS_SD required, please install one.]])])])
|
[AC_MSG_ERROR([[Avahi client or Bonjour DNS_SD required, please install one.]])])])
|
||||||
AM_CONDITIONAL([COND_AVAHI], [[test "x$with_avahi" = "xyes"]])
|
AM_CONDITIONAL([COND_AVAHI], [[test "x$with_avahi" = "xyes"]])
|
||||||
|
|
||||||
dnl iTunes playlists with libplist
|
|
||||||
FORK_ARG_ENABLE([iTunes Music Library XML support], [itunes], [ITUNES],
|
|
||||||
[FORK_MODULES_CHECK([FORKED_OPTS], [LIBPLIST], [libplist >= 0.16],
|
|
||||||
[plist_dict_get_item], [plist/plist.h])])
|
|
||||||
AM_CONDITIONAL([COND_ITUNES], [[test "x$enable_itunes" = "xyes"]])
|
|
||||||
|
|
||||||
dnl Spotify with dynamic linking to libspotify
|
dnl Spotify with dynamic linking to libspotify
|
||||||
FORK_ARG_ENABLE([Spotify support], [spotify], [SPOTIFY],
|
FORK_ARG_ENABLE([Spotify support], [spotify], [SPOTIFY],
|
||||||
[AS_IF([[test "x$with_json" = "xno"]],
|
[AS_IF([[test "x$with_json" = "xno"]],
|
||||||
@ -352,10 +354,27 @@ FORK_ARG_ENABLE([Chromecast support], [chromecast], [CHROMECAST],
|
|||||||
AM_CONDITIONAL([COND_CHROMECAST], [[test "x$enable_chromecast" = "xyes"]])
|
AM_CONDITIONAL([COND_CHROMECAST], [[test "x$enable_chromecast" = "xyes"]])
|
||||||
AM_CONDITIONAL([COND_PROTOBUF_OLD], [[test "x$protobuf_old" = "xyes"]])
|
AM_CONDITIONAL([COND_PROTOBUF_OLD], [[test "x$protobuf_old" = "xyes"]])
|
||||||
|
|
||||||
|
dnl iTunes playlists with libplist
|
||||||
|
FORK_ARG_DISABLE([iTunes Music Library XML support], [itunes], [ITUNES],
|
||||||
|
[AS_IF([[test "x$with_libplist" = "xno"]],
|
||||||
|
[AC_MSG_ERROR([[iTunes Music Library XML support requires libplist]])])
|
||||||
|
])
|
||||||
|
AM_CONDITIONAL([COND_ITUNES], [[test "x$enable_itunes" = "xyes"]])
|
||||||
|
|
||||||
dnl MPD support
|
dnl MPD support
|
||||||
FORK_ARG_DISABLE([MPD client protocol support], [mpd], [MPD])
|
FORK_ARG_DISABLE([MPD client protocol support], [mpd], [MPD])
|
||||||
AM_CONDITIONAL([COND_MPD], [[test "x$enable_mpd" = "xyes"]])
|
AM_CONDITIONAL([COND_MPD], [[test "x$enable_mpd" = "xyes"]])
|
||||||
|
|
||||||
|
dnl Apple device verification
|
||||||
|
FORK_ARG_DISABLE([Apple TV device verification], [verification], [RAOP_VERIFICATION],
|
||||||
|
[
|
||||||
|
AS_IF([[test "x$with_libsodium" = "xno"]],
|
||||||
|
[AC_MSG_ERROR([[Apple TV device verification requires libsodium]])])
|
||||||
|
AS_IF([[test "x$with_libplist" = "xno"]],
|
||||||
|
[AC_MSG_ERROR([[Apple TV device verification requires libplist]])])
|
||||||
|
])
|
||||||
|
AM_CONDITIONAL([COND_RAOP_VERIFICATION], [[test "x$enable_verification" = "xyes"]])
|
||||||
|
|
||||||
dnl Defining users and groups
|
dnl Defining users and groups
|
||||||
AC_ARG_WITH([daapd_user],
|
AC_ARG_WITH([daapd_user],
|
||||||
[AS_HELP_STRING([--with-daapd-user=USER],
|
[AS_HELP_STRING([--with-daapd-user=USER],
|
||||||
|
@ -95,7 +95,7 @@ if [ -z "$pin" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Writing pair.remote to $library_path..."
|
echo "Writing pair.remote to $library_path..."
|
||||||
printf "$remote\n$pin" > "$rf"
|
printf "$pin" > "$rf"
|
||||||
if [ ! -f "$rf" ]; then
|
if [ ! -f "$rf" ]; then
|
||||||
echo "Unable to create '$rf' - check directory permissions"
|
echo "Unable to create '$rf' - check directory permissions"
|
||||||
exit 1
|
exit 1
|
||||||
@ -106,7 +106,7 @@ n=20
|
|||||||
echo "Waiting for pairing to complete (up to $n secs)..."
|
echo "Waiting for pairing to complete (up to $n secs)..."
|
||||||
while [ $n -gt 0 ]; do
|
while [ $n -gt 0 ]; do
|
||||||
n=`expr $n - 1`
|
n=`expr $n - 1`
|
||||||
result=`tail -1000 "$logfile" | sed -n "/.*remote:/ s,.*remote: ,,p" | awk '/^Discovered remote/{ f="" } /^Read Remote pairing data/ { f=$0; } END { print f }'`
|
result=`tail -1000 "$logfile" | sed -n "/.*remote:/ s,.*remote: ,,p" | awk '/^Discovered remote/{ f="" } /^Kickoff pairing with pin/ { f=$0; } END { print f }'`
|
||||||
[ -n "$result" ] && break
|
[ -n "$result" ] && break
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
@ -25,6 +25,10 @@ if COND_MPD
|
|||||||
MPD_SRC=mpd.c mpd.h
|
MPD_SRC=mpd.c mpd.h
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if COND_RAOP_VERIFICATION
|
||||||
|
RAOP_VERIFICATION_SRC=outputs/raop_verification.c outputs/raop_verification.h
|
||||||
|
endif
|
||||||
|
|
||||||
if COND_ALSA
|
if COND_ALSA
|
||||||
ALSA_SRC=outputs/alsa.c
|
ALSA_SRC=outputs/alsa.c
|
||||||
endif
|
endif
|
||||||
@ -112,7 +116,8 @@ forked_daapd_SOURCES = main.c \
|
|||||||
input.h input.c \
|
input.h input.c \
|
||||||
inputs/file_http.c inputs/pipe.c \
|
inputs/file_http.c inputs/pipe.c \
|
||||||
outputs.h outputs.c \
|
outputs.h outputs.c \
|
||||||
outputs/raop.c outputs/streaming.c outputs/dummy.c outputs/fifo.c \
|
outputs/raop.c $(RAOP_VERIFICATION_SRC) \
|
||||||
|
outputs/streaming.c outputs/dummy.c outputs/fifo.c \
|
||||||
$(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \
|
$(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \
|
||||||
evrtsp/rtsp.c evrtsp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \
|
evrtsp/rtsp.c evrtsp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \
|
||||||
$(SPOTIFY_SRC) \
|
$(SPOTIFY_SRC) \
|
||||||
|
25
src/db.c
25
src/db.c
@ -3824,21 +3824,21 @@ db_admin_delete(const char *key)
|
|||||||
|
|
||||||
/* Speakers */
|
/* Speakers */
|
||||||
int
|
int
|
||||||
db_speaker_save(uint64_t id, int selected, int volume, const char *name)
|
db_speaker_save(struct output_device *device)
|
||||||
{
|
{
|
||||||
#define Q_TMPL "INSERT OR REPLACE INTO speakers (id, selected, volume, name) VALUES (%" PRIi64 ", %d, %d, '%q');"
|
#define Q_TMPL "INSERT OR REPLACE INTO speakers (id, selected, volume, name, auth_key) VALUES (%" PRIi64 ", %d, %d, %Q, %Q);"
|
||||||
char *query;
|
char *query;
|
||||||
|
|
||||||
query = sqlite3_mprintf(Q_TMPL, id, selected, volume, name);
|
query = sqlite3_mprintf(Q_TMPL, device->id, device->selected, device->volume, device->name, device->auth_key);
|
||||||
|
|
||||||
return db_query_run(query, 1, 0);
|
return db_query_run(query, 1, 0);
|
||||||
#undef Q_TMPL
|
#undef Q_TMPL
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
db_speaker_get(uint64_t id, int *selected, int *volume)
|
db_speaker_get(struct output_device *device, uint64_t id)
|
||||||
{
|
{
|
||||||
#define Q_TMPL "SELECT s.selected, s.volume FROM speakers s WHERE s.id = %" PRIi64 ";"
|
#define Q_TMPL "SELECT s.selected, s.volume, s.name, s.auth_key FROM speakers s WHERE s.id = %" PRIi64 ";"
|
||||||
sqlite3_stmt *stmt;
|
sqlite3_stmt *stmt;
|
||||||
char *query;
|
char *query;
|
||||||
int ret;
|
int ret;
|
||||||
@ -3847,7 +3847,6 @@ db_speaker_get(uint64_t id, int *selected, int *volume)
|
|||||||
if (!query)
|
if (!query)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3857,7 +3856,6 @@ db_speaker_get(uint64_t id, int *selected, int *volume)
|
|||||||
if (ret != SQLITE_OK)
|
if (ret != SQLITE_OK)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
||||||
|
|
||||||
ret = -1;
|
ret = -1;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
@ -3867,15 +3865,20 @@ db_speaker_get(uint64_t id, int *selected, int *volume)
|
|||||||
{
|
{
|
||||||
if (ret != SQLITE_DONE)
|
if (ret != SQLITE_DONE)
|
||||||
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
ret = -1;
|
ret = -1;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
*selected = sqlite3_column_int(stmt, 0);
|
device->id = id;
|
||||||
*volume = sqlite3_column_int(stmt, 1);
|
device->selected = sqlite3_column_int(stmt, 0);
|
||||||
|
device->volume = sqlite3_column_int(stmt, 1);
|
||||||
|
|
||||||
|
free(device->name);
|
||||||
|
device->name = safe_strdup((char *)sqlite3_column_text(stmt, 2));
|
||||||
|
|
||||||
|
free(device->auth_key);
|
||||||
|
device->auth_key = safe_strdup((char *)sqlite3_column_text(stmt, 3));
|
||||||
|
|
||||||
#ifdef DB_PROFILE
|
#ifdef DB_PROFILE
|
||||||
while (db_blocking_step(stmt) == SQLITE_ROW)
|
while (db_blocking_step(stmt) == SQLITE_ROW)
|
||||||
|
7
src/db.h
7
src/db.h
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
#include "outputs.h"
|
||||||
|
|
||||||
enum index_type {
|
enum index_type {
|
||||||
I_NONE,
|
I_NONE,
|
||||||
@ -675,12 +676,12 @@ db_admin_get(const char *key);
|
|||||||
int
|
int
|
||||||
db_admin_delete(const char *key);
|
db_admin_delete(const char *key);
|
||||||
|
|
||||||
/* Speakers */
|
/* Speakers/outputs */
|
||||||
int
|
int
|
||||||
db_speaker_save(uint64_t id, int selected, int volume, const char *name);
|
db_speaker_save(struct output_device *device);
|
||||||
|
|
||||||
int
|
int
|
||||||
db_speaker_get(uint64_t id, int *selected, int *volume);
|
db_speaker_get(struct output_device *device, uint64_t id);
|
||||||
|
|
||||||
void
|
void
|
||||||
db_speaker_clear_all(void);
|
db_speaker_clear_all(void);
|
||||||
|
@ -140,7 +140,8 @@
|
|||||||
" id INTEGER PRIMARY KEY NOT NULL," \
|
" id INTEGER PRIMARY KEY NOT NULL," \
|
||||||
" selected INTEGER NOT NULL," \
|
" selected INTEGER NOT NULL," \
|
||||||
" volume INTEGER NOT NULL," \
|
" volume INTEGER NOT NULL," \
|
||||||
" name VARCHAR(255) DEFAULT NULL" \
|
" name VARCHAR(255) DEFAULT NULL," \
|
||||||
|
" auth_key VARCHAR(2048) DEFAULT NULL" \
|
||||||
");"
|
");"
|
||||||
|
|
||||||
#define T_INOTIFY \
|
#define T_INOTIFY \
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
* is a major upgrade. In other words minor version upgrades permit downgrading
|
* is a major upgrade. In other words minor version upgrades permit downgrading
|
||||||
* forked-daapd after the database was upgraded. */
|
* forked-daapd after the database was upgraded. */
|
||||||
#define SCHEMA_VERSION_MAJOR 19
|
#define SCHEMA_VERSION_MAJOR 19
|
||||||
#define SCHEMA_VERSION_MINOR 03
|
#define SCHEMA_VERSION_MINOR 04
|
||||||
|
|
||||||
int
|
int
|
||||||
db_init_indices(sqlite3 *hdl);
|
db_init_indices(sqlite3 *hdl);
|
||||||
|
@ -1547,6 +1547,23 @@ static const struct db_upgrade_query db_upgrade_v1903_queries[] =
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#define U_V1904_ALTER_SPEAKERS_ADD_AUTHKEY \
|
||||||
|
"ALTER TABLE speakers ADD COLUMN auth_key VARCHAR(2048) DEFAULT NULL;"
|
||||||
|
|
||||||
|
#define U_V1904_SCVER_MAJOR \
|
||||||
|
"UPDATE admin SET value = '19' WHERE key = 'schema_version_major';"
|
||||||
|
#define U_V1904_SCVER_MINOR \
|
||||||
|
"UPDATE admin SET value = '04' WHERE key = 'schema_version_minor';"
|
||||||
|
|
||||||
|
static const struct db_upgrade_query db_upgrade_v1904_queries[] =
|
||||||
|
{
|
||||||
|
{ U_V1904_ALTER_SPEAKERS_ADD_AUTHKEY, "alter table speakers add column auth_key" },
|
||||||
|
|
||||||
|
{ U_V1904_SCVER_MAJOR, "set schema_version_major to 19" },
|
||||||
|
{ U_V1904_SCVER_MINOR, "set schema_version_minor to 04" },
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
db_upgrade(sqlite3 *hdl, int db_ver)
|
db_upgrade(sqlite3 *hdl, int db_ver)
|
||||||
{
|
{
|
||||||
@ -1678,6 +1695,13 @@ db_upgrade(sqlite3 *hdl, int db_ver)
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
/* FALLTHROUGH */
|
||||||
|
|
||||||
|
case 1903:
|
||||||
|
ret = db_generic_upgrade(hdl, db_upgrade_v1904_queries, sizeof(db_upgrade_v1904_queries) / sizeof(db_upgrade_v1904_queries[0]));
|
||||||
|
if (ret < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -46,6 +46,7 @@ extern "C" {
|
|||||||
/* Response codes */
|
/* Response codes */
|
||||||
#define RTSP_OK 200
|
#define RTSP_OK 200
|
||||||
#define RTSP_UNAUTHORIZED 401
|
#define RTSP_UNAUTHORIZED 401
|
||||||
|
#define RTSP_FORBIDDEN 403
|
||||||
|
|
||||||
struct evrtsp_connection;
|
struct evrtsp_connection;
|
||||||
|
|
||||||
@ -62,6 +63,7 @@ enum evrtsp_cmd_type {
|
|||||||
EVRTSP_REQ_SET_PARAMETER,
|
EVRTSP_REQ_SET_PARAMETER,
|
||||||
EVRTSP_REQ_FLUSH,
|
EVRTSP_REQ_FLUSH,
|
||||||
EVRTSP_REQ_TEARDOWN,
|
EVRTSP_REQ_TEARDOWN,
|
||||||
|
EVRTSP_REQ_POST,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum evrtsp_request_kind { EVRTSP_REQUEST, EVRTSP_RESPONSE };
|
enum evrtsp_request_kind { EVRTSP_REQUEST, EVRTSP_RESPONSE };
|
||||||
|
@ -260,6 +260,10 @@ evrtsp_method(enum evrtsp_cmd_type type)
|
|||||||
method = "TEARDOWN";
|
method = "TEARDOWN";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case EVRTSP_REQ_POST:
|
||||||
|
method = "POST";
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
method = NULL;
|
method = NULL;
|
||||||
break;
|
break;
|
||||||
|
140
src/lastfm.c
140
src/lastfm.c
@ -63,108 +63,6 @@ static char *lastfm_session_key = NULL;
|
|||||||
|
|
||||||
/* --------------------------------- HELPERS ------------------------------- */
|
/* --------------------------------- HELPERS ------------------------------- */
|
||||||
|
|
||||||
/* Reads a LastFM credentials file (1st line username, 2nd line password) */
|
|
||||||
static int
|
|
||||||
credentials_read(char *path, char **username, char **password)
|
|
||||||
{
|
|
||||||
FILE *fp;
|
|
||||||
char *u;
|
|
||||||
char *p;
|
|
||||||
char buf[256];
|
|
||||||
int len;
|
|
||||||
|
|
||||||
fp = fopen(path, "rb");
|
|
||||||
if (!fp)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Could not open lastfm credentials file %s: %s\n", path, strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
u = fgets(buf, sizeof(buf), fp);
|
|
||||||
if (!u)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Empty lastfm credentials file %s\n", path);
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = strlen(u);
|
|
||||||
if (buf[len - 1] != '\n')
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Invalid lastfm credentials file %s: username name too long or missing password\n", path);
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (len)
|
|
||||||
{
|
|
||||||
if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
|
|
||||||
{
|
|
||||||
buf[len - 1] = '\0';
|
|
||||||
len--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!len)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Invalid lastfm credentials file %s: empty line where username expected\n", path);
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
u = strdup(buf);
|
|
||||||
if (!u)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Out of memory for username while reading %s\n", path);
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
p = fgets(buf, sizeof(buf), fp);
|
|
||||||
fclose(fp);
|
|
||||||
if (!p)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Invalid lastfm credentials file %s: no password\n", path);
|
|
||||||
|
|
||||||
free(u);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = strlen(p);
|
|
||||||
|
|
||||||
while (len)
|
|
||||||
{
|
|
||||||
if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
|
|
||||||
{
|
|
||||||
buf[len - 1] = '\0';
|
|
||||||
len--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
p = strdup(buf);
|
|
||||||
if (!p)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Out of memory for password while reading %s\n", path);
|
|
||||||
|
|
||||||
free(u);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
DPRINTF(E_LOG, L_LASTFM, "lastfm credentials file OK, logging in with username %s\n", u);
|
|
||||||
|
|
||||||
*username = u;
|
|
||||||
*password = p;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Creates an md5 signature of the concatenated parameters and adds it to keyval */
|
/* Creates an md5 signature of the concatenated parameters and adds it to keyval */
|
||||||
static int
|
static int
|
||||||
@ -417,59 +315,39 @@ scrobble(int id)
|
|||||||
/* ---------------------------- Our lastfm API --------------------------- */
|
/* ---------------------------- Our lastfm API --------------------------- */
|
||||||
|
|
||||||
/* Thread: filescanner */
|
/* Thread: filescanner */
|
||||||
int
|
void
|
||||||
lastfm_login(char *path)
|
lastfm_login(char **arglist)
|
||||||
{
|
{
|
||||||
struct keyval *kv;
|
struct keyval *kv;
|
||||||
char *username;
|
|
||||||
char *password;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_LASTFM, "Got LastFM login request\n");
|
DPRINTF(E_LOG, L_LASTFM, "LastFM credentials file OK, logging in with username %s\n", arglist[0]);
|
||||||
|
|
||||||
// Delete any existing session key
|
// Delete any existing session key
|
||||||
if (lastfm_session_key)
|
free(lastfm_session_key);
|
||||||
free(lastfm_session_key);
|
|
||||||
|
|
||||||
lastfm_session_key = NULL;
|
lastfm_session_key = NULL;
|
||||||
|
|
||||||
db_admin_delete("lastfm_sk");
|
db_admin_delete("lastfm_sk");
|
||||||
|
|
||||||
// Read the credentials file
|
|
||||||
ret = credentials_read(path, &username, &password);
|
|
||||||
if (ret < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Enable LastFM now that we got a login attempt
|
// Enable LastFM now that we got a login attempt
|
||||||
lastfm_disabled = 0;
|
lastfm_disabled = 0;
|
||||||
|
|
||||||
kv = keyval_alloc();
|
kv = keyval_alloc();
|
||||||
if (!kv)
|
if (!kv)
|
||||||
{
|
return;
|
||||||
ret = -1;
|
|
||||||
goto out_free_credentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = ( (keyval_add(kv, "api_key", lastfm_api_key) == 0) &&
|
ret = ( (keyval_add(kv, "api_key", lastfm_api_key) == 0) &&
|
||||||
(keyval_add(kv, "username", username) == 0) &&
|
(keyval_add(kv, "username", arglist[0]) == 0) &&
|
||||||
(keyval_add(kv, "password", password) == 0) );
|
(keyval_add(kv, "password", arglist[1]) == 0) );
|
||||||
if (!ret)
|
if (!ret)
|
||||||
{
|
goto out_free_kv;
|
||||||
ret = -1;
|
|
||||||
goto out_free_kv;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the login request
|
// Send the login request
|
||||||
ret = request_post("auth.getMobileSession", kv, 1);
|
request_post("auth.getMobileSession", kv, 1);
|
||||||
|
|
||||||
out_free_kv:
|
out_free_kv:
|
||||||
keyval_clear(kv);
|
keyval_clear(kv);
|
||||||
free(kv);
|
free(kv);
|
||||||
out_free_credentials:
|
|
||||||
free(username);
|
|
||||||
free(password);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Thread: worker */
|
/* Thread: worker */
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
#ifndef __LASTFM_H__
|
#ifndef __LASTFM_H__
|
||||||
#define __LASTFM_H__
|
#define __LASTFM_H__
|
||||||
|
|
||||||
int
|
void
|
||||||
lastfm_login(char *path);
|
lastfm_login(char **arglist);
|
||||||
|
|
||||||
int
|
int
|
||||||
lastfm_scrobble(int id);
|
lastfm_scrobble(int id);
|
||||||
|
@ -92,6 +92,7 @@ enum file_type {
|
|||||||
FILE_ITUNES,
|
FILE_ITUNES,
|
||||||
FILE_ARTWORK,
|
FILE_ARTWORK,
|
||||||
FILE_CTRL_REMOTE,
|
FILE_CTRL_REMOTE,
|
||||||
|
FILE_CTRL_RAOP_VERIFICATION,
|
||||||
FILE_CTRL_LASTFM,
|
FILE_CTRL_LASTFM,
|
||||||
FILE_CTRL_SPOTIFY,
|
FILE_CTRL_SPOTIFY,
|
||||||
FILE_CTRL_INITSCAN,
|
FILE_CTRL_INITSCAN,
|
||||||
@ -318,6 +319,9 @@ file_type_get(const char *path) {
|
|||||||
if (strcasecmp(ext, ".remote") == 0)
|
if (strcasecmp(ext, ".remote") == 0)
|
||||||
return FILE_CTRL_REMOTE;
|
return FILE_CTRL_REMOTE;
|
||||||
|
|
||||||
|
if (strcasecmp(ext, ".verification") == 0)
|
||||||
|
return FILE_CTRL_RAOP_VERIFICATION;
|
||||||
|
|
||||||
if (strcasecmp(ext, ".lastfm") == 0)
|
if (strcasecmp(ext, ".lastfm") == 0)
|
||||||
return FILE_CTRL_LASTFM;
|
return FILE_CTRL_LASTFM;
|
||||||
|
|
||||||
@ -356,6 +360,25 @@ process_playlist(char *file, time_t mtime, int dir_id)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If we found a control file we want to kickoff some action */
|
||||||
|
static void
|
||||||
|
kickoff(void (*kickoff_func)(char **arg), const char *file, int lines)
|
||||||
|
{
|
||||||
|
char **file_content;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
file_content = m_readfile(file, lines);
|
||||||
|
if (!file_content)
|
||||||
|
return;
|
||||||
|
|
||||||
|
kickoff_func(file_content);
|
||||||
|
|
||||||
|
for (i = 0; i < lines; i++)
|
||||||
|
free(file_content[i]);
|
||||||
|
|
||||||
|
free(file_content);
|
||||||
|
}
|
||||||
|
|
||||||
/* Thread: scan */
|
/* Thread: scan */
|
||||||
static void
|
static void
|
||||||
defer_playlist(char *path, time_t mtime, int dir_id)
|
defer_playlist(char *path, time_t mtime, int dir_id)
|
||||||
@ -523,7 +546,13 @@ process_file(char *file, struct stat *sb, int type, int flags, int dir_id)
|
|||||||
if (flags & F_SCAN_BULK)
|
if (flags & F_SCAN_BULK)
|
||||||
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
|
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
|
||||||
else
|
else
|
||||||
remote_pairing_kickoff_byfile(file);
|
kickoff(remote_pairing_kickoff, file, 1);
|
||||||
|
|
||||||
|
case FILE_CTRL_RAOP_VERIFICATION:
|
||||||
|
if (flags & F_SCAN_BULK)
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
|
||||||
|
else
|
||||||
|
kickoff(player_raop_verification_kickoff, file, 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FILE_CTRL_LASTFM:
|
case FILE_CTRL_LASTFM:
|
||||||
@ -531,7 +560,7 @@ process_file(char *file, struct stat *sb, int type, int flags, int dir_id)
|
|||||||
if (flags & F_SCAN_BULK)
|
if (flags & F_SCAN_BULK)
|
||||||
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
|
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
|
||||||
else
|
else
|
||||||
lastfm_login(file);
|
kickoff(lastfm_login, file, 2);
|
||||||
#else
|
#else
|
||||||
DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without LastFM support\n", file);
|
DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without LastFM support\n", file);
|
||||||
#endif
|
#endif
|
||||||
@ -542,7 +571,7 @@ process_file(char *file, struct stat *sb, int type, int flags, int dir_id)
|
|||||||
if (flags & F_SCAN_BULK)
|
if (flags & F_SCAN_BULK)
|
||||||
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
|
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
|
||||||
else
|
else
|
||||||
spotify_login(file);
|
kickoff(spotify_login, file, 2);
|
||||||
#else
|
#else
|
||||||
DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without Spotify support\n", file);
|
DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without Spotify support\n", file);
|
||||||
#endif
|
#endif
|
||||||
|
@ -606,6 +606,9 @@ main(int argc, char **argv)
|
|||||||
#ifdef MPD
|
#ifdef MPD
|
||||||
strcat(buildopts, " --enable-mpd");
|
strcat(buildopts, " --enable-mpd");
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef RAOP_VERIFICATION
|
||||||
|
strcat(buildopts, " --enable-verification");
|
||||||
|
#endif
|
||||||
#ifdef HAVE_ALSA
|
#ifdef HAVE_ALSA
|
||||||
strcat(buildopts, " --with-alsa");
|
strcat(buildopts, " --with-alsa");
|
||||||
#endif
|
#endif
|
||||||
|
51
src/misc.c
51
src/misc.c
@ -533,6 +533,57 @@ m_realpath(const char *pathname)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char **
|
||||||
|
m_readfile(const char *path, int num_lines)
|
||||||
|
{
|
||||||
|
char buf[256];
|
||||||
|
FILE *fp;
|
||||||
|
char **lines;
|
||||||
|
char *line;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
// Alloc array of char pointers
|
||||||
|
lines = calloc(num_lines, sizeof(char *));
|
||||||
|
if (!lines)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
fp = fopen(path, "rb");
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MISC, "Could not open file '%s' for reading: %s\n", path, strerror(errno));
|
||||||
|
free(lines);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < num_lines; i++)
|
||||||
|
{
|
||||||
|
line = fgets(buf, sizeof(buf), fp);
|
||||||
|
if (!line)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MISC, "File '%s' has fewer lines than expected (found %d, expected %d)\n", path, i, num_lines);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines[i] = trimwhitespace(line);
|
||||||
|
if (!lines[i] || (strlen(lines[i]) == 0))
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MISC, "Line %d in '%s' is invalid\n", i+1, path);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
|
||||||
|
error:
|
||||||
|
for (i = 0; i < num_lines; i++)
|
||||||
|
free(lines[i]);
|
||||||
|
|
||||||
|
free(lines);
|
||||||
|
fclose(fp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
char *
|
char *
|
||||||
unicode_fixup_string(char *str, const char *fromcode)
|
unicode_fixup_string(char *str, const char *fromcode)
|
||||||
|
@ -80,6 +80,9 @@ keyval_sort(struct keyval *kv);
|
|||||||
char *
|
char *
|
||||||
m_realpath(const char *pathname);
|
m_realpath(const char *pathname);
|
||||||
|
|
||||||
|
char **
|
||||||
|
m_readfile(const char *path, int num_lines);
|
||||||
|
|
||||||
char *
|
char *
|
||||||
unicode_fixup_string(char *str, const char *fromcode);
|
unicode_fixup_string(char *str, const char *fromcode);
|
||||||
|
|
||||||
|
12
src/mpd.c
12
src/mpd.c
@ -3520,7 +3520,13 @@ channel_outputvolume(const char *message)
|
|||||||
static void
|
static void
|
||||||
channel_pairing(const char *message)
|
channel_pairing(const char *message)
|
||||||
{
|
{
|
||||||
remote_pairing_kickoff_bypin(message);
|
remote_pairing_kickoff((char **)&message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
channel_verification(const char *message)
|
||||||
|
{
|
||||||
|
player_raop_verification_kickoff((char **)&message);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct mpd_channel
|
struct mpd_channel
|
||||||
@ -3546,6 +3552,10 @@ static struct mpd_channel mpd_channels[] =
|
|||||||
.channel = "pairing",
|
.channel = "pairing",
|
||||||
.handler = channel_pairing
|
.handler = channel_pairing
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.channel = "verification",
|
||||||
|
.handler = channel_verification
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.channel = NULL,
|
.channel = NULL,
|
||||||
.handler = NULL
|
.handler = NULL
|
||||||
|
@ -100,23 +100,22 @@ outputs_device_probe(struct output_device *device, output_status_cb cb)
|
|||||||
void
|
void
|
||||||
outputs_device_free(struct output_device *device)
|
outputs_device_free(struct output_device *device)
|
||||||
{
|
{
|
||||||
|
if (!device)
|
||||||
|
return;
|
||||||
|
|
||||||
if (outputs[device->type]->disabled)
|
if (outputs[device->type]->disabled)
|
||||||
DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device from a disabled output?\n");
|
DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device from a disabled output?\n");
|
||||||
|
|
||||||
|
if (device->session)
|
||||||
|
DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device with active session?\n");
|
||||||
|
|
||||||
if (outputs[device->type]->device_free_extra)
|
if (outputs[device->type]->device_free_extra)
|
||||||
outputs[device->type]->device_free_extra(device);
|
outputs[device->type]->device_free_extra(device);
|
||||||
|
|
||||||
if (device->name)
|
free(device->name);
|
||||||
free(device->name);
|
free(device->auth_key);
|
||||||
|
free(device->v4_address);
|
||||||
if (device->v4_address)
|
free(device->v6_address);
|
||||||
free(device->v4_address);
|
|
||||||
|
|
||||||
if (device->v6_address)
|
|
||||||
free(device->v6_address);
|
|
||||||
|
|
||||||
if (device->session)
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device with active session?\n");
|
|
||||||
|
|
||||||
free(device);
|
free(device);
|
||||||
}
|
}
|
||||||
@ -313,6 +312,16 @@ outputs_metadata_free(struct output_metadata *omd)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
outputs_authorize(enum output_types type, const char *pin)
|
||||||
|
{
|
||||||
|
if (outputs[type]->disabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (outputs[type]->authorize)
|
||||||
|
outputs[type]->authorize(pin);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
outputs_priority(struct output_device *device)
|
outputs_priority(struct output_device *device)
|
||||||
{
|
{
|
||||||
|
@ -102,9 +102,11 @@ struct output_device
|
|||||||
unsigned advertised:1;
|
unsigned advertised:1;
|
||||||
unsigned has_password:1;
|
unsigned has_password:1;
|
||||||
unsigned has_video:1;
|
unsigned has_video:1;
|
||||||
|
unsigned requires_auth:1;
|
||||||
|
|
||||||
// Password if relevant
|
// Credentials if relevant
|
||||||
const char *password;
|
const char *password;
|
||||||
|
char *auth_key;
|
||||||
|
|
||||||
// Device volume
|
// Device volume
|
||||||
int volume;
|
int volume;
|
||||||
@ -187,6 +189,9 @@ struct output_definition
|
|||||||
// Flush all sessions, the return must be number of sessions pending the flush
|
// Flush all sessions, the return must be number of sessions pending the flush
|
||||||
int (*flush)(output_status_cb cb, uint64_t rtptime);
|
int (*flush)(output_status_cb cb, uint64_t rtptime);
|
||||||
|
|
||||||
|
// Authorize an output with a pin-code (probably coming from the filescanner)
|
||||||
|
void (*authorize)(const char *pin);
|
||||||
|
|
||||||
// Change the call back associated with a session
|
// Change the call back associated with a session
|
||||||
void (*status_cb)(struct output_session *session, output_status_cb cb);
|
void (*status_cb)(struct output_session *session, output_status_cb cb);
|
||||||
|
|
||||||
@ -242,6 +247,9 @@ outputs_metadata_prune(uint64_t rtptime);
|
|||||||
void
|
void
|
||||||
outputs_metadata_free(struct output_metadata *omd);
|
outputs_metadata_free(struct output_metadata *omd);
|
||||||
|
|
||||||
|
void
|
||||||
|
outputs_authorize(enum output_types type, const char *pin);
|
||||||
|
|
||||||
int
|
int
|
||||||
outputs_priority(struct output_device *device);
|
outputs_priority(struct output_device *device);
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
1336
src/outputs/raop_verification.c
Normal file
1336
src/outputs/raop_verification.c
Normal file
File diff suppressed because it is too large
Load Diff
66
src/outputs/raop_verification.h
Normal file
66
src/outputs/raop_verification.h
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#ifndef __VERIFICATION_H__
|
||||||
|
#define __VERIFICATION_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct verification_setup_context;
|
||||||
|
struct verification_verify_context;
|
||||||
|
|
||||||
|
/* When you have the pin-code (must be 4 bytes), create a new context with this
|
||||||
|
* function and then call verification_setup_request1()
|
||||||
|
*/
|
||||||
|
struct verification_setup_context *
|
||||||
|
verification_setup_new(const char *pin);
|
||||||
|
void
|
||||||
|
verification_setup_free(struct verification_setup_context *sctx);
|
||||||
|
|
||||||
|
/* Returns last error message
|
||||||
|
*/
|
||||||
|
const char *
|
||||||
|
verification_setup_errmsg(struct verification_setup_context *sctx);
|
||||||
|
|
||||||
|
uint8_t *
|
||||||
|
verification_setup_request1(uint32_t *len, struct verification_setup_context *sctx);
|
||||||
|
uint8_t *
|
||||||
|
verification_setup_request2(uint32_t *len, struct verification_setup_context *sctx);
|
||||||
|
uint8_t *
|
||||||
|
verification_setup_request3(uint32_t *len, struct verification_setup_context *sctx);
|
||||||
|
|
||||||
|
int
|
||||||
|
verification_setup_response1(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len);
|
||||||
|
int
|
||||||
|
verification_setup_response2(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len);
|
||||||
|
int
|
||||||
|
verification_setup_response3(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len);
|
||||||
|
|
||||||
|
/* Returns a 0-terminated string that is the authorisation key. The caller
|
||||||
|
* should save it and use it later to initialize verification_verify_new().
|
||||||
|
* Note that the pointer becomes invalid when you free sctx.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
verification_setup_result(const char **authorisation_key, struct verification_setup_context *sctx);
|
||||||
|
|
||||||
|
|
||||||
|
/* When you have completed the setup you can extract a key with
|
||||||
|
* verification_setup_result(). Give the string as input to this function to
|
||||||
|
* create a verification context and then call verification_verify_request1()
|
||||||
|
*/
|
||||||
|
struct verification_verify_context *
|
||||||
|
verification_verify_new(const char *authorisation_key);
|
||||||
|
void
|
||||||
|
verification_verify_free(struct verification_verify_context *vctx);
|
||||||
|
|
||||||
|
/* Returns last error message
|
||||||
|
*/
|
||||||
|
const char *
|
||||||
|
verification_verify_errmsg(struct verification_verify_context *vctx);
|
||||||
|
|
||||||
|
uint8_t *
|
||||||
|
verification_verify_request1(uint32_t *len, struct verification_verify_context *vctx);
|
||||||
|
uint8_t *
|
||||||
|
verification_verify_request2(uint32_t *len, struct verification_verify_context *vctx);
|
||||||
|
|
||||||
|
int
|
||||||
|
verification_verify_response1(struct verification_verify_context *vctx, const uint8_t *data, uint32_t data_len);
|
||||||
|
|
||||||
|
#endif /* !__VERIFICATION_H__ */
|
76
src/player.c
76
src/player.c
@ -138,6 +138,12 @@ struct metadata_param
|
|||||||
struct output_metadata *output;
|
struct output_metadata *output;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct speaker_auth_param
|
||||||
|
{
|
||||||
|
enum output_types type;
|
||||||
|
char pin[5];
|
||||||
|
};
|
||||||
|
|
||||||
union player_arg
|
union player_arg
|
||||||
{
|
{
|
||||||
struct volume_param vol_param;
|
struct volume_param vol_param;
|
||||||
@ -150,6 +156,7 @@ union player_arg
|
|||||||
uint32_t *id_ptr;
|
uint32_t *id_ptr;
|
||||||
struct speaker_set_param speaker_set_param;
|
struct speaker_set_param speaker_set_param;
|
||||||
enum repeat_mode mode;
|
enum repeat_mode mode;
|
||||||
|
struct speaker_auth_param auth;
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
int intval;
|
int intval;
|
||||||
};
|
};
|
||||||
@ -1235,7 +1242,7 @@ device_remove(struct output_device *remove)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
/* Save device volume */
|
/* Save device volume */
|
||||||
ret = db_speaker_save(remove->id, remove->selected, remove->volume, remove->name);
|
ret = db_speaker_save(remove);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Could not save state for %s device '%s'\n", remove->type_name, remove->name);
|
DPRINTF(E_LOG, L_PLAYER, "Could not save state for %s device '%s'\n", remove->type_name, remove->name);
|
||||||
|
|
||||||
@ -1273,7 +1280,7 @@ device_add(void *arg, int *retval)
|
|||||||
union player_arg *cmdarg;
|
union player_arg *cmdarg;
|
||||||
struct output_device *add;
|
struct output_device *add;
|
||||||
struct output_device *device;
|
struct output_device *device;
|
||||||
int selected;
|
char *keep_name;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
cmdarg = arg;
|
cmdarg = arg;
|
||||||
@ -1290,15 +1297,21 @@ device_add(void *arg, int *retval)
|
|||||||
{
|
{
|
||||||
device = add;
|
device = add;
|
||||||
|
|
||||||
ret = db_speaker_get(device->id, &selected, &device->volume);
|
keep_name = strdup(device->name);
|
||||||
|
ret = db_speaker_get(device, device->id);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
selected = 0;
|
device->selected = 0;
|
||||||
device->volume = (master_volume >= 0) ? master_volume : PLAYER_DEFAULT_VOLUME;
|
device->volume = (master_volume >= 0) ? master_volume : PLAYER_DEFAULT_VOLUME;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selected && (player_state != PLAY_PLAYING))
|
free(device->name);
|
||||||
|
device->name = keep_name;
|
||||||
|
|
||||||
|
if (device->selected && (player_state != PLAY_PLAYING))
|
||||||
speaker_select_output(device);
|
speaker_select_output(device);
|
||||||
|
else
|
||||||
|
device->selected = 0;
|
||||||
|
|
||||||
device->next = dev_list;
|
device->next = dev_list;
|
||||||
dev_list = device;
|
dev_list = device;
|
||||||
@ -1403,6 +1416,18 @@ device_remove_family(void *arg, int *retval)
|
|||||||
return COMMAND_END;
|
return COMMAND_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static enum command_state
|
||||||
|
device_auth_kickoff(void *arg, int *retval)
|
||||||
|
{
|
||||||
|
union player_arg *cmdarg = arg;
|
||||||
|
|
||||||
|
outputs_authorize(cmdarg->auth.type, cmdarg->auth.pin);
|
||||||
|
|
||||||
|
*retval = 0;
|
||||||
|
return COMMAND_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static enum command_state
|
static enum command_state
|
||||||
metadata_send(void *arg, int *retval)
|
metadata_send(void *arg, int *retval)
|
||||||
{
|
{
|
||||||
@ -1651,10 +1676,12 @@ device_probe_cb(struct output_device *device, struct output_session *session, en
|
|||||||
static void
|
static void
|
||||||
device_restart_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
|
device_restart_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
|
||||||
{
|
{
|
||||||
|
int retval;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_restart_cb\n", outputs_name(device->type));
|
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_restart_cb\n", outputs_name(device->type));
|
||||||
|
|
||||||
|
retval = commands_exec_returnvalue(cmdbase);
|
||||||
ret = device_check(device);
|
ret = device_check(device);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
@ -1663,9 +1690,17 @@ device_restart_cb(struct output_device *device, struct output_session *session,
|
|||||||
outputs_status_cb(session, device_lost_cb);
|
outputs_status_cb(session, device_lost_cb);
|
||||||
outputs_device_stop(session);
|
outputs_device_stop(session);
|
||||||
|
|
||||||
|
if (retval != -2)
|
||||||
|
retval = -1;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status == OUTPUT_STATE_PASSWORD)
|
||||||
|
{
|
||||||
|
status = OUTPUT_STATE_FAILED;
|
||||||
|
retval = -2;
|
||||||
|
}
|
||||||
|
|
||||||
if (status == OUTPUT_STATE_FAILED)
|
if (status == OUTPUT_STATE_FAILED)
|
||||||
{
|
{
|
||||||
speaker_deselect_output(device);
|
speaker_deselect_output(device);
|
||||||
@ -1673,6 +1708,8 @@ device_restart_cb(struct output_device *device, struct output_session *session,
|
|||||||
if (!device->advertised)
|
if (!device->advertised)
|
||||||
device_remove(device);
|
device_remove(device);
|
||||||
|
|
||||||
|
if (retval != -2)
|
||||||
|
retval = -1;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1682,7 +1719,7 @@ device_restart_cb(struct output_device *device, struct output_session *session,
|
|||||||
outputs_status_cb(session, device_streaming_cb);
|
outputs_status_cb(session, device_streaming_cb);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
commands_exec_end(cmdbase, 0);
|
commands_exec_end(cmdbase, retval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Internal abort routine */
|
/* Internal abort routine */
|
||||||
@ -3020,6 +3057,31 @@ player_device_remove(void *device)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
player_device_auth_kickoff(enum output_types type, char **arglist)
|
||||||
|
{
|
||||||
|
union player_arg *cmdarg;
|
||||||
|
|
||||||
|
cmdarg = calloc(1, sizeof(union player_arg));
|
||||||
|
if (!cmdarg)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_PLAYER, "Could not allocate player_command\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdarg->auth.type = type;
|
||||||
|
memcpy(cmdarg->auth.pin, arglist[0], 4);
|
||||||
|
|
||||||
|
commands_exec_async(cmdbase, device_auth_kickoff, cmdarg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Thread: filescanner */
|
||||||
|
void
|
||||||
|
player_raop_verification_kickoff(char **arglist)
|
||||||
|
{
|
||||||
|
player_device_auth_kickoff(OUTPUT_TYPE_RAOP, arglist);
|
||||||
|
}
|
||||||
|
|
||||||
/* Thread: worker */
|
/* Thread: worker */
|
||||||
static void
|
static void
|
||||||
player_metadata_send(struct input_metadata *imd, struct output_metadata *omd)
|
player_metadata_send(struct input_metadata *imd, struct output_metadata *omd)
|
||||||
@ -3057,7 +3119,7 @@ player(void *arg)
|
|||||||
|
|
||||||
for (device = dev_list; device; device = device->next)
|
for (device = dev_list; device; device = device->next)
|
||||||
{
|
{
|
||||||
ret = db_speaker_save(device->id, device->selected, device->volume, device->name);
|
ret = db_speaker_save(device);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Could not save state for %s device '%s'\n", device->type_name, device->name);
|
DPRINTF(E_LOG, L_PLAYER, "Could not save state for %s device '%s'\n", device->type_name, device->name);
|
||||||
}
|
}
|
||||||
|
@ -141,6 +141,9 @@ player_device_add(void *device);
|
|||||||
int
|
int
|
||||||
player_device_remove(void *device);
|
player_device_remove(void *device);
|
||||||
|
|
||||||
|
void
|
||||||
|
player_raop_verification_kickoff(char **arglist);
|
||||||
|
|
||||||
struct player_history *
|
struct player_history *
|
||||||
player_history_get(void);
|
player_history_get(void);
|
||||||
|
|
||||||
|
@ -714,131 +714,28 @@ touch_remote_cb(const char *name, const char *type, const char *domain, const ch
|
|||||||
|
|
||||||
/* Thread: filescanner, mpd */
|
/* Thread: filescanner, mpd */
|
||||||
void
|
void
|
||||||
remote_pairing_kickoff_bypin(const char *pin)
|
remote_pairing_kickoff(char **arglist)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
DPRINTF(E_INFO, L_REMOTE, "Kickoff pairing with pin '%s'\n", pin);
|
ret = strlen(arglist[0]);
|
||||||
|
if (ret != 4)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_REMOTE, "Kickoff pairing failed, unexpected pin length (got %d, expected 4)\n", ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DPRINTF(E_LOG, L_REMOTE, "Kickoff pairing with pin '%s'\n", arglist[0]);
|
||||||
|
|
||||||
CHECK_ERR(L_REMOTE, pthread_mutex_lock(&remote_lck));
|
CHECK_ERR(L_REMOTE, pthread_mutex_lock(&remote_lck));
|
||||||
|
|
||||||
ret = add_remote_pin_data(pin);
|
ret = add_remote_pin_data(arglist[0]);
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
kickoff_pairing();
|
kickoff_pairing();
|
||||||
|
|
||||||
CHECK_ERR(L_REMOTE, pthread_mutex_unlock(&remote_lck));
|
CHECK_ERR(L_REMOTE, pthread_mutex_unlock(&remote_lck));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Thread: filescanner */
|
|
||||||
void
|
|
||||||
remote_pairing_kickoff_byfile(char *path)
|
|
||||||
{
|
|
||||||
char buf[256];
|
|
||||||
FILE *fp;
|
|
||||||
char *devname;
|
|
||||||
char *pin;
|
|
||||||
int len;
|
|
||||||
|
|
||||||
fp = fopen(path, "rb");
|
|
||||||
if (!fp)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_REMOTE, "Could not open Remote pairing file %s: %s\n", path, strerror(errno));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
devname = fgets(buf, sizeof(buf), fp);
|
|
||||||
if (!devname)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_REMOTE, "Empty Remote pairing file %s\n", path);
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = strlen(devname);
|
|
||||||
if (buf[len - 1] != '\n')
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_REMOTE, "Invalid Remote pairing file %s: device name too long or missing pin\n", path);
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (len)
|
|
||||||
{
|
|
||||||
if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
|
|
||||||
{
|
|
||||||
buf[len - 1] = '\0';
|
|
||||||
len--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!len)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_REMOTE, "Invalid Remote pairing file %s: empty line where device name expected\n", path);
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
devname = strdup(buf);
|
|
||||||
if (!devname)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_REMOTE, "Out of memory for device name while reading %s\n", path);
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pin = fgets(buf, sizeof(buf), fp);
|
|
||||||
fclose(fp);
|
|
||||||
if (!pin)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_REMOTE, "Invalid Remote pairing file %s: no pin\n", path);
|
|
||||||
|
|
||||||
free(devname);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = strlen(pin);
|
|
||||||
|
|
||||||
while (len)
|
|
||||||
{
|
|
||||||
if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
|
|
||||||
{
|
|
||||||
buf[len - 1] = '\0';
|
|
||||||
len--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len != 4)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_REMOTE, "Invalid pin in Remote pairing file %s: pin length should be 4, got %d\n", path, len);
|
|
||||||
|
|
||||||
free(devname);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pin = strdup(buf);
|
|
||||||
if (!pin)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_REMOTE, "Out of memory for device pin while reading %s\n", path);
|
|
||||||
|
|
||||||
free(devname);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DPRINTF(E_LOG, L_REMOTE, "Read Remote pairing data (name '%s', pin '%s') from %s\n", devname, pin, path);
|
|
||||||
|
|
||||||
remote_pairing_kickoff_bypin(pin);
|
|
||||||
free(devname);
|
|
||||||
free(pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Thread: main */
|
/* Thread: main */
|
||||||
int
|
int
|
||||||
|
@ -3,10 +3,7 @@
|
|||||||
#define __REMOTE_PAIRING_H__
|
#define __REMOTE_PAIRING_H__
|
||||||
|
|
||||||
void
|
void
|
||||||
remote_pairing_kickoff_bypin(const char *pin);
|
remote_pairing_kickoff(char **arglist);
|
||||||
|
|
||||||
void
|
|
||||||
remote_pairing_kickoff_byfile(char *path);
|
|
||||||
|
|
||||||
int
|
int
|
||||||
remote_pairing_init(void);
|
remote_pairing_init(void);
|
||||||
|
123
src/spotify.c
123
src/spotify.c
@ -406,110 +406,6 @@ webapi_pl_remove(void *arg, int *ret);
|
|||||||
static void
|
static void
|
||||||
create_base_playlist();
|
create_base_playlist();
|
||||||
|
|
||||||
/* ------------------------------- MISC HELPERS ---------------------------- */
|
|
||||||
|
|
||||||
static int
|
|
||||||
spotify_file_read(char *path, char **username, char **password)
|
|
||||||
{
|
|
||||||
FILE *fp;
|
|
||||||
char *u;
|
|
||||||
char *p;
|
|
||||||
char buf[256];
|
|
||||||
int len;
|
|
||||||
|
|
||||||
fp = fopen(path, "rb");
|
|
||||||
if (!fp)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_SPOTIFY, "Could not open Spotify credentials file %s: %s\n", path, strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
u = fgets(buf, sizeof(buf), fp);
|
|
||||||
if (!u)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_SPOTIFY, "Empty Spotify credentials file %s\n", path);
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = strlen(u);
|
|
||||||
if (buf[len - 1] != '\n')
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: username name too long or missing password\n", path);
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (len)
|
|
||||||
{
|
|
||||||
if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
|
|
||||||
{
|
|
||||||
buf[len - 1] = '\0';
|
|
||||||
len--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!len)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: empty line where username expected\n", path);
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
u = strdup(buf);
|
|
||||||
if (!u)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for username while reading %s\n", path);
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
p = fgets(buf, sizeof(buf), fp);
|
|
||||||
fclose(fp);
|
|
||||||
if (!p)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: no password\n", path);
|
|
||||||
|
|
||||||
free(u);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = strlen(p);
|
|
||||||
|
|
||||||
while (len)
|
|
||||||
{
|
|
||||||
if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
|
|
||||||
{
|
|
||||||
buf[len - 1] = '\0';
|
|
||||||
len--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
p = strdup(buf);
|
|
||||||
if (!p)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for password while reading %s\n", path);
|
|
||||||
|
|
||||||
free(u);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
DPRINTF(E_LOG, L_SPOTIFY, "Spotify credentials file OK, logging in with username %s\n", u);
|
|
||||||
|
|
||||||
*username = u;
|
|
||||||
*password = p;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------- PLAYLIST HELPERS ------------------------- */
|
/* -------------------------- PLAYLIST HELPERS ------------------------- */
|
||||||
/* Should only be called from within the spotify thread */
|
/* Should only be called from within the spotify thread */
|
||||||
@ -1945,12 +1841,9 @@ spotify_uri_register(const char *uri)
|
|||||||
|
|
||||||
/* Thread: library */
|
/* Thread: library */
|
||||||
void
|
void
|
||||||
spotify_login(char *path)
|
spotify_login(char **arglist)
|
||||||
{
|
{
|
||||||
sp_error err;
|
sp_error err;
|
||||||
char *username;
|
|
||||||
char *password;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (!g_sess)
|
if (!g_sess)
|
||||||
{
|
{
|
||||||
@ -1982,20 +1875,14 @@ spotify_login(char *path)
|
|||||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
|
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF(E_INFO, L_SPOTIFY, "Logging into Spotify\n");
|
if (arglist)
|
||||||
|
|
||||||
if (path)
|
|
||||||
{
|
{
|
||||||
ret = spotify_file_read(path, &username, &password);
|
DPRINTF(E_LOG, L_SPOTIFY, "Spotify credentials file OK, logging in with username %s\n", arglist[0]);
|
||||||
if (ret < 0)
|
err = fptr_sp_session_login(g_sess, arglist[0], arglist[1], 1, NULL);
|
||||||
return;
|
|
||||||
|
|
||||||
err = fptr_sp_session_login(g_sess, username, password, 1, NULL);
|
|
||||||
free(username);
|
|
||||||
free(password);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
DPRINTF(E_INFO, L_SPOTIFY, "Relogin to Spotify\n");
|
||||||
err = fptr_sp_session_relogin(g_sess);
|
err = fptr_sp_session_relogin(g_sess);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ void
|
|||||||
spotify_oauth_callback(struct evbuffer *evbuf, struct evkeyvalq *param, const char *redirect_uri);
|
spotify_oauth_callback(struct evbuffer *evbuf, struct evkeyvalq *param, const char *redirect_uri);
|
||||||
|
|
||||||
void
|
void
|
||||||
spotify_login(char *path);
|
spotify_login(char **arglist);
|
||||||
|
|
||||||
int
|
int
|
||||||
spotify_init(void);
|
spotify_init(void);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user