From 576731767832647141796b64323f1dca8eedee1e Mon Sep 17 00:00:00 2001 From: Jim Broadus Date: Sun, 21 Feb 2021 23:58:56 -0800 Subject: [PATCH] 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 --- src/internet/skydrive/skydriveservice.cpp | 91 +++++++++++--------- src/internet/skydrive/skydriveservice.h | 8 ++ src/internet/skydrive/skydriveurlhandler.cpp | 6 +- src/internet/skydrive/skydriveurlhandler.h | 2 +- 4 files changed, 65 insertions(+), 42 deletions(-) diff --git a/src/internet/skydrive/skydriveservice.cpp b/src/internet/skydrive/skydriveservice.cpp index 44aebbd74..b9ec6ae09 100644 --- a/src/internet/skydrive/skydriveservice.cpp +++ b/src/internet/skydrive/skydriveservice.cpp @@ -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 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() { diff --git a/src/internet/skydrive/skydriveservice.h b/src/internet/skydrive/skydriveservice.h index 2f8ed7fa8..41f2b8fb9 100644 --- a/src/internet/skydrive/skydriveservice.h +++ b/src/internet/skydrive/skydriveservice.h @@ -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_; diff --git a/src/internet/skydrive/skydriveurlhandler.cpp b/src/internet/skydrive/skydriveurlhandler.cpp index faaf189de..2f1170593 100644 --- a/src/internet/skydrive/skydriveurlhandler.cpp +++ b/src/internet/skydrive/skydriveurlhandler.cpp @@ -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(); } diff --git a/src/internet/skydrive/skydriveurlhandler.h b/src/internet/skydrive/skydriveurlhandler.h index 95cd4c1d9..b6735cd49 100644 --- a/src/internet/skydrive/skydriveurlhandler.h +++ b/src/internet/skydrive/skydriveurlhandler.h @@ -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); }