Refactor cloud tagging into base class.

This commit is contained in:
John Maguire 2012-12-06 14:23:27 +01:00
parent 951cac2ad6
commit 941aaca87c
11 changed files with 192 additions and 257 deletions

View File

@ -47,6 +47,7 @@ message SongMetadata {
optional bool suspicious_tags = 27;
optional string art_automatic = 28;
optional Type type = 29;
optional string etag = 30;
}
message ReadFileRequest {

View File

@ -15,23 +15,10 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mpris_common.h"
#include "song.h"
#include "timeconstants.h"
#include "core/logging.h"
#include "core/messagehandler.h"
#include <algorithm>
#ifdef HAVE_LIBLASTFM
#include "internet/fixlastfm.h"
#ifdef HAVE_LIBLASTFM1
#include <lastfm/Track.h>
#else
#include <lastfm/Track>
#endif
#endif
#include <QFile>
#include <QFileInfo>
#include <QLatin1Literal>
@ -42,6 +29,15 @@
#include <QVariant>
#include <QtConcurrentRun>
#ifdef HAVE_LIBLASTFM
#include "internet/fixlastfm.h"
#ifdef HAVE_LIBLASTFM1
#include <lastfm/Track.h>
#else
#include <lastfm/Track>
#endif
#endif
#include <id3v1genres.h>
#ifdef Q_OS_WIN32
@ -57,14 +53,15 @@
# include <libmtp.h>
#endif
#include <boost/scoped_array.hpp>
#include <boost/scoped_ptr.hpp>
using boost::scoped_ptr;
#include "utilities.h"
#include "core/logging.h"
#include "core/messagehandler.h"
#include "core/mpris_common.h"
#include "core/timeconstants.h"
#include "core/utilities.h"
#include "covers/albumcoverloader.h"
#include "engines/enginebase.h"
#include "library/sqlrow.h"
#include "tagreadermessages.pb.h"
#include "widgets/trackslider.h"
@ -409,6 +406,7 @@ void Song::InitFromProtobuf(const pb::tagreader::SongMetadata& pb) {
d->filesize_ = pb.filesize();
d->suspicious_tags_ = pb.suspicious_tags();
d->filetype_ = static_cast<FileType>(pb.type());
d->etag_ = QStringFromStdString(pb.etag());
if (pb.has_art_automatic()) {
d->art_automatic_ = QStringFromStdString(pb.art_automatic());

View File

@ -25,9 +25,14 @@
#include <QVariantMap>
#include "config.h"
#include "tagreadermessages.pb.h"
#include "engines/engine_fwd.h"
namespace pb {
namespace tagreader {
class SongMetadata;
} // namespace tagreader
} // namespace pb
class QSqlQuery;
class QUrl;

View File

@ -8,6 +8,7 @@
#include "core/mergedproxymodel.h"
#include "core/network.h"
#include "core/player.h"
#include "core/taskmanager.h"
#include "globalsearch/globalsearch.h"
#include "globalsearch/librarysearchprovider.h"
#include "internet/internetmodel.h"
@ -28,6 +29,7 @@ CloudFileService::CloudFileService(
network_(new NetworkAccessManager(this)),
library_sort_model_(new QSortFilterProxyModel(this)),
playlist_manager_(app->playlist_manager()),
task_manager_(app->task_manager()),
icon_(icon),
settings_page_(settings_page) {
library_backend_ = new LibraryBackend;
@ -107,3 +109,73 @@ void CloudFileService::AddToPlaylist(QMimeData* mime) {
void CloudFileService::ShowSettingsDialog() {
app_->OpenSettingsDialogAtPage(settings_page_);
}
bool CloudFileService::ShouldIndexFile(const QUrl& url, const QString& mime_type) const {
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;
}
void CloudFileService::MaybeAddFileToDatabase(
const Song& metadata,
const QString& mime_type,
const QUrl& download_url,
const QString& authorisation) {
if (!ShouldIndexFile(metadata.url(), mime_type)) {
return;
}
const int task_id = task_manager_->StartTask(
tr("Indexing %1").arg(metadata.title()));
TagReaderClient::ReplyType* reply = app_->tag_reader_client()->ReadCloudFile(
download_url,
metadata.title(),
metadata.filesize(),
mime_type,
authorisation);
NewClosure(reply, SIGNAL(Finished(bool)),
this, SLOT(ReadTagsFinished(TagReaderClient::ReplyType*,Song,int)),
reply, metadata, task_id);
}
void CloudFileService::ReadTagsFinished(
TagReaderClient::ReplyType* reply,
const Song& metadata,
const int task_id) {
reply->deleteLater();
TaskManager::ScopedTask(task_id, task_manager_);
const pb::tagreader::ReadCloudFileResponse& message =
reply->message().read_cloud_file_response();
if (!message.has_metadata() || !message.metadata().filesize()) {
qLog(Debug) << "Failed to tag:" << metadata.url();
return;
}
pb::tagreader::SongMetadata metadata_pb;
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 {
return mime_type == "audio/ogg" ||
mime_type == "audio/mpeg" ||
mime_type == "audio/mp4" ||
mime_type == "audio/flac" ||
mime_type == "application/ogg" ||
mime_type == "application/x-flac";
}

View File

@ -5,6 +5,7 @@
#include <QMenu>
#include "core/tagreaderclient.h"
#include "ui/albumcovermanager.h"
class UrlHandler;
@ -33,11 +34,23 @@ class CloudFileService : public InternetService {
protected:
virtual bool has_credentials() const = 0;
virtual void Connect() = 0;
virtual bool ShouldIndexFile(const QUrl& url, const QString& mime_type) const;
virtual void MaybeAddFileToDatabase(
const Song& metadata,
const QString& mime_type,
const QUrl& download_url,
const QString& authorisation);
virtual bool IsSupportedMimeType(const QString& mime_type) const;
protected slots:
void ShowCoverManager();
void AddToPlaylist(QMimeData* mime);
void ShowSettingsDialog();
void ReadTagsFinished(
TagReaderClient::ReplyType* reply,
const Song& metadata,
const int task_id);
protected:
QStandardItem* root_;
@ -50,6 +63,7 @@ class CloudFileService : public InternetService {
boost::scoped_ptr<QMenu> context_menu_;
boost::scoped_ptr<AlbumCoverManager> cover_manager_;
PlaylistManager* playlist_manager_;
TaskManager* task_manager_;
private:
QIcon icon_;

View File

@ -96,17 +96,6 @@ void DropboxService::RequestFileList() {
this, SLOT(RequestFileListFinished(QNetworkReply*)), reply);
}
namespace {
bool IsSupportedMimeType(const QString& mime_type) {
return mime_type == "audio/ogg" ||
mime_type == "audio/mpeg" ||
mime_type == "audio/mp4" ||
mime_type == "audio/flac";
}
} // namespace
void DropboxService::RequestFileListFinished(QNetworkReply* reply) {
reply->deleteLater();
@ -146,7 +135,13 @@ void DropboxService::RequestFileListFinished(QNetworkReply* reply) {
if (metadata["is_dir"].toBool()) {
continue;
}
MaybeAddFileToDatabase(url, metadata);
if (ShouldIndexFile(url, metadata["mime_type"].toString())) {
QNetworkReply* reply = FetchContentUrl(url);
NewClosure(reply, SIGNAL(finished()),
this, SLOT(FetchContentUrlFinished(QNetworkReply*, QVariantMap)),
reply, metadata);
}
}
if (response.contains("has_more") && response["has_more"].toBool()) {
@ -154,22 +149,6 @@ void DropboxService::RequestFileListFinished(QNetworkReply* reply) {
}
}
void DropboxService::MaybeAddFileToDatabase(
const QUrl& url, const QVariantMap& file) {
if (!IsSupportedMimeType(file["mime_type"].toString())) {
return;
}
Song song = library_backend_->GetSongByUrl(url);
if (song.is_valid()) {
return;
}
QNetworkReply* reply = FetchContentUrl(url);
NewClosure(reply, SIGNAL(finished()),
this, SLOT(FetchContentUrlFinished(QNetworkReply*, QVariantMap)),
reply, file);
}
QNetworkReply* DropboxService::FetchContentUrl(const QUrl& url) {
QUrl request_url(QString(kMediaEndpoint) + url.path());
QNetworkRequest request(request_url);
@ -183,44 +162,24 @@ void DropboxService::FetchContentUrlFinished(
QJson::Parser parser;
QVariantMap response = parser.parse(reply).toMap();
QFileInfo info(data["path"].toString());
TagReaderClient::ReplyType* tag_reply = app_->tag_reader_client()->ReadCloudFile(
QUrl::fromEncoded(response["url"].toByteArray()),
info.fileName(),
data["bytes"].toInt(),
data["mime_type"].toString(),
QString::null);
NewClosure(tag_reply, SIGNAL(Finished(bool)),
this, SLOT(ReadTagsFinished(TagReaderClient::ReplyType*,QVariantMap)),
tag_reply, data);
}
void DropboxService::ReadTagsFinished(
TagReaderClient::ReplyType* reply, const QVariantMap& file) {
qLog(Debug) << reply->message().DebugString().c_str();
const auto& message = reply->message().read_cloud_file_response();
if (!message.has_metadata() ||
!message.metadata().filesize()) {
qLog(Debug) << "Failed to tag:" << file["path"].toString();
return;
}
Song song;
song.InitFromProtobuf(message.metadata());
song.set_directory_id(0);
song.set_etag(file["rev"].toString());
song.set_mtime(ParseRFC822DateTime(file["modified"].toString()).toTime_t());
QUrl url;
url.setScheme("dropbox");
url.setPath(file["path"].toString());
song.set_url(url);
if (song.title().isEmpty()) {
QFileInfo info(file["path"].toString());
song.set_title(info.fileName());
}
url.setPath(data["path"].toString());
qLog(Debug) << "Adding song to db:" << song.title();
library_backend_->AddOrUpdateSongs(SongList() << song);
Song song;
song.set_url(url);
song.set_etag(data["rev"].toString());
song.set_mtime(ParseRFC822DateTime(data["modified"].toString()).toTime_t());
song.set_title(info.fileName());
song.set_filesize(data["bytes"].toInt());
song.set_ctime(0);
MaybeAddFileToDatabase(
song,
data["mime_type"].toString(),
QUrl::fromEncoded(response["url"].toByteArray()),
QString::null);
}
QUrl DropboxService::GetStreamingUrlFromSongId(const QUrl& url) {

View File

@ -31,15 +31,11 @@ class DropboxService : public CloudFileService {
private slots:
void RequestFileListFinished(QNetworkReply* reply);
void FetchContentUrlFinished(QNetworkReply* reply, const QVariantMap& file);
void ReadTagsFinished(
TagReaderClient::ReplyType* reply,
const QVariantMap& file);
private:
void RequestFileList();
QByteArray GenerateAuthorisationHeader();
QNetworkReply* FetchContentUrl(const QUrl& url);
void MaybeAddFileToDatabase(const QUrl& url, const QVariantMap& file);
private:
QString access_token_;

View File

@ -11,7 +11,6 @@
#include "core/database.h"
#include "core/mergedproxymodel.h"
#include "core/player.h"
#include "core/taskmanager.h"
#include "core/timeconstants.h"
#include "ui/albumcovermanager.h"
#include "globalsearch/globalsearch.h"
@ -41,8 +40,7 @@ GoogleDriveService::GoogleDriveService(Application* app, InternetModel* parent)
kServiceName, kServiceId,
QIcon(":/providers/googledrive.png"),
SettingsDialog::Page_GoogleDrive),
client_(new google_drive::Client(this)),
task_manager_(app->task_manager()) {
client_(new google_drive::Client(this)) {
app->player()->RegisterUrlHandler(new GoogleDriveUrlHandler(this, this));
}
@ -124,7 +122,34 @@ void GoogleDriveService::EnsureConnected() {
void GoogleDriveService::FilesFound(const QList<google_drive::File>& files) {
foreach (const google_drive::File& file, files) {
MaybeAddFileToDatabase(file);
if (!IsSupportedMimeType(file.mime_type())) {
continue;
}
QUrl url;
url.setScheme("googledrive");
url.setPath(file.id());
Song song;
// Add some extra tags from the Google Drive metadata.
song.set_etag(file.etag().remove('"'));
song.set_mtime(file.modified_date().toTime_t());
song.set_ctime(file.created_date().toTime_t());
song.set_comment(file.description());
song.set_directory_id(0);
song.set_url(QUrl(url));
song.set_filesize(file.size());
// Use the Google Drive title if we couldn't read tags from the file.
if (song.title().isEmpty()) {
song.set_title(file.title());
}
MaybeAddFileToDatabase(
song,
file.mime_type(),
file.download_url(),
QString("Bearer %1").arg(client_->access_token()));
}
}
@ -138,84 +163,6 @@ void GoogleDriveService::FilesDeleted(const QList<QUrl>& files) {
}
}
namespace {
bool IsSupportedMimeType(const QString& mime_type) {
return mime_type == "audio/mpeg" ||
mime_type == "application/ogg" ||
mime_type == "application/x-flac";
}
} // namespace
void GoogleDriveService::MaybeAddFileToDatabase(const google_drive::File& file) {
QString url = QString("googledrive:%1").arg(file.id());
Song song = library_backend_->GetSongByUrl(QUrl(url));
// Song already in index.
// TODO: Check etag and maybe update.
if (song.is_valid()) {
qLog(Debug) << "Already have:" << url;
return;
}
if (!IsSupportedMimeType(file.mime_type())) {
return;
}
const int task_id = task_manager_->StartTask(
tr("Indexing %1").arg(file.title()));
// Song not in index; tag and add.
QString authorisation_header = QString("Bearer %1").arg(client_->access_token());
TagReaderClient::ReplyType* reply = app_->tag_reader_client()->ReadCloudFile(
file.download_url(),
file.title(),
file.size(),
file.mime_type(),
authorisation_header);
NewClosure(reply, SIGNAL(Finished(bool)),
this, SLOT(ReadTagsFinished(TagReaderClient::ReplyType*,google_drive::File,QString,int)),
reply, file, url, task_id);
}
void GoogleDriveService::ReadTagsFinished(TagReaderClient::ReplyType* reply,
const google_drive::File& metadata,
const QString& url,
const int task_id) {
reply->deleteLater();
TaskManager::ScopedTask(task_id, task_manager_);
const pb::tagreader::ReadCloudFileResponse& msg =
reply->message().read_cloud_file_response();
if (!msg.has_metadata() || !msg.metadata().filesize()) {
qLog(Debug) << "Failed to tag:" << metadata.title();
return;
}
// Read the Song metadata from the message.
Song song;
song.InitFromProtobuf(msg.metadata());
// Add some extra tags from the Google Drive metadata.
song.set_etag(metadata.etag().remove('"'));
song.set_mtime(metadata.modified_date().toTime_t());
song.set_ctime(metadata.created_date().toTime_t());
song.set_comment(metadata.description());
song.set_directory_id(0);
song.set_url(QUrl(url));
// Use the Google Drive title if we couldn't read tags from the file.
if (song.title().isEmpty()) {
song.set_title(metadata.title());
}
// Add the song to the database
qLog(Debug) << "Adding song to db:" << song.title();
library_backend_->AddOrUpdateSongs(SongList() << song);
}
QUrl GoogleDriveService::GetStreamingUrlFromSongId(const QString& id) {
EnsureConnected();
QScopedPointer<google_drive::GetFileResponse> response(client_->GetFile(id));

View File

@ -41,25 +41,16 @@ class GoogleDriveService : public CloudFileService {
void FilesFound(const QList<google_drive::File>& files);
void FilesDeleted(const QList<QUrl>& files);
void ListChangesFinished(google_drive::ListChangesResponse* response);
void ReadTagsFinished(TagReaderClient::ReplyType* reply,
const google_drive::File& metadata,
const QString& url,
const int task_id);
void OpenWithDrive();
private:
void EnsureConnected();
void RefreshAuthorisation(const QString& refresh_token);
void MaybeAddFileToDatabase(const google_drive::File& file);
void ListChanges(const QString& cursor);
google_drive::Client* client_;
TaskManager* task_manager_;
int indexing_task_id_;
QAction* open_in_drive_action_;
};

View File

@ -128,22 +128,6 @@ void UbuntuOneService::RequestFileList(const QString& path) {
this, SLOT(FileListRequestFinished(QNetworkReply*)), files_reply);
}
void UbuntuOneService::FileListRequestFinished(QNetworkReply* reply) {
reply->deleteLater();
QJson::Parser parser;
QVariantMap result = parser.parse(reply).toMap();
QVariantList children = result["children"].toList();
foreach (const QVariant& c, children) {
QVariantMap child = c.toMap();
if (child["kind"].toString() == "file") {
MaybeAddFileToDatabase(child);
} else {
RequestFileList(child["resource_path"].toString());
}
}
}
namespace {
QString GuessMimeTypeForFile(const QString& filename) {
@ -161,69 +145,40 @@ QString GuessMimeTypeForFile(const QString& filename) {
} // namespace
void UbuntuOneService::MaybeAddFileToDatabase(const QVariantMap& file) {
const QString content_path = file["content_path"].toString();
const QString filename = file["path"].toString().mid(1);
const QString mime_type = GuessMimeTypeForFile(filename);
if (mime_type.isNull()) {
// Unknown file type.
// Potentially, we could do a HEAD request to see what Ubuntu One thinks
// the Content-Type is, probably not worth it though.
return;
void UbuntuOneService::FileListRequestFinished(QNetworkReply* reply) {
reply->deleteLater();
QJson::Parser parser;
QVariantMap result = parser.parse(reply).toMap();
QVariantList children = result["children"].toList();
foreach (const QVariant& c, children) {
QVariantMap child = c.toMap();
if (child["kind"].toString() == "file") {
QString content_path = child["content_path"].toString();
QUrl content_url(kContentRoot);
content_url.setPath(content_path);
QUrl service_url;
service_url.setScheme("ubuntuonefile");
service_url.setPath(content_path);
Song metadata;
metadata.set_url(service_url);
metadata.set_etag(child["hash"].toString());
metadata.set_mtime(QDateTime::fromString(
child["when_changed"].toString(), Qt::ISODate).toTime_t());
metadata.set_ctime(QDateTime::fromString(
child["when_created"].toString(), Qt::ISODate).toTime_t());
metadata.set_filesize(child["size"].toInt());
metadata.set_title(child["path"].toString().mid(1));
MaybeAddFileToDatabase(
metadata,
GuessMimeTypeForFile(child["path"].toString().mid(1)),
content_url,
GenerateAuthorisationHeader());
} else {
RequestFileList(child["resource_path"].toString());
}
}
QUrl service_url;
service_url.setScheme("ubuntuonefile");
service_url.setPath(content_path);
Song song = library_backend_->GetSongByUrl(service_url);
if (song.is_valid()) {
return;
}
QUrl content_url(kContentRoot);
content_url.setPath(content_path);
TagReaderClient::ReplyType* reply = app_->tag_reader_client()->ReadCloudFile(
content_url,
filename,
file["size"].toInt(),
mime_type,
GenerateAuthorisationHeader());
NewClosure(
reply, SIGNAL(Finished(bool)),
this, SLOT(ReadTagsFinished(TagReaderClient::ReplyType*,QVariantMap, QUrl)),
reply, file, service_url);
}
void UbuntuOneService::ReadTagsFinished(
TagReaderClient::ReplyType* reply, const QVariantMap& file, const QUrl& url) {
qLog(Debug) << reply->message().DebugString().c_str();
const auto& message = reply->message().read_cloud_file_response();
if (!message.has_metadata() ||
!message.metadata().filesize()) {
qLog(Debug) << "Failed to tag:" << url;
return;
}
Song song;
song.InitFromProtobuf(reply->message().read_cloud_file_response().metadata());
song.set_directory_id(0);
song.set_etag(file["hash"].toString());
song.set_mtime(
QDateTime::fromString(file["when_changed"].toString(), Qt::ISODate).toTime_t());
song.set_ctime(
QDateTime::fromString(file["when_created"].toString(), Qt::ISODate).toTime_t());
song.set_url(url);
if (song.title().isEmpty()) {
song.set_title(file["path"].toString().mid(1));
}
qLog(Debug) << "Adding song to db:" << song.title();
library_backend_->AddOrUpdateSongs(SongList() << song);
}
QUrl UbuntuOneService::GetStreamingUrlFromSongId(const QString& song_id) {

View File

@ -21,8 +21,6 @@ class UbuntuOneService : public CloudFileService {
private slots:
void AuthenticationFinished(UbuntuOneAuthenticator* authenticator);
void FileListRequestFinished(QNetworkReply* reply);
void ReadTagsFinished(
TagReaderClient::ReplyType* reply, const QVariantMap& file, const QUrl& url);
void ShowCoverManager();
void AddToPlaylist(QMimeData* mime);
void VolumeListRequestFinished(QNetworkReply* reply);
@ -32,7 +30,6 @@ class UbuntuOneService : public CloudFileService {
QNetworkReply* SendRequest(const QUrl& url);
void RequestVolumeList();
void RequestFileList(const QString& path);
void MaybeAddFileToDatabase(const QVariantMap& file);
bool has_credentials() const;
private: