2012-07-12 14:09:20 +02:00
|
|
|
#include "googledriveservice.h"
|
|
|
|
|
2012-08-24 20:33:05 +02:00
|
|
|
#include <QDesktopServices>
|
2012-07-25 17:57:50 +02:00
|
|
|
#include <QEventLoop>
|
2012-08-24 20:33:05 +02:00
|
|
|
#include <QMenu>
|
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: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-08-06 14:00:54 +02:00
|
|
|
#include "core/taskmanager.h"
|
2012-07-27 16:04:12 +02:00
|
|
|
#include "core/timeconstants.h"
|
2012-10-01 16:39:58 +02:00
|
|
|
#include "ui/albumcovermanager.h"
|
2012-07-27 16:04:12 +02:00
|
|
|
#include "globalsearch/globalsearch.h"
|
|
|
|
#include "globalsearch/librarysearchprovider.h"
|
|
|
|
#include "library/librarybackend.h"
|
|
|
|
#include "library/librarymodel.h"
|
2012-10-01 16:39:58 +02:00
|
|
|
#include "playlist/playlist.h"
|
2012-08-24 20:33:05 +02:00
|
|
|
#include "ui/iconloader.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"
|
|
|
|
|
2012-07-29 16:06:23 +02:00
|
|
|
const char* GoogleDriveService::kServiceName = "Google Drive";
|
|
|
|
const char* GoogleDriveService::kSettingsGroup = "GoogleDrive";
|
2012-07-12 14:09:20 +02:00
|
|
|
|
2012-07-29 16:06:23 +02:00
|
|
|
namespace {
|
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-08-24 20:33:05 +02:00
|
|
|
static const char* kDriveEditFileUrl = "https://docs.google.com/file/d/%1/edit";
|
2012-07-27 16:04:12 +02:00
|
|
|
|
2012-07-12 14:09:20 +02:00
|
|
|
}
|
|
|
|
|
2012-07-25 17:57:50 +02:00
|
|
|
|
2012-07-12 14:09:20 +02:00
|
|
|
GoogleDriveService::GoogleDriveService(Application* app, InternetModel* parent)
|
2012-07-29 16:06:23 +02:00
|
|
|
: InternetService(kServiceName, app, parent, parent),
|
2012-07-12 14:09:20 +02:00
|
|
|
root_(NULL),
|
2012-07-28 18:18:03 +02:00
|
|
|
client_(new google_drive::Client(this)),
|
2012-08-06 14:00:54 +02:00
|
|
|
task_manager_(app->task_manager()),
|
2012-10-01 16:39:58 +02:00
|
|
|
library_sort_model_(new QSortFilterProxyModel(this)),
|
|
|
|
playlist_manager_(app->playlist_manager()) {
|
2012-07-27 16:04:12 +02:00
|
|
|
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-28 20:35:12 +02:00
|
|
|
app->global_search()->AddProvider(new LibrarySearchProvider(
|
2012-07-27 16:04:12 +02:00
|
|
|
library_backend_,
|
|
|
|
tr("Google Drive"),
|
|
|
|
"google_drive",
|
|
|
|
QIcon(":/providers/googledrive.png"),
|
|
|
|
true, app_, this));
|
2012-07-12 14:09:20 +02:00
|
|
|
}
|
|
|
|
|
2012-08-24 20:33:05 +02:00
|
|
|
GoogleDriveService::~GoogleDriveService() {
|
|
|
|
}
|
|
|
|
|
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:
|
2012-08-24 22:26:10 +02:00
|
|
|
if (refresh_token().isEmpty()) {
|
|
|
|
ShowSettingsDialog();
|
|
|
|
} else if (!client_->is_authenticated()) {
|
2012-08-10 20:46:26 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-24 22:26:10 +02:00
|
|
|
QString GoogleDriveService::refresh_token() const {
|
2012-07-26 16:55:59 +02:00
|
|
|
QSettings s;
|
|
|
|
s.beginGroup(kSettingsGroup);
|
|
|
|
|
2012-08-24 22:26:10 +02:00
|
|
|
return s.value("refresh_token").toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
void GoogleDriveService::Connect() {
|
|
|
|
google_drive::ConnectResponse* response = client_->Connect(refresh_token());
|
2012-07-28 18:18:03 +02:00
|
|
|
NewClosure(response, SIGNAL(Finished()),
|
|
|
|
this, SLOT(ConnectFinished(google_drive::ConnectResponse*)),
|
|
|
|
response);
|
2012-07-12 14:09:20 +02:00
|
|
|
}
|
|
|
|
|
2012-08-17 22:48:45 +02:00
|
|
|
void GoogleDriveService::ForgetCredentials() {
|
|
|
|
client_->ForgetCredentials();
|
|
|
|
|
|
|
|
QSettings s;
|
|
|
|
s.beginGroup(kSettingsGroup);
|
|
|
|
|
|
|
|
s.remove("refresh_token");
|
|
|
|
s.remove("user_email");
|
|
|
|
}
|
|
|
|
|
2012-07-31 11:57:04 +02:00
|
|
|
void GoogleDriveService::ListFilesForMimeType(const QString& mime_type) {
|
|
|
|
google_drive::ListFilesResponse* list_response = client_->ListFiles(
|
|
|
|
QString("mimeType = '%1' and trashed = false").arg(mime_type));
|
|
|
|
connect(list_response, SIGNAL(FilesFound(QList<google_drive::File>)),
|
|
|
|
this, SLOT(FilesFound(QList<google_drive::File>)));
|
|
|
|
NewClosure(list_response, SIGNAL(Finished()),
|
|
|
|
this, SLOT(ListFilesFinished(google_drive::ListFilesResponse*)),
|
|
|
|
list_response);
|
|
|
|
}
|
|
|
|
|
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-08-24 22:26:10 +02:00
|
|
|
|
|
|
|
if (!response->user_email().isEmpty()) {
|
|
|
|
// We only fetch the user's email address the first time we authenticate.
|
|
|
|
s.setValue("user_email", response->user_email());
|
|
|
|
}
|
2012-08-17 22:48:45 +02:00
|
|
|
|
|
|
|
emit Connected();
|
2012-07-26 16:55:59 +02:00
|
|
|
|
2012-07-28 18:18:03 +02:00
|
|
|
// Find any music files
|
2012-07-31 17:57:17 +02:00
|
|
|
ListFilesForMimeType("audio/mpeg"); // MP3/AAC
|
2012-07-31 11:57:04 +02:00
|
|
|
ListFilesForMimeType("application/ogg"); // OGG
|
|
|
|
ListFilesForMimeType("application/x-flac"); // FLAC
|
2012-07-28 18:18:03 +02:00
|
|
|
}
|
2012-07-12 14:09:20 +02:00
|
|
|
|
2012-08-10 20:46:26 +02:00
|
|
|
void GoogleDriveService::EnsureConnected() {
|
|
|
|
if (client_->is_authenticated()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QEventLoop loop;
|
|
|
|
connect(client_, SIGNAL(Authenticated()), &loop, SLOT(quit()));
|
|
|
|
Connect();
|
|
|
|
loop.exec();
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-08-06 14:00:54 +02:00
|
|
|
const int task_id = task_manager_->StartTask(
|
|
|
|
tr("Indexing %1").arg(file.title()));
|
|
|
|
|
2012-07-27 16:04:12 +02:00
|
|
|
// Song not in index; tag and add.
|
2012-11-28 14:43:03 +01:00
|
|
|
QString authorisation_header = QString("Bearer %1").arg(client_->access_token());
|
2012-07-28 20:35:12 +02:00
|
|
|
TagReaderClient::ReplyType* reply = app_->tag_reader_client()->ReadGoogleDrive(
|
2012-07-28 18:18:03 +02:00
|
|
|
file.download_url(),
|
|
|
|
file.title(),
|
|
|
|
file.size(),
|
2012-07-31 11:57:04 +02:00
|
|
|
file.mime_type(),
|
2012-11-28 14:43:03 +01:00
|
|
|
authorisation_header);
|
2012-07-28 20:35:12 +02:00
|
|
|
|
|
|
|
NewClosure(reply, SIGNAL(Finished(bool)),
|
2012-08-06 14:00:54 +02:00
|
|
|
this, SLOT(ReadTagsFinished(TagReaderClient::ReplyType*,google_drive::File,QString,int)),
|
|
|
|
reply, file, url, task_id);
|
2012-07-28 20:35:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void GoogleDriveService::ReadTagsFinished(TagReaderClient::ReplyType* reply,
|
|
|
|
const google_drive::File& metadata,
|
2012-08-06 14:00:54 +02:00
|
|
|
const QString& url,
|
|
|
|
const int task_id) {
|
2012-07-28 20:35:12 +02:00
|
|
|
reply->deleteLater();
|
|
|
|
|
2012-08-06 14:00:54 +02:00
|
|
|
TaskManager::ScopedTask(task_id, task_manager_);
|
|
|
|
|
2012-07-28 20:35:12 +02:00
|
|
|
const pb::tagreader::ReadGoogleDriveResponse& msg =
|
|
|
|
reply->message().read_google_drive_response();
|
2012-07-31 11:57:04 +02:00
|
|
|
if (!msg.has_metadata() || !msg.metadata().filesize()) {
|
|
|
|
qLog(Debug) << "Failed to tag:" << metadata.title();
|
2012-07-28 20:35:12 +02:00
|
|
|
return;
|
2012-07-12 14:09:20 +02:00
|
|
|
}
|
2012-07-28 20:35:12 +02:00
|
|
|
|
|
|
|
// 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);
|
2012-09-26 17:09:13 +02:00
|
|
|
song.set_url(QUrl(url));
|
2012-07-28 20:35:12 +02:00
|
|
|
|
|
|
|
// 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);
|
2012-07-12 14:09:20 +02:00
|
|
|
}
|
2012-07-26 16:35:57 +02:00
|
|
|
|
|
|
|
QUrl GoogleDriveService::GetStreamingUrlFromSongId(const QString& id) {
|
2012-08-10 20:46:26 +02:00
|
|
|
EnsureConnected();
|
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());
|
2012-11-02 16:47:40 +01:00
|
|
|
url.addQueryItem("access_token", client_->access_token());
|
2012-07-28 18:18:03 +02:00
|
|
|
return url;
|
2012-07-26 16:35:57 +02:00
|
|
|
}
|
2012-08-24 20:33:05 +02:00
|
|
|
|
|
|
|
void GoogleDriveService::ShowContextMenu(const QPoint& global_pos) {
|
|
|
|
if (!context_menu_) {
|
|
|
|
context_menu_.reset(new QMenu);
|
|
|
|
context_menu_->addActions(GetPlaylistActions());
|
|
|
|
open_in_drive_action_ = context_menu_->addAction(
|
|
|
|
QIcon(":/providers/googledrive.png"), tr("Open in Google Drive"),
|
|
|
|
this, SLOT(OpenWithDrive()));
|
|
|
|
context_menu_->addSeparator();
|
2012-10-01 16:39:58 +02:00
|
|
|
context_menu_->addAction(
|
|
|
|
IconLoader::Load("download"),
|
|
|
|
tr("Cover Manager"),
|
|
|
|
this,
|
|
|
|
SLOT(ShowCoverManager()));
|
2012-08-24 20:33:05 +02:00
|
|
|
context_menu_->addAction(IconLoader::Load("configure"),
|
|
|
|
tr("Configure..."),
|
|
|
|
this, SLOT(ShowSettingsDialog()));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only show some actions if there are real songs selected
|
|
|
|
bool songs_selected = false;
|
|
|
|
foreach (const QModelIndex& index, model()->selected_indexes()) {
|
|
|
|
const int type = index.data(LibraryModel::Role_Type).toInt();
|
|
|
|
if (type == LibraryItem::Type_Song ||
|
|
|
|
type == LibraryItem::Type_Container) {
|
|
|
|
songs_selected = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
open_in_drive_action_->setEnabled(songs_selected);
|
|
|
|
|
|
|
|
context_menu_->popup(global_pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GoogleDriveService::OpenWithDrive() {
|
|
|
|
// Map indexes to the actual library model.
|
|
|
|
QModelIndexList library_indexes;
|
|
|
|
foreach (const QModelIndex& index, model()->selected_indexes()) {
|
|
|
|
if (index.model() == library_sort_model_) {
|
|
|
|
library_indexes << library_sort_model_->mapToSource(index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ask the library for the songs for these indexes.
|
|
|
|
foreach (const Song& song, library_model_->GetChildSongs(library_indexes)) {
|
|
|
|
QDesktopServices::openUrl(
|
|
|
|
QUrl(QString(kDriveEditFileUrl).arg(song.url().path())));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GoogleDriveService::ShowSettingsDialog() {
|
|
|
|
app_->OpenSettingsDialogAtPage(SettingsDialog::Page_GoogleDrive);
|
|
|
|
}
|
2012-10-01 16:39:58 +02:00
|
|
|
|
|
|
|
void GoogleDriveService::ShowCoverManager() {
|
|
|
|
if (!cover_manager_) {
|
|
|
|
cover_manager_.reset(new AlbumCoverManager(app_, library_backend_));
|
|
|
|
cover_manager_->Init();
|
|
|
|
connect(cover_manager_.get(), SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
|
|
|
}
|
|
|
|
|
|
|
|
cover_manager_->show();
|
|
|
|
}
|
|
|
|
|
|
|
|
void GoogleDriveService::AddToPlaylist(QMimeData* mime) {
|
|
|
|
playlist_manager_->current()->dropMimeData(
|
|
|
|
mime, Qt::CopyAction, -1, 0, QModelIndex());
|
|
|
|
}
|