mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-21 20:45:51 +03:00
model: first step
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
This commit is contained in:
parent
8f61d539da
commit
280094b220
8 changed files with 389 additions and 0 deletions
|
@ -186,6 +186,8 @@ set(client_SRCS
|
|||
userstatusselectormodel.cpp
|
||||
emojimodel.h
|
||||
emojimodel.cpp
|
||||
syncconflictsmodel.h
|
||||
syncconflictsmodel.cpp
|
||||
fileactivitylistmodel.h
|
||||
fileactivitylistmodel.cpp
|
||||
filedetails/filedetails.h
|
||||
|
|
|
@ -110,6 +110,10 @@ Window {
|
|||
height: 1
|
||||
}
|
||||
|
||||
SyncConflictsModel {
|
||||
id: realModel
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
|
|
@ -30,6 +30,7 @@ class TestCfApiShellExtensionsIPC;
|
|||
class TestShareModel;
|
||||
class ShareTestHelper;
|
||||
class EndToEndTestHelper;
|
||||
class TestSyncConflictsModel;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
|
@ -391,6 +392,7 @@ private:
|
|||
explicit FolderMan(QObject *parent = nullptr);
|
||||
friend class OCC::Application;
|
||||
friend class ::TestFolderMan;
|
||||
friend class ::TestSyncConflictsModel;
|
||||
friend class ::TestCfApiShellExtensionsIPC;
|
||||
friend class ::ShareTestHelper;
|
||||
friend class ::EndToEndTestHelper;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "settingsdialog.h"
|
||||
#include "theme.h"
|
||||
#include "wheelhandler.h"
|
||||
#include "syncconflictsmodel.h"
|
||||
#include "filedetails/filedetails.h"
|
||||
#include "filedetails/shareemodel.h"
|
||||
#include "filedetails/sharemodel.h"
|
||||
|
@ -125,6 +126,7 @@ ownCloudGui::ownCloudGui(Application *parent)
|
|||
qmlRegisterType<ShareModel>("com.nextcloud.desktopclient", 1, 0, "ShareModel");
|
||||
qmlRegisterType<ShareeModel>("com.nextcloud.desktopclient", 1, 0, "ShareeModel");
|
||||
qmlRegisterType<SortedShareModel>("com.nextcloud.desktopclient", 1, 0, "SortedShareModel");
|
||||
qmlRegisterType<SyncConflictsModel>("com.nextcloud.desktopclient", 1, 0, "SyncConflictsModel");
|
||||
|
||||
qmlRegisterUncreatableType<UnifiedSearchResultsListModel>("com.nextcloud.desktopclient", 1, 0, "UnifiedSearchResultsListModel", "UnifiedSearchResultsListModel");
|
||||
qmlRegisterUncreatableType<UserStatus>("com.nextcloud.desktopclient", 1, 0, "UserStatus", "Access to Status enum");
|
||||
|
|
173
src/gui/syncconflictsmodel.cpp
Normal file
173
src/gui/syncconflictsmodel.cpp
Normal file
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright (C) 2023 by Matthieu Gallien <matthieu.gallien@nextcloud.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; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 "syncconflictsmodel.h"
|
||||
#include "folderman.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcSyncConflictsModel, "nextcloud.syncconflictsmodel", QtInfoMsg)
|
||||
|
||||
SyncConflictsModel::SyncConflictsModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
int SyncConflictsModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mData.size();
|
||||
}
|
||||
|
||||
QVariant SyncConflictsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
auto result = QVariant{};
|
||||
|
||||
Q_ASSERT(checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid));
|
||||
|
||||
if (index.parent().isValid()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (role >= static_cast<int>(SyncConflictRoles::ExistingFileName) && role <= static_cast<int>(SyncConflictRoles::ConflictPreviewUrl)) {
|
||||
auto convertedRole = static_cast<SyncConflictRoles>(role);
|
||||
|
||||
switch (convertedRole) {
|
||||
case SyncConflictRoles::ExistingFileName:
|
||||
result = mConflictData[index.row()].mExistingFileName;
|
||||
break;
|
||||
case SyncConflictRoles::ExistingSize:
|
||||
result = mConflictData[index.row()].mExistingSize;
|
||||
break;
|
||||
case SyncConflictRoles::ConflictSize:
|
||||
result = mConflictData[index.row()].mConflictSize;
|
||||
break;
|
||||
case SyncConflictRoles::ExistingDate:
|
||||
result = mConflictData[index.row()].mExistingDate;
|
||||
break;
|
||||
case SyncConflictRoles::ConflictDate:
|
||||
result = mConflictData[index.row()].mConflictDate;
|
||||
break;
|
||||
case SyncConflictRoles::ExistingSelected:
|
||||
result = mConflictData[index.row()].mExistingSelected;
|
||||
break;
|
||||
case SyncConflictRoles::ConflictSelected:
|
||||
result = mConflictData[index.row()].mConflictSelected;
|
||||
break;
|
||||
case SyncConflictRoles::ExistingPreviewUrl:
|
||||
result = mConflictData[index.row()].mExistingPreviewUrl;
|
||||
break;
|
||||
case SyncConflictRoles::ConflictPreviewUrl:
|
||||
result = mConflictData[index.row()].mConflictPreviewUrl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> SyncConflictsModel::roleNames() const
|
||||
{
|
||||
auto result = QAbstractListModel::roleNames();
|
||||
|
||||
result[static_cast<int>(SyncConflictRoles::ExistingFileName)] = "existingFileName";
|
||||
result[static_cast<int>(SyncConflictRoles::ExistingSize)] = "existingSize";
|
||||
result[static_cast<int>(SyncConflictRoles::ConflictSize)] = "conflictSize";
|
||||
result[static_cast<int>(SyncConflictRoles::ExistingDate)] = "existingDate";
|
||||
result[static_cast<int>(SyncConflictRoles::ConflictDate)] = "conflictDate";
|
||||
result[static_cast<int>(SyncConflictRoles::ExistingSelected)] = "existingSelected";
|
||||
result[static_cast<int>(SyncConflictRoles::ConflictSelected)] = "conflictSelected";
|
||||
result[static_cast<int>(SyncConflictRoles::ExistingPreviewUrl)] = "existingPreviewUrl";
|
||||
result[static_cast<int>(SyncConflictRoles::ConflictPreviewUrl)] = "conflictPreviewUrl";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ActivityList SyncConflictsModel::conflictActivities() const
|
||||
{
|
||||
return mData;
|
||||
}
|
||||
|
||||
void SyncConflictsModel::setConflictActivities(ActivityList conflicts)
|
||||
{
|
||||
if (mData == conflicts) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
|
||||
mData = conflicts;
|
||||
emit conflictActivitiesChanged();
|
||||
|
||||
updateConflictsData();
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void SyncConflictsModel::updateConflictsData()
|
||||
{
|
||||
mConflictData.clear();
|
||||
mConflictData.reserve(mData.size());
|
||||
|
||||
for (const auto &oneConflict : qAsConst(mData)) {
|
||||
if (!FolderMan::instance()) {
|
||||
qCWarning(lcSyncConflictsModel) << "no FolderMan instance";
|
||||
mConflictData.push_back({});
|
||||
continue;
|
||||
}
|
||||
const auto folder = FolderMan::instance()->folder(oneConflict._folder);
|
||||
if (!folder) {
|
||||
qCWarning(lcSyncConflictsModel) << "no Folder instance for" << oneConflict._folder;
|
||||
mConflictData.push_back({});
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto conflictedRelativePath = oneConflict._file;
|
||||
const auto dbRecord = folder->journalDb();
|
||||
const auto baseRelativePath = dbRecord ? dbRecord->conflictFileBaseName(conflictedRelativePath.toUtf8()) : QString{};
|
||||
|
||||
const auto dir = QDir(folder->path());
|
||||
const auto conflictedPath = dir.filePath(conflictedRelativePath);
|
||||
const auto basePath = dir.filePath(baseRelativePath);
|
||||
|
||||
qCInfo(lcSyncConflictsModel()) << "conflictedPath" << conflictedPath << "basePath" << basePath;
|
||||
|
||||
const auto existingFileInfo = QFileInfo(basePath);
|
||||
const auto conflictFileInfo = QFileInfo(conflictedPath);
|
||||
|
||||
const auto existingMimeType = mMimeDb.mimeTypeForFile(existingFileInfo.fileName());
|
||||
const auto conflictMimeType = mMimeDb.mimeTypeForFile(conflictFileInfo.fileName());
|
||||
|
||||
auto newConflictData = ConflictInfo{
|
||||
existingFileInfo.fileName(),
|
||||
mLocale.formattedDataSize(existingFileInfo.size()),
|
||||
mLocale.formattedDataSize(conflictFileInfo.size()),
|
||||
existingFileInfo.lastModified().toString(),
|
||||
conflictFileInfo.lastModified().toString(),
|
||||
QIcon::hasThemeIcon(existingMimeType.iconName()) ? QUrl{} : QUrl{":/qt-project.org/styles/commonstyle/images/file-128.png"},
|
||||
QIcon::hasThemeIcon(conflictMimeType.iconName()) ? QUrl{} : QUrl{":/qt-project.org/styles/commonstyle/images/file-128.png"},
|
||||
false,
|
||||
false,
|
||||
};
|
||||
|
||||
mConflictData.push_back(std::move(newConflictData));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
89
src/gui/syncconflictsmodel.h
Normal file
89
src/gui/syncconflictsmodel.h
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (C) 2023 by Matthieu Gallien <matthieu.gallien@nextcloud.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; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 SYNCCONFLICTSMODEL_H
|
||||
#define SYNCCONFLICTSMODEL_H
|
||||
|
||||
#include "tray/activitydata.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QMimeDatabase>
|
||||
#include <QLocale>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class SyncConflictsModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(OCC::ActivityList conflictActivities READ conflictActivities WRITE setConflictActivities NOTIFY conflictActivitiesChanged)
|
||||
|
||||
struct ConflictInfo {
|
||||
QString mExistingFileName;
|
||||
QString mExistingSize;
|
||||
QString mConflictSize;
|
||||
QString mExistingDate;
|
||||
QString mConflictDate;
|
||||
QUrl mExistingPreviewUrl;
|
||||
QUrl mConflictPreviewUrl;
|
||||
bool mExistingSelected = false;
|
||||
bool mConflictSelected = false;
|
||||
};
|
||||
|
||||
public:
|
||||
enum class SyncConflictRoles : int {
|
||||
ExistingFileName = Qt::UserRole,
|
||||
ExistingSize,
|
||||
ConflictSize,
|
||||
ExistingDate,
|
||||
ConflictDate,
|
||||
ExistingSelected,
|
||||
ConflictSelected,
|
||||
ExistingPreviewUrl,
|
||||
ConflictPreviewUrl,
|
||||
};
|
||||
|
||||
Q_ENUM(SyncConflictRoles)
|
||||
|
||||
explicit SyncConflictsModel(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
[[nodiscard]] QHash<int,QByteArray> roleNames() const override;
|
||||
|
||||
[[nodiscard]] OCC::ActivityList conflictActivities() const;
|
||||
|
||||
public slots:
|
||||
void setConflictActivities(OCC::ActivityList conflicts);
|
||||
|
||||
signals:
|
||||
void conflictActivitiesChanged();
|
||||
|
||||
private:
|
||||
void updateConflictsData();
|
||||
|
||||
OCC::ActivityList mData;
|
||||
|
||||
QVector<ConflictInfo> mConflictData;
|
||||
|
||||
QMimeDatabase mMimeDb;
|
||||
|
||||
QLocale mLocale;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SYNCCONFLICTSMODEL_H
|
|
@ -71,6 +71,7 @@ nextcloud_add_test(ShareeModel)
|
|||
nextcloud_add_test(SortedShareModel)
|
||||
nextcloud_add_test(SecureFileDrop)
|
||||
nextcloud_add_test(FileTagModel)
|
||||
nextcloud_add_test(SyncConflictsModel)
|
||||
|
||||
target_link_libraries(SecureFileDropTest PRIVATE Nextcloud::sync)
|
||||
configure_file(fake2eelocksucceeded.json "${PROJECT_BINARY_DIR}/bin/fake2eelocksucceeded.json" COPYONLY)
|
||||
|
|
116
test/testsyncconflictsmodel.cpp
Normal file
116
test/testsyncconflictsmodel.cpp
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (C) by Claudio Cambra <claudio.cambra@nextcloud.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; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 "gui/syncconflictsmodel.h"
|
||||
#include "folderman.h"
|
||||
#include "accountstate.h"
|
||||
#include "configfile.h"
|
||||
#include "syncfileitem.h"
|
||||
|
||||
#include "syncenginetestutils.h"
|
||||
#include "testhelper.h"
|
||||
|
||||
#include <QTest>
|
||||
#include <QAbstractItemModelTester>
|
||||
#include <QSignalSpy>
|
||||
|
||||
namespace {
|
||||
|
||||
QStringList findConflicts(const FileInfo &dir)
|
||||
{
|
||||
QStringList conflicts;
|
||||
for (const auto &item : dir.children) {
|
||||
if (item.name.contains("(conflicted copy")) {
|
||||
conflicts.append(item.path());
|
||||
}
|
||||
}
|
||||
return conflicts;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using namespace OCC;
|
||||
|
||||
class TestSyncConflictsModel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
|
||||
private slots:
|
||||
void initTestCase()
|
||||
{
|
||||
}
|
||||
|
||||
void testSuccessfulFetchShares()
|
||||
{
|
||||
auto dir = QTemporaryDir {};
|
||||
ConfigFile::setConfDir(dir.path()); // we don't want to pollute the user's config file
|
||||
|
||||
FolderMan fm;
|
||||
|
||||
auto account = Account::create();
|
||||
auto url = QUrl{"http://example.de"};
|
||||
auto cred = new HttpCredentialsTest("testuser", "secret");
|
||||
account->setCredentials(cred);
|
||||
account->setUrl(url);
|
||||
url.setUserName(cred->user());
|
||||
|
||||
auto newAccountState{AccountStatePtr{ new AccountState{account}}};
|
||||
auto folderman = FolderMan::instance();
|
||||
QCOMPARE(folderman, &fm);
|
||||
|
||||
auto fakeFolder = FakeFolder{FileInfo::A12_B12_C12_S12()};
|
||||
|
||||
QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(fakeFolder.localPath())));
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
|
||||
fakeFolder.localModifier().appendByte("A/a2");
|
||||
fakeFolder.remoteModifier().appendByte("A/a2");
|
||||
fakeFolder.remoteModifier().appendByte("A/a2");
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
|
||||
OCC::ActivityList allConflicts;
|
||||
|
||||
const auto conflicts = findConflicts(fakeFolder.currentLocalState().children["A"]);
|
||||
for (const auto &conflict : conflicts) {
|
||||
auto conflictActivity = OCC::Activity{};
|
||||
conflictActivity._file = fakeFolder.localPath() + conflict;
|
||||
conflictActivity._folder = fakeFolder.localPath();
|
||||
allConflicts.push_back(std::move(conflictActivity));
|
||||
}
|
||||
|
||||
SyncConflictsModel model;
|
||||
QAbstractItemModelTester modelTester(&model);
|
||||
|
||||
model.setConflictActivities(allConflicts);
|
||||
|
||||
QCOMPARE(model.rowCount(), 1);
|
||||
QCOMPARE(model.data(model.index(0), static_cast<int>(SyncConflictsModel::SyncConflictRoles::ExistingFileName)), QString{"a2"});
|
||||
QCOMPARE(model.data(model.index(0), static_cast<int>(SyncConflictsModel::SyncConflictRoles::ExistingSize)), QString{"6 bytes"});
|
||||
QCOMPARE(model.data(model.index(0), static_cast<int>(SyncConflictsModel::SyncConflictRoles::ConflictSize)), QString{"5 bytes"});
|
||||
QVERIFY(!model.data(model.index(0), static_cast<int>(SyncConflictsModel::SyncConflictRoles::ExistingDate)).toString().isEmpty());
|
||||
QVERIFY(!model.data(model.index(0), static_cast<int>(SyncConflictsModel::SyncConflictRoles::ConflictDate)).toString().isEmpty());
|
||||
QCOMPARE(model.data(model.index(0), static_cast<int>(SyncConflictsModel::SyncConflictRoles::ExistingPreviewUrl)), QUrl{":/qt-project.org/styles/commonstyle/images/file-128.png"});
|
||||
QCOMPARE(model.data(model.index(0), static_cast<int>(SyncConflictsModel::SyncConflictRoles::ConflictPreviewUrl)), QUrl{":/qt-project.org/styles/commonstyle/images/file-128.png"});
|
||||
QCOMPARE(model.data(model.index(0), static_cast<int>(SyncConflictsModel::SyncConflictRoles::ExistingSelected)), false);
|
||||
QCOMPARE(model.data(model.index(0), static_cast<int>(SyncConflictsModel::SyncConflictRoles::ConflictSelected)), false);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(TestSyncConflictsModel)
|
||||
#include "testsyncconflictsmodel.moc"
|
Loading…
Reference in a new issue