/
transferengine.cpp
1409 lines (1200 loc) · 53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* Copyright (c) 2013 - 2019 Jolla Ltd.
* Copyright (c) 2019 Open Mobile Platform LLC.
*
* All rights reserved.
*
* This file is part of Sailfish Transfer Engine package.
*
* You may use this file under the terms of the GNU Lesser General
* Public License version 2.1 as published by the Free Software Foundation
* and appearing in the file license.lgpl included in the packaging
* of this file.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation
* and appearing in the file license.lgpl included in the packaging
* of this file.
*
* This library 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
* Lesser General Public License for more details.
*/
25
26
27
28
29
30
#include "transferengine.h"
#include "transferengine_p.h"
#include "transferplugininterface.h"
#include "mediaitem.h"
#include "dbmanager.h"
31
#include "logging.h"
32
#include "transferengine_adaptor.h"
33
34
35
36
37
38
39
40
41
#include "transfertypes.h"
#include "transferplugininfo.h"
#include <QDir>
#include <QtDebug>
#include <QPluginLoader>
#include <QDBusMessage>
#include <QFileSystemWatcher>
#include <QTimer>
42
#include <QSettings>
43
44
#include <notification.h>
45
46
47
#include <signal.h>
48
49
#include <Accounts/Manager>
50
#define SHARE_PLUGINS_PATH "/usr/lib/nemo-transferengine/plugins"
51
#define CONFIG_PATH "/usr/share/nemo-transferengine/nemo-transfer-engine.conf"
52
#define FILE_WATCHER_TIMEOUT 5000
53
#define ACTIVITY_MONITOR_TIMEOUT 1*60*1000 // 1 minute in ms
54
#define TRANSFER_EXPIRATION_THRESHOLD 3*60 // 3 minutes in seconds
55
56
57
58
59
60
#define TRANSFER_EVENT_CATEGORY "transfer"
#define TRANSFER_COMPLETE_EVENT_CATEGORY "transfer.complete"
#define TRANSFER_ERROR_EVENT_CATEGORY "transfer.error"
#define TRANSFER_PROGRESS_HINT "x-nemo-progress"
61
62
63
64
65
66
67
68
69
TransferEngineSignalHandler * TransferEngineSignalHandler::instance()
{
static TransferEngineSignalHandler instance;
return &instance;
}
void TransferEngineSignalHandler::signalHandler(int signal)
{
70
if (signal == SIGUSR1) {
71
72
73
74
75
76
77
78
79
80
81
QMetaObject::invokeMethod(TransferEngineSignalHandler::instance(),
"exitSafely",
Qt::AutoConnection);
}
}
TransferEngineSignalHandler::TransferEngineSignalHandler()
{
signal(SIGUSR1, TransferEngineSignalHandler::signalHandler);
}
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// ---------------------------
// ClientActivityMonitor runs periodic checks if there are transfers which are expired.
// A transfer can be expired e.g. when a client has been crashed in the middle of Sync,
// Download or Upload operation or the client API isn't used properly.
//
// NOTE: This class only monitors if there are expired transfers and emit signal to indicate
// that it's cleaning time. It is up to Transfer Engine to remoce expired ids from the
// ClientActivityMonitor instance.
ClientActivityMonitor::ClientActivityMonitor(QObject *parent)
: QObject(parent)
, m_timer(new QTimer(this))
{
connect(m_timer, SIGNAL(timeout()), this, SLOT(checkActivity()));
m_timer->start(ACTIVITY_MONITOR_TIMEOUT);
}
ClientActivityMonitor::~ClientActivityMonitor()
{
m_activityMap.clear();
}
void ClientActivityMonitor::newActivity(int transferId)
{
// Update or add a new timestamp
m_activityMap.insert(transferId, QDateTime::currentDateTimeUtc().toTime_t());
}
void ClientActivityMonitor::activityFinished(int transferId)
{
if (!m_activityMap.contains(transferId)) {
113
qCWarning(lcTransferLog) << Q_FUNC_INFO << "Could not find matching TransferId. This is probably an error!";
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
return;
}
m_activityMap.remove(transferId);
}
bool ClientActivityMonitor::activeTransfers() const
{
return !m_activityMap.isEmpty();
}
void ClientActivityMonitor::checkActivity()
{
// Check if there are existing transfers which are not yet finished and
// they've been around too long. Notify TransferEngine about these transfers.
QList<int> ids;
130
quint32 currTime = QDateTime::currentDateTimeUtc().toTime_t();
131
132
133
134
135
136
137
138
139
140
141
142
143
QMap<int, quint32>::const_iterator i = m_activityMap.constBegin();
while (i != m_activityMap.constEnd()) {
if ((currTime - i.value()) >= TRANSFER_EXPIRATION_THRESHOLD) {
ids << i.key();
}
i++;
}
if (!ids.isEmpty()) {
emit transfersExpired(ids);
}
}
144
145
146
147
148
149
150
151
152
153
154
// ----------------------------
TransferEnginePrivate::TransferEnginePrivate(TransferEngine *parent):
m_notificationsEnabled(true),
q_ptr(parent)
{
m_fileWatcherTimer = new QTimer(this);
m_fileWatcherTimer->setSingleShot(true);
connect(m_fileWatcherTimer, SIGNAL(timeout()), this, SLOT(enabledPluginsCheck()));
155
156
m_delayedExitTimer = new QTimer(this);
m_delayedExitTimer->setSingleShot(true);
157
158
m_delayedExitTimer->setInterval(60000);
m_delayedExitTimer->start(); // Exit if nothing happens within 60 sec
159
160
connect(m_delayedExitTimer, SIGNAL(timeout()), this, SLOT(delayedExitSafely()));
161
162
163
164
165
166
167
168
169
m_fileWatcher = new QFileSystemWatcher(this);
m_fileWatcher->addPath(SHARE_PLUGINS_PATH);
connect(m_fileWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(pluginDirChanged()));
m_accountManager = new Accounts::Manager("sharing", this);
connect(m_accountManager, SIGNAL(accountCreated(Accounts::AccountId)), this, SLOT(enabledPluginsCheck()));
connect(m_accountManager, SIGNAL(accountRemoved(Accounts::AccountId)), this, SLOT(enabledPluginsCheck()));
connect(m_accountManager, SIGNAL(accountUpdated(Accounts::AccountId)), this, SLOT(enabledPluginsCheck()));
170
// Exit safely stuff if we recieve certain signal or there are no active transfers
171
Q_Q(TransferEngine);
172
173
174
175
176
177
connect(TransferEngineSignalHandler::instance(), SIGNAL(exitSafely()), this, SLOT(exitSafely()));
connect(q, SIGNAL(statusChanged(int,int)), this, SLOT(exitSafely()));
// Monitor expired transfers and cleanup them if required
m_activityMonitor = new ClientActivityMonitor(this);
connect(m_activityMonitor, SIGNAL(transfersExpired(QList<int>)), this, SLOT(cleanupExpiredTransfers(QList<int>)));
178
179
180
181
QSettings settings(CONFIG_PATH, QSettings::IniFormat);
if (settings.status() != QSettings::NoError) {
182
qCWarning(lcTransferLog) << Q_FUNC_INFO << "Failed to read settings!" << settings.status();
183
184
185
186
187
188
189
190
191
192
193
194
195
} else {
settings.beginGroup("transfers");
const QString service = settings.value("service").toString();
const QString path = settings.value("path").toString();
const QString iface = settings.value("interface").toString();
const QString method = settings.value("method").toString();
settings.endGroup();
if (!service.isEmpty() && !path.isEmpty() && !iface.isEmpty() && !method.isEmpty()) {
m_defaultActions << Notification::remoteAction("default", "", service, path, iface, method)
<< Notification::remoteAction("app", "", service, path, iface, method);
}
}
196
197
198
199
200
201
202
203
204
205
206
207
}
void TransferEnginePrivate::pluginDirChanged()
{
// We need to check our plugins, but only after a short period of time
// because some operations may cause calling this slot over 10 times.
// E.g. reinstallation of plugins from the RPM package
m_fileWatcherTimer->start(FILE_WATCHER_TIMEOUT);
}
void TransferEnginePrivate::exitSafely()
{
208
if (!m_activityMonitor->activeTransfers()) {
209
qCDebug(lcTransferLog) << "Scheduling exit in" << m_delayedExitTimer->interval() << "ms";
210
211
212
m_delayedExitTimer->start();
} else {
m_delayedExitTimer->stop();
213
214
215
216
217
}
}
void TransferEnginePrivate::delayedExitSafely()
{
218
if (getenv("TRANSFER_ENGINE_KEEP_RUNNING")) {
219
qCDebug(lcTransferLog) << "Keeping transfer engine running";
220
} else {
221
qCDebug(lcTransferLog) << "Stopping transfer engine";
222
223
qApp->exit();
}
224
225
226
227
228
229
230
231
232
}
void TransferEnginePrivate::enabledPluginsCheck()
{
Q_Q(TransferEngine);
if (m_fileWatcherTimer->isActive()) {
m_fileWatcherTimer->stop();
}
233
if (m_infoObjects.count() > 0) {
234
qCWarning(lcTransferLog) << Q_FUNC_INFO << "Already quering account info" << m_infoObjects.count();
235
236
237
return;
}
238
239
// First clear old data
m_enabledPlugins.clear();
240
m_pluginMetaData.clear();
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
qDeleteAll(m_infoObjects);
m_infoObjects.clear();
QPluginLoader loader;
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint | QLibrary::ExportExternalSymbolsHint);
// Handle the case if all the plugins have been removed.
QStringList plugins = pluginList();
if (plugins.isEmpty()) {
emit q->transferMethodListChanged();
return;
}
// We have plugins
Q_FOREACH(QString plugin, plugins) {
loader.setFileName(plugin);
TransferPluginInterface *interface =
qobject_cast<TransferPluginInterface*>(loader.instance());
if (interface && interface->enabled()) {
TransferPluginInfo *info = interface->infoObject();
if (!info) {
264
qCWarning(lcTransferLog) << Q_FUNC_INFO << "NULL Info object!";
265
266
267
continue;
}
268
269
270
271
if (info->ready()) {
if (info->info().count() > 0) {
m_enabledPlugins << info->info();
}
272
m_pluginMetaData << info->metaData();
273
delete info;
274
275
276
277
278
279
280
} else {
// These object will be cleaned in pluginInfoReady() slot.
m_infoObjects << info;
connect(info, SIGNAL(infoReady()), this, SLOT(pluginInfoReady()));
connect(info, SIGNAL(infoError(QString)), this, SLOT(pluginInfoError(QString)));
info->query();
}
281
282
283
}
if (!interface) {
284
qCWarning(lcTransferLog) << Q_FUNC_INFO << loader.errorString();
285
286
}
}
287
}
288
289
290
291
292
293
294
295
296
297
298
299
300
void TransferEnginePrivate::cleanupExpiredTransfers(const QList<int> &expiredIds)
{
// Clean up expired items from the database by changing the status to TransferInterrupted. This way
// database doesn't maintain objects with unifinished state and failed items can be cleaned by the
// user manually from the UI.
Q_Q(TransferEngine);
Q_FOREACH(int id, expiredIds) {
if (DbManager::instance()->updateTransferStatus(id, TransferEngineData::TransferInterrupted)) {
m_activityMonitor->activityFinished(id);
emit q->statusChanged(id, TransferEngineData::TransferInterrupted);
}
}
301
302
303
304
305
306
307
}
void TransferEnginePrivate::recoveryCheck()
{
QList<TransferDBRecord> records = DbManager::instance()->transfers();
// Check all transfer which are not properly finished and mark those as
// interrupted
308
Q_Q(TransferEngine);
309
310
311
Q_FOREACH(TransferDBRecord record, records) {
if (record.status == TransferEngineData::TransferStarted ||
record.status == TransferEngineData::NotStarted) {
312
313
if (DbManager::instance()->updateTransferStatus(record.transfer_id, TransferEngineData::TransferInterrupted)) {
emit q->statusChanged(record.transfer_id, TransferEngineData::TransferInterrupted);
314
315
316
if (record.status == TransferEngineData::TransferStarted) {
emit q_ptr->activeTransfersChanged(); // It's not active anymore
}
317
}
318
319
320
321
322
323
}
}
}
void TransferEnginePrivate::sendNotification(TransferEngineData::TransferType type,
TransferEngineData::TransferStatus status,
324
325
326
qreal progress,
const QString &fileName,
int transferId)
327
{
328
if (!m_notificationsEnabled || fileName.isEmpty()) {
329
330
331
return;
}
332
333
334
335
336
QString category;
QString body;
QString summary;
QString previewBody;
QString previewSummary;
337
338
339
bool useProgress = false;
Notification::Urgency urgency = Notification::Normal;
QString appIcon = QStringLiteral("icon-lock-information");
340
QString icon = QStringLiteral("icon-lock-transfer");
341
342
343
344
// TODO: explicit grouping of transfer notifications is now removed, as grouping
// will now be performed by lipstick. We may need to reinstate group summary
// notifications at some later point...
345
346
347
348
349
350
351
352
353
354
355
356
// Notification & Banner rules:
//
// Show Banner:
// - For succesfull uploads and for downloads
// - For failed Uploads, Downloads, Syncs
//
// Show an event in the EventView:
// - For downloads
// - For failed uploads, downloads and syncs
357
int notificationId = DbManager::instance()->notificationId(transferId);
358
359
360
361
362
363
if (status == TransferEngineData::TransferFinished) {
switch(type) {
case TransferEngineData::Upload:
//: Notification for successful file upload
//% "File uploaded"
364
365
366
previewBody = qtTrId("transferengine-no-file-upload-success");
previewSummary = fileName;
category = TRANSFER_EVENT_CATEGORY; // Use "generic" transfer event for uploads
367
368
break;
case TransferEngineData::Download:
369
category = TRANSFER_COMPLETE_EVENT_CATEGORY;
370
371
//: Notification for successful file download
//% "File downloaded"
372
373
body = qtTrId("transferengine-no-file-download-success");
summary = fileName;
374
375
previewBody = body;
previewSummary = summary;
376
377
378
break;
case TransferEngineData::Sync:
// Ok exit
379
break;
380
default:
381
qCWarning(lcTransferLog) << "TransferEnginePrivate::sendNotification: unknown state";
382
break;
383
}
384
385
} else if (status == TransferEngineData::TransferInterrupted) {
386
387
urgency = Notification::Critical;
appIcon = QStringLiteral("icon-lock-information");
388
category = TRANSFER_ERROR_EVENT_CATEGORY;
389
icon.clear();
390
391
392
393
switch (type) {
case TransferEngineData::Upload:
//: Notification for failed file upload
394
395
//% "Upload failed"
body = qtTrId("transferengine-la-file_upload_failed");
396
397
break;
case TransferEngineData::Download:
398
//: Notification for failed file download
399
400
//% "Download failed"
body = qtTrId("transferengine-la-file_download_failed");
401
402
break;
case TransferEngineData::Sync:
403
404
// no notification required
category.clear();
405
406
break;
default:
407
qCWarning(lcTransferLog) << "TransferEnginePrivate::sendNotification: unknown state";
408
409
category.clear();
break;
410
411
}
412
413
414
summary = fileName;
previewSummary = summary;
previewBody = body;
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
} else if (status == TransferEngineData::TransferStarted) {
if (type == TransferEngineData::Upload || type == TransferEngineData::Download) {
category = TRANSFER_EVENT_CATEGORY;
if (type == TransferEngineData::Upload) {
//: Notification for ongoing upload
//% "File uploading"
body = qtTrId("transferengine-no-file-uploading");
} else {
//: Notification for ongoing file download
//% "File downloading"
body = qtTrId("transferengine-no-file-downloading");
}
summary = fileName;
if (progress > 0)
useProgress = true;
434
}
435
436
437
438
439
440
441
442
443
} else if (status == TransferEngineData::TransferCanceled && notificationId > 0) {
// Exit, no banners or events when user has canceled a transfer
// Remove any existing notification
Notification notification;
notification.setReplacesId(notificationId);
notification.close();
DbManager::instance()->setNotificationId(transferId, 0);
notificationId = 0;
444
}
445
446
447
if (!category.isEmpty()) {
Notification notification;
448
449
450
451
if (notificationId > 0) {
notification.setReplacesId(notificationId);
}
452
453
454
if (!m_defaultActions.isEmpty()) {
notification.setRemoteActions(m_defaultActions);
455
456
}
457
458
459
460
461
462
463
//: Group name for notifications of successful transfers
//% "Transfers"
const QString transfersGroup(qtTrId("transferengine-notification_group"));
//: Group name for notifications of failed transfers
//% "Warnings"
const QString errorsGroup(qtTrId("transferengine-notification_errors_group"));
464
465
466
467
468
469
470
notification.setCategory(category);
notification.setAppName(category == TRANSFER_ERROR_EVENT_CATEGORY ? errorsGroup : transfersGroup);
notification.setSummary(summary);
notification.setBody(body);
notification.setPreviewSummary(previewSummary);
notification.setPreviewBody(previewBody);
notification.setUrgency(urgency);
471
472
473
474
475
if (!icon.isEmpty()) {
notification.setIcon(icon);
}
476
477
478
479
480
481
482
483
484
485
if (useProgress) {
notification.setHintValue(TRANSFER_PROGRESS_HINT, static_cast<double>(progress));
}
notification.publish();
int newId = notification.replacesId();
if (newId != notificationId) {
DbManager::instance()->setNotificationId(transferId, newId);
}
}
486
487
488
489
490
491
492
493
}
int TransferEnginePrivate::uploadMediaItem(MediaItem *mediaItem,
MediaTransferInterface *muif,
const QVariantMap &userData)
{
Q_Q(TransferEngine);
494
if (mediaItem == 0) {
495
qCWarning(lcTransferLog) << "TransferEngine::uploadMediaItem invalid MediaItem";
496
497
498
return -1;
}
if (muif == 0) {
499
qCWarning(lcTransferLog) << "TransferEngine::uploadMediaItem Failed to get MediaTransferInterface";
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
return -1;
}
mediaItem->setValue(MediaItem::TransferType, TransferEngineData::Upload);
mediaItem->setValue(MediaItem::DisplayName, muif->displayName());
mediaItem->setValue(MediaItem::ServiceIcon, muif->serviceIcon());
mediaItem->setValue(MediaItem::CancelSupported, muif->cancelEnabled());
mediaItem->setValue(MediaItem::RestartSupported, muif->restartEnabled());
// Get and set data from user data if that's set. The following user data values
// are stored to the database so that's why they are set to the media item too.
// If the user data is fully custom for plugin it won't be stored to the database and
// it's up to the plugin to handle or ignore it.
QString title = userData.value("title").toString();
QString desc = userData.value("description").toString();
qint64 accId = userData.value("accountId").toInt();
qreal scale = userData.value("scalePercent").toReal();
mediaItem->setValue(MediaItem::Title, title);
mediaItem->setValue(MediaItem::Description, desc);
mediaItem->setValue(MediaItem::AccountId, accId);
mediaItem->setValue(MediaItem::ScalePercent, scale);
muif->setMediaItem(mediaItem);
connect(muif, SIGNAL(statusChanged(MediaTransferInterface::TransferStatus)),
this, SLOT(uploadItemStatusChanged(MediaTransferInterface::TransferStatus)));
connect(muif, SIGNAL(progressUpdated(qreal)),
this, SLOT(updateProgress(qreal)));
// Let's create an entry into Transfer DB
const int key = DbManager::instance()->createTransferEntry(mediaItem);
m_keyTypeCache.insert(key, TransferEngineData::Upload);
if (key < 0) {
534
qCWarning(lcTransferLog) << "TransferEngine::uploadMediaItem: Failed to create an entry to transfer database!";
535
536
537
538
delete muif;
return key;
}
539
m_activityMonitor->newActivity(key);
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
emit q->transfersChanged();
emit q->statusChanged(key, TransferEngineData::NotStarted);
// For now, we just store our uploader to a map. It'll be removed from it when
// the upload has finished.
m_plugins.insert(muif, key);
muif->start();
return key;
}
QStringList TransferEnginePrivate::pluginList() const
{
QDir dir(SHARE_PLUGINS_PATH);
QStringList plugins = dir.entryList(QStringList() << "*.so",
QDir::Files,
QDir::NoSort);
QStringList filePaths;
Q_FOREACH(QString plugin, plugins) {
filePaths << dir.absolutePath() + QDir::separator() + plugin;
}
return filePaths;
}
QList <TransferMethodInfo> TransferEnginePrivate::enabledPlugins() const
{
return m_enabledPlugins;
}
569
570
571
572
573
QList<QVariantMap> TransferEnginePrivate::pluginMetaData() const
{
return m_pluginMetaData;
}
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
MediaTransferInterface *TransferEnginePrivate::loadPlugin(const QString &pluginId) const
{
QPluginLoader loader;
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint | QLibrary::ExportExternalSymbolsHint);
Q_FOREACH(QString plugin, pluginList()) {
loader.setFileName(plugin);
TransferPluginInterface *interface =
qobject_cast<TransferPluginInterface*>(loader.instance());
if (interface && interface->enabled() && interface->pluginId() == pluginId) {
return interface->transferObject();
}
if (!interface) {
589
qCWarning(lcTransferLog) << "TransferEngine::loadPlugin: " + loader.errorString();
590
591
592
593
594
595
596
597
598
599
}
if (loader.isLoaded()) {
loader.unload();
}
}
return 0;
}
600
601
602
603
604
605
606
607
608
609
610
611
612
QString TransferEnginePrivate::mediaFileOrResourceName(MediaItem *mediaItem) const
{
if (!mediaItem) {
return QString();
}
QUrl url = mediaItem->value(MediaItem::Url).toUrl();
if (!url.isEmpty()) {
QStringList split = url.toString().split(QDir::separator());
return split.at(split.length()-1);
}
return mediaItem->value(MediaItem::ResourceName).toString();
}
613
614
615
616
617
618
619
620
621
622
623
624
625
void TransferEnginePrivate::uploadItemStatusChanged(MediaTransferInterface::TransferStatus status)
{
MediaTransferInterface *muif = qobject_cast<MediaTransferInterface*>(sender());
const int key = m_plugins.value(muif);
const TransferEngineData::TransferType type =
static_cast<TransferEngineData::TransferType>(muif->mediaItem()->value(MediaItem::TransferType).toInt());
TransferEngineData::TransferStatus tStatus = static_cast<TransferEngineData::TransferStatus>(status);
bool ok = false;
switch(tStatus) {
case TransferEngineData::TransferStarted:
ok = DbManager::instance()->updateTransferStatus(key, tStatus);
626
m_activityMonitor->newActivity(key);
627
628
629
630
631
632
633
634
635
break;
case TransferEngineData::TransferInterrupted:
case TransferEngineData::TransferCanceled:
case TransferEngineData::TransferFinished:
{
// If the flow ends up here, we are not interested in any signals the same object
// might emit. Let's just disconnect them.
muif->disconnect();
636
sendNotification(type, tStatus, muif->progress(), mediaFileOrResourceName(muif->mediaItem()), key);
637
638
ok = DbManager::instance()->updateTransferStatus(key, tStatus);
if (m_plugins.remove(muif) == 0) {
639
qCWarning(lcTransferLog) << "TransferEnginePrivate::uploadItemStatusChanged: Failed to remove media upload object from the map!";
640
641
642
643
// What to do here.. Let's just delete it..
}
muif->deleteLater();
muif = 0;
644
m_activityMonitor->activityFinished(key);
645
646
647
} break;
default:
648
qCWarning(lcTransferLog) << "TransferEnginePrivate::uploadItemStatusChanged: unhandled status: " << tStatus;
649
650
651
652
break;
}
if (!ok) {
653
qCWarning(lcTransferLog) << "TransferEnginePrivate::uploadItemStatusChanged: Failed update share status for the item: " + key;
654
655
656
657
658
659
660
661
662
663
664
665
666
return;
}
Q_Q(TransferEngine);
emit q->statusChanged(key, tStatus);
}
void TransferEnginePrivate::updateProgress(qreal progress)
{
MediaTransferInterface *muif = qobject_cast<MediaTransferInterface*>(sender());
const int key = m_plugins.value(muif);
if (!DbManager::instance()->updateProgress(key, progress)) {
667
qCWarning(lcTransferLog) << "TransferEnginePrivate::updateProgress: Failed to update progress";
668
669
670
return;
}
671
m_activityMonitor->newActivity(key);
672
Q_Q(TransferEngine);
673
q->updateTransferProgress(key, progress);
674
675
676
677
678
679
}
void TransferEnginePrivate::pluginInfoReady()
{
TransferPluginInfo *infoObj = qobject_cast<TransferPluginInfo*>(sender());
680
681
682
683
QList<TransferMethodInfo> infoList = infoObj->info();
if (!infoList.isEmpty()) {
m_enabledPlugins << infoList;
}
684
m_pluginMetaData << infoObj->metaData();
685
686
687
688
if (m_infoObjects.removeOne(infoObj)) {
delete infoObj;
} else {
689
qCWarning(lcTransferLog) << Q_FUNC_INFO << "Failed to remove info object!";
690
691
delete infoObj;
}
692
693
694
695
696
697
698
699
700
if (m_infoObjects.isEmpty()) {
Q_Q(TransferEngine);
emit q->transferMethodListChanged();
}
}
void TransferEnginePrivate::pluginInfoError(const QString &msg)
{
701
qCWarning(lcTransferLog) << "TransferEnginePrivate::pluginInfoError:" << msg;
702
703
TransferPluginInfo *infoObj = qobject_cast<TransferPluginInfo*>(sender());
m_infoObjects.removeOne(infoObj);
704
infoObj->deleteLater();
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
if (m_infoObjects.isEmpty()) {
Q_Q(TransferEngine);
emit q->transferMethodListChanged();
}
}
TransferEngineData::TransferType TransferEnginePrivate::transferType(int transferId)
{
if (!m_keyTypeCache.contains(transferId)) {
TransferEngineData::TransferType type = DbManager::instance()->transferType(transferId);
m_keyTypeCache.insert(transferId, type);
return type;
} else {
return m_keyTypeCache.value(transferId);
}
}
void TransferEnginePrivate::callbackCall(int transferId, CallbackMethodType method)
{
// Get DBus callback information. Callback list must contain at least
// service, path, interface and one callback method name. Note that even
// if the cancel or restart method is missing, it'll be indicated as an
// empty string. So the list length is always 5.
QStringList callback = DbManager::instance()->callback(transferId);
if (callback.length() != 5) {
732
qCWarning(lcTransferLog) << "TransferEnginePrivate:callbackCall: Invalid callback interface";
733
734
735
736
737
738
739
740
return;
}
QDBusInterface remoteInterface(callback.at(Service),
callback.at(Path),
callback.at(Interface));
if (!remoteInterface.isValid()) {
741
qCWarning(lcTransferLog) << "TransferEnginePrivate::callbackCall: DBus interface is not valid!";
742
743
744
745
return;
}
if (method >= callback.size()) {
746
qCWarning(lcTransferLog) << "TransferEnginePrivate::callbackCall: method index out of range!";
747
748
749
750
751
return;
}
const QString methodName = callback.at(method);
if (methodName.isEmpty()) {
752
qCWarning(lcTransferLog) << "TransferEnginePrivate::callbackCall: Failed to get callback method name!";
753
return;
754
755
}
remoteInterface.call(methodName, transferId);
756
757
758
759
760
761
762
763
764
765
766
}
/*!
\class TransferEngine
\brief The TransferEngine class implements the functionality for different transfer types.
\ingroup transfer-engine
TransferEngine is the central place for:
\list
767
768
769
\li Sharing - Provides requires plugin interfaces for share plugins
\li Downloads - Provides an API to create Download entries
\li Syncs - Provides an API to create Sync entries
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
\endlist
For Downloads and Syncs, the Transfer Engine acts only a place to keep track of these operations.
The actual Download and Sync is executed by a client using TransferEngine API. For sharing
the TransferEngine provides an API containing a few interaces, which a share plugin must implement.
TransferEngine also takes care of loading and executing the sharing, based on the API it defines.
The most essential thing to remember is that Transfer Engine provides share plugin API, DBus API e.g.
for creating Transfer UI or Share UIs, it stores data to the local sqlite database using DbManager
and that's it.
How to implement a share plugin see: TransferPluginInterface, MediaTransferInterface, MediaItem,
TransferPluginInfo
TransferEngine provides DBus API, but instead of using it directly, it's recommended to use
TransferEngineClient. If there is a need to create UI to display e.g. transfer statuses, then
the DBus API is the recommend way to implement it.
*/
/*!
\fn void TransferEngine::progressChanged(int transferId, double progress)
The signal is emitted when \a progress for a transfer with a \a transferId has changed.
*/
/*!
\fn void TransferEngine::statusChanged(int transferId, int status)
The signal is emitted when \a status for a transfer with a \a transferId has changed.
*/
/*!
\fn void TransferEngine::transferMethodListChanged()
The signal is emitted when transfer methods have changed. Usually tranfer methods change
when a new plugin is installed to the system or an account has been enabled or disabled.
*/
/*!
\fn void TransferEngine::transfersChanged()
The signal is emitted when there is a new transfer or a transfer has been removed from the
database.
*/
/*!
Constructor with optional \a parent arguement.
*/
TransferEngine::TransferEngine(QObject *parent) :
QObject(parent),
d_ptr(new TransferEnginePrivate(this))
{
823
TransferMethodInfoDeprecated::registerType();
824
825
TransferMethodInfo::registerType();
TransferDBRecord::registerType();
826
TransferPluginInfo::registerType();
827
828
829
830
831
832
QDBusConnection connection = QDBusConnection::sessionBus();
if (!connection.registerObject("/org/nemo/transferengine", this)) {
qFatal("Could not register object \'/org/nemo/transferengine\'");
}
833
834
835
836
if (!connection.registerService("org.nemo.transferengine")) {
qFatal("DBUS service already taken. Kill the other instance first.");
}
837
new TransferEngineAdaptor(this);
838
839
840
841
842
843
// Let's make sure that db is open by creating
// DbManager singleton instance.
DbManager::instance();
Q_D(TransferEngine);
d->recoveryCheck();
844
d->enabledPluginsCheck();
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
}
/*!
Destroys the TransferEngine object.
*/
TransferEngine::~TransferEngine()
{
Q_D(TransferEngine);
d->recoveryCheck();
delete d_ptr;
d_ptr = 0;
QDBusConnection connection = QDBusConnection::sessionBus();
connection.unregisterObject("/org/nemo/transferengine");
if (!connection.unregisterService("org.nemo.transferengine")) {
861
qCWarning(lcTransferLog) << "Failed to unregister org.nemo.tranferengine service";
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
}
}
/*!
DBus adaptor calls this method to start uploading a media item. The minimum information
needed to start an upload and to create an entry to the transfer database is:
\a source the path to the media item to be downloaded. \a serviceId the ID of the share
plugin. See TransferPluginInterface::pluginId() for more details. \a mimeType is the MimeType
of the media item e.g. "image/jpeg". \a metadataStripped boolean to indicate if the metadata
should be kept or removed before uploading. \a userData is various kind of data which share UI
may provide to the engine. UserData is QVariant map i.e. the data must be provided as key-value
pairs, where the keys must be QStrings.
TransferEngine handles the following user defined data automatically and stores them to the database:
\list
878
879
880
881
\li "title" Title for the media
\li "description" Description for the media
\li "accountId" The ID of the account which is used for sharing. See qt-accounts for more details.
\li "scalePercent" The scale percent e.g. downscale image to 50% from original before uploading.
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
\endlist
In practice this method instantiates a share plugin with \a serviceId and passes a MediaItem instance filled
with required data to it. When the plugin has been loaded, the MediaTransferInterface::start() method is called
and the actual sharing starts.
This method returns a transfer ID which can be used later to fetch information of this specific transfer.
*/
int TransferEngine::uploadMediaItem(const QString &source,
const QString &serviceId,
const QString &mimeType,
bool metadataStripped,
const QVariantMap &userData)
{
Q_D(TransferEngine);
897
898
d->exitSafely();
899
900
MediaTransferInterface *muif = d->loadPlugin(serviceId);
if (muif == 0) {
901
qCWarning(lcTransferLog) << "TransferEngine::uploadMediaItem Failed to get MediaTransferInterface";
902
903
904
905
906
907
return -1;
}
QUrl filePath(source);
QFileInfo fileInfo(filePath.toLocalFile());
if (!fileInfo.exists()) {
908
qCWarning(lcTransferLog) << "TransferEnginePrivate::uploadMediaItem file " << source << " doesn't exist!";
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
}
MediaItem *mediaItem = new MediaItem(muif);
mediaItem->setValue(MediaItem::Url, filePath);
mediaItem->setValue(MediaItem::MetadataStripped, metadataStripped);
mediaItem->setValue(MediaItem::ResourceName, fileInfo.fileName());
mediaItem->setValue(MediaItem::MimeType, mimeType);
mediaItem->setValue(MediaItem::FileSize, fileInfo.size());
mediaItem->setValue(MediaItem::PluginId, serviceId);
mediaItem->setValue(MediaItem::UserData, userData);
return d->uploadMediaItem(mediaItem, muif, userData);
}
/*!
DBus adaptor calls this method to start uploading media item content. Sometimes the content
to be shared is not a file, but data e.g. contact information in vcard format. In order to
avoid serializing data to a file, pass url to the file, reading the data, deleting the file,
TransferEngine provides this convenience API.
\a content is the media item content to be shared. \a serviceId is the id of the share plugin. See
TransferPluginInterface::pluginId() for more details. \a userData is a QVariantMap containing
share plugin specific data. See TransferEngine::uploadMediaItem for more details.
This method returns a transfer ID which can be used later to fetch information of this specific transfer.
*/
int TransferEngine::uploadMediaItemContent(const QVariantMap &content,
const QString &serviceId,
const QVariantMap &userData)
{
Q_D(TransferEngine);
939
940
d->exitSafely();
941
942
MediaTransferInterface *muif = d->loadPlugin(serviceId);
if (muif == 0) {
943
qCWarning(lcTransferLog) << "TransferEngine::uploadMediaItemContent Failed to get MediaTransferInterface";
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
return -1;
}
MediaItem *mediaItem = new MediaItem(muif);
mediaItem->setValue(MediaItem::ContentData, content.value("data"));
mediaItem->setValue(MediaItem::ResourceName, content.value("name"));
mediaItem->setValue(MediaItem::MimeType, content.value("type"));
mediaItem->setValue(MediaItem::ThumbnailIcon, content.value("icon"));
mediaItem->setValue(MediaItem::PluginId, serviceId);
mediaItem->setValue(MediaItem::UserData, userData);
return d->uploadMediaItem(mediaItem, muif, userData);
}
/*!
DBus adaptor calls this method to create a download entry. Note that this is purely write-only
method and doesn't involve anything else from TransferEngine side than creating a new DB record
of type 'Download'.
\list
963
964
965
966
967
968
969
970
971
\li \a displayName The name for Download which may be used by the UI displaying the download
\li \a applicationIcon The application icon of the application created the download
\li \a serviceIcon The service icon, which provides the file to be downloaded
\li \a filePath The filePath e.g. url to the file to be downloaded
\li \a mimeType the MimeType of the file to be downloaded
\li \a expectedFileSize The file size of the file to be downloaded
\li \a callback QStringList containing DBus callback information such as: service, path and interface
\li \a cancelMethod The name of the cancel callback method, which DBus callback provides
\li \a restartMethod The name of the restart callback method, which DBus callback provides
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\endlist
This method returns the transfer id of the created Download transfer. Note that this method only
creates an entry to the database. To start the actual transfer, the startTransfer() method must
be called.
\sa startTransfer(), restartTransfer(), finishTransfer(), updateTransferProgress()
*/
int TransferEngine::createDownload(const QString &displayName,
const QString &applicationIcon,
const QString &serviceIcon,
const QString &filePath,
const QString &mimeType,
qlonglong expectedFileSize,
const QStringList &callback,
const QString &cancelMethod,
const QString &restartMethod)
{
Q_D(TransferEngine);
QUrl url(filePath);
QFileInfo fileInfo(filePath);
MediaItem *mediaItem = new MediaItem();
mediaItem->setValue(MediaItem::Url, url);
mediaItem->setValue(MediaItem::ResourceName, fileInfo.fileName());
mediaItem->setValue(MediaItem::MimeType, mimeType);
mediaItem->setValue(MediaItem::TransferType, TransferEngineData::Download);
mediaItem->setValue(MediaItem::FileSize, expectedFileSize);
mediaItem->setValue(MediaItem::DisplayName, displayName);