SelectiveSync: Show in-progress label #3524

We now show 'Fetching data...' after a second.

This also increased the timeout to 60s, making the error
condition much less likely.
This commit is contained in:
Christian Kamm 2015-10-13 14:10:52 +02:00
parent a752eadd0f
commit 05eee16959
2 changed files with 124 additions and 44 deletions

View file

@ -90,7 +90,7 @@ Qt::ItemFlags FolderStatusModel::flags ( const QModelIndex &index ) const
} }
return Qt::ItemIsEnabled | ret; return Qt::ItemIsEnabled | ret;
} }
case ErrorLabel: case FetchLabel:
return Qt::ItemIsEnabled return Qt::ItemIsEnabled
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
| Qt::ItemNeverHasChildren | Qt::ItemNeverHasChildren
@ -151,11 +151,20 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const
} }
} }
return QVariant(); return QVariant();
case ErrorLabel: case FetchLabel:
{
const auto x = static_cast<SubFolderInfo *>(index.internalPointer());
switch(role) { switch(role) {
case Qt::DisplayRole: return tr("Error while loading the list of folders from the server."); case Qt::DisplayRole:
if (x->_hasError) {
return tr("Error while loading the list of folders from the server.");
} else {
return tr("Fetching folder list from server...");
}
break;
default: return QVariant(); default: return QVariant();
} }
}
case RootFolder: case RootFolder:
break; break;
} }
@ -302,7 +311,7 @@ int FolderStatusModel::rowCount(const QModelIndex& parent) const
auto info = infoForIndex(parent); auto info = infoForIndex(parent);
if (!info) if (!info)
return 0; return 0;
if (info->_hasError) if (info->hasLabel())
return 1; return 1;
return info->_subs.count(); return info->_subs.count();
} }
@ -310,7 +319,11 @@ int FolderStatusModel::rowCount(const QModelIndex& parent) const
FolderStatusModel::ItemType FolderStatusModel::classify(const QModelIndex& index) const FolderStatusModel::ItemType FolderStatusModel::classify(const QModelIndex& index) const
{ {
if (auto sub = static_cast<SubFolderInfo*>(index.internalPointer())) { if (auto sub = static_cast<SubFolderInfo*>(index.internalPointer())) {
return sub->_hasError ? ErrorLabel : SubFolder; if (sub->hasLabel()) {
return FetchLabel;
} else {
return SubFolder;
}
} }
if (index.row() < _folders.count()) { if (index.row() < _folders.count()) {
return RootFolder; return RootFolder;
@ -323,8 +336,7 @@ FolderStatusModel::SubFolderInfo* FolderStatusModel::infoForIndex(const QModelIn
if (!index.isValid()) if (!index.isValid())
return 0; return 0;
if (auto parentInfo = static_cast<SubFolderInfo*>(index.internalPointer())) { if (auto parentInfo = static_cast<SubFolderInfo*>(index.internalPointer())) {
if (parentInfo->_hasError) { if (parentInfo->hasLabel()) {
// Error label
return 0; return 0;
} }
return &parentInfo->_subs[index.row()]; return &parentInfo->_subs[index.row()];
@ -384,20 +396,21 @@ QModelIndex FolderStatusModel::index(int row, int column, const QModelIndex& par
} }
switch(classify(parent)) { switch(classify(parent)) {
case AddButton: case AddButton:
case ErrorLabel: case FetchLabel:
return QModelIndex(); return QModelIndex();
case RootFolder: case RootFolder:
if (_folders.count() <= parent.row()) if (_folders.count() <= parent.row())
return QModelIndex(); // should not happen return QModelIndex(); // should not happen
return createIndex(row, column, const_cast<SubFolderInfo *>(&_folders[parent.row()])); return createIndex(row, column, const_cast<SubFolderInfo *>(&_folders[parent.row()]));
case SubFolder: { case SubFolder: {
auto info = static_cast<SubFolderInfo*>(parent.internalPointer()); auto pinfo = static_cast<SubFolderInfo*>(parent.internalPointer());
if (info->_subs.count() <= parent.row()) if (pinfo->_subs.count() <= parent.row())
return QModelIndex(); // should not happen return QModelIndex(); // should not happen
if (!info->_subs.at(parent.row())._hasError auto & info = pinfo->_subs[parent.row()];
&& info->_subs.at(parent.row())._subs.count() <= row) if (!info.hasLabel()
&& info._subs.count() <= row)
return QModelIndex(); // should not happen return QModelIndex(); // should not happen
return createIndex(row, column, &info->_subs[parent.row()]); return createIndex(row, column, &info);
} }
} }
return QModelIndex(); return QModelIndex();
@ -413,7 +426,7 @@ QModelIndex FolderStatusModel::parent(const QModelIndex& child) const
case AddButton: case AddButton:
return QModelIndex(); return QModelIndex();
case SubFolder: case SubFolder:
case ErrorLabel: case FetchLabel:
break; break;
} }
auto pathIdx = static_cast<SubFolderInfo*>(child.internalPointer())->_pathIdx; auto pathIdx = static_cast<SubFolderInfo*>(child.internalPointer())->_pathIdx;
@ -467,7 +480,9 @@ void FolderStatusModel::fetchMore(const QModelIndex& parent)
if (!info || info->_fetched || info->_fetching) if (!info || info->_fetched || info->_fetching)
return; return;
info->_hasError = false;
info->_fetching = true; info->_fetching = true;
info->_fetchingLabel = false;
QString path = info->_folder->remotePath(); QString path = info->_folder->remotePath();
if (info->_path != QLatin1String("/")) { if (info->_path != QLatin1String("/")) {
if (!path.endsWith(QLatin1Char('/'))) { if (!path.endsWith(QLatin1Char('/'))) {
@ -477,13 +492,18 @@ void FolderStatusModel::fetchMore(const QModelIndex& parent)
} }
LsColJob *job = new LsColJob(_accountState->account(), path, this); LsColJob *job = new LsColJob(_accountState->account(), path, this);
job->setProperties(QList<QByteArray>() << "resourcetype" << "quota-used-bytes"); job->setProperties(QList<QByteArray>() << "resourcetype" << "quota-used-bytes");
job->setTimeout(5 * 1000); job->setTimeout(60 * 1000);
connect(job, SIGNAL(directoryListingSubfolders(QStringList)), connect(job, SIGNAL(directoryListingSubfolders(QStringList)),
SLOT(slotUpdateDirectories(QStringList))); SLOT(slotUpdateDirectories(QStringList)));
connect(job, SIGNAL(finishedWithError(QNetworkReply*)), connect(job, SIGNAL(finishedWithError(QNetworkReply*)),
this, SLOT(slotLscolFinishedWithError(QNetworkReply*))); this, SLOT(slotLscolFinishedWithError(QNetworkReply*)));
job->start(); job->start();
job->setProperty(propertyParentIndexC , QVariant::fromValue<QPersistentModelIndex>(parent)); QPersistentModelIndex persistentIndex(parent);
job->setProperty(propertyParentIndexC , QVariant::fromValue(persistentIndex));
// Show 'fetching data...' hint after a while.
_fetchingItems[persistentIndex].start();
QTimer::singleShot(1000, this, SLOT(slotShowFetchProgress()));
} }
void FolderStatusModel::slotUpdateDirectories(const QStringList &list_) void FolderStatusModel::slotUpdateDirectories(const QStringList &list_)
@ -496,12 +516,16 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list_)
return; return;
} }
if (parentInfo->_hasError) { if (parentInfo->hasLabel()) {
beginRemoveRows(idx, 0 ,0); beginRemoveRows(idx, 0 ,0);
parentInfo->_hasError = false; parentInfo->_hasError = false;
parentInfo->_fetchingLabel = false;
endRemoveRows(); endRemoveRows();
} }
parentInfo->_fetching = false;
parentInfo->_fetched = true;
auto list = list_; auto list = list_;
list.removeFirst(); // remove the parent item list.removeFirst(); // remove the parent item
@ -522,9 +546,6 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list_)
beginInsertRows(idx, 0, list.count() - 1); beginInsertRows(idx, 0, list.count() - 1);
parentInfo->_fetched = true;
parentInfo->_fetching = false;
QStringList selectiveSyncBlackList; QStringList selectiveSyncBlackList;
if (parentInfo->_checked == Qt::PartiallyChecked) { if (parentInfo->_checked == Qt::PartiallyChecked) {
selectiveSyncBlackList = parentInfo->_folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList); selectiveSyncBlackList = parentInfo->_folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList);
@ -589,14 +610,17 @@ void FolderStatusModel::slotLscolFinishedWithError(QNetworkReply* r)
} }
auto parentInfo = infoForIndex(idx); auto parentInfo = infoForIndex(idx);
if (parentInfo) { if (parentInfo) {
parentInfo->_fetching = false;
if (r->error() == QNetworkReply::ContentNotFoundError) { if (r->error() == QNetworkReply::ContentNotFoundError) {
parentInfo->_fetched = true; parentInfo->_fetched = true;
} else if (!parentInfo->_hasError) { } else {
beginInsertRows(idx, 0, 0); if (!parentInfo->hasLabel()) {
beginInsertRows(idx, 0, 0);
endInsertRows();
}
parentInfo->_hasError = true; parentInfo->_hasError = true;
endInsertRows();
} }
parentInfo->_fetching = false;
parentInfo->_fetchingLabel = false;
} }
} }
@ -900,13 +924,8 @@ void FolderStatusModel::slotFolderSyncStateChange(Folder *f)
if (i->_isDirectory && (i->_instruction == CSYNC_INSTRUCTION_NEW if (i->_isDirectory && (i->_instruction == CSYNC_INSTRUCTION_NEW
|| i->_instruction == CSYNC_INSTRUCTION_REMOVE)) { || i->_instruction == CSYNC_INSTRUCTION_REMOVE)) {
// There is a new or a removed folder. reset all data // There is a new or a removed folder. reset all data
_folders[folderIndex]._fetched = false; auto & info = _folders[folderIndex];
_folders[folderIndex]._fetching = false; info.resetSubs(this, index(folderIndex));
if (!_folders.at(folderIndex)._subs.isEmpty()) {
beginRemoveRows(index(folderIndex), 0, _folders.at(folderIndex)._subs.count() - 1);
_folders[folderIndex]._subs.clear();
endRemoveRows();
}
return; return;
} }
} }
@ -942,17 +961,53 @@ void FolderStatusModel::slotNewBigFolder()
} }
if (folderIndex < 0) { return; } if (folderIndex < 0) { return; }
_folders[folderIndex]._fetched = false; _folders[folderIndex].resetSubs(this, index(folderIndex));
_folders[folderIndex]._fetching = false;
if (!_folders.at(folderIndex)._subs.isEmpty()) {
beginRemoveRows(index(folderIndex), 0, _folders.at(folderIndex)._subs.count() - 1);
_folders[folderIndex]._subs.clear();
endRemoveRows();
}
emit suggestExpand(index(folderIndex)); emit suggestExpand(index(folderIndex));
emit dirtyChanged(); emit dirtyChanged();
} }
void FolderStatusModel::slotShowFetchProgress()
{
QMutableMapIterator<QPersistentModelIndex, QElapsedTimer> it(_fetchingItems);
while (it.hasNext()) {
it.next();
if (it.value().elapsed() > 800)
{
auto idx = it.key();
auto* info = infoForIndex(idx);
if (info && info->_fetching) {
if (!info->hasLabel()) {
beginInsertRows(idx, 0, 0);
endInsertRows();
}
info->_fetchingLabel = true;
}
it.remove();
}
}
}
bool FolderStatusModel::SubFolderInfo::hasLabel() const
{
return _hasError || _fetchingLabel;
}
void FolderStatusModel::SubFolderInfo::resetSubs(FolderStatusModel* model, QModelIndex index)
{
_fetched = false;
_fetching = false;
if (hasLabel()) {
model->beginRemoveRows(index, 0 ,0);
_fetchingLabel = false;
_hasError = false;
model->endRemoveRows();
} else if (!_subs.isEmpty()) {
model->beginRemoveRows(index, 0, _subs.count() - 1);
_subs.clear();
model->endRemoveRows();
}
}
} // namespace OCC } // namespace OCC

View file

@ -18,7 +18,7 @@
#include <accountfwd.h> #include <accountfwd.h>
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include <QVector> #include <QVector>
#include <QElapsedTimer>
class QNetworkReply; class QNetworkReply;
namespace OCC { namespace OCC {
@ -51,20 +51,30 @@ public:
struct SubFolderInfo { struct SubFolderInfo {
SubFolderInfo() SubFolderInfo()
: _folder(0), _size(0), _fetched(false), _fetching(false), _isUndecided(false), : _folder(0), _size(0), _fetched(false), _fetching(false),
_hasError(false), _checked(Qt::Checked) {} _hasError(false), _fetchingLabel(false), _isUndecided(false), _checked(Qt::Checked) {}
Folder *_folder; Folder *_folder;
QString _name; QString _name;
QString _path; QString _path;
QVector<int> _pathIdx; QVector<int> _pathIdx;
QVector<SubFolderInfo> _subs; QVector<SubFolderInfo> _subs;
qint64 _size; qint64 _size;
bool _fetched; // If we did the LSCOL for this folder already bool _fetched; // If we did the LSCOL for this folder already
bool _fetching; bool _fetching; // Whether a LSCOL job is currently running
bool _isUndecided; // undecided folders are the big folders that the user has not accepted yet
bool _hasError; // If the last fetching job ended in an error bool _hasError; // If the last fetching job ended in an error
bool _fetchingLabel; // Whether a 'fetching in progress' label is shown.
bool _isUndecided; // undecided folders are the big folders that the user has not accepted yet
Qt::CheckState _checked; Qt::CheckState _checked;
// Whether this has a FetchLabel subrow
bool hasLabel() const;
// Reset all subfolders and fetch status
void resetSubs(FolderStatusModel* model, QModelIndex index);
struct Progress { struct Progress {
Progress() : _warningCount(0), _overallPercent(0) {} Progress() : _warningCount(0), _overallPercent(0) {}
bool isNull() const bool isNull() const
@ -79,7 +89,7 @@ public:
QVector<SubFolderInfo> _folders; QVector<SubFolderInfo> _folders;
enum ItemType { RootFolder, SubFolder, AddButton, ErrorLabel }; enum ItemType { RootFolder, SubFolder, AddButton, FetchLabel };
ItemType classify(const QModelIndex &index) const; ItemType classify(const QModelIndex &index) const;
SubFolderInfo *infoForIndex(const QModelIndex &index) const; SubFolderInfo *infoForIndex(const QModelIndex &index) const;
@ -105,12 +115,25 @@ private slots:
void slotFolderScheduleQueueChanged(); void slotFolderScheduleQueueChanged();
void slotNewBigFolder(); void slotNewBigFolder();
/**
* "In progress" labels for fetching data from the server are only
* added after some time to avoid popping.
*/
void slotShowFetchProgress();
private: private:
QStringList createBlackList(OCC::FolderStatusModel::SubFolderInfo* root, QStringList createBlackList(OCC::FolderStatusModel::SubFolderInfo* root,
const QStringList& oldBlackList) const; const QStringList& oldBlackList) const;
const AccountState* _accountState; const AccountState* _accountState;
bool _dirty; // If the selective sync checkboxes were changed bool _dirty; // If the selective sync checkboxes were changed
/**
* Keeps track of items that are fetching data from the server.
*
* See slotShowPendingFetchProgress()
*/
QMap<QPersistentModelIndex, QElapsedTimer> _fetchingItems;
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
//the roles argument was added in Qt5 //the roles argument was added in Qt5
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>()) void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>())
@ -120,6 +143,8 @@ private:
signals: signals:
void dirtyChanged(); void dirtyChanged();
void suggestExpand(const QModelIndex &); // Tell the view that this item should be expanded because it has an undecided item void suggestExpand(const QModelIndex &); // Tell the view that this item should be expanded because it has an undecided item
friend class SubFolderInfo;
}; };
} // namespace OCC } // namespace OCC