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