input/qobuz: implement InputPlugin::scan_tags()
[mpd-mirror.git] / src / input / plugins / QobuzClient.cxx
blob419f2468207c1d862156a17cbfaea23320961d9a
1 /*
2 * Copyright 2003-2018 The Music Player Daemon Project
3 * http://www.musicpd.org
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "config.h"
21 #include "QobuzClient.hxx"
22 #include "lib/gcrypt/MD5.hxx"
23 #include "util/ConstBuffer.hxx"
25 #include <stdexcept>
27 #include <assert.h>
29 namespace {
31 class QueryStringBuilder {
32 bool first = true;
34 public:
35 QueryStringBuilder &operator()(std::string &dest, const char *name,
36 const char *value) noexcept {
37 dest.push_back(first ? '?' : '&');
38 first = false;
40 dest += name;
41 dest.push_back('=');
42 dest += value; // TODO: escape
44 return *this;
50 QobuzClient::QobuzClient(EventLoop &event_loop,
51 const char *_base_url,
52 const char *_app_id, const char *_app_secret,
53 const char *_device_manufacturer_id,
54 const char *_username, const char *_email,
55 const char *_password,
56 const char *_format_id)
57 :base_url(_base_url), app_id(_app_id), app_secret(_app_secret),
58 device_manufacturer_id(_device_manufacturer_id),
59 username(_username), email(_email), password(_password),
60 format_id(_format_id),
61 curl(event_loop),
62 defer_invoke_handlers(event_loop, BIND_THIS_METHOD(InvokeHandlers))
66 CurlGlobal &
67 QobuzClient::GetCurl() noexcept
69 return *curl;
72 void
73 QobuzClient::StartLogin()
75 assert(!session.IsDefined());
76 assert(!login_request);
77 assert(!handlers.empty());
79 QobuzLoginHandler &handler = *this;
80 login_request = std::make_unique<QobuzLoginRequest>(*curl, base_url,
81 app_id,
82 username, email,
83 password,
84 device_manufacturer_id,
85 handler);
86 login_request->Start();
89 void
90 QobuzClient::AddLoginHandler(QobuzSessionHandler &h) noexcept
92 const std::lock_guard<Mutex> protect(mutex);
93 assert(!h.is_linked());
95 const bool was_empty = handlers.empty();
96 handlers.push_front(h);
98 if (!was_empty || login_request)
99 return;
101 if (session.IsDefined()) {
102 ScheduleInvokeHandlers();
103 } else {
104 // TODO: throttle login attempts?
106 try {
107 StartLogin();
108 } catch (...) {
109 error = std::current_exception();
110 ScheduleInvokeHandlers();
111 return;
116 QobuzSession
117 QobuzClient::GetSession() const
119 const std::lock_guard<Mutex> protect(mutex);
121 if (error)
122 std::rethrow_exception(error);
124 if (!session.IsDefined())
125 throw std::runtime_error("No session");
127 return session;
130 void
131 QobuzClient::OnQobuzLoginSuccess(QobuzSession &&_session) noexcept
134 const std::lock_guard<Mutex> protect(mutex);
135 session = std::move(_session);
136 login_request.reset();
139 ScheduleInvokeHandlers();
142 void
143 QobuzClient::OnQobuzLoginError(std::exception_ptr _error) noexcept
146 const std::lock_guard<Mutex> protect(mutex);
147 error = std::move(_error);
148 login_request.reset();
151 ScheduleInvokeHandlers();
154 void
155 QobuzClient::InvokeHandlers() noexcept
157 const std::lock_guard<Mutex> protect(mutex);
158 while (!handlers.empty()) {
159 auto &h = handlers.front();
160 handlers.pop_front();
162 const ScopeUnlock unlock(mutex);
163 h.OnQobuzSession();
167 std::string
168 QobuzClient::MakeUrl(const char *object, const char *method,
169 const std::multimap<std::string, std::string> &query) const noexcept
171 assert(!query.empty());
173 std::string uri(base_url);
174 uri += object;
175 uri.push_back('/');
176 uri += method;
178 QueryStringBuilder q;
179 for (const auto &i : query)
180 q(uri, i.first.c_str(), i.second.c_str());
182 q(uri, "app_id", app_id);
183 return uri;
186 std::string
187 QobuzClient::MakeSignedUrl(const char *object, const char *method,
188 const std::multimap<std::string, std::string> &query) const noexcept
190 assert(!query.empty());
192 std::string uri(base_url);
193 uri += object;
194 uri.push_back('/');
195 uri += method;
197 QueryStringBuilder q;
198 std::string concatenated_query(object);
199 concatenated_query += method;
200 for (const auto &i : query) {
201 q(uri, i.first.c_str(), i.second.c_str());
203 concatenated_query += i.first;
204 concatenated_query += i.second;
207 q(uri, "app_id", app_id);
209 const auto request_ts = std::to_string(time(nullptr));
210 q(uri, "request_ts", request_ts.c_str());
211 concatenated_query += request_ts;
213 concatenated_query += app_secret;
215 const auto md5_hex = MD5Hex({concatenated_query.data(), concatenated_query.size()});
216 q(uri, "request_sig", &md5_hex.front());
218 return uri;