mirror of
https://github.com/owntone/owntone-server.git
synced 2025-02-04 18:36:02 -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
|
||||
env:
|
||||
matrix:
|
||||
- CFG=""
|
||||
- CFG="--enable-lastfm"
|
||||
- CFG="--enable-itunes"
|
||||
- CFG="--enable-spotify"
|
||||
- CFG="--enable-chromecast"
|
||||
- CFG="--with-pulseaudio"
|
||||
- CFG="--disable-verification"
|
||||
- CFG="--enable-lastfm --disable-verification"
|
||||
- CFG="--enable-spotify --disable-verification"
|
||||
- CFG="--enable-chromecast --disable-verification"
|
||||
- CFG="--with-pulseaudio --disable-verification"
|
||||
|
||||
script:
|
||||
- autoreconf -fi
|
||||
|
42
INSTALL
42
INSTALL
@ -31,16 +31,17 @@ libraries:
|
||||
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
|
||||
libevent-dev libplist-dev libsodium-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
|
||||
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 | --disable-itunes | libplist-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
|
||||
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 \
|
||||
git automake autoconf gettext-devel gperf gawk libtool \
|
||||
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:
|
||||
|
||||
@ -137,12 +139,13 @@ Add the following to .bashrc:
|
||||
|
||||
Optional features require the following additional ports:
|
||||
|
||||
Feature | Configure argument | Ports
|
||||
-----------|---------------------|--------------------------------------------
|
||||
Chromecast | --enable-chromecast | json-c gnutls protobuf-c
|
||||
LastFM | --enable-lastfm | curl
|
||||
iTunes XML | --enable-itunes | libplist
|
||||
Pulseaudio | --with-pulseaudio | pulseaudio
|
||||
Feature | Configure argument | Ports
|
||||
--------------------|------------------------|--------------------------------------------
|
||||
Chromecast | --enable-chromecast | json-c gnutls protobuf-c
|
||||
LastFM | --enable-lastfm | curl
|
||||
iTunes XML | --disable-itunes | libplist
|
||||
Device verification | --disable-verification | libplist libsodium
|
||||
Pulseaudio | --with-pulseaudio | pulseaudio
|
||||
|
||||
Clone the forked-daapd repo:
|
||||
git clone https://github.com/ejurgensen/forked-daapd.git
|
||||
@ -219,8 +222,10 @@ Libraries:
|
||||
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)
|
||||
- libplist 0.16+ (optional - iTunes XML support and Apple TV device verification)
|
||||
from <http://github.com/JonathanBeck/libplist/downloads>
|
||||
- libsodium (optional - Apple TV device verification)
|
||||
from <https://download.libsodium.org/doc/>
|
||||
- libspotify (optional - Spotify support)
|
||||
from <https://developer.spotify.com>
|
||||
- libcurl (optional - LastFM support)
|
||||
@ -278,12 +283,15 @@ disabled).
|
||||
Support for LastFM scrobbling is optional. Use --enable-lastfm to enable this
|
||||
feature.
|
||||
|
||||
Support for iTunes Music Library XML format is optional. Use --enable-itunes
|
||||
to enable this feature.
|
||||
Support for iTunes Music Library XML format is optional. Use --disable-itunes
|
||||
to disable this feature.
|
||||
|
||||
Support for the MPD protocol is optional. Use --disable-mpd to disable this
|
||||
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
|
||||
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.
|
||||
|
||||
**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
|
||||
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.
|
||||
|
||||
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
|
||||
two lines: the first line is the name of your iPod/iPhone/iPad, the second
|
||||
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
|
||||
```
|
||||
doesn't matter, only the .remote ending does. This first line in the file
|
||||
must contain the 4-digit pairing code displayed by Remote.
|
||||
|
||||
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
|
||||
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
|
||||
that is *not* 0.
|
||||
Solution: Copy-paste the name to be sure to get specials characters right. You
|
||||
can also try the pairinghelper script located in the scripts-folder of the
|
||||
source.
|
||||
Solution: Try again. You can also try the pairinghelper script located in the
|
||||
scripts-folder of the source or the mpc method described above.
|
||||
|
||||
#### No response from Remote, possibly a network issue
|
||||
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.
|
||||
|
||||
The name of your iPod/iPhone/iPad is the value of the DvNm field above. In this
|
||||
example, the correct value is Foobar. To check for network issues you can try to
|
||||
connect to address and port with telnet.
|
||||
To check for network issues you can try to connect to address and port with
|
||||
telnet.
|
||||
|
||||
### 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
|
||||
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
|
||||
|
||||
|
31
configure.ac
31
configure.ac
@ -273,6 +273,14 @@ dnl Build with libcurl
|
||||
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libcurl support], [libcurl], [LIBCURL],
|
||||
[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
|
||||
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libevent_pthreads support],
|
||||
[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.]])])])
|
||||
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
|
||||
FORK_ARG_ENABLE([Spotify support], [spotify], [SPOTIFY],
|
||||
[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_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
|
||||
FORK_ARG_DISABLE([MPD client protocol support], [mpd], [MPD])
|
||||
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
|
||||
AC_ARG_WITH([daapd_user],
|
||||
[AS_HELP_STRING([--with-daapd-user=USER],
|
||||
|
@ -95,7 +95,7 @@ if [ -z "$pin" ]; then
|
||||
fi
|
||||
|
||||
echo "Writing pair.remote to $library_path..."
|
||||
printf "$remote\n$pin" > "$rf"
|
||||
printf "$pin" > "$rf"
|
||||
if [ ! -f "$rf" ]; then
|
||||
echo "Unable to create '$rf' - check directory permissions"
|
||||
exit 1
|
||||
@ -106,7 +106,7 @@ n=20
|
||||
echo "Waiting for pairing to complete (up to $n secs)..."
|
||||
while [ $n -gt 0 ]; do
|
||||
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
|
||||
sleep 1
|
||||
done
|
||||
|
@ -25,6 +25,10 @@ if COND_MPD
|
||||
MPD_SRC=mpd.c mpd.h
|
||||
endif
|
||||
|
||||
if COND_RAOP_VERIFICATION
|
||||
RAOP_VERIFICATION_SRC=outputs/raop_verification.c outputs/raop_verification.h
|
||||
endif
|
||||
|
||||
if COND_ALSA
|
||||
ALSA_SRC=outputs/alsa.c
|
||||
endif
|
||||
@ -112,7 +116,8 @@ forked_daapd_SOURCES = main.c \
|
||||
input.h input.c \
|
||||
inputs/file_http.c inputs/pipe.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) \
|
||||
evrtsp/rtsp.c evrtsp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \
|
||||
$(SPOTIFY_SRC) \
|
||||
|
25
src/db.c
25
src/db.c
@ -3824,21 +3824,21 @@ db_admin_delete(const char *key)
|
||||
|
||||
/* Speakers */
|
||||
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;
|
||||
|
||||
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);
|
||||
#undef Q_TMPL
|
||||
}
|
||||
|
||||
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;
|
||||
char *query;
|
||||
int ret;
|
||||
@ -3847,7 +3847,6 @@ db_speaker_get(uint64_t id, int *selected, int *volume)
|
||||
if (!query)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -3857,7 +3856,6 @@ db_speaker_get(uint64_t id, int *selected, int *volume)
|
||||
if (ret != SQLITE_OK)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
||||
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
@ -3867,15 +3865,20 @@ db_speaker_get(uint64_t id, int *selected, int *volume)
|
||||
{
|
||||
if (ret != SQLITE_DONE)
|
||||
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
*selected = sqlite3_column_int(stmt, 0);
|
||||
*volume = sqlite3_column_int(stmt, 1);
|
||||
device->id = id;
|
||||
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
|
||||
while (db_blocking_step(stmt) == SQLITE_ROW)
|
||||
|
7
src/db.h
7
src/db.h
@ -8,6 +8,7 @@
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include "outputs.h"
|
||||
|
||||
enum index_type {
|
||||
I_NONE,
|
||||
@ -675,12 +676,12 @@ db_admin_get(const char *key);
|
||||
int
|
||||
db_admin_delete(const char *key);
|
||||
|
||||
/* Speakers */
|
||||
/* Speakers/outputs */
|
||||
int
|
||||
db_speaker_save(uint64_t id, int selected, int volume, const char *name);
|
||||
db_speaker_save(struct output_device *device);
|
||||
|
||||
int
|
||||
db_speaker_get(uint64_t id, int *selected, int *volume);
|
||||
db_speaker_get(struct output_device *device, uint64_t id);
|
||||
|
||||
void
|
||||
db_speaker_clear_all(void);
|
||||
|
@ -140,7 +140,8 @@
|
||||
" id INTEGER PRIMARY KEY NOT NULL," \
|
||||
" selected 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 \
|
||||
|
@ -26,7 +26,7 @@
|
||||
* 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 03
|
||||
#define SCHEMA_VERSION_MINOR 04
|
||||
|
||||
int
|
||||
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
|
||||
db_upgrade(sqlite3 *hdl, int db_ver)
|
||||
{
|
||||
@ -1678,6 +1695,13 @@ db_upgrade(sqlite3 *hdl, int db_ver)
|
||||
if (ret < 0)
|
||||
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;
|
||||
|
||||
default:
|
||||
|
@ -46,6 +46,7 @@ extern "C" {
|
||||
/* Response codes */
|
||||
#define RTSP_OK 200
|
||||
#define RTSP_UNAUTHORIZED 401
|
||||
#define RTSP_FORBIDDEN 403
|
||||
|
||||
struct evrtsp_connection;
|
||||
|
||||
@ -62,6 +63,7 @@ enum evrtsp_cmd_type {
|
||||
EVRTSP_REQ_SET_PARAMETER,
|
||||
EVRTSP_REQ_FLUSH,
|
||||
EVRTSP_REQ_TEARDOWN,
|
||||
EVRTSP_REQ_POST,
|
||||
};
|
||||
|
||||
enum evrtsp_request_kind { EVRTSP_REQUEST, EVRTSP_RESPONSE };
|
||||
|
@ -260,6 +260,10 @@ evrtsp_method(enum evrtsp_cmd_type type)
|
||||
method = "TEARDOWN";
|
||||
break;
|
||||
|
||||
case EVRTSP_REQ_POST:
|
||||
method = "POST";
|
||||
break;
|
||||
|
||||
default:
|
||||
method = NULL;
|
||||
break;
|
||||
|
140
src/lastfm.c
140
src/lastfm.c
@ -63,108 +63,6 @@ static char *lastfm_session_key = NULL;
|
||||
|
||||
/* --------------------------------- 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 */
|
||||
static int
|
||||
@ -417,59 +315,39 @@ scrobble(int id)
|
||||
/* ---------------------------- Our lastfm API --------------------------- */
|
||||
|
||||
/* Thread: filescanner */
|
||||
int
|
||||
lastfm_login(char *path)
|
||||
void
|
||||
lastfm_login(char **arglist)
|
||||
{
|
||||
struct keyval *kv;
|
||||
char *username;
|
||||
char *password;
|
||||
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
|
||||
if (lastfm_session_key)
|
||||
free(lastfm_session_key);
|
||||
|
||||
free(lastfm_session_key);
|
||||
lastfm_session_key = NULL;
|
||||
|
||||
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
|
||||
lastfm_disabled = 0;
|
||||
|
||||
kv = keyval_alloc();
|
||||
if (!kv)
|
||||
{
|
||||
ret = -1;
|
||||
goto out_free_credentials;
|
||||
}
|
||||
return;
|
||||
|
||||
ret = ( (keyval_add(kv, "api_key", lastfm_api_key) == 0) &&
|
||||
(keyval_add(kv, "username", username) == 0) &&
|
||||
(keyval_add(kv, "password", password) == 0) );
|
||||
(keyval_add(kv, "username", arglist[0]) == 0) &&
|
||||
(keyval_add(kv, "password", arglist[1]) == 0) );
|
||||
if (!ret)
|
||||
{
|
||||
ret = -1;
|
||||
goto out_free_kv;
|
||||
}
|
||||
goto out_free_kv;
|
||||
|
||||
// Send the login request
|
||||
ret = request_post("auth.getMobileSession", kv, 1);
|
||||
request_post("auth.getMobileSession", kv, 1);
|
||||
|
||||
out_free_kv:
|
||||
keyval_clear(kv);
|
||||
free(kv);
|
||||
out_free_credentials:
|
||||
free(username);
|
||||
free(password);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Thread: worker */
|
||||
|
@ -2,8 +2,8 @@
|
||||
#ifndef __LASTFM_H__
|
||||
#define __LASTFM_H__
|
||||
|
||||
int
|
||||
lastfm_login(char *path);
|
||||
void
|
||||
lastfm_login(char **arglist);
|
||||
|
||||
int
|
||||
lastfm_scrobble(int id);
|
||||
|
@ -92,6 +92,7 @@ enum file_type {
|
||||
FILE_ITUNES,
|
||||
FILE_ARTWORK,
|
||||
FILE_CTRL_REMOTE,
|
||||
FILE_CTRL_RAOP_VERIFICATION,
|
||||
FILE_CTRL_LASTFM,
|
||||
FILE_CTRL_SPOTIFY,
|
||||
FILE_CTRL_INITSCAN,
|
||||
@ -318,6 +319,9 @@ file_type_get(const char *path) {
|
||||
if (strcasecmp(ext, ".remote") == 0)
|
||||
return FILE_CTRL_REMOTE;
|
||||
|
||||
if (strcasecmp(ext, ".verification") == 0)
|
||||
return FILE_CTRL_RAOP_VERIFICATION;
|
||||
|
||||
if (strcasecmp(ext, ".lastfm") == 0)
|
||||
return FILE_CTRL_LASTFM;
|
||||
|
||||
@ -356,6 +360,25 @@ process_playlist(char *file, time_t mtime, int dir_id)
|
||||
#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 */
|
||||
static void
|
||||
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)
|
||||
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
|
||||
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;
|
||||
|
||||
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)
|
||||
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
|
||||
else
|
||||
lastfm_login(file);
|
||||
kickoff(lastfm_login, file, 2);
|
||||
#else
|
||||
DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without LastFM support\n", file);
|
||||
#endif
|
||||
@ -542,7 +571,7 @@ process_file(char *file, struct stat *sb, int type, int flags, int dir_id)
|
||||
if (flags & F_SCAN_BULK)
|
||||
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
|
||||
else
|
||||
spotify_login(file);
|
||||
kickoff(spotify_login, file, 2);
|
||||
#else
|
||||
DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without Spotify support\n", file);
|
||||
#endif
|
||||
|
@ -606,6 +606,9 @@ main(int argc, char **argv)
|
||||
#ifdef MPD
|
||||
strcat(buildopts, " --enable-mpd");
|
||||
#endif
|
||||
#ifdef RAOP_VERIFICATION
|
||||
strcat(buildopts, " --enable-verification");
|
||||
#endif
|
||||
#ifdef HAVE_ALSA
|
||||
strcat(buildopts, " --with-alsa");
|
||||
#endif
|
||||
|
51
src/misc.c
51
src/misc.c
@ -533,6 +533,57 @@ m_realpath(const char *pathname)
|
||||
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 *
|
||||
unicode_fixup_string(char *str, const char *fromcode)
|
||||
|
@ -80,6 +80,9 @@ keyval_sort(struct keyval *kv);
|
||||
char *
|
||||
m_realpath(const char *pathname);
|
||||
|
||||
char **
|
||||
m_readfile(const char *path, int num_lines);
|
||||
|
||||
char *
|
||||
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
|
||||
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
|
||||
@ -3546,6 +3552,10 @@ static struct mpd_channel mpd_channels[] =
|
||||
.channel = "pairing",
|
||||
.handler = channel_pairing
|
||||
},
|
||||
{
|
||||
.channel = "verification",
|
||||
.handler = channel_verification
|
||||
},
|
||||
{
|
||||
.channel = NULL,
|
||||
.handler = NULL
|
||||
|
@ -100,23 +100,22 @@ outputs_device_probe(struct output_device *device, output_status_cb cb)
|
||||
void
|
||||
outputs_device_free(struct output_device *device)
|
||||
{
|
||||
if (!device)
|
||||
return;
|
||||
|
||||
if (outputs[device->type]->disabled)
|
||||
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)
|
||||
outputs[device->type]->device_free_extra(device);
|
||||
|
||||
if (device->name)
|
||||
free(device->name);
|
||||
|
||||
if (device->v4_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->name);
|
||||
free(device->auth_key);
|
||||
free(device->v4_address);
|
||||
free(device->v6_address);
|
||||
|
||||
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
|
||||
outputs_priority(struct output_device *device)
|
||||
{
|
||||
|
@ -102,9 +102,11 @@ struct output_device
|
||||
unsigned advertised:1;
|
||||
unsigned has_password:1;
|
||||
unsigned has_video:1;
|
||||
unsigned requires_auth:1;
|
||||
|
||||
// Password if relevant
|
||||
// Credentials if relevant
|
||||
const char *password;
|
||||
char *auth_key;
|
||||
|
||||
// Device volume
|
||||
int volume;
|
||||
@ -187,6 +189,9 @@ struct output_definition
|
||||
// Flush all sessions, the return must be number of sessions pending the flush
|
||||
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
|
||||
void (*status_cb)(struct output_session *session, output_status_cb cb);
|
||||
|
||||
@ -242,6 +247,9 @@ outputs_metadata_prune(uint64_t rtptime);
|
||||
void
|
||||
outputs_metadata_free(struct output_metadata *omd);
|
||||
|
||||
void
|
||||
outputs_authorize(enum output_types type, const char *pin);
|
||||
|
||||
int
|
||||
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 speaker_auth_param
|
||||
{
|
||||
enum output_types type;
|
||||
char pin[5];
|
||||
};
|
||||
|
||||
union player_arg
|
||||
{
|
||||
struct volume_param vol_param;
|
||||
@ -150,6 +156,7 @@ union player_arg
|
||||
uint32_t *id_ptr;
|
||||
struct speaker_set_param speaker_set_param;
|
||||
enum repeat_mode mode;
|
||||
struct speaker_auth_param auth;
|
||||
uint32_t id;
|
||||
int intval;
|
||||
};
|
||||
@ -1235,7 +1242,7 @@ device_remove(struct output_device *remove)
|
||||
return;
|
||||
|
||||
/* Save device volume */
|
||||
ret = db_speaker_save(remove->id, remove->selected, remove->volume, remove->name);
|
||||
ret = db_speaker_save(remove);
|
||||
if (ret < 0)
|
||||
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;
|
||||
struct output_device *add;
|
||||
struct output_device *device;
|
||||
int selected;
|
||||
char *keep_name;
|
||||
int ret;
|
||||
|
||||
cmdarg = arg;
|
||||
@ -1290,15 +1297,21 @@ device_add(void *arg, int *retval)
|
||||
{
|
||||
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)
|
||||
{
|
||||
selected = 0;
|
||||
device->selected = 0;
|
||||
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);
|
||||
else
|
||||
device->selected = 0;
|
||||
|
||||
device->next = dev_list;
|
||||
dev_list = device;
|
||||
@ -1403,6 +1416,18 @@ device_remove_family(void *arg, int *retval)
|
||||
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
|
||||
metadata_send(void *arg, int *retval)
|
||||
{
|
||||
@ -1651,10 +1676,12 @@ device_probe_cb(struct output_device *device, struct output_session *session, en
|
||||
static void
|
||||
device_restart_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_restart_cb\n", outputs_name(device->type));
|
||||
|
||||
retval = commands_exec_returnvalue(cmdbase);
|
||||
ret = device_check(device);
|
||||
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_device_stop(session);
|
||||
|
||||
if (retval != -2)
|
||||
retval = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (status == OUTPUT_STATE_PASSWORD)
|
||||
{
|
||||
status = OUTPUT_STATE_FAILED;
|
||||
retval = -2;
|
||||
}
|
||||
|
||||
if (status == OUTPUT_STATE_FAILED)
|
||||
{
|
||||
speaker_deselect_output(device);
|
||||
@ -1673,6 +1708,8 @@ device_restart_cb(struct output_device *device, struct output_session *session,
|
||||
if (!device->advertised)
|
||||
device_remove(device);
|
||||
|
||||
if (retval != -2)
|
||||
retval = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
@ -1682,7 +1719,7 @@ device_restart_cb(struct output_device *device, struct output_session *session,
|
||||
outputs_status_cb(session, device_streaming_cb);
|
||||
|
||||
out:
|
||||
commands_exec_end(cmdbase, 0);
|
||||
commands_exec_end(cmdbase, retval);
|
||||
}
|
||||
|
||||
/* Internal abort routine */
|
||||
@ -3020,6 +3057,31 @@ player_device_remove(void *device)
|
||||
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 */
|
||||
static void
|
||||
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)
|
||||
{
|
||||
ret = db_speaker_save(device->id, device->selected, device->volume, device->name);
|
||||
ret = db_speaker_save(device);
|
||||
if (ret < 0)
|
||||
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
|
||||
player_device_remove(void *device);
|
||||
|
||||
void
|
||||
player_raop_verification_kickoff(char **arglist);
|
||||
|
||||
struct player_history *
|
||||
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 */
|
||||
void
|
||||
remote_pairing_kickoff_bypin(const char *pin)
|
||||
remote_pairing_kickoff(char **arglist)
|
||||
{
|
||||
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));
|
||||
|
||||
ret = add_remote_pin_data(pin);
|
||||
ret = add_remote_pin_data(arglist[0]);
|
||||
if (ret == 0)
|
||||
kickoff_pairing();
|
||||
|
||||
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 */
|
||||
int
|
||||
|
@ -3,10 +3,7 @@
|
||||
#define __REMOTE_PAIRING_H__
|
||||
|
||||
void
|
||||
remote_pairing_kickoff_bypin(const char *pin);
|
||||
|
||||
void
|
||||
remote_pairing_kickoff_byfile(char *path);
|
||||
remote_pairing_kickoff(char **arglist);
|
||||
|
||||
int
|
||||
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
|
||||
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 ------------------------- */
|
||||
/* Should only be called from within the spotify thread */
|
||||
@ -1945,12 +1841,9 @@ spotify_uri_register(const char *uri)
|
||||
|
||||
/* Thread: library */
|
||||
void
|
||||
spotify_login(char *path)
|
||||
spotify_login(char **arglist)
|
||||
{
|
||||
sp_error err;
|
||||
char *username;
|
||||
char *password;
|
||||
int ret;
|
||||
|
||||
if (!g_sess)
|
||||
{
|
||||
@ -1982,20 +1875,14 @@ spotify_login(char *path)
|
||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
|
||||
}
|
||||
|
||||
DPRINTF(E_INFO, L_SPOTIFY, "Logging into Spotify\n");
|
||||
|
||||
if (path)
|
||||
if (arglist)
|
||||
{
|
||||
ret = spotify_file_read(path, &username, &password);
|
||||
if (ret < 0)
|
||||
return;
|
||||
|
||||
err = fptr_sp_session_login(g_sess, username, password, 1, NULL);
|
||||
free(username);
|
||||
free(password);
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Spotify credentials file OK, logging in with username %s\n", arglist[0]);
|
||||
err = fptr_sp_session_login(g_sess, arglist[0], arglist[1], 1, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTF(E_INFO, L_SPOTIFY, "Relogin to Spotify\n");
|
||||
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);
|
||||
|
||||
void
|
||||
spotify_login(char *path);
|
||||
spotify_login(char **arglist);
|
||||
|
||||
int
|
||||
spotify_init(void);
|
||||
|
Loading…
x
Reference in New Issue
Block a user