-
Notifications
You must be signed in to change notification settings - Fork 667
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
Changes from 13 commits
0eb1041
a831b74
eb00b34
688c550
32e16b3
4a4dac2
b97c832
7d13a1d
8a0ce46
903e79a
2d1ab27
2c2a18a
adf9570
73cd5a9
97f1694
9d219a1
9a2f145
a4dcc27
45c32ec
f7f4120
f71fdab
05de710
0a590b7
328d254
7f22a07
b966345
f587f35
d407aac
d03fcc9
ad60e8a
f70c628
ea2f19b
161d219
1bb3a4a
0c944a0
1fe5d6b
69e8e15
4d59f5e
cacb751
9f438cb
2e30a0e
cd3f612
8166c52
885f8b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/* | ||
* 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: | ||
QHash <QString, QVariant> toVariantHash() { | ||
QHash<QString, QVariant> hash; | ||
|
||
hash["label"] = _label; | ||
hash["link"] = _link; | ||
hash["verb"] = _verb; | ||
hash["primary"] = _isPrimary; | ||
|
||
return hash; | ||
} | ||
|
||
QString _label; | ||
QString _link; | ||
QString _verb; | ||
bool _isPrimary; | ||
}; | ||
|
||
/* ==================================================================== */ | ||
/** | ||
* @brief Activity Structure | ||
* @ingroup gui | ||
* | ||
* contains all the information describing a single activity. | ||
*/ | ||
|
||
class Activity | ||
{ | ||
public: | ||
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 | ||
*/ | ||
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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If inheriting here isn't mandatory for some reason, I'd prefer to have the list as a member. In fact, the accountName on this list seems to be unused anyway? Just typedef QList to ActivityList instead? |
||
{ | ||
public: | ||
void setAccountName( const QString& name ) { | ||
_accountName = name; | ||
} | ||
|
||
QString accountName() const { | ||
return _accountName; | ||
} | ||
|
||
private: | ||
QString _accountName; | ||
}; | ||
|
||
} | ||
|
||
#endif // ACTIVITYDATA_H |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
/* | ||
* 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; | ||
|
||
if (role == Qt::EditRole) | ||
return QVariant(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove? (or put next to the |
||
|
||
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()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for() loops are better since they are more readable and make it obvious it's an iteration. 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; | ||
} | ||
} | ||
++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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove? |
||
job->start(); | ||
} | ||
|
||
void ActivityListModel::slotActivitiesReceived(const QVariantMap& json, int statusCode) | ||
{ | ||
auto activities = json.value("ocs").toMap().value("data").toList(); | ||
// qDebug() << "*** activities" << activities; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove? |
||
|
||
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._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(); | ||
a._dateTime.setTimeSpec(Qt::UTC); | ||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if there used to be more rows before? Does one need to explicitly clear the other rows? |
||
_finalList = resultList; | ||
endInsertRows(); | ||
} | ||
|
||
void ActivityListModel::fetchMore(const QModelIndex &) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 (AccountStatePtr asp, accounts) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. const AccountStatePtr & |
||
bool newItem = false; | ||
|
||
if( !_activityLists.contains(asp.data()) && asp->isConnected() ) { | ||
_activityLists[asp.data()] = ActivityList(); | ||
newItem = true; | ||
} | ||
if( newItem ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
startFetchJob(asp.data()); | ||
} | ||
} | ||
} | ||
|
||
void ActivityListModel::slotRefreshActivity(AccountState *ast) | ||
{ | ||
if(ast && _activityLists.contains(ast)) { | ||
qDebug() << "**** Refreshing Activity list for" << ast->account()->displayName(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove? |
||
_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); | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use QVariantHash (introduced in Qt 4.5)? (the function could also be const)