diff --git a/desktop/CMakeLists.txt b/desktop/CMakeLists.txt index f328734..da56001 100644 --- a/desktop/CMakeLists.txt +++ b/desktop/CMakeLists.txt @@ -1,4 +1,4 @@ install(FILES gdrive-network.desktop DESTINATION ${KDE_INSTALL_DATADIR}/remoteview) -install(FILES org.kde.kio_gdrive.appdata.xml +install(FILES org.kde.kio_gdrive.metainfo.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) diff --git a/desktop/org.kde.kio_gdrive.appdata.xml b/desktop/org.kde.kio_gdrive.metainfo.xml similarity index 98% rename from desktop/org.kde.kio_gdrive.appdata.xml rename to desktop/org.kde.kio_gdrive.metainfo.xml index d16489c..c9ea539 100644 --- a/desktop/org.kde.kio_gdrive.appdata.xml +++ b/desktop/org.kde.kio_gdrive.metainfo.xml @@ -1,128 +1,129 @@ org.kde.kio_gdrive CC0-1.0 GPL-2.0+ org.kde.dolphin.desktop org.kde.konqueror.desktop org.kde.krusader.desktop org.kde.gwenview.desktop KIO GDrive KIO GDrive KIO GDrive KIO GDrive KIO GDrive KIO GDrive KIO GDrive KIO GDrive KIO GDrive KIO GDrive KIO GDrive KIO GDrive KIO GDrive KIO GDrive KIO GDrive KIO-GDrive KIO GDrive KIO GDrive KIO GDrive KIO GDrive KIO для поддержки Google Диска KIO GDrive KIO Gdrive KIO GDrive KIO GDrive xxKIO GDrivexx KIO GDrive KIO GDrive Google Drive integration with KDE Plasma and KDE applications Integració del Drive de Google amb el Plasma del KDE i les aplicacions KDE Integració del Drive de Google amb el Plasma del KDE i les aplicacions KDE Integrace Google Drive do KDE Plasma a aplikací KDE Google Drive-integration med KDE Plasma og KDE-programmer Integration von Google Drive mit Plasma und den KDE-Anwendungen Ενσωμάτωση του Google Drive με το KDE Plasma και τις εφαρμογές του KDE Google Drive integration with KDE Plasma and KDE applications Integración de Google Drive con Plasma de KDE y aplicaciones de KDE Google Drive -integrointi KDE Plasmaan ja KDE-sovelluksiin Intégration de Google Drive à KDE Plasma et aux applications KDE Integración de Google Drive con KDE Plasma e os aplicativos de KDE Google Drive terintegrasi dengan Plasma KDE dan aplikasi KDE Integrazione di Google Drive con le applicazioni di KDE e KDE Plasma KDE Plasma 및 KDE 프로그램과 Google 드라이브 통합 Integratie van Google Drive met KDE Plasma en KDE applications Integrering av Google Drive, KDE Plasma og KDE-programma Integracja Dysku Google z KDE Plazmą i aplikacjami KDE Integração do Google Drive com o Plasma do KDE e as aplicações do KDE Integração do Google Drive com o KDE Plasma e aplicativos do KDE Интеграция Google Диска с KDE Plasma и приложениями от KDE Integrácia Google Drive integration s KDE Plasma a KDE aplikáciami Integrering av Google Drive med KDE Plasma och KDE-program KDE Plasma ve KDE uygulamaları ile Google Drive tümleştirmesi Інтеграція Диска Google із Плазмою KDE та програмами KDE xxGoogle Drive integration with KDE Plasma and KDE applicationsxx KDE Plasma 和 KDE 应用的 Google 云端硬盘整合 適用於 KDE Plasma 和 KDE 應用程式的 Google Drive 整合

KIO GDrive is a KIO slave that enables KIO-aware applications (such as Dolphin, Kate or Gwenview) to access and edit Google Drive files on the cloud.

El KIO GDrive és un esclau del KIO que habilita les aplicacions preparades per al KIO (com el Dolphin, el Kate o el Gwenview) per accedir i editar fitxers del Drive de Google en el núvol.

El KIO GDrive és un esclau del KIO que habilita les aplicacions preparades per al KIO (com el Dolphin, el Kate o el Gwenview) per accedir i editar fitxers del Drive de Google en el núvol.

KIO GDrive er en KIO-slave der gør det muligt for KIO-bevidste programmer (såsom Dolphin, Kate eller Gwenview) at tilgå og redigere Google Drive-filer i skyen.

KIO GDrive ist ein Ein-/Ausgabemodul, mit dem Anwendungen wie Dolphin, Kate oder Gwenview auf Dateien in der Cloud Google Drive zugreifen und bearbeiten können.

Το KIO GDrive είναι ένας KIO slave που ενεργοποιεί εφαρμογές με επίγνωση του KIO (όπως Dolphin, Kate ή Gwenview) για την πρόσβαση και επεξεργασία αρχείων του Google Drive στο νέφος.

KIO GDrive is a KIO slave that enables KIO-aware applications (such as Dolphin, Kate or Gwenview) to access and edit Google Drive files on the cloud.

KIO GDrive es un esclavo de KIO que activa aplicaciones KIO-compatible (tales como, Dolphin, Kate o Gwenview) para acceder y editar archivos de Google Drive en la nube.

KIO GDrive on KIO-asiakas, joka sallii KIO-sovellusten (kuten Dolphinin, Katen tai Gwenview’n) päästä käsiksi Google Driven tiedostoihin pilvessä.

KIO GDrive est un module d'entrée / sortie qui permet aux applications prenant en charge KIO (comme Dolphin, Kate ou Gwenview) d'accéder aux fichiers stockés en ligne sur Google Drive et de les modifier.

KIO GDrive é un escravo de KIO que permite aos aplicativos que usan KIO (como Dolphin, Kate ou Gwenview) acceder e editar ficheiros de Google Drive na nube.

KIO GDrive adalah sebuah KIO slave yang memungkinkan aplikasi KIO-aware (sepertihalnya Dolphin, Kate or Gwenview) untuk mengakses dan mengedit file-file Google Drive di cloud.

KIO GDrive è un KIO slave che consente alle applicazioni che gestiscono KIO (come Dolphin, Kate o Gwenview) di accedere e modificare i file di Google Drive sul cloud.

KIO GDrive는 KIO를 사용할 수 있는 프로그램(Dolphin, Kate, Gwenview 등)에서 Google 드라이브에 저장된 파일을 보고 편집할 수 있는 KIO 슬레이브입니다.

KIO GDrive is een KIO-slave die met KIO bekende toepassingen (zoals Dolphin, Kate of Gwenview) toegang geeft tot bestanden en deze bewerkt op Google Drive in de cloud.

KIO GDrive er ein KIO-slave som lèt program som brukar KIO (for eksempel Dolphin, Kate og Gwenview) få lese- og skrivetilgang til filene dine på Google Drive.

KIO GDrive jest KIO slave, który umożliwia aplikacjom świadomym KIO (takim jak Dolphin, Kate lub Gwenview) na dostęp i edycję plików w chmurze Dysku Google.

O KIO GDrive é um 'KIO slave' que permite às aplicações com suporte do KIO (como o Dolphin, o Kate ou o Gwenview) acederem e editarem ficheiros do Google Drive na 'cloud'.

O KIO GDrive é um KIO slave que permite a aplicativos com suporte a KIO (como Dolphin, Kate ou Gwenview) acessar e editar arquivos do Google Drive na nuvem.

KIO GDrive je KIO slave, ktorý umožňuje nie KIO aplikáciám (ako Dolphin, Kate alebo Gwenview) pristupovať a upravovať súbory na Google Drive na cloude.

KIO GDrive är en I/O-slav som gör det möjligt för program medvetna om KIO (såsom Dolphin, Kate eller Gwenview) att komma åt och redigera Google Drive-filer i molnet.

KIO GDrive, buluttaki Google Drive dosylarına erişimek ve düzenleme yapmak için, KIO uygulamalarını etkinleştiren bir KIO bağımlılığıdır.

KIO GDrive — допоміжний засіб введення-виведення даних, який уможливлює для програм із підтримкою KIO (зокрема Dolphin, Kate та Gwenview) доступ та редагування даних файлів на Диску Google у обчислювальній «хмарі».

xxKIO GDrive is a KIO slave that enables KIO-aware applications (such as Dolphin, Kate or Gwenview) to access and edit Google Drive files on the cloud.xx

KIO GDrive 是 Google 云端硬盘的 KIO 从属,它能够让支持 KIO 的程序 (例如 Dolphin,Kate 或 Gwenview) 访问和编辑 Google 云端硬盘上的文件。

https://community.kde.org/KIO_GDrive https://bugs.kde.org/enter_bug.cgi?format=guided&product=kio-gdrive https://docs.kde.org/index.php?application=kioslave5%2Fgdrive elvis.angelaccio_at_kde.org Dolphin showing KIO GDrive in lower right, and Falkon browsing Google Drive in upper left. El Dolphin mostra el KIO GDrive a la part inferior dreta, i el Falkon mostra el Google Drive a la part superior esquerra. El Dolphin mostra el KIO GDrive a la part inferior dreta, i el Falkon mostra el Google Drive a la part superior esquerra. Ansicht von Dolphin mit KIO GDrive unten rechts und Falkon auf Google Drive oben links. Dolphin showing KIO GDrive in lower right, and Falkon browsing Google Drive in upper left. Dolphin mostrando KIO GDrive abajo a la derecha y Falkon navegando por Google Drive arriba a la izquierda. Dolphin näyttää KIO-GDriven alaoikealla ja Falkon selaa Google Driveen ylävasemmalla. Dolphin affichant IO GDrive dans le coin inférieur gauche et Falkon naviguant sur Google Drive dans le coin supérieur gauche. Dolphin mostrando KIO GDrive na parte inferior dereita, e Falkon navegando Google Drive na parte superior esquerda. Dolphin menampilkan KIO GDrive di sebelah kanan bawah, dan Falkon menelusuri Google Drive di sebelah kiri atas Dolphin che mostra KIO GDrive nella parte inferiore destra, e Falkon che sfoglia Google Drive nella parte superiore sinistra. 오른쪽 아래: Dolphin으로 연 KIO GDrive, 왼쪽 위: Falkon으로 연 Google 드라이브. Dolphin die KIO-GDrive rechtsonder toont en Falkon browsing Google Drive linksboven. + Dolphin-vindauge med KIO GDrive nede til høgre, samt Falkon som viser Google Drive oppe til venstre. Dolphin pokazujący KIO GDrive w dolnym, prawym narożniku i Falkon przeglądający Dysk Google w górnym, lewym narożniku. O Dolphin a mostrar o KIO GDrive em baixo e à direita, assim como o Falkon a navegar pelo Google Drive em cima e à esquerda. O Dolphin mostrando o KIO GDrive abaixo à direita, assim como o Falkon navegando pelo Google Drive acima à esquerda. Dolphin zobrazuje KIO GDrive vpravo dole a Falkon prehliadajúci Google Drive vpravo hore. Dolphin som visar KIO GDrive nere till höger, och Falkon som bläddrat till Google Drive uppe till vänster. Dolphin із відкритим KIO GDrive внизу праворуч і Falkon з відкритою сторінкою Google Диск вгорі ліворуч. xxDolphin showing KIO GDrive in lower right, and Falkon browsing Google Drive in upper left.xx 右下角 Dolphin 显示 KIO GDrive,左上角 Falkon 浏览 Google 云端硬盘网页版。 https://cdn.kde.org/screenshots/kio-gdrive/kio-gdrive.png KDE Network folder-gdrive
diff --git a/src/kio_gdrive.cpp b/src/kio_gdrive.cpp index fc90c91..331d4a5 100644 --- a/src/kio_gdrive.cpp +++ b/src/kio_gdrive.cpp @@ -1,1337 +1,1343 @@ /* * Copyright (C) 2013 - 2014 Daniel Vrátil * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "kio_gdrive.h" #include "gdrivebackend.h" #include "gdrivedebug.h" #include "gdrivehelper.h" #include "gdriveurl.h" #include "gdriveversion.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include using namespace KGAPI2; using namespace Drive; class KIOPluginForMetaData : public QObject { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kio.slave.gdrive" FILE "gdrive.json") }; extern "C" { int Q_DECL_EXPORT kdemain(int argc, char **argv) { QApplication app(argc, argv); app.setApplicationName(QStringLiteral("kio_gdrive")); if (argc != 4) { fprintf(stderr, "Usage: kio_gdrive protocol domain-socket1 domain-socket2\n"); exit(-1); } KIOGDrive slave(argv[1], argv[2], argv[3]); slave.dispatchLoop(); return 0; } } KIOGDrive::KIOGDrive(const QByteArray &protocol, const QByteArray &pool_socket, const QByteArray &app_socket): SlaveBase("gdrive", pool_socket, app_socket) { Q_UNUSED(protocol); m_accountManager.reset(new AccountManager); qCDebug(GDRIVE) << "KIO GDrive ready: version" << GDRIVE_VERSION_STRING; } KIOGDrive::~KIOGDrive() { closeConnection(); } KIOGDrive::Action KIOGDrive::handleError(const KGAPI2::Job &job, const QUrl &url) { qCDebug(GDRIVE) << "Completed job" << (&job) << "error code:" << job.error() << "- message:" << job.errorString(); switch (job.error()) { case KGAPI2::OK: case KGAPI2::NoError: return Success; case KGAPI2::AuthCancelled: case KGAPI2::AuthError: error(KIO::ERR_CANNOT_LOGIN, url.toDisplayString()); return Fail; case KGAPI2::Unauthorized: { const AccountPtr oldAccount = job.account(); const AccountPtr account = m_accountManager->refreshAccount(oldAccount); if (!account) { error(KIO::ERR_CANNOT_LOGIN, url.toDisplayString()); return Fail; } return Restart; } case KGAPI2::Forbidden: error(KIO::ERR_ACCESS_DENIED, url.toDisplayString()); return Fail; case KGAPI2::NotFound: error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); return Fail; case KGAPI2::NoContent: error(KIO::ERR_NO_CONTENT, url.toDisplayString()); return Fail; case KGAPI2::QuotaExceeded: error(KIO::ERR_DISK_FULL, url.toDisplayString()); return Fail; default: error(KIO::ERR_SLAVE_DEFINED, job.errorString()); return Fail; } return Fail; } void KIOGDrive::fileSystemFreeSpace(const QUrl &url) { const auto gdriveUrl = GDriveUrl(url); const QString accountId = gdriveUrl.account(); if (accountId == QLatin1String("new-account")) { qCDebug(GDRIVE) << "fileSystemFreeSpace is not supported for new-account url"; finished(); return; } if (gdriveUrl.isRoot()) { qCDebug(GDRIVE) << "fileSystemFreeSpace is not supported for gdrive root urls"; error(KIO::ERR_CANNOT_STAT, url.toDisplayString()); return; } qCDebug(GDRIVE) << "Getting fileSystemFreeSpace for" << url; AboutFetchJob aboutFetch(getAccount(accountId)); aboutFetch.setFields({ About::Fields::Kind, About::Fields::QuotaBytesTotal, About::Fields::QuotaBytesUsedAggregate }); if (runJob(aboutFetch, url, accountId)) { const AboutPtr about = aboutFetch.aboutData(); if (about) { setMetaData(QStringLiteral("total"), QString::number(about->quotaBytesTotal())); setMetaData(QStringLiteral("available"), QString::number(about->quotaBytesTotal() - about->quotaBytesUsedAggregate())); finished(); return; } } } AccountPtr KIOGDrive::getAccount(const QString &accountName) { return m_accountManager->account(accountName); } void KIOGDrive::virtual_hook(int id, void *data) { switch (id) { case SlaveBase::GetFileSystemFreeSpace: { QUrl *url = static_cast(data); fileSystemFreeSpace(*url); break; } default: SlaveBase::virtual_hook(id, data); } } KIO::UDSEntry KIOGDrive::fileToUDSEntry(const FilePtr &origFile, const QString &path) const { KIO::UDSEntry entry; bool isFolder = false; FilePtr file = origFile; if (GDriveHelper::isGDocsDocument(file)) { GDriveHelper::convertFromGDocs(file); } entry.fastInsert(KIO::UDSEntry::UDS_NAME, file->title()); entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, file->title()); entry.fastInsert(KIO::UDSEntry::UDS_COMMENT, file->description()); if (file->isFolder()) { entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0); isFolder = true; } else { entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, file->mimeType()); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, file->fileSize()); entry.fastInsert(KIO::UDSEntry::UDS_URL, fileToUrl(origFile, path).toString()); } entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, file->createdDate().toTime_t()); entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, file->modifiedDate().toTime_t()); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, file->lastViewedByMeDate().toTime_t()); if (!file->ownerNames().isEmpty()) { entry.fastInsert(KIO::UDSEntry::UDS_USER, file->ownerNames().first()); } if (!isFolder) { if (file->editable()) { entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); } else { entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH); } } else { entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); } return entry; } QUrl KIOGDrive::fileToUrl(const FilePtr &file, const QString &path) const { QUrl url; url.setScheme(GDriveUrl::Scheme); url.setPath(path + QLatin1Char('/') + file->title()); QUrlQuery urlQuery; urlQuery.addQueryItem(QStringLiteral("id"), file->id()); url.setQuery(urlQuery); return url; } void KIOGDrive::openConnection() { qCDebug(GDRIVE) << "Ready to talk to GDrive"; } KIO::UDSEntry KIOGDrive::accountToUDSEntry(const QString &accountNAme) { KIO::UDSEntry entry; entry.fastInsert(KIO::UDSEntry::UDS_NAME, accountNAme); entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, accountNAme); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("folder-gdrive")); return entry; } KIO::UDSEntry KIOGDrive::sharedDriveToUDSEntry(const DrivesPtr sharedDrive) { KIO::UDSEntry entry; qlonglong udsAccess = S_IRUSR | S_IXUSR | S_IRGRP; if (sharedDrive->capabilities()->canRenameDrive() || sharedDrive->capabilities()->canDeleteDrive()) { udsAccess |= S_IWUSR; } entry.fastInsert(KIO::UDSEntry::UDS_NAME, sharedDrive->id()); entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, sharedDrive->name()); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0); entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, sharedDrive->createdDate().toSecsSinceEpoch()); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, udsAccess); entry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, sharedDrive->hidden()); entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("folder-gdrive")); return entry; } void KIOGDrive::createAccount() { const KGAPI2::AccountPtr account = m_accountManager->createAccount(); if (!account->accountName().isEmpty()) { // Redirect to the account we just created. redirection(QUrl(QStringLiteral("gdrive:/%1").arg(account->accountName()))); finished(); return; } if (m_accountManager->accounts().isEmpty()) { error(KIO::ERR_SLAVE_DEFINED, i18n("There are no Google Drive accounts enabled. Please add at least one.")); return; } // Redirect to the root, we already have some account. redirection(QUrl(QStringLiteral("gdrive:/"))); finished(); } void KIOGDrive::listAccounts() { const auto accounts = m_accountManager->accounts(); if (accounts.isEmpty()) { createAccount(); return; } Q_FOREACH (const QString &account, accounts) { const KIO::UDSEntry entry = accountToUDSEntry(account); listEntry(entry); } KIO::UDSEntry newAccountEntry; newAccountEntry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("new-account")); newAccountEntry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18nc("login in a new google account", "New account")); newAccountEntry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); newAccountEntry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("list-add-user")); listEntry(newAccountEntry); // Create also non-writable UDSentry for "." KIO::UDSEntry entry; entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); listEntry(entry); finished(); return; } void KIOGDrive::listSharedDrivesRoot(const QUrl &url) { const auto gdriveUrl = GDriveUrl(url); const QString accountId = gdriveUrl.account(); DrivesFetchJob sharedDrivesFetchJob(getAccount(accountId)); sharedDrivesFetchJob.setFields({ Drives::Fields::Kind, Drives::Fields::Id, Drives::Fields::Name, Drives::Fields::Hidden, Drives::Fields::CreatedDate, Drives::Fields::Capabilities }); if (runJob(sharedDrivesFetchJob, url, accountId)) { ObjectsList objects = sharedDrivesFetchJob.items(); Q_FOREACH (const ObjectPtr &object, objects) { const DrivesPtr sharedDrive = object.dynamicCast(); const KIO::UDSEntry entry = sharedDriveToUDSEntry(sharedDrive); listEntry(entry); } auto entry = fetchSharedDrivesRootEntry(accountId, true); listEntry(entry); finished(); } } bool KIOGDrive::createSharedDrive(const QUrl &url) { const auto gdriveUrl = GDriveUrl(url); const QString accountId = gdriveUrl.account(); DrivesPtr sharedDrive = DrivesPtr::create(); sharedDrive->setName(gdriveUrl.filename()); QString requestId = QUuid::createUuid().toString(); DrivesCreateJob createJob(requestId, sharedDrive, getAccount(accountId)); return runJob(createJob, url, accountId); } bool KIOGDrive::deleteSharedDrive(const QUrl url) { const auto gdriveUrl = GDriveUrl(url); const QString accountId = gdriveUrl.account(); DrivesDeleteJob sharedDriveDeleteJob(gdriveUrl.filename(), getAccount(accountId)); return runJob(sharedDriveDeleteJob, url, accountId); } void KIOGDrive::statSharedDrive(const QUrl url) { const auto gdriveUrl = GDriveUrl(url); const QString accountId = gdriveUrl.account(); DrivesFetchJob sharedDriveFetchJob(gdriveUrl.filename(), getAccount(accountId)); sharedDriveFetchJob.setFields({ Drives::Fields::Kind, Drives::Fields::Id, Drives::Fields::Name, Drives::Fields::Hidden, Drives::Fields::CreatedDate, Drives::Fields::Capabilities }); if (!runJob(sharedDriveFetchJob, url, accountId)) { // runJob called error() return; } ObjectPtr object = sharedDriveFetchJob.items()[0]; const DrivesPtr sharedDrive = object.dynamicCast(); const auto entry = sharedDriveToUDSEntry(sharedDrive); statEntry(entry); finished(); } KIO::UDSEntry KIOGDrive::fetchSharedDrivesRootEntry(QString accountId, bool asCurrentDir) { // Not every user is allowed to create shared Drives, // check with About resource. bool canCreateDrives = false; AboutFetchJob aboutFetch(getAccount(accountId)); aboutFetch.setFields({ About::Fields::Kind, About::Fields::CanCreateDrives }); QEventLoop eventLoop; QObject::connect(&aboutFetch, &KGAPI2::Job::finished, &eventLoop, &QEventLoop::quit); eventLoop.exec(); if (aboutFetch.error() == KGAPI2::OK || aboutFetch.error() == KGAPI2::NoError) { const AboutPtr about = aboutFetch.aboutData(); if (about) { canCreateDrives = about->canCreateDrives(); } } qCDebug(GDRIVE) << "Account" << accountId << (canCreateDrives ? "can" : "can't") << "create Drives"; KIO::UDSEntry entry; if (asCurrentDir) { entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); } else { entry.fastInsert(KIO::UDSEntry::UDS_NAME, GDriveUrl::SharedDrivesDir); entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, GDriveUrl::SharedDrivesDir); } entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0); entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("folder-gdrive")); // If user is allowed to create shared Drives, add write bit on directory qlonglong udsAccess = S_IRUSR | S_IXUSR; if (canCreateDrives) { udsAccess |= S_IWUSR; } entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, udsAccess); return entry; } class RecursionDepthCounter { public: RecursionDepthCounter() { ++sDepth; } ~RecursionDepthCounter() { --sDepth; } RecursionDepthCounter(const RecursionDepthCounter &) = delete; RecursionDepthCounter& operator=(const RecursionDepthCounter &) = delete; int depth() const { return sDepth; } private: static int sDepth; }; int RecursionDepthCounter::sDepth = 0; QString KIOGDrive::resolveFileIdFromPath(const QString &path, PathFlags flags) { qCDebug(GDRIVE) << "Resolving file ID for" << path; if (path.isEmpty()) { return QString(); } QString fileId = m_cache.idForPath(path); if (!fileId.isEmpty()) { qCDebug(GDRIVE) << "Resolved" << path << "to" << fileId << "(from cache)"; return fileId; } QUrl url; url.setScheme(GDriveUrl::Scheme); url.setPath(path); const auto gdriveUrl = GDriveUrl(url); Q_ASSERT(!gdriveUrl.isRoot()); if (gdriveUrl.isAccountRoot() || gdriveUrl.isTrashDir()) { qCDebug(GDRIVE) << "Resolved" << path << "to \"root\""; return rootFolderId(gdriveUrl.account()); } if (gdriveUrl.isSharedDrive()) { qCDebug(GDRIVE) << "Resolved" << path << "to Shared Drive" << gdriveUrl.filename(); return gdriveUrl.filename(); } else if (gdriveUrl.isSharedDrivesRoot()) { qCDebug(GDRIVE) << "Resolved" << path << "to Shared Drives root"; return QString(); } // Try to recursively resolve ID of parent path - either from cache, or by // querying Google const QString parentId = resolveFileIdFromPath(gdriveUrl.parentPath(), KIOGDrive::PathIsFolder); if (parentId.isEmpty()) { // We failed to resolve parent -> error return QString(); } FileSearchQuery query; if (flags != KIOGDrive::None) { query.addQuery(FileSearchQuery::MimeType, (flags & KIOGDrive::PathIsFolder ? FileSearchQuery::Equals : FileSearchQuery::NotEquals), GDriveHelper::folderMimeType()); } query.addQuery(FileSearchQuery::Title, FileSearchQuery::Equals, gdriveUrl.filename()); query.addQuery(FileSearchQuery::Parents, FileSearchQuery::In, parentId); query.addQuery(FileSearchQuery::Trashed, FileSearchQuery::Equals, gdriveUrl.isTrashed()); const QString accountId = gdriveUrl.account(); FileFetchJob fetchJob(query, getAccount(accountId)); fetchJob.setFields({File::Fields::Id, File::Fields::Title, File::Fields::Labels}); if (!runJob(fetchJob, url, accountId)) { return QString(); } const ObjectsList objects = fetchJob.items(); qCDebug(GDRIVE) << objects; if (objects.count() == 0) { qCWarning(GDRIVE) << "Failed to resolve" << path; return QString(); } const FilePtr file = objects[0].dynamicCast(); m_cache.insertPath(path, file->id()); qCDebug(GDRIVE) << "Resolved" << path << "to" << file->id() << "(from network)"; return file->id(); } QString KIOGDrive::rootFolderId(const QString &accountId) { auto it = m_rootIds.constFind(accountId); if (it == m_rootIds.cend()) { qCDebug(GDRIVE) << "Getting root ID for" << accountId; AboutFetchJob aboutFetch(getAccount(accountId)); aboutFetch.setFields({About::Fields::Kind, About::Fields::RootFolderId}); QUrl url; if (!runJob(aboutFetch, url, accountId)) { return QString(); } const AboutPtr about = aboutFetch.aboutData(); if (!about || about->rootFolderId().isEmpty()) { qCWarning(GDRIVE) << "Failed to obtain root ID"; return QString(); } auto v = m_rootIds.insert(accountId, about->rootFolderId()); return *v; } return *it; } void KIOGDrive::listDir(const QUrl &url) { qCDebug(GDRIVE) << "Going to list" << url; const auto gdriveUrl = GDriveUrl(url); const QString accountId = gdriveUrl.account(); if (accountId == QLatin1String("new-account")) { createAccount(); return; } QString folderId; if (gdriveUrl.isRoot()) { listAccounts(); return; } else if (gdriveUrl.isAccountRoot()) { auto entry = fetchSharedDrivesRootEntry(accountId); listEntry(entry); folderId = rootFolderId(accountId); } else if (gdriveUrl.isSharedDrivesRoot()) { listSharedDrivesRoot(url); return; } else { folderId = m_cache.idForPath(url.path()); if (folderId.isEmpty()) { folderId = resolveFileIdFromPath(url.adjusted(QUrl::StripTrailingSlash).path(), KIOGDrive::PathIsFolder); } if (folderId.isEmpty()) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } } FileSearchQuery query; query.addQuery(FileSearchQuery::Trashed, FileSearchQuery::Equals, false); query.addQuery(FileSearchQuery::Parents, FileSearchQuery::In, folderId); FileFetchJob fileFetchJob(query, getAccount(accountId)); const auto extraFields = QStringList({ KGAPI2::Drive::File::Fields::Labels, KGAPI2::Drive::File::Fields::ExportLinks, KGAPI2::Drive::File::Fields::LastViewedByMeDate, }); fileFetchJob.setFields(KGAPI2::Drive::FileFetchJob::FieldShorthands::BasicFields + extraFields); if (!runJob(fileFetchJob, url, accountId)) { return; } ObjectsList objects = fileFetchJob.items(); Q_FOREACH (const ObjectPtr &object, objects) { const FilePtr file = object.dynamicCast(); const KIO::UDSEntry entry = fileToUDSEntry(file, url.adjusted(QUrl::StripTrailingSlash).path()); listEntry(entry); const QString path = url.path().endsWith(QLatin1Char('/')) ? url.path() : url.path() + QLatin1Char('/'); m_cache.insertPath(path + file->title(), file->id()); } // We also need a non-null and writable UDSentry for "." KIO::UDSEntry entry; entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); listEntry(entry); finished(); } void KIOGDrive::mkdir(const QUrl &url, int permissions) { // NOTE: We deliberately ignore the permissions field here, because GDrive // does not recognize any privileges that could be mapped to standard UNIX // file permissions. Q_UNUSED(permissions); qCDebug(GDRIVE) << "Creating directory" << url; const auto gdriveUrl = GDriveUrl(url); const QString accountId = gdriveUrl.account(); // At least account and new folder name if (gdriveUrl.isRoot() || gdriveUrl.isAccountRoot()) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } if (gdriveUrl.isSharedDrive()) { qCDebug(GDRIVE) << "Directory is shared drive, creating that instead" << url; if (createSharedDrive(url)) { // runJon will have shown an error otherwise finished(); } return; } QString parentId; if (gdriveUrl.isTopLevel()) { parentId = rootFolderId(accountId); } else { parentId = resolveFileIdFromPath(gdriveUrl.parentPath(), KIOGDrive::PathIsFolder); } if (parentId.isEmpty()) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } FilePtr file(new File()); file->setTitle(gdriveUrl.filename()); file->setMimeType(File::folderMimeType()); ParentReferencePtr parent(new ParentReference(parentId)); file->setParents(ParentReferencesList() << parent); FileCreateJob createJob(file, getAccount(accountId)); if (runJob(createJob, url, accountId)) { finished(); } } void KIOGDrive::stat(const QUrl &url) { qCDebug(GDRIVE) << "Going to stat()" << url; const auto gdriveUrl = GDriveUrl(url); const QString accountId = gdriveUrl.account(); if (gdriveUrl.isRoot()) { // TODO Can we stat() root? finished(); return; } if (gdriveUrl.isAccountRoot()) { const KIO::UDSEntry entry = accountToUDSEntry(accountId); statEntry(entry); finished(); return; } if (gdriveUrl.isSharedDrivesRoot()) { qCDebug(GDRIVE) << "stat()ing Shared Drives root"; const auto entry = fetchSharedDrivesRootEntry(accountId); statEntry(entry); finished(); return; } if (gdriveUrl.isSharedDrive()) { qCDebug(GDRIVE) << "stat()ing Shared Drive" << url; statSharedDrive(url); return; } const QUrlQuery urlQuery(url); const QString fileId = urlQuery.hasQueryItem(QStringLiteral("id")) ? urlQuery.queryItemValue(QStringLiteral("id")) : resolveFileIdFromPath(url.adjusted(QUrl::StripTrailingSlash).path(), KIOGDrive::None); if (fileId.isEmpty()) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } FileFetchJob fileFetchJob(fileId, getAccount(accountId)); if (!runJob(fileFetchJob, url, accountId)) { qCDebug(GDRIVE) << "Failed stat()ing file" << fileFetchJob.errorString(); return; } const ObjectsList objects = fileFetchJob.items(); if (objects.count() != 1) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } const FilePtr file = objects.first().dynamicCast(); if (file->labels()->trashed()) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } const KIO::UDSEntry entry = fileToUDSEntry(file, gdriveUrl.parentPath()); statEntry(entry); finished(); } void KIOGDrive::get(const QUrl &url) { qCDebug(GDRIVE) << "Fetching content of" << url; const auto gdriveUrl = GDriveUrl(url); const QString accountId = gdriveUrl.account(); if (gdriveUrl.isRoot()) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } if (gdriveUrl.isAccountRoot()) { // You cannot GET an account folder! error(KIO::ERR_ACCESS_DENIED, url.path()); return; } const QUrlQuery urlQuery(url); const QString fileId = urlQuery.hasQueryItem(QStringLiteral("id")) ? urlQuery.queryItemValue(QStringLiteral("id")) : resolveFileIdFromPath(url.adjusted(QUrl::StripTrailingSlash).path(), KIOGDrive::PathIsFile); if (fileId.isEmpty()) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } FileFetchJob fileFetchJob(fileId, getAccount(accountId)); fileFetchJob.setFields({File::Fields::Id, File::Fields::MimeType, File::Fields::ExportLinks, File::Fields::DownloadUrl}); if (!runJob(fileFetchJob, url, accountId)) { return; } const ObjectsList objects = fileFetchJob.items(); if (objects.count() != 1) { error(KIO::ERR_DOES_NOT_EXIST, url.fileName()); return; } FilePtr file = objects.first().dynamicCast(); QUrl downloadUrl; if (GDriveHelper::isGDocsDocument(file)) { downloadUrl = GDriveHelper::convertFromGDocs(file); } else { downloadUrl = file->downloadUrl(); } mimeType(file->mimeType()); FileFetchContentJob contentJob(downloadUrl, getAccount(accountId)); if (!runJob(contentJob, url, accountId)) { return; } QByteArray contentData = contentJob.data(); totalSize(contentData.size()); // data() has a maximum transfer size of 14 MiB so we need to send it in chunks. // See TransferJob::slotDataReq. int transferred = 0; // do-while loop to call data() even for empty files. do { const size_t nextChunk = qMin(contentData.size() - transferred, 14 * 1024 * 1024); data(QByteArray::fromRawData(contentData.constData() + transferred, nextChunk)); transferred += nextChunk; } while (transferred < contentData.size()); finished(); } -bool KIOGDrive::readPutData(QTemporaryFile &tempFile) +bool KIOGDrive::readPutData(QTemporaryFile &tempFile, FilePtr &fileMetaData) { // TODO: Instead of using a temp file, upload directly the raw data (requires // support in LibKGAPI) // TODO: For large files, switch to resumable upload and upload the file in // reasonably large chunks (requires support in LibKGAPI) // TODO: Support resumable upload (requires support in LibKGAPI) if (!tempFile.open()) { error(KIO::ERR_CANNOT_WRITE, tempFile.fileName()); return false; } int result; do { QByteArray buffer; dataReq(); result = readData(buffer); if (!buffer.isEmpty()) { qint64 size = tempFile.write(buffer); if (size != buffer.size()) { error(KIO::ERR_CANNOT_WRITE, tempFile.fileName()); return false; } } } while (result > 0); + + const QMimeType mime = QMimeDatabase().mimeTypeForFileNameAndData(fileMetaData->title(), &tempFile); + fileMetaData->setMimeType(mime.name()); + tempFile.close(); if (result == -1) { qCWarning(GDRIVE) << "Could not read source file" << tempFile.fileName(); error(KIO::ERR_CANNOT_READ, QString()); return false; } return true; } bool KIOGDrive::runJob(KGAPI2::Job &job, const QUrl &url, const QString &accountId) { KIOGDrive::Action action = KIOGDrive::Fail; Q_FOREVER { qCDebug(GDRIVE) << "Running job" << (&job) << "with accessToken" << GDriveHelper::elideToken(job.account()->accessToken()); QEventLoop eventLoop; QObject::connect(&job, &KGAPI2::Job::finished, &eventLoop, &QEventLoop::quit); eventLoop.exec(); action = handleError(job, url); if (action == KIOGDrive::Success) { break; } else if (action == KIOGDrive::Fail) { return false; } job.setAccount(getAccount(accountId)); job.restart(); }; return true; } bool KIOGDrive::putUpdate(const QUrl &url) { const QString fileId = QUrlQuery(url).queryItemValue(QStringLiteral("id")); qCDebug(GDRIVE) << Q_FUNC_INFO << url << fileId; const auto gdriveUrl = GDriveUrl(url); const auto accountId = gdriveUrl.account(); FileFetchJob fetchJob(fileId, getAccount(accountId)); if (!runJob(fetchJob, url, accountId)) { return false; } const ObjectsList objects = fetchJob.items(); if (objects.size() != 1) { putCreate(url); return false; } - const FilePtr file = objects[0].dynamicCast(); + FilePtr file = objects[0].dynamicCast(); + QTemporaryFile tmpFile; - if (!readPutData(tmpFile)) { + if (!readPutData(tmpFile, file)) { error(KIO::ERR_CANNOT_READ, url.path()); return false; } FileModifyJob modifyJob(tmpFile.fileName(), file, getAccount(accountId)); modifyJob.setUpdateModifiedDate(true); if (!runJob(modifyJob, url, accountId)) { return false; } return true; } bool KIOGDrive::putCreate(const QUrl &url) { qCDebug(GDRIVE) << Q_FUNC_INFO << url; ParentReferencesList parentReferences; const auto gdriveUrl = GDriveUrl(url); if (gdriveUrl.isRoot() || gdriveUrl.isAccountRoot()) { error(KIO::ERR_ACCESS_DENIED, url.path()); return false; } if (!gdriveUrl.isTopLevel()) { // Not creating in root directory, fill parent references const QString parentId = resolveFileIdFromPath(gdriveUrl.parentPath()); if (parentId.isEmpty()) { error(KIO::ERR_DOES_NOT_EXIST, url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); return false; } parentReferences << ParentReferencePtr(new ParentReference(parentId)); } FilePtr file(new File); file->setTitle(gdriveUrl.filename()); file->setParents(parentReferences); /* if (hasMetaData(QLatin1String("modified"))) { const QString modified = metaData(QLatin1String("modified")); qCDebug(GDRIVE) << modified; file->setModifiedDate(KDateTime::fromString(modified, KDateTime::ISODate)); } */ QTemporaryFile tmpFile; - if (!readPutData(tmpFile)) { + if (!readPutData(tmpFile, file)) { error(KIO::ERR_CANNOT_READ, url.path()); return false; } const auto accountId = gdriveUrl.account(); FileCreateJob createJob(tmpFile.fileName(), file, getAccount(accountId)); if (!runJob(createJob, url, accountId)) { return false; } return true; } void KIOGDrive::put(const QUrl &url, int permissions, KIO::JobFlags flags) { // NOTE: We deliberately ignore the permissions field here, because GDrive // does not recognize any privileges that could be mapped to standard UNIX // file permissions. Q_UNUSED(permissions) Q_UNUSED(flags) qCDebug(GDRIVE) << Q_FUNC_INFO << url; const auto gdriveUrl = GDriveUrl(url); if (gdriveUrl.isSharedDrive()) { qCDebug(GDRIVE) << "Can't create files in Shared Drives root" << url; error(KIO::ERR_CANNOT_WRITE, url.path()); return; } if (QUrlQuery(url).hasQueryItem(QStringLiteral("id"))) { if (!putUpdate(url)) { return; } } else { if (!putCreate(url)) { return; } } // FIXME: Update the cache now! finished(); } void KIOGDrive::copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags) { qCDebug(GDRIVE) << "Going to copy" << src << "to" << dest; // NOTE: We deliberately ignore the permissions field here, because GDrive // does not recognize any privileges that could be mapped to standard UNIX // file permissions. Q_UNUSED(permissions); // NOTE: We deliberately ignore the flags field here, because the "overwrite" // flag would have no effect on GDrive, since file name don't have to be // unique. IOW if there is a file "foo.bar" and user copy-pastes into the // same directory, the FileCopyJob will succeed and a new file with the same // name will be created. Q_UNUSED(flags); const auto srcGDriveUrl = GDriveUrl(src); const auto destGDriveUrl = GDriveUrl(dest); const QString sourceAccountId = srcGDriveUrl.account(); const QString destAccountId = destGDriveUrl.account(); // TODO: Does this actually happen, or does KIO treat our account name as host? if (sourceAccountId != destAccountId) { // KIO will fallback to get+post error(KIO::ERR_UNSUPPORTED_ACTION, src.path()); return; } if (srcGDriveUrl.isRoot()) { error(KIO::ERR_DOES_NOT_EXIST, src.path()); return; } if (srcGDriveUrl.isAccountRoot()) { error(KIO::ERR_ACCESS_DENIED, src.path()); return; } const QUrlQuery urlQuery(src); const QString sourceFileId = urlQuery.hasQueryItem(QStringLiteral("id")) ? urlQuery.queryItemValue(QStringLiteral("id")) : resolveFileIdFromPath(src.adjusted(QUrl::StripTrailingSlash).path()); if (sourceFileId.isEmpty()) { error(KIO::ERR_DOES_NOT_EXIST, src.path()); return; } FileFetchJob sourceFileFetchJob(sourceFileId, getAccount(sourceAccountId)); sourceFileFetchJob.setFields({File::Fields::Id, File::Fields::ModifiedDate, File::Fields::LastViewedByMeDate, File::Fields::Description}); if (!runJob(sourceFileFetchJob, src, sourceAccountId)) { return; } const ObjectsList objects = sourceFileFetchJob.items(); if (objects.count() != 1) { error(KIO::ERR_DOES_NOT_EXIST, src.path()); return; } const FilePtr sourceFile = objects[0].dynamicCast(); ParentReferencesList destParentReferences; if (destGDriveUrl.isRoot()) { error(KIO::ERR_ACCESS_DENIED, dest.path()); return; } QString destDirId; if (destGDriveUrl.isTopLevel()) { destDirId = rootFolderId(destAccountId); } else { destDirId = resolveFileIdFromPath(destGDriveUrl.parentPath(), KIOGDrive::PathIsFolder); } destParentReferences << ParentReferencePtr(new ParentReference(destDirId)); FilePtr destFile(new File); destFile->setTitle(destGDriveUrl.filename()); destFile->setModifiedDate(sourceFile->modifiedDate()); destFile->setLastViewedByMeDate(sourceFile->lastViewedByMeDate()); destFile->setDescription(sourceFile->description()); destFile->setParents(destParentReferences); FileCopyJob copyJob(sourceFile, destFile, getAccount(sourceAccountId)); if (runJob(copyJob, dest, sourceAccountId)) { finished(); } } void KIOGDrive::del(const QUrl &url, bool isfile) { // FIXME: Verify that a single file cannot actually have multiple parent // references. If it can, then we need to be more careful: currently this // implementation will simply remove the file from all it's parents but // it actually should just remove the current parent reference // FIXME: Because of the above, we are not really deleting the file, but only // moving it to trash - so if users really really really wants to delete the // file, they have to go to GDrive web interface and delete it there. I think // that we should do the DELETE operation here, because for trash people have // their local trashes. This however requires fixing the first FIXME first, // otherwise we are risking severe data loss. const auto gdriveUrl = GDriveUrl(url); // Trying to delete the Team Drive root is pointless if (gdriveUrl.isSharedDrivesRoot()) { qCDebug(GDRIVE) << "Tried deleting Shared Drives root."; error(KIO::ERR_SLAVE_DEFINED, i18n("Can't delete Shared Drives root.")); return; } qCDebug(GDRIVE) << "Deleting URL" << url << "- is it a file?" << isfile; const QUrlQuery urlQuery(url); const QString fileId = isfile && urlQuery.hasQueryItem(QStringLiteral("id")) ? urlQuery.queryItemValue(QStringLiteral("id")) : resolveFileIdFromPath(url.adjusted(QUrl::StripTrailingSlash).path(), isfile ? KIOGDrive::PathIsFile : KIOGDrive::PathIsFolder); if (fileId.isEmpty()) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } const QString accountId = gdriveUrl.account(); // If user tries to delete the account folder, remove the account from the keychain if (gdriveUrl.isAccountRoot()) { const KGAPI2::AccountPtr account = getAccount(accountId); if (account->accountName().isEmpty()) { error(KIO::ERR_DOES_NOT_EXIST, accountId); return; } m_accountManager->removeAccount(accountId); finished(); return; } if (gdriveUrl.isSharedDrive()) { qCDebug(GDRIVE) << "Deleting Shared Drive" << url; if (deleteSharedDrive(url)) { // Error will have been called in case of error finished(); } return; } // GDrive allows us to delete entire directory even when it's not empty, // so we need to emulate the normal behavior ourselves by checking number of // child references if (!isfile) { ChildReferenceFetchJob referencesFetch(fileId, getAccount(accountId)); if (!runJob(referencesFetch, url, accountId)) { return; } const bool isEmpty = !referencesFetch.items().count(); if (!isEmpty && metaData(QStringLiteral("recurse")) != QLatin1String("true")) { error(KIO::ERR_CANNOT_RMDIR, url.path()); return; } } FileTrashJob trashJob(fileId, getAccount(accountId)); if (runJob(trashJob, url, accountId)) { m_cache.removePath(url.path()); finished(); } } void KIOGDrive::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) { Q_UNUSED(flags) qCDebug(GDRIVE) << "Renaming" << src << "to" << dest; const auto srcGDriveUrl = GDriveUrl(src); const auto destGDriveUrl = GDriveUrl(dest); const QString sourceAccountId = srcGDriveUrl.account(); const QString destAccountId = destGDriveUrl.account(); // TODO: Does this actually happen, or does KIO treat our account name as host? if (sourceAccountId != destAccountId) { error(KIO::ERR_UNSUPPORTED_ACTION, src.path()); return; } if (srcGDriveUrl.isRoot()) { error(KIO::ERR_DOES_NOT_EXIST, dest.path()); return; } if (srcGDriveUrl.isAccountRoot()) { error(KIO::ERR_ACCESS_DENIED, dest.path()); return; } const QUrlQuery urlQuery(src); const QString sourceFileId = urlQuery.hasQueryItem(QStringLiteral("id")) ? urlQuery.queryItemValue(QStringLiteral("id")) : resolveFileIdFromPath(src.adjusted(QUrl::StripTrailingSlash).path(), KIOGDrive::PathIsFile); if (sourceFileId.isEmpty()) { error(KIO::ERR_DOES_NOT_EXIST, src.path()); return; } if (srcGDriveUrl.isSharedDrive()) { qCDebug(GDRIVE) << "Renaming Shared Drive" << srcGDriveUrl.filename() << "to" << destGDriveUrl.filename(); DrivesPtr drives = DrivesPtr::create(); drives->setId(sourceFileId); drives->setName(destGDriveUrl.filename()); DrivesModifyJob modifyJob(drives, getAccount(sourceAccountId)); if (!runJob(modifyJob, src, sourceAccountId)) { error(KIO::ERR_DOES_NOT_EXIST, src.path()); return; } finished(); return; } // We need to fetch ALL, so that we can do update later FileFetchJob sourceFileFetchJob(sourceFileId, getAccount(sourceAccountId)); if (!runJob(sourceFileFetchJob, src, sourceAccountId)) { return; } const ObjectsList objects = sourceFileFetchJob.items(); if (objects.count() != 1) { qCDebug(GDRIVE) << "FileFetchJob retrieved" << objects.count() << "items, while only one was expected."; error(KIO::ERR_DOES_NOT_EXIST, src.path()); return; } const FilePtr sourceFile = objects[0].dynamicCast(); ParentReferencesList parentReferences = sourceFile->parents(); if (destGDriveUrl.isRoot()) { // user is trying to move to top-level gdrive:/// error(KIO::ERR_ACCESS_DENIED, dest.fileName()); return; } if (destGDriveUrl.isAccountRoot()) { // user is trying to move to root -> we are only renaming } else { // skip filename and extract the second-to-last component const QString destDirId = resolveFileIdFromPath(destGDriveUrl.parentPath(), KIOGDrive::PathIsFolder); const QString srcDirId = resolveFileIdFromPath(srcGDriveUrl.parentPath(), KIOGDrive::PathIsFolder); // Remove source from parent references auto iter = parentReferences.begin(); bool removed = false; while (iter != parentReferences.end()) { const ParentReferencePtr ref = *iter; if (ref->id() == srcDirId) { parentReferences.erase(iter); removed = true; break; } ++iter; } if (!removed) { qCDebug(GDRIVE) << "Could not remove" << src << "from parent references."; error(KIO::ERR_DOES_NOT_EXIST, src.path()); return; } // Add destination to parent references parentReferences << ParentReferencePtr(new ParentReference(destDirId)); } FilePtr destFile(sourceFile); destFile->setTitle(destGDriveUrl.filename()); destFile->setParents(parentReferences); FileModifyJob modifyJob(destFile, getAccount(sourceAccountId)); modifyJob.setUpdateModifiedDate(true); if (runJob(modifyJob, dest, sourceAccountId)) { finished(); } } void KIOGDrive::mimetype(const QUrl &url) { qCDebug(GDRIVE) << Q_FUNC_INFO << url; const QUrlQuery urlQuery(url); const QString fileId = urlQuery.hasQueryItem(QStringLiteral("id")) ? urlQuery.queryItemValue(QStringLiteral("id")) : resolveFileIdFromPath(url.adjusted(QUrl::StripTrailingSlash).path()); if (fileId.isEmpty()) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } const QString accountId = GDriveUrl(url).account(); FileFetchJob fileFetchJob(fileId, getAccount(accountId)); fileFetchJob.setFields({File::Fields::Id, File::Fields::MimeType}); if (!runJob(fileFetchJob, url, accountId)) { return; } const ObjectsList objects = fileFetchJob.items(); if (objects.count() != 1) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } const FilePtr file = objects.first().dynamicCast(); mimeType(file->mimeType()); finished(); } #include "kio_gdrive.moc" diff --git a/src/kio_gdrive.h b/src/kio_gdrive.h index 531a451..fa0cd3c 100644 --- a/src/kio_gdrive.h +++ b/src/kio_gdrive.h @@ -1,123 +1,123 @@ /* * Copyright (C) 2013 - 2014 Daniel Vrátil * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #ifndef KIO_GDRIVE_H #define KIO_GDRIVE_H #include "pathcache.h" #include #include #include #include class AbstractAccountManager; class QTemporaryFile; namespace KGAPI2 { class Job; } class KIOGDrive : public KIO::SlaveBase { public: enum Action { Success, Fail, Restart }; explicit KIOGDrive(const QByteArray &protocol, const QByteArray &pool_socket, const QByteArray &app_socket); virtual ~KIOGDrive(); virtual void openConnection() Q_DECL_OVERRIDE; virtual void listDir(const QUrl &url) Q_DECL_OVERRIDE; virtual void mkdir(const QUrl &url, int permissions) Q_DECL_OVERRIDE; virtual void stat(const QUrl &url) Q_DECL_OVERRIDE; virtual void get(const QUrl &url) Q_DECL_OVERRIDE; virtual void put(const QUrl &url, int permissions, KIO::JobFlags flags) Q_DECL_OVERRIDE; virtual void copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags) Q_DECL_OVERRIDE; virtual void rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) Q_DECL_OVERRIDE; virtual void del(const QUrl &url, bool isfile) Q_DECL_OVERRIDE; virtual void mimetype(const QUrl &url) Q_DECL_OVERRIDE; protected: void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; private: Q_DISABLE_COPY(KIOGDrive) enum PathFlags { None = 0, PathIsFolder = 1, PathIsFile = 2 }; static KIO::UDSEntry accountToUDSEntry(const QString &accountName); static KIO::UDSEntry sharedDriveToUDSEntry(const KGAPI2::Drive::DrivesPtr sharedDrive); void listAccounts(); void createAccount(); void listSharedDrivesRoot(const QUrl &url); bool createSharedDrive(const QUrl &url); bool deleteSharedDrive(const QUrl url); void statSharedDrive(const QUrl url); KIO::UDSEntry fetchSharedDrivesRootEntry(QString accountId, bool asCurrentDir = false); QString resolveFileIdFromPath(const QString &path, PathFlags flags = None); Action handleError(const KGAPI2::Job &job, const QUrl &url); KIO::UDSEntry fileToUDSEntry(const KGAPI2::Drive::FilePtr &file, const QString &path) const; QUrl fileToUrl(const KGAPI2::Drive::FilePtr &file, const QString &path) const; void fileSystemFreeSpace(const QUrl &url); KGAPI2::AccountPtr getAccount(const QString &accountName); QString rootFolderId(const QString &accountId); bool putUpdate(const QUrl &url); bool putCreate(const QUrl &url); - bool readPutData(QTemporaryFile &tmpFile); + bool readPutData(QTemporaryFile &tmpFile, KGAPI2::Drive::FilePtr &file); /** * Executes a KGAPI2::Job in an event loop, retrying the job until success or failure. * If the Job fails, SlaveBase::error() with an appropriate error message will be called. * * @return Whether @p job succeeded. */ bool runJob(KGAPI2::Job &job, const QUrl &url, const QString &accountId); std::unique_ptr m_accountManager; PathCache m_cache; QMap m_rootIds; }; #endif // KIO_GDRIVE_H