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 2013-2014, David Sansome <me@davidsansome.com>
|
|
|
|
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/>.
|
|
|
|
*/
|
|
|
|
|
2014-12-18 23:35:21 +01:00
|
|
|
#include "internet/core/cloudfileservice.h"
|
2012-11-28 16:13:10 +01:00
|
|
|
|
|
|
|
#include <QMenu>
|
2021-02-25 06:15:23 +01:00
|
|
|
#include <QMessageBox>
|
|
|
|
#include <QPushButton>
|
2012-11-28 16:13:10 +01:00
|
|
|
#include <QSortFilterProxyModel>
|
|
|
|
|
|
|
|
#include "core/application.h"
|
|
|
|
#include "core/database.h"
|
|
|
|
#include "core/mergedproxymodel.h"
|
|
|
|
#include "core/network.h"
|
|
|
|
#include "core/player.h"
|
2012-12-06 14:23:27 +01:00
|
|
|
#include "core/taskmanager.h"
|
2012-11-28 16:13:10 +01:00
|
|
|
#include "globalsearch/globalsearch.h"
|
2014-12-18 23:35:21 +01:00
|
|
|
#include "internet/core/cloudfilesearchprovider.h"
|
|
|
|
#include "internet/core/internetmodel.h"
|
2012-11-28 16:13:10 +01:00
|
|
|
#include "library/librarybackend.h"
|
|
|
|
#include "library/librarymodel.h"
|
|
|
|
#include "playlist/playlist.h"
|
|
|
|
#include "ui/iconloader.h"
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
CloudFileService::CloudFileService(Application* app, InternetModel* parent,
|
|
|
|
const QString& service_name,
|
|
|
|
const QString& service_id, const QIcon& icon,
|
|
|
|
SettingsDialog::Page settings_page)
|
|
|
|
: InternetService(service_name, app, parent, parent),
|
|
|
|
root_(nullptr),
|
|
|
|
network_(new NetworkAccessManager(this)),
|
|
|
|
library_sort_model_(new QSortFilterProxyModel(this)),
|
|
|
|
playlist_manager_(app->playlist_manager()),
|
|
|
|
task_manager_(app->task_manager()),
|
|
|
|
icon_(icon),
|
2014-03-30 06:47:25 +02:00
|
|
|
settings_page_(settings_page),
|
|
|
|
indexing_task_id_(-1),
|
|
|
|
indexing_task_progress_(0),
|
|
|
|
indexing_task_max_(0) {
|
2020-01-12 08:34:35 +01:00
|
|
|
library_backend_.reset(new LibraryBackend,
|
|
|
|
[](QObject* obj) { obj->deleteLater(); });
|
2012-11-28 16:13:10 +01:00
|
|
|
library_backend_->moveToThread(app_->database()->thread());
|
|
|
|
|
|
|
|
QString songs_table = service_id + "_songs";
|
|
|
|
QString songs_fts_table = service_id + "_songs_fts";
|
|
|
|
|
2020-03-23 05:36:55 +01:00
|
|
|
library_backend_->Init(app->database(), songs_table, songs_fts_table);
|
2012-11-28 16:13:10 +01:00
|
|
|
library_model_ = new LibraryModel(library_backend_, app_, this);
|
|
|
|
|
|
|
|
library_sort_model_->setSourceModel(library_model_);
|
|
|
|
library_sort_model_->setSortRole(LibraryModel::Role_SortText);
|
|
|
|
library_sort_model_->setDynamicSortFilter(true);
|
2013-02-05 12:17:09 +01:00
|
|
|
library_sort_model_->setSortLocaleAware(true);
|
2012-11-28 16:13:10 +01:00
|
|
|
library_sort_model_->sort(0);
|
|
|
|
|
2020-01-12 08:34:35 +01:00
|
|
|
app->global_search()->AddProvider(new CloudFileSearchProvider(
|
|
|
|
library_backend_.get(), service_id, icon_, this));
|
2012-11-28 16:13:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
QStandardItem* CloudFileService::CreateRootItem() {
|
|
|
|
root_ = new QStandardItem(icon_, name());
|
|
|
|
root_->setData(true, InternetModel::Role_CanLazyLoad);
|
|
|
|
return root_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CloudFileService::LazyPopulate(QStandardItem* item) {
|
|
|
|
switch (item->data(InternetModel::Role_Type).toInt()) {
|
|
|
|
case InternetModel::Type_Service:
|
|
|
|
if (!has_credentials()) {
|
2021-06-30 08:56:20 +02:00
|
|
|
ShowConfig();
|
2012-11-28 16:13:10 +01:00
|
|
|
} else {
|
|
|
|
Connect();
|
|
|
|
}
|
|
|
|
library_model_->Init();
|
|
|
|
model()->merged_model()->AddSubModel(item->index(), library_sort_model_);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-25 07:30:47 +01:00
|
|
|
void CloudFileService::PopulateContextMenu() {
|
|
|
|
context_menu_->addActions(GetPlaylistActions());
|
|
|
|
context_menu_->addAction(IconLoader::Load("download", IconLoader::Base),
|
|
|
|
tr("Cover Manager"), this, SLOT(ShowCoverManager()));
|
|
|
|
context_menu_->addSeparator();
|
|
|
|
context_menu_->addAction(IconLoader::Load("configure", IconLoader::Base),
|
2021-06-30 08:56:20 +02:00
|
|
|
tr("Configure..."), this, SLOT(ShowConfig()));
|
2021-02-25 07:30:47 +01:00
|
|
|
}
|
|
|
|
|
2012-11-28 16:13:10 +01:00
|
|
|
void CloudFileService::ShowCoverManager() {
|
|
|
|
if (!cover_manager_) {
|
2020-01-12 08:34:35 +01:00
|
|
|
cover_manager_.reset(new AlbumCoverManager(app_, library_backend_.get()));
|
2012-11-28 16:13:10 +01:00
|
|
|
cover_manager_->Init();
|
|
|
|
connect(cover_manager_.get(), SIGNAL(AddToPlaylist(QMimeData*)),
|
|
|
|
SLOT(AddToPlaylist(QMimeData*)));
|
|
|
|
}
|
|
|
|
cover_manager_->show();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CloudFileService::AddToPlaylist(QMimeData* mime) {
|
2014-02-07 16:34:20 +01:00
|
|
|
playlist_manager_->current()->dropMimeData(mime, Qt::CopyAction, -1, 0,
|
|
|
|
QModelIndex());
|
2012-11-28 16:13:10 +01:00
|
|
|
}
|
|
|
|
|
2021-06-30 08:56:20 +02:00
|
|
|
void CloudFileService::ShowConfig() {
|
2012-11-28 16:13:10 +01:00
|
|
|
app_->OpenSettingsDialogAtPage(settings_page_);
|
|
|
|
}
|
2012-12-06 14:23:27 +01:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
bool CloudFileService::ShouldIndexFile(const QUrl& url,
|
|
|
|
const QString& mime_type) const {
|
2012-12-06 14:23:27 +01:00
|
|
|
if (!IsSupportedMimeType(mime_type)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Song library_song = library_backend_->GetSongByUrl(url);
|
|
|
|
if (library_song.is_valid()) {
|
|
|
|
qLog(Debug) << "Already have:" << url;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
void CloudFileService::MaybeAddFileToDatabase(const Song& metadata,
|
|
|
|
const QString& mime_type,
|
|
|
|
const QUrl& download_url,
|
|
|
|
const QString& authorisation) {
|
2012-12-06 14:23:27 +01:00
|
|
|
if (!ShouldIndexFile(metadata.url(), mime_type)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-03-30 06:47:25 +02:00
|
|
|
if (indexing_task_id_ == -1) {
|
2014-12-17 19:02:21 +01:00
|
|
|
indexing_task_id_ = task_manager_->StartTask(tr("Indexing %1").arg(name()));
|
2014-03-30 06:47:25 +02:00
|
|
|
indexing_task_progress_ = 0;
|
|
|
|
indexing_task_max_ = 0;
|
|
|
|
}
|
2014-12-17 19:02:21 +01:00
|
|
|
indexing_task_max_++;
|
|
|
|
task_manager_->SetTaskProgress(indexing_task_id_, indexing_task_progress_,
|
|
|
|
indexing_task_max_);
|
2012-12-06 14:23:27 +01:00
|
|
|
|
|
|
|
TagReaderClient::ReplyType* reply = app_->tag_reader_client()->ReadCloudFile(
|
2014-02-07 16:34:20 +01:00
|
|
|
download_url, metadata.title(), metadata.filesize(), mime_type,
|
2012-12-06 14:23:27 +01:00
|
|
|
authorisation);
|
2015-02-20 20:13:47 +01:00
|
|
|
pending_tagreader_replies_.append(reply);
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
NewClosure(reply, SIGNAL(Finished(bool)), this,
|
2014-12-17 19:02:21 +01:00
|
|
|
SLOT(ReadTagsFinished(TagReaderClient::ReplyType*, Song)), reply,
|
|
|
|
metadata);
|
2012-12-06 14:23:27 +01:00
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
void CloudFileService::ReadTagsFinished(TagReaderClient::ReplyType* reply,
|
2014-03-30 06:47:25 +02:00
|
|
|
const Song& metadata) {
|
2015-02-20 20:13:47 +01:00
|
|
|
int index_reply;
|
|
|
|
|
2012-12-06 14:23:27 +01:00
|
|
|
reply->deleteLater();
|
2014-03-30 06:47:25 +02:00
|
|
|
|
2015-02-20 20:13:47 +01:00
|
|
|
if ((index_reply = pending_tagreader_replies_.indexOf(reply)) == -1) {
|
|
|
|
qLog(Debug) << "Ignore the reply";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
pending_tagreader_replies_.removeAt(index_reply);
|
|
|
|
|
2014-12-17 19:02:21 +01:00
|
|
|
indexing_task_progress_++;
|
2014-03-30 06:47:25 +02:00
|
|
|
if (indexing_task_progress_ == indexing_task_max_) {
|
|
|
|
task_manager_->SetTaskFinished(indexing_task_id_);
|
|
|
|
indexing_task_id_ = -1;
|
2014-03-30 07:01:58 +02:00
|
|
|
emit AllIndexingTasksFinished();
|
2014-03-30 06:47:25 +02:00
|
|
|
} else {
|
2014-12-17 19:02:21 +01:00
|
|
|
task_manager_->SetTaskProgress(indexing_task_id_, indexing_task_progress_,
|
|
|
|
indexing_task_max_);
|
2014-03-30 06:47:25 +02:00
|
|
|
}
|
2012-12-06 14:23:27 +01:00
|
|
|
|
2021-02-20 22:20:04 +01:00
|
|
|
const cpb::tagreader::ReadCloudFileResponse& message =
|
2012-12-06 14:23:27 +01:00
|
|
|
reply->message().read_cloud_file_response();
|
|
|
|
if (!message.has_metadata() || !message.metadata().filesize()) {
|
|
|
|
qLog(Debug) << "Failed to tag:" << metadata.url();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-20 22:20:04 +01:00
|
|
|
cpb::tagreader::SongMetadata metadata_pb;
|
2012-12-06 14:23:27 +01:00
|
|
|
metadata.ToProtobuf(&metadata_pb);
|
|
|
|
metadata_pb.MergeFrom(message.metadata());
|
|
|
|
|
|
|
|
Song song;
|
|
|
|
song.InitFromProtobuf(metadata_pb);
|
|
|
|
song.set_directory_id(0);
|
|
|
|
|
|
|
|
qLog(Debug) << "Adding song to db:" << song.title();
|
|
|
|
library_backend_->AddOrUpdateSongs(SongList() << song);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CloudFileService::IsSupportedMimeType(const QString& mime_type) const {
|
2014-02-07 16:34:20 +01:00
|
|
|
return mime_type == "audio/ogg" || mime_type == "audio/mpeg" ||
|
|
|
|
mime_type == "audio/mp4" || mime_type == "audio/flac" ||
|
|
|
|
mime_type == "audio/x-flac" || mime_type == "application/ogg" ||
|
|
|
|
mime_type == "application/x-flac" || mime_type == "audio/x-ms-wma";
|
2012-12-06 14:23:27 +01:00
|
|
|
}
|
2012-12-13 14:27:21 +01:00
|
|
|
|
|
|
|
QString CloudFileService::GuessMimeTypeForFile(const QString& filename) const {
|
2017-01-12 16:58:44 +01:00
|
|
|
if (filename.endsWith(".mp3", Qt::CaseInsensitive)) {
|
2012-12-13 14:27:21 +01:00
|
|
|
return "audio/mpeg";
|
2017-01-12 16:58:44 +01:00
|
|
|
} else if (filename.endsWith(".m4a", Qt::CaseInsensitive) ||
|
|
|
|
filename.endsWith(".m4b", Qt::CaseInsensitive)) {
|
2012-12-13 14:27:21 +01:00
|
|
|
return "audio/mpeg";
|
2017-01-12 16:58:44 +01:00
|
|
|
} else if (filename.endsWith(".ogg", Qt::CaseInsensitive) ||
|
|
|
|
filename.endsWith(".opus", Qt::CaseInsensitive)) {
|
2012-12-13 14:27:21 +01:00
|
|
|
return "application/ogg";
|
2017-01-12 16:58:44 +01:00
|
|
|
} else if (filename.endsWith(".flac", Qt::CaseInsensitive)) {
|
2012-12-13 14:27:21 +01:00
|
|
|
return "application/x-flac";
|
2017-01-12 16:58:44 +01:00
|
|
|
} else if (filename.endsWith(".wma", Qt::CaseInsensitive)) {
|
2012-12-13 14:27:21 +01:00
|
|
|
return "audio/x-ms-wma";
|
|
|
|
}
|
2019-11-09 23:45:28 +01:00
|
|
|
return QString();
|
2012-12-13 14:27:21 +01:00
|
|
|
}
|
2015-02-20 20:13:47 +01:00
|
|
|
|
|
|
|
void CloudFileService::AbortReadTagsReplies() {
|
|
|
|
qLog(Debug) << "Aborting the read tags replies";
|
|
|
|
pending_tagreader_replies_.clear();
|
|
|
|
|
|
|
|
task_manager_->SetTaskFinished(indexing_task_id_);
|
|
|
|
indexing_task_id_ = -1;
|
|
|
|
emit AllIndexingTasksFinished();
|
|
|
|
}
|
2021-02-25 06:15:23 +01:00
|
|
|
|
|
|
|
void CloudFileService::FullRescanRequested() {
|
|
|
|
QMessageBox* message_box = new QMessageBox(
|
|
|
|
QMessageBox::Warning, tr("Do a full rescan"),
|
|
|
|
tr("Doing a full rescan will lose any metadata you've saved in "
|
|
|
|
"Clementine such as cover art, play counts and ratings. Clementine "
|
|
|
|
"will rescan all your music in %1 which may take some "
|
|
|
|
"time.")
|
|
|
|
.arg(name()),
|
|
|
|
QMessageBox::NoButton);
|
|
|
|
QPushButton* button = message_box->addButton(tr("Do a full rescan"),
|
|
|
|
QMessageBox::DestructiveRole);
|
|
|
|
connect(button, SIGNAL(clicked()), SLOT(DoFullRescan()));
|
|
|
|
|
|
|
|
message_box->addButton(QMessageBox::Cancel);
|
|
|
|
message_box->setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
message_box->show();
|
|
|
|
}
|