mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-13 16:03:23 -05:00
Merge branch 'spwebapi2'
This commit is contained in:
commit
3921cf5732
44
README.md
44
README.md
@ -435,40 +435,46 @@ curl "http://localhost:3689/logout?session-id=50"
|
|||||||
|
|
||||||
## Spotify
|
## Spotify
|
||||||
|
|
||||||
forked-daapd has *some* support for Spotify. It must be compiled with the
|
forked-daapd has support for playback of the tracks in your Spotify library. It
|
||||||
`--enable-spotify option` (see
|
must have been compiled with the `--enable-spotify` option (see
|
||||||
[INSTALL](https://github.com/ejurgensen/forked-daapd/blob/master/INSTALL)).
|
[INSTALL](https://github.com/ejurgensen/forked-daapd/blob/master/INSTALL)).
|
||||||
You must have also have libspotify installed, otherwise the Spotify integration
|
You must also have libspotify installed, otherwise Spotify integration will not
|
||||||
will not be available. You can get libspotify here:
|
be available. Unfortunately the library is no longer available from Spotify, and
|
||||||
|
at the time of writing they have not provided an alternative. You can, however,
|
||||||
|
still get libspotify here:
|
||||||
|
|
||||||
- Original (binary) tar.gz, see https://developer.spotify.com
|
- Debian package (libspotify-dev), see https://apt.mopidy.com
|
||||||
- Debian package (libspotify-dev), see https://apt.mopidy.com
|
|
||||||
|
|
||||||
You must also have a Spotify premium account. If you normally log into Spotify
|
You must also have a Spotify premium account. If you normally log into Spotify
|
||||||
with your Facebook account you must first go to Spotify's web site where you can
|
with your Facebook account you must first go to Spotify's web site where you can
|
||||||
get the Spotify username and password that matches your account. With
|
get the Spotify username and password that matches your account. With
|
||||||
forked-daapd you cannot login into Spotify with your Facebook username/password.
|
forked-daapd you cannot login into Spotify with your Facebook username/password.
|
||||||
|
|
||||||
The procedure for logging in to Spotify is very much like the Remote pairing
|
The procedure for logging in to Spotify is a two-step procedure due to the
|
||||||
procedure. You must prepare a file, which should have the ending ".spotify".
|
current state of libspotify:
|
||||||
The file must have two lines: The first is your Spotify user name, and the
|
|
||||||
second is your password. Move the file to your forked-daapd library.
|
1. Put a file in your forked-daapd library containing two lines, the first being
|
||||||
Forked-daapd will then log in and add all the music in your Spotify playlists
|
your Spotify user name, and the second your password. The filename must have
|
||||||
to its database.
|
the ending ".spotify"
|
||||||
|
2. Delete the file again - forked-daapd will have read it.
|
||||||
|
3. forked-daapd will log in and add all music in your Spotify playlists to its
|
||||||
|
database. Wait until completed (follow progress in the log file).
|
||||||
|
4. In a browser, go to http://forked-daapd.local:3689/oauth and click the link
|
||||||
|
to authorize forked-daapd with Spotify.
|
||||||
|
|
||||||
Spotify will automatically notify forked-daapd about playlist updates, so you
|
Spotify will automatically notify forked-daapd about playlist updates, so you
|
||||||
should not need to restart forked-daapd to syncronize with Spotify.
|
should not need to restart forked-daapd to syncronize with Spotify. However,
|
||||||
|
Spotify only notifies about playlist updates, not new saved tracks/albums, so
|
||||||
|
you need to repeat step 4 above to load those.
|
||||||
|
|
||||||
For safety you should delete the ".spotify" file after first login. Forked-daapd
|
Forked-daapd will not store your password, but will still be able to log you in
|
||||||
will not store your password, but will still be able to log you in automatically
|
automatically afterwards, because libspotify saves a login token. You can
|
||||||
afterwards, because libspotify saves a login token. You can configure the
|
configure the location of your Spotify user data in the configuration file.
|
||||||
location of your Spotify user data in the configuration file.
|
|
||||||
|
|
||||||
To permanently logout and remove credentials, delete the contents of
|
To permanently logout and remove credentials, delete the contents of
|
||||||
`/var/cache/forked-daapd/libspotify` (while forked-daapd is stopped).
|
`/var/cache/forked-daapd/libspotify` (while forked-daapd is stopped).
|
||||||
|
|
||||||
Limitations: You will only be able to play tracks from your Spotify playlists,
|
Limitations:
|
||||||
so you can't search and listen to music from the rest of the Spotify catalogue.
|
|
||||||
You will not be able to do any playlist management through forked-daapd - use
|
You will not be able to do any playlist management through forked-daapd - use
|
||||||
a Spotify client for that. You also can only listen to your music by letting
|
a Spotify client for that. You also can only listen to your music by letting
|
||||||
forked-daapd do the playback - so that means you can't stream from forked-daapd
|
forked-daapd do the playback - so that means you can't stream from forked-daapd
|
||||||
|
129
configure.ac
129
configure.ac
@ -159,63 +159,6 @@ AC_CHECK_HEADERS(getopt.h,,)
|
|||||||
AC_CHECK_HEADERS(stdint.h,,)
|
AC_CHECK_HEADERS(stdint.h,,)
|
||||||
|
|
||||||
dnl --- Begin configuring the options ---
|
dnl --- Begin configuring the options ---
|
||||||
dnl iTunes playlists with libplist
|
|
||||||
AC_ARG_ENABLE(itunes, AS_HELP_STRING([--enable-itunes], [enable iTunes Music Library XML support (default=no)]))
|
|
||||||
AS_IF([test "x$enable_itunes" = "xyes"], [
|
|
||||||
AC_DEFINE(ITUNES, 1, [Define to 1 to enable iTunes XML support])
|
|
||||||
PKG_CHECK_MODULES(LIBPLIST, [ libplist >= 0.16 ])
|
|
||||||
])
|
|
||||||
AM_CONDITIONAL(COND_ITUNES, [test "x$enable_itunes" = "xyes"])
|
|
||||||
|
|
||||||
dnl Spotify with dynamic linking to libspotify
|
|
||||||
AC_ARG_ENABLE(spotify, AS_HELP_STRING([--enable-spotify], [enable Spotify support (default=no)]))
|
|
||||||
AS_IF([test "x$enable_spotify" = "xyes"], [
|
|
||||||
AC_DEFINE(SPOTIFY, 1, [Define to 1 to enable Spotify support])
|
|
||||||
AC_CHECK_HEADER(libspotify/api.h, , AC_MSG_ERROR([libspotify/api.h not found]))
|
|
||||||
AC_DEFINE(HAVE_SPOTIFY_H, 1, [Define to 1 if you have the <libspotify/api.h> header file.])
|
|
||||||
|
|
||||||
dnl Don't link to libspotify, but instead enable dynamic linking
|
|
||||||
SPOTIFY_CFLAGS="-rdynamic"
|
|
||||||
SPOTIFY_LIBS="-ldl"
|
|
||||||
AC_SUBST(SPOTIFY_CFLAGS)
|
|
||||||
AC_SUBST(SPOTIFY_LIBS)
|
|
||||||
])
|
|
||||||
AM_CONDITIONAL(COND_SPOTIFY, [test "x$enable_spotify" = "xyes"])
|
|
||||||
|
|
||||||
dnl LastFM support with libcurl
|
|
||||||
AC_ARG_ENABLE(lastfm, AS_HELP_STRING([--enable-lastfm], [enable LastFM support (default=no)]))
|
|
||||||
AS_IF([test "x$enable_lastfm" = "xyes"], [
|
|
||||||
AC_DEFINE(LASTFM, 1, [Define to 1 to enable LastFM support])
|
|
||||||
PKG_CHECK_MODULES(LIBCURL, [ libcurl ])
|
|
||||||
AC_CHECK_LIB([mxml], [mxmlGetOpaque], AC_DEFINE(HAVE_MXML_GETOPAQUE, 1, [Define to 1 if your mxml has mxmlGetOpaque.]))
|
|
||||||
])
|
|
||||||
AM_CONDITIONAL(COND_LASTFM, [test "x$enable_lastfm" = "xyes"])
|
|
||||||
|
|
||||||
dnl ChromeCast support with libprotobuf-c
|
|
||||||
AC_ARG_ENABLE(chromecast, AS_HELP_STRING([--enable-chromecast], [enable ChromeCast support (default=no)]))
|
|
||||||
AS_IF([test "x$enable_chromecast" = "xyes"], [
|
|
||||||
AC_DEFINE(CHROMECAST, 1, [Define to 1 to enable Chromecast support])
|
|
||||||
PKG_CHECK_MODULES(LIBPROTOBUF_C, [ libprotobuf-c >= 1.0.0 ], , [ protobuf_old="yes" ])
|
|
||||||
PKG_CHECK_MODULES(GNUTLS, [ gnutls ])
|
|
||||||
PKG_CHECK_EXISTS([ json-c >= 0.11 ],
|
|
||||||
[ PKG_CHECK_MODULES(JSON_C, [ json-c ]) ],
|
|
||||||
[ PKG_CHECK_MODULES(JSON_C, [ json ], AC_DEFINE(HAVE_JSON_C_OLD, 1, [Define 1 to if you have json-c < 0.11])) ]
|
|
||||||
)
|
|
||||||
])
|
|
||||||
AS_IF([test "x$protobuf_old" = "xyes"], [
|
|
||||||
AC_DEFINE(HAVE_PROTOBUF_OLD, 1, [Define to 1 if you have libprotobuf < 1.0.0])
|
|
||||||
LDFLAGS="${LDFLAGS} -lprotobuf-c"
|
|
||||||
])
|
|
||||||
AM_CONDITIONAL(COND_CHROMECAST, [test "x$enable_chromecast" = "xyes"])
|
|
||||||
AM_CONDITIONAL(COND_PROTOBUF_OLD, [test "x$protobuf_old" = "xyes"])
|
|
||||||
|
|
||||||
dnl MPD support
|
|
||||||
AC_ARG_ENABLE(mpd, AS_HELP_STRING([--disable-mpd], [disable MPD client protocol support (default=no)]))
|
|
||||||
AS_IF([test "x$enable_mpd" != "xno"], [
|
|
||||||
AC_DEFINE(MPD, 1, [Define to 1 to enable MPD support])
|
|
||||||
])
|
|
||||||
AM_CONDITIONAL(COND_MPD, [test "x$enable_mpd" != "xno"])
|
|
||||||
|
|
||||||
dnl ALSA
|
dnl ALSA
|
||||||
AC_ARG_WITH(alsa, AS_HELP_STRING([--without-alsa], [without ALSA support (default=no)]))
|
AC_ARG_WITH(alsa, AS_HELP_STRING([--without-alsa], [without ALSA support (default=no)]))
|
||||||
AS_IF([test "x$with_alsa" != "xno"], [
|
AS_IF([test "x$with_alsa" != "xno"], [
|
||||||
@ -234,6 +177,78 @@ AS_IF([test "x$with_pulseaudio" = "xyes"], [
|
|||||||
)
|
)
|
||||||
])
|
])
|
||||||
AM_CONDITIONAL(COND_PULSEAUDIO, [test "x$with_pulseaudio" = "xyes"])
|
AM_CONDITIONAL(COND_PULSEAUDIO, [test "x$with_pulseaudio" = "xyes"])
|
||||||
|
|
||||||
|
dnl Build with libcurl
|
||||||
|
AC_ARG_WITH([libcurl], AS_HELP_STRING([--without-libcurl], [without libcurl (default=no)]))
|
||||||
|
AS_IF([test "x$with_libcurl" != "xno"], [
|
||||||
|
AC_DEFINE(HAVE_LIBCURL, 1, [Define to 1 to build with libcurl])
|
||||||
|
PKG_CHECK_MODULES(LIBCURL, [ libcurl ])
|
||||||
|
])
|
||||||
|
|
||||||
|
dnl Build with json-c
|
||||||
|
AC_ARG_WITH([json], AS_HELP_STRING([--without-json-c], [without json-c (default=no)]))
|
||||||
|
AS_IF([test "x$with_json" != "xno"], [
|
||||||
|
AC_DEFINE(HAVE_JSON, 1, [Define to 1 to build with json-c])
|
||||||
|
PKG_CHECK_EXISTS([ json-c >= 0.11 ],
|
||||||
|
[ PKG_CHECK_MODULES(JSON_C, [ json-c ]) ],
|
||||||
|
[ PKG_CHECK_MODULES(JSON_C, [ json ], AC_DEFINE(HAVE_JSON_C_OLD, 1, [Define 1 to if you have json-c < 0.11])) ]
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
dnl iTunes playlists with libplist
|
||||||
|
AC_ARG_ENABLE(itunes, AS_HELP_STRING([--enable-itunes], [enable iTunes Music Library XML support (default=no)]))
|
||||||
|
AS_IF([test "x$enable_itunes" = "xyes"], [
|
||||||
|
AC_DEFINE(ITUNES, 1, [Define to 1 to enable iTunes XML support])
|
||||||
|
PKG_CHECK_MODULES(LIBPLIST, [ libplist >= 0.16 ])
|
||||||
|
])
|
||||||
|
AM_CONDITIONAL(COND_ITUNES, [test "x$enable_itunes" = "xyes"])
|
||||||
|
|
||||||
|
dnl Spotify with dynamic linking to libspotify
|
||||||
|
AC_ARG_ENABLE(spotify, AS_HELP_STRING([--enable-spotify], [enable Spotify support (default=no)]))
|
||||||
|
AS_IF([test "x$enable_spotify" = "xyes"], [
|
||||||
|
AC_DEFINE(SPOTIFY, 1, [Define to 1 to enable Spotify support])
|
||||||
|
AS_IF([test "x$with_json" = "xno"], AC_MSG_ERROR([Spotify support requires json-c]))
|
||||||
|
AC_CHECK_HEADER(libspotify/api.h, , AC_MSG_ERROR([libspotify/api.h not found]))
|
||||||
|
AC_DEFINE(HAVE_SPOTIFY_H, 1, [Define to 1 if you have the <libspotify/api.h> header file.])
|
||||||
|
|
||||||
|
dnl Don't link to libspotify, but instead enable dynamic linking
|
||||||
|
SPOTIFY_CFLAGS="-rdynamic"
|
||||||
|
SPOTIFY_LIBS="-ldl"
|
||||||
|
AC_SUBST(SPOTIFY_CFLAGS)
|
||||||
|
AC_SUBST(SPOTIFY_LIBS)
|
||||||
|
])
|
||||||
|
AM_CONDITIONAL(COND_SPOTIFY, [test "x$enable_spotify" = "xyes"])
|
||||||
|
|
||||||
|
dnl LastFM support with libcurl
|
||||||
|
AC_ARG_ENABLE(lastfm, AS_HELP_STRING([--enable-lastfm], [enable LastFM support (default=no)]))
|
||||||
|
AS_IF([test "x$enable_lastfm" = "xyes"], [
|
||||||
|
AC_DEFINE(LASTFM, 1, [Define to 1 to enable LastFM support])
|
||||||
|
AS_IF([test "x$with_libcurl" = "xno"], AC_MSG_ERROR([LastFM support requires libcurl]))
|
||||||
|
AC_CHECK_LIB([mxml], [mxmlGetOpaque], AC_DEFINE(HAVE_MXML_GETOPAQUE, 1, [Define to 1 if your mxml has mxmlGetOpaque.]))
|
||||||
|
])
|
||||||
|
AM_CONDITIONAL(COND_LASTFM, [test "x$enable_lastfm" = "xyes"])
|
||||||
|
|
||||||
|
dnl ChromeCast support with libprotobuf-c
|
||||||
|
AC_ARG_ENABLE(chromecast, AS_HELP_STRING([--enable-chromecast], [enable ChromeCast support (default=no)]))
|
||||||
|
AS_IF([test "x$enable_chromecast" = "xyes"], [
|
||||||
|
AC_DEFINE(CHROMECAST, 1, [Define to 1 to enable Chromecast support])
|
||||||
|
AS_IF([test "x$with_json" = "xno"], AC_MSG_ERROR([Chromecast support requires json-c]))
|
||||||
|
PKG_CHECK_MODULES(LIBPROTOBUF_C, [ libprotobuf-c >= 1.0.0 ], , [ protobuf_old="yes" ])
|
||||||
|
PKG_CHECK_MODULES(GNUTLS, [ gnutls ])
|
||||||
|
])
|
||||||
|
AS_IF([test "x$protobuf_old" = "xyes"], [
|
||||||
|
AC_DEFINE(HAVE_PROTOBUF_OLD, 1, [Define to 1 if you have libprotobuf < 1.0.0])
|
||||||
|
LDFLAGS="${LDFLAGS} -lprotobuf-c"
|
||||||
|
])
|
||||||
|
AM_CONDITIONAL(COND_CHROMECAST, [test "x$enable_chromecast" = "xyes"])
|
||||||
|
AM_CONDITIONAL(COND_PROTOBUF_OLD, [test "x$protobuf_old" = "xyes"])
|
||||||
|
|
||||||
|
dnl MPD support
|
||||||
|
AC_ARG_ENABLE(mpd, AS_HELP_STRING([--disable-mpd], [disable MPD client protocol support (default=no)]))
|
||||||
|
AS_IF([test "x$enable_mpd" != "xno"], [
|
||||||
|
AC_DEFINE(MPD, 1, [Define to 1 to enable MPD support])
|
||||||
|
])
|
||||||
|
AM_CONDITIONAL(COND_MPD, [test "x$enable_mpd" != "xno"])
|
||||||
dnl --- End options ---
|
dnl --- End options ---
|
||||||
|
|
||||||
dnl Checks for header files.
|
dnl Checks for header files.
|
||||||
|
@ -1129,8 +1129,8 @@ source_item_stream_get(struct artwork_ctx *ctx)
|
|||||||
|
|
||||||
memset(&client, 0, sizeof(struct http_client_ctx));
|
memset(&client, 0, sizeof(struct http_client_ctx));
|
||||||
client.url = url;
|
client.url = url;
|
||||||
client.headers = kv;
|
client.input_headers = kv;
|
||||||
client.body = ctx->evbuf;
|
client.input_body = ctx->evbuf;
|
||||||
|
|
||||||
if (http_client_request(&client) < 0)
|
if (http_client_request(&client) < 0)
|
||||||
goto out_kv;
|
goto out_kv;
|
||||||
|
4
src/db.c
4
src/db.c
@ -3906,7 +3906,7 @@ db_spotify_purge(void)
|
|||||||
ret = db_query_run(queries[i], 0, 1);
|
ret = db_query_run(queries[i], 0, 1);
|
||||||
|
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
DPRINTF(E_DBG, L_DB, "Purged %d rows\n", sqlite3_changes(hdl));
|
DPRINTF(E_DBG, L_DB, "Processed %d rows\n", sqlite3_changes(hdl));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable the spotify directory by setting 'disabled' to INOTIFY_FAKE_COOKIE value
|
// Disable the spotify directory by setting 'disabled' to INOTIFY_FAKE_COOKIE value
|
||||||
@ -3950,7 +3950,7 @@ db_spotify_pl_delete(int id)
|
|||||||
|
|
||||||
/* Spotify */
|
/* Spotify */
|
||||||
void
|
void
|
||||||
db_spotify_files_delete()
|
db_spotify_files_delete(void)
|
||||||
{
|
{
|
||||||
#define Q_TMPL "DELETE FROM files WHERE path LIKE 'spotify:%%' AND NOT path IN (SELECT filepath FROM playlistitems);"
|
#define Q_TMPL "DELETE FROM files WHERE path LIKE 'spotify:%%' AND NOT path IN (SELECT filepath FROM playlistitems);"
|
||||||
char *query;
|
char *query;
|
||||||
|
2
src/db.h
2
src/db.h
@ -620,7 +620,7 @@ void
|
|||||||
db_spotify_pl_delete(int id);
|
db_spotify_pl_delete(int id);
|
||||||
|
|
||||||
void
|
void
|
||||||
db_spotify_files_delete();
|
db_spotify_files_delete(void);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Admin */
|
/* Admin */
|
||||||
|
@ -1222,11 +1222,8 @@ bulk_scan(int flags)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Protect spotify from the imminent purge if rescanning */
|
/* Protect spotify from the imminent purge if rescanning */
|
||||||
if (flags & F_SCAN_RESCAN)
|
db_file_ping_bymatch("spotify:", 0);
|
||||||
{
|
db_pl_ping_bymatch("spotify:", 0);
|
||||||
db_file_ping_bymatch("spotify:", 0);
|
|
||||||
db_pl_ping_bymatch("spotify:", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_SCAN, "Purging old database content\n");
|
DPRINTF(E_DBG, L_SCAN, "Purging old database content\n");
|
||||||
db_purge_cruft(start);
|
db_purge_cruft(start);
|
||||||
|
164
src/http.c
164
src/http.c
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Espen Jürgensen <espenjurgensen@gmail.com>
|
* Copyright (C) 2016 Espen Jürgensen <espenjurgensen@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -36,6 +36,10 @@
|
|||||||
|
|
||||||
#include <event2/event.h>
|
#include <event2/event.h>
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBCURL
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
@ -128,10 +132,10 @@ request_cb(struct evhttp_request *req, void *arg)
|
|||||||
|
|
||||||
ctx->ret = 0;
|
ctx->ret = 0;
|
||||||
|
|
||||||
if (ctx->headers)
|
if (ctx->input_headers)
|
||||||
headers_save(ctx->headers, evhttp_request_get_input_headers(req));
|
headers_save(ctx->input_headers, evhttp_request_get_input_headers(req));
|
||||||
if (ctx->body)
|
if (ctx->input_body)
|
||||||
evbuffer_add_buffer(ctx->body, evhttp_request_get_input_buffer(req));
|
evbuffer_add_buffer(ctx->input_body, evhttp_request_get_input_buffer(req));
|
||||||
|
|
||||||
event_base_loopbreak(ctx->evbase);
|
event_base_loopbreak(ctx->evbase);
|
||||||
|
|
||||||
@ -171,8 +175,8 @@ request_header_cb(struct evhttp_request *req, void *arg)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int
|
static int
|
||||||
http_client_request(struct http_client_ctx *ctx)
|
http_client_request_impl(struct http_client_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct evhttp_connection *evcon;
|
struct evhttp_connection *evcon;
|
||||||
struct evhttp_request *req;
|
struct evhttp_request *req;
|
||||||
@ -269,6 +273,144 @@ http_client_request(struct http_client_ctx *ctx)
|
|||||||
return ctx->ret;
|
return ctx->ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBCURL
|
||||||
|
static size_t
|
||||||
|
curl_request_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
|
||||||
|
{
|
||||||
|
size_t realsize;
|
||||||
|
struct http_client_ctx *ctx;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
realsize = size * nmemb;
|
||||||
|
ctx = (struct http_client_ctx *)userdata;
|
||||||
|
|
||||||
|
if (!ctx->input_body)
|
||||||
|
return realsize;
|
||||||
|
|
||||||
|
ret = evbuffer_add(ctx->input_body, ptr, realsize);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_HTTP, "Error adding reply from %s to input buffer\n", ctx->url);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
https_client_request_impl(struct http_client_ctx *ctx)
|
||||||
|
{
|
||||||
|
CURL *curl;
|
||||||
|
CURLcode res;
|
||||||
|
struct curl_slist *headers;
|
||||||
|
struct onekeyval *okv;
|
||||||
|
char header[1024];
|
||||||
|
|
||||||
|
curl = curl_easy_init();
|
||||||
|
if (!curl)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_HTTP, "Error: Could not get curl handle\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, ctx->url);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "forked-daapd/" VERSION);
|
||||||
|
|
||||||
|
if (ctx->output_headers)
|
||||||
|
{
|
||||||
|
headers = NULL;
|
||||||
|
for (okv = ctx->output_headers->head; okv; okv = okv->next)
|
||||||
|
{
|
||||||
|
snprintf(header, sizeof(header), "%s: %s", okv->name, okv->value);
|
||||||
|
headers = curl_slist_append(headers, header);
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->output_body)
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ctx->output_body);
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, HTTP_CLIENT_TIMEOUT);
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_request_cb);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx);
|
||||||
|
|
||||||
|
/* Make request */
|
||||||
|
DPRINTF(E_INFO, L_HTTP, "Making request for %s\n", ctx->url);
|
||||||
|
|
||||||
|
res = curl_easy_perform(curl);
|
||||||
|
if (res != CURLE_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_HTTP, "Request to %s failed: %s\n", ctx->url, curl_easy_strerror(res));
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* HAVE_LIBCURL */
|
||||||
|
|
||||||
|
int
|
||||||
|
http_client_request(struct http_client_ctx *ctx)
|
||||||
|
{
|
||||||
|
if (strncmp(ctx->url, "http:", strlen("http:")) == 0)
|
||||||
|
return http_client_request_impl(ctx);
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBCURL
|
||||||
|
if (strncmp(ctx->url, "https:", strlen("https:")) == 0)
|
||||||
|
return https_client_request_impl(ctx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DPRINTF(E_LOG, L_HTTP, "Request for %s is not supported (not built with libcurl?)\n", ctx->url);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
http_form_urlencode(struct keyval *kv)
|
||||||
|
{
|
||||||
|
struct evbuffer *evbuf;
|
||||||
|
struct onekeyval *okv;
|
||||||
|
char *body;
|
||||||
|
char *k;
|
||||||
|
char *v;
|
||||||
|
|
||||||
|
evbuf = evbuffer_new();
|
||||||
|
|
||||||
|
for (okv = kv->head; okv; okv = okv->next)
|
||||||
|
{
|
||||||
|
k = evhttp_encode_uri(okv->name);
|
||||||
|
if (!k)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
v = evhttp_encode_uri(okv->value);
|
||||||
|
if (!v)
|
||||||
|
{
|
||||||
|
free(k);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
evbuffer_add_printf(evbuf, "%s=%s", k, v);
|
||||||
|
if (okv->next)
|
||||||
|
evbuffer_add_printf(evbuf, "&");
|
||||||
|
|
||||||
|
free(k);
|
||||||
|
free(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
evbuffer_add(evbuf, "\n", 1);
|
||||||
|
|
||||||
|
body = evbuffer_readln(evbuf, NULL, EVBUFFER_EOL_ANY);
|
||||||
|
|
||||||
|
evbuffer_free(evbuf);
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_HTTP, "Parameters in request are: %s\n", body);
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
http_stream_setup(char **stream, const char *url)
|
http_stream_setup(char **stream, const char *url)
|
||||||
{
|
{
|
||||||
@ -296,7 +438,7 @@ http_stream_setup(char **stream, const char *url)
|
|||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
ctx.url = url;
|
ctx.url = url;
|
||||||
ctx.body = evbuf;
|
ctx.input_body = evbuf;
|
||||||
|
|
||||||
ret = http_client_request(&ctx);
|
ret = http_client_request(&ctx);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
@ -308,13 +450,13 @@ http_stream_setup(char **stream, const char *url)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pad with CRLF because evbuffer_readln() might not read the last line otherwise
|
// Pad with CRLF because evbuffer_readln() might not read the last line otherwise
|
||||||
evbuffer_add(ctx.body, "\r\n", 2);
|
evbuffer_add(ctx.input_body, "\r\n", 2);
|
||||||
|
|
||||||
/* Read the playlist until the first stream link is found, but give up if
|
/* Read the playlist until the first stream link is found, but give up if
|
||||||
* nothing is found in the first 10 lines
|
* nothing is found in the first 10 lines
|
||||||
*/
|
*/
|
||||||
n = 0;
|
n = 0;
|
||||||
while ((line = evbuffer_readln(ctx.body, NULL, EVBUFFER_EOL_ANY)) && (n < 10))
|
while ((line = evbuffer_readln(ctx.input_body, NULL, EVBUFFER_EOL_ANY)) && (n < 10))
|
||||||
{
|
{
|
||||||
n++;
|
n++;
|
||||||
if (strncasecmp(line, "http://", strlen("http://")) == 0)
|
if (strncasecmp(line, "http://", strlen("http://")) == 0)
|
||||||
@ -328,7 +470,7 @@ http_stream_setup(char **stream, const char *url)
|
|||||||
free(line);
|
free(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
evbuffer_free(ctx.body);
|
evbuffer_free(ctx.input_body);
|
||||||
|
|
||||||
if (n != -1)
|
if (n != -1)
|
||||||
{
|
{
|
||||||
|
25
src/http.h
25
src/http.h
@ -10,13 +10,18 @@
|
|||||||
|
|
||||||
struct http_client_ctx
|
struct http_client_ctx
|
||||||
{
|
{
|
||||||
|
/* Destination URL, header and body of outgoing request body (headers and
|
||||||
|
* body is currently only supported for https)
|
||||||
|
*/
|
||||||
const char *url;
|
const char *url;
|
||||||
|
struct keyval *output_headers;
|
||||||
|
char *output_body;
|
||||||
|
|
||||||
/* A keyval/evbuf to store response headers and body.
|
/* A keyval/evbuf to store response headers and body.
|
||||||
* Can be set to NULL to ignore that part of the response.
|
* Can be set to NULL to ignore that part of the response.
|
||||||
*/
|
*/
|
||||||
struct keyval *headers;
|
struct keyval *input_headers;
|
||||||
struct evbuffer *body;
|
struct evbuffer *input_body;
|
||||||
|
|
||||||
/* Cut the connection after the headers have been received
|
/* Cut the connection after the headers have been received
|
||||||
* Used for getting Shoutcast/ICY headers for old versions of libav/ffmpeg
|
* Used for getting Shoutcast/ICY headers for old versions of libav/ffmpeg
|
||||||
@ -47,15 +52,27 @@ struct http_icy_metadata
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/* Generic HTTP client. No support for https.
|
/* Make a http(s) request. We use libcurl to make https requests. We could use
|
||||||
|
* libevent and avoid the dependency, but for SSL, libevent needs to be v2.1
|
||||||
|
* or better, which is still a bit too new to be in the major distros.
|
||||||
*
|
*
|
||||||
* @param ctx HTTP request params, see above
|
* @param ctx HTTP request params, see above
|
||||||
* @return 0 if successful, -1 if an error occurred
|
* @return 0 if successful, -1 if an error occurred (e.g. no libcurl)
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
http_client_request(struct http_client_ctx *ctx);
|
http_client_request(struct http_client_ctx *ctx);
|
||||||
|
|
||||||
|
|
||||||
|
/* Converts the keyval dictionary to a application/x-www-form-urlencoded string.
|
||||||
|
* The values will be uri_encoded. Example output: "key1=foo%20bar&key2=123".
|
||||||
|
*
|
||||||
|
* @param kv is the struct containing the parameters
|
||||||
|
* @return encoded string if succesful, NULL if an error occurred
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
http_form_urlencode(struct keyval *kv);
|
||||||
|
|
||||||
|
|
||||||
/* Returns a newly allocated string with the first stream in the m3u given in
|
/* Returns a newly allocated string with the first stream in the m3u given in
|
||||||
* url. If url is not a m3u, the string will be a copy of url.
|
* url. If url is not a m3u, the string will be a copy of url.
|
||||||
*
|
*
|
||||||
|
76
src/httpd.c
76
src/httpd.c
@ -64,6 +64,9 @@
|
|||||||
#ifdef LASTFM
|
#ifdef LASTFM
|
||||||
# include "lastfm.h"
|
# include "lastfm.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_SPOTIFY_H
|
||||||
|
# include "spotify.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* HTTP client quirks by User-Agent, from mt-daapd
|
* HTTP client quirks by User-Agent, from mt-daapd
|
||||||
@ -140,6 +143,7 @@ static struct evhttp *evhttpd;
|
|||||||
static pthread_t tid_httpd;
|
static pthread_t tid_httpd;
|
||||||
|
|
||||||
static char *allow_origin;
|
static char *allow_origin;
|
||||||
|
static int httpd_port;
|
||||||
|
|
||||||
#ifdef HAVE_LIBEVENT2_OLD
|
#ifdef HAVE_LIBEVENT2_OLD
|
||||||
struct stream_ctx *g_st;
|
struct stream_ctx *g_st;
|
||||||
@ -198,6 +202,61 @@ scrobble_cb(void *arg)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static void
|
||||||
|
oauth_interface(struct evhttp_request *req, const char *uri)
|
||||||
|
{
|
||||||
|
struct evbuffer *evbuf;
|
||||||
|
struct evkeyvalq query;
|
||||||
|
const char *req_uri;
|
||||||
|
const char *ptr;
|
||||||
|
char redirect_uri[256];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
req_uri = evhttp_request_get_uri(req);
|
||||||
|
|
||||||
|
evbuf = evbuffer_new();
|
||||||
|
if (!evbuf)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_HTTPD, "Could not alloc evbuf for oauth\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
evbuffer_add_printf(evbuf, "<H1>forked-daapd oauth</H1>\n\n");
|
||||||
|
|
||||||
|
memset(&query, 0, sizeof(struct evkeyvalq));
|
||||||
|
|
||||||
|
ptr = strchr(req_uri, '?');
|
||||||
|
if (ptr)
|
||||||
|
{
|
||||||
|
ret = evhttp_parse_query_str(ptr + 1, &query);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
evbuffer_add_printf(evbuf, "OAuth error: Could not parse parameters in callback (%s)\n", req_uri);
|
||||||
|
|
||||||
|
httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
|
||||||
|
evbuffer_free(evbuf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_SPOTIFY_H
|
||||||
|
snprintf(redirect_uri, sizeof(redirect_uri), "http://forked-daapd.local:%d/oauth/spotify", httpd_port);
|
||||||
|
|
||||||
|
if (strncmp(uri, "/oauth/spotify", strlen("/oauth/spotify")) == 0)
|
||||||
|
spotify_oauth_callback(evbuf, &query, redirect_uri);
|
||||||
|
else
|
||||||
|
spotify_oauth_interface(evbuf, redirect_uri);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
evbuffer_add_printf(evbuf, "<p><i>(sorry about this ugly interface)</i></p>\n");
|
||||||
|
|
||||||
|
evhttp_clear_headers(&query);
|
||||||
|
|
||||||
|
httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
|
||||||
|
|
||||||
|
evbuffer_free(evbuf);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
stream_end_register(struct stream_ctx *st)
|
stream_end_register(struct stream_ctx *st)
|
||||||
{
|
{
|
||||||
@ -912,6 +971,12 @@ serve_file(struct evhttp_request *req, char *uri)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strncmp(uri, "/oauth", strlen("/oauth")) == 0)
|
||||||
|
{
|
||||||
|
oauth_interface(req, uri);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ret = snprintf(path, sizeof(path), "%s%s", WEBFACE_ROOT, uri + 1); /* skip starting '/' */
|
ret = snprintf(path, sizeof(path), "%s%s", WEBFACE_ROOT, uri + 1); /* skip starting '/' */
|
||||||
if ((ret < 0) || (ret >= sizeof(path)))
|
if ((ret < 0) || (ret >= sizeof(path)))
|
||||||
{
|
{
|
||||||
@ -1347,7 +1412,6 @@ int
|
|||||||
httpd_init(void)
|
httpd_init(void)
|
||||||
{
|
{
|
||||||
int v6enabled;
|
int v6enabled;
|
||||||
unsigned short port;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
httpd_exit = 0;
|
httpd_exit = 0;
|
||||||
@ -1428,7 +1492,7 @@ httpd_init(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6");
|
v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6");
|
||||||
port = cfg_getint(cfg_getsec(cfg, "library"), "port");
|
httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port");
|
||||||
|
|
||||||
// For CORS headers
|
// For CORS headers
|
||||||
allow_origin = cfg_getstr(cfg_getsec(cfg, "general"), "allow_origin");
|
allow_origin = cfg_getstr(cfg_getsec(cfg, "general"), "allow_origin");
|
||||||
@ -1442,20 +1506,20 @@ httpd_init(void)
|
|||||||
|
|
||||||
if (v6enabled)
|
if (v6enabled)
|
||||||
{
|
{
|
||||||
ret = evhttp_bind_socket(evhttpd, "::", port);
|
ret = evhttp_bind_socket(evhttpd, "::", httpd_port);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_HTTPD, "Could not bind to port %d with IPv6, falling back to IPv4\n", port);
|
DPRINTF(E_LOG, L_HTTPD, "Could not bind to port %d with IPv6, falling back to IPv4\n", httpd_port);
|
||||||
v6enabled = 0;
|
v6enabled = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!v6enabled)
|
if (!v6enabled)
|
||||||
{
|
{
|
||||||
ret = evhttp_bind_socket(evhttpd, "0.0.0.0", port);
|
ret = evhttp_bind_socket(evhttpd, "0.0.0.0", httpd_port);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_FATAL, L_HTTPD, "Could not bind to port %d (forked-daapd already running?)\n", port);
|
DPRINTF(E_FATAL, L_HTTPD, "Could not bind to port %d (forked-daapd already running?)\n", httpd_port);
|
||||||
goto bind_fail;
|
goto bind_fail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
150
src/lastfm.c
150
src/lastfm.c
@ -39,14 +39,7 @@
|
|||||||
#include "lastfm.h"
|
#include "lastfm.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
|
#include "http.h"
|
||||||
|
|
||||||
struct https_client_ctx
|
|
||||||
{
|
|
||||||
const char *url;
|
|
||||||
const char *body;
|
|
||||||
struct evbuffer *data;
|
|
||||||
};
|
|
||||||
|
|
||||||
// LastFM becomes disabled if we get a scrobble, try initialising session,
|
// LastFM becomes disabled if we get a scrobble, try initialising session,
|
||||||
// but can't (probably no session key in db because user does not use LastFM)
|
// but can't (probably no session key in db because user does not use LastFM)
|
||||||
@ -172,51 +165,6 @@ credentials_read(char *path, char **username, char **password)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Converts parameters to a string in application/x-www-form-urlencoded format */
|
|
||||||
static int
|
|
||||||
body_print(char **body, struct keyval *kv)
|
|
||||||
{
|
|
||||||
struct evbuffer *evbuf;
|
|
||||||
struct onekeyval *okv;
|
|
||||||
char *k;
|
|
||||||
char *v;
|
|
||||||
|
|
||||||
evbuf = evbuffer_new();
|
|
||||||
|
|
||||||
for (okv = kv->head; okv; okv = okv->next)
|
|
||||||
{
|
|
||||||
k = evhttp_encode_uri(okv->name);
|
|
||||||
if (!k)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
v = evhttp_encode_uri(okv->value);
|
|
||||||
if (!v)
|
|
||||||
{
|
|
||||||
free(k);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
evbuffer_add(evbuf, k, strlen(k));
|
|
||||||
evbuffer_add(evbuf, "=", 1);
|
|
||||||
evbuffer_add(evbuf, v, strlen(v));
|
|
||||||
if (okv->next)
|
|
||||||
evbuffer_add(evbuf, "&", 1);
|
|
||||||
|
|
||||||
free(k);
|
|
||||||
free(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
evbuffer_add(evbuf, "\n", 1);
|
|
||||||
|
|
||||||
*body = evbuffer_readln(evbuf, NULL, EVBUFFER_EOL_ANY);
|
|
||||||
|
|
||||||
evbuffer_free(evbuf);
|
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_LASTFM, "Parameters in request are: %s\n", *body);
|
|
||||||
|
|
||||||
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
|
||||||
param_sign(struct keyval *kv)
|
param_sign(struct keyval *kv)
|
||||||
@ -288,28 +236,8 @@ mxmlGetOpaque(mxml_node_t *node) /* I - Node to get */
|
|||||||
|
|
||||||
/* --------------------------------- MAIN --------------------------------- */
|
/* --------------------------------- MAIN --------------------------------- */
|
||||||
|
|
||||||
static size_t
|
|
||||||
request_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
|
|
||||||
{
|
|
||||||
size_t realsize;
|
|
||||||
struct https_client_ctx *ctx;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
realsize = size * nmemb;
|
|
||||||
ctx = (struct https_client_ctx *)userdata;
|
|
||||||
|
|
||||||
ret = evbuffer_add(ctx->data, ptr, realsize);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Error adding reply from LastFM to data buffer\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return realsize;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
response_proces(struct https_client_ctx *ctx)
|
response_proces(struct http_client_ctx *ctx)
|
||||||
{
|
{
|
||||||
mxml_node_t *tree;
|
mxml_node_t *tree;
|
||||||
mxml_node_t *s_node;
|
mxml_node_t *s_node;
|
||||||
@ -319,9 +247,9 @@ response_proces(struct https_client_ctx *ctx)
|
|||||||
char *sk;
|
char *sk;
|
||||||
|
|
||||||
// NULL-terminate the buffer
|
// NULL-terminate the buffer
|
||||||
evbuffer_add(ctx->data, "", 1);
|
evbuffer_add(ctx->input_body, "", 1);
|
||||||
|
|
||||||
body = (char *)evbuffer_pullup(ctx->data, -1);
|
body = (char *)evbuffer_pullup(ctx->input_body, -1);
|
||||||
if (!body || (strlen(body) == 0))
|
if (!body || (strlen(body) == 0))
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Empty response\n");
|
DPRINTF(E_LOG, L_LASTFM, "Empty response\n");
|
||||||
@ -380,59 +308,10 @@ response_proces(struct https_client_ctx *ctx)
|
|||||||
mxmlDelete(tree);
|
mxmlDelete(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We use libcurl to make the request. We could use libevent and avoid the
|
|
||||||
// dependency, but for SSL, libevent needs to be v2.1 or better, which is still
|
|
||||||
// a bit too new to be in the major distros
|
|
||||||
static int
|
|
||||||
https_client_request(struct https_client_ctx *ctx)
|
|
||||||
{
|
|
||||||
CURL *curl;
|
|
||||||
CURLcode res;
|
|
||||||
|
|
||||||
curl = curl_easy_init();
|
|
||||||
if (!curl)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Error: Could not get curl handle\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
|
|
||||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ctx->body);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, ctx->url);
|
|
||||||
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, request_cb);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx);
|
|
||||||
|
|
||||||
ctx->data = evbuffer_new();
|
|
||||||
if (!ctx->data)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Could not create evbuffer for LastFM response\n");
|
|
||||||
curl_easy_cleanup(curl);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
res = curl_easy_perform(curl);
|
|
||||||
if (res != CURLE_OK)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Request to %s failed: %s\n", ctx->url, curl_easy_strerror(res));
|
|
||||||
curl_easy_cleanup(curl);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
response_proces(ctx);
|
|
||||||
|
|
||||||
evbuffer_free(ctx->data);
|
|
||||||
|
|
||||||
curl_easy_cleanup(curl);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
request_post(char *method, struct keyval *kv, int auth)
|
request_post(char *method, struct keyval *kv, int auth)
|
||||||
{
|
{
|
||||||
struct https_client_ctx ctx;
|
struct http_client_ctx ctx;
|
||||||
char *body;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = keyval_add(kv, "method", method);
|
ret = keyval_add(kv, "method", method);
|
||||||
@ -453,18 +332,27 @@ request_post(char *method, struct keyval *kv, int auth)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = body_print(&body, kv);
|
memset(&ctx, 0, sizeof(struct http_client_ctx));
|
||||||
|
|
||||||
|
ctx.output_body = http_form_urlencode(kv);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Aborting request, body_print failed\n");
|
DPRINTF(E_LOG, L_LASTFM, "Aborting request, http_form_urlencode failed\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&ctx, 0, sizeof(struct https_client_ctx));
|
|
||||||
ctx.url = auth ? auth_url : api_url;
|
ctx.url = auth ? auth_url : api_url;
|
||||||
ctx.body = body;
|
ctx.input_body = evbuffer_new();
|
||||||
|
|
||||||
ret = https_client_request(&ctx);
|
ret = http_client_request(&ctx);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out_free_ctx;
|
||||||
|
|
||||||
|
response_proces(&ctx);
|
||||||
|
|
||||||
|
out_free_ctx:
|
||||||
|
free(ctx.output_body);
|
||||||
|
evbuffer_free(ctx.input_body);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL;
|
|||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "worker.h"
|
#include "worker.h"
|
||||||
|
|
||||||
#ifdef LASTFM
|
#ifdef HAVE_LIBCURL
|
||||||
# include <curl/curl.h>
|
# include <curl/curl.h>
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_SPOTIFY_H
|
#ifdef HAVE_SPOTIFY_H
|
||||||
@ -632,7 +632,7 @@ main(int argc, char **argv)
|
|||||||
#endif
|
#endif
|
||||||
av_log_set_callback(logger_ffmpeg);
|
av_log_set_callback(logger_ffmpeg);
|
||||||
|
|
||||||
#ifdef LASTFM
|
#ifdef HAVE_LIBCURL
|
||||||
/* Initialize libcurl */
|
/* Initialize libcurl */
|
||||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||||
#endif
|
#endif
|
||||||
@ -810,6 +810,9 @@ main(int argc, char **argv)
|
|||||||
goto mdns_reg_fail;
|
goto mdns_reg_fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Register this CNAME with mDNS for OAuth */
|
||||||
|
mdns_cname("forked-daapd.local");
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
/* Set up signal fd */
|
/* Set up signal fd */
|
||||||
sigfd = signalfd(-1, &sigs, SFD_NONBLOCK | SFD_CLOEXEC);
|
sigfd = signalfd(-1, &sigs, SFD_NONBLOCK | SFD_CLOEXEC);
|
||||||
@ -936,7 +939,7 @@ main(int argc, char **argv)
|
|||||||
|
|
||||||
signal_block_fail:
|
signal_block_fail:
|
||||||
gcrypt_init_fail:
|
gcrypt_init_fail:
|
||||||
#ifdef LASTFM
|
#ifdef HAVE_LIBCURL
|
||||||
curl_global_cleanup();
|
curl_global_cleanup();
|
||||||
#endif
|
#endif
|
||||||
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 13)
|
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 13)
|
||||||
|
10
src/mdns.h
10
src/mdns.h
@ -37,6 +37,16 @@ mdns_deinit(void);
|
|||||||
int
|
int
|
||||||
mdns_register(char *name, char *type, int port, char **txt);
|
mdns_register(char *name, char *type, int port, char **txt);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Register a CNAME record, it will be an alias for hostname
|
||||||
|
* Call only from the main thread!
|
||||||
|
*
|
||||||
|
* @in name The CNAME alias, e.g. "forked-daapd.local"
|
||||||
|
* @return 0 on success, -1 on error
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mdns_cname(char *name);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Start a service browser, a callback will be made when the service changes state
|
* Start a service browser, a callback will be made when the service changes state
|
||||||
* Call only from the main thread!
|
* Call only from the main thread!
|
||||||
|
173
src/mdns_avahi.c
173
src/mdns_avahi.c
@ -33,6 +33,7 @@
|
|||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <net/if.h>
|
#include <net/if.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <event2/event.h>
|
#include <event2/event.h>
|
||||||
|
|
||||||
@ -351,8 +352,15 @@ struct mdns_record_browser {
|
|||||||
int port;
|
int port;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum publish
|
||||||
|
{
|
||||||
|
MDNS_PUBLISH_SERVICE,
|
||||||
|
MDNS_PUBLISH_CNAME,
|
||||||
|
};
|
||||||
|
|
||||||
struct mdns_group_entry
|
struct mdns_group_entry
|
||||||
{
|
{
|
||||||
|
enum publish publish;
|
||||||
char *name;
|
char *name;
|
||||||
char *type;
|
char *type;
|
||||||
int port;
|
int port;
|
||||||
@ -622,13 +630,100 @@ entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_U
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
_create_services(void)
|
create_group_entry(struct mdns_group_entry *ge, int commit)
|
||||||
{
|
{
|
||||||
struct mdns_group_entry *pentry;
|
char hostname[HOST_NAME_MAX + 1];
|
||||||
|
char rdata[HOST_NAME_MAX + 6 + 1]; // Includes room for ".local" and 0-terminator
|
||||||
|
int count;
|
||||||
|
int i;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_MDNS, "Creating service group\n");
|
if (!mdns_group)
|
||||||
|
{
|
||||||
|
mdns_group = avahi_entry_group_new(mdns_client, entry_group_callback, NULL);
|
||||||
|
if (!mdns_group)
|
||||||
|
{
|
||||||
|
DPRINTF(E_WARN, L_MDNS, "Could not create Avahi EntryGroup: %s\n", MDNSERR);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ge->publish == MDNS_PUBLISH_SERVICE)
|
||||||
|
{
|
||||||
|
DPRINTF(E_DBG, L_MDNS, "Adding service %s/%s\n", ge->name, ge->type);
|
||||||
|
|
||||||
|
ret = avahi_entry_group_add_service_strlst(mdns_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0,
|
||||||
|
ge->name, ge->type,
|
||||||
|
NULL, NULL, ge->port, ge->txt);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MDNS, "Could not add mDNS service %s/%s: %s\n", ge->name, ge->type, avahi_strerror(ret));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ge->publish == MDNS_PUBLISH_CNAME)
|
||||||
|
{
|
||||||
|
DPRINTF(E_DBG, L_MDNS, "Adding CNAME record %s\n", ge->name);
|
||||||
|
|
||||||
|
ret = gethostname(hostname, HOST_NAME_MAX);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MDNS, "Could not add CNAME %s, gethostname failed\n", ge->name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note, gethostname does not guarantee 0-termination
|
||||||
|
ret = snprintf(rdata, sizeof(rdata), ".%s.local", hostname);
|
||||||
|
if (!(ret > 0 && ret < sizeof(rdata)))
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MDNS, "Could not add CNAME %s, hostname is invalid\n", ge->name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to dns string: .forked-daapd.local -> \12forked-daapd\6local
|
||||||
|
count = 0;
|
||||||
|
for (i = ret - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (rdata[i] == '.')
|
||||||
|
{
|
||||||
|
rdata[i] = count;
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ret + 1 should be the string length of rdata incl. 0-terminator
|
||||||
|
ret = avahi_entry_group_add_record(mdns_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
|
||||||
|
AVAHI_PUBLISH_USE_MULTICAST | AVAHI_PUBLISH_ALLOW_MULTIPLE,
|
||||||
|
ge->name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_CNAME,
|
||||||
|
AVAHI_DEFAULT_TTL, rdata, ret + 1);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MDNS, "Could not add CNAME record %s: %s\n", ge->name, avahi_strerror(ret));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!commit)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
ret = avahi_entry_group_commit(mdns_group);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MDNS, "Could not commit mDNS services: %s\n", MDNSERR);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
create_all_group_entries(void)
|
||||||
|
{
|
||||||
|
struct mdns_group_entry *ge;
|
||||||
|
int ret;
|
||||||
|
|
||||||
if (!group_entries)
|
if (!group_entries)
|
||||||
{
|
{
|
||||||
@ -636,36 +731,21 @@ _create_services(void)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mdns_group == NULL)
|
if (mdns_group)
|
||||||
{
|
avahi_entry_group_reset(mdns_group);
|
||||||
mdns_group = avahi_entry_group_new(mdns_client, entry_group_callback, NULL);
|
|
||||||
if (!mdns_group)
|
|
||||||
{
|
|
||||||
DPRINTF(E_WARN, L_MDNS, "Could not create Avahi EntryGroup: %s\n", MDNSERR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pentry = group_entries;
|
DPRINTF(E_INFO, L_MDNS, "Re-registering mDNS groups (services and records)\n");
|
||||||
while (pentry)
|
|
||||||
{
|
|
||||||
DPRINTF(E_DBG, L_MDNS, "Re-registering %s/%s\n", pentry->name, pentry->type);
|
|
||||||
|
|
||||||
ret = avahi_entry_group_add_service_strlst(mdns_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0,
|
for (ge = group_entries; ge; ge = ge->next)
|
||||||
pentry->name, pentry->type,
|
{
|
||||||
NULL, NULL, pentry->port, pentry->txt);
|
create_group_entry(ge, 0);
|
||||||
if (ret < 0)
|
if (!mdns_group)
|
||||||
{
|
return;
|
||||||
DPRINTF(E_WARN, L_MDNS, "Could not add mDNS services: %s\n", avahi_strerror(ret));
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pentry = pentry->next;
|
ret = avahi_entry_group_commit(mdns_group);
|
||||||
}
|
if (ret < 0)
|
||||||
|
DPRINTF(E_WARN, L_MDNS, "Could not commit mDNS services: %s\n", MDNSERR);
|
||||||
ret = avahi_entry_group_commit(mdns_group);
|
|
||||||
if (ret < 0)
|
|
||||||
DPRINTF(E_WARN, L_MDNS, "Could not commit mDNS services: %s\n", MDNSERR);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -680,7 +760,7 @@ client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void *
|
|||||||
case AVAHI_CLIENT_S_RUNNING:
|
case AVAHI_CLIENT_S_RUNNING:
|
||||||
DPRINTF(E_LOG, L_MDNS, "Avahi state change: Client running\n");
|
DPRINTF(E_LOG, L_MDNS, "Avahi state change: Client running\n");
|
||||||
if (!mdns_group)
|
if (!mdns_group)
|
||||||
_create_services();
|
create_all_group_entries();
|
||||||
|
|
||||||
for (mb = browser_list; mb; mb = mb->next)
|
for (mb = browser_list; mb; mb = mb->next)
|
||||||
{
|
{
|
||||||
@ -808,8 +888,6 @@ mdns_register(char *name, char *type, int port, char **txt)
|
|||||||
AvahiStringList *txt_sl;
|
AvahiStringList *txt_sl;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_MDNS, "Adding mDNS service %s/%s\n", name, type);
|
|
||||||
|
|
||||||
ge = calloc(1, sizeof(struct mdns_group_entry));
|
ge = calloc(1, sizeof(struct mdns_group_entry));
|
||||||
if (!ge)
|
if (!ge)
|
||||||
{
|
{
|
||||||
@ -817,6 +895,7 @@ mdns_register(char *name, char *type, int port, char **txt)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ge->publish = MDNS_PUBLISH_SERVICE;
|
||||||
ge->name = strdup(name);
|
ge->name = strdup(name);
|
||||||
ge->type = strdup(type);
|
ge->type = strdup(type);
|
||||||
ge->port = port;
|
ge->port = port;
|
||||||
@ -837,14 +916,30 @@ mdns_register(char *name, char *type, int port, char **txt)
|
|||||||
ge->next = group_entries;
|
ge->next = group_entries;
|
||||||
group_entries = ge;
|
group_entries = ge;
|
||||||
|
|
||||||
if (mdns_group)
|
create_all_group_entries(); // TODO why is this required?
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mdns_cname(char *name)
|
||||||
|
{
|
||||||
|
struct mdns_group_entry *ge;
|
||||||
|
|
||||||
|
ge = calloc(1, sizeof(struct mdns_group_entry));
|
||||||
|
if (!ge)
|
||||||
{
|
{
|
||||||
DPRINTF(E_DBG, L_MDNS, "Resetting mDNS group\n");
|
DPRINTF(E_LOG, L_MDNS, "Out of memory for mDNS CNAME\n");
|
||||||
avahi_entry_group_reset(mdns_group);
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_MDNS, "Creating service group\n");
|
ge->publish = MDNS_PUBLISH_CNAME;
|
||||||
_create_services();
|
ge->name = strdup(name);
|
||||||
|
|
||||||
|
ge->next = group_entries;
|
||||||
|
group_entries = ge;
|
||||||
|
|
||||||
|
create_all_group_entries();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
841
src/spotify.c
841
src/spotify.c
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <event2/event.h>
|
#include <event2/event.h>
|
||||||
#include <event2/buffer.h>
|
#include <event2/buffer.h>
|
||||||
|
#include <event2/http.h>
|
||||||
|
|
||||||
int
|
int
|
||||||
spotify_playback_setup(const char *path);
|
spotify_playback_setup(const char *path);
|
||||||
@ -32,6 +33,12 @@ spotify_audio_get(struct evbuffer *evbuf, int wanted);
|
|||||||
int
|
int
|
||||||
spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h);
|
spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h);
|
||||||
|
|
||||||
|
void
|
||||||
|
spotify_oauth_interface(struct evbuffer *evbuf, const char *redirect_uri);
|
||||||
|
|
||||||
|
void
|
||||||
|
spotify_oauth_callback(struct evbuffer *evbuf, struct evkeyvalq *param, const char *redirect_uri);
|
||||||
|
|
||||||
void
|
void
|
||||||
spotify_login(char *path);
|
spotify_login(char *path);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user