mirror of
https://github.com/clementine-player/Clementine
synced 2025-02-06 22:24:04 +01:00
Beginnings of playback from Ubuntu One file store.
This commit is contained in:
parent
81e68145cd
commit
24ac9adbe5
@ -190,6 +190,9 @@ set(SOURCES
|
||||
internet/somafmservice.cpp
|
||||
internet/somafmurlhandler.cpp
|
||||
internet/soundcloudservice.cpp
|
||||
internet/ubuntuoneauthenticator.cpp
|
||||
internet/ubuntuoneservice.cpp
|
||||
internet/ubuntuoneurlhandler.cpp
|
||||
|
||||
library/groupbydialog.cpp
|
||||
library/library.cpp
|
||||
@ -464,6 +467,9 @@ set(HEADERS
|
||||
internet/somafmservice.h
|
||||
internet/somafmurlhandler.h
|
||||
internet/soundcloudservice.h
|
||||
internet/ubuntuoneauthenticator.h
|
||||
internet/ubuntuoneservice.h
|
||||
internet/ubuntuoneurlhandler.h
|
||||
|
||||
library/groupbydialog.h
|
||||
library/library.h
|
||||
|
@ -382,6 +382,11 @@ QByteArray Hmac(const QByteArray& key, const QByteArray& data, HashFunction meth
|
||||
QCryptographicHash::hash(inner_padding + data,
|
||||
QCryptographicHash::Md5),
|
||||
QCryptographicHash::Md5);
|
||||
} else if (Sha1_Algo == method) {
|
||||
return QCryptographicHash::hash(outer_padding +
|
||||
QCryptographicHash::hash(inner_padding + data,
|
||||
QCryptographicHash::Sha1),
|
||||
QCryptographicHash::Sha1);
|
||||
} else { // Sha256_Algo, currently default
|
||||
return Sha256(outer_padding + Sha256(inner_padding + data));
|
||||
}
|
||||
@ -395,6 +400,10 @@ QByteArray HmacMd5(const QByteArray& key, const QByteArray& data) {
|
||||
return Hmac(key, data, Md5_Algo);
|
||||
}
|
||||
|
||||
QByteArray HmacSha1(const QByteArray& key, const QByteArray& data) {
|
||||
return Hmac(key, data, Sha1_Algo);
|
||||
}
|
||||
|
||||
QByteArray Sha256(const QByteArray& data) {
|
||||
SHA256_CTX context;
|
||||
SHA256_Init(&context);
|
||||
|
@ -58,10 +58,12 @@ namespace Utilities {
|
||||
enum HashFunction {
|
||||
Md5_Algo,
|
||||
Sha256_Algo,
|
||||
Sha1_Algo,
|
||||
};
|
||||
QByteArray Hmac(const QByteArray& key, const QByteArray& data, HashFunction algo);
|
||||
QByteArray HmacMd5(const QByteArray& key, const QByteArray& data);
|
||||
QByteArray HmacSha256(const QByteArray& key, const QByteArray& data);
|
||||
QByteArray HmacSha1(const QByteArray& key, const QByteArray& data);
|
||||
QByteArray Sha256(const QByteArray& data);
|
||||
|
||||
|
||||
|
@ -774,6 +774,19 @@ void GstEnginePipeline::SourceSetupCallback(GstURIDecodeBin* bin, GParamSpec *ps
|
||||
gst_structure_free(headers);
|
||||
}
|
||||
|
||||
if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "extra-headers") &&
|
||||
instance->url().host().contains("files.one.ubuntu.com")) {
|
||||
GstStructure* headers;
|
||||
headers = gst_structure_new(
|
||||
"extra-headers",
|
||||
"Authorization",
|
||||
G_TYPE_STRING,
|
||||
instance->url().fragment().toAscii().data(),
|
||||
NULL);
|
||||
g_object_set(element, "extra-headers", headers, NULL);
|
||||
gst_structure_free(headers);
|
||||
}
|
||||
|
||||
if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "user-agent")) {
|
||||
QString user_agent = QString("%1 %2").arg(
|
||||
QCoreApplication::applicationName(),
|
||||
|
@ -46,6 +46,8 @@
|
||||
#include "googledriveservice.h"
|
||||
#endif
|
||||
|
||||
#include "ubuntuoneservice.h"
|
||||
|
||||
using smart_playlists::Generator;
|
||||
using smart_playlists::GeneratorMimeData;
|
||||
using smart_playlists::GeneratorPtr;
|
||||
@ -85,6 +87,7 @@ InternetModel::InternetModel(Application* app, QObject* parent)
|
||||
#ifdef HAVE_SPOTIFY
|
||||
AddService(new SpotifyService(app, this));
|
||||
#endif
|
||||
AddService(new UbuntuOneService(app, this));
|
||||
}
|
||||
|
||||
void InternetModel::AddService(InternetService *service) {
|
||||
|
@ -61,14 +61,10 @@ void UbuntuOneAuthenticator::AuthorisationFinished(QNetworkReply* reply) {
|
||||
}
|
||||
|
||||
QVariantMap auth_info = json.toMap();
|
||||
QString consumer_key = auth_info["consumer_key"].toString();
|
||||
QString consumer_secret = auth_info["consumer_secret"].toString();
|
||||
QString token = auth_info["token"].toString();
|
||||
QString token_secret = auth_info["token_secret"].toString();
|
||||
consumer_key_ = auth_info["consumer_key"].toString();
|
||||
consumer_secret_ = auth_info["consumer_secret"].toString();
|
||||
token_ = auth_info["token"].toString();
|
||||
token_secret_ = auth_info["token_secret"].toString();
|
||||
|
||||
qLog(Debug)
|
||||
<< consumer_key
|
||||
<< consumer_secret
|
||||
<< token
|
||||
<< token_secret;
|
||||
emit Finished();
|
||||
}
|
||||
|
@ -12,6 +12,11 @@ class UbuntuOneAuthenticator : public QObject {
|
||||
explicit UbuntuOneAuthenticator(QObject* parent = 0);
|
||||
void StartAuthorisation(const QString& email, const QString& password);
|
||||
|
||||
QString consumer_key() const { return consumer_key_; }
|
||||
QString consumer_secret() const { return consumer_secret_; }
|
||||
QString token() const { return token_; }
|
||||
QString token_secret() const { return token_secret_; }
|
||||
|
||||
signals:
|
||||
void Finished();
|
||||
|
||||
@ -20,6 +25,12 @@ class UbuntuOneAuthenticator : public QObject {
|
||||
|
||||
private:
|
||||
NetworkAccessManager* network_;
|
||||
|
||||
QString consumer_key_;
|
||||
QString consumer_secret_;
|
||||
|
||||
QString token_;
|
||||
QString token_secret_;
|
||||
};
|
||||
|
||||
#endif // UBUNTUONEAUTHENTICATOR_H
|
||||
|
165
src/internet/ubuntuoneservice.cpp
Normal file
165
src/internet/ubuntuoneservice.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
#include "ubuntuoneservice.h"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
#include <qjson/parser.h>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/player.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "internet/ubuntuoneauthenticator.h"
|
||||
#include "internet/ubuntuoneurlhandler.h"
|
||||
|
||||
const char* UbuntuOneService::kServiceName = "Ubuntu One";
|
||||
const char* UbuntuOneService::kSettingsGroup = "Ubuntu One";
|
||||
|
||||
namespace {
|
||||
static const char* kFileStorageEndpoint =
|
||||
"https://one.ubuntu.com/api/file_storage/v1/~/Ubuntu One/";
|
||||
static const char* kOAuthSSOFinishedEndpoint =
|
||||
"https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/";
|
||||
static const char* kContentRoot = "https://files.one.ubuntu.com";
|
||||
static const char* kOAuthHeaderPrefix = "OAuth realm=\"\", ";
|
||||
}
|
||||
|
||||
UbuntuOneService::UbuntuOneService(Application* app, InternetModel* parent)
|
||||
: InternetService(kServiceName, app, parent, parent),
|
||||
root_(nullptr),
|
||||
network_(new NetworkAccessManager(this)) {
|
||||
app->player()->RegisterUrlHandler(new UbuntuOneUrlHandler(this, this));
|
||||
}
|
||||
|
||||
QStandardItem* UbuntuOneService::CreateRootItem() {
|
||||
root_ = new QStandardItem(QIcon(), "Ubuntu One");
|
||||
root_->setData(true, InternetModel::Role_CanLazyLoad);
|
||||
return root_;
|
||||
}
|
||||
|
||||
void UbuntuOneService::LazyPopulate(QStandardItem* item) {
|
||||
switch (item->data(InternetModel::Role_Type).toInt()) {
|
||||
case InternetModel::Type_Service:
|
||||
Connect();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UbuntuOneService::Connect() {
|
||||
UbuntuOneAuthenticator* authenticator = new UbuntuOneAuthenticator;
|
||||
authenticator->StartAuthorisation(
|
||||
"Username",
|
||||
"Password");
|
||||
NewClosure(authenticator, SIGNAL(Finished()),
|
||||
this, SLOT(AuthenticationFinished(UbuntuOneAuthenticator*)),
|
||||
authenticator);
|
||||
}
|
||||
|
||||
QByteArray UbuntuOneService::GenerateAuthorisationHeader() {
|
||||
typedef QPair<QString, QString> Param;
|
||||
QString timestamp = QString::number(
|
||||
QDateTime::currentMSecsSinceEpoch() / kMsecPerSec);
|
||||
QList<Param> parameters;
|
||||
parameters << Param("oauth_nonce", QString::number(qrand()))
|
||||
<< Param("oauth_timestamp", timestamp)
|
||||
<< Param("oauth_version", "1.0")
|
||||
<< Param("oauth_consumer_key", consumer_key_)
|
||||
<< Param("oauth_token", token_)
|
||||
<< Param("oauth_signature_method", "PLAINTEXT");
|
||||
qSort(parameters.begin(), parameters.end());
|
||||
QStringList encoded_params;
|
||||
for (const Param& p : parameters) {
|
||||
encoded_params << QString("%1=%2").arg(p.first, p.second);
|
||||
}
|
||||
|
||||
QString signing_key =
|
||||
consumer_secret_ + "&" + token_secret_;
|
||||
QByteArray signature = QUrl::toPercentEncoding(signing_key);
|
||||
|
||||
// Construct authorisation header
|
||||
parameters << Param("oauth_signature", signature);
|
||||
QStringList header_params;
|
||||
for (const Param& p : parameters) {
|
||||
header_params << QString("%1=\"%2\"").arg(p.first, p.second);
|
||||
}
|
||||
QString authorisation_header = header_params.join(", ");
|
||||
authorisation_header.prepend(kOAuthHeaderPrefix);
|
||||
|
||||
return authorisation_header.toAscii();
|
||||
}
|
||||
|
||||
void UbuntuOneService::AuthenticationFinished(
|
||||
UbuntuOneAuthenticator* authenticator) {
|
||||
authenticator->deleteLater();
|
||||
|
||||
consumer_key_ = authenticator->consumer_key();
|
||||
consumer_secret_ = authenticator->consumer_secret();
|
||||
token_ = authenticator->token();
|
||||
token_secret_ = authenticator->token_secret();
|
||||
|
||||
QUrl sso_url(kOAuthSSOFinishedEndpoint);
|
||||
QNetworkRequest request(sso_url);
|
||||
request.setRawHeader("Authorization", GenerateAuthorisationHeader());
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
|
||||
qLog(Debug) << "Sending SSO copy request";
|
||||
QNetworkReply* reply = network_->get(request);
|
||||
NewClosure(reply, SIGNAL(finished()),
|
||||
this, SLOT(SSORequestFinished(QNetworkReply*)), reply);
|
||||
}
|
||||
|
||||
void UbuntuOneService::SSORequestFinished(QNetworkReply* reply) {
|
||||
qLog(Debug) << Q_FUNC_INFO;
|
||||
QUrl files_url(kFileStorageEndpoint);
|
||||
files_url.addQueryItem("include_children", "true");
|
||||
QNetworkRequest request(files_url);
|
||||
request.setRawHeader("Authorization", GenerateAuthorisationHeader());
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
|
||||
qLog(Debug) << "Sending files request";
|
||||
QNetworkReply* files_reply = network_->get(request);
|
||||
NewClosure(files_reply, SIGNAL(finished()),
|
||||
this, SLOT(FileListRequestFinished(QNetworkReply*)), files_reply);
|
||||
}
|
||||
|
||||
void UbuntuOneService::FileListRequestFinished(QNetworkReply* reply) {
|
||||
QByteArray data = reply->readAll();
|
||||
qLog(Debug) << reply->url();
|
||||
qLog(Debug) << data;
|
||||
qLog(Debug) << reply->rawHeaderList();
|
||||
qLog(Debug) << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
qLog(Debug) << reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute);
|
||||
|
||||
QJson::Parser parser;
|
||||
QVariantMap result = parser.parse(data).toMap();
|
||||
|
||||
QVariantList children = result["children"].toList();
|
||||
for (const QVariant& c : children) {
|
||||
QVariantMap child = c.toMap();
|
||||
QString content_path = child["content_path"].toString();
|
||||
|
||||
QUrl content_url;
|
||||
content_url.setScheme("ubuntuonefile");
|
||||
content_url.setPath(content_path);
|
||||
|
||||
Song song;
|
||||
song.set_title("One More Chance");
|
||||
song.set_artist("Bloc Party");
|
||||
song.set_url(content_url);
|
||||
|
||||
root_->appendRow(CreateSongItem(song));
|
||||
}
|
||||
}
|
||||
|
||||
QUrl UbuntuOneService::GetStreamingUrlFromSongId(const QString& song_id) {
|
||||
QUrl url(kContentRoot);
|
||||
url.setPath(song_id);
|
||||
url.setFragment(GenerateAuthorisationHeader());
|
||||
return url;
|
||||
}
|
44
src/internet/ubuntuoneservice.h
Normal file
44
src/internet/ubuntuoneservice.h
Normal file
@ -0,0 +1,44 @@
|
||||
#ifndef UBUNTUONESERVICE_H
|
||||
#define UBUNTUONESERVICE_H
|
||||
|
||||
#include "internet/internetservice.h"
|
||||
|
||||
class NetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class UbuntuOneAuthenticator;
|
||||
|
||||
class UbuntuOneService : public InternetService {
|
||||
Q_OBJECT
|
||||
public:
|
||||
UbuntuOneService(Application* app, InternetModel* parent);
|
||||
|
||||
static const char* kServiceName;
|
||||
static const char* kSettingsGroup;
|
||||
|
||||
// InternetService
|
||||
virtual QStandardItem* CreateRootItem();
|
||||
virtual void LazyPopulate(QStandardItem* parent);
|
||||
|
||||
QUrl GetStreamingUrlFromSongId(const QString& song_id);
|
||||
|
||||
private slots:
|
||||
void AuthenticationFinished(UbuntuOneAuthenticator* authenticator);
|
||||
void SSORequestFinished(QNetworkReply* reply);
|
||||
void FileListRequestFinished(QNetworkReply* reply);
|
||||
|
||||
private:
|
||||
void Connect();
|
||||
|
||||
private:
|
||||
QByteArray GenerateAuthorisationHeader();
|
||||
|
||||
QStandardItem* root_;
|
||||
NetworkAccessManager* network_;
|
||||
|
||||
QString consumer_key_;
|
||||
QString consumer_secret_;
|
||||
QString token_;
|
||||
QString token_secret_;
|
||||
};
|
||||
|
||||
#endif // UBUNTUONESERVICE_H
|
16
src/internet/ubuntuoneurlhandler.cpp
Normal file
16
src/internet/ubuntuoneurlhandler.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include "ubuntuoneurlhandler.h"
|
||||
|
||||
#include "ubuntuoneservice.h"
|
||||
|
||||
UbuntuOneUrlHandler::UbuntuOneUrlHandler(
|
||||
UbuntuOneService* service,
|
||||
QObject* parent)
|
||||
: UrlHandler(parent),
|
||||
service_(service) {
|
||||
}
|
||||
|
||||
UrlHandler::LoadResult UbuntuOneUrlHandler::StartLoading(const QUrl& url) {
|
||||
QString file_id = url.path();
|
||||
QUrl real_url = service_->GetStreamingUrlFromSongId(file_id);
|
||||
return LoadResult(url, LoadResult::TrackAvailable, real_url);
|
||||
}
|
21
src/internet/ubuntuoneurlhandler.h
Normal file
21
src/internet/ubuntuoneurlhandler.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef UBUNTUONEURLHANDLER_H
|
||||
#define UBUNTUONEURLHANDLER_H
|
||||
|
||||
#include "core/urlhandler.h"
|
||||
|
||||
class UbuntuOneService;
|
||||
|
||||
class UbuntuOneUrlHandler : public UrlHandler {
|
||||
Q_OBJECT
|
||||
public:
|
||||
UbuntuOneUrlHandler(UbuntuOneService* service, QObject* parent = 0);
|
||||
|
||||
QString scheme() const { return "ubuntuonefile"; }
|
||||
QIcon icon() const { return QIcon(); }
|
||||
LoadResult StartLoading(const QUrl& url);
|
||||
|
||||
private:
|
||||
UbuntuOneService* service_;
|
||||
};
|
||||
|
||||
#endif // UBUNTUONEURLHANDLER_H
|
Loading…
x
Reference in New Issue
Block a user