onedrive: Update to use graph api

The live API was deprecated in 2018. This change implements basic
onedrive access using the MS graph API.

The URL scheme was also changed from skydrive to onedrive. This is based
on the assumption that existing playlists won't have compatible item
ids.

Known issues:
- Directories with over 200 items will be truncated.
- No mechanism for discovering changes at runtime.
- No mechanism for removing deleted items or rescanning.

Reference: https://docs.microsoft.com/en-us/onedrive/developer/rest-api/concepts/migrating-from-live-sdk?view=odsp-graph-online
This commit is contained in:
Jim Broadus 2021-02-21 23:58:56 -08:00 committed by John Maguire
parent 7b5d2fd79f
commit 5767317678
4 changed files with 65 additions and 42 deletions

View File

@ -34,6 +34,7 @@
namespace {
// Keep legacy name since it needs to match the database table name.
static const char* kServiceId = "skydrive";
static const char* kClientId = "905def38-34d2-4e32-8ba7-c37bcc329047";
@ -47,8 +48,10 @@ static const char* kOAuthTokenEndpoint =
static const char* kOAuthScope =
"User.Read Files.Read Files.Read.All offline_access";
static const char* kLiveUserInfo = "https://apis.live.net/v5.0/me";
static const char* kSkydriveBase = "https://apis.live.net/v5.0/";
// MS Graph API
// https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0
static const char* kGraphUserInfo = "https://graph.microsoft.com/v1.0/me";
static const char* kDriveBase = "https://graph.microsoft.com/v1.0/me/drive/";
} // namespace
@ -96,7 +99,11 @@ void SkydriveService::ConnectFinished(OAuthenticator* oauth) {
access_token_ = oauth->access_token();
expiry_time_ = oauth->expiry_time();
QUrl url(kLiveUserInfo);
FetchUserInfo();
}
void SkydriveService::FetchUserInfo() {
QUrl url(kGraphUserInfo);
QNetworkRequest request(url);
AddAuthorizationHeader(&request);
@ -105,9 +112,12 @@ void SkydriveService::ConnectFinished(OAuthenticator* oauth) {
SLOT(FetchUserInfoFinished(QNetworkReply*)), reply);
}
QByteArray SkydriveService::GetAuthHeader() const {
return QString("Bearer %1").arg(access_token_).toUtf8();
}
void SkydriveService::AddAuthorizationHeader(QNetworkRequest* request) {
request->setRawHeader("Authorization",
QString("Bearer %1").arg(access_token_).toUtf8());
request->setRawHeader("Authorization", GetAuthHeader());
}
void SkydriveService::FetchUserInfoFinished(QNetworkReply* reply) {
@ -118,7 +128,7 @@ void SkydriveService::FetchUserInfoFinished(QNetworkReply* reply) {
QJsonObject json_response = document.object();
QString name = json_response["name"].toString();
QString name = json_response["displayName"].toString();
if (!name.isEmpty()) {
QSettings s;
s.beginGroup(kSettingsGroup);
@ -127,11 +137,11 @@ void SkydriveService::FetchUserInfoFinished(QNetworkReply* reply) {
emit Connected();
ListFiles("me/skydrive");
ListFiles("root");
}
void SkydriveService::ListFiles(const QString& folder) {
QUrl url(QString(kSkydriveBase) + folder + "/files");
QUrl url(QString(kDriveBase) + folder + "/children");
QNetworkRequest request(url);
AddAuthorizationHeader(&request);
@ -148,51 +158,52 @@ void SkydriveService::ListFilesFinished(QNetworkReply* reply) {
QJsonObject json_response = document.object();
QJsonArray files = json_response["data"].toArray();
for (const QJsonValue& f : files) {
QJsonObject file = f.toObject();
if (file["type"].toString() == "folder") {
ListFiles(file["id"].toString());
} else {
QString mime_type = GuessMimeTypeForFile(file["name"].toString());
QJsonArray items = json_response["value"].toArray();
for (const QJsonValue& f : items) {
QJsonObject item = f.toObject();
const QString id = item["id"].toString();
const QString name = item["name"].toString();
if (item.contains("folder")) {
ListFiles(QString("items/%1").arg(id));
} else if (item.contains("file")) {
// The response provides a mime type, but it doesn't know about some
// types that we care about.
QString mime_type = GuessMimeTypeForFile(name);
QUrl url;
url.setScheme("skydrive");
url.setPath("/" + file["id"].toString());
url.setScheme(GetScheme());
url.setPath("/" + id);
Song song;
song.set_url(url);
song.set_ctime(
QDateTime::fromString(file["created_time"].toString()).toTime_t());
song.set_mtime(
QDateTime::fromString(file["updated_time"].toString()).toTime_t());
song.set_comment(file["description"].toString());
song.set_filesize(file["size"].toInt());
song.set_title(file["name"].toString());
QDateTime::fromString(item["createdDateTime"].toString(), Qt::ISODate)
.toTime_t());
song.set_mtime(QDateTime::fromString(
item["lastModifiedDateTime"].toString(), Qt::ISODate)
.toTime_t());
song.set_comment(item["description"].toString());
song.set_filesize(item["size"].toInt());
song.set_title(name);
QUrl download_url(file["source"].toString());
// HTTPS appears to be broken somehow between Qt & Skydrive downloads.
// Fortunately, just changing the scheme to HTTP works.
download_url.setScheme("http");
MaybeAddFileToDatabase(song, mime_type, download_url, QString());
QUrl download_url = ItemUrl(id, "content");
MaybeAddFileToDatabase(song, mime_type, download_url,
QString("Bearer %1").arg(access_token_));
} else {
qLog(Debug) << "Unknown item type for" << name;
}
}
}
QUrl SkydriveService::ItemUrl(const QString& id, const QString& path) {
return QUrl(QString(kDriveBase) + "items/" + id + "/" + path);
}
QUrl SkydriveService::GetStreamingUrlFromSongId(const QString& file_id) {
EnsureConnected();
QUrl url(QString(kSkydriveBase) + file_id);
QNetworkRequest request(url);
AddAuthorizationHeader(&request);
std::unique_ptr<QNetworkReply> reply(network_->get(request));
WaitForSignal(reply.get(), SIGNAL(finished()));
QJsonDocument document = ParseJsonReply(reply.get());
if (document.isNull()) return QUrl();
QJsonObject json_response = document.object();
return QUrl(json_response["source"].toString());
return ItemUrl(file_id, "content");
}
void SkydriveService::EnsureConnected() {

View File

@ -40,6 +40,8 @@ class SkydriveService : public CloudFileService {
virtual bool has_credentials() const;
QUrl GetStreamingUrlFromSongId(const QString& song_id);
QString GetScheme() const { return "onedrive"; }
public slots:
virtual void Connect();
void ForgetCredentials();
@ -52,11 +54,17 @@ class SkydriveService : public CloudFileService {
signals:
void Connected();
private:
friend class SkydriveUrlHandler;
QByteArray GetAuthHeader() const;
private:
QString refresh_token() const;
void AddAuthorizationHeader(QNetworkRequest* request);
void FetchUserInfo();
void ListFiles(const QString& folder);
void EnsureConnected();
QUrl ItemUrl(const QString& id, const QString& path);
QString access_token_;
QDateTime expiry_time_;

View File

@ -27,5 +27,9 @@ SkydriveUrlHandler::SkydriveUrlHandler(SkydriveService* service,
UrlHandler::LoadResult SkydriveUrlHandler::StartLoading(const QUrl& url) {
QString file_id(url.path());
QUrl real_url = service_->GetStreamingUrlFromSongId(file_id);
return LoadResult(url, LoadResult::TrackAvailable, real_url);
LoadResult res(url, LoadResult::TrackAvailable, real_url);
res.auth_header_ = service_->GetAuthHeader();
return res;
}
QString SkydriveUrlHandler::scheme() const { return service_->GetScheme(); }

View File

@ -31,7 +31,7 @@ class SkydriveUrlHandler : public UrlHandler {
explicit SkydriveUrlHandler(SkydriveService* service,
QObject* parent = nullptr);
QString scheme() const { return "skydrive"; }
QString scheme() const;
QIcon icon() const {
return IconLoader::Load("skydrive", IconLoader::Provider);
}