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

View File

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

View File

@ -27,5 +27,9 @@ SkydriveUrlHandler::SkydriveUrlHandler(SkydriveService* service,
UrlHandler::LoadResult SkydriveUrlHandler::StartLoading(const QUrl& url) { UrlHandler::LoadResult SkydriveUrlHandler::StartLoading(const QUrl& url) {
QString file_id(url.path()); QString file_id(url.path());
QUrl real_url = service_->GetStreamingUrlFromSongId(file_id); 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, explicit SkydriveUrlHandler(SkydriveService* service,
QObject* parent = nullptr); QObject* parent = nullptr);
QString scheme() const { return "skydrive"; } QString scheme() const;
QIcon icon() const { QIcon icon() const {
return IconLoader::Load("skydrive", IconLoader::Provider); return IconLoader::Load("skydrive", IconLoader::Provider);
} }