1 /* This file is part of Clementine.
2 Copyright 2012-2014, John Maguire <john.maguire@gmail.com>
3 Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
4 Copyright 2014, David Sansome <me@davidsansome.com>
5
6 Clementine is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Clementine is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Clementine. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "skydriveservice.h"
21
22 #include <QJsonDocument>
23 #include <QJsonObject>
24 #include <QJsonArray>
25 #include <QUrlQuery>
26
27 #include <memory>
28
29 #include "core/application.h"
30 #include "core/player.h"
31 #include "core/waitforsignal.h"
32 #include "internet/core/oauthenticator.h"
33 #include "internet/skydrive/skydriveurlhandler.h"
34 #include "ui/iconloader.h"
35
36 namespace {
37
38 static const char* kServiceId = "skydrive";
39
40 static const char* kClientId = "0000000040111F16";
41 static const char* kClientSecret = "w2ClguSX0jG56cBl1CeUniypTBRjXt2Z";
42
43 static const char* kOAuthEndpoint =
44 "https://login.live.com/oauth20_authorize.srf";
45 static const char* kOAuthTokenEndpoint =
46 "https://login.live.com/oauth20_token.srf";
47 static const char* kOAuthScope = "wl.basic wl.skydrive wl.offline_access";
48
49 static const char* kLiveUserInfo = "https://apis.live.net/v5.0/me";
50 static const char* kSkydriveBase = "https://apis.live.net/v5.0/";
51
52 } // namespace
53
54 const char* SkydriveService::kServiceName = "OneDrive";
55 const char* SkydriveService::kSettingsGroup = "Skydrive";
56
SkydriveService(Application * app,InternetModel * parent)57 SkydriveService::SkydriveService(Application* app, InternetModel* parent)
58 : CloudFileService(app, parent, kServiceName, kServiceId,
59 IconLoader::Load("skydrive", IconLoader::Provider),
60 SettingsDialog::Page_Skydrive) {
61 app->player()->RegisterUrlHandler(new SkydriveUrlHandler(this, this));
62 }
63
has_credentials() const64 bool SkydriveService::has_credentials() const {
65 return !refresh_token().isEmpty();
66 }
67
refresh_token() const68 QString SkydriveService::refresh_token() const {
69 QSettings s;
70 s.beginGroup(kSettingsGroup);
71
72 return s.value("refresh_token").toString();
73 }
74
Connect()75 void SkydriveService::Connect() {
76 OAuthenticator* oauth = new OAuthenticator(
77 kClientId, kClientSecret, OAuthenticator::RedirectStyle::REMOTE, this);
78 if (!refresh_token().isEmpty()) {
79 oauth->RefreshAuthorisation(kOAuthTokenEndpoint, refresh_token());
80 } else {
81 oauth->StartAuthorisation(kOAuthEndpoint, kOAuthTokenEndpoint, kOAuthScope);
82 }
83
84 NewClosure(oauth, SIGNAL(Finished()), this,
85 SLOT(ConnectFinished(OAuthenticator*)), oauth);
86 }
87
ConnectFinished(OAuthenticator * oauth)88 void SkydriveService::ConnectFinished(OAuthenticator* oauth) {
89 oauth->deleteLater();
90
91 QSettings s;
92 s.beginGroup(kSettingsGroup);
93 s.setValue("refresh_token", oauth->refresh_token());
94
95 access_token_ = oauth->access_token();
96 expiry_time_ = oauth->expiry_time();
97
98 QUrl url(kLiveUserInfo);
99 QNetworkRequest request(url);
100 AddAuthorizationHeader(&request);
101
102 QNetworkReply* reply = network_->get(request);
103 NewClosure(reply, SIGNAL(finished()), this,
104 SLOT(FetchUserInfoFinished(QNetworkReply*)), reply);
105 }
106
AddAuthorizationHeader(QNetworkRequest * request)107 void SkydriveService::AddAuthorizationHeader(QNetworkRequest* request) {
108 request->setRawHeader("Authorization",
109 QString("Bearer %1").arg(access_token_).toUtf8());
110 }
111
FetchUserInfoFinished(QNetworkReply * reply)112 void SkydriveService::FetchUserInfoFinished(QNetworkReply* reply) {
113 reply->deleteLater();
114 QJsonObject json_response = QJsonDocument::fromBinaryData(reply->readAll()).object();
115
116 QString name = json_response["name"].toString();
117 if (!name.isEmpty()) {
118 QSettings s;
119 s.beginGroup(kSettingsGroup);
120 s.setValue("name", name);
121 }
122
123 emit Connected();
124
125 ListFiles("me/skydrive");
126 }
127
ListFiles(const QString & folder)128 void SkydriveService::ListFiles(const QString& folder) {
129 QUrl url(QString(kSkydriveBase) + folder + "/files");
130 QNetworkRequest request(url);
131 AddAuthorizationHeader(&request);
132
133 QNetworkReply* reply = network_->get(request);
134 NewClosure(reply, SIGNAL(finished()), this,
135 SLOT(ListFilesFinished(QNetworkReply*)), reply);
136 }
137
ListFilesFinished(QNetworkReply * reply)138 void SkydriveService::ListFilesFinished(QNetworkReply* reply) {
139 reply->deleteLater();
140 QJsonObject json_response = QJsonDocument::fromBinaryData(reply->readAll()).object();
141
142 QJsonArray files = json_response["data"].toArray();
143 for (const QJsonValue& f : files) {
144 QJsonObject file = f.toObject();
145 if (file["type"].toString() == "folder") {
146 ListFiles(file["id"].toString());
147 } else {
148 QString mime_type = GuessMimeTypeForFile(file["name"].toString());
149 QUrl url;
150 url.setScheme("skydrive");
151 url.setPath("/" + file["id"].toString());
152
153 Song song;
154 song.set_url(url);
155 song.set_ctime(QDateTime::fromString(file["created_time"].toString()).toTime_t());
156 song.set_mtime(QDateTime::fromString(file["updated_time"].toString()).toTime_t());
157 song.set_comment(file["description"].toString());
158 song.set_filesize(file["size"].toInt());
159 song.set_title(file["name"].toString());
160
161 QUrl download_url(file["source"].toString());
162 // HTTPS appears to be broken somehow between Qt & Skydrive downloads.
163 // Fortunately, just changing the scheme to HTTP works.
164 download_url.setScheme("http");
165 MaybeAddFileToDatabase(song, mime_type, download_url, QString());
166 }
167 }
168 }
169
GetStreamingUrlFromSongId(const QString & file_id)170 QUrl SkydriveService::GetStreamingUrlFromSongId(const QString& file_id) {
171 EnsureConnected();
172
173 QUrl url(QString(kSkydriveBase) + file_id);
174 QNetworkRequest request(url);
175 AddAuthorizationHeader(&request);
176 std::unique_ptr<QNetworkReply> reply(network_->get(request));
177 WaitForSignal(reply.get(), SIGNAL(finished()));
178
179 QJsonObject json_response = QJsonDocument::fromBinaryData(reply.get()->readAll()).object();
180 return QUrl(json_response["source"].toString());
181 }
182
EnsureConnected()183 void SkydriveService::EnsureConnected() {
184 if (!access_token_.isEmpty()) {
185 return;
186 }
187
188 Connect();
189 WaitForSignal(this, SIGNAL(Connected()));
190 }
191
ForgetCredentials()192 void SkydriveService::ForgetCredentials() {
193 QSettings s;
194 s.beginGroup(kSettingsGroup);
195
196 s.remove("refresh_token");
197 s.remove("name");
198 }
199