Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
mpd
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Иван Мажукин
mpd
Commits
94200668
Commit
94200668
authored
Jan 13, 2018
by
Max Kellermann
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
input/qobuz: new input plugin to receive Qobuz streams
parent
74eac1d4
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
1446 additions
and
1 deletion
+1446
-1
Makefile.am
Makefile.am
+12
-0
NEWS
NEWS
+1
-0
configure.ac
configure.ac
+20
-1
user.xml
doc/user.xml
+64
-0
libgcrypt.m4
m4/libgcrypt.m4
+143
-0
Registry.cxx
src/input/Registry.cxx
+4
-0
QobuzClient.cxx
src/input/plugins/QobuzClient.cxx
+201
-0
QobuzClient.hxx
src/input/plugins/QobuzClient.hxx
+110
-0
QobuzInputPlugin.cxx
src/input/plugins/QobuzInputPlugin.cxx
+185
-0
QobuzInputPlugin.hxx
src/input/plugins/QobuzInputPlugin.hxx
+25
-0
QobuzLoginRequest.cxx
src/input/plugins/QobuzLoginRequest.cxx
+237
-0
QobuzLoginRequest.hxx
src/input/plugins/QobuzLoginRequest.hxx
+90
-0
QobuzSession.hxx
src/input/plugins/QobuzSession.hxx
+40
-0
QobuzTrackRequest.cxx
src/input/plugins/QobuzTrackRequest.cxx
+138
-0
QobuzTrackRequest.hxx
src/input/plugins/QobuzTrackRequest.hxx
+87
-0
MD5.cxx
src/lib/gcrypt/MD5.cxx
+49
-0
MD5.hxx
src/lib/gcrypt/MD5.hxx
+37
-0
ls.cxx
src/ls.cxx
+3
-0
No files found.
Makefile.am
View file @
94200668
...
@@ -1376,6 +1376,18 @@ libinput_a_SOURCES += \
...
@@ -1376,6 +1376,18 @@ libinput_a_SOURCES += \
INPUT_LIBS
+=
$(YAJL_LIBS)
INPUT_LIBS
+=
$(YAJL_LIBS)
endif
endif
if
ENABLE_QOBUZ
libinput_a_SOURCES
+=
\
$(YAJL_SOURCES)
\
src/lib/gcrypt/MD5.cxx src/lib/gcrypt/MD5.hxx
\
src/input/plugins/QobuzSession.hxx
\
src/input/plugins/QobuzClient.cxx src/input/plugins/QobuzClient.hxx
\
src/input/plugins/QobuzLoginRequest.cxx src/input/plugins/QobuzLoginRequest.hxx
\
src/input/plugins/QobuzTrackRequest.cxx src/input/plugins/QobuzTrackRequest.hxx
\
src/input/plugins/QobuzInputPlugin.cxx src/input/plugins/QobuzInputPlugin.hxx
INPUT_LIBS
+=
$(YAJL_LIBS)
$(LIBGCRYPT_LIBS)
endif
if
ENABLE_SMBCLIENT
if
ENABLE_SMBCLIENT
libinput_a_SOURCES
+=
\
libinput_a_SOURCES
+=
\
$(SMBCLIENT_SOURCES)
\
$(SMBCLIENT_SOURCES)
\
...
...
NEWS
View file @
94200668
...
@@ -6,6 +6,7 @@ ver 0.21 (not yet released)
...
@@ -6,6 +6,7 @@ ver 0.21 (not yet released)
- "outputset" sets runtime attributes
- "outputset" sets runtime attributes
- close connection when client sends HTTP request
- close connection when client sends HTTP request
* input
* input
- qobuz: new plugin to play Qobuz streams
- tidal: new plugin to play Tidal streams
- tidal: new plugin to play Tidal streams
* tags
* tags
- new tags "OriginalDate", "MUSICBRAINZ_WORKID"
- new tags "OriginalDate", "MUSICBRAINZ_WORKID"
...
...
configure.ac
View file @
94200668
...
@@ -362,6 +362,11 @@ AC_ARG_ENABLE(ipv6,
...
@@ -362,6 +362,11 @@ AC_ARG_ENABLE(ipv6,
AC_SYS_LARGEFILE
AC_SYS_LARGEFILE
AC_ARG_ENABLE(qobuz,
AS_HELP_STRING([--enable-qobuz],
[enable support for Qobuz streaming]),,
[enable_qobuz=auto])
AC_ARG_ENABLE(soundcloud,
AC_ARG_ENABLE(soundcloud,
AS_HELP_STRING([--enable-soundcloud],
AS_HELP_STRING([--enable-soundcloud],
[enable support for soundcloud.com]),,
[enable support for soundcloud.com]),,
...
@@ -565,9 +570,15 @@ dnl -------------------------------- expat --------------------------------
...
@@ -565,9 +570,15 @@ dnl -------------------------------- expat --------------------------------
MPD_ENABLE_AUTO_PKG(expat, EXPAT, [expat],
MPD_ENABLE_AUTO_PKG(expat, EXPAT, [expat],
[expat XML parser], [expat not found])
[expat XML parser], [expat not found])
dnl -------------------------------- libgcrypt --------------------------------
if test x$enable_qobuz != xno; then
AM_PATH_LIBGCRYPT([1], [found_gcrypt=yes], [found_gcrypt=no])
fi
dnl -------------------------------- yajl -------------------------------------
dnl -------------------------------- yajl -------------------------------------
if test x$enable_soundcloud != xno || test x$enable_tidal != xno; then
if test x$enable_
qobuz != xno || x$enable_
soundcloud != xno || test x$enable_tidal != xno; then
PKG_CHECK_MODULES([YAJL], [yajl >= 2.0],
PKG_CHECK_MODULES([YAJL], [yajl >= 2.0],
[found_yajl=yes],
[found_yajl=yes],
[found_yajl=no])
[found_yajl=no])
...
@@ -718,6 +729,13 @@ dnl ----------------------------------- NFS -----------------------------
...
@@ -718,6 +729,13 @@ dnl ----------------------------------- NFS -----------------------------
MPD_ENABLE_AUTO_PKG(nfs, NFS, [libnfs],
MPD_ENABLE_AUTO_PKG(nfs, NFS, [libnfs],
[NFS input plugin], [libnfs not found])
[NFS input plugin], [libnfs not found])
dnl --------------------------------- Qobuz -----------------------------------
MPD_DEPENDS([enable_qobuz], [found_yajl], [Qobuz streaming], [libyajl not found])
MPD_DEPENDS([enable_qobuz], [found_gcrypt], [Qobuz streaming], [libgcrypt not found])
MPD_DEPENDS([enable_qobuz], [enable_curl], [Qobuz streaming], [libcurl not found])
MPD_AUTO(qobuz, [Qobuz streaming], [Qobuz not available], [found_qobuz=yes])
MPD_DEFINE_CONDITIONAL(enable_qobuz, ENABLE_QOBUZ, [Qobuz streaming])
dnl --------------------------------- Soundcloud ------------------------------
dnl --------------------------------- Soundcloud ------------------------------
MPD_DEPENDS([enable_soundcloud], [found_yajl],
MPD_DEPENDS([enable_soundcloud], [found_yajl],
[soundcloud.com support], [libyajl not found])
[soundcloud.com support], [libyajl not found])
...
@@ -1525,6 +1543,7 @@ fi
...
@@ -1525,6 +1543,7 @@ fi
printf '\nStreaming support:\n\t'
printf '\nStreaming support:\n\t'
results(cdio_paranoia, [CDIO_PARANOIA])
results(cdio_paranoia, [CDIO_PARANOIA])
results(curl,[CURL])
results(curl,[CURL])
results(qobuz,[Qobuz])
results(smbclient,[SMBCLIENT])
results(smbclient,[SMBCLIENT])
results(soundcloud,[Soundcloud])
results(soundcloud,[Soundcloud])
results(tidal,[Tidal])
results(tidal,[Tidal])
...
...
doc/user.xml
View file @
94200668
...
@@ -2370,6 +2370,70 @@ run</programlisting>
...
@@ -2370,6 +2370,70 @@ run</programlisting>
</para>
</para>
</section>
</section>
<section
id=
"qobuz_input"
>
<title><varname>
qobuz
</varname></title>
<para>
Play songs from the commercial streaming service
<ulink
url=
"https://www.qobuz.com/"
>
Qobuz
</ulink>
. It plays URLs in the
form
<filename>
qobuz://track/ID
</filename>
, e.g.:
</para>
<programlisting>
mpc add qobuz://track/23601296
</programlisting>
<informaltable>
<tgroup
cols=
"2"
>
<thead>
<row>
<entry>
Setting
</entry>
<entry>
Description
</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>
app_id
</varname>
<parameter>
ID
</parameter>
</entry>
<entry>
The Qobuz application id.
</entry>
</row>
<row>
<entry>
<varname>
app_secret
</varname>
<parameter>
SECRET
</parameter>
</entry>
<entry>
The Qobuz application secret.
</entry>
</row>
<row>
<entry>
<varname>
username
</varname>
<parameter>
USERNAME
</parameter>
</entry>
<entry>
The Qobuz user name.
</entry>
</row>
<row>
<entry>
<varname>
password
</varname>
<parameter>
PASSWORD
</parameter>
</entry>
<entry>
The Qobuz password.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section
id=
"tidal_input"
>
<section
id=
"tidal_input"
>
<title><varname>
tidal
</varname></title>
<title><varname>
tidal
</varname></title>
...
...
m4/libgcrypt.m4
0 → 100644
View file @
94200668
# libgcrypt.m4 - Autoconf macros to detect libgcrypt
# Copyright (C) 2002, 2003, 2004, 2011, 2014 g10 Code GmbH
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This file is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Last-changed: 2014-10-02
dnl AM_PATH_LIBGCRYPT([MINIMUM-VERSION,
dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]])
dnl Test for libgcrypt and define LIBGCRYPT_CFLAGS and LIBGCRYPT_LIBS.
dnl MINIMUN-VERSION is a string with the version number optionalliy prefixed
dnl with the API version to also check the API compatibility. Example:
dnl a MINIMUN-VERSION of 1:1.2.5 won't pass the test unless the installed
dnl version of libgcrypt is at least 1.2.5 *and* the API number is 1. Using
dnl this features allows to prevent build against newer versions of libgcrypt
dnl with a changed API.
dnl
dnl If a prefix option is not used, the config script is first
dnl searched in $SYSROOT/bin and then along $PATH. If the used
dnl config script does not match the host specification the script
dnl is added to the gpg_config_script_warn variable.
dnl
AC_DEFUN([AM_PATH_LIBGCRYPT],
[ AC_REQUIRE([AC_CANONICAL_HOST])
AC_ARG_WITH(libgcrypt-prefix,
AC_HELP_STRING([--with-libgcrypt-prefix=PFX],
[prefix where LIBGCRYPT is installed (optional)]),
libgcrypt_config_prefix="$withval", libgcrypt_config_prefix="")
if test x"${LIBGCRYPT_CONFIG}" = x ; then
if test x"${libgcrypt_config_prefix}" != x ; then
LIBGCRYPT_CONFIG="${libgcrypt_config_prefix}/bin/libgcrypt-config"
else
case "${SYSROOT}" in
/*)
if test -x "${SYSROOT}/bin/libgcrypt-config" ; then
LIBGCRYPT_CONFIG="${SYSROOT}/bin/libgcrypt-config"
fi
;;
'')
;;
*)
AC_MSG_WARN([Ignoring \$SYSROOT as it is not an absolute path.])
;;
esac
fi
fi
AC_PATH_PROG(LIBGCRYPT_CONFIG, libgcrypt-config, no)
tmp=ifelse([$1], ,1:1.2.0,$1)
if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then
req_libgcrypt_api=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\1/'`
min_libgcrypt_version=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\2/'`
else
req_libgcrypt_api=0
min_libgcrypt_version="$tmp"
fi
AC_MSG_CHECKING(for LIBGCRYPT - version >= $min_libgcrypt_version)
ok=no
if test "$LIBGCRYPT_CONFIG" != "no" ; then
req_major=`echo $min_libgcrypt_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\1/'`
req_minor=`echo $min_libgcrypt_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'`
req_micro=`echo $min_libgcrypt_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'`
libgcrypt_config_version=`$LIBGCRYPT_CONFIG --version`
major=`echo $libgcrypt_config_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'`
minor=`echo $libgcrypt_config_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\2/'`
micro=`echo $libgcrypt_config_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\3/'`
if test "$major" -gt "$req_major"; then
ok=yes
else
if test "$major" -eq "$req_major"; then
if test "$minor" -gt "$req_minor"; then
ok=yes
else
if test "$minor" -eq "$req_minor"; then
if test "$micro" -ge "$req_micro"; then
ok=yes
fi
fi
fi
fi
fi
fi
if test $ok = yes; then
AC_MSG_RESULT([yes ($libgcrypt_config_version)])
else
AC_MSG_RESULT(no)
fi
if test $ok = yes; then
# If we have a recent libgcrypt, we should also check that the
# API is compatible
if test "$req_libgcrypt_api" -gt 0 ; then
tmp=`$LIBGCRYPT_CONFIG --api-version 2>/dev/null || echo 0`
if test "$tmp" -gt 0 ; then
AC_MSG_CHECKING([LIBGCRYPT API version])
if test "$req_libgcrypt_api" -eq "$tmp" ; then
AC_MSG_RESULT([okay])
else
ok=no
AC_MSG_RESULT([does not match. want=$req_libgcrypt_api got=$tmp])
fi
fi
fi
fi
if test $ok = yes; then
LIBGCRYPT_CFLAGS=`$LIBGCRYPT_CONFIG --cflags`
LIBGCRYPT_LIBS=`$LIBGCRYPT_CONFIG --libs`
ifelse([$2], , :, [$2])
libgcrypt_config_host=`$LIBGCRYPT_CONFIG --host 2>/dev/null || echo none`
if test x"$libgcrypt_config_host" != xnone ; then
if test x"$libgcrypt_config_host" != x"$host" ; then
AC_MSG_WARN([[
***
*** The config script $LIBGCRYPT_CONFIG was
*** built for $libgcrypt_config_host and thus may not match the
*** used host $host.
*** You may want to use the configure option --with-libgcrypt-prefix
*** to specify a matching config script or use \$SYSROOT.
***]])
gpg_config_script_warn="$gpg_config_script_warn libgcrypt"
fi
fi
else
LIBGCRYPT_CFLAGS=""
LIBGCRYPT_LIBS=""
ifelse([$3], , :, [$3])
fi
AC_SUBST(LIBGCRYPT_CFLAGS)
AC_SUBST(LIBGCRYPT_LIBS)
])
src/input/Registry.cxx
View file @
94200668
...
@@ -22,6 +22,7 @@
...
@@ -22,6 +22,7 @@
#include "util/Macros.hxx"
#include "util/Macros.hxx"
#include "plugins/FileInputPlugin.hxx"
#include "plugins/FileInputPlugin.hxx"
#include "plugins/TidalInputPlugin.hxx"
#include "plugins/TidalInputPlugin.hxx"
#include "plugins/QobuzInputPlugin.hxx"
#ifdef ENABLE_ALSA
#ifdef ENABLE_ALSA
#include "plugins/AlsaInputPlugin.hxx"
#include "plugins/AlsaInputPlugin.hxx"
...
@@ -66,6 +67,9 @@ const InputPlugin *const input_plugins[] = {
...
@@ -66,6 +67,9 @@ const InputPlugin *const input_plugins[] = {
#ifdef ENABLE_TIDAL
#ifdef ENABLE_TIDAL
&
tidal_input_plugin
,
&
tidal_input_plugin
,
#endif
#endif
#ifdef ENABLE_QOBUZ
&
qobuz_input_plugin
,
#endif
#ifdef ENABLE_CURL
#ifdef ENABLE_CURL
&
input_plugin_curl
,
&
input_plugin_curl
,
#endif
#endif
...
...
src/input/plugins/QobuzClient.cxx
0 → 100644
View file @
94200668
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "QobuzClient.hxx"
#include "lib/gcrypt/MD5.hxx"
#include "util/ConstBuffer.hxx"
#include <stdexcept>
#include <assert.h>
namespace
{
class
QueryStringBuilder
{
bool
first
=
true
;
public
:
QueryStringBuilder
&
operator
()(
std
::
string
&
dest
,
const
char
*
name
,
const
char
*
value
)
noexcept
{
dest
.
push_back
(
first
?
'?'
:
'&'
);
first
=
false
;
dest
+=
name
;
dest
.
push_back
(
'='
);
dest
+=
value
;
// TODO: escape
return
*
this
;
}
};
}
QobuzClient
::
QobuzClient
(
EventLoop
&
event_loop
,
const
char
*
_base_url
,
const
char
*
_app_id
,
const
char
*
_app_secret
,
const
char
*
_device_manufacturer_id
,
const
char
*
_username
,
const
char
*
_email
,
const
char
*
_password
)
:
base_url
(
_base_url
),
app_id
(
_app_id
),
app_secret
(
_app_secret
),
device_manufacturer_id
(
_device_manufacturer_id
),
username
(
_username
),
email
(
_email
),
password
(
_password
),
curl
(
event_loop
),
defer_invoke_handlers
(
event_loop
,
BIND_THIS_METHOD
(
InvokeHandlers
))
{
}
CurlGlobal
&
QobuzClient
::
GetCurl
()
noexcept
{
return
*
curl
;
}
void
QobuzClient
::
StartLogin
()
noexcept
{
assert
(
!
session
.
IsDefined
());
assert
(
!
login_request
);
assert
(
!
handlers
.
empty
());
QobuzLoginHandler
&
handler
=
*
this
;
login_request
=
std
::
make_unique
<
QobuzLoginRequest
>
(
*
curl
,
base_url
,
app_id
,
username
,
email
,
password
,
device_manufacturer_id
,
handler
);
login_request
->
Start
();
}
void
QobuzClient
::
AddLoginHandler
(
QobuzSessionHandler
&
h
)
noexcept
{
const
std
::
lock_guard
<
Mutex
>
protect
(
mutex
);
assert
(
!
h
.
is_linked
());
const
bool
was_empty
=
handlers
.
empty
();
handlers
.
push_front
(
h
);
if
(
!
was_empty
||
login_request
)
return
;
if
(
session
.
IsDefined
())
{
ScheduleInvokeHandlers
();
}
else
{
// TODO: throttle login attempts?
std
::
string
login_uri
(
base_url
);
login_uri
+=
"/login/username"
;
try
{
StartLogin
();
}
catch
(...)
{
error
=
std
::
current_exception
();
ScheduleInvokeHandlers
();
return
;
}
}
}
QobuzSession
QobuzClient
::
GetSession
()
const
{
const
std
::
lock_guard
<
Mutex
>
protect
(
mutex
);
if
(
error
)
std
::
rethrow_exception
(
error
);
if
(
!
session
.
IsDefined
())
throw
std
::
runtime_error
(
"No session"
);
return
session
;
}
void
QobuzClient
::
OnQobuzLoginSuccess
(
QobuzSession
&&
_session
)
noexcept
{
{
const
std
::
lock_guard
<
Mutex
>
protect
(
mutex
);
session
=
std
::
move
(
_session
);
}
ScheduleInvokeHandlers
();
}
void
QobuzClient
::
OnQobuzLoginError
(
std
::
exception_ptr
_error
)
noexcept
{
{
const
std
::
lock_guard
<
Mutex
>
protect
(
mutex
);
error
=
std
::
move
(
_error
);
}
ScheduleInvokeHandlers
();
}
void
QobuzClient
::
InvokeHandlers
()
noexcept
{
const
std
::
lock_guard
<
Mutex
>
protect
(
mutex
);
while
(
!
handlers
.
empty
())
{
auto
&
h
=
handlers
.
front
();
handlers
.
pop_front
();
const
ScopeUnlock
unlock
(
mutex
);
h
.
OnQobuzSession
();
}
login_request
.
reset
();
}
std
::
string
QobuzClient
::
MakeSignedUrl
(
const
char
*
object
,
const
char
*
method
,
const
std
::
multimap
<
std
::
string
,
std
::
string
>
&
query
)
const
noexcept
{
assert
(
!
query
.
empty
());
std
::
string
uri
(
base_url
);
uri
+=
object
;
uri
.
push_back
(
'/'
);
uri
+=
method
;
QueryStringBuilder
q
;
std
::
string
concatenated_query
(
object
);
concatenated_query
+=
method
;
for
(
const
auto
&
i
:
query
)
{
q
(
uri
,
i
.
first
.
c_str
(),
i
.
second
.
c_str
());
concatenated_query
+=
i
.
first
;
concatenated_query
+=
i
.
second
;
}
q
(
uri
,
"app_id"
,
app_id
);
const
auto
request_ts
=
std
::
to_string
(
time
(
nullptr
));
q
(
uri
,
"request_ts"
,
request_ts
.
c_str
());
concatenated_query
+=
request_ts
;
concatenated_query
+=
app_secret
;
const
auto
md5_hex
=
MD5Hex
({
concatenated_query
.
data
(),
concatenated_query
.
size
()});
q
(
uri
,
"request_sig"
,
&
md5_hex
.
front
());
return
uri
;
}
src/input/plugins/QobuzClient.hxx
0 → 100644
View file @
94200668
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef QOBUZ_CLIENT_HXX
#define QOBUZ_CLIENT_HXX
#include "check.h"
#include "QobuzSession.hxx"
#include "QobuzLoginRequest.hxx"
#include "lib/curl/Init.hxx"
#include "thread/Mutex.hxx"
#include "event/DeferEvent.hxx"
#include <boost/intrusive/list.hpp>
#include <memory>
#include <map>
#include <string>
class
QobuzSessionHandler
:
public
boost
::
intrusive
::
list_base_hook
<
boost
::
intrusive
::
link_mode
<
boost
::
intrusive
::
safe_link
>>
{
public
:
virtual
void
OnQobuzSession
()
noexcept
=
0
;
};
class
QobuzClient
final
:
QobuzLoginHandler
{
const
char
*
const
base_url
;
const
char
*
const
app_id
,
*
const
app_secret
;
const
char
*
const
device_manufacturer_id
;
const
char
*
const
username
,
*
const
email
,
*
const
password
;
CurlInit
curl
;
DeferEvent
defer_invoke_handlers
;
/**
* Protects #session, #error, #login_request, #handlers.
*/
mutable
Mutex
mutex
;
QobuzSession
session
;
std
::
exception_ptr
error
;
typedef
boost
::
intrusive
::
list
<
QobuzSessionHandler
,
boost
::
intrusive
::
constant_time_size
<
false
>>
LoginHandlerList
;
LoginHandlerList
handlers
;
std
::
unique_ptr
<
QobuzLoginRequest
>
login_request
;
public
:
QobuzClient
(
EventLoop
&
event_loop
,
const
char
*
_base_url
,
const
char
*
_app_id
,
const
char
*
_app_secret
,
const
char
*
_device_manufacturer_id
,
const
char
*
_username
,
const
char
*
_email
,
const
char
*
_password
);
gcc_pure
CurlGlobal
&
GetCurl
()
noexcept
;
void
AddLoginHandler
(
QobuzSessionHandler
&
h
)
noexcept
;
void
RemoveLoginHandler
(
QobuzSessionHandler
&
h
)
noexcept
{
const
std
::
lock_guard
<
Mutex
>
protect
(
mutex
);
if
(
h
.
is_linked
())
handlers
.
erase
(
handlers
.
iterator_to
(
h
));
}
/**
* Throws on error.
*/
QobuzSession
GetSession
()
const
;
std
::
string
MakeSignedUrl
(
const
char
*
object
,
const
char
*
method
,
const
std
::
multimap
<
std
::
string
,
std
::
string
>
&
query
)
const
noexcept
;
private
:
void
StartLogin
()
noexcept
;
void
InvokeHandlers
()
noexcept
;
void
ScheduleInvokeHandlers
()
noexcept
{
defer_invoke_handlers
.
Schedule
();
}
/* virtual methods from QobuzLoginHandler */
void
OnQobuzLoginSuccess
(
QobuzSession
&&
session
)
noexcept
override
;
void
OnQobuzLoginError
(
std
::
exception_ptr
error
)
noexcept
override
;
};
#endif
src/input/plugins/QobuzInputPlugin.cxx
0 → 100644
View file @
94200668
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "QobuzInputPlugin.hxx"
#include "QobuzClient.hxx"
#include "QobuzTrackRequest.hxx"
#include "CurlInputPlugin.hxx"
#include "PluginUnavailable.hxx"
#include "input/ProxyInputStream.hxx"
#include "input/FailingInputStream.hxx"
#include "input/InputPlugin.hxx"
#include "config/Block.hxx"
#include "thread/Mutex.hxx"
#include "util/StringCompare.hxx"
#include <stdexcept>
#include <memory>
#include <time.h>
static
QobuzClient
*
qobuz_client
;
class
QobuzInputStream
final
:
public
ProxyInputStream
,
QobuzSessionHandler
,
QobuzTrackHandler
{
const
std
::
string
track_id
;
std
::
unique_ptr
<
QobuzTrackRequest
>
track_request
;
std
::
exception_ptr
error
;
public
:
QobuzInputStream
(
const
char
*
_uri
,
const
char
*
_track_id
,
Mutex
&
_mutex
,
Cond
&
_cond
)
noexcept
:
ProxyInputStream
(
_uri
,
_mutex
,
_cond
),
track_id
(
_track_id
)
{
qobuz_client
->
AddLoginHandler
(
*
this
);
}
~
QobuzInputStream
()
{
qobuz_client
->
RemoveLoginHandler
(
*
this
);
}
/* virtual methods from InputStream */
void
Check
()
override
{
if
(
error
)
std
::
rethrow_exception
(
error
);
}
private
:
void
Failed
(
std
::
exception_ptr
e
)
{
SetInput
(
std
::
make_unique
<
FailingInputStream
>
(
GetURI
(),
e
,
mutex
,
cond
));
}
/* virtual methods from QobuzSessionHandler */
void
OnQobuzSession
()
noexcept
override
;
/* virtual methods from QobuzTrackHandler */
void
OnQobuzTrackSuccess
(
std
::
string
&&
url
)
noexcept
override
;
void
OnQobuzTrackError
(
std
::
exception_ptr
error
)
noexcept
override
;
};
void
QobuzInputStream
::
OnQobuzSession
()
noexcept
{
const
std
::
lock_guard
<
Mutex
>
protect
(
mutex
);
try
{
const
auto
session
=
qobuz_client
->
GetSession
();
QobuzTrackHandler
&
handler
=
*
this
;
track_request
=
std
::
make_unique
<
QobuzTrackRequest
>
(
*
qobuz_client
,
session
,
track_id
.
c_str
(),
handler
);
track_request
->
Start
();
}
catch
(...)
{
Failed
(
std
::
current_exception
());
}
}
void
QobuzInputStream
::
OnQobuzTrackSuccess
(
std
::
string
&&
url
)
noexcept
{
const
std
::
lock_guard
<
Mutex
>
protect
(
mutex
);
try
{
SetInput
(
OpenCurlInputStream
(
url
.
c_str
(),
{},
mutex
,
cond
));
}
catch
(...)
{
Failed
(
std
::
current_exception
());
}
}
void
QobuzInputStream
::
OnQobuzTrackError
(
std
::
exception_ptr
e
)
noexcept
{
const
std
::
lock_guard
<
Mutex
>
protect
(
mutex
);
Failed
(
e
);
}
static
void
InitQobuzInput
(
EventLoop
&
event_loop
,
const
ConfigBlock
&
block
)
{
const
char
*
base_url
=
block
.
GetBlockValue
(
"base_url"
,
"http://www.qobuz.com/api.json/0.2/"
);
const
char
*
app_id
=
block
.
GetBlockValue
(
"app_id"
);
if
(
app_id
==
nullptr
)
throw
PluginUnavailable
(
"No Qobuz app_id configured"
);
const
char
*
app_secret
=
block
.
GetBlockValue
(
"app_secret"
);
if
(
app_secret
==
nullptr
)
throw
PluginUnavailable
(
"No Qobuz app_secret configured"
);
const
char
*
device_manufacturer_id
=
block
.
GetBlockValue
(
"device_manufacturer_id"
,
"df691fdc-fa36-11e7-9718-635337d7df8f"
);
const
char
*
username
=
block
.
GetBlockValue
(
"username"
);
const
char
*
email
=
block
.
GetBlockValue
(
"email"
);
if
(
username
==
nullptr
&&
email
==
nullptr
)
throw
PluginUnavailable
(
"No Qobuz username configured"
);
const
char
*
password
=
block
.
GetBlockValue
(
"password"
);
if
(
password
==
nullptr
)
throw
PluginUnavailable
(
"No Qobuz password configured"
);
qobuz_client
=
new
QobuzClient
(
event_loop
,
base_url
,
app_id
,
app_secret
,
device_manufacturer_id
,
username
,
email
,
password
);
}
static
void
FinishQobuzInput
()
{
delete
qobuz_client
;
}
static
InputStreamPtr
OpenQobuzInput
(
const
char
*
uri
,
Mutex
&
mutex
,
Cond
&
cond
)
{
assert
(
qobuz_client
!=
nullptr
);
const
char
*
track_id
;
// TODO: what's the standard "qobuz://" URI syntax?
track_id
=
StringAfterPrefix
(
uri
,
"qobuz://track/"
);
if
(
track_id
==
nullptr
||
*
track_id
==
0
)
return
nullptr
;
// TODO: validate track_id
return
std
::
make_unique
<
QobuzInputStream
>
(
uri
,
track_id
,
mutex
,
cond
);
}
const
InputPlugin
qobuz_input_plugin
=
{
"qobuz"
,
InitQobuzInput
,
FinishQobuzInput
,
OpenQobuzInput
,
};
src/input/plugins/QobuzInputPlugin.hxx
0 → 100644
View file @
94200668
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef INPUT_QOBUZ_HXX
#define INPUT_QOBUZ_HXX
extern
const
struct
InputPlugin
qobuz_input_plugin
;
#endif
src/input/plugins/QobuzLoginRequest.cxx
0 → 100644
View file @
94200668
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "QobuzLoginRequest.hxx"
#include "lib/curl/Form.hxx"
#include "lib/yajl/Callbacks.hxx"
#include "util/RuntimeError.hxx"
using
Wrapper
=
Yajl
::
CallbacksWrapper
<
QobuzLoginRequest
>
;
static
constexpr
yajl_callbacks
parse_callbacks
=
{
nullptr
,
nullptr
,
nullptr
,
nullptr
,
nullptr
,
Wrapper
::
String
,
Wrapper
::
StartMap
,
Wrapper
::
MapKey
,
Wrapper
::
EndMap
,
nullptr
,
nullptr
,
};
static
std
::
multimap
<
std
::
string
,
std
::
string
>
MakeLoginForm
(
const
char
*
app_id
,
const
char
*
username
,
const
char
*
email
,
const
char
*
password
,
const
char
*
device_manufacturer_id
)
{
assert
(
username
!=
nullptr
||
email
!=
nullptr
);
std
::
multimap
<
std
::
string
,
std
::
string
>
form
{
{
"app_id"
,
app_id
},
{
"password"
,
password
},
{
"device_manufacturer_id"
,
device_manufacturer_id
},
};
if
(
username
!=
nullptr
)
form
.
emplace
(
"username"
,
username
);
else
form
.
emplace
(
"email"
,
email
);
return
form
;
}
static
std
::
string
MakeLoginUrl
(
CURL
*
curl
,
const
char
*
base_url
,
const
char
*
app_id
,
const
char
*
username
,
const
char
*
email
,
const
char
*
password
,
const
char
*
device_manufacturer_id
)
{
std
::
string
url
(
base_url
);
url
+=
"user/login?"
;
url
+=
EncodeForm
(
curl
,
MakeLoginForm
(
app_id
,
username
,
email
,
password
,
device_manufacturer_id
));
return
url
;
}
QobuzLoginRequest
::
QobuzLoginRequest
(
CurlGlobal
&
curl
,
const
char
*
base_url
,
const
char
*
app_id
,
const
char
*
username
,
const
char
*
email
,
const
char
*
password
,
const
char
*
device_manufacturer_id
,
QobuzLoginHandler
&
_handler
)
noexcept
:
request
(
curl
,
*
this
),
parser
(
&
parse_callbacks
,
nullptr
,
this
),
handler
(
_handler
)
{
request
.
SetUrl
(
MakeLoginUrl
(
request
.
Get
(),
base_url
,
app_id
,
username
,
email
,
password
,
device_manufacturer_id
).
c_str
());
}
QobuzLoginRequest
::~
QobuzLoginRequest
()
noexcept
{
request
.
StopIndirect
();
}
void
QobuzLoginRequest
::
OnHeaders
(
unsigned
status
,
std
::
multimap
<
std
::
string
,
std
::
string
>
&&
headers
)
{
if
(
status
!=
200
)
throw
FormatRuntimeError
(
"Status %u from Qobuz"
,
status
);
auto
i
=
headers
.
find
(
"content-type"
);
if
(
i
==
headers
.
end
()
||
i
->
second
.
find
(
"/json"
)
==
i
->
second
.
npos
)
throw
std
::
runtime_error
(
"Not a JSON response from Qobuz"
);
}
void
QobuzLoginRequest
::
OnData
(
ConstBuffer
<
void
>
data
)
{
parser
.
Parse
((
const
unsigned
char
*
)
data
.
data
,
data
.
size
);
}
void
QobuzLoginRequest
::
OnEnd
()
{
parser
.
CompleteParse
();
if
(
session
.
user_auth_token
.
empty
())
throw
std
::
runtime_error
(
"No user_auth_token in login response"
);
if
(
session
.
device_id
.
empty
())
throw
std
::
runtime_error
(
"No device id in login response"
);
handler
.
OnQobuzLoginSuccess
(
std
::
move
(
session
));
}
void
QobuzLoginRequest
::
OnError
(
std
::
exception_ptr
e
)
noexcept
{
handler
.
OnQobuzLoginError
(
e
);
}
inline
bool
QobuzLoginRequest
::
String
(
StringView
value
)
noexcept
{
switch
(
state
)
{
case
State
:
:
NONE
:
case
State
:
:
DEVICE
:
break
;
case
State
:
:
DEVICE_ID
:
session
.
device_id
.
assign
(
value
.
data
,
value
.
size
);
break
;
case
State
:
:
USER_AUTH_TOKEN
:
session
.
user_auth_token
.
assign
(
value
.
data
,
value
.
size
);
break
;
}
return
true
;
}
inline
bool
QobuzLoginRequest
::
StartMap
()
noexcept
{
switch
(
state
)
{
case
State
:
:
NONE
:
break
;
case
State
:
:
DEVICE
:
case
State
:
:
DEVICE_ID
:
++
map_depth
;
break
;
case
State
:
:
USER_AUTH_TOKEN
:
break
;
}
return
true
;
}
inline
bool
QobuzLoginRequest
::
MapKey
(
StringView
value
)
noexcept
{
switch
(
state
)
{
case
State
:
:
NONE
:
if
(
value
.
Equals
(
"user_auth_token"
))
state
=
State
::
USER_AUTH_TOKEN
;
else
if
(
value
.
Equals
(
"device"
))
{
state
=
State
::
DEVICE
;
map_depth
=
0
;
}
break
;
case
State
:
:
DEVICE
:
if
(
value
.
Equals
(
"id"
))
state
=
State
::
DEVICE_ID
;
break
;
case
State
:
:
DEVICE_ID
:
break
;
case
State
:
:
USER_AUTH_TOKEN
:
break
;
}
return
true
;
}
inline
bool
QobuzLoginRequest
::
EndMap
()
noexcept
{
switch
(
state
)
{
case
State
:
:
NONE
:
break
;
case
State
:
:
DEVICE_ID
:
state
=
State
::
DEVICE
;
break
;
case
State
:
:
DEVICE
:
case
State
:
:
USER_AUTH_TOKEN
:
break
;
}
switch
(
state
)
{
case
State
:
:
NONE
:
case
State
:
:
DEVICE_ID
:
break
;
case
State
:
:
DEVICE
:
assert
(
map_depth
>
0
);
if
(
--
map_depth
==
0
)
state
=
State
::
NONE
;
break
;
case
State
:
:
USER_AUTH_TOKEN
:
break
;
}
return
true
;
}
src/input/plugins/QobuzLoginRequest.hxx
0 → 100644
View file @
94200668
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef QOBUZ_LOGIN_REQUEST_HXX
#define QOBUZ_LOGIN_REQUEST_HXX
#include "check.h"
#include "QobuzSession.hxx"
#include "lib/curl/Handler.hxx"
#include "lib/curl/Request.hxx"
#include "lib/yajl/Handle.hxx"
#include <exception>
#include <string>
class
CurlRequest
;
class
QobuzLoginHandler
{
public
:
virtual
void
OnQobuzLoginSuccess
(
QobuzSession
&&
session
)
noexcept
=
0
;
virtual
void
OnQobuzLoginError
(
std
::
exception_ptr
error
)
noexcept
=
0
;
};
class
QobuzLoginRequest
final
:
CurlResponseHandler
{
CurlRequest
request
;
Yajl
::
Handle
parser
;
enum
class
State
{
NONE
,
DEVICE
,
DEVICE_ID
,
USER_AUTH_TOKEN
,
}
state
=
State
::
NONE
;
unsigned
map_depth
=
0
;
QobuzSession
session
;
std
::
exception_ptr
error
;
QobuzLoginHandler
&
handler
;
public
:
QobuzLoginRequest
(
CurlGlobal
&
curl
,
const
char
*
base_url
,
const
char
*
app_id
,
const
char
*
username
,
const
char
*
email
,
const
char
*
password
,
const
char
*
device_manufacturer_id
,
QobuzLoginHandler
&
_handler
)
noexcept
;
~
QobuzLoginRequest
()
noexcept
;
void
Start
()
noexcept
{
request
.
StartIndirect
();
}
private
:
/* virtual methods from CurlResponseHandler */
void
OnHeaders
(
unsigned
status
,
std
::
multimap
<
std
::
string
,
std
::
string
>
&&
headers
)
override
;
void
OnData
(
ConstBuffer
<
void
>
data
)
override
;
void
OnEnd
()
override
;
void
OnError
(
std
::
exception_ptr
e
)
noexcept
override
;
public
:
/* yajl callbacks */
bool
String
(
StringView
value
)
noexcept
;
bool
StartMap
()
noexcept
;
bool
MapKey
(
StringView
value
)
noexcept
;
bool
EndMap
()
noexcept
;
};
#endif
src/input/plugins/QobuzSession.hxx
0 → 100644
View file @
94200668
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef QOBUZ_SESSION_HXX
#define QOBUZ_SESSION_HXX
#include <string>
class
CurlRequest
;
struct
QobuzSession
{
std
::
string
user_auth_token
,
device_id
;
bool
IsDefined
()
const
noexcept
{
return
!
user_auth_token
.
empty
();
}
void
Clear
()
{
user_auth_token
.
clear
();
device_id
.
clear
();
}
};
#endif
src/input/plugins/QobuzTrackRequest.cxx
0 → 100644
View file @
94200668
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "QobuzTrackRequest.hxx"
#include "QobuzClient.hxx"
#include "lib/yajl/Callbacks.hxx"
#include "util/RuntimeError.hxx"
using
Wrapper
=
Yajl
::
CallbacksWrapper
<
QobuzTrackRequest
>
;
static
constexpr
yajl_callbacks
parse_callbacks
=
{
nullptr
,
nullptr
,
nullptr
,
nullptr
,
nullptr
,
Wrapper
::
String
,
nullptr
,
Wrapper
::
MapKey
,
Wrapper
::
EndMap
,
nullptr
,
nullptr
,
};
static
std
::
string
MakeTrackUrl
(
QobuzClient
&
client
,
const
char
*
track_id
)
{
return
client
.
MakeSignedUrl
(
"track"
,
"getFileUrl"
,
{
{
"track_id"
,
track_id
},
{
"format_id"
,
"5"
},
});
}
QobuzTrackRequest
::
QobuzTrackRequest
(
QobuzClient
&
client
,
const
QobuzSession
&
session
,
const
char
*
track_id
,
QobuzTrackHandler
&
_handler
)
noexcept
:
request
(
client
.
GetCurl
(),
MakeTrackUrl
(
client
,
track_id
).
c_str
(),
*
this
),
parser
(
&
parse_callbacks
,
nullptr
,
this
),
handler
(
_handler
)
{
request_headers
.
Append
((
"X-User-Auth-Token:"
+
session
.
user_auth_token
).
c_str
());
request
.
SetOption
(
CURLOPT_HTTPHEADER
,
request_headers
.
Get
());
}
QobuzTrackRequest
::~
QobuzTrackRequest
()
noexcept
{
request
.
StopIndirect
();
}
void
QobuzTrackRequest
::
OnHeaders
(
unsigned
status
,
std
::
multimap
<
std
::
string
,
std
::
string
>
&&
headers
)
{
if
(
status
!=
200
)
throw
FormatRuntimeError
(
"Status %u from Qobuz"
,
status
);
auto
i
=
headers
.
find
(
"content-type"
);
if
(
i
==
headers
.
end
()
||
i
->
second
.
find
(
"/json"
)
==
i
->
second
.
npos
)
throw
std
::
runtime_error
(
"Not a JSON response from Qobuz"
);
}
void
QobuzTrackRequest
::
OnData
(
ConstBuffer
<
void
>
data
)
{
parser
.
Parse
((
const
unsigned
char
*
)
data
.
data
,
data
.
size
);
}
void
QobuzTrackRequest
::
OnEnd
()
{
parser
.
CompleteParse
();
if
(
url
.
empty
())
throw
std
::
runtime_error
(
"No url in track response"
);
handler
.
OnQobuzTrackSuccess
(
std
::
move
(
url
));
}
void
QobuzTrackRequest
::
OnError
(
std
::
exception_ptr
e
)
noexcept
{
handler
.
OnQobuzTrackError
(
e
);
}
inline
bool
QobuzTrackRequest
::
String
(
StringView
value
)
noexcept
{
switch
(
state
)
{
case
State
:
:
NONE
:
break
;
case
State
:
:
URL
:
url
.
assign
(
value
.
data
,
value
.
size
);
break
;
}
return
true
;
}
inline
bool
QobuzTrackRequest
::
MapKey
(
StringView
value
)
noexcept
{
if
(
value
.
Equals
(
"url"
))
state
=
State
::
URL
;
else
state
=
State
::
NONE
;
return
true
;
}
inline
bool
QobuzTrackRequest
::
EndMap
()
noexcept
{
state
=
State
::
NONE
;
return
true
;
}
src/input/plugins/QobuzTrackRequest.hxx
0 → 100644
View file @
94200668
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef QOBUZ_TRACK_REQUEST_HXX
#define QOBUZ_TRACK_REQUEST_HXX
#include "check.h"
#include "lib/curl/Handler.hxx"
#include "lib/curl/Slist.hxx"
#include "lib/curl/Request.hxx"
#include "lib/yajl/Handle.hxx"
#include <exception>
#include <string>
class
QobuzClient
;
struct
QobuzSession
;
class
QobuzTrackHandler
:
public
boost
::
intrusive
::
list_base_hook
<
boost
::
intrusive
::
link_mode
<
boost
::
intrusive
::
safe_link
>>
{
public
:
virtual
void
OnQobuzTrackSuccess
(
std
::
string
&&
url
)
noexcept
=
0
;
virtual
void
OnQobuzTrackError
(
std
::
exception_ptr
error
)
noexcept
=
0
;
};
class
QobuzTrackRequest
final
:
CurlResponseHandler
{
CurlSlist
request_headers
;
CurlRequest
request
;
Yajl
::
Handle
parser
;
enum
class
State
{
NONE
,
URL
,
}
state
=
State
::
NONE
;
std
::
string
url
;
std
::
exception_ptr
error
;
QobuzTrackHandler
&
handler
;
public
:
QobuzTrackRequest
(
QobuzClient
&
client
,
const
QobuzSession
&
session
,
const
char
*
track_id
,
QobuzTrackHandler
&
_handler
)
noexcept
;
~
QobuzTrackRequest
()
noexcept
;
void
Start
()
noexcept
{
request
.
StartIndirect
();
}
private
:
/* virtual methods from CurlResponseHandler */
void
OnHeaders
(
unsigned
status
,
std
::
multimap
<
std
::
string
,
std
::
string
>
&&
headers
)
override
;
void
OnData
(
ConstBuffer
<
void
>
data
)
override
;
void
OnEnd
()
override
;
void
OnError
(
std
::
exception_ptr
e
)
noexcept
override
;
public
:
/* yajl callbacks */
bool
String
(
StringView
value
)
noexcept
;
bool
MapKey
(
StringView
value
)
noexcept
;
bool
EndMap
()
noexcept
;
};
#endif
src/lib/gcrypt/MD5.cxx
0 → 100644
View file @
94200668
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "MD5.hxx"
#include "util/ConstBuffer.hxx"
#include <gcrypt.h>
#include <stdio.h>
std
::
array
<
uint8_t
,
16
>
MD5
(
ConstBuffer
<
void
>
input
)
noexcept
{
std
::
array
<
uint8_t
,
16
>
result
;
gcry_md_hash_buffer
(
GCRY_MD_MD5
,
&
result
.
front
(),
input
.
data
,
input
.
size
);
return
result
;
}
std
::
array
<
char
,
33
>
MD5Hex
(
ConstBuffer
<
void
>
input
)
noexcept
{
const
auto
raw
=
MD5
(
input
);
std
::
array
<
char
,
33
>
result
;
char
*
p
=
&
result
.
front
();
for
(
const
auto
i
:
raw
)
{
sprintf
(
p
,
"%02x"
,
i
);
p
+=
2
;
}
return
result
;
}
src/lib/gcrypt/MD5.hxx
0 → 100644
View file @
94200668
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef GCRYPT_MD5_HXX
#define GCRYPT_MD5_HXX
#include "Compiler.h"
#include <array>
template
<
typename
T
>
struct
ConstBuffer
;
gcc_pure
std
::
array
<
uint8_t
,
16
>
MD5
(
ConstBuffer
<
void
>
input
)
noexcept
;
gcc_pure
std
::
array
<
char
,
33
>
MD5Hex
(
ConstBuffer
<
void
>
input
)
noexcept
;
#endif
src/ls.cxx
View file @
94200668
...
@@ -61,6 +61,9 @@ static const char *const remoteUrlPrefixes[] = {
...
@@ -61,6 +61,9 @@ static const char *const remoteUrlPrefixes[] = {
#ifdef ENABLE_ALSA
#ifdef ENABLE_ALSA
"alsa://"
,
"alsa://"
,
#endif
#endif
#ifdef ENABLE_QOBUZ
"qobuz://"
,
#endif
#ifdef ENABLE_TIDAL
#ifdef ENABLE_TIDAL
"tidal://"
,
"tidal://"
,
#endif
#endif
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment