kioobexftp.cpp 15.1 KB
Newer Older
1
2
3
/*************************************************************************************
 *  Copyright (C) 2010-2012 by Alejandro Fiestas Olivares <afiestas@kde.org>         *
 *  Copyright (C) 2010 UFO Coders <info@ufocoders.com>                               *
4
 *  Copyright (C) 2014-2015 David Rosca <nowrep@gmail.com>                           *
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 *                                                                                   *
 *  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   *
 *************************************************************************************/
20

David Rosca's avatar
David Rosca committed
21
#include "kioobexftp.h"
22
#include "kdedobexftp.h"
23
#include "version.h"
24
#include "transferfilejob.h"
25
#include "debug_p.h"
26

David Rosca's avatar
David Rosca committed
27
28
#include <unistd.h>

29
30
31
32
#include <QMimeData>
#include <QTemporaryFile>
#include <QCoreApplication>
#include <QMimeDatabase>
33

34
#include <KLocalizedString>
35

David Rosca's avatar
David Rosca committed
36
37
#include <BluezQt/PendingCall>
#include <BluezQt/ObexTransfer>
38

39
extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv)
40
{
41
    QCoreApplication app(argc, argv);
42

43
    if (argc != 4) {
Rafael Fernández López's avatar
Rafael Fernández López committed
44
        fprintf(stderr, "Usage: kio_obexftp protocol domain-socket1 domain-socket2\n");
45
46
47
        exit(-1);
    }

Rafael Fernández López's avatar
Rafael Fernández López committed
48
    KioFtp slave(argv[2], argv[3]);
49
50
51
52
    slave.dispatchLoop();
    return 0;
}

David Rosca's avatar
David Rosca committed
53
54
55
56
57
58
59
static QString urlDirectory(const QUrl &url)
{
    const QUrl &u = url.adjusted(QUrl::StripTrailingSlash);
    return u.adjusted(QUrl::RemoveFilename).path();
}

static QString urlFileName(const QUrl &url)
60
{
David Rosca's avatar
David Rosca committed
61
62
63
64
    const QUrl &u = url.adjusted(QUrl::StripTrailingSlash);
    return u.fileName();
}

65
66
67
68
69
70
static QUrl urlUpDir(const QUrl &url)
{
    const QUrl &u = url.adjusted(QUrl::StripTrailingSlash);
    return u.adjusted(QUrl::RemoveFilename);
}

David Rosca's avatar
David Rosca committed
71
72
73
74
static bool urlIsRoot(const QUrl &url)
{
    const QString &directory = urlDirectory(url);
    return (directory.isEmpty() || directory == QLatin1String("/")) && urlFileName(url).isEmpty();
75
76
}

77
KioFtp::KioFtp(const QByteArray &pool, const QByteArray &app)
78
    : SlaveBase(QByteArrayLiteral("obexftp"), pool, app)
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
79
    , m_transfer(nullptr)
Rafael Fernández López's avatar
Rafael Fernández López committed
80
{
81
82
    m_kded = new org::kde::BlueDevil::ObexFtp(QStringLiteral("org.kde.kded5"), QStringLiteral("/modules/bluedevil"),
                                              QDBusConnection::sessionBus(), this);
83
84
}

85
86
void KioFtp::connectToHost()
{
87
88
89
90
    const QString &target = m_kded->preferredTarget(m_host);

    if (target != QLatin1String("ftp")) {
        if (createSession(target)) {
91
92
93
94
95
            return;
        }
        // Fallback to ftp
    }

96
    createSession(QStringLiteral("ftp"));
97
98
99
100
101
102
103
104
105
106
107
108
}

bool KioFtp::testConnection()
{
    if (!m_kded->isOnline().value()) {
        error(KIO::ERR_SLAVE_DEFINED, i18n("Obexd service is not running."));
        return false;
    }

    connectToHost();

    if (!m_transfer) {
109
        error(KIO::ERR_CANNOT_CONNECT, m_host);
110
111
112
113
114
115
116
117
        return false;
    }
    return true;
}

bool KioFtp::createSession(const QString &target)
{
    QDBusPendingReply<QString> reply = m_kded->session(m_host, target);
118
119
120
121
122
    reply.waitForFinished();

    const QString &sessionPath = reply.value();

    if (reply.isError() || sessionPath.isEmpty()) {
123
        qCDebug(OBEXFTP) << "Create session error" << reply.error().name() << reply.error().message();
124
        delete m_transfer;
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
125
        m_transfer = nullptr;
126
        m_sessionPath.clear();
127
        return false;
128
129
130
131
132
    }

    if (m_sessionPath != sessionPath) {
        m_statMap.clear();
        delete m_transfer;
David Rosca's avatar
David Rosca committed
133
        m_transfer = new BluezQt::ObexFileTransfer(QDBusObjectPath(sessionPath));
134
135
136
137
138
139
        m_sessionPath = sessionPath;
    }

    return true;
}

David Rosca's avatar
David Rosca committed
140
void KioFtp::listDir(const QUrl &url)
141
{
142
143
144
145
    if (!testConnection()) {
        return;
    }

146
    qCDebug(OBEXFTP) << "listdir: " << url;
147

Rafael Fernández López's avatar
Rafael Fernández López committed
148
149
    infoMessage(i18n("Retrieving information from remote device..."));

David Rosca's avatar
David Rosca committed
150
    qCDebug(OBEXFTP) << "Asking for listFolder" << url.path();
151

152
    if (!changeFolder(url.path())) {
153
154
        return;
    }
Àlex Fiestas's avatar
Àlex Fiestas committed
155

156
157
158
159
160
    bool ok;
    const QList<KIO::UDSEntry> &list = listFolder(url, &ok);
    if (!ok) {
        return;
    }
Àlex Fiestas's avatar
Àlex Fiestas committed
161

162
    Q_FOREACH (const KIO::UDSEntry &entry, list) {
163
        listEntry(entry);
164
    }
165

166
    finished();
167
168
}

David Rosca's avatar
David Rosca committed
169
void KioFtp::copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags)
170
{
Alex Fiestas's avatar
Alex Fiestas committed
171
172
173
    Q_UNUSED(permissions)
    Q_UNUSED(flags)

174
175
176
177
    if (!testConnection()) {
        return;
    }

178
    qCDebug(OBEXFTP) << "copy: " << src.url() << " to " << dest.url();
179

180
    copyHelper(src, dest);
David Rosca's avatar
David Rosca committed
181

182
    finished();
183
184
}

185
void KioFtp::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags)
Alex Fiestas's avatar
Ups :)    
Alex Fiestas committed
186
{
Alex Fiestas's avatar
Alex Fiestas committed
187
188
189
190
    Q_UNUSED(src)
    Q_UNUSED(dest)
    Q_UNUSED(flags)

191
    error(KIO::ERR_UNSUPPORTED_ACTION, QString());
Alex Fiestas's avatar
Ups :)    
Alex Fiestas committed
192
193
}

194
void KioFtp::get(const QUrl &url)
195
{
196
197
198
199
    if (!testConnection()) {
        return;
    }

200
201
    qCDebug(OBEXFTP) << "get" << url;

202
    QTemporaryFile tempFile(QStringLiteral("%1/kioftp_XXXXXX.%2").arg(QDir::tempPath(), urlFileName(url)));
David Rosca's avatar
David Rosca committed
203
    tempFile.open();
204

205
    copyHelper(url, QUrl::fromLocalFile(tempFile.fileName()));
206

207
208
209
210
    QMimeDatabase mimeDatabase;
    const QMimeType &mime = mimeDatabase.mimeTypeForFile(tempFile.fileName());
    mimeType(mime.name());
    qCDebug(OBEXFTP) << "Mime: " << mime.name();
211
212
213
214
215
216

    totalSize(tempFile.size());
    data(tempFile.readAll());
    finished();
}

217
218
219
220
221
bool KioFtp::cancelTransfer(const QString &transfer)
{
    return m_kded->cancelTransfer(transfer);
}

Rafael Fernández López's avatar
Rafael Fernández López committed
222
void KioFtp::setHost(const QString &host, quint16 port, const QString &user, const QString &pass)
223
{
Alex Fiestas's avatar
Alex Fiestas committed
224
225
226
227
    Q_UNUSED(port)
    Q_UNUSED(user)
    Q_UNUSED(pass)

228
    m_host = host;
229
    m_host = m_host.replace(QLatin1Char('-'), QLatin1Char(':')).toUpper();
230

231
    infoMessage(i18n("Connecting to the device"));
232

233
    connectToHost();
234
235
}

236
void KioFtp::del(const QUrl &url, bool isfile)
237
{
Alex Fiestas's avatar
Alex Fiestas committed
238
239
    Q_UNUSED(isfile)

240
241
242
    if (!testConnection()) {
        return;
    }
David Rosca's avatar
David Rosca committed
243
244
245

    qCDebug(OBEXFTP) << "Del: " << url.url();

David Rosca's avatar
David Rosca committed
246
    if (!changeFolder(urlDirectory(url))) {
247
248
        return;
    }
David Rosca's avatar
David Rosca committed
249

David Rosca's avatar
David Rosca committed
250
    if (!deleteFile(urlFileName(url))) {
251
252
253
        return;
    }

Àlex Fiestas's avatar
Àlex Fiestas committed
254
    finished();
255
256
}

257
void KioFtp::mkdir(const QUrl &url, int permissions)
258
{
Alex Fiestas's avatar
Alex Fiestas committed
259
260
    Q_UNUSED(permissions)

261
262
263
264
    if (!testConnection()) {
        return;
    }

265
    qCDebug(OBEXFTP) << "MkDir: " << url.url();
266

David Rosca's avatar
David Rosca committed
267
    if (!changeFolder(urlDirectory(url))) {
268
269
        return;
    }
David Rosca's avatar
David Rosca committed
270

David Rosca's avatar
David Rosca committed
271
    if (!createFolder(urlFileName(url))) {
272
273
274
        return;
    }

Àlex Fiestas's avatar
Àlex Fiestas committed
275
    finished();
276
277
}

David Rosca's avatar
David Rosca committed
278
void KioFtp::stat(const QUrl &url)
279
{
280
281
282
283
    if (!testConnection()) {
        return;
    }

284
285
286
287
    qCDebug(OBEXFTP) << "Stat: " << url.url();
    qCDebug(OBEXFTP) << "Stat Dir: " << urlDirectory(url);
    qCDebug(OBEXFTP) << "Stat File: " << urlFileName(url);
    qCDebug(OBEXFTP) << "Empty Dir: " << urlDirectory(url).isEmpty();
288
289
290

    statHelper(url);

291
    qCDebug(OBEXFTP) << "Finished";
292
    finished();
293
}
294

295
void KioFtp::copyHelper(const QUrl &src, const QUrl &dest)
296
{
297
    if (src.scheme() == QLatin1String("obexftp") && dest.scheme() == QLatin1String("obexftp")) {
298
        copyWithinObexftp(src, dest);
299
300
301
        return;
    }

302
    if (src.scheme() == QLatin1String("obexftp")) {
303
304
305
        copyFromObexftp(src, dest);
        return;
    }
306

307
    if (dest.scheme() == QLatin1String("obexftp")) {
308
309
310
        copyToObexftp(src, dest);
        return;
    }
311

312
    qCDebug(OBEXFTP) << "This shouldn't happen...";
313
314
}

David Rosca's avatar
David Rosca committed
315
void KioFtp::copyWithinObexftp(const QUrl &src, const QUrl &dest)
316
{
317
    qCDebug(OBEXFTP) << "Source: " << src << "Dest:" << dest;
318

David Rosca's avatar
David Rosca committed
319
320
321
322
323
324
325
326
327
328
329
330
    if (!changeFolder(urlDirectory(src))) {
        return;
    }

    BluezQt::PendingCall *call = m_transfer->copyFile(src.path(), dest.path());
    call->waitForFinished();

    if (call->error()) {
        // Copying files within obexftp is currently not implemented in obexd
        if (call->errorText() == QLatin1String("Not Implemented")) {
            error(KIO::ERR_UNSUPPORTED_ACTION, src.path());
        } else {
331
            error(KIO::ERR_CANNOT_WRITE, src.path());
David Rosca's avatar
David Rosca committed
332
333
334
335
336
        }
        return;
    }

    finished();
337
338
}

David Rosca's avatar
David Rosca committed
339
void KioFtp::copyFromObexftp(const QUrl &src, const QUrl &dest)
340
{
David Rosca's avatar
David Rosca committed
341
    qCDebug(OBEXFTP) << "Source: " << src << "Dest:" << dest;
342

David Rosca's avatar
David Rosca committed
343
    if (!changeFolder(urlDirectory(src))) {
344
345
346
        return;
    }

347
348
349
350
351
    if (!m_statMap.contains(src.toDisplayString())) {
        bool ok;
        listFolder(urlUpDir(src), &ok);
    }

David Rosca's avatar
David Rosca committed
352
353
    BluezQt::PendingCall *call = m_transfer->getFile(dest.path(), urlFileName(src));
    call->waitForFinished();
354

355
    int size = m_statMap.value(src.toDisplayString()).numberValue(KIO::UDSEntry::UDS_SIZE);
356
357
    totalSize(size);

David Rosca's avatar
David Rosca committed
358
359
    BluezQt::ObexTransferPtr transfer = call->value().value<BluezQt::ObexTransferPtr>();
    TransferFileJob *getFile = new TransferFileJob(transfer, this);
360
361
362
    getFile->exec();
}

363
void KioFtp::copyToObexftp(const QUrl &src, const QUrl &dest)
364
{
365
    qCDebug(OBEXFTP) << "Source:" << src << "Dest:" << dest;
366

David Rosca's avatar
David Rosca committed
367
    if (!changeFolder(urlDirectory(dest))) {
368
369
370
        return;
    }

David Rosca's avatar
David Rosca committed
371
372
    BluezQt::PendingCall *call = m_transfer->putFile(src.path(), urlFileName(dest));
    call->waitForFinished();
373

374
375
376
    int size = QFile(src.path()).size();
    totalSize(size);

David Rosca's avatar
David Rosca committed
377
378
    BluezQt::ObexTransferPtr transfer = call->value().value<BluezQt::ObexTransferPtr>();
    TransferFileJob *putFile = new TransferFileJob(transfer, this);
379
    putFile->exec();
380
381
}

382
void KioFtp::statHelper(const QUrl &url)
383
{
David Rosca's avatar
David Rosca committed
384
    if (m_statMap.contains(url.toDisplayString())) {
385
        qCDebug(OBEXFTP) << "statMap contains the url";
386
        statEntry(m_statMap.value(url.toDisplayString()));
387
388
389
        return;
    }

390
    if (urlIsRoot(url)) {
391
        qCDebug(OBEXFTP) << "Url is root";
392
        KIO::UDSEntry entry;
David Rosca's avatar
David Rosca committed
393
        entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral("/"));
394
        entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
Àlex Fiestas's avatar
Àlex Fiestas committed
395
        entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700);
396

David Rosca's avatar
David Rosca committed
397
        qCDebug(OBEXFTP) << "Adding stat cache" << url.toDisplayString();
398
        m_statMap.insert(url.toDisplayString(), entry);
399
400
401
402
        statEntry(entry);
        return;
    }

403
    qCDebug(OBEXFTP) << "statMap does not contains the url";
404

David Rosca's avatar
David Rosca committed
405
    if (!changeFolder(urlDirectory(url))) {
406
407
408
409
        return;
    }

    bool ok;
410
    listFolder(urlUpDir(url), &ok);
411
412
413
414
    if (!ok) {
        return;
    }

415
416
    if (!m_statMap.contains(url.toDisplayString())) {
        qCWarning(OBEXFTP) << "statMap still does not contains the url!";
417
418
    }

419
    statEntry(m_statMap.value(url.toDisplayString()));
420
421
}

David Rosca's avatar
David Rosca committed
422
QList<KIO::UDSEntry> KioFtp::listFolder(const QUrl &url, bool *ok)
423
424
425
{
    QList<KIO::UDSEntry> list;

David Rosca's avatar
David Rosca committed
426
427
    BluezQt::PendingCall *call = m_transfer->listFolder();
    call->waitForFinished();
428

David Rosca's avatar
David Rosca committed
429
    if (call->error()) {
430
431
        qCDebug(OBEXFTP) << "List folder error" << call->errorText();
        error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.path());
432
433
434
435
        *ok = false;
        return list;
    }

David Rosca's avatar
David Rosca committed
436
437
438
439
440
441
    const QList<BluezQt::ObexFileTransferEntry> &items = call->value().value<QList<BluezQt::ObexFileTransferEntry> >();

    Q_FOREACH (const BluezQt::ObexFileTransferEntry &item, items) {
        if (!item.isValid()) {
            continue;
        }
David Rosca's avatar
David Rosca committed
442

443
        KIO::UDSEntry entry;
David Rosca's avatar
David Rosca committed
444
445
        entry.insert(KIO::UDSEntry::UDS_NAME, item.name());
        entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, item.label());
446
        entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700);
447
        entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, item.modificationTime().toSecsSinceEpoch());
David Rosca's avatar
David Rosca committed
448
        entry.insert(KIO::UDSEntry::UDS_SIZE, item.size());
David Rosca's avatar
David Rosca committed
449

David Rosca's avatar
David Rosca committed
450
        if (item.type() == BluezQt::ObexFileTransferEntry::Folder) {
451
            entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
David Rosca's avatar
David Rosca committed
452
        } else if (item.type() == BluezQt::ObexFileTransferEntry::File) {
453
            entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
454
        }
David Rosca's avatar
David Rosca committed
455

456
        if (urlIsRoot(url)) {
David Rosca's avatar
David Rosca committed
457
            updateRootEntryIcon(entry, item.memoryType());
458
        }
David Rosca's avatar
David Rosca committed
459

460
        list.append(entry);
461

David Rosca's avatar
David Rosca committed
462
463
        // Most probably the client of the kio will stat each file
        // so since we are on it, let's cache all of them.
464
        QUrl statUrl = url;
465
466
467
468
469
470

        if (statUrl.path().endsWith('/')) {
            statUrl.setPath(statUrl.path() + item.name());
        } else {
            statUrl.setPath(statUrl.path() + QLatin1Char('/') + item.name());
        }
471

David Rosca's avatar
David Rosca committed
472
        if (!m_statMap.contains(statUrl.toDisplayString())) {
473
474
475
476
            qCDebug(OBEXFTP) << "Stat:"
                             << statUrl.toDisplayString()
                             << entry.stringValue(KIO::UDSEntry::UDS_NAME)
                             << entry.numberValue(KIO::UDSEntry::UDS_SIZE);
David Rosca's avatar
David Rosca committed
477
            m_statMap.insert(statUrl.toDisplayString(), entry);
478
479
480
        }
    }

481
482
    *ok = true;
    return list;
483
}
484

485
bool KioFtp::changeFolder(const QString &folder)
486
{
David Rosca's avatar
David Rosca committed
487
488
    BluezQt::PendingCall *call = m_transfer->changeFolder(folder);
    call->waitForFinished();
489

David Rosca's avatar
David Rosca committed
490
    if (call->error()) {
491
492
493
494
495
        error(KIO::ERR_CANNOT_ENTER_DIRECTORY, folder);
        return false;
    }
    return true;
}
496

497
498
499
bool KioFtp::createFolder(const QString &folder)
{

David Rosca's avatar
David Rosca committed
500
501
    BluezQt::PendingCall *call = m_transfer->createFolder(folder);
    call->waitForFinished();
502

David Rosca's avatar
David Rosca committed
503
504
    if (call->error()) {
        error(KIO::ERR_CANNOT_MKDIR, folder);
505
506
507
508
509
510
511
        return false;
    }
    return true;
}

bool KioFtp::deleteFile(const QString &file)
{
David Rosca's avatar
David Rosca committed
512
513
    BluezQt::PendingCall *call = m_transfer->deleteFile(file);
    call->waitForFinished();
514

David Rosca's avatar
David Rosca committed
515
    if (call->error()) {
516
517
518
519
520
521
522
523
524
525
526
527
528
        error(KIO::ERR_CANNOT_DELETE, file);
        return false;
    }
    return true;
}

void KioFtp::updateRootEntryIcon(KIO::UDSEntry &entry, const QString &memoryType)
{
    const QString &path = entry.stringValue(KIO::UDSEntry::UDS_NAME);

    // Nokia (mount-points are C: D: E: ...)
    if (path.size() == 2 && path.at(1) == QLatin1Char(':')) {
        if (memoryType.startsWith(QLatin1String("DEV"))) {
David Rosca's avatar
David Rosca committed
529
            entry.insert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("drive-removable-media"));
530
        } else if (memoryType == QLatin1String("MMC")) {
David Rosca's avatar
David Rosca committed
531
            entry.insert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("media-flash-sd-mmc"));
532
533
        }
    }
534

535
536
537
    // Android
    if (entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1String("PHONE_MEMORY")) {
        entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Phone memory"));
538
        entry.insert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("drive-removable-media"));
539
540
    } else if (entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1String("EXTERNAL_MEMORY")) {
        entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("External memory"));
David Rosca's avatar
David Rosca committed
541
        entry.insert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("media-flash-sd-mmc"));
542
543
    }
}