application.cpp
invalidfilenamedialog.h
invalidfilenamedialog.cpp
+ callstatechecker.h
+ callstatechecker.cpp
conflictdialog.h
conflictdialog.cpp
conflictsolver.h
--- /dev/null
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License
+ * for more details.
+ */
+
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+
+#include "callstatechecker.h"
+#include "account.h"
+
+namespace OCC {
+
+Q_LOGGING_CATEGORY(lcCallStateChecker, "nextcloud.gui.callstatechecker", QtInfoMsg)
+
+constexpr int successStatusCode = 200;
+
+CallStateChecker::CallStateChecker(QObject *parent)
+ : QObject(parent)
+{
+ setup();
+}
+
+void CallStateChecker::setup()
+{
+ _notificationTimer.setSingleShot(true);
+ _notificationTimer.setInterval(60 * 1000);
+ connect(&_notificationTimer, &QTimer::timeout, this, &CallStateChecker::slotNotificationTimerElapsed);
+
+ _statusCheckTimer.setInterval(2 * 1000);
+ connect(&_statusCheckTimer, &QTimer::timeout, this, &CallStateChecker::slotStatusCheckTimerElapsed);
+}
+
+QString CallStateChecker::token() const
+{
+ return _token;
+}
+
+void CallStateChecker::setToken(const QString &token)
+{
+ _token = token;
+ Q_EMIT tokenChanged();
+ reset();
+}
+
+AccountState* CallStateChecker::accountState() const
+{
+ return _accountState;
+}
+
+void CallStateChecker::setAccountState(AccountState *state)
+{
+ _accountState = state;
+ Q_EMIT accountStateChanged();
+ reset();
+}
+
+bool CallStateChecker::checking() const
+{
+ return _checking;
+}
+
+void CallStateChecker::setChecking(const bool checking)
+{
+ if(checking) {
+ qCInfo(lcCallStateChecker) << "Starting to check state of call with token:" << _token;
+ _notificationTimer.start();
+ _statusCheckTimer.start();
+ } else {
+ qCInfo(lcCallStateChecker) << "Stopping checking of call state for call with token:" << _token;
+ _notificationTimer.stop();
+ _statusCheckTimer.stop();
+ _stateCheckJob.clear();
+ }
+
+ _checking = checking;
+ Q_EMIT checkingChanged();
+}
+
+void CallStateChecker::reset()
+{
+ qCInfo(lcCallStateChecker, "Resetting call check");
+ setChecking(false);
+ setChecking(true);
+}
+
+void CallStateChecker::slotNotificationTimerElapsed()
+{
+ qCInfo(lcCallStateChecker) << "Notification timer elapsed, stopping call checking of call with token:" << _token;
+ setChecking(false);
+ Q_EMIT stopNotifying();
+}
+
+void CallStateChecker::slotStatusCheckTimerElapsed()
+{
+ // Don't run check if another check is still ongoing
+ if (_stateCheckJob) {
+ return;
+ }
+
+ startCallStateCheck();
+}
+
+bool CallStateChecker::isAccountServerVersion22OrLater() const
+{
+ if(!_accountState || !_accountState->account()) {
+ return false;
+ }
+
+ const auto accountNcVersion = _accountState->account()->serverVersionInt();
+ constexpr auto ncVersion22 = OCC::Account::makeServerVersion(22, 0, 0);
+
+ return accountNcVersion >= ncVersion22;
+}
+
+void CallStateChecker::startCallStateCheck()
+{
+ // check connectivity and credentials
+ if (!(_accountState && _accountState->isConnected() &&
+ _accountState->account() && _accountState->account()->credentials() &&
+ _accountState->account()->credentials()->ready())) {
+ qCInfo(lcCallStateChecker, "Could not connect, can't check call state.");
+ return;
+ }
+
+ // Check for token
+ if(_token.isEmpty()) {
+ qCInfo(lcCallStateChecker, "No call token set, can't check without it.");
+ return;
+ }
+
+ qCInfo(lcCallStateChecker) << "Checking state of call with token: " << _token;
+
+ const auto spreedPath = QStringLiteral("ocs/v2.php/apps/spreed/");
+ const auto callApiPath = isAccountServerVersion22OrLater() ? QStringLiteral("api/v4/call/") : QStringLiteral("api/v1/call/");
+ const QString callPath = spreedPath + callApiPath + _token; // Make sure it's a QString and not a QStringBuilder
+
+ _stateCheckJob = new JsonApiJob(_accountState->account(), callPath, this);
+ connect(_stateCheckJob.data(), &JsonApiJob::jsonReceived, this, &CallStateChecker::slotCallStateReceived);
+
+ _stateCheckJob->setVerb(JsonApiJob::Verb::Get);
+ _stateCheckJob->start();
+}
+
+void CallStateChecker::slotCallStateReceived(const QJsonDocument &json, const int statusCode)
+{
+ if (statusCode != successStatusCode) {
+ qCInfo(lcCallStateChecker) << "Failed to retrieve call state data. Server returned status code: " << statusCode;
+ return;
+ }
+
+ const auto participantsJsonArray = json.object().value("ocs").toObject().value("data").toArray();
+
+ if (participantsJsonArray.empty()) {
+ qCInfo(lcCallStateChecker, "Call has no participants and has therefore been abandoned.");
+ Q_EMIT stopNotifying();
+ setChecking(false);
+ return;
+ }
+
+ for (const auto &participant : participantsJsonArray) {
+ const auto participantDataObject = participant.toObject();
+ const auto participantId = isAccountServerVersion22OrLater() ? participantDataObject.value("actorId").toString() : participantDataObject.value("userId").toString();
+
+ if (participantId == _accountState->account()->davUser()) {
+ qCInfo(lcCallStateChecker, "Found own account ID in participants list, meaning call has been joined.");
+ Q_EMIT stopNotifying();
+ setChecking(false);
+ return;
+ }
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License
+ * for more details.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <QTimer>
+
+#include "networkjobs.h"
+#include "accountstate.h"
+
+namespace OCC {
+
+class CallStateChecker : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString token READ token WRITE setToken NOTIFY tokenChanged)
+ Q_PROPERTY(AccountState* accountState READ accountState WRITE setAccountState NOTIFY accountStateChanged)
+ Q_PROPERTY(bool checking READ checking WRITE setChecking NOTIFY checkingChanged)
+
+public:
+ explicit CallStateChecker(QObject *parent = nullptr);
+
+ QString token() const;
+ AccountState* accountState() const;
+ bool checking() const;
+
+signals:
+ void tokenChanged();
+ void accountStateChanged();
+ void checkingChanged();
+
+ void stopNotifying();
+
+public slots:
+ void setToken(const QString &token);
+ void setAccountState(OCC::AccountState *accountState);
+ void setChecking(const bool checking);
+
+private slots:
+ void slotStatusCheckTimerElapsed();
+ void slotNotificationTimerElapsed();
+ void slotCallStateReceived(const QJsonDocument &json, const int statusCode);
+ void reset();
+
+private:
+ void setup();
+ void startCallStateCheck();
+ bool isAccountServerVersion22OrLater() const;
+
+ AccountState *_accountState = nullptr;
+ QString _token;
+ QTimer _statusCheckTimer; // How often we check the status of the call
+ QTimer _notificationTimer; // How long we present the call notification for
+ QPointer<JsonApiJob> _stateCheckJob;
+ bool _checking = false;
+};
+
+}
#include "tray/trayimageprovider.h"
#include "configfile.h"
#include "accessmanager.h"
+#include "callstatechecker.h"
#include <QCursor>
#include <QGuiApplication>
);
qmlRegisterType<WheelHandler>("com.nextcloud.desktopclient", 1, 0, "WheelHandler");
+ qmlRegisterType<CallStateChecker>("com.nextcloud.desktopclient", 1, 0, "CallStateChecker");
#if defined(Q_OS_MACOS) && defined(BUILD_OWNCLOUD_OSX_BUNDLE)
setUserNotificationCenterDelegate();
});
}
-void Systray::createCallDialog(const Activity &callNotification)
+void Systray::createCallDialog(const Activity &callNotification, const AccountStatePtr accountState)
{
qCDebug(lcSystray) << "Starting a new call dialog for notification with id: " << callNotification._id << "with text: " << callNotification._subject;
}
const QVariantMap initialProperties{
+ {"accountState", QVariant::fromValue(accountState.data())},
{"talkNotificationData", talkNotificationData},
{"links", links},
{"subject", callNotification._subject},
bool isOpen();
QString windowTitle() const;
bool useNormalWindow() const;
- void createCallDialog(const Activity &callNotification);
+ void createCallDialog(const Activity &callNotification, const AccountStatePtr accountState);
Q_INVOKABLE void pauseResumeSync();
Q_INVOKABLE bool syncIsPaused();
+/*
+ * Copyright (C) 2022 by Camila Ayres <camila@nextcloud.com>
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License
+ * for more details.
+ */
+
import QtQuick 2.15
import QtQuick.Window 2.15
import Style 1.0
readonly property string deleteIcon: svgImage.arg("delete")
// We set talkNotificationData, subject, and links properties in C++
+ property var accountState: ({})
property var talkNotificationData: ({})
property string subject: ""
property var links: []
readonly property bool usingUserAvatar: root.talkNotificationData.userAvatar !== ""
function closeNotification() {
+ callStateChecker.checking = false;
ringSound.stop();
root.close();
}
root.requestActivate();
ringSound.play();
- }
+ callStateChecker.checking = true;
+ }
+
+ CallStateChecker {
+ id: callStateChecker
+ token: root.talkNotificationData.conversationToken
+ accountState: root.accountState
+
+ onStopNotifying: root.closeNotification()
+ }
Audio {
id: ringSound
source: root.ringtonePath
loops: 9 // about 45 seconds of audio playing
audioRole: Audio.RingtoneRole
- onStopped: root.closeNotification()
}
Rectangle {
if(systray) {
for(const auto &activity : list) {
- systray->createCallDialog(activity);
+ systray->createCallDialog(activity, _account);
}
}
}
components.value(2).toInt());
}
-int Account::makeServerVersion(int majorVersion, int minorVersion, int patchVersion)
-{
- return (majorVersion << 16) + (minorVersion << 8) + patchVersion;
-}
-
bool Account::serverVersionUnsupported() const
{
if (serverVersionInt() == 0) {
*/
int serverVersionInt() const;
- static int makeServerVersion(int majorVersion, int minorVersion, int patchVersion);
+ static constexpr int makeServerVersion(const int majorVersion, const int minorVersion, const int patchVersion) {
+ return (majorVersion << 16) + (minorVersion << 8) + patchVersion;
+ };
+
void setServerVersion(const QString &version);
/** Whether the server is too old.