2012-07-12 14:09:20 +02:00
|
|
|
#include "googledriveservice.h"
|
|
|
|
|
2012-07-25 17:57:50 +02:00
|
|
|
#include <QEventLoop>
|
2012-07-28 18:18:03 +02:00
|
|
|
#include <QScopedPointer>
|
2012-07-27 16:04:12 +02:00
|
|
|
#include <QSortFilterProxyModel>
|
2012-07-25 17:57:50 +02:00
|
|
|
|
2012-07-26 16:06:34 +02:00
|
|
|
#include <google/sparsetable>
|
|
|
|
|
2012-07-25 17:57:50 +02:00
|
|
|
#include <taglib/id3v2framefactory.h>
|
|
|
|
#include <taglib/mpegfile.h>
|
|
|
|
#include <taglib/tiostream.h>
|
|
|
|
using TagLib::ByteVector;
|
|
|
|
|
2012-07-26 16:35:57 +02:00
|
|
|
#include "core/application.h"
|
2012-07-12 14:09:20 +02:00
|
|
|
#include "core/closure.h"
|
2012-07-27 16:04:12 +02:00
|
|
|
#include "core/database.h"
|
|
|
|
#include "core/mergedproxymodel.h"
|
2012-07-26 16:35:57 +02:00
|
|
|
#include "core/player.h"
|
2012-07-27 16:04:12 +02:00
|
|
|
#include "core/timeconstants.h"
|
|
|
|
#include "globalsearch/globalsearch.h"
|
|
|
|
#include "globalsearch/librarysearchprovider.h"
|
|
|
|
#include "library/librarybackend.h"
|
|
|
|
#include "library/librarymodel.h"
|
2012-07-28 18:18:03 +02:00
|
|
|
#include "googledriveclient.h"
|
2012-07-26 16:35:57 +02:00
|
|
|
#include "googledriveurlhandler.h"
|
2012-07-12 14:09:20 +02:00
|
|
|
#include "internetmodel.h"
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
2012-07-26 16:55:59 +02:00
|
|
|
static const char* kSettingsGroup = "GoogleDrive";
|
2012-07-12 14:09:20 +02:00
|
|
|
|
2012-07-27 16:04:12 +02:00
|
|
|
static const char* kSongsTable = "google_drive_songs";
|
|
|
|
static const char* kFtsTable = "google_drive_songs_fts";
|
|
|
|
|
2012-07-12 14:09:20 +02:00
|
|
|
}
|
|
|
|
|
2012-07-25 17:57:50 +02:00
|
|
|
|
|
|
|
class DriveStream : public TagLib::IOStream {
|
|
|
|
public:
|
|
|
|
DriveStream(const QUrl& url,
|
|
|
|
const QString& filename,
|
|
|
|
const long length,
|
|
|
|
const QString& auth,
|
|
|
|
QNetworkAccessManager* network)
|
|
|
|
: url_(url),
|
|
|
|
filename_(filename),
|
|
|
|
encoded_filename_(filename_.toUtf8()),
|
|
|
|
length_(length),
|
|
|
|
auth_(auth),
|
|
|
|
cursor_(0),
|
2012-07-26 16:06:34 +02:00
|
|
|
network_(network),
|
|
|
|
cache_(length) {
|
2012-07-25 17:57:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual TagLib::FileName name() const {
|
|
|
|
return encoded_filename_.data();
|
|
|
|
}
|
|
|
|
|
2012-07-26 16:06:34 +02:00
|
|
|
bool CheckCache(int start, int end) {
|
|
|
|
for (int i = start; i <= end; ++i) {
|
|
|
|
if (!cache_.test(i)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FillCache(int start, TagLib::ByteVector data) {
|
|
|
|
for (int i = 0; i < data.size(); ++i) {
|
|
|
|
cache_.set(start + i, data[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TagLib::ByteVector GetCached(int start, int end) {
|
|
|
|
const uint size = end - start + 1;
|
|
|
|
TagLib::ByteVector ret(size);
|
|
|
|
for (int i = 0; i < size; ++i) {
|
|
|
|
ret[i] = cache_.get(start + i);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-07-25 17:57:50 +02:00
|
|
|
virtual TagLib::ByteVector readBlock(ulong length) {
|
2012-07-26 16:06:34 +02:00
|
|
|
const uint start = cursor_;
|
|
|
|
const uint end = qMin(cursor_ + length - 1, length_ - 1);
|
|
|
|
|
|
|
|
if (end <= start) {
|
|
|
|
return TagLib::ByteVector();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (CheckCache(start, end)) {
|
|
|
|
TagLib::ByteVector cached = GetCached(start, end);
|
|
|
|
cursor_ += cached.size();
|
|
|
|
return cached;
|
|
|
|
}
|
|
|
|
|
2012-07-25 17:57:50 +02:00
|
|
|
QNetworkRequest request = QNetworkRequest(url_);
|
|
|
|
request.setRawHeader(
|
|
|
|
"Authorization", QString("Bearer %1").arg(auth_).toUtf8());
|
|
|
|
request.setRawHeader(
|
2012-07-26 11:36:07 +02:00
|
|
|
"Range", QString("bytes=%1-%2").arg(start).arg(end).toUtf8());
|
2012-07-25 17:57:50 +02:00
|
|
|
|
|
|
|
QNetworkReply* reply = network_->get(request);
|
|
|
|
|
|
|
|
QEventLoop loop;
|
|
|
|
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
|
|
|
loop.exec();
|
|
|
|
reply->deleteLater();
|
|
|
|
|
|
|
|
QByteArray data = reply->readAll();
|
|
|
|
TagLib::ByteVector bytes(data.data(), data.size());
|
2012-07-26 11:36:07 +02:00
|
|
|
cursor_ += data.size();
|
2012-07-26 16:06:34 +02:00
|
|
|
|
|
|
|
FillCache(start, bytes);
|
2012-07-25 17:57:50 +02:00
|
|
|
return bytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void writeBlock(const ByteVector&) {
|
|
|
|
qLog(Debug) << Q_FUNC_INFO << "not implemented";
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void insert(const ByteVector&, ulong, ulong) {
|
|
|
|
qLog(Debug) << Q_FUNC_INFO << "not implemented";
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void removeBlock(ulong, ulong) {
|
|
|
|
qLog(Debug) << Q_FUNC_INFO << "not implemented";
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool readOnly() const {
|
|
|
|
qLog(Debug) << Q_FUNC_INFO;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool isOpen() const {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void seek(long offset, TagLib::IOStream::Position p) {
|
|
|
|
switch (p) {
|
|
|
|
case TagLib::IOStream::Beginning:
|
|
|
|
cursor_ = offset;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TagLib::IOStream::Current:
|
2012-07-26 16:06:34 +02:00
|
|
|
cursor_ = qMin(ulong(cursor_ + offset), length_);
|
2012-07-25 17:57:50 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case TagLib::IOStream::End:
|
2012-07-26 16:06:34 +02:00
|
|
|
cursor_ = qMax(0UL, length_ - offset);
|
2012-07-25 17:57:50 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void clear() {
|
|
|
|
cursor_ = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual long tell() const {
|
|
|
|
return cursor_;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual long length() {
|
|
|
|
return length_;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void truncate(long) {
|
|
|
|
qLog(Debug) << Q_FUNC_INFO << "not implemented";
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
const QUrl url_;
|
|
|
|
const QString filename_;
|
|
|
|
const QByteArray encoded_filename_;
|
2012-07-26 16:06:34 +02:00
|
|
|
const ulong length_;
|
2012-07-25 17:57:50 +02:00
|
|
|
const QString auth_;
|
|
|
|
|
|
|
|
int cursor_;
|
|
|
|
QNetworkAccessManager* network_;
|
2012-07-26 16:06:34 +02:00
|
|
|
|
|
|
|
google::sparsetable<char> cache_;
|
2012-07-25 17:57:50 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2012-07-12 14:09:20 +02:00
|
|
|
GoogleDriveService::GoogleDriveService(Application* app, InternetModel* parent)
|
|
|
|
: InternetService("Google Drive", app, parent, parent),
|
|
|
|
root_(NULL),
|
2012-07-28 18:18:03 +02:00
|
|
|
client_(new google_drive::Client(this)),
|
2012-07-27 16:04:12 +02:00
|
|
|
library_sort_model_(new QSortFilterProxyModel(this)) {
|
|
|
|
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);
|
|
|
|
|
2012-07-26 16:35:57 +02:00
|
|
|
app->player()->RegisterUrlHandler(new GoogleDriveUrlHandler(this, this));
|
2012-07-27 16:04:12 +02:00
|
|
|
app_->global_search()->AddProvider(new LibrarySearchProvider(
|
|
|
|
library_backend_,
|
|
|
|
tr("Google Drive"),
|
|
|
|
"google_drive",
|
|
|
|
QIcon(":/providers/googledrive.png"),
|
|
|
|
true, app_, this));
|
2012-07-12 14:09:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QStandardItem* GoogleDriveService::CreateRootItem() {
|
|
|
|
root_ = new QStandardItem(QIcon(":providers/googledrive.png"), "Google Drive");
|
|
|
|
root_->setData(true, InternetModel::Role_CanLazyLoad);
|
|
|
|
return root_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GoogleDriveService::LazyPopulate(QStandardItem* item) {
|
|
|
|
switch (item->data(InternetModel::Role_Type).toInt()) {
|
|
|
|
case InternetModel::Type_Service:
|
|
|
|
Connect();
|
2012-07-27 16:04:12 +02:00
|
|
|
library_model_->Init();
|
|
|
|
model()->merged_model()->AddSubModel(item->index(), library_sort_model_);
|
2012-07-12 14:09:20 +02:00
|
|
|
break;
|
2012-07-27 16:04:12 +02:00
|
|
|
|
2012-07-12 14:09:20 +02:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GoogleDriveService::Connect() {
|
2012-07-26 16:55:59 +02:00
|
|
|
QSettings s;
|
|
|
|
s.beginGroup(kSettingsGroup);
|
|
|
|
|
2012-07-28 18:18:03 +02:00
|
|
|
google_drive::ConnectResponse* response =
|
|
|
|
client_->Connect(s.value("refresh_token").toString());
|
|
|
|
NewClosure(response, SIGNAL(Finished()),
|
|
|
|
this, SLOT(ConnectFinished(google_drive::ConnectResponse*)),
|
|
|
|
response);
|
2012-07-12 14:09:20 +02:00
|
|
|
}
|
|
|
|
|
2012-07-28 18:18:03 +02:00
|
|
|
void GoogleDriveService::ConnectFinished(google_drive::ConnectResponse* response) {
|
|
|
|
response->deleteLater();
|
2012-07-12 14:09:20 +02:00
|
|
|
|
2012-07-28 18:18:03 +02:00
|
|
|
// Save the refresh token
|
2012-07-26 16:55:59 +02:00
|
|
|
QSettings s;
|
|
|
|
s.beginGroup(kSettingsGroup);
|
2012-07-28 18:18:03 +02:00
|
|
|
s.setValue("refresh_token", response->refresh_token());
|
2012-07-26 16:55:59 +02:00
|
|
|
|
2012-07-28 18:18:03 +02:00
|
|
|
// Find any music files
|
|
|
|
google_drive::ListFilesResponse* list_response =
|
|
|
|
client_->ListFiles("mimeType = 'audio/mpeg'");
|
|
|
|
connect(list_response, SIGNAL(FilesFound(QList<google_drive::File>)),
|
|
|
|
this, SLOT(FilesFound(QList<google_drive::File>)));
|
2012-07-12 14:09:20 +02:00
|
|
|
|
2012-07-28 18:18:03 +02:00
|
|
|
NewClosure(list_response, SIGNAL(Finished()),
|
|
|
|
this, SLOT(ListFilesFinished(google_drive::ListFilesResponse*)));
|
|
|
|
}
|
2012-07-12 14:09:20 +02:00
|
|
|
|
2012-07-28 18:18:03 +02:00
|
|
|
void GoogleDriveService::FilesFound(const QList<google_drive::File>& files) {
|
|
|
|
foreach (const google_drive::File& file, files) {
|
2012-07-27 16:04:12 +02:00
|
|
|
MaybeAddFileToDatabase(file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-28 18:18:03 +02:00
|
|
|
void GoogleDriveService::ListFilesFinished(google_drive::ListFilesResponse* response) {
|
|
|
|
response->deleteLater();
|
|
|
|
}
|
|
|
|
|
|
|
|
void GoogleDriveService::MaybeAddFileToDatabase(const google_drive::File& file) {
|
|
|
|
QString url = QString("googledrive:%1").arg(file.id());
|
2012-07-27 16:04:12 +02:00
|
|
|
Song song = library_backend_->GetSongByUrl(QUrl(url));
|
|
|
|
// Song already in index.
|
|
|
|
// TODO: Check etag and maybe update.
|
|
|
|
if (song.is_valid()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Song not in index; tag and add.
|
|
|
|
DriveStream* stream = new DriveStream(
|
2012-07-28 18:18:03 +02:00
|
|
|
file.download_url(),
|
|
|
|
file.title(),
|
|
|
|
file.size(),
|
|
|
|
client_->access_token(),
|
2012-07-27 16:04:12 +02:00
|
|
|
&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);
|
2012-07-28 18:18:03 +02:00
|
|
|
song.set_filesize(file.size());
|
|
|
|
song.set_etag(file.etag().remove('"'));
|
2012-07-27 16:04:12 +02:00
|
|
|
|
2012-07-28 18:18:03 +02:00
|
|
|
song.set_mtime(file.modified_date().toTime_t());
|
|
|
|
song.set_ctime(file.created_date().toTime_t());
|
2012-07-27 16:04:12 +02:00
|
|
|
|
|
|
|
song.set_filetype(Song::Type_Stream);
|
|
|
|
song.set_directory_id(0);
|
|
|
|
|
|
|
|
if (tag.audioProperties()) {
|
|
|
|
song.set_length_nanosec(tag.audioProperties()->length() * kNsecPerSec);
|
2012-07-26 11:36:07 +02:00
|
|
|
}
|
2012-07-27 16:04:12 +02:00
|
|
|
|
|
|
|
SongList songs;
|
|
|
|
songs << song;
|
|
|
|
qLog(Debug) << "Adding song to db:" << song.title();
|
|
|
|
library_backend_->AddOrUpdateSongs(songs);
|
|
|
|
} else {
|
|
|
|
qLog(Debug) << "Failed to tag:" << url;
|
2012-07-12 14:09:20 +02:00
|
|
|
}
|
|
|
|
}
|
2012-07-26 16:35:57 +02:00
|
|
|
|
|
|
|
QUrl GoogleDriveService::GetStreamingUrlFromSongId(const QString& id) {
|
2012-07-28 18:18:03 +02:00
|
|
|
QScopedPointer<google_drive::GetFileResponse> response(client_->GetFile(id));
|
|
|
|
|
2012-07-26 16:35:57 +02:00
|
|
|
QEventLoop loop;
|
2012-07-28 18:18:03 +02:00
|
|
|
connect(response.data(), SIGNAL(Finished()), &loop, SLOT(quit()));
|
2012-07-26 16:35:57 +02:00
|
|
|
loop.exec();
|
|
|
|
|
2012-07-28 18:18:03 +02:00
|
|
|
QUrl url(response->file().download_url());
|
|
|
|
url.setFragment(client_->access_token());
|
|
|
|
return url;
|
2012-07-26 16:35:57 +02:00
|
|
|
}
|