Skip to content

Commit

Permalink
Add push notifications for file changes
Browse files Browse the repository at this point in the history
Resolves #2802

Signed-off-by: Felix Weilbach <[email protected]>
  • Loading branch information
Felix Weilbach authored and Felix Weilbach committed Jan 25, 2021
1 parent bc753d5 commit 581a4a7
Show file tree
Hide file tree
Showing 19 changed files with 1,075 additions and 22 deletions.
131 changes: 110 additions & 21 deletions src/gui/folderman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "filesystem.h"
#include "lockwatcher.h"
#include "common/asserts.h"
#include <pushnotifications.h>
#include <syncengine.h>

#ifdef Q_OS_MAC
Expand Down Expand Up @@ -77,6 +78,8 @@ FolderMan::FolderMan(QObject *parent)

connect(_lockWatcher.data(), &LockWatcher::fileUnlocked,
this, &FolderMan::slotWatchedFileUnlocked);

connect(this, &FolderMan::folderListChanged, this, &FolderMan::slotSetupPushNotifications);
}

FolderMan *FolderMan::instance()
Expand Down Expand Up @@ -823,32 +826,80 @@ void FolderMan::slotStartScheduledFolderSync()
}
}

bool FolderMan::pushNotificationsFilesReady(Account *account)
{
const auto pushNotifications = account->pushNotifications();
const auto pushFilesAvailable = account->capabilities().availablePushNotifications() & PushNotificationType::Files;

return pushFilesAvailable && pushNotifications && pushNotifications->isReady();
}

void FolderMan::slotEtagPollTimerTimeout()
{
ConfigFile cfg;
auto polltime = cfg.remotePollInterval();
qCInfo(lcFolderMan) << "Etag poll timer timeout";

for (Folder *f : qAsConst(_folderMap)) {
if (!f) {
continue;
}
if (f->isSyncRunning()) {
continue;
}
if (_scheduledFolders.contains(f)) {
continue;
}
if (_disabledFolders.contains(f)) {
continue;
}
if (f->etagJob() || f->isBusy() || !f->canSync()) {
continue;
}
if (f->msecSinceLastSync() < polltime) {
continue;
const auto folderMapValues = _folderMap.values();

qCInfo(lcFolderMan) << "Folders to sync:" << folderMapValues.size();

QList<Folder *> foldersToRun;

// Some folders need not to be checked because they use the push notifications
std::copy_if(folderMapValues.begin(), folderMapValues.end(), std::back_inserter(foldersToRun), [this](Folder *folder) -> bool {
const auto account = folder->accountState()->account();
const auto capabilities = account->capabilities();
const auto pushNotifications = account->pushNotifications();

return !pushNotificationsFilesReady(account.data());
});

qCInfo(lcFolderMan) << "Number of folders that don't use push notifications:" << foldersToRun.size();

runEtagJobsIfPossible(foldersToRun);
}

void FolderMan::runEtagJobsIfPossible(const QList<Folder *> &folderMap)
{
for (auto folder : folderMap) {
runEtagJobIfPossible(folder);
}
}

void FolderMan::runEtagJobIfPossible(Folder *folder)
{
const ConfigFile cfg;
const auto polltime = cfg.remotePollInterval();

qCInfo(lcFolderMan) << "Run etag job on folder" << folder;

if (!folder) {
return;
}
if (folder->isSyncRunning()) {
qCInfo(lcFolderMan) << "Can not run etag job: Sync is running";
return;
}
if (_scheduledFolders.contains(folder)) {
qCInfo(lcFolderMan) << "Can not run etag job: Folder is alreday scheduled";
return;
}
if (_disabledFolders.contains(folder)) {
qCInfo(lcFolderMan) << "Can not run etag job: Folder is disabled";
return;
}
if (folder->etagJob() || folder->isBusy() || !folder->canSync()) {
qCInfo(lcFolderMan) << "Can not run etag job: Folder is busy";
return;
}
// When not using push notifications, make sure polltime is reached
if (!pushNotificationsFilesReady(folder->accountState()->account().data())) {
if (folder->msecSinceLastSync() < polltime) {
qCInfo(lcFolderMan) << "Can not run etag job: Polltime not reached";
return;
}
QMetaObject::invokeMethod(f, "slotRunEtagJob", Qt::QueuedConnection);
}

QMetaObject::invokeMethod(folder, "slotRunEtagJob", Qt::QueuedConnection);
}

void FolderMan::slotRemoveFoldersForAccount(AccountState *accountState)
Expand Down Expand Up @@ -1631,4 +1682,42 @@ void FolderMan::restartApplication()
}
}

void FolderMan::slotSetupPushNotifications(const Folder::Map &folderMap)
{
for (auto folder : folderMap) {
const auto account = folder->accountState()->account();

// See if the account already provides the PushNotifications object and if yes connect to it.
// If we can't connect at this point, the signals will be connected in slotPushNotificationsReady()
// after the PushNotification object emitted the ready signal
slotConnectToPushNotifications(account.data());
connect(account.data(), &Account::pushNotificationsReady, this, &FolderMan::slotConnectToPushNotifications, Qt::UniqueConnection);
}
}

void FolderMan::slotProcessFilesPushNotification(Account *account)
{
qCInfo(lcFolderMan) << "Got files push notification for account" << account;

for (auto folder : _folderMap) {
// Just run on the folders that belong to this account
if (folder->accountState()->account() != account) {
continue;
}

qCInfo(lcFolderMan) << "Schedule folder" << folder << "for sync";
scheduleFolder(folder);
}
}

void FolderMan::slotConnectToPushNotifications(Account *account)
{
const auto pushNotifications = account->pushNotifications();

if (pushNotificationsFilesReady(account)) {
qCInfo(lcFolderMan) << "Push notifications ready";
connect(pushNotifications, &PushNotifications::filesChanged, this, &FolderMan::slotProcessFilesPushNotification, Qt::UniqueConnection);
}
}

} // namespace OCC
9 changes: 9 additions & 0 deletions src/gui/folderman.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ private slots:
*/
void slotScheduleFolderByTime();

void slotSetupPushNotifications(const Folder::Map &);
void slotProcessFilesPushNotification(Account *account);
void slotConnectToPushNotifications(Account *account);

private:
/** Adds a new folder, does not add it to the account settings and
* does not set an account on the new folder.
Expand All @@ -313,6 +317,11 @@ private slots:

void setupFoldersHelper(QSettings &settings, AccountStatePtr account, const QStringList &ignoreKeys, bool backwardsCompatible, bool foldersWithPlaceholders);

void runEtagJobsIfPossible(const QList<Folder *> &folderMap);
void runEtagJobIfPossible(Folder *folder);

bool pushNotificationsFilesReady(Account *account);

QSet<Folder *> _disabledFolders;
Folder::Map _folderMap;
QString _folderConfigPath;
Expand Down
3 changes: 3 additions & 0 deletions src/libsync/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD|NetBSD|OpenBSD")

set(libsync_SRCS
account.cpp
pushnotifications.cpp
wordlist.cpp
bandwidthmanager.cpp
capabilities.cpp
Expand Down Expand Up @@ -116,13 +117,15 @@ IF (NOT APPLE)
)
ENDIF(NOT APPLE)

find_package(Qt5 REQUIRED COMPONENTS WebSockets)
add_library(${synclib_NAME} SHARED ${libsync_SRCS})
target_link_libraries(${synclib_NAME}
"${csync_NAME}"
OpenSSL::Crypto
OpenSSL::SSL
${OS_SPECIFIC_LINK_LIBRARIES}
Qt5::Core Qt5::Network
Qt5::WebSockets
)

if (NOT TOKEN_AUTH_ONLY)
Expand Down
35 changes: 35 additions & 0 deletions src/libsync/account.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "creds/abstractcredentials.h"
#include "capabilities.h"
#include "theme.h"
#include "pushnotifications.h"

#include "common/asserts.h"
#include "clientsideencryption.h"
Expand Down Expand Up @@ -56,6 +57,7 @@ Account::Account(QObject *parent)
, _davPath(Theme::instance()->webDavPath())
{
qRegisterMetaType<AccountPtr>("AccountPtr");
qRegisterMetaType<Account *>("Account*");
}

AccountPtr Account::create()
Expand Down Expand Up @@ -201,6 +203,32 @@ void Account::setCredentials(AbstractCredentials *cred)
this, &Account::slotCredentialsFetched);
connect(_credentials.data(), &AbstractCredentials::asked,
this, &Account::slotCredentialsAsked);

trySetupPushNotifications();
}

void Account::trySetupPushNotifications()
{
if (_capabilities.availablePushNotifications() != PushNotificationType::None) {
qCInfo(lcAccount) << "Try to setup push notifications";

if (!_pushNotifications) {
_pushNotifications = new PushNotifications(this, this);

connect(_pushNotifications, &PushNotifications::ready, this, [this]() { emit pushNotificationsReady(this); });

const auto deletePushNotifications = [this]() {
qCInfo(lcAccount) << "Delete push notifications object because authentication failed or connection lost";
_pushNotifications->deleteLater();
_pushNotifications = nullptr;
};

connect(_pushNotifications, &PushNotifications::connectionLost, this, deletePushNotifications);
connect(_pushNotifications, &PushNotifications::authenticationFailed, this, deletePushNotifications);
}
// If push notifications already running it is no problem to call setup again
_pushNotifications->setup();
}
}

QUrl Account::davUrl() const
Expand Down Expand Up @@ -478,6 +506,8 @@ const Capabilities &Account::capabilities() const
void Account::setCapabilities(const QVariantMap &caps)
{
_capabilities = Capabilities(caps);

trySetupPushNotifications();
}

QString Account::serverVersion() const
Expand Down Expand Up @@ -661,4 +691,9 @@ void Account::slotDirectEditingRecieved(const QJsonDocument &json)
}
}

PushNotifications *Account::pushNotifications() const
{
return _pushNotifications;
}

} // namespace OCC
9 changes: 9 additions & 0 deletions src/libsync/account.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class Account;
using AccountPtr = QSharedPointer<Account>;
class AccessManager;
class SimpleNetworkJob;
class PushNotifications;

/**
* @brief Reimplement this to handle SSL errors from libsync
Expand Down Expand Up @@ -250,6 +251,8 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject
// Check for the directEditing capability
void fetchDirectEditors(const QUrl &directEditingURL, const QString &directEditingETag);

PushNotifications *pushNotifications() const;

public slots:
/// Used when forgetting credentials
void clearQNAMCache();
Expand Down Expand Up @@ -279,6 +282,8 @@ public slots:
/// Used in RemoteWipe
void appPasswordRetrieved(QString);

void pushNotificationsReady(Account *account);

protected Q_SLOTS:
void slotCredentialsFetched();
void slotCredentialsAsked();
Expand All @@ -287,6 +292,7 @@ protected Q_SLOTS:
private:
Account(QObject *parent = nullptr);
void setSharedThis(AccountPtr sharedThis);
void trySetupPushNotifications();

QWeakPointer<Account> _sharedThis;
QString _id;
Expand Down Expand Up @@ -331,6 +337,8 @@ protected Q_SLOTS:
// Direct Editing
QString _lastDirectEditingETag;

PushNotifications *_pushNotifications = nullptr;

/* IMPORTANT - remove later - FIXME MS@2019-12-07 -->
* TODO: For "Log out" & "Remove account": Remove client CA certs and KEY!
*
Expand All @@ -350,5 +358,6 @@ protected Q_SLOTS:
}

Q_DECLARE_METATYPE(OCC::AccountPtr)
Q_DECLARE_METATYPE(OCC::Account *)

#endif //SERVERCONNECTION_H
23 changes: 23 additions & 0 deletions src/libsync/capabilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include <QVariantMap>
#include <QLoggingCategory>
#include <QUrl>

#include <QDebug>

Expand Down Expand Up @@ -176,6 +177,28 @@ bool Capabilities::chunkingNg() const
return _capabilities["dav"].toMap()["chunking"].toByteArray() >= "1.0";
}

PushNotificationTypes Capabilities::availablePushNotifications() const
{
if (!_capabilities.contains("notify_push")) {
return PushNotificationType::None;
}

const auto types = _capabilities["notify_push"].toMap()["type"].toStringList();
PushNotificationTypes pushNotificationTypes;

if (types.contains("files")) {
pushNotificationTypes.setFlag(PushNotificationType::Files);
}

return pushNotificationTypes;
}

QUrl Capabilities::pushNotificationsWebSocketUrl() const
{
const auto websocket = _capabilities["notify_push"].toMap()["endpoints"].toMap()["websocket"].toString();
return QUrl(websocket);
}

bool Capabilities::chunkingParallelUploadDisabled() const
{
return _capabilities["dav"].toMap()["chunkingParallelUploadDisabled"].toBool();
Expand Down
13 changes: 13 additions & 0 deletions src/libsync/capabilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ namespace OCC {

class DirectEditor;

enum PushNotificationType {
None = 0,
Files = 1
};
Q_DECLARE_FLAGS(PushNotificationTypes, PushNotificationType)
Q_DECLARE_OPERATORS_FOR_FLAGS(PushNotificationTypes)

/**
* @brief The Capabilities class represents the capabilities of an ownCloud
* server
Expand All @@ -48,6 +55,12 @@ class OWNCLOUDSYNC_EXPORT Capabilities
bool shareResharing() const;
bool chunkingNg() const;

/// Returns which kind of push notfications are available
PushNotificationTypes availablePushNotifications() const;

/// Websocket url for files push notifications if available
QUrl pushNotificationsWebSocketUrl() const;

/// disable parallel upload in chunking
bool chunkingParallelUploadDisabled() const;

Expand Down
1 change: 1 addition & 0 deletions src/libsync/creds/abstractcredentials.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class OWNCLOUDSYNC_EXPORT AbstractCredentials : public QObject

virtual QString authType() const = 0;
virtual QString user() const = 0;
virtual QString password() const = 0;
virtual QNetworkAccessManager *createQNAM() const = 0;

/** Whether there are credentials that can be used for a connection attempt. */
Expand Down
Loading

0 comments on commit 581a4a7

Please sign in to comment.