2014-12-17 19:02:21 +01:00
|
|
|
/* 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>
|
2020-09-18 16:15:19 +02:00
|
|
|
#include <QJsonArray>
|
2015-04-15 18:26:09 +02:00
|
|
|
#include <QJsonDocument>
|
|
|
|
#include <QJsonObject>
|
2020-09-18 16:15:19 +02:00
|
|
|
#include <QTimer>
|
2012-11-29 18:48:49 +01:00
|
|
|
|
2012-11-29 20:18:08 +01:00
|
|
|
#include "core/application.h"
|
2012-11-29 18:43:56 +01:00
|
|
|
#include "core/logging.h"
|
|
|
|
#include "core/network.h"
|
2012-11-29 20:18:08 +01:00
|
|
|
#include "core/player.h"
|
2012-11-29 20:36:13 +01:00
|
|
|
#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"
|
2014-12-18 23:35:21 +01:00
|
|
|
#include "internet/dropbox/dropboxurlhandler.h"
|
2012-11-29 20:36:13 +01:00
|
|
|
#include "library/librarybackend.h"
|
2015-10-14 03:01:08 +02:00
|
|
|
#include "ui/iconloader.h"
|
2012-11-29 20:36:13 +01:00
|
|
|
|
|
|
|
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";
|
2013-12-19 14:56:46 +01:00
|
|
|
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:43:56 +01:00
|
|
|
|
2012-11-29 18:19:41 +01:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
DropboxService::DropboxService(Application* app, InternetModel* parent)
|
2014-02-07 16:34:20 +01:00
|
|
|
: CloudFileService(app, parent, kServiceName, kServiceId,
|
2015-10-14 03:01:08 +02:00
|
|
|
IconLoader::Load("dropbox", IconLoader::Provider),
|
2014-02-07 16:34:20 +01:00
|
|
|
SettingsDialog::Page_Dropbox),
|
2012-11-29 18:43:56 +01:00
|
|
|
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() {
|
2012-11-29 18:43:56 +01:00
|
|
|
if (has_credentials()) {
|
2012-11-30 15:33:03 +01:00
|
|
|
RequestFileList();
|
2012-11-29 18:19:41 +01:00
|
|
|
} else {
|
2021-06-30 08:56:20 +02:00
|
|
|
ShowConfig();
|
2012-11-29 18:19:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-25 17:18:58 +02:00
|
|
|
void DropboxService::AuthenticationFinished(OAuthenticator* authenticator) {
|
2012-11-29 18:19:41 +01:00
|
|
|
authenticator->deleteLater();
|
|
|
|
|
2012-11-29 18:43:56 +01:00
|
|
|
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();
|
2012-11-29 18:43:56 +01:00
|
|
|
|
2012-11-30 15:33:03 +01:00
|
|
|
RequestFileList();
|
2012-11-29 18:43:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray DropboxService::GenerateAuthorisationHeader() {
|
2015-09-25 17:18:58 +02:00
|
|
|
return QString("Bearer %1").arg(access_token_).toUtf8();
|
2012-11-29 18:43:56 +01:00
|
|
|
}
|
|
|
|
|
2012-11-30 15:33:03 +01:00
|
|
|
void DropboxService::RequestFileList() {
|
|
|
|
QSettings s;
|
|
|
|
s.beginGroup(kSettingsGroup);
|
|
|
|
|
2016-06-29 13:12:59 +02:00
|
|
|
QString cursor = s.value("cursor", "").toString();
|
2012-11-29 18:43:56 +01:00
|
|
|
|
2016-06-29 13:12:59 +02:00
|
|
|
if (cursor.isEmpty()) {
|
|
|
|
QUrl url = QUrl(QString(kListFolderEndpoint));
|
|
|
|
|
2016-10-07 13:29:50 +02:00
|
|
|
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");
|
|
|
|
|
2016-10-07 13:29:50 +02:00
|
|
|
QJsonDocument document(json);
|
|
|
|
QNetworkReply* reply = network_->post(request, document.toJson());
|
2020-02-14 09:56:18 +01:00
|
|
|
connect(reply, &QNetworkReply::finished,
|
|
|
|
[=] { this->RequestFileListFinished(reply); });
|
2016-06-29 13:12:59 +02:00
|
|
|
} else {
|
|
|
|
QUrl url = QUrl(kListFolderContinueEndpoint);
|
2016-10-07 13:29:50 +02:00
|
|
|
QJsonObject json;
|
2016-06-29 13:12:59 +02:00
|
|
|
json.insert("cursor", cursor);
|
2016-10-07 13:29:50 +02:00
|
|
|
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");
|
2016-10-07 13:29:50 +02:00
|
|
|
QNetworkReply* reply = network_->post(request, document.toJson());
|
2020-02-14 09:56:18 +01:00
|
|
|
connect(reply, &QNetworkReply::finished,
|
|
|
|
[=] { this->RequestFileListFinished(reply); });
|
2016-06-29 13:12:59 +02:00
|
|
|
}
|
2012-11-29 18:43:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void DropboxService::RequestFileListFinished(QNetworkReply* reply) {
|
|
|
|
reply->deleteLater();
|
2012-11-29 18:48:49 +01:00
|
|
|
|
2020-02-14 08:50:15 +01:00
|
|
|
QJsonDocument document = ParseJsonReply(reply);
|
|
|
|
if (document.isNull()) return;
|
|
|
|
|
2015-04-15 18:26:09 +02:00
|
|
|
QJsonObject json_response = document.object();
|
|
|
|
|
|
|
|
if (json_response.contains("reset") && json_response["reset"].toBool()) {
|
2012-11-30 15:40:09 +01:00
|
|
|
qLog(Debug) << "Resetting Dropbox DB";
|
|
|
|
library_backend_->DeleteAll();
|
2012-11-30 15:33:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
QSettings settings;
|
|
|
|
settings.beginGroup(kSettingsGroup);
|
2015-04-15 18:26:09 +02:00
|
|
|
settings.setValue("cursor", json_response["cursor"].toString());
|
2012-11-30 15:33:03 +01:00
|
|
|
|
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) {
|
2016-10-07 13:29:50 +02:00
|
|
|
QJsonObject item = c.toObject();
|
2016-06-29 13:12:59 +02:00
|
|
|
QString path = item["path_lower"].toString();
|
2012-11-30 15:40:09 +01:00
|
|
|
|
|
|
|
QUrl url;
|
|
|
|
url.setScheme("dropbox");
|
|
|
|
url.setPath(path);
|
|
|
|
|
2016-06-29 13:12:59 +02:00
|
|
|
if (item[".tag"].toString() == "deleted") {
|
2012-11-30 15:40:09 +01:00
|
|
|
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") {
|
2012-11-30 15:33:03 +01:00
|
|
|
continue;
|
2012-11-29 18:48:49 +01:00
|
|
|
}
|
2012-12-06 14:23:27 +01:00
|
|
|
|
2016-06-29 13:12:59 +02:00
|
|
|
if (ShouldIndexFile(url, GuessMimeTypeForFile(url.toString()))) {
|
2012-12-06 14:23:27 +01:00
|
|
|
QNetworkReply* reply = FetchContentUrl(url);
|
2020-02-14 09:56:18 +01:00
|
|
|
connect(reply, &QNetworkReply::finished, [=] {
|
|
|
|
this->FetchContentUrlFinished(reply, item.toVariantMap());
|
|
|
|
});
|
2012-12-06 14:23:27 +01:00
|
|
|
}
|
2012-11-30 15:33:03 +01:00
|
|
|
}
|
|
|
|
|
2020-09-18 16:15:19 +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);
|
2016-10-07 13:29:50 +02:00
|
|
|
s.setValue("cursor", json_response["cursor"].toVariant());
|
2012-11-30 15:33:03 +01:00
|
|
|
RequestFileList();
|
2013-12-19 14:56:46 +01:00
|
|
|
} 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;
|
|
|
|
}
|
2013-12-19 14:56:46 +01:00
|
|
|
QSettings s;
|
|
|
|
s.beginGroup(kSettingsGroup);
|
|
|
|
|
|
|
|
QUrl request_url = QUrl(QString(kLongPollEndpoint));
|
2016-10-07 13:29:50 +02:00
|
|
|
QJsonObject json;
|
2016-06-29 13:12:59 +02:00
|
|
|
json.insert("cursor", s.value("cursor").toString());
|
|
|
|
json.insert("timeout", 30);
|
2013-12-19 14:56:46 +01:00
|
|
|
QNetworkRequest request(request_url);
|
2016-06-29 13:12:59 +02:00
|
|
|
request.setRawHeader("Content-Type", "application/json; charset=utf-8");
|
2016-10-07 13:29:50 +02:00
|
|
|
QJsonDocument document(json);
|
|
|
|
QNetworkReply* reply = network_->post(request, document.toJson());
|
2020-02-14 09:56:18 +01:00
|
|
|
connect(reply, &QNetworkReply::finished,
|
|
|
|
[=] { this->LongPollFinished(reply); });
|
2013-12-19 14:56:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void DropboxService::LongPollFinished(QNetworkReply* reply) {
|
|
|
|
reply->deleteLater();
|
2020-02-14 08:50:15 +01:00
|
|
|
|
|
|
|
QJsonDocument document = ParseJsonReply(reply);
|
|
|
|
if (document.isNull()) return;
|
|
|
|
|
|
|
|
QJsonObject json_response = document.object();
|
2015-04-15 18:26:09 +02:00
|
|
|
if (json_response["changes"].toBool()) {
|
2013-12-19 14:56:46 +01:00
|
|
|
// 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);
|
2013-12-19 14:56:46 +01:00
|
|
|
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);
|
2016-10-07 13:29:50 +02:00
|
|
|
QJsonObject json;
|
2016-06-29 13:12:59 +02:00
|
|
|
json.insert("path", url.path());
|
2016-10-07 13:29:50 +02:00
|
|
|
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");
|
2016-10-07 13:29:50 +02:00
|
|
|
return network_->post(request, document.toJson());
|
2012-11-29 20:18:08 +01:00
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
void DropboxService::FetchContentUrlFinished(QNetworkReply* reply,
|
|
|
|
const QVariantMap& data) {
|
2012-11-29 20:18:08 +01:00
|
|
|
reply->deleteLater();
|
2020-02-14 08:50:15 +01:00
|
|
|
|
|
|
|
QJsonDocument document = ParseJsonReply(reply);
|
|
|
|
if (document.isNull()) return;
|
|
|
|
|
|
|
|
QJsonObject json_response = document.object();
|
2016-06-29 13:12:59 +02:00
|
|
|
QFileInfo info(data["path_lower"].toString());
|
2012-11-29 20:18:08 +01:00
|
|
|
|
2012-11-29 20:36:13 +01:00
|
|
|
QUrl url;
|
|
|
|
url.setScheme("dropbox");
|
2016-06-29 13:12:59 +02:00
|
|
|
url.setPath(data["path_lower"].toString());
|
2012-11-29 20:36:13 +01:00
|
|
|
|
2012-12-06 14:23:27 +01:00
|
|
|
Song song;
|
|
|
|
song.set_url(url);
|
|
|
|
song.set_etag(data["rev"].toString());
|
2020-09-18 16:15:19 +02:00
|
|
|
song.set_mtime(
|
|
|
|
QDateTime::fromString(data["server_modified"].toString(), Qt::ISODate)
|
|
|
|
.toTime_t());
|
2012-12-06 14:23:27 +01:00
|
|
|
song.set_title(info.fileName());
|
2016-06-29 13:12:59 +02:00
|
|
|
song.set_filesize(data["size"].toInt());
|
2012-12-06 14:23:27 +01:00
|
|
|
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()));
|
|
|
|
|
2020-02-14 08:50:15 +01:00
|
|
|
QJsonDocument document = ParseJsonReply(reply);
|
|
|
|
if (document.isNull()) return QUrl();
|
|
|
|
|
|
|
|
QJsonObject json_response = document.object();
|
2016-10-07 13:29:50 +02:00
|
|
|
return QUrl::fromEncoded(json_response["link"].toVariant().toByteArray());
|
2012-11-29 20:18:08 +01:00
|
|
|
}
|