nextcloud-desktop/src/gui/activitywidget.cpp
Klaas Freitag 9c5b9f932b Activities: Hide if non of the accounts has the app enabled.
If the ownCloud server does not have the activity app enabled,
it returns 999 as status code. If all the configured accounts
do that, this code hides the entire tab with the server
activities.

This is supposed to fix #4533
2016-03-08 18:01:42 +01:00

515 lines
15 KiB
C++

/*
* 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 <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QtWidgets>
#endif
#include "activitywidget.h"
#include "configfile.h"
#include "syncresult.h"
#include "logger.h"
#include "utility.h"
#include "theme.h"
#include "folderman.h"
#include "syncfileitem.h"
#include "folder.h"
#include "openfilemanager.h"
#include "owncloudpropagator.h"
#include "account.h"
#include "accountstate.h"
#include "accountmanager.h"
#include "activityitemdelegate.h"
#include "protocolwidget.h"
#include "QProgressIndicator.h"
#include "ui_activitywidget.h"
#include <climits>
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();
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);
_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->setupUi(this);
// Adjust copyToClipboard() when making changes here!
#if defined(Q_OS_MAC)
_ui->_activityList->setMinimumWidth(400);
#endif
_model = new ActivityListModel(this);
ActivityItemDelegate *delegate = new ActivityItemDelegate;
delegate->setParent(this);
_ui->_activityList->setItemDelegate(delegate);
_ui->_activityList->setAlternatingRowColors(true);
_ui->_activityList->setModel(_model);
showLabels();
connect(_model, SIGNAL(activityJobStatusCode(AccountState*,int)),
this, SLOT(slotAccountActivityStatus(AccountState*,int)));
_copyBtn = _ui->_dialogButtonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
_copyBtn->setToolTip( tr("Copy the activity list to the clipboard."));
connect(_copyBtn, SIGNAL(clicked()), SIGNAL(copyToClipboard()));
connect(_model, SIGNAL(rowsInserted(QModelIndex,int,int)), SIGNAL(rowsInserted()));
connect( _ui->_activityList, SIGNAL(activated(QModelIndex)), this,
SLOT(slotOpenFile(QModelIndex)));
}
ActivityWidget::~ActivityWidget()
{
delete _ui;
}
void ActivityWidget::slotRefresh(AccountState *ptr)
{
_model->slotRefreshActivity(ptr);
}
void ActivityWidget::slotRemoveAccount( AccountState *ptr )
{
_model->slotRemoveAccount(ptr);
}
void ActivityWidget::showLabels()
{
QString t = tr("Server Activities");
_ui->_headerLabel->setTextFormat(Qt::RichText);
_ui->_headerLabel->setText(t);
t.clear();
QSetIterator<QString> i(_accountsWithoutActivities);
while (i.hasNext() ) {
t.append( tr("<br/>Account %1 does not have activities enabled.").arg(i.next()));
}
_ui->_bottomLabel->setTextFormat(Qt::RichText);
_ui->_bottomLabel->setText(t);
}
void ActivityWidget::slotAccountActivityStatus(AccountState *ast, int statusCode)
{
if( !(ast && ast->account()) ) {
return;
}
if( statusCode == 999 ) {
_accountsWithoutActivities.insert(ast->account()->displayName());
} else {
_accountsWithoutActivities.remove(ast->account()->displayName());
}
int accountCount = AccountManager::instance()->accounts().count();
emit hideAcitivityTab(_accountsWithoutActivities.count() == accountCount);
showLabels();
}
// FIXME: Reused from protocol widget. Move over to utilities.
QString ActivityWidget::timeString(QDateTime dt, QLocale::FormatType format) const
{
const QLocale loc = QLocale::system();
QString dtFormat = loc.dateTimeFormat(format);
static const QRegExp re("(HH|H|hh|h):mm(?!:s)");
dtFormat.replace(re, "\\1:mm:ss");
return loc.toString(dt, dtFormat);
}
void ActivityWidget::storeActivityList( QTextStream& ts )
{
ActivityList activities = _model->activityList();
foreach( Activity activity, activities ) {
ts << right
// account name
<< qSetFieldWidth(30)
<< activity._accName
// separator
<< qSetFieldWidth(0) << ","
// date and time
<< qSetFieldWidth(34)
<< activity._dateTime.toString()
// separator
<< qSetFieldWidth(0) << ","
// file
<< qSetFieldWidth(30)
<< activity._file
// separator
<< qSetFieldWidth(0) << ","
// subject
<< qSetFieldWidth(100)
<< activity._subject
// separator
<< qSetFieldWidth(0) << ","
// message (mostly empty)
<< qSetFieldWidth(55)
<< activity._message
//
<< qSetFieldWidth(0)
<< endl;
}
}
void ActivityWidget::slotOpenFile(QModelIndex indx)
{
qDebug() << indx.isValid() << indx.data(ActivityItemDelegate::PathRole).toString() << QFile::exists(indx.data(ActivityItemDelegate::PathRole).toString());
if( indx.isValid() ) {
QString fullPath = indx.data(ActivityItemDelegate::PathRole).toString();
if (QFile::exists(fullPath)) {
showInFileManager(fullPath);
}
}
}
/* ==================================================================== */
ActivitySettings::ActivitySettings(QWidget *parent)
:QWidget(parent)
{
QHBoxLayout *hbox = new QHBoxLayout(this);
setLayout(hbox);
// create a tab widget for the three activity views
_tab = new QTabWidget(this);
hbox->addWidget(_tab);
_activityWidget = new ActivityWidget(this);
_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)));
_protocolWidget = new ProtocolWidget(this);
_tab->insertTab(1, _protocolWidget, Theme::instance()->syncStateIcon(SyncResult::Success), tr("Sync Protocol"));
connect(_protocolWidget, SIGNAL(copyToClipboard()), this, SLOT(slotCopyToClipboard()));
// Add the not-synced list into the tab
QWidget *w = new QWidget;
QVBoxLayout *vbox2 = new QVBoxLayout(w);
vbox2->addWidget(new QLabel(tr("List of ignored or erroneous files"), this));
vbox2->addWidget(_protocolWidget->issueWidget());
QDialogButtonBox *dlgButtonBox = new QDialogButtonBox(this);
vbox2->addWidget(dlgButtonBox);
QPushButton *_copyBtn = dlgButtonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
_copyBtn->setToolTip( tr("Copy the activity list to the clipboard."));
_copyBtn->setEnabled(true);
connect(_copyBtn, SIGNAL(clicked()), this, SLOT(slotCopyToClipboard()));
w->setLayout(vbox2);
_tab->insertTab(2, w, Theme::instance()->syncStateIcon(SyncResult::Problem), tr("Not Synced"));
// Add a progress indicator to spin if the acitivity list is updated.
_progressIndicator = new QProgressIndicator(this);
_tab->setCornerWidget(_progressIndicator);
// connect a model signal to stop the animation.
connect(_activityWidget, SIGNAL(rowsInserted()), _progressIndicator, SLOT(stopAnimation()));
}
void ActivitySettings::setActivityTabHidden(bool hidden)
{
if( hidden && _activityTabId > -1 ) {
_tab->removeTab(_activityTabId);
_activityTabId = -1;
}
if( !hidden && _activityTabId == -1 ) {
_activityTabId = _tab->insertTab(0, _activityWidget, Theme::instance()->applicationIcon(), tr("Server Activity"));
}
}
void ActivitySettings::slotCopyToClipboard()
{
QString text;
QTextStream ts(&text);
int idx = _tab->currentIndex();
QString message;
if( idx == 0 ) {
// the activity widget
_activityWidget->storeActivityList(ts);
message = tr("The server activity list has been copied to the clipboard.");
} else if(idx == 1 ) {
// the protocol widget
_protocolWidget->storeSyncActivity(ts);
message = tr("The sync activity list has been copied to the clipboard.");
} else if(idx == 2 ) {
// issues Widget
message = tr("The list of unsynched items has been copied to the clipboard.");
_protocolWidget->storeSyncIssues(ts);
}
QApplication::clipboard()->setText(text);
emit guiLog(tr("Copied to clipboard"), message);
}
void ActivitySettings::slotRemoveAccount( AccountState *ptr )
{
_activityWidget->slotRemoveAccount(ptr);
}
void ActivitySettings::slotRefresh( AccountState* ptr )
{
if( ptr && ptr->isConnected() && isVisible()) {
qDebug() << "Refreshing Activity list for " << ptr->account()->displayName();
_progressIndicator->startAnimation();
_activityWidget->slotRefresh(ptr);
}
}
bool ActivitySettings::event(QEvent* e)
{
if (e->type() == QEvent::Show) {
AccountManager *am = AccountManager::instance();
foreach (AccountStatePtr a, am->accounts()) {
slotRefresh(a.data());
}
}
return QWidget::event(e);
}
ActivitySettings::~ActivitySettings()
{
}
}