mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-22 13:05:51 +03:00
Merge branch 'notifications'
This commit is contained in:
commit
6b0d535120
30 changed files with 1559 additions and 318 deletions
|
@ -22,5 +22,6 @@
|
|||
<file>resources/account.png</file>
|
||||
<file>resources/more.png</file>
|
||||
<file>resources/delete.png</file>
|
||||
<file>resources/bell.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -25,3 +25,6 @@ You can change the following configuration settings (must be under the ``[ownClo
|
|||
- ``chunkSize`` (default: ``5242880``) -- Specifies the chunk size of uploaded files in bytes.
|
||||
|
||||
- ``promptDeleteAllFiles`` (default: ``true``) -- If a UI prompt should ask for confirmation if it was detected that all files and folders were deleted.
|
||||
|
||||
- ``notificationRefreshInterval`` (default``300,000``) -- Specifies the default interval of checking for new server notifications in milliseconds.
|
||||
|
||||
|
|
BIN
resources/bell.png
Normal file
BIN
resources/bell.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 900 B |
|
@ -32,6 +32,7 @@ set(client_UI
|
|||
owncloudsetuppage.ui
|
||||
addcertificatedialog.ui
|
||||
proxyauthdialog.ui
|
||||
notificationwidget.ui
|
||||
wizard/owncloudadvancedsetuppage.ui
|
||||
wizard/owncloudconnectionmethoddialog.ui
|
||||
wizard/owncloudhttpcredspage.ui
|
||||
|
@ -62,6 +63,8 @@ set(client_SRCS
|
|||
owncloudgui.cpp
|
||||
owncloudsetupwizard.cpp
|
||||
protocolwidget.cpp
|
||||
activitydata.cpp
|
||||
activitylistmodel.cpp
|
||||
activitywidget.cpp
|
||||
activityitemdelegate.cpp
|
||||
selectivesyncdialog.cpp
|
||||
|
@ -85,6 +88,9 @@ set(client_SRCS
|
|||
proxyauthdialog.cpp
|
||||
synclogdialog.cpp
|
||||
tooltipupdater.cpp
|
||||
notificationwidget.cpp
|
||||
notificationconfirmjob.cpp
|
||||
servernotificationhandler.cpp
|
||||
creds/credentialsfactory.cpp
|
||||
creds/httpcredentialsgui.cpp
|
||||
creds/shibbolethcredentials.cpp
|
||||
|
|
35
src/gui/activitydata.cpp
Normal file
35
src/gui/activitydata.cpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.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; version 2 of the License.
|
||||
*
|
||||
* 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 <QtCore>
|
||||
|
||||
#include "activitydata.h"
|
||||
|
||||
|
||||
namespace OCC
|
||||
{
|
||||
|
||||
bool operator<( const Activity& rhs, const Activity& lhs ) {
|
||||
return rhs._dateTime.toMSecsSinceEpoch() > lhs._dateTime.toMSecsSinceEpoch();
|
||||
}
|
||||
|
||||
bool operator==( const Activity& rhs, const Activity& lhs ) {
|
||||
return (rhs._type == lhs._type && rhs._id== lhs._id && rhs._accName == lhs._accName);
|
||||
}
|
||||
|
||||
Activity::Identifier Activity::ident() const {
|
||||
return Identifier( _id, _accName );
|
||||
}
|
||||
|
||||
|
||||
}
|
89
src/gui/activitydata.h
Normal file
89
src/gui/activitydata.h
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.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; version 2 of the License.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ACTIVITYDATA_H
|
||||
#define ACTIVITYDATA_H
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
namespace OCC {
|
||||
/**
|
||||
* @brief The ActivityLink class describes actions of an activity
|
||||
*
|
||||
* These are part of notifications which are mapped into activities.
|
||||
*/
|
||||
|
||||
class ActivityLink
|
||||
{
|
||||
public:
|
||||
QString _label;
|
||||
QString _link;
|
||||
QByteArray _verb;
|
||||
bool _isPrimary;
|
||||
};
|
||||
|
||||
/* ==================================================================== */
|
||||
/**
|
||||
* @brief Activity Structure
|
||||
* @ingroup gui
|
||||
*
|
||||
* contains all the information describing a single activity.
|
||||
*/
|
||||
|
||||
class Activity
|
||||
{
|
||||
public:
|
||||
typedef QPair<qlonglong, QString> Identifier;
|
||||
|
||||
enum Type {
|
||||
ActivityType,
|
||||
NotificationType
|
||||
};
|
||||
|
||||
Type _type;
|
||||
qlonglong _id;
|
||||
QString _subject;
|
||||
QString _message;
|
||||
QString _file;
|
||||
QUrl _link;
|
||||
QDateTime _dateTime;
|
||||
QString _accName;
|
||||
|
||||
QVector <ActivityLink> _links;
|
||||
/**
|
||||
* @brief Sort operator to sort the list youngest first.
|
||||
* @param val
|
||||
* @return
|
||||
*/
|
||||
|
||||
|
||||
Identifier ident() const;
|
||||
};
|
||||
|
||||
bool operator==( const Activity& rhs, const Activity& lhs );
|
||||
bool operator<( const Activity& rhs, const Activity& lhs );
|
||||
|
||||
/* ==================================================================== */
|
||||
/**
|
||||
* @brief The ActivityList
|
||||
* @ingroup gui
|
||||
*
|
||||
* A QList based list of Activities
|
||||
*/
|
||||
|
||||
typedef QList<Activity> ActivityList;
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // ACTIVITYDATA_H
|
228
src/gui/activitylistmodel.cpp
Normal file
228
src/gui/activitylistmodel.cpp
Normal file
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.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; version 2 of the License.
|
||||
*
|
||||
* 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 <QtCore>
|
||||
#include <QAbstractListModel>
|
||||
#include <QWidget>
|
||||
#include <QIcon>
|
||||
|
||||
#include "account.h"
|
||||
#include "accountstate.h"
|
||||
#include "accountmanager.h"
|
||||
#include "folderman.h"
|
||||
#include "accessmanager.h"
|
||||
#include "activityitemdelegate.h"
|
||||
|
||||
#include "activitydata.h"
|
||||
#include "activitylistmodel.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
ActivityListModel::ActivityListModel(QWidget *parent)
|
||||
:QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
Activity a;
|
||||
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
a = _finalList.at(index.row());
|
||||
AccountStatePtr ast = AccountManager::instance()->account(a._accName);
|
||||
QStringList list;
|
||||
|
||||
switch (role) {
|
||||
case ActivityItemDelegate::PathRole:
|
||||
list = FolderMan::instance()->findFileInLocalFolders(a._file, ast->account());
|
||||
if( list.count() > 0 ) {
|
||||
return QVariant(list.at(0));
|
||||
}
|
||||
// File does not exist anymore? Let's try to open its path
|
||||
list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(a._file).path(), ast->account());
|
||||
if( list.count() > 0 ) {
|
||||
return QVariant(list.at(0));
|
||||
}
|
||||
return QVariant();
|
||||
break;
|
||||
case ActivityItemDelegate::ActionIconRole:
|
||||
return QVariant(); // FIXME once the action can be quantified, display on Icon
|
||||
break;
|
||||
case ActivityItemDelegate::UserIconRole:
|
||||
return QIcon(QLatin1String(":/client/resources/account.png"));
|
||||
break;
|
||||
case Qt::ToolTipRole:
|
||||
case ActivityItemDelegate::ActionTextRole:
|
||||
return a._subject;
|
||||
break;
|
||||
case ActivityItemDelegate::LinkRole:
|
||||
return a._link;
|
||||
break;
|
||||
case ActivityItemDelegate::AccountRole:
|
||||
return a._accName;
|
||||
break;
|
||||
case ActivityItemDelegate::PointInTimeRole:
|
||||
return Utility::timeAgoInWords(a._dateTime);
|
||||
break;
|
||||
case ActivityItemDelegate::AccountConnectedRole:
|
||||
return (ast && ast->isConnected());
|
||||
break;
|
||||
default:
|
||||
return QVariant();
|
||||
|
||||
}
|
||||
return QVariant();
|
||||
|
||||
}
|
||||
|
||||
int ActivityListModel::rowCount(const QModelIndex&) const
|
||||
{
|
||||
return _finalList.count();
|
||||
}
|
||||
|
||||
// current strategy: Fetch 100 items per Account
|
||||
// ATTENTION: This method is const and thus it is not possible to modify
|
||||
// the _activityLists hash or so. Doesn't make it easier...
|
||||
bool ActivityListModel::canFetchMore(const QModelIndex& ) const
|
||||
{
|
||||
if( _activityLists.count() == 0 ) return true;
|
||||
|
||||
for(auto i = _activityLists.begin() ; i != _activityLists.end(); ++i) {
|
||||
AccountState *ast = i.key();
|
||||
if( ast && ast->isConnected() ) {
|
||||
ActivityList activities = i.value();
|
||||
if( activities.count() == 0 &&
|
||||
! _currentlyFetching.contains(ast) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ActivityListModel::startFetchJob(AccountState* s)
|
||||
{
|
||||
if( !s->isConnected() ) {
|
||||
return;
|
||||
}
|
||||
JsonApiJob *job = new JsonApiJob(s->account(), QLatin1String("ocs/v1.php/cloud/activity"), this);
|
||||
QObject::connect(job, SIGNAL(jsonReceived(QVariantMap, int)),
|
||||
this, SLOT(slotActivitiesReceived(QVariantMap, int)));
|
||||
job->setProperty("AccountStatePtr", QVariant::fromValue<AccountState*>(s));
|
||||
|
||||
QList< QPair<QString,QString> > params;
|
||||
params.append(qMakePair(QString::fromLatin1("page"), QString::fromLatin1("0")));
|
||||
params.append(qMakePair(QString::fromLatin1("pagesize"), QString::fromLatin1("100")));
|
||||
job->addQueryParams(params);
|
||||
|
||||
_currentlyFetching.insert(s);
|
||||
qDebug() << Q_FUNC_INFO << "Start fetching activities for " << s->account()->displayName();
|
||||
job->start();
|
||||
}
|
||||
|
||||
void ActivityListModel::slotActivitiesReceived(const QVariantMap& json, int statusCode)
|
||||
{
|
||||
auto activities = json.value("ocs").toMap().value("data").toList();
|
||||
|
||||
ActivityList list;
|
||||
AccountState* ast = qvariant_cast<AccountState*>(sender()->property("AccountStatePtr"));
|
||||
_currentlyFetching.remove(ast);
|
||||
|
||||
foreach( auto activ, activities ) {
|
||||
auto json = activ.toMap();
|
||||
|
||||
Activity a;
|
||||
a._type = Activity::ActivityType;
|
||||
a._accName = ast->account()->displayName();
|
||||
a._id = json.value("id").toLongLong();
|
||||
a._subject = json.value("subject").toString();
|
||||
a._message = json.value("message").toString();
|
||||
a._file = json.value("file").toString();
|
||||
a._link = json.value("link").toUrl();
|
||||
a._dateTime = json.value("date").toDateTime();
|
||||
list.append(a);
|
||||
}
|
||||
|
||||
_activityLists[ast] = list;
|
||||
|
||||
emit activityJobStatusCode(ast, statusCode);
|
||||
|
||||
combineActivityLists();
|
||||
}
|
||||
|
||||
|
||||
void ActivityListModel::combineActivityLists()
|
||||
{
|
||||
ActivityList resultList;
|
||||
|
||||
foreach( ActivityList list, _activityLists.values() ) {
|
||||
resultList.append(list);
|
||||
}
|
||||
|
||||
std::sort( resultList.begin(), resultList.end() );
|
||||
|
||||
beginResetModel();
|
||||
_finalList.clear();
|
||||
endResetModel();
|
||||
|
||||
beginInsertRows(QModelIndex(), 0, resultList.count());
|
||||
_finalList = resultList;
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void ActivityListModel::fetchMore(const QModelIndex &)
|
||||
{
|
||||
QList<AccountStatePtr> accounts = AccountManager::instance()->accounts();
|
||||
|
||||
foreach (const AccountStatePtr& asp, accounts) {
|
||||
|
||||
if( !_activityLists.contains(asp.data()) && asp->isConnected() ) {
|
||||
_activityLists[asp.data()] = ActivityList();
|
||||
startFetchJob(asp.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityListModel::slotRefreshActivity(AccountState *ast)
|
||||
{
|
||||
if(ast && _activityLists.contains(ast)) {
|
||||
_activityLists.remove(ast);
|
||||
}
|
||||
startFetchJob(ast);
|
||||
}
|
||||
|
||||
void ActivityListModel::slotRemoveAccount(AccountState *ast )
|
||||
{
|
||||
if( _activityLists.contains(ast) ) {
|
||||
int i = 0;
|
||||
const QString accountToRemove = ast->account()->displayName();
|
||||
|
||||
QMutableListIterator<Activity> it(_finalList);
|
||||
|
||||
while (it.hasNext()) {
|
||||
Activity activity = it.next();
|
||||
if( activity._accName == accountToRemove ) {
|
||||
beginRemoveRows(QModelIndex(), i, i+1);
|
||||
it.remove();
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
_activityLists.remove(ast);
|
||||
_currentlyFetching.remove(ast);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
67
src/gui/activitylistmodel.h
Normal file
67
src/gui/activitylistmodel.h
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.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; version 2 of the License.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ACTIVITYLISTMODEL_H
|
||||
#define ACTIVITYLISTMODEL_H
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
#include "activitydata.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class AccountState;
|
||||
|
||||
/**
|
||||
* @brief The ActivityListModel
|
||||
* @ingroup gui
|
||||
*
|
||||
* Simple list model to provide the list view with data.
|
||||
*/
|
||||
|
||||
class ActivityListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ActivityListModel(QWidget *parent=0);
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
|
||||
bool canFetchMore(const QModelIndex& ) const Q_DECL_OVERRIDE;
|
||||
void fetchMore(const QModelIndex&) Q_DECL_OVERRIDE;
|
||||
|
||||
ActivityList activityList() { return _finalList; }
|
||||
|
||||
public slots:
|
||||
void slotRefreshActivity(AccountState* ast);
|
||||
void slotRemoveAccount( AccountState *ast );
|
||||
|
||||
private slots:
|
||||
void slotActivitiesReceived(const QVariantMap& json, int statusCode);
|
||||
|
||||
signals:
|
||||
void activityJobStatusCode(AccountState* ast, int statusCode);
|
||||
|
||||
private:
|
||||
void startFetchJob(AccountState* s);
|
||||
void combineActivityLists();
|
||||
|
||||
QMap<AccountState*, ActivityList> _activityLists;
|
||||
ActivityList _finalList;
|
||||
QSet<AccountState*> _currentlyFetching;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
#endif // ACTIVITYLISTMODEL_H
|
|
@ -16,8 +16,8 @@
|
|||
#include <QtWidgets>
|
||||
#endif
|
||||
|
||||
#include "activitylistmodel.h"
|
||||
#include "activitywidget.h"
|
||||
#include "configfile.h"
|
||||
#include "syncresult.h"
|
||||
#include "logger.h"
|
||||
#include "utility.h"
|
||||
|
@ -33,234 +33,28 @@
|
|||
#include "activityitemdelegate.h"
|
||||
#include "protocolwidget.h"
|
||||
#include "QProgressIndicator.h"
|
||||
#include "notificationwidget.h"
|
||||
#include "notificationconfirmjob.h"
|
||||
#include "servernotificationhandler.h"
|
||||
#include "theme.h"
|
||||
#include "ocsjob.h"
|
||||
|
||||
#include "ui_activitywidget.h"
|
||||
|
||||
#include <climits>
|
||||
|
||||
// time span in milliseconds which has to be between two
|
||||
// refreshes of the notifications
|
||||
#define NOTIFICATION_REQUEST_FREE_PERIOD 15000
|
||||
|
||||
namespace OCC {
|
||||
|
||||
void ActivityList::setAccountName( const QString& name )
|
||||
{
|
||||
_accountName = name;
|
||||
}
|
||||
|
||||
QString ActivityList::accountName() const
|
||||
{
|
||||
return _accountName;
|
||||
}
|
||||
|
||||
/* ==================================================================== */
|
||||
|
||||
ActivityListModel::ActivityListModel(QWidget *parent)
|
||||
:QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
Activity a;
|
||||
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
a = _finalList.at(index.row());
|
||||
AccountStatePtr ast = AccountManager::instance()->account(a._accName);
|
||||
QStringList list;
|
||||
|
||||
if (role == Qt::EditRole)
|
||||
return QVariant();
|
||||
|
||||
switch (role) {
|
||||
case ActivityItemDelegate::PathRole:
|
||||
list = FolderMan::instance()->findFileInLocalFolders(a._file, ast->account());
|
||||
if( list.count() > 0 ) {
|
||||
return QVariant(list.at(0));
|
||||
}
|
||||
// File does not exist anymore? Let's try to open its path
|
||||
list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(a._file).path(), ast->account());
|
||||
if( list.count() > 0 ) {
|
||||
return QVariant(list.at(0));
|
||||
}
|
||||
return QVariant();
|
||||
break;
|
||||
case ActivityItemDelegate::ActionIconRole:
|
||||
return QVariant(); // FIXME once the action can be quantified, display on Icon
|
||||
break;
|
||||
case ActivityItemDelegate::UserIconRole:
|
||||
return QIcon(QLatin1String(":/client/resources/account.png"));
|
||||
break;
|
||||
case Qt::ToolTipRole:
|
||||
case ActivityItemDelegate::ActionTextRole:
|
||||
return a._subject;
|
||||
break;
|
||||
case ActivityItemDelegate::LinkRole:
|
||||
return a._link;
|
||||
break;
|
||||
case ActivityItemDelegate::AccountRole:
|
||||
return a._accName;
|
||||
break;
|
||||
case ActivityItemDelegate::PointInTimeRole:
|
||||
return Utility::timeAgoInWords(a._dateTime);
|
||||
break;
|
||||
case ActivityItemDelegate::AccountConnectedRole:
|
||||
return (ast && ast->isConnected());
|
||||
break;
|
||||
default:
|
||||
return QVariant();
|
||||
|
||||
}
|
||||
return QVariant();
|
||||
|
||||
}
|
||||
|
||||
int ActivityListModel::rowCount(const QModelIndex&) const
|
||||
{
|
||||
return _finalList.count();
|
||||
}
|
||||
|
||||
// current strategy: Fetch 100 items per Account
|
||||
// ATTENTION: This method is const and thus it is not possible to modify
|
||||
// the _activityLists hash or so. Doesn't make it easier...
|
||||
bool ActivityListModel::canFetchMore(const QModelIndex& ) const
|
||||
{
|
||||
if( _activityLists.count() == 0 ) return true;
|
||||
|
||||
QMap<AccountState*, ActivityList>::const_iterator i = _activityLists.begin();
|
||||
while (i != _activityLists.end()) {
|
||||
AccountState *ast = i.key();
|
||||
if( ast && ast->isConnected() ) {
|
||||
ActivityList activities = i.value();
|
||||
if( activities.count() == 0 &&
|
||||
! _currentlyFetching.contains(ast) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ActivityListModel::startFetchJob(AccountState* s)
|
||||
{
|
||||
if( !s->isConnected() ) {
|
||||
return;
|
||||
}
|
||||
JsonApiJob *job = new JsonApiJob(s->account(), QLatin1String("ocs/v1.php/cloud/activity"), this);
|
||||
QObject::connect(job, SIGNAL(jsonReceived(QVariantMap, int)),
|
||||
this, SLOT(slotActivitiesReceived(QVariantMap, int)));
|
||||
job->setProperty("AccountStatePtr", QVariant::fromValue<AccountState*>(s));
|
||||
|
||||
QList< QPair<QString,QString> > params;
|
||||
params.append(qMakePair(QString::fromLatin1("page"), QString::fromLatin1("0")));
|
||||
params.append(qMakePair(QString::fromLatin1("pagesize"), QString::fromLatin1("100")));
|
||||
job->addQueryParams(params);
|
||||
|
||||
_currentlyFetching.insert(s);
|
||||
qDebug() << "Start fetching activities for " << s->account()->displayName();
|
||||
job->start();
|
||||
}
|
||||
|
||||
void ActivityListModel::slotActivitiesReceived(const QVariantMap& json, int statusCode)
|
||||
{
|
||||
auto activities = json.value("ocs").toMap().value("data").toList();
|
||||
qDebug() << "*** activities" << activities;
|
||||
|
||||
ActivityList list;
|
||||
AccountState* ast = qvariant_cast<AccountState*>(sender()->property("AccountStatePtr"));
|
||||
_currentlyFetching.remove(ast);
|
||||
list.setAccountName( ast->account()->displayName());
|
||||
|
||||
foreach( auto activ, activities ) {
|
||||
auto json = activ.toMap();
|
||||
|
||||
Activity a;
|
||||
a._accName = ast->account()->displayName();
|
||||
a._id = json.value("id").toLongLong();
|
||||
a._subject = json.value("subject").toString();
|
||||
a._message = json.value("message").toString();
|
||||
a._file = json.value("file").toString();
|
||||
a._link = json.value("link").toUrl();
|
||||
a._dateTime = json.value("date").toDateTime();
|
||||
list.append(a);
|
||||
}
|
||||
|
||||
_activityLists[ast] = list;
|
||||
|
||||
emit activityJobStatusCode(ast, statusCode);
|
||||
|
||||
combineActivityLists();
|
||||
}
|
||||
|
||||
|
||||
void ActivityListModel::combineActivityLists()
|
||||
{
|
||||
ActivityList resultList;
|
||||
|
||||
foreach( ActivityList list, _activityLists.values() ) {
|
||||
resultList.append(list);
|
||||
}
|
||||
|
||||
std::sort( resultList.begin(), resultList.end() );
|
||||
|
||||
beginInsertRows(QModelIndex(), 0, resultList.count()-1);
|
||||
_finalList = resultList;
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void ActivityListModel::fetchMore(const QModelIndex &)
|
||||
{
|
||||
QList<AccountStatePtr> accounts = AccountManager::instance()->accounts();
|
||||
|
||||
foreach (AccountStatePtr asp, accounts) {
|
||||
bool newItem = false;
|
||||
|
||||
if( !_activityLists.contains(asp.data()) && asp->isConnected() ) {
|
||||
_activityLists[asp.data()] = ActivityList();
|
||||
newItem = true;
|
||||
}
|
||||
if( newItem ) {
|
||||
startFetchJob(asp.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityListModel::slotRefreshActivity(AccountState *ast)
|
||||
{
|
||||
if(ast && _activityLists.contains(ast)) {
|
||||
qDebug() << "**** Refreshing Activity list for" << ast->account()->displayName();
|
||||
_activityLists.remove(ast);
|
||||
}
|
||||
startFetchJob(ast);
|
||||
}
|
||||
|
||||
void ActivityListModel::slotRemoveAccount(AccountState *ast )
|
||||
{
|
||||
if( _activityLists.contains(ast) ) {
|
||||
int i = 0;
|
||||
const QString accountToRemove = ast->account()->displayName();
|
||||
|
||||
QMutableListIterator<Activity> it(_finalList);
|
||||
|
||||
while (it.hasNext()) {
|
||||
Activity activity = it.next();
|
||||
if( activity._accName == accountToRemove ) {
|
||||
beginRemoveRows(QModelIndex(), i, i+1);
|
||||
it.remove();
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
_activityLists.remove(ast);
|
||||
_currentlyFetching.remove(ast);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================================================================== */
|
||||
|
||||
ActivityWidget::ActivityWidget(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
_ui(new Ui::ActivityWidget)
|
||||
_ui(new Ui::ActivityWidget),
|
||||
_notificationRequestsRunning(0)
|
||||
{
|
||||
_ui->setupUi(this);
|
||||
|
||||
|
@ -276,6 +70,16 @@ ActivityWidget::ActivityWidget(QWidget *parent) :
|
|||
_ui->_activityList->setAlternatingRowColors(true);
|
||||
_ui->_activityList->setModel(_model);
|
||||
|
||||
_ui->_notifyLabel->hide();
|
||||
_ui->_notifyScroll->hide();
|
||||
|
||||
// Create a widget container for the notifications. The ui file defines
|
||||
// a scroll area that get a widget with a layout as children
|
||||
QWidget *w = new QWidget(this);
|
||||
_notificationsLayout = new QVBoxLayout(this);
|
||||
w->setLayout(_notificationsLayout);
|
||||
_ui->_notifyScroll->setWidget(w);
|
||||
|
||||
showLabels();
|
||||
|
||||
connect(_model, SIGNAL(activityJobStatusCode(AccountState*,int)),
|
||||
|
@ -289,6 +93,9 @@ ActivityWidget::ActivityWidget(QWidget *parent) :
|
|||
|
||||
connect( _ui->_activityList, SIGNAL(activated(QModelIndex)), this,
|
||||
SLOT(slotOpenFile(QModelIndex)));
|
||||
|
||||
connect( &_removeTimer, SIGNAL(timeout()), this, SLOT(slotCheckToCleanWidgets()) );
|
||||
_removeTimer.setInterval(1000);
|
||||
}
|
||||
|
||||
ActivityWidget::~ActivityWidget()
|
||||
|
@ -296,11 +103,26 @@ ActivityWidget::~ActivityWidget()
|
|||
delete _ui;
|
||||
}
|
||||
|
||||
void ActivityWidget::slotRefresh(AccountState *ptr)
|
||||
void ActivityWidget::slotRefreshActivities(AccountState *ptr)
|
||||
{
|
||||
_model->slotRefreshActivity(ptr);
|
||||
}
|
||||
|
||||
void ActivityWidget::slotRefreshNotifications(AccountState *ptr)
|
||||
{
|
||||
// start a server notification handler if no notification requests
|
||||
// are running
|
||||
if( _notificationRequestsRunning == 0 ) {
|
||||
ServerNotificationHandler *snh = new ServerNotificationHandler;
|
||||
connect(snh, SIGNAL(newNotificationList(ActivityList)), this,
|
||||
SLOT(slotBuildNotificationDisplay(ActivityList)));
|
||||
|
||||
snh->slotFetchNotifications(ptr);
|
||||
} else {
|
||||
qDebug() << Q_FUNC_INFO << "========> notification request counter not zero.";
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityWidget::slotRemoveAccount( AccountState *ptr )
|
||||
{
|
||||
_model->slotRemoveAccount(ptr);
|
||||
|
@ -312,6 +134,8 @@ void ActivityWidget::showLabels()
|
|||
_ui->_headerLabel->setTextFormat(Qt::RichText);
|
||||
_ui->_headerLabel->setText(t);
|
||||
|
||||
_ui->_notifyLabel->setText(tr("Action Required: Notifications"));
|
||||
|
||||
t.clear();
|
||||
QSetIterator<QString> i(_accountsWithoutActivities);
|
||||
while (i.hasNext() ) {
|
||||
|
@ -389,7 +213,7 @@ void ActivityWidget::storeActivityList( QTextStream& ts )
|
|||
|
||||
void ActivityWidget::slotOpenFile(QModelIndex indx)
|
||||
{
|
||||
qDebug() << indx.isValid() << indx.data(ActivityItemDelegate::PathRole).toString() << QFile::exists(indx.data(ActivityItemDelegate::PathRole).toString());
|
||||
qDebug() << Q_FUNC_INFO << indx.isValid() << indx.data(ActivityItemDelegate::PathRole).toString() << QFile::exists(indx.data(ActivityItemDelegate::PathRole).toString());
|
||||
if( indx.isValid() ) {
|
||||
QString fullPath = indx.data(ActivityItemDelegate::PathRole).toString();
|
||||
|
||||
|
@ -399,6 +223,264 @@ void ActivityWidget::slotOpenFile(QModelIndex indx)
|
|||
}
|
||||
}
|
||||
|
||||
// GUI: Display the notifications.
|
||||
// All notifications in list are coming from the same account
|
||||
// but in the _widgetForNotifId hash widgets for all accounts are
|
||||
// collected.
|
||||
void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list)
|
||||
{
|
||||
QHash<QString, int> accNotified;
|
||||
QString listAccountName;
|
||||
|
||||
foreach( auto activity, list ) {
|
||||
if( _blacklistedNotifications.contains(activity)) {
|
||||
qDebug() << Q_FUNC_INFO << "Activity in blacklist, skip";
|
||||
continue;
|
||||
}
|
||||
|
||||
NotificationWidget *widget = 0;
|
||||
|
||||
if( _widgetForNotifId.contains( activity.ident()) ) {
|
||||
widget = _widgetForNotifId[activity.ident()];
|
||||
} else {
|
||||
widget = new NotificationWidget(this);
|
||||
connect(widget, SIGNAL(sendNotificationRequest(QString, QString, QByteArray)),
|
||||
this, SLOT(slotSendNotificationRequest(QString, QString, QByteArray)));
|
||||
connect(widget, SIGNAL(requestCleanupAndBlacklist(Activity)),
|
||||
this, SLOT(slotRequestCleanupAndBlacklist(Activity)));
|
||||
|
||||
_notificationsLayout->addWidget(widget);
|
||||
// _ui->_notifyScroll->setMinimumHeight( widget->height());
|
||||
_ui->_notifyScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
|
||||
_widgetForNotifId[activity.ident()] = widget;
|
||||
}
|
||||
|
||||
widget->setActivity( activity );
|
||||
|
||||
// remember the list account name for the strayCat handling below.
|
||||
listAccountName = activity._accName;
|
||||
|
||||
// handle gui logs. In order to NOT annoy the user with every fetching of the
|
||||
// notifications the notification id is stored in a Set. Only if an id
|
||||
// is not in the set, it qualifies for guiLog.
|
||||
// Important: The _guiLoggedNotifications set must be wiped regularly which
|
||||
// will repeat the gui log.
|
||||
|
||||
// after one hour, clear the gui log notification store
|
||||
if( _guiLogTimer.elapsed() > 60*60*1000 ) {
|
||||
_guiLoggedNotifications.clear();
|
||||
}
|
||||
if( !_guiLoggedNotifications.contains(activity._id)) {
|
||||
QString host = activity._accName;
|
||||
// store the name of the account that sends the notification to be
|
||||
// able to add it to the tray notification
|
||||
// remove the user name from the account as that is not accurate here.
|
||||
int indx = host.indexOf(QChar('@'));
|
||||
if( indx>-1 ) {
|
||||
host.remove(0, 1+indx);
|
||||
}
|
||||
if( !host.isEmpty() ) {
|
||||
if( accNotified.contains(host)) {
|
||||
accNotified[host] = accNotified[host]+1;
|
||||
} else {
|
||||
accNotified[host] = 1;
|
||||
}
|
||||
}
|
||||
_guiLoggedNotifications.insert(activity._id);
|
||||
}
|
||||
}
|
||||
|
||||
// check if there are widgets that have no corresponding activity from
|
||||
// the server any more. Collect them in a list
|
||||
QList< Activity::Identifier > strayCats;
|
||||
foreach( auto id, _widgetForNotifId.keys() ) {
|
||||
NotificationWidget *widget = _widgetForNotifId[id];
|
||||
|
||||
bool found = false;
|
||||
// do not mark widgets of other accounts to delete.
|
||||
if( widget->activity()._accName != listAccountName ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach( auto activity, list ) {
|
||||
if( activity.ident() == id ) {
|
||||
// found an activity
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( ! found ) {
|
||||
// the activity does not exist any more.
|
||||
strayCats.append(id);
|
||||
}
|
||||
}
|
||||
|
||||
// .. and now delete all these stray cat widgets.
|
||||
foreach( auto strayCatId, strayCats ) {
|
||||
NotificationWidget *widgetToGo = _widgetForNotifId[strayCatId];
|
||||
scheduleWidgetToRemove(widgetToGo, 0);
|
||||
}
|
||||
|
||||
_ui->_notifyLabel->setHidden( _widgetForNotifId.isEmpty() );
|
||||
_ui->_notifyScroll->setHidden( _widgetForNotifId.isEmpty() );
|
||||
|
||||
int newGuiLogCount = accNotified.count();
|
||||
|
||||
if( newGuiLogCount > 0 ) {
|
||||
// restart the gui log timer now that we show a notification
|
||||
_guiLogTimer.restart();
|
||||
|
||||
// Assemble a tray notification
|
||||
QString msg = tr("You received %n new notification(s) from %2.", "", accNotified[accNotified.keys().at(0)]).
|
||||
arg(accNotified.keys().at(0));
|
||||
|
||||
if( newGuiLogCount >= 2 ) {
|
||||
QString acc1 = accNotified.keys().at(0);
|
||||
QString acc2 = accNotified.keys().at(1);
|
||||
if( newGuiLogCount == 2 ) {
|
||||
int notiCount = accNotified[ acc1 ] + accNotified[ acc2 ];
|
||||
msg = tr("You received %n new notification(s) from %1 and %2.", "", notiCount).arg(acc1, acc2);
|
||||
} else {
|
||||
msg = tr("You received new notifications from %1, %2 and other accounts.").arg(acc1, acc2);
|
||||
}
|
||||
}
|
||||
|
||||
const QString log = tr("%1 Notifications - Action Required").arg(Theme::instance()->appNameGUI());
|
||||
emit guiLog( log, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityWidget::slotSendNotificationRequest(const QString& accountName, const QString& link, const QByteArray& verb)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << "Server Notification Request " << verb << link << "on account" << accountName;
|
||||
NotificationWidget *theSender = qobject_cast<NotificationWidget*>(sender());
|
||||
|
||||
const QStringList validVerbs = QStringList() << "GET" << "PUT" << "POST" << "DELETE";
|
||||
|
||||
if( validVerbs.contains(verb)) {
|
||||
AccountStatePtr acc = AccountManager::instance()->account(accountName);
|
||||
if( acc ) {
|
||||
NotificationConfirmJob *job = new NotificationConfirmJob(acc->account());
|
||||
QUrl l(link);
|
||||
job->setLinkAndVerb(l, verb);
|
||||
job->setWidget(theSender);
|
||||
connect( job, SIGNAL( networkError(QNetworkReply*)),
|
||||
this, SLOT(slotNotifyNetworkError(QNetworkReply*)));
|
||||
connect( job, SIGNAL( jobFinished(QString, int)),
|
||||
this, SLOT(slotNotifyServerFinished(QString, int)) );
|
||||
job->start();
|
||||
|
||||
// count the number of running notification requests. If this member var
|
||||
// is larger than zero, no new fetching of notifications is started
|
||||
_notificationRequestsRunning++;
|
||||
}
|
||||
} else {
|
||||
qDebug() << Q_FUNC_INFO << "Notification Links: Invalid verb:" << verb;
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityWidget::endNotificationRequest( NotificationWidget *widget, int replyCode )
|
||||
{
|
||||
_notificationRequestsRunning--;
|
||||
if( widget ) {
|
||||
widget->slotNotificationRequestFinished(replyCode);
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityWidget::slotNotifyNetworkError( QNetworkReply *reply)
|
||||
{
|
||||
NotificationConfirmJob *job = qobject_cast<NotificationConfirmJob*>(sender());
|
||||
if( !job ) {
|
||||
return;
|
||||
}
|
||||
|
||||
int resultCode =0;
|
||||
if( reply ) {
|
||||
resultCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
}
|
||||
|
||||
endNotificationRequest(job->widget(), resultCode);
|
||||
qDebug() << Q_FUNC_INFO << "Server notify job failed with code " << resultCode;
|
||||
|
||||
}
|
||||
|
||||
void ActivityWidget::slotNotifyServerFinished( const QString& reply, int replyCode )
|
||||
{
|
||||
NotificationConfirmJob *job = qobject_cast<NotificationConfirmJob*>(sender());
|
||||
if( !job ) {
|
||||
return;
|
||||
}
|
||||
|
||||
endNotificationRequest(job->widget(), replyCode);
|
||||
// FIXME: remove the widget after a couple of seconds
|
||||
qDebug() << Q_FUNC_INFO << "Server Notification reply code"<< replyCode << reply;
|
||||
|
||||
// if the notification was successful start a timer that triggers
|
||||
// removal of the done widgets in a few seconds
|
||||
// Add 200 millisecs to the predefined value to make sure that the timer in
|
||||
// widget's method readyToClose() has elapsed.
|
||||
if( replyCode == OCS_SUCCESS_STATUS_CODE ) {
|
||||
scheduleWidgetToRemove( job->widget() );
|
||||
}
|
||||
}
|
||||
|
||||
// blacklist the activity coming in here.
|
||||
void ActivityWidget::slotRequestCleanupAndBlacklist(const Activity& blacklistActivity)
|
||||
{
|
||||
if ( ! _blacklistedNotifications.contains(blacklistActivity) ) {
|
||||
_blacklistedNotifications.append(blacklistActivity);
|
||||
}
|
||||
|
||||
NotificationWidget *widget = _widgetForNotifId[ blacklistActivity.ident() ];
|
||||
scheduleWidgetToRemove(widget);
|
||||
}
|
||||
|
||||
void ActivityWidget::scheduleWidgetToRemove(NotificationWidget *widget, int milliseconds)
|
||||
{
|
||||
if( !widget ) {
|
||||
return;
|
||||
}
|
||||
// in fife seconds from now, remove the widget.
|
||||
QDateTime removeTime = QDateTime::currentDateTime().addMSecs(milliseconds);
|
||||
|
||||
QPair<QDateTime, NotificationWidget*> removeInfo = qMakePair(removeTime, widget);
|
||||
if( !_widgetsToRemove.contains(removeInfo) ) {
|
||||
_widgetsToRemove.insert( removeInfo );
|
||||
if( !_removeTimer.isActive() ) {
|
||||
_removeTimer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called every second to see if widgets need to be removed.
|
||||
void ActivityWidget::slotCheckToCleanWidgets()
|
||||
{
|
||||
// loop over all widgets in the to-remove queue
|
||||
foreach( auto toRemove, _widgetsToRemove ) {
|
||||
QDateTime t = toRemove.first;
|
||||
NotificationWidget *widget = toRemove.second;
|
||||
|
||||
if( QDateTime::currentDateTime() > t ) {
|
||||
// found one to remove!
|
||||
Activity::Identifier id = widget->activity().ident();
|
||||
_widgetForNotifId.remove(id);
|
||||
widget->deleteLater();
|
||||
_widgetsToRemove.remove(toRemove);
|
||||
}
|
||||
}
|
||||
|
||||
if( _widgetsToRemove.isEmpty() ) {
|
||||
_removeTimer.stop();
|
||||
}
|
||||
|
||||
// check to see if the whole notification pane should be hidden
|
||||
if( _widgetForNotifId.isEmpty() ) {
|
||||
_ui->_notifyLabel->setHidden(true);
|
||||
_ui->_notifyScroll->setHidden(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ==================================================================== */
|
||||
|
||||
ActivitySettings::ActivitySettings(QWidget *parent)
|
||||
|
@ -414,6 +496,7 @@ ActivitySettings::ActivitySettings(QWidget *parent)
|
|||
_activityTabId = _tab->insertTab(0, _activityWidget, Theme::instance()->applicationIcon(), tr("Server Activity"));
|
||||
connect(_activityWidget, SIGNAL(copyToClipboard()), this, SLOT(slotCopyToClipboard()));
|
||||
connect(_activityWidget, SIGNAL(hideAcitivityTab(bool)), this, SLOT(setActivityTabHidden(bool)));
|
||||
connect(_activityWidget, SIGNAL(guiLog(QString,QString)), this, SIGNAL(guiLog(QString,QString)));
|
||||
|
||||
_protocolWidget = new ProtocolWidget(this);
|
||||
_tab->insertTab(1, _protocolWidget, Theme::instance()->syncStateIcon(SyncResult::Success), tr("Sync Protocol"));
|
||||
|
@ -438,6 +521,9 @@ ActivitySettings::ActivitySettings(QWidget *parent)
|
|||
_progressIndicator = new QProgressIndicator(this);
|
||||
_tab->setCornerWidget(_progressIndicator);
|
||||
|
||||
connect(&_notificationCheckTimer, SIGNAL(timeout()),
|
||||
this, SLOT(slotRegularNotificationCheck()));
|
||||
|
||||
// connect a model signal to stop the animation.
|
||||
connect(_activityWidget, SIGNAL(rowsInserted()), _progressIndicator, SLOT(stopAnimation()));
|
||||
|
||||
|
@ -445,6 +531,12 @@ ActivitySettings::ActivitySettings(QWidget *parent)
|
|||
_tab->setCurrentIndex(1);
|
||||
}
|
||||
|
||||
void ActivitySettings::setNotificationRefreshInterval( quint64 interval )
|
||||
{
|
||||
qDebug() << "Starting Notification refresh timer with " << interval/1000 << " sec interval";
|
||||
_notificationCheckTimer.start(interval);
|
||||
}
|
||||
|
||||
void ActivitySettings::setActivityTabHidden(bool hidden)
|
||||
{
|
||||
if( hidden && _activityTabId > -1 ) {
|
||||
|
@ -490,10 +582,32 @@ void ActivitySettings::slotRemoveAccount( AccountState *ptr )
|
|||
|
||||
void ActivitySettings::slotRefresh( AccountState* ptr )
|
||||
{
|
||||
if( ptr && ptr->isConnected() && isVisible()) {
|
||||
qDebug() << "Refreshing Activity list for " << ptr->account()->displayName();
|
||||
QElapsedTimer timer = _timeSinceLastCheck[ptr];
|
||||
|
||||
// Fetch Activities only if visible and if last check is longer than 15 secs ago
|
||||
if( timer.isValid() && timer.elapsed() < NOTIFICATION_REQUEST_FREE_PERIOD ) {
|
||||
qDebug() << Q_FUNC_INFO << "do not check as last check is only secs ago: " << timer.elapsed() / 1000;
|
||||
return;
|
||||
}
|
||||
if( ptr && ptr->isConnected() ) {
|
||||
if( isVisible() ) {
|
||||
_progressIndicator->startAnimation();
|
||||
_activityWidget->slotRefresh(ptr);
|
||||
_activityWidget->slotRefreshActivities( ptr);
|
||||
}
|
||||
_activityWidget->slotRefreshNotifications(ptr);
|
||||
if( !( _timeSinceLastCheck[ptr].isValid() ) ) {
|
||||
_timeSinceLastCheck[ptr].start();
|
||||
} else {
|
||||
_timeSinceLastCheck[ptr].restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActivitySettings::slotRegularNotificationCheck()
|
||||
{
|
||||
AccountManager *am = AccountManager::instance();
|
||||
foreach (AccountStatePtr a, am->accounts()) {
|
||||
slotRefresh(a.data());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "progressdispatcher.h"
|
||||
#include "owncloudgui.h"
|
||||
#include "account.h"
|
||||
#include "activitydata.h"
|
||||
|
||||
#include "ui_activitywidget.h"
|
||||
|
||||
|
@ -33,97 +34,15 @@ namespace OCC {
|
|||
class Account;
|
||||
class AccountStatusPtr;
|
||||
class ProtocolWidget;
|
||||
class JsonApiJob;
|
||||
class NotificationWidget;
|
||||
class ActivityListModel;
|
||||
|
||||
namespace Ui {
|
||||
class ActivityWidget;
|
||||
}
|
||||
class Application;
|
||||
|
||||
/**
|
||||
* @brief Activity Structure
|
||||
* @ingroup gui
|
||||
*
|
||||
* contains all the information describing a single activity.
|
||||
*/
|
||||
|
||||
class Activity
|
||||
{
|
||||
public:
|
||||
qlonglong _id;
|
||||
QString _subject;
|
||||
QString _message;
|
||||
QString _file;
|
||||
QUrl _link;
|
||||
QDateTime _dateTime;
|
||||
QString _accName;
|
||||
|
||||
/**
|
||||
* @brief Sort operator to sort the list youngest first.
|
||||
* @param val
|
||||
* @return
|
||||
*/
|
||||
bool operator<( const Activity& val ) const {
|
||||
return _dateTime.toMSecsSinceEpoch() > val._dateTime.toMSecsSinceEpoch();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The ActivityList
|
||||
* @ingroup gui
|
||||
*
|
||||
* A QList based list of Activities
|
||||
*/
|
||||
class ActivityList:public QList<Activity>
|
||||
{
|
||||
public:
|
||||
void setAccountName( const QString& name );
|
||||
QString accountName() const;
|
||||
|
||||
private:
|
||||
QString _accountName;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief The ActivityListModel
|
||||
* @ingroup gui
|
||||
*
|
||||
* Simple list model to provide the list view with data.
|
||||
*/
|
||||
class ActivityListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ActivityListModel(QWidget *parent=0);
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
|
||||
bool canFetchMore(const QModelIndex& ) const Q_DECL_OVERRIDE;
|
||||
void fetchMore(const QModelIndex&) Q_DECL_OVERRIDE;
|
||||
|
||||
ActivityList activityList() { return _finalList; }
|
||||
|
||||
public slots:
|
||||
void slotRefreshActivity(AccountState* ast);
|
||||
void slotRemoveAccount( AccountState *ast );
|
||||
|
||||
private slots:
|
||||
void slotActivitiesReceived(const QVariantMap& json, int statusCode);
|
||||
|
||||
signals:
|
||||
void activityJobStatusCode(AccountState* ast, int statusCode);
|
||||
|
||||
private:
|
||||
void startFetchJob(AccountState* s);
|
||||
void combineActivityLists();
|
||||
|
||||
QMap<AccountState*, ActivityList> _activityLists;
|
||||
ActivityList _finalList;
|
||||
QSet<AccountState*> _currentlyFetching;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The ActivityWidget class
|
||||
* @ingroup gui
|
||||
|
@ -143,15 +62,27 @@ public:
|
|||
|
||||
public slots:
|
||||
void slotOpenFile(QModelIndex indx);
|
||||
void slotRefresh(AccountState* ptr);
|
||||
void slotRefreshActivities(AccountState* ptr);
|
||||
void slotRefreshNotifications(AccountState *ptr);
|
||||
void slotRemoveAccount( AccountState *ptr );
|
||||
void slotAccountActivityStatus(AccountState *ast, int statusCode);
|
||||
void slotRequestCleanupAndBlacklist(const Activity& blacklistActivity);
|
||||
|
||||
signals:
|
||||
void guiLog(const QString&, const QString&);
|
||||
void copyToClipboard();
|
||||
void rowsInserted();
|
||||
void hideAcitivityTab(bool);
|
||||
void newNotificationList(const ActivityList& list);
|
||||
|
||||
private slots:
|
||||
void slotBuildNotificationDisplay(const ActivityList& list);
|
||||
void slotSendNotificationRequest(const QString &accountName, const QString& link, const QByteArray &verb);
|
||||
void slotNotifyNetworkError( QNetworkReply* );
|
||||
void slotNotifyServerFinished( const QString& reply, int replyCode );
|
||||
void endNotificationRequest(NotificationWidget *widget , int replyCode);
|
||||
void scheduleWidgetToRemove(NotificationWidget *widget, int milliseconds = 4500);
|
||||
void slotCheckToCleanWidgets();
|
||||
|
||||
private:
|
||||
void showLabels();
|
||||
|
@ -160,8 +91,21 @@ private:
|
|||
QPushButton *_copyBtn;
|
||||
|
||||
QSet<QString> _accountsWithoutActivities;
|
||||
QMap<Activity::Identifier, NotificationWidget*> _widgetForNotifId;
|
||||
QElapsedTimer _guiLogTimer;
|
||||
QSet<int> _guiLoggedNotifications;
|
||||
ActivityList _blacklistedNotifications;
|
||||
|
||||
QSet< QPair<QDateTime, NotificationWidget*> > _widgetsToRemove;
|
||||
QTimer _removeTimer;
|
||||
|
||||
// number of currently running notification requests. If non zero,
|
||||
// no query for notifications is started.
|
||||
int _notificationRequestsRunning;
|
||||
|
||||
ActivityListModel *_model;
|
||||
QVBoxLayout *_notificationsLayout;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -184,9 +128,12 @@ public slots:
|
|||
void slotRefresh( AccountState* ptr );
|
||||
void slotRemoveAccount( AccountState *ptr );
|
||||
|
||||
void setNotificationRefreshInterval( quint64 interval );
|
||||
|
||||
private slots:
|
||||
void slotCopyToClipboard();
|
||||
void setActivityTabHidden(bool hidden);
|
||||
void slotRegularNotificationCheck();
|
||||
|
||||
signals:
|
||||
void guiLog(const QString&, const QString&);
|
||||
|
@ -200,7 +147,8 @@ private:
|
|||
ActivityWidget *_activityWidget;
|
||||
ProtocolWidget *_protocolWidget;
|
||||
QProgressIndicator *_progressIndicator;
|
||||
|
||||
QTimer _notificationCheckTimer;
|
||||
QHash<AccountState*, QElapsedTimer> _timeSinceLastCheck;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -15,23 +15,60 @@
|
|||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="_headerLabel">
|
||||
<widget class="QLabel" name="_notifyLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QListView" name="_activityList"/>
|
||||
<widget class="QScrollArea" name="_notifyScroll">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="_scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>677</width>
|
||||
<height>70</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="_bottomLabel">
|
||||
<widget class="QLabel" name="_headerLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QListView" name="_activityList">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="_bottomLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QDialogButtonBox" name="_dialogButtonBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
|
|
81
src/gui/notificationconfirmjob.cpp
Normal file
81
src/gui/notificationconfirmjob.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.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; version 2 of the License.
|
||||
*
|
||||
* 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 "notificationconfirmjob.h"
|
||||
#include "networkjobs.h"
|
||||
#include "account.h"
|
||||
#include "json.h"
|
||||
|
||||
#include <QBuffer>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
NotificationConfirmJob::NotificationConfirmJob(AccountPtr account)
|
||||
: AbstractNetworkJob(account, ""),
|
||||
_widget(0)
|
||||
{
|
||||
setIgnoreCredentialFailure(true);
|
||||
}
|
||||
|
||||
void NotificationConfirmJob::setLinkAndVerb(const QUrl& link, const QByteArray &verb)
|
||||
{
|
||||
_link = link;
|
||||
_verb = verb;
|
||||
}
|
||||
|
||||
void NotificationConfirmJob::setWidget( NotificationWidget *widget )
|
||||
{
|
||||
_widget = widget;
|
||||
}
|
||||
|
||||
NotificationWidget *NotificationConfirmJob::widget()
|
||||
{
|
||||
return _widget;
|
||||
}
|
||||
|
||||
void NotificationConfirmJob::start()
|
||||
{
|
||||
if( !_link.isValid() ) {
|
||||
qDebug() << "Attempt to trigger invalid URL: " << _link.toString();
|
||||
return;
|
||||
}
|
||||
QNetworkRequest req;
|
||||
req.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
QIODevice *iodevice = 0;
|
||||
setReply(davRequest(_verb, _link, req, iodevice));
|
||||
setupConnections(reply());
|
||||
|
||||
AbstractNetworkJob::start();
|
||||
}
|
||||
|
||||
bool NotificationConfirmJob::finished()
|
||||
{
|
||||
int replyCode = 0;
|
||||
// FIXME: check for the reply code!
|
||||
const QString replyStr = reply()->readAll();
|
||||
|
||||
if( replyStr.contains( "<?xml version=\"1.0\"?>") ) {
|
||||
QRegExp rex("<statuscode>(\\d+)</statuscode>");
|
||||
if( replyStr.contains(rex) ) {
|
||||
// this is a error message coming back from ocs.
|
||||
replyCode = rex.cap(1).toInt();
|
||||
}
|
||||
}
|
||||
emit jobFinished(replyStr, replyCode);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
90
src/gui/notificationconfirmjob.h
Normal file
90
src/gui/notificationconfirmjob.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.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; version 2 of the License.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef NOTIFICATIONCONFIRMJOB_H
|
||||
#define NOTIFICATIONCONFIRMJOB_H
|
||||
|
||||
#include "accountfwd.h"
|
||||
#include "abstractnetworkjob.h"
|
||||
|
||||
#include <QVector>
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
#include <QUrl>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class NotificationWidget;
|
||||
|
||||
/**
|
||||
* @brief The NotificationConfirmJob class
|
||||
* @ingroup gui
|
||||
*
|
||||
* Class to call an action-link of a notification coming from the server.
|
||||
* All the communication logic is handled in this class.
|
||||
*
|
||||
*/
|
||||
class NotificationConfirmJob : public AbstractNetworkJob {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
explicit NotificationConfirmJob(AccountPtr account);
|
||||
|
||||
/**
|
||||
* @brief Set the verb and link for the job
|
||||
*
|
||||
* @param verb currently supported GET PUT POST DELETE
|
||||
*/
|
||||
void setLinkAndVerb(const QUrl& link, const QByteArray &verb);
|
||||
|
||||
/**
|
||||
* @brief Start the OCS request
|
||||
*/
|
||||
void start() Q_DECL_OVERRIDE;
|
||||
|
||||
/**
|
||||
* @brief setWidget stores the associated widget to be able to use
|
||||
* it when the job has finished
|
||||
* @param widget pointer to the notification widget to store
|
||||
*/
|
||||
void setWidget( NotificationWidget *widget );
|
||||
|
||||
/**
|
||||
* @brief widget - get the associated notification widget as stored
|
||||
* with setWidget method.
|
||||
* @return widget pointer to the notification widget
|
||||
*/
|
||||
NotificationWidget *widget();
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
* Result of the OCS request
|
||||
*
|
||||
* @param reply the reply
|
||||
*/
|
||||
void jobFinished(QString reply, int replyCode);
|
||||
|
||||
private slots:
|
||||
virtual bool finished() Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
QByteArray _verb;
|
||||
QUrl _link;
|
||||
NotificationWidget *_widget;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NotificationConfirmJob_H
|
147
src/gui/notificationwidget.cpp
Normal file
147
src/gui/notificationwidget.cpp
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.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; version 2 of the License.
|
||||
*
|
||||
* 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 "notificationwidget.h"
|
||||
#include "QProgressIndicator.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
#include "ocsjob.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
NotificationWidget::NotificationWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
_ui.setupUi(this);
|
||||
_progressIndi = new QProgressIndicator(this);
|
||||
_ui.horizontalLayout->addWidget(_progressIndi);
|
||||
}
|
||||
|
||||
void NotificationWidget::setActivity(const Activity& activity)
|
||||
{
|
||||
_myActivity = activity;
|
||||
|
||||
Q_ASSERT( !activity._accName.isEmpty() );
|
||||
_accountName = activity._accName;
|
||||
|
||||
// _ui._headerLabel->setText( );
|
||||
_ui._subjectLabel->setText(activity._subject);
|
||||
if( activity._message.isEmpty()) {
|
||||
_ui._messageLabel->hide();
|
||||
} else {
|
||||
_ui._messageLabel->setText(activity._message);
|
||||
}
|
||||
_ui._notifIcon->setPixmap(QPixmap(":/client/resources/bell.png"));
|
||||
_ui._notifIcon->setMinimumWidth(64);
|
||||
_ui._notifIcon->setMinimumHeight(64);
|
||||
_ui._notifIcon->show();
|
||||
|
||||
QString tText = tr("Created at %1").arg(Utility::timeAgoInWords(activity._dateTime));
|
||||
_ui._timeLabel->setText(tText);
|
||||
|
||||
// always remove the buttons
|
||||
foreach( auto button, _ui._buttonBox->buttons() ) {
|
||||
_ui._buttonBox->removeButton(button);
|
||||
}
|
||||
_buttons.clear();
|
||||
|
||||
// display buttons for the links
|
||||
if( activity._links.isEmpty() ) {
|
||||
// in case there is no action defined, do a close button.
|
||||
QPushButton *b = _ui._buttonBox->addButton( QDialogButtonBox::Close );
|
||||
b->setDefault(true);
|
||||
connect(b, SIGNAL(clicked()), this, SLOT(slotButtonClicked()));
|
||||
_buttons.append(b);
|
||||
} else {
|
||||
foreach( auto link, activity._links ) {
|
||||
QPushButton *b = _ui._buttonBox->addButton(link._label, QDialogButtonBox::AcceptRole);
|
||||
b->setDefault(link._isPrimary);
|
||||
connect(b, SIGNAL(clicked()), this, SLOT(slotButtonClicked()));
|
||||
_buttons.append(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Activity NotificationWidget::activity() const
|
||||
{
|
||||
return _myActivity;
|
||||
}
|
||||
|
||||
void NotificationWidget::slotButtonClicked()
|
||||
{
|
||||
QObject *buttonWidget = QObject::sender();
|
||||
int index = -1;
|
||||
if( buttonWidget ) {
|
||||
// find the button that was clicked, it has to be in the list
|
||||
// of buttons that were added to the button box before.
|
||||
for( int i = 0; i < _buttons.count(); i++ ) {
|
||||
if( _buttons.at(i) == buttonWidget ) {
|
||||
index = i;
|
||||
}
|
||||
_buttons.at(i)->setEnabled(false);
|
||||
}
|
||||
|
||||
// if the button was found, the link must be called
|
||||
if( index > -1 && _myActivity._links.count() == 0 ) {
|
||||
// no links, that means it was the close button
|
||||
// empty link. Just close and remove the widget.
|
||||
QString doneText = tr("Closing in a few seconds...");
|
||||
_ui._timeLabel->setText(doneText);
|
||||
emit requestCleanupAndBlacklist(_myActivity);
|
||||
return;
|
||||
}
|
||||
|
||||
if( index > -1 && index < _myActivity._links.count() ) {
|
||||
ActivityLink triggeredLink = _myActivity._links.at(index);
|
||||
_actionLabel = triggeredLink._label;
|
||||
|
||||
if( ! triggeredLink._link.isEmpty() ) {
|
||||
qDebug() << Q_FUNC_INFO << "Notification Link: "<< triggeredLink._verb << triggeredLink._link;
|
||||
_progressIndi->startAnimation();
|
||||
emit sendNotificationRequest( _accountName, triggeredLink._link, triggeredLink._verb );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationWidget::slotNotificationRequestFinished(int statusCode)
|
||||
{
|
||||
int i = 0;
|
||||
QString doneText;
|
||||
QLocale locale;
|
||||
|
||||
QString timeStr = locale.toString(QTime::currentTime());
|
||||
|
||||
// the ocs API returns stat code 100 if it succeeded.
|
||||
if( statusCode != OCS_SUCCESS_STATUS_CODE ) {
|
||||
qDebug() << Q_FUNC_INFO << "Notification Request to Server failed, leave button visible.";
|
||||
for( i = 0; i < _buttons.count(); i++ ) {
|
||||
_buttons.at(i)->setEnabled(true);
|
||||
}
|
||||
//: The second parameter is a time, such as 'failed at 09:58pm'
|
||||
doneText = tr("%1 request failed at %2").arg(_actionLabel, timeStr);
|
||||
} else {
|
||||
// the call to the ocs API succeeded.
|
||||
_ui._buttonBox->hide();
|
||||
|
||||
//: The second parameter is a time, such as 'selected at 09:58pm'
|
||||
doneText = tr("'%1' selected at %2").arg(_actionLabel, timeStr);
|
||||
}
|
||||
_ui._timeLabel->setText( doneText );
|
||||
|
||||
_progressIndi->stopAnimation();
|
||||
|
||||
}
|
||||
|
||||
}
|
60
src/gui/notificationwidget.h
Normal file
60
src/gui/notificationwidget.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.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; version 2 of the License.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef NOTIFICATIONWIDGET_H
|
||||
#define NOTIFICATIONWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "activitydata.h"
|
||||
|
||||
#include "ui_notificationwidget.h"
|
||||
|
||||
#define NOTIFICATION_WIDGET_CLOSE_AFTER_MILLISECS 4800
|
||||
|
||||
class QProgressIndicator;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class NotificationWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit NotificationWidget(QWidget *parent = 0);
|
||||
|
||||
bool readyToClose();
|
||||
Activity activity() const;
|
||||
|
||||
signals:
|
||||
void sendNotificationRequest( const QString&, const QString& link, const QByteArray& verb);
|
||||
void requestCleanupAndBlacklist( const Activity& activity );
|
||||
|
||||
public slots:
|
||||
void setActivity(const Activity& activity);
|
||||
void slotNotificationRequestFinished(int statusCode);
|
||||
|
||||
private slots:
|
||||
void slotButtonClicked();
|
||||
|
||||
private:
|
||||
Ui_NotificationWidget _ui;
|
||||
Activity _myActivity;
|
||||
QList<QPushButton*> _buttons;
|
||||
QString _accountName;
|
||||
QProgressIndicator *_progressIndi;
|
||||
QString _actionLabel;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NOTIFICATIONWIDGET_H
|
129
src/gui/notificationwidget.ui
Normal file
129
src/gui/notificationwidget.ui
Normal file
|
@ -0,0 +1,129 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>NotificationWidget</class>
|
||||
<widget class="QWidget" name="NotificationWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>725</width>
|
||||
<height>139</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="_notifIcon">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap>../../../../resources/bell.png</pixmap>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="_subjectLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Lorem ipsum dolor sit amet</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="_messageLabel">
|
||||
<property name="text">
|
||||
<string>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam </string>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="_timeLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>8</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="_buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>4</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>1</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -23,7 +23,7 @@ namespace OCC {
|
|||
OcsJob::OcsJob(AccountPtr account)
|
||||
: AbstractNetworkJob(account, "")
|
||||
{
|
||||
_passStatusCodes.append(100);
|
||||
_passStatusCodes.append(OCS_SUCCESS_STATUS_CODE);
|
||||
setIgnoreCredentialFailure(true);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
#include <QPair>
|
||||
#include <QUrl>
|
||||
|
||||
#define OCS_SUCCESS_STATUS_CODE 100
|
||||
|
||||
namespace OCC {
|
||||
|
||||
/**
|
||||
|
|
103
src/gui/servernotificationhandler.cpp
Normal file
103
src/gui/servernotificationhandler.cpp
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.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; version 2 of the License.
|
||||
*
|
||||
* 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 "servernotificationhandler.h"
|
||||
#include "accountstate.h"
|
||||
#include "capabilities.h"
|
||||
#include "json.h"
|
||||
#include "networkjobs.h"
|
||||
|
||||
namespace OCC
|
||||
{
|
||||
|
||||
ServerNotificationHandler::ServerNotificationHandler(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ServerNotificationHandler::slotFetchNotifications(AccountState *ptr)
|
||||
{
|
||||
// check connectivity and credentials
|
||||
if( !( ptr && ptr->isConnected() && ptr->account() &&
|
||||
ptr->account()->credentials() &&
|
||||
ptr->account()->credentials()->ready() ) ) {
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
// check if the account has notifications enabled. If the capabilities are
|
||||
// not yet valid, its assumed that notifications are available.
|
||||
if( ptr->account()->capabilities().isValid() ) {
|
||||
if( ! ptr->account()->capabilities().notificationsAvailable() ) {
|
||||
qDebug() << Q_FUNC_INFO << "Account" << ptr->account()->displayName() << "does not have notifications enabled.";
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if the previous notification job has finished, start next.
|
||||
_notificationJob = new JsonApiJob( ptr->account(), QLatin1String("ocs/v2.php/apps/notifications/api/v1/notifications"), this );
|
||||
QObject::connect(_notificationJob.data(), SIGNAL(jsonReceived(QVariantMap, int)),
|
||||
this, SLOT(slotNotificationsReceived(QVariantMap, int)));
|
||||
_notificationJob->setProperty("AccountStatePtr", QVariant::fromValue<AccountState*>(ptr));
|
||||
|
||||
_notificationJob->start();
|
||||
}
|
||||
|
||||
void ServerNotificationHandler::slotNotificationsReceived(const QVariantMap& json, int statusCode)
|
||||
{
|
||||
if( statusCode != 200 ) {
|
||||
qDebug() << Q_FUNC_INFO << "Notifications failed with status code " << statusCode;
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
auto notifies = json.value("ocs").toMap().value("data").toList();
|
||||
|
||||
AccountState* ai = qvariant_cast<AccountState*>(sender()->property("AccountStatePtr"));
|
||||
|
||||
ActivityList list;
|
||||
|
||||
foreach( auto element, notifies ) {
|
||||
Activity a;
|
||||
auto json = element.toMap();
|
||||
a._type = Activity::NotificationType;
|
||||
a._accName = ai->account()->displayName();
|
||||
a._id = json.value("notification_id").toLongLong();
|
||||
a._subject = json.value("subject").toString();
|
||||
a._message = json.value("message").toString();
|
||||
QString s = json.value("link").toString();
|
||||
if( !s.isEmpty() ) {
|
||||
a._link = QUrl(s);
|
||||
}
|
||||
a._dateTime = json.value("datetime").toDateTime();
|
||||
|
||||
auto actions = json.value("actions").toList();
|
||||
foreach( auto action, actions) {
|
||||
auto actionJson = action.toMap();
|
||||
ActivityLink al;
|
||||
al._label = QUrl::fromPercentEncoding(actionJson.value("label").toByteArray());
|
||||
al._link = actionJson.value("link").toString();
|
||||
al._verb = actionJson.value("type").toByteArray();
|
||||
al._isPrimary = actionJson.value("primary").toBool();
|
||||
|
||||
a._links.append(al);
|
||||
}
|
||||
list.append(a);
|
||||
}
|
||||
emit newNotificationList( list );
|
||||
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
}
|
47
src/gui/servernotificationhandler.h
Normal file
47
src/gui/servernotificationhandler.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.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; version 2 of the License.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef SERVERNOTIFICATIONHANDLER_H
|
||||
#define SERVERNOTIFICATIONHANDLER_H
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
#include "activitywidget.h"
|
||||
|
||||
namespace OCC
|
||||
{
|
||||
|
||||
class ServerNotificationHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ServerNotificationHandler(QObject *parent = 0);
|
||||
|
||||
signals:
|
||||
void newNotificationList(ActivityList);
|
||||
|
||||
public slots:
|
||||
void slotFetchNotifications(AccountState *ptr);
|
||||
|
||||
private slots:
|
||||
void slotNotificationsReceived(const QVariantMap& json, int statusCode);
|
||||
|
||||
private:
|
||||
QPointer<JsonApiJob> _notificationJob;
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SERVERNOTIFICATIONHANDLER_H
|
|
@ -61,6 +61,8 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent) :
|
|||
QDialog(parent)
|
||||
, _ui(new Ui::SettingsDialog), _gui(gui)
|
||||
{
|
||||
ConfigFile cfg;
|
||||
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
_ui->setupUi(this);
|
||||
_toolBar = new QToolBar;
|
||||
|
@ -89,6 +91,7 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent) :
|
|||
_ui->stack->addWidget(_activitySettings);
|
||||
connect( _activitySettings, SIGNAL(guiLog(QString,QString)), _gui,
|
||||
SLOT(slotShowOptionalTrayMessage(QString,QString)) );
|
||||
_activitySettings->setNotificationRefreshInterval( cfg.notificationRefreshInterval());
|
||||
|
||||
QAction *generalAction = createColorAwareAction(QLatin1String(":/client/resources/settings.png"), tr("General"));
|
||||
_actionGroup->addAction(generalAction);
|
||||
|
@ -128,7 +131,6 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent) :
|
|||
|
||||
customizeStyle();
|
||||
|
||||
ConfigFile cfg;
|
||||
cfg.restoreGeometry(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -148,6 +148,11 @@ QNetworkReply *AbstractNetworkJob::headRequest(const QUrl &url)
|
|||
return addTimer(_account->headRequest(url));
|
||||
}
|
||||
|
||||
QNetworkReply *AbstractNetworkJob::deleteRequest(const QUrl &url)
|
||||
{
|
||||
return addTimer(_account->deleteRequest(url));
|
||||
}
|
||||
|
||||
void AbstractNetworkJob::slotFinished()
|
||||
{
|
||||
_timer.stop();
|
||||
|
|
|
@ -77,6 +77,7 @@ protected:
|
|||
QNetworkReply* getRequest(const QUrl &url);
|
||||
QNetworkReply* headRequest(const QString &relPath);
|
||||
QNetworkReply* headRequest(const QUrl &url);
|
||||
QNetworkReply* deleteRequest(const QUrl &url);
|
||||
|
||||
int maxRedirects() const { return 10; }
|
||||
virtual bool finished() = 0;
|
||||
|
|
|
@ -239,6 +239,15 @@ QNetworkReply *Account::getRequest(const QUrl &url)
|
|||
return _am->get(request);
|
||||
}
|
||||
|
||||
QNetworkReply *Account::deleteRequest( const QUrl &url)
|
||||
{
|
||||
QNetworkRequest request(url);
|
||||
#if QT_VERSION > QT_VERSION_CHECK(4, 8, 4)
|
||||
request.setSslConfiguration(this->getOrCreateSslConfig());
|
||||
#endif
|
||||
return _am->deleteResource(request);
|
||||
}
|
||||
|
||||
QNetworkReply *Account::davRequest(const QByteArray &verb, const QString &relPath, QNetworkRequest req, QIODevice *data)
|
||||
{
|
||||
return davRequest(verb, concatUrlPath(davUrl(), relPath), req, data);
|
||||
|
|
|
@ -111,6 +111,7 @@ public:
|
|||
QNetworkReply* headRequest(const QUrl &url);
|
||||
QNetworkReply* getRequest(const QString &relPath);
|
||||
QNetworkReply* getRequest(const QUrl &url);
|
||||
QNetworkReply* deleteRequest( const QUrl &url);
|
||||
QNetworkReply* davRequest(const QByteArray &verb, const QString &relPath, QNetworkRequest req, QIODevice *data = 0);
|
||||
QNetworkReply* davRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data = 0);
|
||||
|
||||
|
|
|
@ -71,6 +71,16 @@ bool Capabilities::shareResharing() const
|
|||
return _capabilities["files_sharing"].toMap()["resharing"].toBool();
|
||||
}
|
||||
|
||||
bool Capabilities::notificationsAvailable() const
|
||||
{
|
||||
return _capabilities.contains("notifications");
|
||||
}
|
||||
|
||||
bool Capabilities::isValid() const
|
||||
{
|
||||
return !_capabilities.isEmpty();
|
||||
}
|
||||
|
||||
QList<QByteArray> Capabilities::supportedChecksumTypesAdvertised() const
|
||||
{
|
||||
return QList<QByteArray>();
|
||||
|
|
|
@ -40,6 +40,12 @@ public:
|
|||
int sharePublicLinkExpireDateDays() const;
|
||||
bool shareResharing() const;
|
||||
|
||||
/// returns true if the capabilities report notifications
|
||||
bool notificationsAvailable() const;
|
||||
|
||||
/// returns true if the capabilities are loaded already.
|
||||
bool isValid() const;
|
||||
|
||||
/// Returns the checksum types the server explicitly advertises
|
||||
QList<QByteArray> supportedChecksumTypesAdvertised() const;
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ namespace OCC {
|
|||
//static const char caCertsKeyC[] = "CaCertificates"; only used from account.cpp
|
||||
static const char remotePollIntervalC[] = "remotePollInterval";
|
||||
static const char forceSyncIntervalC[] = "forceSyncInterval";
|
||||
static const char notificationRefreshIntervalC[] = "notificationRefreshInterval";
|
||||
static const char monoIconsC[] = "monoIcons";
|
||||
static const char promptDeleteC[] = "promptDeleteAllFiles";
|
||||
static const char crashReporterC[] = "crashReporter";
|
||||
|
@ -390,6 +391,22 @@ quint64 ConfigFile::forceSyncInterval(const QString& connection) const
|
|||
return interval;
|
||||
}
|
||||
|
||||
quint64 ConfigFile::notificationRefreshInterval(const QString& connection) const
|
||||
{
|
||||
QString con( connection );
|
||||
if( connection.isEmpty() ) con = defaultConnection();
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
settings.beginGroup( con );
|
||||
|
||||
quint64 defaultInterval = 5 * 60 * 1000ull; // 5 minutes
|
||||
quint64 interval = settings.value( QLatin1String(notificationRefreshIntervalC), defaultInterval ).toULongLong();
|
||||
if( interval < 60*1000ull) {
|
||||
qDebug() << "notification refresh interval smaller than one minute, setting to one minute";
|
||||
interval = 60*1000ull;
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
int ConfigFile::updateCheckInterval( const QString& connection ) const
|
||||
{
|
||||
QString con( connection );
|
||||
|
|
|
@ -63,6 +63,9 @@ public:
|
|||
/* Set poll interval. Value in milliseconds has to be larger than 5000 */
|
||||
void setRemotePollInterval(int interval, const QString& connection = QString() );
|
||||
|
||||
/* Interval to check for new notifications */
|
||||
quint64 notificationRefreshInterval(const QString& connection = QString()) const;
|
||||
|
||||
/* Force sync interval, in milliseconds */
|
||||
quint64 forceSyncInterval(const QString &connection = QString()) const;
|
||||
|
||||
|
|
|
@ -453,7 +453,7 @@ QByteArray Utility::versionOfInstalledBinary( const QString& command )
|
|||
|
||||
QString Utility::timeAgoInWords(const QDateTime& dt, const QDateTime& from)
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||
|
||||
if( from.isValid() ) {
|
||||
now = from;
|
||||
|
|
Loading…
Reference in a new issue