mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-29 12:19:03 +03:00
Use push notifications for Tray activities/notifications fetch trigger.
Signed-off-by: allexzander <blackslayer4@gmail.com>
This commit is contained in:
parent
f2ffa74a7b
commit
8c4d5333c1
11 changed files with 282 additions and 22 deletions
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "accountmanager.h"
|
||||
#include "owncloudgui.h"
|
||||
#include <pushnotifications.h>
|
||||
#include "syncengine.h"
|
||||
#include "ocsjob.h"
|
||||
#include "configfile.h"
|
||||
|
@ -40,7 +41,7 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
|
|||
this, &User::slotRefresh);
|
||||
|
||||
connect(_account.data(), &AccountState::stateChanged,
|
||||
[=]() { if (isConnected()) {slotRefresh();} });
|
||||
[=]() { if (isConnected()) {slotRefreshImmediately();} });
|
||||
connect(_account.data(), &AccountState::stateChanged, this, &User::accountStateChanged);
|
||||
connect(_account.data(), &AccountState::hasFetchedNavigationApps,
|
||||
this, &User::slotRebuildNavigationAppList);
|
||||
|
@ -105,19 +106,90 @@ void User::slotBuildNotificationDisplay(const ActivityList &list)
|
|||
|
||||
void User::setNotificationRefreshInterval(std::chrono::milliseconds interval)
|
||||
{
|
||||
qCDebug(lcActivity) << "Starting Notification refresh timer with " << interval.count() / 1000 << " sec interval";
|
||||
_notificationCheckTimer.start(interval.count());
|
||||
if (!checkPushNotificationsAreReady()) {
|
||||
qCDebug(lcActivity) << "Starting Notification refresh timer with " << interval.count() / 1000 << " sec interval";
|
||||
_notificationCheckTimer.start(interval.count());
|
||||
}
|
||||
}
|
||||
|
||||
void User::slotPushNotificationsReady()
|
||||
{
|
||||
qCInfo(lcActivity) << "Push notifications are ready";
|
||||
|
||||
if (_notificationCheckTimer.isActive()) {
|
||||
// as we are now able to use push notifications - let's stop the polling timer
|
||||
_notificationCheckTimer.stop();
|
||||
}
|
||||
|
||||
connectPushNotifications();
|
||||
}
|
||||
|
||||
void User::slotDisconnectPushNotifications()
|
||||
{
|
||||
disconnect(_account->account()->pushNotifications(), &PushNotifications::notificationsChanged, this, &User::slotReceivedPushNotification);
|
||||
disconnect(_account->account()->pushNotifications(), &PushNotifications::activitiesChanged, this, &User::slotReceivedPushActivity);
|
||||
|
||||
disconnect(_account->account().data(), &Account::pushNotificationsDisabled, this, &User::slotDisconnectPushNotifications);
|
||||
|
||||
// connection to WebSocket may have dropped or an error occured, so we need to bring back the polling until we have re-established the connection
|
||||
setNotificationRefreshInterval(ConfigFile().notificationRefreshInterval());
|
||||
}
|
||||
|
||||
void User::slotReceivedPushNotification(Account *account)
|
||||
{
|
||||
if (account->id() == _account->account()->id()) {
|
||||
slotRefreshNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
void User::slotReceivedPushActivity(Account *account)
|
||||
{
|
||||
if (account->id() == _account->account()->id()) {
|
||||
slotRefreshActivities();
|
||||
}
|
||||
}
|
||||
|
||||
void User::connectPushNotifications() const
|
||||
{
|
||||
connect(_account->account().data(), &Account::pushNotificationsDisabled, this, &User::slotDisconnectPushNotifications, Qt::UniqueConnection);
|
||||
|
||||
connect(_account->account()->pushNotifications(), &PushNotifications::notificationsChanged, this, &User::slotReceivedPushNotification, Qt::UniqueConnection);
|
||||
connect(_account->account()->pushNotifications(), &PushNotifications::activitiesChanged, this, &User::slotReceivedPushActivity, Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
bool User::checkPushNotificationsAreReady() const
|
||||
{
|
||||
const auto pushNotifications = _account->account()->pushNotifications();
|
||||
|
||||
const auto pushActivitiesAvailable = _account->account()->capabilities().availablePushNotifications() & PushNotificationType::Activities;
|
||||
const auto pushNotificationsAvailable = _account->account()->capabilities().availablePushNotifications() & PushNotificationType::Notifications;
|
||||
|
||||
const auto pushActivitiesAndNotificationsAvailable = pushActivitiesAvailable && pushNotificationsAvailable;
|
||||
|
||||
if (pushActivitiesAndNotificationsAvailable && pushNotifications && pushNotifications->isReady()) {
|
||||
connectPushNotifications();
|
||||
return true;
|
||||
} else {
|
||||
connect(_account->account().data(), &Account::pushNotificationsReady, this, &User::slotPushNotificationsReady, Qt::UniqueConnection);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void User::slotRefreshImmediately() {
|
||||
if (_account.data() && _account.data()->isConnected()) {
|
||||
this->slotRefreshActivities();
|
||||
slotRefreshActivities();
|
||||
}
|
||||
this->slotRefreshNotifications();
|
||||
slotRefreshNotifications();
|
||||
}
|
||||
|
||||
void User::slotRefresh()
|
||||
{
|
||||
if (checkPushNotificationsAreReady()) {
|
||||
// we are relying on WebSocket push notifications - ignore refresh attempts from UI
|
||||
_timeSinceLastCheck[_account.data()].invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
// QElapsedTimer isn't actually constructed as invalid.
|
||||
if (!_timeSinceLastCheck.contains(_account.data())) {
|
||||
_timeSinceLastCheck[_account.data()].invalidate();
|
||||
|
@ -131,9 +203,9 @@ void User::slotRefresh()
|
|||
}
|
||||
if (_account.data() && _account.data()->isConnected()) {
|
||||
if (!timer.isValid()) {
|
||||
this->slotRefreshActivities();
|
||||
slotRefreshActivities();
|
||||
}
|
||||
this->slotRefreshNotifications();
|
||||
slotRefreshNotifications();
|
||||
timer.start();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,15 @@ public slots:
|
|||
void setNotificationRefreshInterval(std::chrono::milliseconds interval);
|
||||
void slotRebuildNavigationAppList();
|
||||
|
||||
private:
|
||||
void slotPushNotificationsReady();
|
||||
void slotDisconnectPushNotifications();
|
||||
void slotReceivedPushNotification(Account *account);
|
||||
void slotReceivedPushActivity(Account *account);
|
||||
|
||||
void connectPushNotifications() const;
|
||||
bool checkPushNotificationsAreReady() const;
|
||||
|
||||
private:
|
||||
AccountStatePtr _account;
|
||||
bool _isCurrentUser;
|
||||
|
|
|
@ -216,6 +216,7 @@ void Account::trySetupPushNotifications()
|
|||
qCInfo(lcAccount) << "Delete push notifications object because authentication failed or connection lost";
|
||||
_pushNotifications->deleteLater();
|
||||
_pushNotifications = nullptr;
|
||||
emit pushNotificationsDisabled(this);
|
||||
};
|
||||
|
||||
connect(_pushNotifications, &PushNotifications::connectionLost, this, deletePushNotifications);
|
||||
|
|
|
@ -283,6 +283,7 @@ signals:
|
|||
void appPasswordRetrieved(QString);
|
||||
|
||||
void pushNotificationsReady(Account *account);
|
||||
void pushNotificationsDisabled(Account *account);
|
||||
|
||||
protected Q_SLOTS:
|
||||
void slotCredentialsFetched();
|
||||
|
|
|
@ -190,6 +190,14 @@ PushNotificationTypes Capabilities::availablePushNotifications() const
|
|||
pushNotificationTypes.setFlag(PushNotificationType::Files);
|
||||
}
|
||||
|
||||
if (types.contains("activities")) {
|
||||
pushNotificationTypes.setFlag(PushNotificationType::Activities);
|
||||
}
|
||||
|
||||
if (types.contains("notifications")) {
|
||||
pushNotificationTypes.setFlag(PushNotificationType::Notifications);
|
||||
}
|
||||
|
||||
return pushNotificationTypes;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,9 @@ class DirectEditor;
|
|||
|
||||
enum PushNotificationType {
|
||||
None = 0,
|
||||
Files = 1
|
||||
Files = 1,
|
||||
Activities = 2,
|
||||
Notifications = 4
|
||||
};
|
||||
Q_DECLARE_FLAGS(PushNotificationTypes, PushNotificationType)
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(PushNotificationTypes)
|
||||
|
|
|
@ -73,8 +73,10 @@ void PushNotifications::onWebSocketTextMessageReceived(const QString &message)
|
|||
|
||||
if (message == "notify_file") {
|
||||
handleNotifyFile();
|
||||
} else if (message == "notify_activity" || message == "notify_notification") {
|
||||
handleNotification();
|
||||
} else if (message == "notify_activity") {
|
||||
handleNotifyActivity();
|
||||
} else if (message == "notify_notification") {
|
||||
handleNotifyNotification();
|
||||
} else if (message == "authenticated") {
|
||||
handleAuthenticated();
|
||||
} else if (message == "err: Invalid credentials") {
|
||||
|
@ -181,9 +183,15 @@ void PushNotifications::handleInvalidCredentials()
|
|||
}
|
||||
}
|
||||
|
||||
void PushNotifications::handleNotification()
|
||||
void PushNotifications::handleNotifyNotification()
|
||||
{
|
||||
qCInfo(lcPushNotifications) << "Notification or activity push notification arrived";
|
||||
emit notification(_account);
|
||||
qCInfo(lcPushNotifications) << "Push notification arrived";
|
||||
emit notificationsChanged(_account);
|
||||
}
|
||||
|
||||
void PushNotifications::handleNotifyActivity()
|
||||
{
|
||||
qCInfo(lcPushNotifications) << "Push activity arrived";
|
||||
emit activitiesChanged(_account);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,9 +64,14 @@ signals:
|
|||
void filesChanged(Account *account);
|
||||
|
||||
/**
|
||||
* Will be emitted if there is a new notification or activity on the server
|
||||
* Will be emitted if activities have been changed on the server
|
||||
*/
|
||||
void notification(Account *account);
|
||||
void activitiesChanged(Account *account);
|
||||
|
||||
/**
|
||||
* Will be emitted if notifications have been changed on the server
|
||||
*/
|
||||
void notificationsChanged(Account *account);
|
||||
|
||||
/**
|
||||
* Will be emitted if push notifications are unable to authenticate
|
||||
|
@ -100,7 +105,8 @@ private:
|
|||
void handleAuthenticated();
|
||||
void handleNotifyFile();
|
||||
void handleInvalidCredentials();
|
||||
void handleNotification();
|
||||
void handleNotifyNotification();
|
||||
void handleNotifyActivity();
|
||||
|
||||
Account *_account = nullptr;
|
||||
QWebSocket *_webSocket = nullptr;
|
||||
|
|
|
@ -70,6 +70,8 @@ OCC::AccountPtr FakeWebSocketServer::createAccount()
|
|||
|
||||
QStringList typeList;
|
||||
typeList.append("files");
|
||||
typeList.append("activities");
|
||||
typeList.append("notifications");
|
||||
|
||||
QString websocketUrl("ws://localhost:12345");
|
||||
|
||||
|
|
|
@ -7,6 +7,40 @@ class TestCapabilities : public QObject
|
|||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void testPushNotificationsAvailable_pushNotificationsForActivitiesAvailable_returnTrue()
|
||||
{
|
||||
QStringList typeList;
|
||||
typeList.append("activities");
|
||||
|
||||
QVariantMap notifyPushMap;
|
||||
notifyPushMap["type"] = typeList;
|
||||
|
||||
QVariantMap capabilitiesMap;
|
||||
capabilitiesMap["notify_push"] = notifyPushMap;
|
||||
|
||||
const auto &capabilities = OCC::Capabilities(capabilitiesMap);
|
||||
const auto activitiesPushNotificationsAvailable = capabilities.availablePushNotifications().testFlag(OCC::PushNotificationType::Activities);
|
||||
|
||||
QCOMPARE(activitiesPushNotificationsAvailable, true);
|
||||
}
|
||||
|
||||
void testPushNotificationsAvailable_pushNotificationsForActivitiesNotAvailable_returnFalse()
|
||||
{
|
||||
QStringList typeList;
|
||||
typeList.append("noactivities");
|
||||
|
||||
QVariantMap notifyPushMap;
|
||||
notifyPushMap["type"] = typeList;
|
||||
|
||||
QVariantMap capabilitiesMap;
|
||||
capabilitiesMap["notify_push"] = notifyPushMap;
|
||||
|
||||
const auto &capabilities = OCC::Capabilities(capabilitiesMap);
|
||||
const auto activitiesPushNotificationsAvailable = capabilities.availablePushNotifications().testFlag(OCC::PushNotificationType::Activities);
|
||||
|
||||
QCOMPARE(activitiesPushNotificationsAvailable, false);
|
||||
}
|
||||
|
||||
void testPushNotificationsAvailable_pushNotificationsForFilesAvailable_returnTrue()
|
||||
{
|
||||
QStringList typeList;
|
||||
|
@ -41,12 +75,50 @@ private slots:
|
|||
QCOMPARE(filesPushNotificationsAvailable, false);
|
||||
}
|
||||
|
||||
void testPushNotificationsAvailable_pushNotificationsForNotificationsAvailable_returnTrue()
|
||||
{
|
||||
QStringList typeList;
|
||||
typeList.append("notifications");
|
||||
|
||||
QVariantMap notifyPushMap;
|
||||
notifyPushMap["type"] = typeList;
|
||||
|
||||
QVariantMap capabilitiesMap;
|
||||
capabilitiesMap["notify_push"] = notifyPushMap;
|
||||
|
||||
const auto &capabilities = OCC::Capabilities(capabilitiesMap);
|
||||
const auto notificationsPushNotificationsAvailable = capabilities.availablePushNotifications().testFlag(OCC::PushNotificationType::Notifications);
|
||||
|
||||
QCOMPARE(notificationsPushNotificationsAvailable, true);
|
||||
}
|
||||
|
||||
void testPushNotificationsAvailable_pushNotificationsForNotificationsNotAvailable_returnFalse()
|
||||
{
|
||||
QStringList typeList;
|
||||
typeList.append("nonotifications");
|
||||
|
||||
QVariantMap notifyPushMap;
|
||||
notifyPushMap["type"] = typeList;
|
||||
|
||||
QVariantMap capabilitiesMap;
|
||||
capabilitiesMap["notify_push"] = notifyPushMap;
|
||||
|
||||
const auto &capabilities = OCC::Capabilities(capabilitiesMap);
|
||||
const auto notificationsPushNotificationsAvailable = capabilities.availablePushNotifications().testFlag(OCC::PushNotificationType::Notifications);
|
||||
|
||||
QCOMPARE(notificationsPushNotificationsAvailable, false);
|
||||
}
|
||||
|
||||
void testPushNotificationsAvailable_pushNotificationsNotAvailable_returnFalse()
|
||||
{
|
||||
const auto &capabilities = OCC::Capabilities(QVariantMap());
|
||||
const auto activitiesPushNotificationsAvailable = capabilities.availablePushNotifications().testFlag(OCC::PushNotificationType::Activities);
|
||||
const auto filesPushNotificationsAvailable = capabilities.availablePushNotifications().testFlag(OCC::PushNotificationType::Files);
|
||||
const auto notificationsPushNotificationsAvailable = capabilities.availablePushNotifications().testFlag(OCC::PushNotificationType::Notifications);
|
||||
|
||||
QCOMPARE(activitiesPushNotificationsAvailable, false);
|
||||
QCOMPARE(filesPushNotificationsAvailable, false);
|
||||
QCOMPARE(notificationsPushNotificationsAvailable, false);
|
||||
}
|
||||
|
||||
void testPushNotificationsWebSocketUrl_urlAvailable_returnUrl()
|
||||
|
|
|
@ -84,8 +84,8 @@ private slots:
|
|||
auto account = FakeWebSocketServer::createAccount();
|
||||
auto credentials = new CredentialsStub(user, password);
|
||||
account->setCredentials(credentials);
|
||||
QSignalSpy notificationSpy(account->pushNotifications(), &OCC::PushNotifications::notification);
|
||||
QVERIFY(notificationSpy.isValid());
|
||||
QSignalSpy activitySpy(account->pushNotifications(), &OCC::PushNotifications::activitiesChanged);
|
||||
QVERIFY(activitySpy.isValid());
|
||||
|
||||
// Wait for authentication and then send notify_file push notification
|
||||
QVERIFY(processTextMessageSpy.wait());
|
||||
|
@ -94,9 +94,9 @@ private slots:
|
|||
socket->sendTextMessage("notify_activity");
|
||||
|
||||
// notification signal should be emitted
|
||||
QVERIFY(notificationSpy.wait());
|
||||
QCOMPARE(notificationSpy.count(), 1);
|
||||
auto accountFilesChanged = notificationSpy.at(0).at(0).value<OCC::Account *>();
|
||||
QVERIFY(activitySpy.wait());
|
||||
QCOMPARE(activitySpy.count(), 1);
|
||||
auto accountFilesChanged = activitySpy.at(0).at(0).value<OCC::Account *>();
|
||||
QCOMPARE(accountFilesChanged, account.data());
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,7 @@ private slots:
|
|||
auto account = FakeWebSocketServer::createAccount();
|
||||
auto credentials = new CredentialsStub(user, password);
|
||||
account->setCredentials(credentials);
|
||||
QSignalSpy notificationSpy(account->pushNotifications(), &OCC::PushNotifications::notification);
|
||||
QSignalSpy notificationSpy(account->pushNotifications(), &OCC::PushNotifications::notificationsChanged);
|
||||
QVERIFY(notificationSpy.isValid());
|
||||
|
||||
// Wait for authentication and then send notify_file push notification
|
||||
|
@ -273,6 +273,85 @@ private slots:
|
|||
auto accountSent = pushNotificationsReady.at(0).at(0).value<OCC::Account *>();
|
||||
QCOMPARE(accountSent, account.data());
|
||||
}
|
||||
|
||||
void testAccount_web_socket_connectionLost_emitNotificationsDisabled()
|
||||
{
|
||||
FakeWebSocketServer fakeServer;
|
||||
auto account = FakeWebSocketServer::createAccount();
|
||||
QSignalSpy processTextMessageSpy(&fakeServer, &FakeWebSocketServer::processTextMessage);
|
||||
QVERIFY(processTextMessageSpy.isValid());
|
||||
const QString user = "user";
|
||||
const QString password = "password";
|
||||
auto credentials = new CredentialsStub(user, password);
|
||||
account->setCredentials(credentials);
|
||||
|
||||
// Need to set reconnect timer interval to zero for tests
|
||||
account->pushNotifications()->setReconnectTimerInterval(0);
|
||||
|
||||
QSignalSpy connectionLostSpy(account->pushNotifications(), &OCC::PushNotifications::connectionLost);
|
||||
QVERIFY(connectionLostSpy.isValid());
|
||||
|
||||
QSignalSpy pushNotificationsDisabledSpy(account.data(), &OCC::Account::pushNotificationsDisabled);
|
||||
QVERIFY(pushNotificationsDisabledSpy.isValid());
|
||||
|
||||
// Wait for authentication and then sent a network error
|
||||
processTextMessageSpy.wait();
|
||||
QCOMPARE(processTextMessageSpy.count(), 2);
|
||||
auto socket = processTextMessageSpy.at(0).at(0).value<QWebSocket *>();
|
||||
socket->abort();
|
||||
|
||||
QVERIFY(pushNotificationsDisabledSpy.wait());
|
||||
QCOMPARE(pushNotificationsDisabledSpy.count(), 1);
|
||||
|
||||
QCOMPARE(connectionLostSpy.count(), 1);
|
||||
|
||||
auto accountSent = pushNotificationsDisabledSpy.at(0).at(0).value<OCC::Account *>();
|
||||
QCOMPARE(accountSent, account.data());
|
||||
}
|
||||
|
||||
void testAccount_web_socket_authenticationFailed_emitNotificationsDisabled()
|
||||
{
|
||||
FakeWebSocketServer fakeServer;
|
||||
auto account = FakeWebSocketServer::createAccount();
|
||||
QSignalSpy processTextMessageSpy(&fakeServer, &FakeWebSocketServer::processTextMessage);
|
||||
QVERIFY(processTextMessageSpy.isValid());
|
||||
const QString user = "user";
|
||||
const QString password = "password";
|
||||
auto credentials = new CredentialsStub(user, password);
|
||||
account->setCredentials(credentials);
|
||||
|
||||
account->pushNotifications()->setReconnectTimerInterval(0);
|
||||
QSignalSpy authenticationFailedSpy(account->pushNotifications(), &OCC::PushNotifications::authenticationFailed);
|
||||
QVERIFY(authenticationFailedSpy.isValid());
|
||||
|
||||
QSignalSpy pushNotificationsDisabledSpy(account.data(), &OCC::Account::pushNotificationsDisabled);
|
||||
QVERIFY(pushNotificationsDisabledSpy.isValid());
|
||||
|
||||
// Let three authentication attempts fail
|
||||
QVERIFY(processTextMessageSpy.wait());
|
||||
QCOMPARE(processTextMessageSpy.count(), 2);
|
||||
auto socket = processTextMessageSpy.at(0).at(0).value<QWebSocket *>();
|
||||
socket->sendTextMessage("err: Invalid credentials");
|
||||
|
||||
QVERIFY(processTextMessageSpy.wait());
|
||||
QCOMPARE(processTextMessageSpy.count(), 4);
|
||||
socket = processTextMessageSpy.at(2).at(0).value<QWebSocket *>();
|
||||
socket->sendTextMessage("err: Invalid credentials");
|
||||
|
||||
QVERIFY(processTextMessageSpy.wait());
|
||||
QCOMPARE(processTextMessageSpy.count(), 6);
|
||||
socket = processTextMessageSpy.at(4).at(0).value<QWebSocket *>();
|
||||
socket->sendTextMessage("err: Invalid credentials");
|
||||
|
||||
// Now the authenticationFailed and pushNotificationsDisabled Signals should be emitted
|
||||
QVERIFY(pushNotificationsDisabledSpy.wait());
|
||||
QCOMPARE(pushNotificationsDisabledSpy.count(), 1);
|
||||
|
||||
QCOMPARE(authenticationFailedSpy.count(), 1);
|
||||
|
||||
auto accountSent = pushNotificationsDisabledSpy.at(0).at(0).value<OCC::Account *>();
|
||||
QCOMPARE(accountSent, account.data());
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(TestPushNotifications)
|
||||
|
|
Loading…
Reference in a new issue