kio_gdrive.cpp 32.4 KB
Newer Older
Daniel Vrátil's avatar
Daniel Vrátil committed
1
/*
Daniel Vrátil's avatar
Daniel Vrátil committed
2
 * Copyright (C) 2013 - 2014  Daniel Vrátil <dvratil@redhat.com>
Daniel Vrátil's avatar
Daniel Vrátil committed
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 *
 * 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"
21
#include "gdrivedebug.h"
22
#include "gdrivehelper.h"
Daniel Vrátil's avatar
Daniel Vrátil committed
23

24
#include <QApplication>
Elvis Angelaccio's avatar
Elvis Angelaccio committed
25
#include <QTemporaryFile>
Daniel Vrátil's avatar
Daniel Vrátil committed
26

Elvis Angelaccio's avatar
Elvis Angelaccio committed
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <KGAPI/Account>
#include <KGAPI/AuthJob>
#include <KGAPI/Drive/About>
#include <KGAPI/Drive/AboutFetchJob>
#include <KGAPI/Drive/ChildReference>
#include <KGAPI/Drive/ChildReferenceFetchJob>
#include <KGAPI/Drive/ChildReferenceCreateJob>
#include <KGAPI/Drive/File>
#include <KGAPI/Drive/FileCopyJob>
#include <KGAPI/Drive/FileCreateJob>
#include <KGAPI/Drive/FileModifyJob>
#include <KGAPI/Drive/FileTrashJob>
#include <KGAPI/Drive/FileFetchJob>
#include <KGAPI/Drive/FileFetchContentJob>
#include <KGAPI/Drive/FileSearchQuery>
#include <KGAPI/Drive/ParentReference>
#include <KGAPI/Drive/Permission>
Elvis Angelaccio's avatar
Elvis Angelaccio committed
44
45
46
47
#include <KIO/AccessManager>
#include <KIO/Job>
#include <KLocalizedString>
#include <KWallet>
Daniel Vrátil's avatar
Daniel Vrátil committed
48

Elvis Angelaccio's avatar
Elvis Angelaccio committed
49
50
#include <QNetworkRequest>
#include <QNetworkReply>
Daniel Vrátil's avatar
Daniel Vrátil committed
51
52
53
54

using namespace KGAPI2;
using namespace Drive;

Elvis Angelaccio's avatar
Elvis Angelaccio committed
55
56
57
58
59
60
class KIOPluginForMetaData : public QObject
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.kde.kio.slave.gdrive" FILE "gdrive.json")
};

61
62
63
64
65
66
67
68
69
70
71
72
static QString joinSublist(const QStringList &strv, int start, int end, const QChar &joinChar)
{
    QString res;
    for (int i = start; i <= end; ++i) {
        res += strv[i];
        if (i < end) {
            res += joinChar;
        }
    }
    return res;
}

Daniel Vrátil's avatar
Daniel Vrátil committed
73
74
extern "C"
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
75
    int Q_DECL_EXPORT kdemain(int argc, char **argv)
Daniel Vrátil's avatar
Daniel Vrátil committed
76
    {
77
        QApplication app(argc, argv);
78
        app.setApplicationName(QStringLiteral("kio_gdrive"));
Daniel Vrátil's avatar
Daniel Vrátil committed
79
80
81
82
83
84

        if (argc != 4) {
             fprintf(stderr, "Usage: kio_gdrive protocol domain-socket1 domain-socket2\n");
             exit(-1);
        }

Daniel Vrátil's avatar
Daniel Vrátil committed
85
        KIOGDrive slave(argv[1], argv[2], argv[3]);
Daniel Vrátil's avatar
Daniel Vrátil committed
86
87
88
89
90
        slave.dispatchLoop();
        return 0;
    }
}

Daniel Vrátil's avatar
Daniel Vrátil committed
91
92
KIOGDrive::KIOGDrive(const QByteArray &protocol, const QByteArray &pool_socket,
                      const QByteArray &app_socket):
Daniel Vrátil's avatar
Daniel Vrátil committed
93
    SlaveBase("gdrive", pool_socket, app_socket)
Daniel Vrátil's avatar
Daniel Vrátil committed
94
{
Daniel Vrátil's avatar
Daniel Vrátil committed
95
    Q_UNUSED(protocol);
Daniel Vrátil's avatar
Daniel Vrátil committed
96

97
    qCDebug(GDRIVE) << "GDrive ready";
Daniel Vrátil's avatar
Daniel Vrátil committed
98
}
Daniel Vrátil's avatar
Daniel Vrátil committed
99

Daniel Vrátil's avatar
Daniel Vrátil committed
100
101
102
103
KIOGDrive::~KIOGDrive()
{
    closeConnection();
}
Daniel Vrátil's avatar
Daniel Vrátil committed
104

105
KIOGDrive::Action KIOGDrive::handleError(const KGAPI2::Job &job, const QUrl &url)
Daniel Vrátil's avatar
Daniel Vrátil committed
106
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
107
    qCDebug(GDRIVE) << "Job status code:" << job.error() << "- message:" << job.errorString();
Daniel Vrátil's avatar
Daniel Vrátil committed
108

109
    switch (job.error()) {
Daniel Vrátil's avatar
Daniel Vrátil committed
110
111
112
113
114
        case KGAPI2::OK:
        case KGAPI2::NoError:
            return Success;
        case KGAPI2::AuthCancelled:
        case KGAPI2::AuthError:
Elvis Angelaccio's avatar
Elvis Angelaccio committed
115
            error(KIO::ERR_COULD_NOT_LOGIN, url.toDisplayString());
Daniel Vrátil's avatar
Daniel Vrátil committed
116
117
            return Fail;
        case KGAPI2::Unauthorized: {
118
            const AccountPtr oldAccount = job.account();
119
            const AccountPtr account = m_accountManager.refreshAccount(oldAccount);
Daniel Vrátil's avatar
Daniel Vrátil committed
120
            if (!account) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
121
                error(KIO::ERR_COULD_NOT_LOGIN, url.toDisplayString());
Daniel Vrátil's avatar
Daniel Vrátil committed
122
123
124
125
126
                return Fail;
            }
            return Restart;
        }
        case KGAPI2::Forbidden:
Elvis Angelaccio's avatar
Elvis Angelaccio committed
127
            error(KIO::ERR_ACCESS_DENIED, url.toDisplayString());
Daniel Vrátil's avatar
Daniel Vrátil committed
128
129
            return Fail;
        case KGAPI2::NotFound:
Elvis Angelaccio's avatar
Elvis Angelaccio committed
130
            error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString());
Daniel Vrátil's avatar
Daniel Vrátil committed
131
132
            return Fail;
        case KGAPI2::NoContent:
Elvis Angelaccio's avatar
Elvis Angelaccio committed
133
            error(KIO::ERR_NO_CONTENT, url.toDisplayString());
Daniel Vrátil's avatar
Daniel Vrátil committed
134
135
            return Fail;
        case KGAPI2::QuotaExceeded:
Elvis Angelaccio's avatar
Elvis Angelaccio committed
136
            error(KIO::ERR_DISK_FULL, url.toDisplayString());
Daniel Vrátil's avatar
Daniel Vrátil committed
137
138
            return Fail;
        default:
139
            error(KIO::ERR_SLAVE_DEFINED, job.errorString());
Daniel Vrátil's avatar
Daniel Vrátil committed
140
141
142
143
144
145
            return Fail;
    }

    return Fail;
}

146
KIO::UDSEntry KIOGDrive::fileToUDSEntry(const FilePtr &origFile, const QString &path) const
Daniel Vrátil's avatar
Daniel Vrátil committed
147
148
149
150
{
    KIO::UDSEntry entry;
    bool isFolder = false;

151
152
153
154
155
    FilePtr file = origFile;
    if (GDriveHelper::isGDocsDocument(file)) {
        GDriveHelper::convertFromGDocs(file);
    }

156
    entry.insert(KIO::UDSEntry::UDS_NAME, file->title());
Daniel Vrátil's avatar
Daniel Vrátil committed
157
    entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, file->title());
158
    entry.insert(KIO::UDSEntry::UDS_COMMENT, file->description());
Daniel Vrátil's avatar
Daniel Vrátil committed
159

Daniel Vrátil's avatar
Daniel Vrátil committed
160
161
162
    if (file->isFolder()) {
        entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
        entry.insert(KIO::UDSEntry::UDS_SIZE, 0);
Daniel Vrátil's avatar
Daniel Vrátil committed
163
164
        isFolder = true;
    } else {
Daniel Vrátil's avatar
Daniel Vrátil committed
165
166
167
        entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
        entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, file->mimeType());
        entry.insert(KIO::UDSEntry::UDS_SIZE, file->fileSize());
168
        entry.insert(KIO::UDSEntry::UDS_URL, QString::fromLatin1("gdrive://%1/%2?id=%3").arg(path, origFile->title(), origFile->id()));
Daniel Vrátil's avatar
Daniel Vrátil committed
169
170
    }

Daniel Vrátil's avatar
Daniel Vrátil committed
171
172
173
    entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, file->createdDate().toTime_t());
    entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, file->modifiedDate().toTime_t());
    entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, file->lastViewedByMeDate().toTime_t());
174
175
176
    if (!file->ownerNames().isEmpty()) {
        entry.insert(KIO::UDSEntry::UDS_USER, file->ownerNames().first());
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
177
    entry.insert(KIO::UDSEntry::UDS_HIDDEN, file->labels()->hidden());
Daniel Vrátil's avatar
Daniel Vrátil committed
178

Daniel Vrátil's avatar
Daniel Vrátil committed
179
180
181
    if (!isFolder) {
        if (file->editable()) {
            entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
Daniel Vrátil's avatar
Daniel Vrátil committed
182
        } else {
Daniel Vrátil's avatar
Daniel Vrátil committed
183
            entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH);
Daniel Vrátil's avatar
Daniel Vrátil committed
184
185
        }
    } else {
Daniel Vrátil's avatar
Daniel Vrátil committed
186
        entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
Daniel Vrátil's avatar
Daniel Vrátil committed
187
188
189
190
191
    }

    return entry;
}

192
void KIOGDrive::openConnection()
Daniel Vrátil's avatar
Daniel Vrátil committed
193
{
194
    qCDebug(GDRIVE) << "Ready to talk to GDrive";
195
196
197
198
199
200
201
202
}




QStringList KIOGDrive::pathComponents(const QString &path) const
{
    return path.split(QLatin1Char('/'), QString::SkipEmptyParts);
Daniel Vrátil's avatar
Daniel Vrátil committed
203
204
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
205
QString KIOGDrive::accountFromPath(const QUrl &url) const
Daniel Vrátil's avatar
Daniel Vrátil committed
206
{
207
    const QStringList components = pathComponents(url);
Daniel Vrátil's avatar
Daniel Vrátil committed
208
209
210
211
    if (components.isEmpty()) {
        return QString();
    }
    return components[0];
Daniel Vrátil's avatar
Daniel Vrátil committed
212
}
Daniel Vrátil's avatar
Daniel Vrátil committed
213

Elvis Angelaccio's avatar
Elvis Angelaccio committed
214
bool KIOGDrive::isRoot(const QUrl &url) const
215
216
217
{
    return pathComponents(url).length() == 0;
}
Daniel Vrátil's avatar
Daniel Vrátil committed
218

219

Elvis Angelaccio's avatar
Elvis Angelaccio committed
220
bool KIOGDrive::isAccountRoot(const QUrl &url) const
Daniel Vrátil's avatar
Daniel Vrátil committed
221
{
222
223
224
225
226
227
228
    return pathComponents(url).length() == 1;
}


void KIOGDrive::createAccount()
{
    const KGAPI2::AccountPtr account = m_accountManager.account(QString());
Elvis Angelaccio's avatar
Elvis Angelaccio committed
229
    redirection(QUrl(QString::fromLatin1("gdrive:/%1").arg(account->accountName())));
230
231
232
233
234
    finished();
}

void KIOGDrive::listAccounts()
{
235
    const QStringList accounts = m_accountManager.accounts();
236
237
238
239
240
    if (accounts.isEmpty()) {
        createAccount();
        return;
    }

241
    Q_FOREACH (const QString &account, accounts) {
242
        const KIO::UDSEntry entry = AccountManager::accountToUDSEntry(account);
243
        listEntry(entry);
244
245
    }
    KIO::UDSEntry newAccountEntry;
246
    newAccountEntry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral("new-account"));
Elvis Angelaccio's avatar
Elvis Angelaccio committed
247
    newAccountEntry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18nc("login in a new google account", "New account"));
248
    newAccountEntry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
249
    newAccountEntry.insert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("list-add-user"));
250
    listEntry(newAccountEntry);
251
252
253
254
    finished();
    return;
}

255
class RecursionDepthCounter
256
{
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
public:
    RecursionDepthCounter()
    {
        ++sDepth;
    }
    ~RecursionDepthCounter()
    {
        --sDepth;
    }

    int depth() const
    {
        return sDepth;
    }

private:
    static int sDepth;
};

int RecursionDepthCounter::sDepth = 0;


QString KIOGDrive::resolveFileIdFromPath(const QString &path, PathFlags flags)
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
281
    qCDebug(GDRIVE) << Q_FUNC_INFO << path;
282
283
284
285
286
287
288
289
290
    RecursionDepthCounter recursion;
    if (recursion.depth() == 1) {
        m_cache.dump();
    }

    if (path.isEmpty()) {
        return QString();
    }

291
292
    QString fileId = m_cache.idForPath(path);
    if (!fileId.isEmpty()) {
293
        qCDebug(GDRIVE) << "Resolved" << path << "to" << fileId << "(from cache)";
294
295
296
        return fileId;
    }

297
298
299
    const QStringList components = pathComponents(path);
    Q_ASSERT(!components.isEmpty());
    if (components.size() == 1 || (components.size() == 2 && components[1] == QLatin1String("trash"))) {
300
        qCDebug(GDRIVE) << "Resolved" << path << "to \"root\"";
301
        return rootFolderId(components[0]);
302
303
    }

304
305
306
    QString parentName;
    Q_ASSERT(components.size() >= 2);
    const QString parentPath = joinSublist(components, 0, components.size() - 2, QLatin1Char('/'));
307
308
    // Try to recursively resolve ID of parent path - either from cache, or by
    // querying Google
309
310
311
312
313
    const QString parentId = resolveFileIdFromPath(parentPath, KIOGDrive::PathIsFolder);
    if (parentId.isEmpty()) {
        // We failed to resolve parent -> error
        return QString();
    }
314
315

    FileSearchQuery query;
316
317
318
319
320
321
    if (flags != KIOGDrive::None) {
        query.addQuery(FileSearchQuery::MimeType,
                       (flags & KIOGDrive::PathIsFolder ? FileSearchQuery::Equals : FileSearchQuery::NotEquals),
                       GDriveHelper::folderMimeType());
    }
    query.addQuery(FileSearchQuery::Title, FileSearchQuery::Equals, components.last());
322
    query.addQuery(FileSearchQuery::Parents, FileSearchQuery::In, parentId);
323
    query.addQuery(FileSearchQuery::Trashed, FileSearchQuery::Equals, components[1] == QLatin1String("trash"));
324

Elvis Angelaccio's avatar
Elvis Angelaccio committed
325
    const QString accountId = accountFromPath(QUrl::fromUserInput(path));
326
    FileFetchJob fetchJob(query, getAccount(accountId));
327
328
    fetchJob.setFields(FileFetchJob::Id | FileFetchJob::Title | FileFetchJob::Labels);
    QUrl url(path);
329
330
331
    if (!runJob(fetchJob, url, accountId)) {
        return QString();
    }
332
333

    const ObjectsList objects = fetchJob.items();
334
    qCDebug(GDRIVE) << objects;
335
    if (objects.count() == 0) {
336
        qCWarning(GDRIVE) << "Failed to resolve" << path;
337
338
339
340
        return QString();
    }

    const FilePtr file = objects[0].dynamicCast<File>();
341

342
343
    m_cache.insertPath(path, file->id());

344
    qCDebug(GDRIVE) << "Resolved" << path << "to" << file->id() << "(from network)";
345
    return file->id();
Daniel Vrátil's avatar
Daniel Vrátil committed
346
347
}

348
349
350
351
352
QString KIOGDrive::rootFolderId(const QString &accountId)
{
    if (!m_rootIds.contains(accountId)) {
        AboutFetchJob aboutFetch(getAccount(accountId));
        QUrl url;
353
354
355
        if (!runJob(aboutFetch, url, accountId)) {
            return QString();
        }
356
357
358

        const AboutPtr about = aboutFetch.aboutData();
        if (!about || about->rootFolderId().isEmpty()) {
359
            qCWarning(GDRIVE) << "Failed to obtain root ID";
360
361
            return QString();
        }
Daniel Vrátil's avatar
Daniel Vrátil committed
362

363
364
365
366
367
368
        m_rootIds.insert(accountId, about->rootFolderId());
        return about->rootFolderId();
    }

    return m_rootIds[accountId];
}
Daniel Vrátil's avatar
Daniel Vrátil committed
369

Elvis Angelaccio's avatar
Elvis Angelaccio committed
370
void KIOGDrive::listDir(const QUrl &url)
Daniel Vrátil's avatar
Daniel Vrátil committed
371
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
372
    qCDebug(GDRIVE) << "Going to list" << url;
Daniel Vrátil's avatar
Daniel Vrátil committed
373

374
375
376
    const QString accountId = accountFromPath(url);
    if (accountId == QLatin1String("new-account")) {
        createAccount();
377
        return;
Daniel Vrátil's avatar
Daniel Vrátil committed
378
379
    }

380
    QString folderId;
381
    const QStringList components = pathComponents(url);
382
383
384
385
    if (components.isEmpty())  {
        listAccounts();
        return;
    } else if (components.size() == 1) {
386
        folderId = rootFolderId(accountId);
387
    } else {
388
389
        folderId = m_cache.idForPath(url.path());
        if (folderId.isEmpty()) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
390
            folderId = resolveFileIdFromPath(url.adjusted(QUrl::StripTrailingSlash).path(),
391
                                             KIOGDrive::PathIsFolder);
392
        }
393
394
395
        if (folderId.isEmpty()) {
            error(KIO::ERR_DOES_NOT_EXIST, url.path());
            return;
396
397
        }
    }
398

Daniel Vrátil's avatar
Daniel Vrátil committed
399
    FileSearchQuery query;
400
401
    query.addQuery(FileSearchQuery::Trashed, FileSearchQuery::Equals, false);
    query.addQuery(FileSearchQuery::Parents, FileSearchQuery::In, folderId);
Daniel Vrátil's avatar
Daniel Vrátil committed
402
    FileFetchJob fileFetchJob(query, getAccount(accountId));
403
404
405
406
    fileFetchJob.setFields((FileFetchJob::BasicFields & ~FileFetchJob::Permissions)
                            | FileFetchJob::Labels
                            | FileFetchJob::ExportLinks
                            | FileFetchJob::LastViewedByMeDate);
407
    runJob(fileFetchJob, url, accountId);
Daniel Vrátil's avatar
Daniel Vrátil committed
408

Daniel Vrátil's avatar
Daniel Vrátil committed
409
    ObjectsList objects = fileFetchJob.items();
Daniel Vrátil's avatar
Daniel Vrátil committed
410
    Q_FOREACH (const ObjectPtr &object, objects) {
Daniel Vrátil's avatar
Daniel Vrátil committed
411
        const FilePtr file = object.dynamicCast<File>();
Daniel Vrátil's avatar
Daniel Vrátil committed
412

Elvis Angelaccio's avatar
Elvis Angelaccio committed
413
        const KIO::UDSEntry entry = fileToUDSEntry(file, url.adjusted(QUrl::StripTrailingSlash).path());
414
        listEntry(entry);
415

416
        const QString path = url.path().endsWith(QLatin1Char('/')) ? url.path() : url.path() + QLatin1Char('/');
Elvis Angelaccio's avatar
Elvis Angelaccio committed
417
        m_cache.insertPath(path + file->title(), file->id());
Daniel Vrátil's avatar
Daniel Vrátil committed
418
419
420
421
422
    }

    finished();
}

423

Elvis Angelaccio's avatar
Elvis Angelaccio committed
424
void KIOGDrive::mkdir(const QUrl &url, int permissions)
425
{
Daniel Vrátil's avatar
Daniel Vrátil committed
426
427
428
429
430
    // 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);

Elvis Angelaccio's avatar
Elvis Angelaccio committed
431
    qCDebug(GDRIVE) << "Creating directory" << url;
432

433
    const QString accountId = accountFromPath(url);
434
435
436
437
438
439
440
    const QStringList components = pathComponents(url);
    QString parentId;
    // At least account and new folder name
    if (components.size() < 2) {
        error(KIO::ERR_DOES_NOT_EXIST, url.path());
        return;
    } else if (components.size() == 2) {
441
        parentId = rootFolderId(accountId);
442
443
444
445
446
447
448
449
450
451
452
    } else {
        const QString subpath = joinSublist(components, 0, components.size() - 2, QLatin1Char('/'));
        parentId = resolveFileIdFromPath(subpath, KIOGDrive::PathIsFolder);
    }

    if (parentId.isEmpty()) {
        error(KIO::ERR_DOES_NOT_EXIST, url.path());
        return;
    }

    const QString folderName = components.last();
453

Daniel Vrátil's avatar
Daniel Vrátil committed
454
455
456
    FilePtr file(new File());
    file->setTitle(folderName);
    file->setMimeType(File::folderMimeType());
457

Daniel Vrátil's avatar
Daniel Vrátil committed
458
459
    ParentReferencePtr parent(new ParentReference(parentId));
    file->setParents(ParentReferencesList() << parent);
460

Daniel Vrátil's avatar
Daniel Vrátil committed
461
    FileCreateJob createJob(file, getAccount(accountId));
462
    runJob(createJob, url, accountId);
463
464
465
466

    finished();
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
467
void KIOGDrive::stat(const QUrl &url)
Daniel Vrátil's avatar
Daniel Vrátil committed
468
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
469
    qCDebug(GDRIVE) << "Going to stat()" << url;
Daniel Vrátil's avatar
Daniel Vrátil committed
470

Daniel Vrátil's avatar
Daniel Vrátil committed
471
    const QString accountId = accountFromPath(url);
472
473
474
475
476
477
    const QStringList components = pathComponents(url);
    if (components.isEmpty()) {
        // TODO Can we stat() root?
        finished();
        return;
    } else if (components.size() == 1) {
478
479
        const KIO::UDSEntry entry = AccountManager::accountToUDSEntry(accountId);
        statEntry(entry);
Daniel Vrátil's avatar
Daniel Vrátil committed
480
481
        finished();
        return;
482
483
    }

484
485
    const QString fileId
        = url.hasQueryItem(QLatin1String("id"))
Elvis Angelaccio's avatar
Elvis Angelaccio committed
486
487
            ? QUrlQuery(url).queryItemValue(QLatin1String("id"))
            : resolveFileIdFromPath(url.adjusted(QUrl::StripTrailingSlash).path(),
488
                                    KIOGDrive::None);
489
490
491
    if (fileId.isEmpty()) {
        error(KIO::ERR_DOES_NOT_EXIST, url.path());
        return;
Daniel Vrátil's avatar
Daniel Vrátil committed
492
493
494
    }

    FileFetchJob fileFetchJob(fileId, getAccount(accountId));
495
    runJob(fileFetchJob, url, accountId);
Daniel Vrátil's avatar
Daniel Vrátil committed
496

Daniel Vrátil's avatar
Daniel Vrátil committed
497
    const ObjectsList objects = fileFetchJob.items();
498
    if (objects.count() != 1) {
499
        error(KIO::ERR_DOES_NOT_EXIST, url.path());
500
501
        return;
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
502
503

    const FilePtr file = objects.first().dynamicCast<File>();
504
505
506
507
508
509
    if (file->labels()->trashed()) {
        error(KIO::ERR_DOES_NOT_EXIST, url.path());
        return;
    }

    const KIO::UDSEntry entry = fileToUDSEntry(file, joinSublist(components, 0, components.size() - 2, QLatin1Char('/')));
Daniel Vrátil's avatar
Daniel Vrátil committed
510

Daniel Vrátil's avatar
Daniel Vrátil committed
511
    statEntry(entry);
Daniel Vrátil's avatar
Daniel Vrátil committed
512
    finished();
Daniel Vrátil's avatar
Daniel Vrátil committed
513
}
Daniel Vrátil's avatar
Daniel Vrátil committed
514

Elvis Angelaccio's avatar
Elvis Angelaccio committed
515
void KIOGDrive::get(const QUrl &url)
Daniel Vrátil's avatar
Daniel Vrátil committed
516
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
517
    qCDebug(GDRIVE) << "Fetching content of" << url;
Daniel Vrátil's avatar
Daniel Vrátil committed
518

Daniel Vrátil's avatar
Daniel Vrátil committed
519
    const QString accountId = accountFromPath(url);
520
    const QStringList components = pathComponents(url);
521

522
523
524
525
526
527
528
529
530
    if (components.isEmpty()) {
        error(KIO::ERR_DOES_NOT_EXIST, url.path());
        return;
    } else if (components.size() == 1) {
        // You cannot GET an account folder!
        error(KIO::ERR_ACCESS_DENIED, url.path());
        return;
    }

531
532
    const QString fileId =
        url.hasQueryItem(QLatin1String("id"))
Elvis Angelaccio's avatar
Elvis Angelaccio committed
533
534
            ? QUrlQuery(url).queryItemValue(QLatin1String("id"))
            : resolveFileIdFromPath(url.adjusted(QUrl::StripTrailingSlash).path(),
535
                                    KIOGDrive::PathIsFile);
536
537
538
539
    if (fileId.isEmpty()) {
        error(KIO::ERR_DOES_NOT_EXIST, url.path());
        return;
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
540
541

    FileFetchJob fileFetchJob(fileId, getAccount(accountId));
542
543
544
545
    fileFetchJob.setFields(FileFetchJob::Id
                            | FileFetchJob::MimeType
                            | FileFetchJob::ExportLinks
                            | FileFetchJob::DownloadUrl);
546
    runJob(fileFetchJob, url, accountId);
Daniel Vrátil's avatar
Daniel Vrátil committed
547

Daniel Vrátil's avatar
Daniel Vrátil committed
548
    const ObjectsList objects = fileFetchJob.items();
549
550
551
552
    if (objects.count() != 1) {
        error(KIO::ERR_DOES_NOT_EXIST, url.fileName());
        return;
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
553

554
555
556
557
558
559
560
    FilePtr file = objects.first().dynamicCast<File>();
    QUrl downloadUrl;
    if (GDriveHelper::isGDocsDocument(file)) {
        downloadUrl = GDriveHelper::convertFromGDocs(file);
    } else {
        downloadUrl = file->downloadUrl();
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
561

Daniel Vrátil's avatar
Daniel Vrátil committed
562
    mimeType(file->mimeType());
Daniel Vrátil's avatar
Daniel Vrátil committed
563

564
    FileFetchContentJob contentJob(downloadUrl, getAccount(accountId));
565
    runJob(contentJob, url, accountId);
Daniel Vrátil's avatar
Daniel Vrátil committed
566

Daniel Vrátil's avatar
Daniel Vrátil committed
567
    data(contentJob.data());
Daniel Vrátil's avatar
Daniel Vrátil committed
568
569
    finished();
}
Daniel Vrátil's avatar
Daniel Vrátil committed
570

Elvis Angelaccio's avatar
Elvis Angelaccio committed
571
bool KIOGDrive::readPutData(QTemporaryFile &tempFile)
Daniel Vrátil's avatar
Daniel Vrátil committed
572
{
Daniel Vrátil's avatar
Daniel Vrátil committed
573
574
575
576
577
578
579
580
581
582
    // 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_COULD_NOT_WRITE, tempFile.fileName());
583
        return false;
Daniel Vrátil's avatar
Daniel Vrátil committed
584
585
586
587
588
589
590
    }

    int result;
    do {
        QByteArray buffer;
        dataReq();
        result = readData(buffer);
591
592
593
594
        if (!buffer.isEmpty()) {
            qint64 size = tempFile.write(buffer);
            if (size != buffer.size()) {
                error(KIO::ERR_COULD_NOT_WRITE, tempFile.fileName());
595
                return false;
596
            }
Daniel Vrátil's avatar
Daniel Vrátil committed
597
598
        }
    } while (result > 0);
599
    tempFile.close();
Daniel Vrátil's avatar
Daniel Vrátil committed
600
601

    if (result == -1) {
602
        qCWarning(GDRIVE) << "Could not read source file" << tempFile.fileName();
603
604
605
606
607
608
609
        error(KIO::ERR_COULD_NOT_READ, QString());
        return false;
    }

    return true;
}

610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
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" << 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;
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
632
bool KIOGDrive::putUpdate(const QUrl &url, const QString &accountId, const QStringList &pathComponents)
633
{
634
    const QString fileId = QUrlQuery(url).queryItemValue(QStringLiteral("id"));
Elvis Angelaccio's avatar
Elvis Angelaccio committed
635
    qCDebug(GDRIVE) << Q_FUNC_INFO << url << fileId;
636
637

    FileFetchJob fetchJob(fileId, getAccount(accountId));
638
639
640
    if (!runJob(fetchJob, url, accountId)) {
        return false;
    }
641
642
643
644
645
646
647
648

    const ObjectsList objects = fetchJob.items();
    if (objects.size() != 1) {
        putCreate(url, accountId, pathComponents);
        return false;
    }

    const FilePtr file = objects[0].dynamicCast<File>();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
649
    QTemporaryFile tmpFile;
650
    if (!readPutData(tmpFile)) {
651
        error(KIO::ERR_COULD_NOT_READ, url.path());
652
        return false;
Daniel Vrátil's avatar
Daniel Vrátil committed
653
654
    }

655
656
    FileModifyJob modifyJob(tmpFile.fileName(), file, getAccount(accountId));
    modifyJob.setUpdateModifiedDate(true);
657
658
659
    if (!runJob(modifyJob, url, accountId)) {
        return false;
    }
660
661
662
663

    return true;
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
664
bool KIOGDrive::putCreate(const QUrl &url, const QString &accountId, const QStringList &components)
665
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
666
    qCDebug(GDRIVE) << Q_FUNC_INFO << url;
667
668
669
670
671
672
673
674
675
    ParentReferencesList parentReferences;
    if (components.size() < 2) {
        error(KIO::ERR_ACCESS_DENIED, url.path());
        return false;
    } else if (components.length() == 2) {
        // Creating in root directory
    } else {
        const QString parentId = resolveFileIdFromPath(joinSublist(components, 0, components.size() - 2, QLatin1Char('/')));
        if (parentId.isEmpty()) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
676
            error(KIO::ERR_DOES_NOT_EXIST, url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path());
677
678
679
680
681
682
683
684
685
686
687
            return false;
        }
        parentReferences << ParentReferencePtr(new ParentReference(parentId));
    }

    FilePtr file(new File);
    file->setTitle(components.last());
    file->setParents(parentReferences);
    /*
    if (hasMetaData(QLatin1String("modified"))) {
        const QString modified = metaData(QLatin1String("modified"));
688
        qCDebug(GDRIVE) << modified;
689
690
691
692
        file->setModifiedDate(KDateTime::fromString(modified, KDateTime::ISODate));
    }
    */

Elvis Angelaccio's avatar
Elvis Angelaccio committed
693
    QTemporaryFile tmpFile;
694
695
696
697
698
699
    if (!readPutData(tmpFile)) {
        error(KIO::ERR_COULD_NOT_READ, url.path());
        return false;
    }

    FileCreateJob createJob(tmpFile.fileName(), file, getAccount(accountId));
700
701
702
    if (!runJob(createJob, url, accountId)) {
        return false;
    }
703
704
705
706
707

    return true;
}


Elvis Angelaccio's avatar
Elvis Angelaccio committed
708
void KIOGDrive::put(const QUrl &url, int permissions, KIO::JobFlags flags)
709
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
710
711
712
713
714
715
716
    // 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;
717
718
719
720

    const QString accountId = accountFromPath(url);
    const QStringList components = pathComponents(url);

721
    if (url.hasQueryItem(QStringLiteral("id"))) {
722
723
724
        if (!putUpdate(url, accountId, components)) {
            return;
        }
725
    } else {
726
727
728
        if (!putCreate(url, accountId, components)) {
            return;
        }
729
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
730

731
732
    // FIXME: Update the cache now!

Daniel Vrátil's avatar
Daniel Vrátil committed
733
    finished();
734
}
Daniel Vrátil's avatar
Daniel Vrátil committed
735
736


Elvis Angelaccio's avatar
Elvis Angelaccio committed
737
void KIOGDrive::copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags)
738
{
Daniel Vrátil's avatar
Daniel Vrátil committed
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
    // 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 QString sourceAccountId = accountFromPath(src);
    const QString destAccountId = accountFromPath(dest);

    // TODO: Does this actually happen, or does KIO treat our account name as host?
    if (sourceAccountId != destAccountId) {
        // KIO will fallback to get+post
757
        error(KIO::ERR_UNSUPPORTED_ACTION, src.path());
Daniel Vrátil's avatar
Daniel Vrátil committed
758
759
760
        return;
    }

761
762
763
764
765
766
767
768
769
    const QStringList srcPathComps = pathComponents(src);
    if (srcPathComps.isEmpty()) {
        error(KIO::ERR_DOES_NOT_EXIST, src.path());
        return;
    } else if (srcPathComps.size() == 1) {
        error(KIO::ERR_ACCESS_DENIED, src.path());
        return;
    }

770
771
    const QString sourceFileId
        = src.hasQueryItem(QLatin1String("id"))
772
              ? QUrlQuery(src).queryItemValue(QStringLiteral("id"))
Elvis Angelaccio's avatar
Elvis Angelaccio committed
773
              : resolveFileIdFromPath(src.adjusted(QUrl::StripTrailingSlash).path());
774
775
776
777
    if (sourceFileId.isEmpty()) {
        error(KIO::ERR_DOES_NOT_EXIST, src.path());
        return;
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
778
779
780
    FileFetchJob sourceFileFetchJob(sourceFileId, getAccount(sourceAccountId));
    sourceFileFetchJob.setFields(FileFetchJob::Id | FileFetchJob::ModifiedDate |
                                 FileFetchJob::LastViewedByMeDate | FileFetchJob::Description);
781
    runJob(sourceFileFetchJob, src, sourceAccountId);
Daniel Vrátil's avatar
Daniel Vrátil committed
782
783
784

    const ObjectsList objects = sourceFileFetchJob.items();
    if (objects.count() != 1) {
785
        error(KIO::ERR_DOES_NOT_EXIST, src.path());
Daniel Vrátil's avatar
Daniel Vrátil committed
786
787
788
789
790
        return;
    }

    const FilePtr sourceFile = objects[0].dynamicCast<File>();

791
    const QStringList destPathComps = pathComponents(dest);
Daniel Vrátil's avatar
Daniel Vrátil committed
792
    ParentReferencesList destParentReferences;
793
794
    if (destPathComps.isEmpty()) {
        error(KIO::ERR_ACCESS_DENIED, dest.path());
Daniel Vrátil's avatar
Daniel Vrátil committed
795
        return;
796
797
798
799
    } else if (destPathComps.size() == 1) {
        // copy to root
    } else {
        const QString destDirId = destPathComps[destPathComps.count() - 2];
Daniel Vrátil's avatar
Daniel Vrátil committed
800
801
        destParentReferences << ParentReferencePtr(new ParentReference(destDirId));
    }
802
    const QString destFileName = destPathComps.last();
Daniel Vrátil's avatar
Daniel Vrátil committed
803
804
805
806
807
808
809
810
811

    FilePtr destFile(new File);
    destFile->setTitle(destFileName);
    destFile->setModifiedDate(sourceFile->modifiedDate());
    destFile->setLastViewedByMeDate(sourceFile->lastViewedByMeDate());
    destFile->setDescription(sourceFile->description());
    destFile->setParents(destParentReferences);

    FileCopyJob copyJob(sourceFile, destFile, getAccount(sourceAccountId));
812
    runJob(copyJob, dest, sourceAccountId);
Daniel Vrátil's avatar
Daniel Vrátil committed
813
814

    finished();
815
}
Daniel Vrátil's avatar
Daniel Vrátil committed
816

Elvis Angelaccio's avatar
Elvis Angelaccio committed
817
void KIOGDrive::del(const QUrl &url, bool isfile)
818
{
Daniel Vrátil's avatar
Daniel Vrátil committed
819
820
821
822
823
824
825
826
827
828
829
830
    // 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.

Elvis Angelaccio's avatar
Elvis Angelaccio committed
831
    qCDebug(GDRIVE) << "Deleting URL" << url << "- is it a file?" << isfile;
Daniel Vrátil's avatar
Daniel Vrátil committed
832

833
834
    const QString fileId
        = isfile && url.hasQueryItem(QLatin1String("id"))
Elvis Angelaccio's avatar
Elvis Angelaccio committed
835
836
            ? QUrlQuery(url).queryItemValue(QLatin1String("id"))
            : resolveFileIdFromPath(url.adjusted(QUrl::StripTrailingSlash).path(),
837
                                    isfile ? KIOGDrive::PathIsFile : KIOGDrive::PathIsFolder);
838
839
840
841
    if (fileId.isEmpty()) {
        error(KIO::ERR_DOES_NOT_EXIST, url.path());
        return;
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
842
    const QString accountId = accountFromPath(url);
843
    const QStringList components = pathComponents(url);
Daniel Vrátil's avatar
Daniel Vrátil committed
844

845
    // If user tries to delete the account folder, remove the account from KWallet
846
    if (components.count() == 1) {
847
848
849
850
851
852
853
854
855
856
        const KGAPI2::AccountPtr account = m_accountManager.account(accountId);
        if (!account) {
            error(KIO::ERR_DOES_NOT_EXIST, accountId);
            return;
        }
        m_accountManager.removeAccount(accountId);
        finished();
        return;
    }

Daniel Vrátil's avatar
Daniel Vrátil committed
857
858
859
860
861
    // 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));
862
        runJob(referencesFetch, url, accountId);
Daniel Vrátil's avatar
Daniel Vrátil committed
863
864
865
        const bool isEmpty = !referencesFetch.items().count();

        if (!isEmpty && metaData("recurse") != QLatin1String("true")) {
866
            error(KIO::ERR_COULD_NOT_RMDIR, url.path());
Daniel Vrátil's avatar
Daniel Vrátil committed
867
868
869
870
871
            return;
        }
    }

    FileTrashJob trashJob(fileId, getAccount(accountId));
872
    runJob(trashJob, url, accountId);
Daniel Vrátil's avatar
Daniel Vrátil committed
873

874
875
    m_cache.removePath(url.path());

Daniel Vrátil's avatar
Daniel Vrátil committed
876
877
    finished();

878
}
Daniel Vrátil's avatar
Daniel Vrátil committed
879

Elvis Angelaccio's avatar
Elvis Angelaccio committed
880
void KIOGDrive::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags)
881
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
882
883
    Q_UNUSED(flags)
    qCDebug(GDRIVE) << "Renaming" << src << "to" << dest;
Daniel Vrátil's avatar
Daniel Vrátil committed
884

Daniel Vrátil's avatar
Daniel Vrátil committed
885
886
887
888
889
    const QString sourceAccountId = accountFromPath(src);
    const QString destAccountId = accountFromPath(dest);

    // TODO: Does this actually happen, or does KIO treat our account name as host?
    if (sourceAccountId != destAccountId) {
890
891
892
893
894
895
896
897
898
899
900
901
        error(KIO::ERR_UNSUPPORTED_ACTION, src.path());
        return;
    }

    const QStringList srcPathComps = pathComponents(src);
    if (srcPathComps.isEmpty()) {
        error(KIO::ERR_DOES_NOT_EXIST, dest.path());
        return;
    } else if (srcPathComps.size() == 1) {
        error(KIO::ERR_ACCESS_DENIED, dest.path());
        return;
    }
902
903
    const QString sourceFileId
        = src.hasQueryItem(QLatin1String("id"))
Elvis Angelaccio's avatar
Elvis Angelaccio committed
904
905
            ? QUrlQuery(src).queryItemValue(QLatin1String("id"))
            : resolveFileIdFromPath(src.adjusted(QUrl::StripTrailingSlash).path(),
906
                                    KIOGDrive::PathIsFile);
907
908
    if (sourceFileId.isEmpty()) {
        error(KIO::ERR_DOES_NOT_EXIST, src.path());
Daniel Vrátil's avatar
Daniel Vrátil committed
909
910
911
912
913
        return;
    }

    // We need to fetch ALL, so that we can do update later
    FileFetchJob sourceFileFetchJob(sourceFileId, getAccount(sourceAccountId));
914
    runJob(sourceFileFetchJob, src, sourceAccountId);
Daniel Vrátil's avatar
Daniel Vrátil committed
915
916
917

    const ObjectsList objects = sourceFileFetchJob.items();
    if (objects.count() != 1) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
918
        qCDebug(GDRIVE) << "FileFetchJob retrieved" << objects.count() << "items, while only one was expected.";
919
        error(KIO::ERR_DOES_NOT_EXIST, src.path());
Daniel Vrátil's avatar
Daniel Vrátil committed
920
921
        return;
    }
922

Daniel Vrátil's avatar
Daniel Vrátil committed
923
924
925
    const FilePtr sourceFile = objects[0].dynamicCast<File>();

    ParentReferencesList parentReferences = sourceFile->parents();
926
927
    const QStringList destPathComps = pathComponents(dest);
    if (destPathComps.isEmpty()) {
Daniel Vrátil's avatar
Daniel Vrátil committed
928
929
930
        // paths.size == 0 -> error, user is trying to move to top-level gdrive:///
        error(KIO::ERR_ACCESS_DENIED, dest.fileName());
        return;
931
    } else if (destPathComps.size() == 1) {
Daniel Vrátil's avatar
Daniel Vrátil committed
932
        // user is trying to move to root -> we are only renaming
933
    } else {
934
         // skip filename and extract the second-to-last component
935
936
937
938
        const QString destDirId = resolveFileIdFromPath(joinSublist(destPathComps, 0, destPathComps.count() - 2, QLatin1Char('/')),
                                                        KIOGDrive::PathIsFolder);
        const QString srcDirId = resolveFileIdFromPath(joinSublist(srcPathComps, 0, srcPathComps.count() - 2, QLatin1Char('/')),
                                                       KIOGDrive::PathIsFolder);
Daniel Vrátil's avatar
Daniel Vrátil committed
939
940
941
942
943
944
945
946
947
948
949
950
951
952

        // 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) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
953
            qCDebug(GDRIVE) << "Could not remove" << src << "from parent references.";
954
            error(KIO::ERR_DOES_NOT_EXIST, src.path());
Daniel Vrátil's avatar
Daniel Vrátil committed
955
956
957
958
959
960
961
            return;
        }

        // Add destination to parent references
        parentReferences << ParentReferencePtr(new ParentReference(destDirId));
    }

962
963
    const QString destFileName = destPathComps.last();

Daniel Vrátil's avatar
Daniel Vrátil committed
964
965
966
967
968
969
    FilePtr destFile(sourceFile);
    destFile->setTitle(destFileName);
    destFile->setParents(parentReferences);

    FileModifyJob modifyJob(destFile, getAccount(sourceAccountId));
    modifyJob.setUpdateModifiedDate(true);
970
    runJob(modifyJob, dest, sourceAccountId);
Daniel Vrátil's avatar
Daniel Vrátil committed
971
972
973

    finished();
}
974

Elvis Angelaccio's avatar
Elvis Angelaccio committed
975
void KIOGDrive::mimetype(const QUrl &url)
976
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
977
    qCDebug(GDRIVE) << Q_FUNC_INFO << url;
Daniel Vrátil's avatar
Daniel Vrátil committed
978

979
980
    const QString fileId
        = url.hasQueryItem(QLatin1String("id"))
Elvis Angelaccio's avatar
Elvis Angelaccio committed
981
982
            ? QUrlQuery(url).queryItemValue(QLatin1String("id"))
            : resolveFileIdFromPath(url.adjusted(QUrl::StripTrailingSlash).path());
983
984
985
986
    if (fileId.isEmpty()) {
        error(KIO::ERR_DOES_NOT_EXIST, url.path());
        return;
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
987
988
989
990
    const QString accountId = accountFromPath(url);

    FileFetchJob fileFetchJob(fileId, getAccount(accountId));
    fileFetchJob.setFields(FileFetchJob::Id | FileFetchJob::MimeType);
991
    runJob(fileFetchJob, url, accountId);
Daniel Vrátil's avatar
Daniel Vrátil committed
992
993
994

    const ObjectsList objects = fileFetchJob.items();
    if (objects.count() != 1) {
995
        error(KIO::ERR_DOES_NOT_EXIST, url.path());
Daniel Vrátil's avatar
Daniel Vrátil committed
996
997
998
999
1000
1001
        return;
    }

    const FilePtr file = objects.first().dynamicCast<File>();
    mimeType(file->mimeType());
    finished();
Daniel Vrátil's avatar
Daniel Vrátil committed
1002
}
Elvis Angelaccio's avatar
Elvis Angelaccio committed
1003
1004

#include "kio_gdrive.moc"