1/*
2 * Copyright (C) by Klaas Freitag <freitag@owncloud.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * for more details.
13 */
14
15#include <QtCore>
16#include <QAbstractListModel>
17#include <QWidget>
18#include <QIcon>
19#include <QJsonObject>
20#include <QJsonDocument>
21
22#include "account.h"
23#include "accountstate.h"
24#include "accountmanager.h"
25#include "folderman.h"
26#include "accessmanager.h"
27#include "activityitemdelegate.h"
28
29#include "activitydata.h"
30#include "activitylistmodel.h"
31
32namespace OCC {
33
34Q_LOGGING_CATEGORY(lcActivity, "gui.activity", QtInfoMsg)
35
36ActivityListModel::ActivityListModel(QWidget *parent)
37 : QAbstractListModel(parent)
38{
39}
40
41QVariant ActivityListModel::data(const QModelIndex &index, int role) const
42{
43 Activity a;
44
45 if (!index.isValid())
46 return QVariant();
47
48 a = _finalList.at(index.row());
49 AccountStatePtr ast = AccountManager::instance()->account(a._accName);
50 if (!ast)
51 return QVariant();
52 QStringList list;
53
54 switch (role) {
55 case ActivityItemDelegate::PathRole:
56 list = FolderMan::instance()->findFileInLocalFolders(a._file, ast->account());
57 if (list.count() > 0) {
58 return QVariant(list.at(0));
59 }
60 // File does not exist anymore? Let's try to open its path
61 list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(a._file).path(), ast->account());
62 if (list.count() > 0) {
63 return QVariant(list.at(0));
64 }
65 return QVariant();
66 break;
67 case ActivityItemDelegate::ActionIconRole:
68 return QVariant(); // FIXME once the action can be quantified, display on Icon
69 break;
70 case ActivityItemDelegate::UserIconRole:
71 return QIcon(QLatin1String(":/client/resources/account.png"));
72 break;
73 case Qt::ToolTipRole:
74 case ActivityItemDelegate::ActionTextRole:
75 return a._subject;
76 break;
77 case ActivityItemDelegate::LinkRole:
78 return a._link;
79 break;
80 case ActivityItemDelegate::AccountRole:
81 return a._accName;
82 break;
83 case ActivityItemDelegate::PointInTimeRole:
84 return Utility::timeAgoInWords(a._dateTime);
85 break;
86 case ActivityItemDelegate::AccountConnectedRole:
87 return (ast && ast->isConnected());
88 break;
89 default:
90 return QVariant();
91 }
92 return QVariant();
93}
94
95int ActivityListModel::rowCount(const QModelIndex &) const
96{
97 return _finalList.count();
98}
99
100// current strategy: Fetch 100 items per Account
101// ATTENTION: This method is const and thus it is not possible to modify
102// the _activityLists hash or so. Doesn't make it easier...
103bool ActivityListModel::canFetchMore(const QModelIndex &) const
104{
105 if (_activityLists.count() == 0)
106 return true;
107
108 for (auto i = _activityLists.begin(); i != _activityLists.end(); ++i) {
109 AccountState *ast = i.key();
110 if (ast && ast->isConnected()) {
111 ActivityList activities = i.value();
112 if (activities.count() == 0 && !_currentlyFetching.contains(ast)) {
113 return true;
114 }
115 }
116 }
117
118 return false;
119}
120
121void ActivityListModel::startFetchJob(AccountState *s)
122{
123 if (!s->isConnected()) {
124 return;
125 }
126 JsonApiJob *job = new JsonApiJob(s->account(), QLatin1String("ocs/v1.php/cloud/activity"), this);
127 QObject::connect(job, &JsonApiJob::jsonReceived,
128 this, &ActivityListModel::slotActivitiesReceived);
129 job->setProperty("AccountStatePtr", QVariant::fromValue<QPointer<AccountState>>(s));
130
131 QUrlQuery params;
132 params.addQueryItem(QLatin1String("page"), QLatin1String("0"));
133 params.addQueryItem(QLatin1String("pagesize"), QLatin1String("100"));
134 job->addQueryParams(params);
135
136 _currentlyFetching.insert(s);
137 qCInfo(lcActivity) << "Start fetching activities for " << s->account()->displayName();
138 job->start();
139}
140
141void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int statusCode)
142{
143 auto activities = json.object().value("ocs").toObject().value("data").toArray();
144
145 ActivityList list;
146 auto ast = qvariant_cast<QPointer<AccountState>>(sender()->property("AccountStatePtr"));
147 if (!ast)
148 return;
149
150 _currentlyFetching.remove(ast);
151
152 foreach (auto activ, activities) {
153 auto json = activ.toObject();
154
155 Activity a;
156 a._type = Activity::ActivityType;
157 a._accName = ast->account()->displayName();
158 a._id = json.value("id").toInt();
159 a._subject = json.value("subject").toString();
160 a._message = json.value("message").toString();
161 a._file = json.value("file").toString();
162 a._link = QUrl(json.value("link").toString());
163 a._dateTime = QDateTime::fromString(json.value("date").toString(), Qt::ISODate);
164 list.append(a);
165 }
166
167 _activityLists[ast] = list;
168
169 emit activityJobStatusCode(ast, statusCode);
170
171 combineActivityLists();
172}
173
174
175void ActivityListModel::combineActivityLists()
176{
177 ActivityList resultList;
178
179 foreach (ActivityList list, _activityLists.values()) {
180 resultList.append(list);
181 }
182
183 std::sort(resultList.begin(), resultList.end());
184
185 beginResetModel();
186 _finalList.clear();
187 endResetModel();
188
189 beginInsertRows(QModelIndex(), 0, resultList.count());
190 _finalList = resultList;
191 endInsertRows();
192}
193
194void ActivityListModel::fetchMore(const QModelIndex &)
195{
196 QList<AccountStatePtr> accounts = AccountManager::instance()->accounts();
197
198 foreach (const AccountStatePtr &asp, accounts) {
199 if (!_activityLists.contains(asp.data()) && asp->isConnected()) {
200 _activityLists[asp.data()] = ActivityList();
201 startFetchJob(asp.data());
202 }
203 }
204}
205
206void ActivityListModel::slotRefreshActivity(AccountState *ast)
207{
208 if (ast && _activityLists.contains(ast)) {
209 _activityLists.remove(ast);
210 }
211 startFetchJob(ast);
212}
213
214void ActivityListModel::slotRemoveAccount(AccountState *ast)
215{
216 if (_activityLists.contains(ast)) {
217 int i = 0;
218 const QString accountToRemove = ast->account()->displayName();
219
220 QMutableListIterator<Activity> it(_finalList);
221
222 while (it.hasNext()) {
223 Activity activity = it.next();
224 if (activity._accName == accountToRemove) {
225 beginRemoveRows(QModelIndex(), i, i + 1);
226 it.remove();
227 endRemoveRows();
228 }
229 }
230 _activityLists.remove(ast);
231 _currentlyFetching.remove(ast);
232 }
233}
234}
235