From 0b2cb88bf6b076e323263ce4b409362b0bc3a9df Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Mon, 11 Apr 2016 08:53:21 +0200 Subject: [PATCH] OwnCloud plugin can delete feeds. --- resources/text/CHANGELOG | 1 + src/miscellaneous/databasequeries.cpp | 21 ++++++++ src/miscellaneous/databasequeries.h | 1 + src/network-web/downloader.cpp | 52 +++++++++---------- src/network-web/downloader.h | 8 +-- src/network-web/networkfactory.cpp | 31 ++++++++++- src/network-web/networkfactory.h | 4 ++ .../network/owncloudnetworkfactory.cpp | 21 +++++++- .../owncloud/network/owncloudnetworkfactory.h | 4 ++ src/services/owncloud/owncloudfeed.cpp | 18 +++++++ src/services/owncloud/owncloudfeed.h | 2 + src/services/tt-rss/ttrssfeed.cpp | 18 +------ 12 files changed, 133 insertions(+), 48 deletions(-) diff --git a/resources/text/CHANGELOG b/resources/text/CHANGELOG index 738ad0119..d87a6119c 100755 --- a/resources/text/CHANGELOG +++ b/resources/text/CHANGELOG @@ -8,6 +8,7 @@ Main: Added: +▪ ownCloud plugin now can delete feeds. ▪ Global auto-update feed interval spinbox now has better format. (issue #176) ▪ Message preview's font is now fully adjustable in settings. (issue #177) ▪ RSS Guard now automatically switches to SQLite backend if MySQL is not available on program startup. diff --git a/src/miscellaneous/databasequeries.cpp b/src/miscellaneous/databasequeries.cpp index d64472041..79e9c9b26 100644 --- a/src/miscellaneous/databasequeries.cpp +++ b/src/miscellaneous/databasequeries.cpp @@ -975,5 +975,26 @@ Assignment DatabaseQueries::getOwnCloudFeeds(QSqlDatabase db, int account_id, bo return feeds; } +bool DatabaseQueries::deleteFeed(QSqlDatabase db, int feed_custom_id, int account_id) { + QSqlQuery query_remove(db); + query_remove.setForwardOnly(true); + + // Remove all messages from this feed. + query_remove.prepare(QSL("DELETE FROM Messages WHERE feed = :feed AND account_id = :account_id;")); + query_remove.bindValue(QSL(":feed"), feed_custom_id); + query_remove.bindValue(QSL(":account_id"), account_id); + + if (!query_remove.exec()) { + return false; + } + + // Remove feed itself. + query_remove.prepare(QSL("DELETE FROM Feeds WHERE custom_id = :feed AND account_id = :account_id;")); + query_remove.bindValue(QSL(":feed"), feed_custom_id); + query_remove.bindValue(QSL(":account_id"), account_id); + + return query_remove.exec(); +} + DatabaseQueries::DatabaseQueries() { } diff --git a/src/miscellaneous/databasequeries.h b/src/miscellaneous/databasequeries.h index ffbb8bc6e..d58f76f2f 100644 --- a/src/miscellaneous/databasequeries.h +++ b/src/miscellaneous/databasequeries.h @@ -68,6 +68,7 @@ class DatabaseQueries { static int createAccount(QSqlDatabase db, const QString &code, bool *ok = NULL); static Assignment getOwnCloudCategories(QSqlDatabase db, int account_id, bool *ok = NULL); static Assignment getOwnCloudFeeds(QSqlDatabase db, int account_id, bool *ok = NULL); + static bool deleteFeed(QSqlDatabase db, int feed_custom_id, int account_id); private: explicit DatabaseQueries(); diff --git a/src/network-web/downloader.cpp b/src/network-web/downloader.cpp index 67ebd10e1..935a80b8f 100755 --- a/src/network-web/downloader.cpp +++ b/src/network-web/downloader.cpp @@ -39,33 +39,12 @@ Downloader::~Downloader() { void Downloader::downloadFile(const QString &url, int timeout, bool protected_contents, const QString &username, const QString &password) { - QNetworkRequest request; - QString non_const_url = url; - - foreach (const QByteArray &header_name, m_customHeaders.keys()) { - request.setRawHeader(header_name, m_customHeaders.value(header_name)); - } - - // Set url for this request and fire it up. - m_timer->setInterval(timeout); - - if (non_const_url.startsWith(URI_SCHEME_FEED)) { - qDebug("Replacing URI schemes for '%s'.", qPrintable(non_const_url)); - request.setUrl(non_const_url.replace(QRegExp(QString('^') + URI_SCHEME_FEED), QString(URI_SCHEME_HTTP))); - } - else { - request.setUrl(non_const_url); - } - - m_targetProtected = protected_contents; - m_targetUsername = username; - m_targetPassword = password; - - runGetRequest(request); + manipulateData(url, QNetworkAccessManager::GetOperation, QByteArray(), timeout, + protected_contents, username, password); } -void Downloader::uploadData(const QString &url, const QByteArray &data, QNetworkAccessManager::Operation operation, - int timeout, bool protected_contents, const QString &username, const QString &password) { +void Downloader::manipulateData(const QString &url, QNetworkAccessManager::Operation operation, const QByteArray &data, + int timeout, bool protected_contents, const QString &username, const QString &password) { QNetworkRequest request; QString non_const_url = url; @@ -93,9 +72,15 @@ void Downloader::uploadData(const QString &url, const QByteArray &data, QNetwork if (operation == QNetworkAccessManager::PostOperation) { runPostRequest(request, m_inputData); } - else { + else if (operation == QNetworkAccessManager::GetOperation) { + runGetRequest(request); + } + else if (operation == QNetworkAccessManager::PutOperation) { runPutRequest(request, m_inputData); } + else if (operation == QNetworkAccessManager::DeleteOperation) { + runDeleteRequest(request); + } } void Downloader::finished() { @@ -131,6 +116,9 @@ void Downloader::finished() { else if (reply_operation == QNetworkAccessManager::PutOperation) { runPutRequest(request, m_inputData); } + else if (reply_operation == QNetworkAccessManager::DeleteOperation) { + runDeleteRequest(request); + } } else { // No redirection is indicated. Final file is obtained in our "reply" object. @@ -161,6 +149,18 @@ void Downloader::timeout() { } } +void Downloader::runDeleteRequest(const QNetworkRequest &request) { + m_timer->start(); + m_activeReply = m_downloadManager->deleteResource(request); + + m_activeReply->setProperty("protected", m_targetProtected); + m_activeReply->setProperty("username", m_targetUsername); + m_activeReply->setProperty("password", m_targetPassword); + + connect(m_activeReply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(progressInternal(qint64,qint64))); + connect(m_activeReply, SIGNAL(finished()), this, SLOT(finished())); +} + void Downloader::runPutRequest(const QNetworkRequest &request, const QByteArray &data) { m_timer->start(); m_activeReply = m_downloadManager->put(request, data); diff --git a/src/network-web/downloader.h b/src/network-web/downloader.h index cb7b6beac..2ca814708 100755 --- a/src/network-web/downloader.h +++ b/src/network-web/downloader.h @@ -52,9 +52,10 @@ class Downloader : public QObject { // Performs asynchronous upload of given data as HTTP POST. // User needs to setup "Content-Encoding" header which // matches encoding of the data. - void uploadData(const QString &url, const QByteArray &data, QNetworkAccessManager::Operation operation, - int timeout = DOWNLOAD_TIMEOUT, bool protected_contents = false, - const QString &username = QString(), const QString &password = QString()); + void manipulateData(const QString &url, QNetworkAccessManager::Operation operation, + const QByteArray &data = QByteArray(), + int timeout = DOWNLOAD_TIMEOUT, bool protected_contents = false, + const QString &username = QString(), const QString &password = QString()); signals: // Emitted when new progress is known. @@ -72,6 +73,7 @@ class Downloader : public QObject { void timeout(); private: + void runDeleteRequest(const QNetworkRequest &request); void runPutRequest(const QNetworkRequest &request, const QByteArray &data); void runPostRequest(const QNetworkRequest &request, const QByteArray &data); void runGetRequest(const QNetworkRequest &request); diff --git a/src/network-web/networkfactory.cpp b/src/network-web/networkfactory.cpp index 0cc3c41f8..54eb54ad8 100755 --- a/src/network-web/networkfactory.cpp +++ b/src/network-web/networkfactory.cpp @@ -167,8 +167,7 @@ NetworkResult NetworkFactory::uploadData(const QString &url, int timeout, const // We need to quit event loop when the download finishes. QObject::connect(&downloader, SIGNAL(completed(QNetworkReply::NetworkError)), &loop, SLOT(quit())); - downloader.uploadData(url, input_data, operation, - timeout, protected_contents, username, password); + downloader.manipulateData(url, operation, input_data, timeout, protected_contents, username, password); loop.exec(); output = downloader.lastOutputData(); result.first = downloader.lastOutputError(); @@ -227,3 +226,31 @@ NetworkResult NetworkFactory::downloadFile(const QString &url, int timeout, return result; } + +NetworkResult NetworkFactory::deleteResource(const QString &url, int timeout, bool protected_contents, + const QString &username, const QString &password, bool set_basic_header) { + // Here, we want to achieve "synchronous" approach because we want synchronous download API for + // some use-cases too. + Downloader downloader; + QEventLoop loop; + NetworkResult result; + + if (set_basic_header) { + QString basic_value = username + ":" + password; + QString header_value = QString("Basic ") + QString(basic_value.toUtf8().toBase64()); + + downloader.appendRawHeader("Authorization", header_value.toLocal8Bit()); + } + + // We need to quit event loop when the download finishes. + QObject::connect(&downloader, SIGNAL(completed(QNetworkReply::NetworkError)), &loop, SLOT(quit())); + + downloader.manipulateData(url, QNetworkAccessManager::DeleteOperation, QByteArray(), + timeout, protected_contents, username, password); + loop.exec(); + + result.first = downloader.lastOutputError(); + result.second = downloader.lastContentType(); + + return result; +} diff --git a/src/network-web/networkfactory.h b/src/network-web/networkfactory.h index dc77c350d..f1972337e 100755 --- a/src/network-web/networkfactory.h +++ b/src/network-web/networkfactory.h @@ -58,6 +58,10 @@ class NetworkFactory { static NetworkResult downloadFile(const QString &url, int timeout, QByteArray &output, bool protected_contents = false, const QString &username = QString(), const QString &password = QString(), bool set_basic_header = false); + + static NetworkResult deleteResource(const QString &url, int timeout, + bool protected_contents = false, const QString &username = QString(), + const QString &password = QString(), bool set_basic_header = false); }; #endif // NETWORKFACTORY_H diff --git a/src/services/owncloud/network/owncloudnetworkfactory.cpp b/src/services/owncloud/network/owncloudnetworkfactory.cpp index ef3c0e4c7..d888956b7 100755 --- a/src/services/owncloud/network/owncloudnetworkfactory.cpp +++ b/src/services/owncloud/network/owncloudnetworkfactory.cpp @@ -33,7 +33,7 @@ OwnCloudNetworkFactory::OwnCloudNetworkFactory() : m_url(QString()), m_fixedUrl(QString()), m_forceServerSideUpdate(false), m_authUsername(QString()), m_authPassword(QString()), m_urlUser(QString()), m_urlStatus(QString()), m_urlFolders(QString()), m_urlFeeds(QString()), m_urlMessages(QString()), m_urlFeedsUpdate(QString()), - m_userId(QString()) { + m_urlDeleteFeed(QString()), m_userId(QString()) { } OwnCloudNetworkFactory::~OwnCloudNetworkFactory() { @@ -60,6 +60,7 @@ void OwnCloudNetworkFactory::setUrl(const QString &url) { m_urlFeeds = m_fixedUrl + API_PATH + "feeds"; m_urlMessages = m_fixedUrl + API_PATH + "items?id=%1&batchSize=%2&type=%3"; m_urlFeedsUpdate = m_fixedUrl + API_PATH + "feeds/update?userId=%1&feedId=%2"; + m_urlDeleteFeed = m_fixedUrl + API_PATH + "feeds/%1"; setUserId(QString()); } @@ -168,6 +169,24 @@ OwnCloudGetFeedsCategoriesResponse OwnCloudNetworkFactory::feedsCategories() { return OwnCloudGetFeedsCategoriesResponse(content_categories, content_feeds); } +bool OwnCloudNetworkFactory::deleteFeed(int feed_id) { + QString final_url = m_urlDeleteFeed.arg(QString::number(feed_id)); + NetworkResult network_reply = NetworkFactory::deleteResource(final_url, + qApp->settings()->value(GROUP(Feeds), + SETTING(Feeds::UpdateTimeout)).toInt(), + true, m_authUsername, m_authPassword, true); + + m_lastError = network_reply.first; + + if (network_reply.first != QNetworkReply::NoError) { + qWarning("ownCloud: Obtaining of categories failed with error %d.", network_reply.first); + return false; + } + else { + return true; + } +} + OwnCloudGetMessagesResponse OwnCloudNetworkFactory::getMessages(int feed_id) { if (forceServerSideUpdate()) { triggerFeedUpdate(feed_id); diff --git a/src/services/owncloud/network/owncloudnetworkfactory.h b/src/services/owncloud/network/owncloudnetworkfactory.h index 56a168f6c..a0be363d4 100755 --- a/src/services/owncloud/network/owncloudnetworkfactory.h +++ b/src/services/owncloud/network/owncloudnetworkfactory.h @@ -119,6 +119,9 @@ class OwnCloudNetworkFactory { // Get feeds & categories (used for sync-in). OwnCloudGetFeedsCategoriesResponse feedsCategories(); + // Delete a feed. + bool deleteFeed(int feed_id); + // Get messages for given feed. OwnCloudGetMessagesResponse getMessages(int feed_id); @@ -143,6 +146,7 @@ class OwnCloudNetworkFactory { QString m_urlFeeds; QString m_urlMessages; QString m_urlFeedsUpdate; + QString m_urlDeleteFeed; QString m_userId; }; diff --git a/src/services/owncloud/owncloudfeed.cpp b/src/services/owncloud/owncloudfeed.cpp index 643b8f236..166c5f0d8 100755 --- a/src/services/owncloud/owncloudfeed.cpp +++ b/src/services/owncloud/owncloudfeed.cpp @@ -18,6 +18,7 @@ #include "services/owncloud/owncloudfeed.h" #include "miscellaneous/iconfactory.h" +#include "miscellaneous/databasequeries.h" #include "services/owncloud/owncloudserviceroot.h" #include "services/owncloud/network/owncloudnetworkfactory.h" #include "services/owncloud/gui/formeditowncloudfeed.h" @@ -53,6 +54,23 @@ bool OwnCloudFeed::editViaGui() { return false; } +bool OwnCloudFeed::canBeDeleted() const { + return true; +} + +bool OwnCloudFeed::deleteViaGui() { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + + if (serviceRoot()->network()->deleteFeed(customId()) && + DatabaseQueries::deleteFeed(database, customId(), serviceRoot()->accountId())) { + serviceRoot()->requestItemRemoval(this); + return true; + } + else { + return false; + } +} + bool OwnCloudFeed::markAsReadUnread(RootItem::ReadStatus status) { QStringList ids = getParentServiceRoot()->customIDSOfMessagesForItem(this); QNetworkReply::NetworkError response = serviceRoot()->network()->markMessagesRead(status, ids); diff --git a/src/services/owncloud/owncloudfeed.h b/src/services/owncloud/owncloudfeed.h index fd215095b..0491a46e1 100755 --- a/src/services/owncloud/owncloudfeed.h +++ b/src/services/owncloud/owncloudfeed.h @@ -33,6 +33,8 @@ class OwnCloudFeed : public Feed { bool canBeEdited() const; bool editViaGui(); + bool canBeDeleted() const; + bool deleteViaGui(); bool markAsReadUnread(ReadStatus status); bool cleanMessages(bool clear_only_read); diff --git a/src/services/tt-rss/ttrssfeed.cpp b/src/services/tt-rss/ttrssfeed.cpp index 8a2199011..79be13fea 100755 --- a/src/services/tt-rss/ttrssfeed.cpp +++ b/src/services/tt-rss/ttrssfeed.cpp @@ -20,6 +20,7 @@ #include "definitions/definitions.h" #include "miscellaneous/application.h" #include "miscellaneous/databasefactory.h" +#include "miscellaneous/databasequeries.h" #include "miscellaneous/iconfactory.h" #include "miscellaneous/textfactory.h" #include "gui/dialogs/formmain.h" @@ -200,23 +201,8 @@ bool TtRssFeed::removeItself() { if (response.code() == UFF_OK) { // Feed was removed online from server, remove local data. QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); - QSqlQuery query_remove(database); - query_remove.setForwardOnly(true); - - // Remove all messages from this standard feed. - query_remove.prepare(QSL("DELETE FROM Messages WHERE feed = :feed;")); - query_remove.bindValue(QSL(":feed"), customId()); - - if (!query_remove.exec()) { - return false; - } - - // Remove feed itself. - query_remove.prepare(QSL("DELETE FROM Feeds WHERE id = :feed;")); - query_remove.bindValue(QSL(":feed"), id()); - - return query_remove.exec(); + return DatabaseQueries::deleteFeed(database, customId(), serviceRoot()->accountId()); } else { qWarning("TT-RSS: Unsubscribing from feed failed, received JSON: '%s'", qPrintable(response.toString()));