Clementine-audio-player-Mac.../src/internet/dropbox/dropboxservice.cpp

273 lines
8.8 KiB
C++
Raw Normal View History

/* This file is part of Clementine.
Copyright 2012-2014, John Maguire <john.maguire@gmail.com>
Copyright 2013, Martin Brodbeck <martin@brodbeck-online.de>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
2012-11-29 18:19:41 +01:00
#include "dropboxservice.h"
2012-11-29 20:18:08 +01:00
#include <QFileInfo>
#include <QTimer>
2015-04-15 18:26:09 +02:00
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
2012-11-29 18:48:49 +01:00
2012-11-29 20:18:08 +01:00
#include "core/application.h"
#include "core/logging.h"
#include "core/network.h"
2012-11-29 20:18:08 +01:00
#include "core/player.h"
#include "core/utilities.h"
2012-11-29 20:18:08 +01:00
#include "core/waitforsignal.h"
2015-09-25 17:18:58 +02:00
#include "internet/core/oauthenticator.h"
#include "internet/dropbox/dropboxurlhandler.h"
#include "library/librarybackend.h"
#include "ui/iconloader.h"
using Utilities::ParseRFC822DateTime;
2012-11-29 18:19:41 +01:00
const char* DropboxService::kServiceName = "Dropbox";
const char* DropboxService::kSettingsGroup = "Dropbox";
namespace {
static const char* kServiceId = "dropbox";
2016-06-29 13:12:59 +02:00
static const char* kMediaEndpoint =
"https://api.dropboxapi.com/2/files/get_temporary_link";
static const char* kListFolderEndpoint =
"https://api.dropboxapi.com/2/files/list_folder";
static const char* kListFolderContinueEndpoint =
"https://api.dropboxapi.com/2/files/list_folder/continue";
static const char* kLongPollEndpoint =
2016-06-29 13:12:59 +02:00
"https://notify.dropboxapi.com/2/files/list_folder/longpoll";
2012-11-29 18:19:41 +01:00
} // namespace
DropboxService::DropboxService(Application* app, InternetModel* parent)
: CloudFileService(app, parent, kServiceName, kServiceId,
IconLoader::Load("dropbox", IconLoader::Provider),
SettingsDialog::Page_Dropbox),
network_(new NetworkAccessManager(this)) {
QSettings settings;
settings.beginGroup(kSettingsGroup);
2015-09-25 17:18:58 +02:00
// OAuth2 version of dropbox auth token.
access_token_ = settings.value("access_token2").toString();
2012-11-29 20:18:08 +01:00
app->player()->RegisterUrlHandler(new DropboxUrlHandler(this, this));
2012-11-29 18:19:41 +01:00
}
bool DropboxService::has_credentials() const {
return !access_token_.isEmpty();
}
void DropboxService::Connect() {
if (has_credentials()) {
RequestFileList();
2012-11-29 18:19:41 +01:00
} else {
ShowSettingsDialog();
}
}
2015-09-25 17:18:58 +02:00
void DropboxService::AuthenticationFinished(OAuthenticator* authenticator) {
2012-11-29 18:19:41 +01:00
authenticator->deleteLater();
access_token_ = authenticator->access_token();
2012-11-29 18:19:41 +01:00
QSettings settings;
settings.beginGroup(kSettingsGroup);
2015-09-25 17:18:58 +02:00
settings.setValue("access_token2", access_token_);
2012-11-29 18:19:41 +01:00
emit Connected();
RequestFileList();
}
QByteArray DropboxService::GenerateAuthorisationHeader() {
2015-09-25 17:18:58 +02:00
return QString("Bearer %1").arg(access_token_).toUtf8();
}
void DropboxService::RequestFileList() {
QSettings s;
s.beginGroup(kSettingsGroup);
2016-06-29 13:12:59 +02:00
QString cursor = s.value("cursor", "").toString();
2016-06-29 13:12:59 +02:00
if (cursor.isEmpty()) {
QUrl url = QUrl(QString(kListFolderEndpoint));
QJsonObject json;
2016-06-29 13:12:59 +02:00
json.insert("path", "");
json.insert("recursive", true);
json.insert("include_deleted", true);
QNetworkRequest request(url);
request.setRawHeader("Authorization", GenerateAuthorisationHeader());
request.setRawHeader("Content-Type", "application/json; charset=utf-8");
QJsonDocument document(json);
QNetworkReply* reply = network_->post(request, document.toJson());
2016-06-29 13:12:59 +02:00
NewClosure(reply, SIGNAL(finished()), this,
SLOT(RequestFileListFinished(QNetworkReply*)), reply);
} else {
QUrl url = QUrl(kListFolderContinueEndpoint);
QJsonObject json;
2016-06-29 13:12:59 +02:00
json.insert("cursor", cursor);
QJsonDocument document(json);
2016-06-29 13:12:59 +02:00
QNetworkRequest request(url);
request.setRawHeader("Authorization", GenerateAuthorisationHeader());
request.setRawHeader("Content-Type", "application/json; charset=utf-8");
QNetworkReply* reply = network_->post(request, document.toJson());
2016-06-29 13:12:59 +02:00
NewClosure(reply, SIGNAL(finished()), this,
SLOT(RequestFileListFinished(QNetworkReply*)), reply);
}
}
void DropboxService::RequestFileListFinished(QNetworkReply* reply) {
reply->deleteLater();
2012-11-29 18:48:49 +01:00
2015-04-15 18:26:09 +02:00
QJsonDocument document = QJsonDocument::fromBinaryData(reply->readAll());
QJsonObject json_response = document.object();
if (json_response.contains("reset") && json_response["reset"].toBool()) {
qLog(Debug) << "Resetting Dropbox DB";
library_backend_->DeleteAll();
}
QSettings settings;
settings.beginGroup(kSettingsGroup);
2015-04-15 18:26:09 +02:00
settings.setValue("cursor", json_response["cursor"].toString());
2015-04-15 18:26:09 +02:00
QJsonArray contents = json_response["entries"].toArray();
2016-06-29 13:12:59 +02:00
qLog(Debug) << "File list found:" << contents.size();
2015-04-15 18:26:09 +02:00
for (const QJsonValue& c : contents) {
QJsonObject item = c.toObject();
2016-06-29 13:12:59 +02:00
QString path = item["path_lower"].toString();
QUrl url;
url.setScheme("dropbox");
url.setPath(path);
2016-06-29 13:12:59 +02:00
if (item[".tag"].toString() == "deleted") {
qLog(Debug) << "Deleting:" << url;
Song song = library_backend_->GetSongByUrl(url);
if (song.is_valid()) {
library_backend_->DeleteSongs(SongList() << song);
}
continue;
}
2016-06-29 13:12:59 +02:00
if (item[".tag"].toString() == "folder") {
continue;
2012-11-29 18:48:49 +01:00
}
2016-06-29 13:12:59 +02:00
if (ShouldIndexFile(url, GuessMimeTypeForFile(url.toString()))) {
QNetworkReply* reply = FetchContentUrl(url);
NewClosure(reply, SIGNAL(finished()), this,
SLOT(FetchContentUrlFinished(QNetworkReply*, QVariantMap)),
2016-06-29 13:12:59 +02:00
reply, item);
}
}
2015-04-15 18:26:09 +02:00
if (json_response.contains("has_more") && json_response["has_more"].toBool()) {
2016-06-29 13:12:59 +02:00
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("cursor", json_response["cursor"].toVariant());
RequestFileList();
} else {
// Long-poll wait for changes.
LongPollDelta();
}
}
void DropboxService::LongPollDelta() {
2015-09-25 17:25:42 +02:00
if (!has_credentials()) {
// Might have been signed out by the user.
return;
}
QSettings s;
s.beginGroup(kSettingsGroup);
QUrl request_url = QUrl(QString(kLongPollEndpoint));
QJsonObject json;
2016-06-29 13:12:59 +02:00
json.insert("cursor", s.value("cursor").toString());
json.insert("timeout", 30);
QNetworkRequest request(request_url);
2016-06-29 13:12:59 +02:00
request.setRawHeader("Content-Type", "application/json; charset=utf-8");
QJsonDocument document(json);
QNetworkReply* reply = network_->post(request, document.toJson());
NewClosure(reply, SIGNAL(finished()), this,
SLOT(LongPollFinished(QNetworkReply*)), reply);
}
void DropboxService::LongPollFinished(QNetworkReply* reply) {
reply->deleteLater();
2015-04-15 18:26:09 +02:00
QJsonObject json_response = QJsonDocument::fromBinaryData(reply->readAll()).object();
if (json_response["changes"].toBool()) {
// New changes, we should request deltas again.
qLog(Debug) << "Detected new dropbox changes; fetching...";
RequestFileList();
} else {
bool ok = false;
2015-04-15 18:26:09 +02:00
int backoff_secs = json_response["backoff"].toString().toInt(&ok);
backoff_secs = ok ? backoff_secs : 0;
QTimer::singleShot(backoff_secs * 1000, this, SLOT(LongPollDelta()));
2012-11-29 18:48:49 +01:00
}
2012-11-29 18:19:41 +01:00
}
2012-11-29 20:18:08 +01:00
QNetworkReply* DropboxService::FetchContentUrl(const QUrl& url) {
2016-06-29 13:12:59 +02:00
QUrl request_url(kMediaEndpoint);
QJsonObject json;
2016-06-29 13:12:59 +02:00
json.insert("path", url.path());
QJsonDocument document(json);
2012-11-29 20:18:08 +01:00
QNetworkRequest request(request_url);
request.setRawHeader("Authorization", GenerateAuthorisationHeader());
2016-06-29 13:12:59 +02:00
request.setRawHeader("Content-Type", "application/json; charset=utf-8");
return network_->post(request, document.toJson());
2012-11-29 20:18:08 +01:00
}
void DropboxService::FetchContentUrlFinished(QNetworkReply* reply,
const QVariantMap& data) {
2012-11-29 20:18:08 +01:00
reply->deleteLater();
2015-04-15 18:26:09 +02:00
QJsonObject json_response = QJsonDocument::fromBinaryData(reply->readAll()).object();
2016-06-29 13:12:59 +02:00
QFileInfo info(data["path_lower"].toString());
2012-11-29 20:18:08 +01:00
QUrl url;
url.setScheme("dropbox");
2016-06-29 13:12:59 +02:00
url.setPath(data["path_lower"].toString());
Song song;
song.set_url(url);
song.set_etag(data["rev"].toString());
2016-06-29 13:12:59 +02:00
song.set_mtime(QDateTime::fromString(data["server_modified"].toString(),
Qt::ISODate).toTime_t());
song.set_title(info.fileName());
2016-06-29 13:12:59 +02:00
song.set_filesize(data["size"].toInt());
song.set_ctime(0);
2019-11-10 00:31:37 +01:00
MaybeAddFileToDatabase(
song, GuessMimeTypeForFile(url.toString()),
QUrl::fromEncoded(json_response["link"].toVariant().toByteArray()),
QString());
2012-11-29 20:18:08 +01:00
}
QUrl DropboxService::GetStreamingUrlFromSongId(const QUrl& url) {
QNetworkReply* reply = FetchContentUrl(url);
WaitForSignal(reply, SIGNAL(finished()));
2015-04-15 18:26:09 +02:00
QJsonObject json_response = QJsonDocument::fromJson(reply->readAll()).object();
return QUrl::fromEncoded(json_response["link"].toVariant().toByteArray());
2012-11-29 20:18:08 +01:00
}