Commit 11c3fe71 authored by Pekka Vuorela's avatar Pekka Vuorela

[transfer-engine] Add progress info to notifications. Contributes to JB#31692

Extra categories are now also removed in favor of just defining the
properties explicitly. Switched to using freedesktop.org categories
just because they exist, no actual expected effect as of now.
parent b63f5b49
TEMPLATE = aux
notification_categories.path = /usr/share/lipstick/notificationcategories
notification_categories.files = notificationcategories/*.conf
INSTALLS += notification_categories
appIcon=icon-lock-transfer
x-nemo-icon=icon-lock-transfer
urgency=1
appIcon=icon-lock-transfer
x-nemo-icon=icon-lock-transfer
transient=true
urgency=1
......@@ -416,7 +416,7 @@ QSqlDatabase TransferModel::database()
TransferDatabase database;
database.setDatabaseName(absDbPath);
database.setConnectOptions(QLatin1String("QSQLITE_OPEN_READONLY")); // sanity check
thread_database.setLocalData(database);
thread_database.setLocalData(database);
}
QSqlDatabase &database = thread_database.localData();
......
......@@ -53,7 +53,6 @@ public:
MediaItem *mediaItem();
virtual QString displayName() const = 0;
virtual QUrl serviceIcon() const = 0;
virtual bool cancelEnabled() const = 0;
......
......@@ -32,9 +32,7 @@
class TransferDBRecord
{
public:
enum TransferDBRecordField {
TransferID = 0,
TransferType,
......
......@@ -182,7 +182,7 @@ QVariant TransferMethodInfo::value(int index) const
return accountId;
case AccountIcon:
return accountIcon;
default:
default:
return QVariant();
}
}
......
......@@ -36,7 +36,6 @@ Obsoletes: nemo-transferengine <= 0.0.19
%{_bindir}/nemo-transfer-engine
%{_datadir}/dbus-1/services/org.nemo.transferengine.service
%{_datadir}/translations/nemo-transfer-engine_eng_en.qm
%{_datadir}/lipstick/notificationcategories/*
%package -n libnemotransferengine-qt5
Summary: Transfer engine library.
......
......@@ -81,7 +81,8 @@
"strip_metadata INTEGER,\n" \
"scale_percent REAL,\n" \
"cancel_supported INTEGER,\n" \
"restart_supported INTEGER\n" \
"restart_supported INTEGER,\n" \
"notification_id INTEGER\n" \
");\n"
// Cascade trigger i.e. when transfer is removed and it has metadata or callbacks, this
......@@ -95,7 +96,7 @@
"END;\n"
// Update the following version if database schema changes.
#define USER_VERSION 1
#define USER_VERSION 2
#define PRAGMA_USER_VERSION QString("PRAGMA user_version=%1").arg(USER_VERSION)
class DbManagerPrivate {
......@@ -242,11 +243,28 @@ DbManager::DbManager():
// Create database schema if db didn't exist
if (!dbExists) {
if(!d->createDatabaseSchema()) {
if (!d->createDatabaseSchema()) {
qCritical("DbManager::DbManager: Failed to create DB schema. Can't continue!");
}
} else {
// Database exists, check the schema version
if (d->userVersion() == 1) {
// For this we get away with DeclarativeTransferModel directly reading database without
// update because notification_id is the last column
QSqlQuery query;
if (query.exec("ALTER TABLE transfers ADD COLUMN notification_id INTEGER")) {
qWarning() << "Extended transfers table";
if (!query.exec(PRAGMA_USER_VERSION)) {
qWarning() << "DbManager pragma user_version update:"
<< query.lastError().text() << ":" << query.lastError().databaseText();
}
} else {
qWarning() << "Failed to extend transfers table!"
<< query.lastError().text() << ":" << query.lastError().databaseText();
}
}
if (d->userVersion() != USER_VERSION) {
d->deleteOldTables();
d->createDatabaseSchema();
......@@ -408,9 +426,11 @@ int DbManager::createTransferEntry(const MediaItem *mediaItem)
Q_D(DbManager);
QSqlQuery query;
query.prepare("INSERT INTO transfers (transfer_type, timestamp, status, progress, display_name, application_icon, thumbnail_icon, "
"service_icon, url, resource_name, mime_type, file_size, plugin_id, account_id, strip_metadata, scale_percent, cancel_supported, restart_supported)"
"VALUES (:transfer_type, :timestamp, :status, :progress, :display_name, :application_icon, :thumbnail_icon, :service_icon, "
":url, :resource_name, :mime_type, :file_size, :plugin_id, :account_id, :strip_metadata, :scale_percent, :cancel_supported, :restart_supported)");
" service_icon, url, resource_name, mime_type, file_size, plugin_id, account_id, strip_metadata, scale_percent, "
" cancel_supported, restart_supported, notification_id)"
"VALUES (:transfer_type, :timestamp, :status, :progress, :display_name, :application_icon, :thumbnail_icon, "
" :service_icon, :url, :resource_name, :mime_type, :file_size, :plugin_id, :account_id, :strip_metadata, :scale_percent, "
" :cancel_supported, :restart_supported, :notification_id)");
query.bindValue(":transfer_type", mediaItem->value(MediaItem::TransferType));
query.bindValue(":status", TransferEngineData::NotStarted);
query.bindValue(":timestamp", d->currentDateTime());
......@@ -429,9 +449,10 @@ int DbManager::createTransferEntry(const MediaItem *mediaItem)
query.bindValue(":scale_percent", mediaItem->value(MediaItem::ScalePercent));
query.bindValue(":cancel_supported", mediaItem->value(MediaItem::CancelSupported));
query.bindValue(":restart_supported", mediaItem->value(MediaItem::RestartSupported));
query.bindValue(":notification_id", 0);
if (!query.exec()) {
qWarning() << "DbManager::createTransfereEntry: Failed to execute SQL query. Couldn't create an entry!"
qWarning() << "DbManager::createTransferEntry: Failed to execute SQL query. Couldn't create an entry!"
<< query.lastError().text() << ": "
<< query.lastError().databaseText();
return -1;
......@@ -771,6 +792,66 @@ TransferEngineData::TransferStatus DbManager::transferStatus(int key) const
return TransferEngineData::Unknown;
}
}
/*!
Returns the transfer progress or -1 if unavailable
*/
qreal DbManager::transferProgress(int key) const
{
QString queryStr = QString("SELECT progress FROM transfers WHERE transfer_id='%1';").arg(QString::number(key));
QSqlQuery query;
if (!query.exec(queryStr)) {
qWarning() << "DbManager::transferStatus: Failed to execute SQL query. Couldn't get the progress!"
<< query.lastError().text() << ": "
<< query.lastError().databaseText();
return -1;
}
if (query.isActive() && query.isSelect()) {
query.first();
return static_cast<qreal>(query.value(0).toReal());
} else {
return -1;
}
}
int DbManager::notificationId(int key)
{
QString queryStr = QString("SELECT notification_id FROM transfers WHERE transfer_id='%1';").arg(QString::number(key));
QSqlQuery query;
if (!query.exec(queryStr)) {
qWarning() << "DbManager::transferStatus: Failed to execute SQL query. Couldn't get the notification id!"
<< query.lastError().text() << ": "
<< query.lastError().databaseText();
return 0;
}
if (query.isActive() && query.isSelect()) {
query.first();
return static_cast<qreal>(query.value(0).toReal());
} else {
return 0;
}
}
bool DbManager::setNotificationId(int key, int notificationId)
{
QString queryStr = QString("UPDATE transfers SET notification_id='%1' WHERE transfer_id='%2';")
.arg(QString::number(notificationId)).arg(QString::number(key));
QSqlQuery query;
if (!query.exec(queryStr)) {
qWarning() << "Failed to execute SQL query. Couldn't update the notification id!"
<< query.lastError().text() << ": "
<< query.lastError().databaseText();
return false;
}
query.finish();
return true;
}
/*!
Reads the callback method names from the database for the transfer with \a key. The method names are set to the
output arguments \a cancelMethod and \a restartMethod.
......
......@@ -77,6 +77,11 @@ public:
TransferEngineData::TransferStatus transferStatus(int key) const;
qreal transferProgress(int key) const;
int notificationId(int key);
bool setNotificationId(int key, int notificationId);
bool callbackMethods(int key, QString &cancelMethod, QString &restartMethod) const;
MediaItem * mediaItem(int key) const;
......
......@@ -52,9 +52,11 @@
#define ACTIVITY_MONITOR_TIMEOUT 1*60*1000 // 1 minute in ms
#define TRANSFER_EXPIRATION_THRESHOLD 3*60 // 3 minutes in seconds
#define TRANSFER_EVENT_CATEGORY "x-nemo.transfer"
#define TRANSFER_COMPLETE_EVENT_CATEGORY "x-nemo.transfer.complete"
#define TRANSFER_ERROR_EVENT_CATEGORY "x-nemo.transfer.error"
#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"
TransferEngineSignalHandler * TransferEngineSignalHandler::instance()
{
......@@ -299,7 +301,9 @@ void TransferEnginePrivate::recoveryCheck()
void TransferEnginePrivate::sendNotification(TransferEngineData::TransferType type,
TransferEngineData::TransferStatus status,
const QString &fileName)
qreal progress,
const QString &fileName,
int transferId)
{
if (!m_notificationsEnabled || fileName.isEmpty()) {
return;
......@@ -310,6 +314,10 @@ void TransferEnginePrivate::sendNotification(TransferEngineData::TransferType ty
QString summary;
QString previewBody;
QString previewSummary;
bool useProgress = false;
Notification::Urgency urgency = Notification::Normal;
QString appIcon = QStringLiteral("icon-lock-information");
QString icon = QStringLiteral("x-nemo-icon=icon-lock-transfer");
// TODO: explicit grouping of transfer notifications is now removed, as grouping
// will now be performed by lipstick. We may need to reinstate group summary
......@@ -325,18 +333,8 @@ void TransferEnginePrivate::sendNotification(TransferEngineData::TransferType ty
// - For downloads
// - For failed uploads, downloads and syncs
QList<QObject *> nList = Notification::notifications();
Notification *existing = 0;
foreach (QObject *obj, nList) {
if (Notification *n = qobject_cast<Notification *>(obj)) {
if (n->summary() == fileName || n->previewSummary() == fileName) {
// This existing notification is for this file
existing = n;
break;
}
}
}
int notificationId = DbManager::instance()->notificationId(transferId);
if (status == TransferEngineData::TransferFinished) {
switch(type) {
......@@ -363,8 +361,11 @@ void TransferEnginePrivate::sendNotification(TransferEngineData::TransferType ty
qWarning() << "TransferEnginePrivate::sendNotification: unknown state";
break;
}
} else if (status == TransferEngineData::TransferInterrupted) {
category = TRANSFER_ERROR_EVENT_CATEGORY;
urgency = Notification::Critical;
appIcon = QStringLiteral("icon-lock-information");
icon.clear();
switch (type) {
case TransferEngineData::Upload:
......@@ -390,37 +391,58 @@ void TransferEnginePrivate::sendNotification(TransferEngineData::TransferType ty
summary = fileName;
previewSummary = summary;
previewBody = body;
} else if (status == TransferEngineData::TransferCanceled) {
// Exit, no banners or events when user has canceled a transfer
// Remove any existing notification
if (existing) {
existing->close();
} 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;
}
} 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;
}
if (!category.isEmpty()) {
Notification notification;
if (!existing) {
// Create a new notification
if (m_settings.status() != QSettings::NoError) {
qWarning() << Q_FUNC_INFO << "Failed to read settings!" << m_settings.status();
} else {
m_settings.beginGroup("transfers");
const QString service = m_settings.value("service").toString();
const QString path = m_settings.value("path").toString();
const QString iface = m_settings.value("interface").toString();
const QString method = m_settings.value("method").toString();
m_settings.endGroup();
if (!service.isEmpty() && !path.isEmpty() && !iface.isEmpty() && !method.isEmpty()) {
notification.setRemoteActions(QVariantList() << Notification::remoteAction("default", "", service, path, iface, method)
<< Notification::remoteAction("app", "", service, path, iface, method));
}
}
if (notificationId > 0) {
notification.setReplacesId(notificationId);
}
existing = &notification;
if (m_settings.status() != QSettings::NoError) {
qWarning() << Q_FUNC_INFO << "Failed to read settings!" << m_settings.status();
} else {
m_settings.beginGroup("transfers");
const QString service = m_settings.value("service").toString();
const QString path = m_settings.value("path").toString();
const QString iface = m_settings.value("interface").toString();
const QString method = m_settings.value("method").toString();
m_settings.endGroup();
if (!service.isEmpty() && !path.isEmpty() && !iface.isEmpty() && !method.isEmpty()) {
notification.setRemoteActions(QVariantList() << Notification::remoteAction("default", "", service, path, iface, method)
<< Notification::remoteAction("app", "", service, path, iface, method));
}
}
//: Group name for notifications of successful transfers
......@@ -430,17 +452,24 @@ void TransferEnginePrivate::sendNotification(TransferEngineData::TransferType ty
//% "Warnings"
const QString errorsGroup(qtTrId("transferengine-notification_errors_group"));
// Update the notification
existing->setCategory(category);
existing->setAppName(category == TRANSFER_ERROR_EVENT_CATEGORY ? errorsGroup : transfersGroup);
existing->setSummary(summary);
existing->setBody(body);
existing->setPreviewSummary(previewSummary);
existing->setPreviewBody(previewBody);
existing->publish();
}
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);
qDeleteAll(nList);
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);
}
}
}
int TransferEnginePrivate::uploadMediaItem(MediaItem *mediaItem,
......@@ -449,7 +478,7 @@ int TransferEnginePrivate::uploadMediaItem(MediaItem *mediaItem,
{
Q_Q(TransferEngine);
if (muif == 0) {
if (mediaItem == 0) {
qWarning() << "TransferEngine::uploadMediaItem invalid MediaItem";
return -1;
}
......@@ -586,7 +615,7 @@ void TransferEnginePrivate::uploadItemStatusChanged(MediaTransferInterface::Tran
// 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();
sendNotification(type, tStatus, mediaFileOrResourceName(muif->mediaItem()));
sendNotification(type, tStatus, muif->progress(), mediaFileOrResourceName(muif->mediaItem()), key);
ok = DbManager::instance()->updateTransferStatus(key, tStatus);
if (m_plugins.remove(muif) == 0) {
qWarning() << "TransferEnginePrivate::uploadItemStatusChanged: Failed to remove media upload object from the map!";
......@@ -623,7 +652,7 @@ void TransferEnginePrivate::updateProgress(qreal progress)
m_activityMonitor->newActivity(key);
Q_Q(TransferEngine);
emit q->progressChanged(key, progress);
q->updateTransferProgress(key, progress);
}
void TransferEnginePrivate::pluginInfoReady()
......@@ -673,7 +702,6 @@ TransferEngineData::TransferType TransferEnginePrivate::transferType(int transfe
}
}
void TransferEnginePrivate::callbackCall(int transferId, CallbackMethodType method)
{
// Get DBus callback information. Callback list must contain at least
......@@ -1145,7 +1173,7 @@ void TransferEngine::finishTransfer(int transferId, int status, const QString &r
transferStatus == TransferEngineData::TransferCanceled ||
transferStatus == TransferEngineData::TransferInterrupted) {
DbManager::instance()->updateTransferStatus(transferId, transferStatus);
d->sendNotification(type, transferStatus, fileName);
d->sendNotification(type, transferStatus, DbManager::instance()->transferProgress(transferId), fileName, transferId);
d->m_activityMonitor->activityFinished(transferId);
emit statusChanged(transferId, status);
......@@ -1183,13 +1211,25 @@ void TransferEngine::updateTransferProgress(int transferId, double progress)
d->exitSafely();
TransferEngineData::TransferType type = d->transferType(transferId);
if (type == TransferEngineData::Undefined || type == TransferEngineData::Upload) {
if (type != TransferEngineData::Download && type != TransferEngineData::Upload) {
return;
}
MediaItem *mediaItem = DbManager::instance()->mediaItem(transferId);
if (!mediaItem) {
qWarning() << "TransferEngine::finishTransfer: Failed to fetch MediaItem";
return;
}
QString fileName = d->mediaFileOrResourceName(mediaItem);
int oldProgressPercentage = DbManager::instance()->transferProgress(transferId) * 100;
if (DbManager::instance()->updateProgress(transferId, progress)) {
d->m_activityMonitor->newActivity(transferId);
emit progressChanged(transferId, progress);
if (oldProgressPercentage != (progress * 100)) {
d->sendNotification(type, DbManager::instance()->transferStatus(transferId), progress, fileName, transferId);
}
} else {
qWarning() << "TransferEngine::updateTransferProgress: Failed to update progress for " << transferId;
}
......@@ -1313,7 +1353,7 @@ void TransferEngine::cancelTransfer(int transferId)
}
}
/*!
DBus adaptor calls this method to enable or disable transfer speicific notifications
DBus adaptor calls this method to enable or disable transfer specific notifications
based on \a enable argument.
*/
void TransferEngine::enableNotifications(bool enable)
......
......@@ -105,7 +105,9 @@ public:
void recoveryCheck();
void sendNotification(TransferEngineData::TransferType type,
TransferEngineData::TransferStatus status,
const QString &fileName);
qreal progress,
const QString &fileName,
int transferId);
int uploadMediaItem(MediaItem *mediaItem,
MediaTransferInterface *muif,
const QVariantMap &userData);
......
TEMPLATE = subdirs
SUBDIRS = lib src declarative tests data
SUBDIRS = lib src declarative tests
src.depends = lib
tests.depends = lib
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment