Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable-3.1] Add push notifications for file changes #2866

Merged
merged 1 commit into from
Jan 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -753,32 +756,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 (_currentSyncFolder == f) {
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 (_currentSyncFolder == folder) {
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 @@ -1505,4 +1556,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 @@ -276,6 +276,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 @@ -301,6 +305,11 @@ private slots:

void setupFoldersHelper(QSettings &settings, AccountStatePtr account, bool backwardsCompatible);

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 @@ -17,6 +17,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 @@ -91,13 +92,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}
ocsync
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 @@ -198,6 +200,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 @@ -471,6 +499,8 @@ const Capabilities &Account::capabilities() const
void Account::setCapabilities(const QVariantMap &caps)
{
_capabilities = Capabilities(caps);

trySetupPushNotifications();
}

QString Account::serverVersion() const
Expand Down Expand Up @@ -658,4 +688,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 @@ -249,6 +250,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 @@ -278,6 +281,8 @@ public slots:
/// Used in RemoteWipe
void appPasswordRetrieved(QString);

void pushNotificationsReady(Account *account);

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

QWeakPointer<Account> _sharedThis;
QString _id;
Expand Down Expand Up @@ -330,6 +336,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 @@ -349,5 +357,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 @@ -174,6 +175,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 @@ -43,6 +43,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