diff --git a/declarative/declarative.pro b/declarative/declarative.pro new file mode 100644 index 0000000..b6aa979 --- /dev/null +++ b/declarative/declarative.pro @@ -0,0 +1,38 @@ +TEMPLATE = lib +QT += dbus sql +CONFIG += qt plugin + +INCLUDEPATH += ../lib +LIBS += -lnemotransferengine-qt5 -L../lib + +TARGET = declarativetransferengine +QT += qml + +uri = org.nemomobile.transferengine + + +# Input +SOURCES += \ + plugin.cpp \ + declarativetransfermodel.cpp + +HEADERS += \ + synchronizelists_p.h \ + declarativetransfermodel.h + +OTHER_FILES = qmldir *.qml *.js + +!equals(_PRO_FILE_PWD_, $$OUT_PWD) { + copy_qmldir.target = $$OUT_PWD/qmldir + copy_qmldir.depends = $$_PRO_FILE_PWD_/qmldir + copy_qmldir.commands = $(COPY_FILE) \"$$replace(copy_qmldir.depends, /, $$QMAKE_DIR_SEP)\" \"$$replace(copy_qmldir.target, /, $$QMAKE_DIR_SEP)\" + QMAKE_EXTRA_TARGETS += copy_qmldir + PRE_TARGETDEPS += $$copy_qmldir.target +} + +target.path = $$[QT_INSTALL_QML]/$$replace(uri, \\., /) + +qmldir.files = qmldir *.qml *.js +qmldir.path = $$target.path + +INSTALLS += target qmldir diff --git a/declarative/declarativetransfermodel.cpp b/declarative/declarativetransfermodel.cpp new file mode 100644 index 0000000..3d21100 --- /dev/null +++ b/declarative/declarativetransfermodel.cpp @@ -0,0 +1,421 @@ +/**************************************************************************************** +** +** Copyright (C) 2014 Jolla Ltd. +** Contact: Marko Mattila +** All rights reserved. +** +** This file is part of Nemo 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. +** +****************************************************************************************/ + +#include "declarativetransfermodel.h" +#include "synchronizelists_p.h" +#include "transferdbrecord.h" + +#include +#include +#include + +#include +#include +#include +#include + +#define DB_PATH ".local/nemo-transferengine" +#define DB_NAME "transferdb.sqlite" + +template <> bool compareIdentity( + const TransferDBRecord &item, const TransferDBRecord &reference) +{ + return item.transfer_id == reference.transfer_id; +} + +TransferModel::TransferModel(QObject *parent) + : QAbstractListModel(parent) + , m_rows(new QVector()) + , m_rowsIndex(0) + , m_asyncIndex(0) + , m_transfersInProgress(0) + , m_status(Null) + , m_asyncStatus(Null) + , m_asyncPending(false) + , m_asyncRunning(false) + , m_notified(false) + , m_complete(false) + , m_rowsChanges(false) +{ + setAutoDelete(false); +} + +TransferModel::~TransferModel() +{ + QMutexLocker locker(&m_mutex); + + if (m_asyncRunning) { + m_condition.wait(&m_mutex); + } + + delete m_rows; +} + +TransferModel::Status TransferModel::status() const +{ + return m_status; +} + +void TransferModel::refresh() +{ + if (!m_complete || m_roles.isEmpty()) { + return; + } + + QMutexLocker locker(&m_mutex); + + if (!m_asyncRunning) { + m_asyncRunning = true; + QThreadPool::globalInstance()->start(static_cast(this)); + } + m_status = Querying; + m_asyncStatus = Querying; + m_asyncPending = true; + + locker.unlock(); + + if (m_status != Querying) { + m_status = Querying; + emit statusChanged(); + } +} + +QJSValue TransferModel::get(int index) const +{ + QJSValue properties; + QQmlEngine *engine = qmlEngine(this); + + if (engine && index >= 0 && index < m_rows->count()) { + properties = engine->newObject(); + const TransferDBRecord &row = m_rows->at(index); + + foreach (const int &key, m_roles.keys()) { + const QString name = m_roles.value(key); + if (!name.isEmpty() && key >= 0) { + properties.setProperty(name, engine->toScriptValue(row.value(key))); + } + } + } + return properties; +} + +void TransferModel::clearTransfers() +{ + if (m_client) { + m_client->clearTransfers(); + } +} + +QHash TransferModel::roleNames() const +{ + return m_roles; +} + +int TransferModel::rowCount(const QModelIndex &parent) const +{ + return !parent.isValid() ? m_rows->count() : 0; +} + +QModelIndex TransferModel::index(int row, int column, const QModelIndex &parent) const +{ + return !parent.isValid() && row >= 0 && row < m_rows->count() && column == 0 + ? createIndex(row, column) + : QModelIndex(); +} + +QVariant TransferModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || role < 0) { + return QVariant(); + } else if (role < m_roles.count()) { + return m_rows->at(index.row()).value(role); + } else { + return QVariant(); + } +} + +void TransferModel::classBegin() +{ +} + +void TransferModel::componentComplete() +{ + m_complete = true; + + m_roles[TransferDBRecord::TransferID] = "transferId"; + m_roles[TransferDBRecord::TransferType] = "transferType"; + m_roles[TransferDBRecord::Progress] = "progress"; + m_roles[TransferDBRecord::URL] = "url"; + m_roles[TransferDBRecord::Status] = "status"; + m_roles[TransferDBRecord::PluginID] = "pluginId"; + m_roles[TransferDBRecord::Timestamp] = "timestamp"; + m_roles[TransferDBRecord::DisplayName] = "displayName"; + m_roles[TransferDBRecord::ResourceName] = "resourceName"; + m_roles[TransferDBRecord::MimeType] = "mimeType"; + m_roles[TransferDBRecord::FileSize] = "fileSize"; + m_roles[TransferDBRecord::ServiceIcon] = "serviceIcon"; + m_roles[TransferDBRecord::ApplicationIcon] = "applicationIcon"; + m_roles[TransferDBRecord::ThumbnailIcon] = "thumbnailIcon"; + m_roles[TransferDBRecord::CancelSupported] = "cancelEnabled"; + m_roles[TransferDBRecord::RestartSupported] = "restartEnabled"; + + refresh(); + + m_client = new TransferEngineInterface("org.nemo.transferengine", + "/org/nemo/transferengine", + QDBusConnection::sessionBus(), + this); + + connect(m_client, SIGNAL(progressChanged(int,double)), + this, SLOT(refresh())); + connect(m_client, SIGNAL(transfersChanged()), + this, SLOT(refresh())); + connect(m_client, SIGNAL(statusChanged(int,int)), + this, SLOT(refresh())); +} + +void TransferModel::insertRange( + int index, int count, const QVector &source, int sourceIndex) +{ + if (m_rowsChanges) { + beginInsertRows(QModelIndex(), index, index + count - 1); + + for (int i = 0; i < count; ++i) { + m_rows->insert(index + i, source.at(sourceIndex + i)); + } + + endInsertRows(); + emit countChanged(); + } +} + +void TransferModel::updateRange( + int index, int count, const QVector &source, int sourceIndex) +{ + for (int i = 0; i < count; ++i) { + const int begin = i; + while (i < count + && m_rows->at(i).transfer_id == source.at(sourceIndex + i).transfer_id + && (m_rows->at(i).progress != source.at(sourceIndex + i).progress + || m_rows->at(i).status != source.at(sourceIndex + i).status)) { + + TransferDBRecord &destinationRow = (*m_rows)[index + i]; + const TransferDBRecord &sourceRow = source.at(sourceIndex + i); + + destinationRow = sourceRow; + + ++i; + } + + if (begin != i) { + emit dataChanged(createIndex(index + begin, 0), createIndex(index + i - 1, 0)); + } + } +} + +void TransferModel::removeRange(int index, int count) +{ + if (m_rowsChanges) { + beginRemoveRows(QModelIndex(), index, index + count - 1); + + m_rows->remove(index, count); + + endRemoveRows(); + emit countChanged(); + } +} + +bool TransferModel::event(QEvent *event) +{ + if (event->type() == QEvent::UpdateRequest) { + QMutexLocker locker(&m_mutex); + + const Status previousStatus = m_status; + const QString errorString = m_asyncErrorString; + m_status = m_asyncStatus; + m_notified = false; + + const QVector rows = m_asyncRows; + + locker.unlock(); + + m_rowsChanges = true; + synchronizeList(this, *m_rows, m_rowsIndex, rows, m_asyncIndex); + + if (m_status >= Finished) { + completeSynchronizeList(this, *m_rows, m_rowsIndex, rows, m_asyncIndex); + } + m_rowsChanges = false; + + if (m_asyncTransfersInProgress != m_transfersInProgress) { + m_transfersInProgress = m_asyncTransfersInProgress; + emit transfersInProgressChanged(); + } + + if (previousStatus != m_status) { + if (m_status == Error) { + qmlInfo(this) << errorString; + } + emit statusChanged(); + } + + return true; + } else { + return QAbstractListModel::event(event); + } +} + +int TransferModel::transfersInProgress() const +{ + return m_transfersInProgress; +} + +void TransferModel::run() +{ + QMutexLocker locker(&m_mutex); + + for (;;) { + if (m_asyncStatus == Querying) { + m_asyncPending = false; + if (m_status == Null) { + m_asyncStatus = Null; + continue; + } + + locker.unlock(); + + QVector rows; + QString errorString; + int activeTransfers = 0; + const bool ok = executeQuery(&rows, &activeTransfers, &errorString); + + locker.relock(); + + m_asyncRows = rows; + m_asyncTransfersInProgress = activeTransfers; + + if (!m_asyncPending) { + m_asyncErrorString = errorString; + m_asyncStatus = ok ? Finished : Error; + } + } else { + m_asyncRunning = false; + if (!m_notified) { + m_notified = true; + QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); + } + m_condition.wakeOne(); + return; + } + } +} + + +bool TransferModel::executeQuery(QVector *rows, int *activeTransfers, QString *errorString) +{ + // Query items from the database + QSqlDatabase db = database(); + if (!db.isValid()) { + qWarning() << Q_FUNC_INFO << "Invalid database!"; + return false; + } + + QSqlQuery query(db); + query.setForwardOnly(true); + + if (!query.exec(QString(QStringLiteral("SELECT * FROM transfers ORDER BY transfer_id DESC")))) { + qWarning() << Q_FUNC_INFO; + qWarning() << "Failed to create tmp table"; + qWarning() << query.lastQuery(); + qWarning() << query.lastError().text(); + *errorString = query.lastError().text(); + return false; + } + + *activeTransfers = 0; + while (query.next()) { + int i = 0; + TransferDBRecord record; + record.transfer_id = query.value(i++).toInt(); + record.transfer_type = query.value(i++).toInt(); + record.timestamp = query.value(i++).toString(); + record.status = query.value(i++).toInt(); + record.progress = query.value(i++).toDouble(); + record.display_name = query.value(i++).toString(); + record.application_icon = query.value(i++).toString(); + record.thumbnail_icon = query.value(i++).toString(); + record.service_icon = query.value(i++).toString(); + record.url = query.value(i++).toString(); + record.resource_name = query.value(i++).toString(); + record.mime_type = query.value(i++).toString(); + record.size = query.value(i++).toInt(); + record.plugin_id = query.value(i++).toString(); + i++; // account id + i++; // strip metadata + i++; // scale percent + record.cancel_supported = query.value(i++).toBool(); + record.restart_supported = query.value(i++).toBool(); + + if (record.status == TransferModel::TransferStarted) { + ++(*activeTransfers); + } + + rows->append(record); + } + + QMutexLocker locker(&m_mutex); + m_asyncRows = *rows; + return true; +} + + +QSqlDatabase TransferModel::database() +{ + static QThreadStorage thread_database; + + if (!thread_database.hasLocalData()) { + const QString absDbPath = QDir::homePath() + QDir::separator() + + DB_PATH + QDir::separator() + + DB_NAME; + + const QString uuid = QUuid::createUuid().toString(); + QSqlDatabase database = QSqlDatabase::addDatabase( + QLatin1String("QSQLITE"), + QLatin1String("transferengine-") + uuid); + database.setDatabaseName(absDbPath); + database.setConnectOptions(QLatin1String("QSQLITE_OPEN_READONLY")); // sanity check + thread_database.setLocalData(database); + } + + QSqlDatabase &database = thread_database.localData(); + if (!database.isOpen() && !database.open()) { + qWarning() << "Failed to open transfer engine database"; + qWarning() << database.lastError(); + return QSqlDatabase(); + } + + return database; +} diff --git a/declarative/declarativetransfermodel.h b/declarative/declarativetransfermodel.h new file mode 100644 index 0000000..f0a2683 --- /dev/null +++ b/declarative/declarativetransfermodel.h @@ -0,0 +1,143 @@ +/**************************************************************************************** +** +** Copyright (C) 2014 Jolla Ltd. +** Contact: Marko Mattila +** All rights reserved. +** +** This file is part of Nemo 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. +** +****************************************************************************************/ + +#ifndef DECLARATIVETRANSFERMODEL_HH +#define DECLARATIVETRANSFERMODEL_HH + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "transferengineinterface.h" +#include "transfertypes.h" + + +class TransferModel + : public QAbstractListModel + , public QQmlParserStatus + , private QRunnable +{ + Q_OBJECT + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + Q_PROPERTY(int transfersInProgress READ transfersInProgress NOTIFY transfersInProgressChanged) + Q_ENUMS(Status) + Q_ENUMS(TransferStatus) + Q_ENUMS(TransferType) + Q_INTERFACES(QQmlParserStatus) +public: + enum Status { + Null, + Querying, + Finished, + Error + }; + + enum TransferStatus { + NotStarted = TransferEngineData::NotStarted, + TransferStarted = TransferEngineData::TransferStarted, + TransferCanceled = TransferEngineData::TransferCanceled, + TransferFinished = TransferEngineData::TransferFinished, + TransferInterrupted = TransferEngineData::TransferInterrupted + }; + + enum TransferType { + Upload = TransferEngineData::Upload, + Download = TransferEngineData::Download, + Sync = TransferEngineData::Sync + }; + + TransferModel(QObject *parent = 0); + ~TransferModel(); + + Status status() const; + + Q_INVOKABLE QJSValue get(int index) const; + Q_INVOKABLE void clearTransfers(); + + QHash roleNames() const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + + void classBegin(); + void componentComplete(); + + void insertRange(int index, int count, const QVector &source, int sourceIndex); + void updateRange(int index, int count, const QVector &source, int sourceIndex); + void removeRange(int index, int count); + + bool event(QEvent *event); + + int transfersInProgress() const; + +public slots: + void refresh(); + +signals: + void queryChanged(); + void classNamesChanged(); + void statusChanged(); + void countChanged(); + void transfersInProgressChanged(); + +private: + void run(); + bool executeQuery(QVector *rows, int *activeTransfers, QString *errorString); + + QSqlDatabase database(); + + QString m_asyncErrorString; + QVector m_asyncRows; + QVector *m_rows; + QHash m_roles; + + QMutex m_mutex; + QWaitCondition m_condition; + + int m_rowsIndex; + int m_asyncIndex; + int m_asyncTransfersInProgress; + int m_transfersInProgress; + + Status m_status; + Status m_asyncStatus; + bool m_asyncPending; + bool m_asyncRunning; + bool m_notified; + bool m_complete; + bool m_rowsChanges; + + TransferEngineInterface *m_client; +}; + +#endif diff --git a/declarative/plugin.cpp b/declarative/plugin.cpp new file mode 100644 index 0000000..e17f7ee --- /dev/null +++ b/declarative/plugin.cpp @@ -0,0 +1,57 @@ +/**************************************************************************************** +** +** Copyright (C) 2014 Jolla Ltd. +** Contact: Marko Mattila +** All rights reserved. +** +** This file is part of Nemo 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. +** +****************************************************************************************/ + +#include +#include +#include +#include + +#include "declarativetransfermodel.h" + +class Q_DECL_EXPORT DeclarativePlugin: public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.nemomobile.transferengine") + +public: + + void initializeEngine(QQmlEngine *engine, const char *uri) + { + Q_UNUSED(engine) + Q_UNUSED(uri) + Q_ASSERT(QLatin1String(uri) == QLatin1String("org.nemomobile.transferengine")); + + } + + void registerTypes(const char *uri) + { + Q_ASSERT(QLatin1String(uri) == QLatin1String("org.nemomobile.transferengine")); + + qmlRegisterType(uri, 1, 0, "TransferModel"); + } +}; + +#include "plugin.moc" diff --git a/declarative/qmldir b/declarative/qmldir new file mode 100644 index 0000000..c770c02 --- /dev/null +++ b/declarative/qmldir @@ -0,0 +1,3 @@ +module org.nemomobile.transferengine +plugin declarativetransferengine + diff --git a/declarative/synchronizelists_p.h b/declarative/synchronizelists_p.h new file mode 100644 index 0000000..eec0bdf --- /dev/null +++ b/declarative/synchronizelists_p.h @@ -0,0 +1,216 @@ +/**************************************************************************************** +** +** Copyright (C) 2014 Jolla Ltd. +** Contact: Andrew den Exter +** All rights reserved. +** +** This file is part of Nemo 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. +** +****************************************************************************************/ + +#ifndef SYNCHRONIZELISTS_P_H +#define SYNCHRONIZELISTS_P_H + +template +bool compareIdentity(const T &item, const T &reference) +{ + return item == reference; +} + +template +int insertRange(Agent *agent, int index, int count, const ReferenceList &source, int sourceIndex) +{ + agent->insertRange(index, count, source, sourceIndex); + return count; +} + +template +int removeRange(Agent *agent, int index, int count) +{ + agent->removeRange(index, count); + return 0; +} + +template +int updateRange(Agent *agent, int index, int count, const ReferenceList &source, int sourceIndex) +{ + agent->updateRange(index, count, source, sourceIndex); + return count; +} + +template +class SynchronizeList +{ +public: + SynchronizeList( + Agent *agent, + const CacheList &cache, + int &c, + const ReferenceList &reference, + int &r) + : agent(agent), cache(cache), c(c), reference(reference), r(r) + { + int lastEqualC = c; + int lastEqualR = r; + for (; c < cache.count() && r < reference.count(); ++c, ++r) { + if (compareIdentity(cache.at(c), reference.at(r))) { + continue; + } + + if (c > lastEqualC) { + lastEqualC += updateRange(agent, lastEqualC, c - lastEqualC, reference, lastEqualR); + c = lastEqualC; + lastEqualR = r; + } + + bool match = false; + + // Iterate through both the reference and cache lists in parallel looking for first + // point of commonality, when that is found resolve the differences and continue + // looking. + int count = 1; + for (; !match && c + count < cache.count() && r + count < reference.count(); ++count) { + typename CacheList::const_reference cacheItem = cache.at(c + count); + typename ReferenceList::const_reference referenceItem = reference.at(r + count); + + for (int i = 0; i <= count; ++i) { + if (cacheMatch(i, count, referenceItem) || referenceMatch(i, count, cacheItem)) { + match = true; + break; + } + } + } + + // Continue scanning the reference list if the cache has been exhausted. + for (int re = r + count; !match && re < reference.count(); ++re) { + typename ReferenceList::const_reference referenceItem = reference.at(re); + for (int i = 0; i < count; ++i) { + if (cacheMatch(i, re - r, referenceItem)) { + match = true; + break; + } + } + } + + // Continue scanning the cache if the reference list has been exhausted. + for (int ce = c + count; !match && ce < cache.count(); ++ce) { + typename CacheList::const_reference cacheItem = cache.at(ce); + for (int i = 0; i < count; ++i) { + if (referenceMatch(i, ce - c, cacheItem)) { + match = true; + break; + } + } + } + + if (!match) + return; + + lastEqualC = c; + lastEqualR = r; + } + + if (c > lastEqualC) { + updateRange(agent, lastEqualC, c - lastEqualC, reference, lastEqualR); + } + } + +private: + // Tests if the cached id at i matches a referenceId. + // If there is a match removes all items traversed in the cache since the previous match + // and inserts any items in the reference set found to to not be in the cache. + bool cacheMatch(int i, int count, typename ReferenceList::const_reference referenceItem) + { + if (compareIdentity(cache.at(c + i), referenceItem)) { + if (i > 0) + c += removeRange(agent, c, i); + c += insertRange(agent, c, count, reference, r); + r += count; + return true; + } else { + return false; + } + } + + // Tests if the referenceid at i matches a cacheId. + // If there is a match inserts all items traversed in the reference set since the + // previous match and removes any items from the cache that were not found in the + // reference list. + bool referenceMatch(int i, int count, typename ReferenceList::const_reference cacheItem) + { + if (compareIdentity(reference.at(r + i), cacheItem)) { + c += removeRange(agent, c, count); + if (i > 0) + c += insertRange(agent, c, i, reference, r); + r += i; + return true; + } else { + return false; + } + } + + Agent * const agent; + const CacheList &cache; + int &c; + const ReferenceList &reference; + int &r; +}; + +template +void completeSynchronizeList( + Agent *agent, + const CacheList &cache, + int &cacheIndex, + const ReferenceList &reference, + int &referenceIndex) +{ + if (cacheIndex < cache.count()) { + agent->removeRange(cacheIndex, cache.count() - cacheIndex); + } + if (referenceIndex < reference.count()) { + agent->insertRange(cache.count(), reference.count() - referenceIndex, reference, referenceIndex); + } + + cacheIndex = 0; + referenceIndex = 0; +} + +template +void synchronizeList( + Agent *agent, + const CacheList &cache, + int &cacheIndex, + const ReferenceList &reference, + int &referenceIndex) +{ + SynchronizeList( + agent, cache, cacheIndex, reference, referenceIndex); +} + +template +void synchronizeList(Agent *agent, const CacheList &cache, const ReferenceList &reference) +{ + int cacheIndex = 0; + int referenceIndex = 0; + SynchronizeList( + agent, cache, cacheIndex, reference, referenceIndex); + completeSynchronizeList(agent, cache, cacheIndex, reference, referenceIndex); +} + +#endif diff --git a/lib/transferdbrecord.cpp b/lib/transferdbrecord.cpp index 367ccfa..2bc7ce4 100644 --- a/lib/transferdbrecord.cpp +++ b/lib/transferdbrecord.cpp @@ -248,6 +248,29 @@ QVariant TransferDBRecord::value(int index) const return restart_supported; default: + qWarning() << Q_FUNC_INFO << "Unknown index: " << index; return QVariant(); } } + +bool TransferDBRecord::isValid() const +{ + return transfer_id > 0 && transfer_type > 0; +} + +bool operator ==(const TransferDBRecord &left, const TransferDBRecord &right) +{ + return left.transfer_id == right.transfer_id && + left.transfer_type == right.transfer_type && + left.status == right.status && + left.progress == right.progress; +} + +bool operator !=(const TransferDBRecord &left, const TransferDBRecord &right) +{ + return left.transfer_id != right.transfer_id || + left.transfer_type != right.transfer_type || + left.status != right.status || + left.progress != right.progress; +} + diff --git a/lib/transferdbrecord.h b/lib/transferdbrecord.h index 71fd05e..c61b0e7 100644 --- a/lib/transferdbrecord.h +++ b/lib/transferdbrecord.h @@ -67,6 +67,12 @@ class TransferDBRecord QVariant value(int index) const; + bool isValid() const; + + friend bool operator ==(const TransferDBRecord &left, const TransferDBRecord &right); + friend bool operator !=(const TransferDBRecord &left, const TransferDBRecord &right); + + // TODO: Maybe this could use QVariantList internally to ease of pain of keeping thigs up to date // when database structure / fields change int transfer_id; @@ -87,6 +93,9 @@ class TransferDBRecord bool restart_supported; }; +bool operator ==(const TransferDBRecord &left, const TransferDBRecord &right); +bool operator !=(const TransferDBRecord &left, const TransferDBRecord &right); + #endif // DBUSTYPES_H diff --git a/rpm/transfer-engine-qt5.spec b/rpm/transfer-engine-qt5.spec index ffa66bc..8e8d4cb 100644 --- a/rpm/transfer-engine-qt5.spec +++ b/rpm/transfer-engine-qt5.spec @@ -1,5 +1,5 @@ Name: nemo-transferengine-qt5 -Version: 0.0.29 +Version: 0.0.30 Release: 0 Summary: Transfer Engine for uploading media content and tracking transfers. Group: System Environment/Daemon @@ -33,7 +33,6 @@ Obsoletes: nemo-transferengine <= 0.0.19 %{_datadir}/translations/nemo-transfer-engine_eng_en.qm - %package -n libnemotransferengine-qt5 Summary: Transfer engine library. Group: Development/Libraries @@ -44,6 +43,7 @@ Group: Development/Libraries %files -n libnemotransferengine-qt5 %defattr(-,root,root,-) %{_libdir}/*.so.* +%{_libdir}/qt5/qml/org/nemomobile/transferengine/* %package -n libnemotransferengine-qt5-devel Summary: Development headers for transfer engine library. diff --git a/src/dbmanager.cpp b/src/dbmanager.cpp index 2e4d37e..cc1e3ea 100644 --- a/src/dbmanager.cpp +++ b/src/dbmanager.cpp @@ -557,7 +557,9 @@ bool DbManager::removeTransfer(int key) QSqlQuery query; if (!query.exec(queryStr)) { - qWarning() << "Failed to execute SQL query. Couldn't delete the transfer with key " << key; + qWarning() << Q_FUNC_INFO; + qWarning() << "Failed to execute SQL query: " << query.lastQuery(); + qWarning() << query.lastError().text(); return false; } @@ -565,6 +567,32 @@ bool DbManager::removeTransfer(int key) return true; } +/*! + Remove failed transfers with same provider except the one having the \a excludeKey. Argument \a type defines the type + of the removed failed transfers and can be one of TransferEngineData::Download, TransferEngineData::Upload + or TransferEngineData::Sync. + + This methods returns true on success or false on failure + */ +bool DbManager::clearFailedTransfers(int excludeKey, TransferEngineData::TransferType type) +{ + // DELETE FROM transfers where transfer_id!=4584 AND status=5 AND display_name=(SELECT display_name FROM transfers WHERE transfer_id=4584); + QString queryStr = QString("DELETE FROM transfers WHERE transfer_id!=%1 AND status=%2 AND transfer_type=%3 AND display_name=(SELECT display_name FROM transfers WHERE transfer_id=%1);") + .arg(excludeKey) + .arg(TransferEngineData::TransferInterrupted) + .arg(type); + + QSqlQuery query; + if (!query.exec(queryStr)) { + qWarning() << Q_FUNC_INFO; + qWarning() << "Failed to execute query: " << query.lastQuery(); + qWarning() << query.lastError().text(); + return false; + } + query.finish(); + return true; +} + /*! Clears all finished, canceled or failed transfers from the database. diff --git a/src/dbmanager.h b/src/dbmanager.h index bdbd63b..d08ec7b 100644 --- a/src/dbmanager.h +++ b/src/dbmanager.h @@ -59,6 +59,8 @@ class DbManager bool removeTransfer(int key); + bool clearFailedTransfers(int excludeKey, TransferEngineData::TransferType type); + bool clearTransfers(); QList transfers() const; diff --git a/src/transferengine.cpp b/src/transferengine.cpp index f12de8f..40a182e 100644 --- a/src/transferengine.cpp +++ b/src/transferengine.cpp @@ -656,7 +656,7 @@ void TransferEnginePrivate::pluginInfoError(const QString &msg) qWarning() << "TransferEnginePrivate::pluginInfoError:" << msg; TransferPluginInfo *infoObj = qobject_cast(sender()); m_infoObjects.removeOne(infoObj); - delete infoObj; + infoObj->deleteLater(); if (m_infoObjects.isEmpty()) { Q_Q(TransferEngine); @@ -1152,17 +1152,30 @@ void TransferEngine::finishTransfer(int transferId, int status, const QString &r transferStatus == TransferEngineData::TransferCanceled || transferStatus == TransferEngineData::TransferInterrupted) { DbManager::instance()->updateTransferStatus(transferId, transferStatus); - d->m_activityMonitor->activityFinished(transferId); d->sendNotification(type, transferStatus, fileName); emit statusChanged(transferId, status); - // We don't want to leave successfully finished syncs to populate the database, just remove it. - if (type == TransferEngineData::Sync && - transferStatus == TransferEngineData::TransferFinished) { - if (DbManager::instance()->removeTransfer(transferId)) { - emit transfersChanged(); + bool notify = false; + + // Clean up old failed syncs from the database and leave only the latest one there + if (type == TransferEngineData::Sync) { + if (DbManager::instance()->clearFailedTransfers(transferId, type)) { + notify = true; + } + + // We don't want to leave successfully finished syncs to populate the database, just remove it. + if (transferStatus == TransferEngineData::TransferFinished) { + if (DbManager::instance()->removeTransfer(transferId)) { + notify = true; + } } } + + if (notify) { + emit transfersChanged(); + } + + d->m_activityMonitor->activityFinished(transferId); } } diff --git a/transfer-engine.pro b/transfer-engine.pro index 71a357c..f023096 100644 --- a/transfer-engine.pro +++ b/transfer-engine.pro @@ -1,5 +1,5 @@ TEMPLATE = subdirs -SUBDIRS = lib src tests +SUBDIRS = lib src declarative tests src.depends = lib tests.depends = lib