Basic support for Google Drive & OAuth.
This commit is contained in:
parent
497928d693
commit
145f1efaf5
@ -284,6 +284,7 @@
|
||||
<file>providers/echonest.png</file>
|
||||
<file>providers/songkick.png</file>
|
||||
<file>providers/twitter.png</file>
|
||||
<file>providers/googledrive.png</file>
|
||||
<file>lumberjacksong.txt</file>
|
||||
<file>schema/schema-18.sql</file>
|
||||
<file>star-off.png</file>
|
||||
|
BIN
data/providers/googledrive.png
Normal file
BIN
data/providers/googledrive.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
@ -160,6 +160,7 @@ set(SOURCES
|
||||
internet/digitallyimportedsettingspage.cpp
|
||||
internet/digitallyimportedurlhandler.cpp
|
||||
internet/geolocator.cpp
|
||||
internet/googledriveservice.cpp
|
||||
internet/groovesharkradio.cpp
|
||||
internet/groovesharksearchplaylisttype.cpp
|
||||
internet/groovesharkservice.cpp
|
||||
@ -182,6 +183,7 @@ set(SOURCES
|
||||
internet/magnatunesettingspage.cpp
|
||||
internet/magnatuneservice.cpp
|
||||
internet/magnatuneurlhandler.cpp
|
||||
internet/oauthenticator.cpp
|
||||
internet/savedradio.cpp
|
||||
internet/searchboxwidget.cpp
|
||||
internet/somafmservice.cpp
|
||||
@ -431,6 +433,7 @@ set(HEADERS
|
||||
internet/digitallyimportedservicebase.h
|
||||
internet/digitallyimportedsettingspage.h
|
||||
internet/geolocator.h
|
||||
internet/googledriveservice.h
|
||||
internet/groovesharkservice.h
|
||||
internet/groovesharksettingspage.h
|
||||
internet/groovesharkurlhandler.h
|
||||
@ -449,6 +452,7 @@ set(HEADERS
|
||||
internet/magnatunedownloaddialog.h
|
||||
internet/magnatunesettingspage.h
|
||||
internet/magnatuneservice.h
|
||||
internet/oauthenticator.h
|
||||
internet/savedradio.h
|
||||
internet/searchboxwidget.h
|
||||
internet/somafmservice.h
|
||||
|
@ -736,6 +736,21 @@ void GstEnginePipeline::SourceSetupCallback(GstURIDecodeBin* bin, GParamSpec *ps
|
||||
g_object_set(element, "extra-headers", headers, NULL);
|
||||
gst_structure_free(headers);
|
||||
}
|
||||
|
||||
if (element &&
|
||||
g_object_class_find_property(G_OBJECT_GET_CLASS(element), "extra-headers") &&
|
||||
instance->url().host().contains("googleusercontent.com") &&
|
||||
instance->url().hasFragment()) {
|
||||
QByteArray authorization = QString("Bearer %1").arg(
|
||||
instance->url().fragment()).toAscii();
|
||||
GstStructure* headers = gst_structure_new(
|
||||
"extra-headers",
|
||||
"Authorization", G_TYPE_STRING,
|
||||
authorization.constData(),
|
||||
NULL);
|
||||
g_object_set(element, "extra-headers", headers, NULL);
|
||||
gst_structure_free(headers);
|
||||
}
|
||||
}
|
||||
|
||||
void GstEnginePipeline::TransitionToNext() {
|
||||
|
75
src/internet/googledriveservice.cpp
Normal file
75
src/internet/googledriveservice.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#include "googledriveservice.h"
|
||||
|
||||
#include <qjson/parser.h>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "internetmodel.h"
|
||||
#include "oauthenticator.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const char* kGoogleDriveFiles = "https://www.googleapis.com/drive/v2/files";
|
||||
|
||||
}
|
||||
|
||||
GoogleDriveService::GoogleDriveService(Application* app, InternetModel* parent)
|
||||
: InternetService("Google Drive", app, parent, parent),
|
||||
root_(NULL),
|
||||
oauth_(new OAuthenticator(this)) {
|
||||
connect(oauth_, SIGNAL(AccessTokenAvailable(QString)), SLOT(AccessTokenAvailable(QString)));
|
||||
}
|
||||
|
||||
QStandardItem* GoogleDriveService::CreateRootItem() {
|
||||
root_ = new QStandardItem(QIcon(":providers/googledrive.png"), "Google Drive");
|
||||
root_->setData(true, InternetModel::Role_CanLazyLoad);
|
||||
return root_;
|
||||
}
|
||||
|
||||
void GoogleDriveService::LazyPopulate(QStandardItem* item) {
|
||||
switch (item->data(InternetModel::Role_Type).toInt()) {
|
||||
case InternetModel::Type_Service:
|
||||
Connect();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GoogleDriveService::Connect() {
|
||||
oauth_->StartAuthorisation();
|
||||
}
|
||||
|
||||
void GoogleDriveService::AccessTokenAvailable(const QString& token) {
|
||||
access_token_ = token;
|
||||
QUrl url = QUrl(kGoogleDriveFiles);
|
||||
url.addQueryItem("q", "mimeType = 'audio/mpeg'");
|
||||
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
request.setRawHeader(
|
||||
"Authorization", QString("Bearer %1").arg(token).toUtf8());
|
||||
QNetworkReply* reply = network_.get(request);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(ListFilesFinished(QNetworkReply*)), reply);
|
||||
}
|
||||
|
||||
void GoogleDriveService::ListFilesFinished(QNetworkReply* reply) {
|
||||
reply->deleteLater();
|
||||
|
||||
QJson::Parser parser;
|
||||
bool ok = false;
|
||||
QVariantMap result = parser.parse(reply, &ok).toMap();
|
||||
if (!ok) {
|
||||
qLog(Error) << "Failed to request files from Google Drive";
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantList items = result["items"].toList();
|
||||
foreach (const QVariant& v, items) {
|
||||
QVariantMap file = v.toMap();
|
||||
Song song;
|
||||
song.set_title(file["title"].toString());
|
||||
QString url = file["downloadUrl"].toString() + "#" + access_token_;
|
||||
song.set_url(url);
|
||||
song.set_filesize(file["fileSize"].toInt());
|
||||
root_->appendRow(CreateSongItem(song));
|
||||
}
|
||||
}
|
35
src/internet/googledriveservice.h
Normal file
35
src/internet/googledriveservice.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef GOOGLEDRIVESERVICE_H
|
||||
#define GOOGLEDRIVESERVICE_H
|
||||
|
||||
#include "internetservice.h"
|
||||
|
||||
#include "core/network.h"
|
||||
|
||||
class QStandardItem;
|
||||
|
||||
class OAuthenticator;
|
||||
|
||||
class GoogleDriveService : public InternetService {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GoogleDriveService(Application* app, InternetModel* parent);
|
||||
|
||||
QStandardItem* CreateRootItem();
|
||||
void LazyPopulate(QStandardItem* item);
|
||||
|
||||
private slots:
|
||||
void AccessTokenAvailable(const QString& token);
|
||||
void ListFilesFinished(QNetworkReply* reply);
|
||||
|
||||
private:
|
||||
void Connect();
|
||||
|
||||
QStandardItem* root_;
|
||||
OAuthenticator* oauth_;
|
||||
|
||||
QString access_token_;
|
||||
|
||||
NetworkAccessManager network_;
|
||||
};
|
||||
|
||||
#endif
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include "digitallyimportedservicebase.h"
|
||||
#include "icecastservice.h"
|
||||
#include "googledriveservice.h"
|
||||
#include "jamendoservice.h"
|
||||
#include "magnatuneservice.h"
|
||||
#include "internetmimedata.h"
|
||||
@ -64,6 +65,7 @@ InternetModel::InternetModel(Application* app, QObject* parent)
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
AddService(new LastFMService(app, this));
|
||||
#endif
|
||||
AddService(new GoogleDriveService(app, this));
|
||||
AddService(new GroovesharkService(app, this));
|
||||
AddService(new MagnatuneService(app, this));
|
||||
AddService(new PodcastService(app, this));
|
||||
|
147
src/internet/oauthenticator.cpp
Normal file
147
src/internet/oauthenticator.cpp
Normal file
@ -0,0 +1,147 @@
|
||||
#include "oauthenticator.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QStringList>
|
||||
#include <QTcpSocket>
|
||||
#include <QUrl>
|
||||
|
||||
#include <qjson/parser.h>
|
||||
|
||||
#include "core/closure.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const char* kGoogleOAuthEndpoint = "https://accounts.google.com/o/oauth2/auth";
|
||||
const char* kGoogleOAuthTokenEndpoint =
|
||||
"https://accounts.google.com/o/oauth2/token";
|
||||
const char* kGoogleDriveFiles = "https://www.googleapis.com/drive/v2/files";
|
||||
|
||||
const char* kClientId = "679260893280.apps.googleusercontent.com";
|
||||
const char* kClientSecret = "l3cWb8efUZsrBI4wmY3uKl6i";
|
||||
|
||||
} // namespace
|
||||
|
||||
OAuthenticator::OAuthenticator(QObject* parent)
|
||||
: QObject(parent) {
|
||||
}
|
||||
|
||||
void OAuthenticator::StartAuthorisation() {
|
||||
server_.listen(QHostAddress::LocalHost);
|
||||
const quint16 port = server_.serverPort();
|
||||
|
||||
NewClosure(&server_, SIGNAL(newConnection()), this, SLOT(NewConnection()));
|
||||
|
||||
QUrl url = QUrl(kGoogleOAuthEndpoint);
|
||||
url.addQueryItem("response_type", "code");
|
||||
url.addQueryItem("client_id", kClientId);
|
||||
url.addQueryItem("redirect_uri", QString("http://localhost:%1").arg(port));
|
||||
url.addQueryItem("scope", "https://www.googleapis.com/auth/drive.readonly");
|
||||
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
|
||||
void OAuthenticator::NewConnection() {
|
||||
QTcpSocket* socket = server_.nextPendingConnection();
|
||||
server_.close();
|
||||
|
||||
QByteArray buffer;
|
||||
|
||||
NewClosure(socket, SIGNAL(readyRead()),
|
||||
this, SLOT(RedirectArrived(QTcpSocket*, QByteArray)), socket, buffer);
|
||||
|
||||
// Everything is bon.
|
||||
socket->write("HTTP/1.0 200 OK\r\n");
|
||||
socket->flush();
|
||||
}
|
||||
|
||||
void OAuthenticator::RedirectArrived(QTcpSocket* socket, QByteArray buffer) {
|
||||
buffer.append(socket->readAll());
|
||||
|
||||
if (socket->atEnd() || buffer.endsWith("\r\n\r\n")) {
|
||||
socket->deleteLater();
|
||||
const QByteArray& code = ParseHttpRequest(buffer);
|
||||
qLog(Debug) << "Code:" << code;
|
||||
RequestAccessToken(code, socket->localPort());
|
||||
} else {
|
||||
NewClosure(socket, SIGNAL(readyReady()),
|
||||
this, SLOT(RedirectArrived(QTcpSocket*, QByteArray)), socket, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray OAuthenticator::ParseHttpRequest(const QByteArray& request) const {
|
||||
QList<QByteArray> split = request.split('\r');
|
||||
const QByteArray& request_line = split[0];
|
||||
QByteArray path = request_line.split(' ')[1];
|
||||
QByteArray code = path.split('=')[1];
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
void OAuthenticator::RequestAccessToken(const QByteArray& code, quint16 port) {
|
||||
typedef QPair<QString, QString> Param;
|
||||
QList<Param> parameters;
|
||||
parameters << Param("code", code)
|
||||
<< Param("client_id", kClientId)
|
||||
<< Param("client_secret", kClientSecret)
|
||||
<< Param("grant_type", "authorization_code")
|
||||
// Even though we don't use this URI anymore, it must match the
|
||||
// original one.
|
||||
<< Param("redirect_uri", QString("http://localhost:%1").arg(port));
|
||||
|
||||
QStringList params;
|
||||
foreach (const Param& p, parameters) {
|
||||
params.append(QString("%1=%2").arg(p.first, QString(QUrl::toPercentEncoding(p.second))));
|
||||
}
|
||||
QString post_data = params.join("&");
|
||||
qLog(Debug) << post_data;
|
||||
|
||||
QNetworkRequest request = QNetworkRequest(QUrl(kGoogleOAuthTokenEndpoint));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader,
|
||||
"application/x-www-form-urlencoded");
|
||||
|
||||
QNetworkReply* reply = network_.post(request, post_data.toUtf8());
|
||||
NewClosure(reply, SIGNAL(finished()), this,
|
||||
SLOT(FetchAccessTokenFinished(QNetworkReply*)), reply);
|
||||
}
|
||||
|
||||
void OAuthenticator::FetchAccessTokenFinished(QNetworkReply* reply) {
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) != 200) {
|
||||
qLog(Error) << "Failed to get access token"
|
||||
<< reply->readAll();
|
||||
return;
|
||||
}
|
||||
|
||||
QJson::Parser parser;
|
||||
bool ok = false;
|
||||
QVariantMap result = parser.parse(reply, &ok).toMap();
|
||||
if (!ok) {
|
||||
qLog(Error) << "Failed to parse oauth reply";
|
||||
return;
|
||||
}
|
||||
|
||||
qLog(Debug) << result;
|
||||
|
||||
access_token_ = result["access_token"].toString();
|
||||
refresh_token_ = result["refresh_token"].toString();
|
||||
|
||||
emit AccessTokenAvailable(access_token_);
|
||||
}
|
||||
|
||||
void OAuthenticator::ListFiles(const QString& access_token) {
|
||||
QUrl url = QUrl(kGoogleDriveFiles);
|
||||
url.addQueryItem("q", "mimeType = 'audio/mpeg'");
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
request.setRawHeader(
|
||||
"Authorization", QString("Bearer %1").arg(access_token).toUtf8());
|
||||
qLog(Debug) << "Header:" << request.rawHeader("Authorization");
|
||||
QNetworkReply* reply = network_.get(request);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(ListFilesResponse(QNetworkReply*)), reply);
|
||||
}
|
||||
|
||||
void OAuthenticator::ListFilesResponse(QNetworkReply* reply) {
|
||||
reply->deleteLater();
|
||||
|
||||
qLog(Debug) << reply->readAll();
|
||||
}
|
38
src/internet/oauthenticator.h
Normal file
38
src/internet/oauthenticator.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef OAUTHENTICATOR_H
|
||||
#define OAUTHENTICATOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTcpServer>
|
||||
|
||||
#include "core/network.h"
|
||||
|
||||
class QTcpSocket;
|
||||
|
||||
class OAuthenticator : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OAuthenticator(QObject* parent = 0);
|
||||
void StartAuthorisation();
|
||||
|
||||
signals:
|
||||
void AccessTokenAvailable(QString token);
|
||||
|
||||
private slots:
|
||||
void NewConnection();
|
||||
void RedirectArrived(QTcpSocket* socket, QByteArray buffer);
|
||||
void FetchAccessTokenFinished(QNetworkReply* reply);
|
||||
void ListFilesResponse(QNetworkReply* reply);
|
||||
|
||||
private:
|
||||
QByteArray ParseHttpRequest(const QByteArray& request) const;
|
||||
void RequestAccessToken(const QByteArray& code, quint16 port);
|
||||
void ListFiles(const QString& access_token);
|
||||
|
||||
QTcpServer server_;
|
||||
NetworkAccessManager network_;
|
||||
|
||||
QString access_token_;
|
||||
QString refresh_token_;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user