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.12] count the files deletion and warn if threshold is exceeded #7245

Merged
merged 1 commit into from
Oct 1, 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
17 changes: 16 additions & 1 deletion src/libsync/configfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
static constexpr char notificationRefreshIntervalC[] = "notificationRefreshInterval";
static constexpr char monoIconsC[] = "monoIcons";
static constexpr char promptDeleteC[] = "promptDeleteAllFiles";
static constexpr char deleteFilesThresholdC[] = "deleteFilesThreshold";

Check warning on line 62 in src/libsync/configfile.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/configfile.cpp:62:23 [readability-static-definition-in-anonymous-namespace]

'deleteFilesThresholdC' is a static definition in anonymous namespace; static is redundant here
static constexpr char crashReporterC[] = "crashReporter";
static constexpr char optionalServerNotificationsC[] = "optionalServerNotifications";
static constexpr char showCallNotificationsC[] = "showCallNotifications";
Expand Down Expand Up @@ -113,6 +114,8 @@
static const QSet validUpdateChannels { QStringLiteral("stable"), QStringLiteral("beta") };

static constexpr auto macFileProviderModuleEnabledC = "macFileProviderModuleEnabled";

static constexpr int deleteFilesThresholdDefaultValue = 100;

Check warning on line 118 in src/libsync/configfile.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/configfile.cpp:118:22 [readability-static-definition-in-anonymous-namespace]

'deleteFilesThresholdDefaultValue' is a static definition in anonymous namespace; static is redundant here
}

namespace OCC {
Expand Down Expand Up @@ -1019,7 +1022,7 @@
bool ConfigFile::promptDeleteFiles() const
{
QSettings settings(configFile(), QSettings::IniFormat);
return settings.value(QLatin1String(promptDeleteC), false).toBool();
return settings.value(QLatin1String(promptDeleteC), true).toBool();
}

void ConfigFile::setPromptDeleteFiles(bool promptDeleteFiles)
Expand All @@ -1028,6 +1031,18 @@
settings.setValue(QLatin1String(promptDeleteC), promptDeleteFiles);
}

int ConfigFile::deleteFilesThreshold() const
{
QSettings settings(configFile(), QSettings::IniFormat);
return settings.value(QLatin1String(deleteFilesThresholdC), deleteFilesThresholdDefaultValue).toInt();
}

void ConfigFile::setDeleteFilesThreshold(int thresholdValue)
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setValue(QLatin1String(deleteFilesThresholdC), thresholdValue);
}

bool ConfigFile::monoIcons() const
{
QSettings settings(configFile(), QSettings::IniFormat);
Expand Down
3 changes: 3 additions & 0 deletions src/libsync/configfile.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ class OWNCLOUDSYNC_EXPORT ConfigFile
[[nodiscard]] bool promptDeleteFiles() const;
void setPromptDeleteFiles(bool promptDeleteFiles);

[[nodiscard]] int deleteFilesThreshold() const;
void setDeleteFilesThreshold(int thresholdValue);

[[nodiscard]] bool crashReporter() const;
void setCrashReporter(bool enabled);

Expand Down
293 changes: 169 additions & 124 deletions src/libsync/syncengine.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*

Check notice on line 1 in src/libsync/syncengine.cpp

View workflow job for this annotation

GitHub Actions / build

Run clang-format on src/libsync/syncengine.cpp

File src/libsync/syncengine.cpp does not conform to Custom style guidelines. (lines 998, 1025, 1032, 1037, 1058, 1061, 1063, 1086, 1098, 1122, 1130)
* Copyright (C) by Duncan Mac-Vicar P. <duncan@kde.org>
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
*
Expand Down Expand Up @@ -799,132 +799,11 @@
_progressInfo->_status = ProgressInfo::Reconcile;
emit transmissionProgress(*_progressInfo);

// qCInfo(lcEngine) << "Permissions of the root folder: " << _csync_ctx->remote.root_perms.toString();
auto finish = [this]{
auto databaseFingerprint = _journal->dataFingerprint();
// If databaseFingerprint is empty, this means that there was no information in the database
// (for example, upgrading from a previous version, or first sync, or server not supporting fingerprint)
if (!databaseFingerprint.isEmpty() && _discoveryPhase
&& _discoveryPhase->_dataFingerprint != databaseFingerprint) {
qCInfo(lcEngine) << "data fingerprint changed, assume restore from backup" << databaseFingerprint << _discoveryPhase->_dataFingerprint;
restoreOldFiles(_syncItems);
}

if (_discoveryPhase->_anotherSyncNeeded && !_discoveryPhase->_filesNeedingScheduledSync.empty()) {
slotScheduleFilesDelayedSync();
} else if (_discoveryPhase->_anotherSyncNeeded && _anotherSyncNeeded == NoFollowUpSync) {
_anotherSyncNeeded = ImmediateFollowUp;
}

if (!_discoveryPhase->_filesUnscheduleSync.empty()) {
slotUnscheduleFilesDelayedSync();
}

if (_discoveryPhase->_hasDownloadRemovedItems && _discoveryPhase->_hasUploadErrorItems) {
for (const auto &item : qAsConst(_syncItems)) {
if (item->_instruction == CSYNC_INSTRUCTION_ERROR && item->_direction == SyncFileItem::Up) {
item->_instruction = CSYNC_INSTRUCTION_IGNORE;
}
}
_anotherSyncNeeded = ImmediateFollowUp;
}

Q_ASSERT(std::is_sorted(_syncItems.begin(), _syncItems.end()));

qCInfo(lcEngine) << "#### Reconcile (aboutToPropagate) #################################################### " << _stopWatch.addLapTime(QStringLiteral("Reconcile (aboutToPropagate)")) << "ms";

_localDiscoveryPaths.clear();

// To announce the beginning of the sync
emit aboutToPropagate(_syncItems);

qCInfo(lcEngine) << "#### Reconcile (aboutToPropagate OK) #################################################### "<< _stopWatch.addLapTime(QStringLiteral("Reconcile (aboutToPropagate OK)")) << "ms";

// it's important to do this before ProgressInfo::start(), to announce start of new sync
_progressInfo->_status = ProgressInfo::Propagation;
emit transmissionProgress(*_progressInfo);
_progressInfo->startEstimateUpdates();

// post update phase script: allow to tweak stuff by a custom script in debug mode.
if (!qEnvironmentVariableIsEmpty("OWNCLOUD_POST_UPDATE_SCRIPT")) {
#ifndef NDEBUG
const QString script = qEnvironmentVariable("OWNCLOUD_POST_UPDATE_SCRIPT");

qCDebug(lcEngine) << "Post Update Script: " << script;
auto scriptArgs = script.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
if (scriptArgs.size() > 0) {
const auto scriptExecutable = scriptArgs.takeFirst();
QProcess::execute(scriptExecutable, scriptArgs);
}
#else
qCWarning(lcEngine) << "**** Attention: POST_UPDATE_SCRIPT installed, but not executed because compiled with NDEBUG";
#endif
}

// do a database commit
_journal->commit(QStringLiteral("post treewalk"));

_propagator = QSharedPointer<OwncloudPropagator>(
new OwncloudPropagator(_account, _localPath, _remotePath, _journal, _bulkUploadBlackList));
_propagator->setSyncOptions(_syncOptions);
connect(_propagator.data(), &OwncloudPropagator::itemCompleted,
this, &SyncEngine::slotItemCompleted);
connect(_propagator.data(), &OwncloudPropagator::progress,
this, &SyncEngine::slotProgress);
connect(_propagator.data(), &OwncloudPropagator::finished, this, &SyncEngine::slotPropagationFinished, Qt::QueuedConnection);
connect(_propagator.data(), &OwncloudPropagator::seenLockedFile, this, &SyncEngine::seenLockedFile);
connect(_propagator.data(), &OwncloudPropagator::touchedFile, this, &SyncEngine::slotAddTouchedFile);
connect(_propagator.data(), &OwncloudPropagator::insufficientLocalStorage, this, &SyncEngine::slotInsufficientLocalStorage);
connect(_propagator.data(), &OwncloudPropagator::insufficientRemoteStorage, this, &SyncEngine::slotInsufficientRemoteStorage);
connect(_propagator.data(), &OwncloudPropagator::newItem, this, &SyncEngine::slotNewItem);

// apply the network limits to the propagator
setNetworkLimits(_uploadLimit, _downloadLimit);

deleteStaleDownloadInfos(_syncItems);
deleteStaleUploadInfos(_syncItems);
deleteStaleErrorBlacklistEntries(_syncItems);
_journal->commit(QStringLiteral("post stale entry removal"));

// Emit the started signal only after the propagator has been set up.
if (_needsUpdate)
Q_EMIT started();

_propagator->start(std::move(_syncItems));

qCInfo(lcEngine) << "#### Post-Reconcile end #################################################### " << _stopWatch.addLapTime(QStringLiteral("Post-Reconcile Finished")) << "ms";
};

if (!_hasNoneFiles && _hasRemoveFile) {
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) {
if (it->_instruction == CSYNC_INSTRUCTION_REMOVE) {
side += it->_direction == SyncFileItem::Down ? 1 : -1;
}
}

QPointer<QObject> guard = new QObject();
QPointer<QObject> self = this;
auto callback = [this, self, finish, guard](bool cancel) -> void {
// use a guard to ensure its only called once...
// qpointer to self to ensure we still exist
if (!guard || !self) {
return;
}
guard->deleteLater();
if (cancel) {
qCInfo(lcEngine) << "User aborted sync";
finalize(false);
return;
} else {
finish();
}
};
emit aboutToRemoveAllFiles(side >= 0 ? SyncFileItem::Down : SyncFileItem::Up, callback);
if (handleMassDeletion()) {
return;
}
finish();

finishSync();
}

void SyncEngine::slotCleanPollsJobAborted(const QString &error, const ErrorCategory errorCategory)
Expand Down Expand Up @@ -1101,6 +980,172 @@
}
}

void SyncEngine::cancelSyncOrContinue(bool cancel)
{
if (cancel) {
qCInfo(lcEngine) << "User aborted sync";
finalize(false);
} else {
finishSync();
}
}

void SyncEngine::finishSync()
{
auto databaseFingerprint = _journal->dataFingerprint();
// If databaseFingerprint is empty, this means that there was no information in the database
// (for example, upgrading from a previous version, or first sync, or server not supporting fingerprint)
if (!databaseFingerprint.isEmpty() && _discoveryPhase
&& _discoveryPhase->_dataFingerprint != databaseFingerprint) {
qCInfo(lcEngine) << "data fingerprint changed, assume restore from backup" << databaseFingerprint << _discoveryPhase->_dataFingerprint;
restoreOldFiles(_syncItems);
}

if (_discoveryPhase && _discoveryPhase->_anotherSyncNeeded && !_discoveryPhase->_filesNeedingScheduledSync.empty()) {
slotScheduleFilesDelayedSync();
} else if (_discoveryPhase && _discoveryPhase->_anotherSyncNeeded && _anotherSyncNeeded == NoFollowUpSync) {
_anotherSyncNeeded = ImmediateFollowUp;
}

if (_discoveryPhase && !_discoveryPhase->_filesUnscheduleSync.empty()) {
slotUnscheduleFilesDelayedSync();
}

if (_discoveryPhase && _discoveryPhase->_hasDownloadRemovedItems && _discoveryPhase->_hasUploadErrorItems) {
for (const auto &item : qAsConst(_syncItems)) {
if (item->_instruction == CSYNC_INSTRUCTION_ERROR && item->_direction == SyncFileItem::Up) {
// item->_instruction = CSYNC_INSTRUCTION_IGNORE;
}
}
_anotherSyncNeeded = ImmediateFollowUp;
}

Q_ASSERT(std::is_sorted(_syncItems.begin(), _syncItems.end()));

qCInfo(lcEngine) << "#### Reconcile (aboutToPropagate) #################################################### " << _stopWatch.addLapTime(QStringLiteral("Reconcile (aboutToPropagate)")) << "ms";

_localDiscoveryPaths.clear();

// To announce the beginning of the sync
emit aboutToPropagate(_syncItems);

qCInfo(lcEngine) << "#### Reconcile (aboutToPropagate OK) #################################################### "<< _stopWatch.addLapTime(QStringLiteral("Reconcile (aboutToPropagate OK)")) << "ms";

// it's important to do this before ProgressInfo::start(), to announce start of new sync
_progressInfo->_status = ProgressInfo::Propagation;
emit transmissionProgress(*_progressInfo);
_progressInfo->startEstimateUpdates();

// post update phase script: allow to tweak stuff by a custom script in debug mode.
if (!qEnvironmentVariableIsEmpty("OWNCLOUD_POST_UPDATE_SCRIPT")) {
#ifndef NDEBUG
const QString script = qEnvironmentVariable("OWNCLOUD_POST_UPDATE_SCRIPT");

qCDebug(lcEngine) << "Post Update Script: " << script;
auto scriptArgs = script.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
if (scriptArgs.size() > 0) {
const auto scriptExecutable = scriptArgs.takeFirst();
QProcess::execute(scriptExecutable, scriptArgs);
}
#else
qCWarning(lcEngine) << "**** Attention: POST_UPDATE_SCRIPT installed, but not executed because compiled with NDEBUG";
#endif
}

// do a database commit
_journal->commit(QStringLiteral("post treewalk"));

_propagator = QSharedPointer<OwncloudPropagator>(
new OwncloudPropagator(_account, _localPath, _remotePath, _journal, _bulkUploadBlackList));
_propagator->setSyncOptions(_syncOptions);
connect(_propagator.data(), &OwncloudPropagator::itemCompleted,
this, &SyncEngine::slotItemCompleted);
connect(_propagator.data(), &OwncloudPropagator::progress,
this, &SyncEngine::slotProgress);
connect(_propagator.data(), &OwncloudPropagator::finished, this, &SyncEngine::slotPropagationFinished, Qt::QueuedConnection);
connect(_propagator.data(), &OwncloudPropagator::seenLockedFile, this, &SyncEngine::seenLockedFile);
connect(_propagator.data(), &OwncloudPropagator::touchedFile, this, &SyncEngine::slotAddTouchedFile);
connect(_propagator.data(), &OwncloudPropagator::insufficientLocalStorage, this, &SyncEngine::slotInsufficientLocalStorage);
connect(_propagator.data(), &OwncloudPropagator::insufficientRemoteStorage, this, &SyncEngine::slotInsufficientRemoteStorage);
connect(_propagator.data(), &OwncloudPropagator::newItem, this, &SyncEngine::slotNewItem);

// apply the network limits to the propagator
setNetworkLimits(_uploadLimit, _downloadLimit);

deleteStaleDownloadInfos(_syncItems);
deleteStaleUploadInfos(_syncItems);
deleteStaleErrorBlacklistEntries(_syncItems);
_journal->commit(QStringLiteral("post stale entry removal"));

// Emit the started signal only after the propagator has been set up.
if (_needsUpdate)
Q_EMIT started();

_propagator->start(std::move(_syncItems));

qCInfo(lcEngine) << "#### Post-Reconcile end #################################################### " << _stopWatch.addLapTime(QStringLiteral("Post-Reconcile Finished")) << "ms";
}

bool SyncEngine::handleMassDeletion()
{
const auto displayDialog = ConfigFile().promptDeleteFiles();
const auto allFilesDeleted = !_hasNoneFiles && _hasRemoveFile;

auto deletionCounter = 0;
for (const auto &oneItem : qAsConst(_syncItems)) {
if (oneItem->_instruction == CSYNC_INSTRUCTION_REMOVE) {
if (oneItem->isDirectory()) {
const auto result = _journal->listFilesInPath(oneItem->_file.toUtf8(), [&deletionCounter] (const auto &oneRecord) {
if (oneRecord.isFile()) {
++deletionCounter;
}
});
if (!result) {
qCDebug(lcEngine()) << "unable to find the number of files within a deleted folder:" << oneItem->_file;
}
} else {
++deletionCounter;
}
}
}
const auto filesDeletedThresholdExceeded = deletionCounter > ConfigFile().deleteFilesThreshold();

if ((allFilesDeleted || filesDeletedThresholdExceeded) && 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
for (const auto &it : qAsConst(_syncItems)) {
if (it->_instruction == CSYNC_INSTRUCTION_REMOVE) {
side += it->_direction == SyncFileItem::Down ? 1 : -1;
}
}

promptUserBeforePropagation([this, side](auto &&callback){
emit aboutToRemoveAllFiles(side >= 0 ? SyncFileItem::Down : SyncFileItem::Up, callback);
});
return true;
}
return false;
}

template <typename T>
void SyncEngine::promptUserBeforePropagation(T &&lambda)
{
QPointer<QObject> guard = new QObject();
QPointer<QObject> self = this;
auto callback = [this, self, guard](bool cancel) -> void {
// use a guard to ensure its only called once...
// qpointer to self to ensure we still exist
if (!guard || !self) {
return;
}
guard->deleteLater();

cancelSyncOrContinue(cancel);
};

lambda(callback);
}

void SyncEngine::slotAddTouchedFile(const QString &fn)
{
QElapsedTimer now;
Expand Down
9 changes: 9 additions & 0 deletions src/libsync/syncengine.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*

Check notice on line 1 in src/libsync/syncengine.h

View workflow job for this annotation

GitHub Actions / build

Run clang-format on src/libsync/syncengine.h

File src/libsync/syncengine.h does not conform to Custom style guidelines. (lines 363)
* Copyright (C) by Duncan Mac-Vicar P. <duncan@kde.org>
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
*
Expand Down Expand Up @@ -354,6 +354,15 @@
*/
void restoreOldFiles(SyncFileItemVector &syncItems);

void cancelSyncOrContinue(bool cancel);

void finishSync();

bool handleMassDeletion();

template <typename T>
void promptUserBeforePropagation(T &&lambda);

// true if there is at least one file which was not changed on the server
bool _hasNoneFiles = false;

Expand Down
Loading