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

Show Server Notifications on the Client #4567

Merged
merged 44 commits into from
Apr 4, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
0eb1041
AbstractNetworkJob: Add a delete job.
Mar 4, 2016
a831b74
Added temporar icon for notifications.
Mar 4, 2016
eb00b34
Minor wording fixes
Mar 4, 2016
688c550
New GUI class NotificationWidget.
Mar 4, 2016
32e16b3
Display server notifications on the client (#3733)
Mar 4, 2016
4a4dac2
Notifications: Add a Progress indicator and handle job results.
Mar 9, 2016
b97c832
Capabilities: Add isValid check and check for notifications
Mar 9, 2016
7d13a1d
Notifications: Check capabilities if the notifications are enabled
Mar 9, 2016
8a0ce46
Notifications: Properly delete the notification check job.
Mar 9, 2016
903e79a
Notifications: Do a GUI tray notification if new notifciations arrive.
Mar 10, 2016
2d1ab27
Notifications: Refactor - create a notification handler class
Mar 10, 2016
2c2a18a
Activitiy: Refactor - move classes to their own source files.
Mar 11, 2016
adf9570
Notification: Enhance the tray message
Mar 11, 2016
73cd5a9
Notifications: Cleaner notification string build
Mar 14, 2016
97f1694
ActivityData: Simplified implementation.
Mar 14, 2016
9d219a1
ActivityListModel: Code cleanups
Mar 14, 2016
9a2f145
ocs jobs: Add a define for OCS job success.
Mar 14, 2016
a4dcc27
Notification: Fix plural handling for tray message
Mar 14, 2016
45c32ec
NotificationWidget: Remove not needed method.
Mar 16, 2016
f7f4120
Activity: Some documentation and better varialbe names
Mar 16, 2016
f71fdab
Fix timeAgoInWords
Mar 18, 2016
05de710
Notifications: Display timestamp of the notification in the widget
Mar 18, 2016
0a590b7
Notifications: Give feedback if notifcation request succeeded.
Mar 18, 2016
328d254
Notifications: Remove "done" notification widgets after fife seconds.
Mar 18, 2016
7f22a07
Notifications: Check if the account is connected before querying.
Mar 11, 2016
b966345
Notifications: Refresh the notifications based on a config value.
Mar 18, 2016
f587f35
Fix plural translation handling, remove the superflous arg()
Mar 18, 2016
d407aac
Notifications: remove notification widgets if the notification is gone.
Mar 21, 2016
d03fcc9
Notifications: Maintain a timeSinceLastCheck for every Account.
Mar 22, 2016
ad60e8a
Notifications: Fix handling of notifications to remove from the list.
Mar 22, 2016
f70c628
Notifications: Remove unused variable.
Mar 22, 2016
ea2f19b
Docs: Add new config option for the notification sync interval.
Mar 22, 2016
161d219
ActivityData: Add source file for implementation details
Mar 23, 2016
1bb3a4a
NotificationWidget: Remove accountName() and add activity() method.
Mar 23, 2016
0c944a0
NotificationWidgetUI: Fix sizing and sizePolicy
Mar 23, 2016
1fe5d6b
Notifications: Handle Notifications without an action.
Mar 23, 2016
69e8e15
Remove explicit time spec specification as it is not needed.
Mar 29, 2016
4d59f5e
ActivityData: Declare operators outside the class
Mar 29, 2016
cacb751
Cleaups based on review feedback.
Mar 29, 2016
9f438cb
Doc: Add milliseconds unit to notificationRefreshInterval doc
Mar 29, 2016
2e30a0e
Remove superflous iterator increment
Mar 29, 2016
cd3f612
ActivityWidget: Rename blacklistActivities to blacklistNotifications.
Mar 29, 2016
8166c52
NotificationHandling: Use QByteArray for the verb.
Mar 29, 2016
885f8b3
ActivityWidget: Handle plural properly in translations.
Mar 29, 2016
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
1 change: 1 addition & 0 deletions client.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -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>
2 changes: 2 additions & 0 deletions doc/conffile.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ You can change the following configuration settings (must be under the ``[ownClo
- ``maxLogLines`` (default: ``20000``) -- Specifies the maximum number of log lines displayed in the log window.

- ``chunkSize`` (default: ``5242880``) -- Specifies the chunk size of uploaded files in bytes.

- ``notificationRefreshInterval`` (default``300,000``) -- Specifies the default interval of checking for new server notifications in milliseconds.
Binary file added resources/bell.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ set(client_UI
owncloudsetuppage.ui
addcertificatedialog.ui
proxyauthdialog.ui
notificationwidget.ui
wizard/owncloudadvancedsetuppage.ui
wizard/owncloudconnectionmethoddialog.ui
wizard/owncloudhttpcredspage.ui
Expand Down Expand Up @@ -62,6 +63,8 @@ set(client_SRCS
owncloudgui.cpp
owncloudsetupwizard.cpp
protocolwidget.cpp
activitydata.cpp
activitylistmodel.cpp
activitywidget.cpp
activityitemdelegate.cpp
selectivesyncdialog.cpp
Expand All @@ -84,6 +87,9 @@ set(client_SRCS
proxyauthhandler.cpp
proxyauthdialog.cpp
synclogdialog.cpp
notificationwidget.cpp
notificationconfirmjob.cpp
servernotificationhandler.cpp
creds/credentialsfactory.cpp
creds/httpcredentialsgui.cpp
creds/shibbolethcredentials.cpp
Expand Down
35 changes: 35 additions & 0 deletions src/gui/activitydata.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (C) by Klaas Freitag <[email protected]>
*
* 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 changes: 89 additions & 0 deletions src/gui/activitydata.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright (C) by Klaas Freitag <[email protected]>
*
* 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 changes: 228 additions & 0 deletions src/gui/activitylistmodel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/*
* Copyright (C) by Klaas Freitag <[email protected]>
*
* 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 &)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normaly, fetchMore is too be used when there are more items to fetch that are hidden.
So that can be used in order to fetch older items form example.
But here it's used to fetch other accounts despite it's not the next item.
fetchMore will be called when the scrollbar reach the end.
I don't think it's a good use of the fetchMore capabilities

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. let me handle that in a new PR together with a proxy model maybe.

{
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);
}
}

}
Loading