Skip to content
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

[stable-3.13] Bugfix nextcloudcmd when syncing results in deleting all files #6857

Merged
merged 5 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/cmd/cmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

#include <iostream>
#include <random>
#include <qcoreapplication.h>

Check failure on line 19 in src/cmd/cmd.cpp

View workflow job for this annotation

GitHub Actions / build

src/cmd/cmd.cpp:19:10 [clang-diagnostic-error]

'qcoreapplication.h' file not found
#include <QStringList>
#include <QUrl>
#include <QFile>
Expand Down Expand Up @@ -529,10 +529,11 @@
selectiveSyncFixup(&db, selectiveSyncList);
}

SyncOptions opt;
opt.fillFromEnvironmentVariables();
opt.verifyChunkSizes();
SyncEngine engine(account, options.source_dir, opt, folder, &db);
SyncOptions syncOptions;

Check warning on line 532 in src/cmd/cmd.cpp

View workflow job for this annotation

GitHub Actions / build

src/cmd/cmd.cpp:532:17 [cppcoreguidelines-init-variables]

variable 'syncOptions' is not initialized
syncOptions.fillFromEnvironmentVariables();
syncOptions.verifyChunkSizes();
syncOptions.setIsCmd(true);
SyncEngine engine(account, options.source_dir, syncOptions, folder, &db);

Check warning on line 536 in src/cmd/cmd.cpp

View workflow job for this annotation

GitHub Actions / build

src/cmd/cmd.cpp:536:16 [cppcoreguidelines-init-variables]

variable 'engine' is not initialized
engine.setIgnoreHiddenFiles(options.ignoreHiddenFiles);
engine.setNetworkLimits(options.uplimit, options.downlimit);
QObject::connect(&engine, &SyncEngine::finished,
Expand Down
25 changes: 6 additions & 19 deletions src/gui/folder.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*

Check notice on line 1 in src/gui/folder.cpp

View workflow job for this annotation

GitHub Actions / build

Run clang-format on src/gui/folder.cpp

File src/gui/folder.cpp does not conform to Custom style guidelines. (lines 1631, 1633)
* Copyright (C) by Duncan Mac-Vicar P. <duncan@kde.org>
* Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
Expand All @@ -13,7 +13,7 @@
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "common/syncjournaldb.h"

Check failure on line 16 in src/gui/folder.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/folder.cpp:16:10 [clang-diagnostic-error]

'common/syncjournaldb.h' file not found
#include "config.h"

#include "account.h"
Expand Down Expand Up @@ -1628,27 +1628,14 @@

void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction dir, std::function<void(bool)> callback)
{
ConfigFile cfgFile;
if (!cfgFile.promptDeleteFiles()) {
callback(false);
return;
}

const QString msg = dir == SyncFileItem::Down ? tr("All files in the sync folder \"%1\" folder were deleted on the server.\n"
"These deletes will be synchronized to your local sync folder, making such files "
"unavailable unless you have a right to restore. \n"
"If you decide to restore the files, they will be re-synced with the server if you have rights to do so.\n"
"If you decide to delete the files, they will be unavailable to you, unless you are the owner.")
: tr("All the files in your local sync folder \"%1\" were deleted. These deletes will be "
"synchronized with your server, making such files unavailable unless restored.\n"
"Are you sure you want to sync those actions with the server?\n"
"If this was an accident and you decide to keep your files, they will be re-synced from the server.");
auto msgBox = new QMessageBox(QMessageBox::Warning, tr("Remove All Files?"),
msg.arg(shortGuiLocalPath()), QMessageBox::NoButton);
const QString msg = dir == SyncFileItem::Down ? tr("All files in the server folder \"%1\" were deleted.\n\nIf you restore the files, they will be uploaded again to the server.")
: tr("All files in the local folder \"%1\" were deleted.\n\nIf you restore the files, they will be downloaded again from the server.");
auto msgBox = new QMessageBox(QMessageBox::Warning, tr("Remove all files?"),
msg.arg(shortGuiLocalPath()), QMessageBox::NoButton);
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setWindowFlags(msgBox->windowFlags() | Qt::WindowStaysOnTopHint);
msgBox->addButton(tr("Remove all files"), QMessageBox::DestructiveRole);
QPushButton *keepBtn = msgBox->addButton(tr("Keep files"), QMessageBox::AcceptRole);
msgBox->addButton(tr("Proceed to remove all files"), QMessageBox::DestructiveRole);
QPushButton *keepBtn = msgBox->addButton(tr("Restore files"), QMessageBox::AcceptRole);
bool oldPaused = syncPaused();
setSyncPaused(true);
connect(msgBox, &QMessageBox::finished, this, [msgBox, keepBtn, callback, oldPaused, this] {
Expand Down
5 changes: 3 additions & 2 deletions src/libsync/syncengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -898,10 +898,11 @@ void SyncEngine::slotDiscoveryFinished()
qCInfo(lcEngine) << "#### Post-Reconcile end #################################################### " << _stopWatch.addLapTime(QStringLiteral("Post-Reconcile Finished")) << "ms";
};

if (!_hasNoneFiles && _hasRemoveFile) {
const auto displayDialog = ConfigFile().promptDeleteFiles() && !_syncOptions.isCmd();
if (!_hasNoneFiles && _hasRemoveFile && displayDialog) {
qCInfo(lcEngine) << "All the files are going to be changed, asking the user";
int side = 0; // > 0 means more deleted on the server. < 0 means more deleted on the client
foreach (const auto &it, _syncItems) {
for (const auto &it : _syncItems) {
if (it->_instruction == CSYNC_INSTRUCTION_REMOVE) {
side += it->_direction == SyncFileItem::Down ? 1 : -1;
}
Expand Down
11 changes: 11 additions & 0 deletions src/libsync/syncoptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ using namespace OCC;

SyncOptions::SyncOptions()
: _vfs(new VfsOff)
, _isCmd(false)
{
}

Expand Down Expand Up @@ -91,3 +92,13 @@ void SyncOptions::setPathPattern(const QString &pattern)
_fileRegex.setPatternOptions(Utility::fsCasePreserving() ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption);
_fileRegex.setPattern(pattern);
}

void SyncOptions::setIsCmd(const bool isCmd)
{
_isCmd = isCmd;
}

bool SyncOptions::isCmd() const
{
return _isCmd;
}
7 changes: 6 additions & 1 deletion src/libsync/syncoptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

#pragma once

#include "owncloudlib.h"

Check failure on line 17 in src/libsync/syncoptions.h

View workflow job for this annotation

GitHub Actions / build

src/libsync/syncoptions.h:17:10 [clang-diagnostic-error]

'owncloudlib.h' file not found
#include "common/vfs.h"

#include <QRegularExpression>
Expand Down Expand Up @@ -93,7 +93,6 @@
*/
void verifyChunkSizes();


/** A regular expression to match file names
* If no pattern is provided the default is an invalid regular expression.
*/
Expand All @@ -109,6 +108,10 @@
*/
void setPathPattern(const QString &pattern);

/** sync had been started via nextcloudcmd command line */
[[nodiscard]] bool isCmd() const;
void setIsCmd(const bool isCmd);

private:
/**
* Only sync files that match the expression
Expand All @@ -118,6 +121,8 @@

qint64 _minChunkSize = chunkV2MinChunkSize;
qint64 _maxChunkSize = chunkV2MaxChunkSize;

bool _isCmd = false;
};

}
69 changes: 69 additions & 0 deletions test/testsyncengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1906,6 +1906,75 @@ private slots:
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}

void testRemoveAllFilesWithNextcloudCmd()
{
FakeFolder fakeFolder{FileInfo{}};
auto nextcloudCmdSyncOptions = fakeFolder.syncEngine().syncOptions();
nextcloudCmdSyncOptions.setIsCmd(true);
fakeFolder.syncEngine().setSyncOptions(nextcloudCmdSyncOptions);
ConfigFile().setPromptDeleteFiles(true);
QSignalSpy displayDialogSignal(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles);

fakeFolder.remoteModifier().mkdir("folder");
fakeFolder.remoteModifier().insert("folder/file1");
fakeFolder.remoteModifier().insert("folder/file2");
fakeFolder.remoteModifier().insert("folder/file3");
fakeFolder.remoteModifier().mkdir("folder2");
fakeFolder.remoteModifier().insert("file1");
fakeFolder.remoteModifier().insert("file2");
fakeFolder.remoteModifier().insert("file3");

QVERIFY(fakeFolder.syncOnce());

fakeFolder.remoteModifier().remove("folder");
fakeFolder.remoteModifier().remove("folder2");
fakeFolder.remoteModifier().remove("file1");
fakeFolder.remoteModifier().remove("file2");
fakeFolder.remoteModifier().remove("file3");

QVERIFY(fakeFolder.syncOnce());
// the signal to display the dialog should not be emitted
QCOMPARE(displayDialogSignal.count(), 0);
QCOMPARE(fakeFolder.remoteModifier().find("folder"), nullptr);
QCOMPARE(fakeFolder.remoteModifier().find("folder2"), nullptr);
QCOMPARE(fakeFolder.remoteModifier().find("file1"), nullptr);
}

void testRemoveAllFilesWithoutNextcloudCmd()
{
FakeFolder fakeFolder{FileInfo{}};
auto nextcloudCmdSyncOptions = fakeFolder.syncEngine().syncOptions();
nextcloudCmdSyncOptions.setIsCmd(false);
fakeFolder.syncEngine().setSyncOptions(nextcloudCmdSyncOptions);
ConfigFile().setPromptDeleteFiles(true);
QSignalSpy displayDialogSignal(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles);

fakeFolder.remoteModifier().mkdir("folder");
fakeFolder.remoteModifier().insert("folder/file1");
fakeFolder.remoteModifier().insert("folder/file2");
fakeFolder.remoteModifier().insert("folder/file3");
fakeFolder.remoteModifier().mkdir("folder2");
fakeFolder.remoteModifier().insert("file1");
fakeFolder.remoteModifier().insert("file2");
fakeFolder.remoteModifier().insert("file3");

QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());

fakeFolder.remoteModifier().remove("folder");
fakeFolder.remoteModifier().remove("folder2");
fakeFolder.remoteModifier().remove("file1");
fakeFolder.remoteModifier().remove("file2");
fakeFolder.remoteModifier().remove("file3");

QVERIFY(fakeFolder.syncOnce());
// the signal to show the dialog should be emitted
QCOMPARE(displayDialogSignal.count(), 1);
QCOMPARE(fakeFolder.remoteModifier().find("folder"), nullptr);
QCOMPARE(fakeFolder.remoteModifier().find("folder2"), nullptr);
QCOMPARE(fakeFolder.remoteModifier().find("file1"), nullptr);
}
};

QTEST_GUILESS_MAIN(TestSyncEngine)
Expand Down
Loading