1
0
mirror of https://github.com/clementine-player/Clementine synced 2024-12-18 04:19:55 +01:00

Index Google Drive MP3s and write to local database.

This commit is contained in:
John Maguire 2012-07-27 16:04:12 +02:00
parent 49326981c3
commit f48383c73e
8 changed files with 156 additions and 43 deletions

View File

@ -325,6 +325,7 @@
<file>schema/schema-35.sql</file>
<file>schema/schema-36.sql</file>
<file>schema/schema-37.sql</file>
<file>schema/schema-38.sql</file>
<file>schema/schema-3.sql</file>
<file>schema/schema-4.sql</file>
<file>schema/schema-5.sql</file>

50
data/schema/schema-38.sql Normal file
View File

@ -0,0 +1,50 @@
ALTER TABLE %allsongstables ADD COLUMN etag TEXT;
CREATE TABLE google_drive_songs(
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
composer TEXT,
track INTEGER,
disc INTEGER,
bpm REAL,
year INTEGER,
genre TEXT,
comment TEXT,
compilation INTEGER,
length INTEGER,
bitrate INTEGER,
samplerate INTEGER,
directory INTEGER NOT NULL,
filename TEXT NOT NULL,
mtime INTEGER NOT NULL,
ctime INTEGER NOT NULL,
filesize INTEGER NOT NULL,
sampler INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
filetype INTEGER NOT NULL DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER,
rating INTEGER,
forced_compilation_on INTEGER NOT NULL DEFAULT 0,
forced_compilation_off INTEGER NOT NULL DEFAULT 0,
effective_compilation NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
score INTEGER NOT NULL DEFAULT 0,
beginning INTEGER NOT NULL DEFAULT 0,
cue_path TEXT,
unavailable INTEGER DEFAULT 0,
effective_albumartist TEXT,
etag TEXT
);
CREATE VIRTUAL TABLE google_drive_songs_fts USING fts3 (
ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsgenre, ftscomment,
tokenize=unicode
);
UPDATE schema_version SET version=38;

View File

@ -37,7 +37,7 @@
#include <QVariant>
const char* Database::kDatabaseFilename = "clementine.db";
const int Database::kSchemaVersion = 37;
const int Database::kSchemaVersion = 38;
const char* Database::kMagicAllSongsTables = "%allsongstables";
int Database::sNextConnectionId = 1;

View File

@ -79,7 +79,7 @@ const QStringList Song::kColumns = QStringList()
<< "art_manual" << "filetype" << "playcount" << "lastplayed" << "rating"
<< "forced_compilation_on" << "forced_compilation_off"
<< "effective_compilation" << "skipcount" << "score" << "beginning" << "length"
<< "cue_path" << "unavailable" << "effective_albumartist";
<< "cue_path" << "unavailable" << "effective_albumartist" << "etag";
const QString Song::kColumnSpec = Song::kColumns.join(", ");
const QString Song::kBindSpec = Utilities::Prepend(":", Song::kColumns).join(", ");
@ -167,6 +167,8 @@ struct Song::Private : public QSharedData {
// Whether the song does not exist on the file system anymore, but is still
// stored in the database so as to remember the user's metadata.
bool unavailable_;
QString etag_;
};
@ -263,6 +265,7 @@ bool Song::is_stream() const { return d->filetype_ == Type_Stream; }
bool Song::is_cdda() const { return d->filetype_ == Type_Cdda; }
const QString& Song::art_automatic() const { return d->art_automatic_; }
const QString& Song::art_manual() const { return d->art_manual_; }
const QString& Song::etag() const { return d->etag_; }
bool Song::has_manually_unset_cover() const { return d->art_manual_ == kManuallyUnsetCover; }
void Song::manually_unset_cover() { d->art_manual_ = kManuallyUnsetCover; }
bool Song::has_embedded_cover() const { return d->art_automatic_ == kEmbeddedCover; }
@ -304,6 +307,7 @@ void Song::set_lastplayed(int v) { d->lastplayed_ = v; }
void Song::set_score(int v) { d->score_ = qBound(0, v, 100); }
void Song::set_cue_path(const QString& v) { d->cue_path_ = v; }
void Song::set_unavailable(bool v) { d->unavailable_ = v; }
void Song::set_etag(const QString& etag) { d->etag_ = etag; }
void Song::set_url(const QUrl& v) { d->url_ = v; }
void Song::set_basefilename(const QString& v) { d->basefilename_ = v; }
void Song::set_directory_id(int v) { d->directory_id_ = v; }
@ -1002,8 +1006,11 @@ void Song::BindToQuery(QSqlQuery *query) const {
query->bindValue(":unavailable", d->unavailable_ ? 1 : 0);
query->bindValue(":effective_albumartist", this->effective_albumartist());
query->bindValue(":etag", strval(d->etag_));
#undef intval
#undef notnullintval
#undef strval
}
void Song::BindToFtsQuery(QSqlQuery *query) const {

View File

@ -187,6 +187,8 @@ class Song {
const QString& art_automatic() const;
const QString& art_manual() const;
const QString& etag() const;
// Returns true if this Song had it's cover manually unset by user.
bool has_manually_unset_cover() const;
// This method represents an explicit request to unset this song's
@ -250,6 +252,7 @@ class Song {
void set_score(int v);
void set_cue_path(const QString& v);
void set_unavailable(bool v);
void set_etag(const QString& etag);
// Setters that should only be used by tests
void set_url(const QUrl& v);

View File

@ -1,6 +1,7 @@
#include "googledriveservice.h"
#include <QEventLoop>
#include <QSortFilterProxyModel>
#include <qjson/parser.h>
@ -13,7 +14,14 @@ using TagLib::ByteVector;
#include "core/application.h"
#include "core/closure.h"
#include "core/database.h"
#include "core/mergedproxymodel.h"
#include "core/player.h"
#include "core/timeconstants.h"
#include "globalsearch/globalsearch.h"
#include "globalsearch/librarysearchprovider.h"
#include "library/librarybackend.h"
#include "library/librarymodel.h"
#include "googledriveurlhandler.h"
#include "internetmodel.h"
#include "oauthenticator.h"
@ -24,6 +32,9 @@ static const char* kGoogleDriveFiles = "https://www.googleapis.com/drive/v2/file
static const char* kGoogleDriveFile = "https://www.googleapis.com/drive/v2/files/%1";
static const char* kSettingsGroup = "GoogleDrive";
static const char* kSongsTable = "google_drive_songs";
static const char* kFtsTable = "google_drive_songs_fts";
}
@ -42,14 +53,9 @@ class DriveStream : public TagLib::IOStream {
cursor_(0),
network_(network),
cache_(length) {
qLog(Debug) << Q_FUNC_INFO
<< url_
<< filename_
<< length_;
}
virtual TagLib::FileName name() const {
qLog(Debug) << Q_FUNC_INFO;
return encoded_filename_.data();
}
@ -78,7 +84,6 @@ class DriveStream : public TagLib::IOStream {
}
virtual TagLib::ByteVector readBlock(ulong length) {
qLog(Debug) << Q_FUNC_INFO;
const uint start = cursor_;
const uint end = qMin(cursor_ + length - 1, length_ - 1);
@ -87,7 +92,6 @@ class DriveStream : public TagLib::IOStream {
}
if (CheckCache(start, end)) {
qLog(Debug) << "Cache hit at:" << start << end;
TagLib::ByteVector cached = GetCached(start, end);
cursor_ += cached.size();
return cached;
@ -132,12 +136,10 @@ class DriveStream : public TagLib::IOStream {
}
virtual bool isOpen() const {
qLog(Debug) << Q_FUNC_INFO;
return true;
}
virtual void seek(long offset, TagLib::IOStream::Position p) {
qLog(Debug) << Q_FUNC_INFO;
switch (p) {
case TagLib::IOStream::Beginning:
cursor_ = offset;
@ -154,17 +156,14 @@ class DriveStream : public TagLib::IOStream {
}
virtual void clear() {
qLog(Debug) << Q_FUNC_INFO;
cursor_ = 0;
}
virtual long tell() const {
qLog(Debug) << Q_FUNC_INFO;
return cursor_;
}
virtual long length() {
qLog(Debug) << Q_FUNC_INFO;
return length_;
}
@ -189,11 +188,29 @@ class DriveStream : public TagLib::IOStream {
GoogleDriveService::GoogleDriveService(Application* app, InternetModel* parent)
: InternetService("Google Drive", app, parent, parent),
root_(NULL),
oauth_(new OAuthenticator(this)) {
oauth_(new OAuthenticator(this)),
library_sort_model_(new QSortFilterProxyModel(this)) {
connect(oauth_, SIGNAL(AccessTokenAvailable(QString)), SLOT(AccessTokenAvailable(QString)));
connect(oauth_, SIGNAL(RefreshTokenAvailable(QString)), SLOT(RefreshTokenAvailable(QString)));
library_backend_ = new LibraryBackend;
library_backend_->moveToThread(app_->database()->thread());
library_backend_->Init(app_->database(), kSongsTable,
QString::null, QString::null, kFtsTable);
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);
library_sort_model_->sort(0);
app->player()->RegisterUrlHandler(new GoogleDriveUrlHandler(this, this));
app_->global_search()->AddProvider(new LibrarySearchProvider(
library_backend_,
tr("Google Drive"),
"google_drive",
QIcon(":/providers/googledrive.png"),
true, app_, this));
}
QStandardItem* GoogleDriveService::CreateRootItem() {
@ -206,7 +223,10 @@ void GoogleDriveService::LazyPopulate(QStandardItem* item) {
switch (item->data(InternetModel::Role_Type).toInt()) {
case InternetModel::Type_Service:
Connect();
library_model_->Init();
model()->merged_model()->AddSubModel(item->index(), library_sort_model_);
break;
default:
break;
}
@ -260,35 +280,59 @@ void GoogleDriveService::ListFilesFinished(QNetworkReply* reply) {
QVariantList items = result["items"].toList();
foreach (const QVariant& v, items) {
QVariantMap file = v.toMap();
qLog(Debug) << "Creating stream";
DriveStream* stream = new DriveStream(
file["downloadUrl"].toUrl(),
file["title"].toString(),
file["fileSize"].toUInt(),
access_token_,
&network_);
qLog(Debug) << "Creating tag";
TagLib::MPEG::File tag(
stream, // Takes ownership.
TagLib::ID3v2::FrameFactory::instance(),
TagLib::AudioProperties::Fast);
qLog(Debug) << "Tagging done";
if (tag.tag()) {
qLog(Debug) << tag.tag()->artist().toCString();
Song song;
song.set_title(tag.tag()->title().toCString(true));
song.set_artist(tag.tag()->artist().toCString(true));
song.set_album(tag.tag()->album().toCString(true));
MaybeAddFileToDatabase(file);
}
}
QString url = QString("googledrive:%1").arg(file["id"].toString());
song.set_url(url);
qLog(Debug) << "Set url to:" << url;
void GoogleDriveService::MaybeAddFileToDatabase(const QVariantMap& file) {
QString url = QString("googledrive:%1").arg(file["id"].toString());
Song song = library_backend_->GetSongByUrl(QUrl(url));
// Song already in index.
// TODO: Check etag and maybe update.
if (song.is_valid()) {
return;
}
song.set_filesize(file["fileSize"].toInt());
root_->appendRow(CreateSongItem(song));
} else {
qLog(Debug) << "Tagging failed";
// Song not in index; tag and add.
DriveStream* stream = new DriveStream(
file["downloadUrl"].toUrl(),
file["title"].toString(),
file["fileSize"].toUInt(),
access_token_,
&network_);
TagLib::MPEG::File tag(
stream, // Takes ownership.
TagLib::ID3v2::FrameFactory::instance(),
TagLib::AudioProperties::Fast);
if (tag.tag()) {
Song song;
song.set_title(tag.tag()->title().toCString(true));
song.set_artist(tag.tag()->artist().toCString(true));
song.set_album(tag.tag()->album().toCString(true));
song.set_url(url);
song.set_filesize(file["fileSize"].toInt());
song.set_etag(file["etag"].toString().remove('"'));
QString modified_date = file["modifiedDate"].toString();
QString created_date = file["createdDate"].toString();
song.set_mtime(QDateTime::fromString(modified_date, Qt::ISODate).toTime_t());
song.set_ctime(QDateTime::fromString(created_date, Qt::ISODate).toTime_t());
song.set_filetype(Song::Type_Stream);
song.set_directory_id(0);
if (tag.audioProperties()) {
song.set_length_nanosec(tag.audioProperties()->length() * kNsecPerSec);
}
SongList songs;
songs << song;
qLog(Debug) << "Adding song to db:" << song.title();
library_backend_->AddOrUpdateSongs(songs);
} else {
qLog(Debug) << "Failed to tag:" << url;
}
}

View File

@ -7,7 +7,10 @@
class QStandardItem;
class LibraryBackend;
class LibraryModel;
class OAuthenticator;
class QSortFilterProxyModel;
class GoogleDriveService : public InternetService {
Q_OBJECT
@ -27,6 +30,7 @@ class GoogleDriveService : public InternetService {
private:
void Connect();
void RefreshAuthorisation(const QString& refresh_token);
void MaybeAddFileToDatabase(const QVariantMap& file);
QStandardItem* root_;
OAuthenticator* oauth_;
@ -34,6 +38,12 @@ class GoogleDriveService : public InternetService {
QString access_token_;
NetworkAccessManager network_;
LibraryBackend* library_backend_;
LibraryModel* library_model_;
QSortFilterProxyModel* library_sort_model_;
int indexing_task_id_;
};
#endif

View File

@ -463,8 +463,6 @@ QPixmap SongSourceDelegate::LookupPixmap(const QUrl& url, const QSize& size) con
icon = IconLoader::Load("folder-sound");
} else if (url.host() == "api.jamendo.com") {
icon = QIcon(":/providers/jamendo.png");
} else if (url.host().endsWith("googleusercontent.com")) {
icon = QIcon(":/providers/googledrive.png");
}
}
pixmap = icon.pixmap(size.height());