diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp
index 23e33c16999c..03719c6d2bf8 100644
--- a/src/common/syncjournaldb.cpp
+++ b/src/common/syncjournaldb.cpp
@@ -205,6 +205,33 @@ bool SyncJournalDb::maybeMigrateDb(const QString &localPath, const QString &abso
return true;
}
+bool SyncJournalDb::findPathInSelectiveSyncList(const QStringList &list, const QString &path)
+{
+ Q_ASSERT(std::is_sorted(list.cbegin(), list.cend()));
+
+ if (list.size() == 1 && list.first() == QStringLiteral("/")) {
+ // Special case for the case "/" is there, it matches everything
+ return true;
+ }
+
+ const QString pathSlash = path + QLatin1Char('/');
+
+ // Since the list is sorted, we can do a binary search.
+ // If the path is a prefix of another item or right after in the lexical order.
+ auto it = std::lower_bound(list.cbegin(), list.cend(), pathSlash);
+
+ if (it != list.cend() && *it == pathSlash) {
+ return true;
+ }
+
+ if (it == list.cbegin()) {
+ return false;
+ }
+ --it;
+ Q_ASSERT(it->endsWith(QLatin1Char('/'))); // Folder::setSelectiveSyncBlackList makes sure of that
+ return pathSlash.startsWith(*it);
+}
+
bool SyncJournalDb::exists()
{
QMutexLocker locker(&_mutex);
@@ -1958,10 +1985,7 @@ QStringList SyncJournalDb::getSelectiveSyncList(SyncJournalDb::SelectiveSyncList
if (!next.hasData)
break;
- auto entry = query->stringValue(0);
- if (!entry.endsWith(QLatin1Char('/'))) {
- entry.append(QLatin1Char('/'));
- }
+ const auto entry = Utility::trailingSlashPath(query->stringValue(0));
result.append(entry);
}
*ok = true;
@@ -1986,7 +2010,7 @@ void SyncJournalDb::setSelectiveSyncList(SyncJournalDb::SelectiveSyncListType ty
}
SqlQuery insQuery("INSERT INTO selectivesync VALUES (?1, ?2)", _db);
- foreach (const auto &path, list) {
+ for (const auto &path : list) {
insQuery.reset_and_clear_bindings();
insQuery.bindValue(1, path);
insQuery.bindValue(2, int(type));
diff --git a/src/common/syncjournaldb.h b/src/common/syncjournaldb.h
index d9a3e36b84b3..eb0ab0e21e0a 100644
--- a/src/common/syncjournaldb.h
+++ b/src/common/syncjournaldb.h
@@ -58,6 +58,9 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject
/// Migrate a csync_journal to the new path, if necessary. Returns false on error
static bool maybeMigrateDb(const QString &localPath, const QString &absoluteJournalPath);
+ /// Given a sorted list of paths ending with '/', return whether or not the given path is within one of the paths of the list
+ static bool findPathInSelectiveSyncList(const QStringList &list, const QString &path);
+
// To verify that the record could be found check with SyncJournalFileRecord::isValid()
[[nodiscard]] bool getFileRecord(const QString &filename, SyncJournalFileRecord *rec) { return getFileRecord(filename.toUtf8(), rec); }
[[nodiscard]] bool getFileRecord(const QByteArray &filename, SyncJournalFileRecord *rec);
diff --git a/src/common/utility.cpp b/src/common/utility.cpp
index 9baf1377720f..61f2489e4255 100644
--- a/src/common/utility.cpp
+++ b/src/common/utility.cpp
@@ -723,4 +723,10 @@ bool Utility::isCaseClashConflictFile(const QString &name)
return bname.contains(QStringLiteral("(case clash from"));
}
+QString Utility::trailingSlashPath(const QString &path)
+{
+ static const auto slash = QLatin1Char('/');
+ return path.endsWith(slash) ? path : QString(path + slash);
+}
+
} // namespace OCC
diff --git a/src/common/utility.h b/src/common/utility.h
index 0dd693f284a7..b554a196be18 100644
--- a/src/common/utility.h
+++ b/src/common/utility.h
@@ -255,6 +255,8 @@ namespace Utility {
*/
OCSYNC_EXPORT void registerUriHandlerForLocalEditing();
+ OCSYNC_EXPORT QString trailingSlashPath(const QString &path);
+
#ifdef Q_OS_WIN
OCSYNC_EXPORT bool registryKeyExists(HKEY hRootKey, const QString &subKey);
OCSYNC_EXPORT QVariant registryGetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName);
diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp
index b4ca6b3b7808..cc300174d506 100644
--- a/src/gui/accountsettings.cpp
+++ b/src/gui/accountsettings.cpp
@@ -14,6 +14,7 @@
#include "accountsettings.h"
+#include "common/syncjournaldb.h"
#include "common/syncjournalfilerecord.h"
#include "qmessagebox.h"
#include "ui_accountsettings.h"
@@ -1480,48 +1481,81 @@ void AccountSettings::folderTerminateSyncAndUpdateBlackList(const QStringList &b
void AccountSettings::refreshSelectiveSyncStatus()
{
- QString msg;
- auto cnt = 0;
+ QString unsyncedFoldersString;
+ QString becameBigFoldersString;
+
const auto folders = FolderMan::instance()->map().values();
+
+ static const auto folderSeparatorString = QStringLiteral(", ");
+ static const auto folderLinkString = [](const QString &slashlessFolderPath, const QString &folderName) {
+ return QStringLiteral("%1").arg(slashlessFolderPath, folderName);
+ };
+ static const auto appendFolderDisplayString = [](QString &foldersString, const QString &folderDisplayString) {
+ if (!foldersString.isEmpty()) {
+ foldersString += folderSeparatorString;
+ }
+ foldersString += folderDisplayString;
+ };
+
_ui->bigFolderUi->setVisible(false);
+
for (const auto folder : folders) {
if (folder->accountState() != _accountState) {
continue;
}
auto ok = false;
+ auto blacklistOk = false;
const auto undecidedList = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, &ok);
+ auto blacklist = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &blacklistOk);
+ blacklist.sort();
+
for (const auto &it : undecidedList) {
// FIXME: add the folder alias in a hoover hint.
// folder->alias() + QLatin1String("/")
- if (cnt++) {
- msg += QLatin1String(", ");
- }
- auto myFolder = (it);
- if (myFolder.endsWith('/')) {
- myFolder.chop(1);
- }
- const auto theIndx = _model->indexForPath(folder, myFolder);
- if (theIndx.isValid()) {
- msg += QString::fromLatin1("%1")
- .arg(Utility::escape(myFolder), Utility::escape(folder->alias()));
+
+ const auto folderTrailingSlash = Utility::trailingSlashPath(it);
+ const auto folderWithoutTrailingSlash = it.endsWith('/') ? it.left(it.length() - 1) : it;
+ const auto escapedFolderString = Utility::escape(folderWithoutTrailingSlash);
+ const auto escapedFolderName = Utility::escape(folder->alias());
+ const auto folderIdx = _model->indexForPath(folder, folderWithoutTrailingSlash);
+
+ // If we do not know the index yet then do not provide a link string
+ const auto folderDisplayString = folderIdx.isValid() ? folderLinkString(escapedFolderString, escapedFolderName) : folderWithoutTrailingSlash;
+
+ // The new big folder procedure automatically places these new big folders in the blacklist.
+ // This is not the case for existing folders discovered to have gone beyond the limit.
+ // So we need to check if the folder is in the blacklist or not and tweak the message accordingly.
+ if (SyncJournalDb::findPathInSelectiveSyncList(blacklist, folderTrailingSlash)) {
+ appendFolderDisplayString(unsyncedFoldersString, folderDisplayString);
} else {
- msg += myFolder; // no link because we do not know the index yet.
+ appendFolderDisplayString(becameBigFoldersString, folderDisplayString);
}
}
}
- if (!msg.isEmpty()) {
- ConfigFile cfg;
- const auto info = !cfg.confirmExternalStorage() ?
- tr("There are folders that were not synchronized because they are too big: ") :
- !cfg.newBigFolderSizeLimit().first ?
- tr("There are folders that were not synchronized because they are external storages: ") :
- tr("There are folders that were not synchronized because they are too big or external storages: ");
+ ConfigFile cfg;
+ QString infoString;
- _ui->selectiveSyncNotification->setText(info + msg);
- _ui->bigFolderUi->setVisible(true);
+ if (!unsyncedFoldersString.isEmpty()) {
+ infoString += !cfg.confirmExternalStorage() ? tr("There are folders that were not synchronized because they are too big: ")
+ : !cfg.newBigFolderSizeLimit().first ? tr("There are folders that were not synchronized because they are external storages: ")
+ : tr("There are folders that were not synchronized because they are too big or external storages: ");
+
+ infoString += unsyncedFoldersString;
}
+
+ if (!becameBigFoldersString.isEmpty()) {
+ if (!infoString.isEmpty()) {
+ infoString += QStringLiteral("\n");
+ }
+
+ const auto folderSizeLimitString = QString::number(cfg.newBigFolderSizeLimit().second);
+ infoString += tr("There are folders that have grown in size beyond %1MB: %2").arg(folderSizeLimitString, becameBigFoldersString);
+ }
+
+ _ui->selectiveSyncNotification->setText(infoString);
+ _ui->bigFolderUi->setVisible(!infoString.isEmpty());
}
bool AccountSettings::event(QEvent *e)
diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp
index 8d15e8da3021..0511cba81a10 100644
--- a/src/gui/folder.cpp
+++ b/src/gui/folder.cpp
@@ -13,6 +13,7 @@
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
+#include "common/syncjournaldb.h"
#include "config.h"
#include "account.h"
@@ -52,6 +53,7 @@ namespace {
constexpr auto versionC = "version";
#endif
}
+
namespace OCC {
Q_LOGGING_CATEGORY(lcFolder, "nextcloud.gui.folder", QtInfoMsg)
@@ -102,6 +104,7 @@ Folder::Folder(const FolderDefinition &definition,
this, &Folder::slotItemCompleted);
connect(_engine.data(), &SyncEngine::newBigFolder,
this, &Folder::slotNewBigFolderDiscovered);
+ connect(_engine.data(), &SyncEngine::existingFolderNowBig, this, &Folder::slotExistingFolderNowBig);
connect(_engine.data(), &SyncEngine::seenLockedFile, FolderMan::instance(), &FolderMan::slotSyncOnceFileUnlocks);
connect(_engine.data(), &SyncEngine::aboutToPropagate,
this, &Folder::slotLogPropagationStart);
@@ -167,10 +170,10 @@ void Folder::checkLocalPath()
if (_canonicalLocalPath.isEmpty()) {
qCWarning(lcFolder) << "Broken symlink:" << _definition.localPath;
_canonicalLocalPath = _definition.localPath;
- } else if (!_canonicalLocalPath.endsWith('/')) {
- _canonicalLocalPath.append('/');
}
+ _canonicalLocalPath = Utility::trailingSlashPath(_canonicalLocalPath);
+
if (fi.isDir() && fi.isReadable()) {
qCDebug(lcFolder) << "Checked local path ok";
} else {
@@ -214,10 +217,8 @@ QString Folder::path() const
QString Folder::shortGuiLocalPath() const
{
QString p = _definition.localPath;
- QString home = QDir::homePath();
- if (!home.endsWith('/')) {
- home.append('/');
- }
+ const auto home = Utility::trailingSlashPath(QDir::homePath());
+
if (p.startsWith(home)) {
p = p.mid(home.length());
}
@@ -266,10 +267,7 @@ QString Folder::remotePath() const
QString Folder::remotePathTrailingSlash() const
{
- QString result = remotePath();
- if (!result.endsWith('/'))
- result.append('/');
- return result;
+ return Utility::trailingSlashPath(remotePath());
}
QUrl Folder::remoteUrl() const
@@ -484,7 +482,7 @@ void Folder::createGuiLog(const QString &filename, LogStatus status, int count,
if (!text.isEmpty()) {
// Ignores the settings in case of an error or conflict
if(status == LogStatusError || status == LogStatusConflict)
- logger->postOptionalGuiLog(tr("Sync Activity"), text);
+ logger->postGuiLog(tr("Sync Activity"), text);
}
}
}
@@ -840,6 +838,46 @@ bool Folder::pathIsIgnored(const QString &path) const
return false;
}
+void Folder::appendPathToSelectiveSyncList(const QString &path, const SyncJournalDb::SelectiveSyncListType listType)
+{
+ const auto folderPath = Utility::trailingSlashPath(path);
+ const auto journal = journalDb();
+ auto ok = false;
+ auto list = journal->getSelectiveSyncList(listType, &ok);
+
+ if (ok) {
+ list.append(folderPath);
+ journal->setSelectiveSyncList(listType, list);
+ }
+}
+
+void Folder::removePathFromSelectiveSyncList(const QString &path, const SyncJournalDb::SelectiveSyncListType listType)
+{
+ const auto folderPath = Utility::trailingSlashPath(path);
+ const auto journal = journalDb();
+ auto ok = false;
+ auto list = journal->getSelectiveSyncList(listType, &ok);
+
+ if (ok) {
+ list.removeAll(folderPath);
+ journal->setSelectiveSyncList(listType, list);
+ }
+}
+
+void Folder::whitelistPath(const QString &path)
+{
+ removePathFromSelectiveSyncList(path, SyncJournalDb::SelectiveSyncUndecidedList);
+ removePathFromSelectiveSyncList(path, SyncJournalDb::SelectiveSyncBlackList);
+ appendPathToSelectiveSyncList(path, SyncJournalDb::SelectiveSyncWhiteList);
+}
+
+void Folder::blacklistPath(const QString &path)
+{
+ removePathFromSelectiveSyncList(path, SyncJournalDb::SelectiveSyncUndecidedList);
+ removePathFromSelectiveSyncList(path, SyncJournalDb::SelectiveSyncWhiteList);
+ appendPathToSelectiveSyncList(path, SyncJournalDb::SelectiveSyncBlackList);
+}
+
bool Folder::isFileExcludedAbsolute(const QString &fullPath) const
{
return _engine->excludedFiles().isExcluded(fullPath, path(), _definition.ignoreHiddenFiles);
@@ -1092,11 +1130,6 @@ void Folder::slotSyncFinished(bool success)
qCInfo(lcFolder) << "the last" << _consecutiveFailingSyncs << "syncs failed";
}
- if (_syncResult.status() == SyncResult::Success && success) {
- // Clear the white list as all the folders that should be on that list are sync-ed
- journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, QStringList());
- }
-
if ((_syncResult.status() == SyncResult::Success
|| _syncResult.status() == SyncResult::Problem)
&& success) {
@@ -1183,10 +1216,7 @@ void Folder::slotItemCompleted(const SyncFileItemPtr &item, ErrorCategory errorC
void Folder::slotNewBigFolderDiscovered(const QString &newF, bool isExternal)
{
- auto newFolder = newF;
- if (!newFolder.endsWith(QLatin1Char('/'))) {
- newFolder += QLatin1Char('/');
- }
+ const auto newFolder = Utility::trailingSlashPath(newF);
auto journal = journalDb();
// Add the entry to the blacklist if it is neither in the blacklist or whitelist already
@@ -1214,10 +1244,102 @@ void Folder::slotNewBigFolderDiscovered(const QString &newF, bool isExternal)
message += tr("Please go in the settings to select it if you wish to download it.");
auto logger = Logger::instance();
- logger->postOptionalGuiLog(Theme::instance()->appNameGUI(), message);
+ logger->postGuiLog(Theme::instance()->appNameGUI(), message);
+ }
+}
+
+void Folder::slotExistingFolderNowBig(const QString &folderPath)
+{
+ const auto trailSlashFolderPath = Utility::trailingSlashPath(folderPath);
+ const auto journal = journalDb();
+ const auto stopSyncing = ConfigFile().stopSyncingExistingFoldersOverLimit();
+
+ // Add the entry to the whitelist if it is neither in the blacklist or whitelist already
+ bool ok1 = false;
+ bool ok2 = false;
+ auto blacklist = journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok1);
+ auto whitelist = journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, &ok2);
+
+ const auto inDecidedLists = blacklist.contains(trailSlashFolderPath) || whitelist.contains(trailSlashFolderPath);
+ if (inDecidedLists) {
+ return;
+ }
+
+ auto relevantList = stopSyncing ? blacklist : whitelist;
+ const auto relevantListType = stopSyncing ? SyncJournalDb::SelectiveSyncBlackList : SyncJournalDb::SelectiveSyncWhiteList;
+
+ if (ok1 && ok2 && !inDecidedLists) {
+ relevantList.append(trailSlashFolderPath);
+ journal->setSelectiveSyncList(relevantListType, relevantList);
+
+ if (stopSyncing) {
+ // Abort current down sync and start again
+ slotTerminateSync();
+ scheduleThisFolderSoon();
+ }
+ }
+
+ auto undecidedListQueryOk = false;
+ auto undecidedList = journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, &undecidedListQueryOk);
+ if (undecidedListQueryOk) {
+ if (!undecidedList.contains(trailSlashFolderPath)) {
+ undecidedList.append(trailSlashFolderPath);
+ journal->setSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, undecidedList);
+ emit newBigFolderDiscovered(trailSlashFolderPath);
+ }
+
+ postExistingFolderNowBigNotification(folderPath);
+ postExistingFolderNowBigActivity(folderPath);
}
}
+void Folder::postExistingFolderNowBigNotification(const QString &folderPath)
+{
+ const auto stopSyncing = ConfigFile().stopSyncingExistingFoldersOverLimit();
+ const auto messageInstruction =
+ stopSyncing ? "Synchronisation of this folder has been disabled." : "Synchronisation of this folder can be disabled in the settings window.";
+ const auto message = tr("A folder has surpassed the set folder size limit of %1MB: %2.\n%3")
+ .arg(QString::number(ConfigFile().newBigFolderSizeLimit().second), folderPath, messageInstruction);
+ Logger::instance()->postGuiLog(Theme::instance()->appNameGUI(), message);
+}
+
+void Folder::postExistingFolderNowBigActivity(const QString &folderPath) const
+{
+ const auto stopSyncing = ConfigFile().stopSyncingExistingFoldersOverLimit();
+ const auto trailSlashFolderPath = Utility::trailingSlashPath(folderPath);
+
+ auto whitelistActivityLink = ActivityLink();
+ whitelistActivityLink._label = tr("Keep syncing");
+ whitelistActivityLink._primary = false;
+ whitelistActivityLink._verb = ActivityLink::WhitelistFolderVerb;
+
+ QVector activityLinks = {whitelistActivityLink};
+
+ if (!stopSyncing) {
+ auto blacklistActivityLink = ActivityLink();
+ blacklistActivityLink._label = tr("Stop syncing");
+ blacklistActivityLink._primary = true;
+ blacklistActivityLink._verb = ActivityLink::BlacklistFolderVerb;
+
+ activityLinks.append(blacklistActivityLink);
+ }
+
+ auto existingFolderNowBigActivity = Activity();
+ existingFolderNowBigActivity._type = Activity::NotificationType;
+ existingFolderNowBigActivity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate);
+ existingFolderNowBigActivity._subject =
+ tr("The folder %1 has surpassed the set folder size limit of %2MB.").arg(folderPath, QString::number(ConfigFile().newBigFolderSizeLimit().second));
+ existingFolderNowBigActivity._message = tr("Would you like to stop syncing this folder?");
+ existingFolderNowBigActivity._accName = _accountState->account()->displayName();
+ existingFolderNowBigActivity._folder = alias();
+ existingFolderNowBigActivity._file = cleanPath() + '/' + trailSlashFolderPath;
+ existingFolderNowBigActivity._links = activityLinks;
+ existingFolderNowBigActivity._id = qHash(existingFolderNowBigActivity._file);
+
+ const auto user = UserModel::instance()->findUserForAccount(_accountState.data());
+ user->slotAddNotification(this, existingFolderNowBigActivity);
+}
+
void Folder::slotLogPropagationStart()
{
_fileLog->logLap("Propagation starts");
@@ -1283,7 +1405,7 @@ void Folder::warnOnNewExcludedItem(const SyncJournalFileRecord &record, const QS
"It will not be synchronized.")
.arg(fi.filePath());
- Logger::instance()->postOptionalGuiLog(Theme::instance()->appNameGUI(), message);
+ Logger::instance()->postGuiLog(Theme::instance()->appNameGUI(), message);
}
void Folder::slotWatcherUnreliable(const QString &message)
@@ -1451,7 +1573,7 @@ void Folder::removeLocalE2eFiles()
}
if (!parentPathEncrypted) {
- const auto pathAdjusted = rec._path.endsWith('/') ? rec._path : QString(rec._path + QStringLiteral("/"));
+ const auto pathAdjusted = Utility::trailingSlashPath(rec._path);
e2eFoldersToBlacklist.append(pathAdjusted);
}
}
@@ -1566,11 +1688,8 @@ bool FolderDefinition::load(QSettings &settings, const QString &alias,
QString FolderDefinition::prepareLocalPath(const QString &path)
{
- QString p = QDir::fromNativeSeparators(path);
- if (!p.endsWith(QLatin1Char('/'))) {
- p.append(QLatin1Char('/'));
- }
- return p;
+ const auto normalisedPath = QDir::fromNativeSeparators(path);
+ return Utility::trailingSlashPath(normalisedPath);
}
QString FolderDefinition::prepareTargetPath(const QString &path)
diff --git a/src/gui/folder.h b/src/gui/folder.h
index cac47809ba11..13f493102ee9 100644
--- a/src/gui/folder.h
+++ b/src/gui/folder.h
@@ -301,6 +301,9 @@ class Folder : public QObject
QString fileFromLocalPath(const QString &localPath) const;
+ void whitelistPath(const QString &path);
+ void blacklistPath(const QString &path);
+
signals:
void syncStateChange();
void syncStarted();
@@ -405,6 +408,7 @@ private slots:
void slotEmitFinishedDelayed();
void slotNewBigFolderDiscovered(const QString &, bool isExternal);
+ void slotExistingFolderNowBig(const QString &folderPath);
void slotLogPropagationStart();
@@ -467,6 +471,12 @@ private slots:
void correctPlaceholderFiles();
+ void appendPathToSelectiveSyncList(const QString &path, const SyncJournalDb::SelectiveSyncListType listType);
+ void removePathFromSelectiveSyncList(const QString &path, const SyncJournalDb::SelectiveSyncListType listType);
+
+ static void postExistingFolderNowBigNotification(const QString &folderPath);
+ void postExistingFolderNowBigActivity(const QString &folderPath) const;
+
AccountStatePtr _accountState;
FolderDefinition _definition;
QString _canonicalLocalPath; // As returned with QFileInfo:canonicalFilePath. Always ends with "/"
diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp
index 27a79520036f..f41a3142480b 100644
--- a/src/gui/folderman.cpp
+++ b/src/gui/folderman.cpp
@@ -1259,6 +1259,38 @@ Folder *FolderMan::folderForPath(const QString &path)
return it != folders.cend() ? *it : nullptr;
}
+void FolderMan::addFolderToSelectiveSyncList(const QString &path, const SyncJournalDb::SelectiveSyncListType list)
+{
+ const auto folder = folderForPath(path);
+ if (!folder) {
+ return;
+ }
+
+ const QString folderPath = folder->cleanPath() + QLatin1Char('/');
+ const auto relPath = path.mid(folderPath.length());
+
+ switch (list) {
+ case SyncJournalDb::SelectiveSyncListType::SelectiveSyncWhiteList:
+ folder->whitelistPath(relPath);
+ break;
+ case SyncJournalDb::SelectiveSyncListType::SelectiveSyncBlackList:
+ folder->blacklistPath(relPath);
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+}
+
+void FolderMan::whitelistFolderPath(const QString &path)
+{
+ addFolderToSelectiveSyncList(path, SyncJournalDb::SelectiveSyncListType::SelectiveSyncWhiteList);
+}
+
+void FolderMan::blacklistFolderPath(const QString &path)
+{
+ addFolderToSelectiveSyncList(path, SyncJournalDb::SelectiveSyncListType::SelectiveSyncBlackList);
+}
+
QStringList FolderMan::findFileInLocalFolders(const QString &relPath, const AccountPtr acc)
{
QStringList re;
diff --git a/src/gui/folderman.h b/src/gui/folderman.h
index 6c236ce8e8a4..684ba29829e4 100644
--- a/src/gui/folderman.h
+++ b/src/gui/folderman.h
@@ -104,6 +104,10 @@ class FolderMan : public QObject
/** Returns the folder which the file or directory stored in path is in */
Folder *folderForPath(const QString &path);
+ // Takes local file paths and finds the corresponding folder, adding to correct selective sync list
+ void whitelistFolderPath(const QString &path);
+ void blacklistFolderPath(const QString &path);
+
/**
* returns a list of local files that exist on the local harddisk for an
* incoming relative server path. The method checks with all existing sync
@@ -354,6 +358,8 @@ private slots:
[[nodiscard]] bool isSwitchToVfsNeeded(const FolderDefinition &folderDefinition) const;
+ void addFolderToSelectiveSyncList(const QString &path, const SyncJournalDb::SelectiveSyncListType list);
+
QSet _disabledFolders;
Folder::Map _folderMap;
QString _folderConfigPath;
diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp
index 8a92b291ab67..061efc8624b0 100644
--- a/src/gui/folderstatusmodel.cpp
+++ b/src/gui/folderstatusmodel.cpp
@@ -13,12 +13,13 @@
*/
#include "folderstatusmodel.h"
-#include "folderman.h"
#include "accountstate.h"
#include "common/asserts.h"
-#include
-#include
+#include "common/utility.h"
+#include "folderman.h"
#include "folderstatusdelegate.h"
+#include
+#include
#include
#include
@@ -703,9 +704,7 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
parentInfo->_fetched = true;
QUrl url = parentInfo->_folder->remoteUrl();
- QString pathToRemove = url.path();
- if (!pathToRemove.endsWith('/'))
- pathToRemove += '/';
+ const auto pathToRemove = Utility::trailingSlashPath(url.path());
QStringList selectiveSyncBlackList;
bool ok1 = true;
diff --git a/src/gui/folderwatcher_win.h b/src/gui/folderwatcher_win.h
index d72665f0607d..6c4a261e0e05 100644
--- a/src/gui/folderwatcher_win.h
+++ b/src/gui/folderwatcher_win.h
@@ -15,8 +15,9 @@
#ifndef MIRALL_FOLDERWATCHER_WIN_H
#define MIRALL_FOLDERWATCHER_WIN_H
-#include
+#include "common/utility.h"
#include
+#include
#include
namespace OCC {
@@ -33,7 +34,7 @@ class WatcherThread : public QThread
public:
WatcherThread(const QString &path)
: QThread()
- , _path(path + (path.endsWith(QLatin1Char('/')) ? QString() : QStringLiteral("/")))
+ , _path(Utility::trailingSlashPath(path))
, _directory(0)
, _resultEvent(0)
, _stopEvent(0)
diff --git a/src/gui/generalsettings.cpp b/src/gui/generalsettings.cpp
index 0296c7083280..394d13c0d977 100644
--- a/src/gui/generalsettings.cpp
+++ b/src/gui/generalsettings.cpp
@@ -186,6 +186,8 @@ GeneralSettings::GeneralSettings(QWidget *parent)
connect(_ui->crashreporterCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::saveMiscSettings);
connect(_ui->newFolderLimitCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::saveMiscSettings);
connect(_ui->newFolderLimitSpinBox, static_cast(&QSpinBox::valueChanged), this, &GeneralSettings::saveMiscSettings);
+ connect(_ui->existingFolderLimitCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::saveMiscSettings);
+ connect(_ui->stopExistingFolderNowBigSyncCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::saveMiscSettings);
connect(_ui->newExternalStorage, &QAbstractButton::toggled, this, &GeneralSettings::saveMiscSettings);
connect(_ui->moveFilesToTrashCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::saveMiscSettings);
@@ -261,6 +263,12 @@ void GeneralSettings::loadMiscSettings()
auto newFolderLimit = cfgFile.newBigFolderSizeLimit();
_ui->newFolderLimitCheckBox->setChecked(newFolderLimit.first);
_ui->newFolderLimitSpinBox->setValue(newFolderLimit.second);
+ _ui->existingFolderLimitCheckBox->setEnabled(_ui->newFolderLimitCheckBox->isChecked());
+ _ui->existingFolderLimitCheckBox->setChecked(_ui->newFolderLimitCheckBox->isChecked() && cfgFile.notifyExistingFoldersOverLimit());
+ _ui->stopExistingFolderNowBigSyncCheckBox->setEnabled(_ui->existingFolderLimitCheckBox->isChecked());
+ _ui->stopExistingFolderNowBigSyncCheckBox->setChecked(_ui->existingFolderLimitCheckBox->isChecked() && cfgFile.stopSyncingExistingFoldersOverLimit());
+ _ui->newExternalStorage->setChecked(cfgFile.confirmExternalStorage());
+ _ui->monoIconsCheckBox->setChecked(cfgFile.monoIcons());
}
#if defined(BUILD_UPDATER)
@@ -274,8 +282,12 @@ void GeneralSettings::slotUpdateInfo()
}
if (updater) {
- connect(_ui->updateButton, &QAbstractButton::clicked, this,
- &GeneralSettings::slotUpdateCheckNow, Qt::UniqueConnection);
+ connect(_ui->updateButton,
+ &QAbstractButton::clicked,
+ this,
+
+ &GeneralSettings::slotUpdateCheckNow,
+ Qt::UniqueConnection);
connect(_ui->autoCheckForUpdatesCheckBox, &QAbstractButton::toggled, this,
&GeneralSettings::slotToggleAutoUpdateCheck, Qt::UniqueConnection);
_ui->autoCheckForUpdatesCheckBox->setChecked(ConfigFile().autoUpdateCheck());
@@ -422,14 +434,22 @@ void GeneralSettings::saveMiscSettings()
ConfigFile cfgFile;
- const auto monoIconsChecked = _ui->monoIconsCheckBox->isChecked();
- cfgFile.setMonoIcons(monoIconsChecked);
- Theme::instance()->setSystrayUseMonoIcons(monoIconsChecked);
+ const auto useMonoIcons = _ui->monoIconsCheckBox->isChecked();
+ const auto newFolderLimitEnabled = _ui->newFolderLimitCheckBox->isChecked();
+ const auto existingFolderLimitEnabled = newFolderLimitEnabled && _ui->existingFolderLimitCheckBox->isChecked();
+ const auto stopSyncingExistingFoldersOverLimit = existingFolderLimitEnabled && _ui->stopExistingFolderNowBigSyncCheckBox->isChecked();
+ Theme::instance()->setSystrayUseMonoIcons(useMonoIcons);
+ cfgFile.setMonoIcons(useMonoIcons);
cfgFile.setCrashReporter(_ui->crashreporterCheckBox->isChecked());
- cfgFile.setNewBigFolderSizeLimit(_ui->newFolderLimitCheckBox->isChecked(), _ui->newFolderLimitSpinBox->value());
- cfgFile.setConfirmExternalStorage(_ui->newExternalStorage->isChecked());
cfgFile.setMoveToTrash(_ui->moveFilesToTrashCheckBox->isChecked());
+ cfgFile.setNewBigFolderSizeLimit(newFolderLimitEnabled, _ui->newFolderLimitSpinBox->value());
+ cfgFile.setConfirmExternalStorage(_ui->newExternalStorage->isChecked());
+ cfgFile.setNotifyExistingFoldersOverLimit(existingFolderLimitEnabled);
+ cfgFile.setStopSyncingExistingFoldersOverLimit(stopSyncingExistingFoldersOverLimit);
+
+ _ui->existingFolderLimitCheckBox->setEnabled(newFolderLimitEnabled);
+ _ui->stopExistingFolderNowBigSyncCheckBox->setEnabled(existingFolderLimitEnabled);
}
void GeneralSettings::slotToggleLaunchOnStartup(bool enable)
diff --git a/src/gui/generalsettings.ui b/src/gui/generalsettings.ui
index ee82031c911e..dad84c66c7ea 100644
--- a/src/gui/generalsettings.ui
+++ b/src/gui/generalsettings.ui
@@ -6,8 +6,8 @@
0
0
- 556
- 613
+ 581
+ 663
@@ -222,46 +222,94 @@
-
-
-
-
-
-
- Ask for confirmation before synchronizing folders larger than
-
-
- true
-
-
-
+
+
+ 0
+
-
-
-
- 999999
-
-
- 99
-
-
+
+
-
+
+
+ Ask for confirmation before synchronizing new folders larger than
+
+
+ true
+
+
+
+ -
+
+
+ 999999
+
+
+ 99
+
+
+
+ -
+
+
+ MB
+
+
+
+
-
-
-
- MB
-
-
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 20
+
+
+
+
+ -
+
+
+ Notify when synchronised folders grow larger than specified limit
+
+
+
+
-
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Automatically disable synchronisation of folders that overcome limit
+
+
+
+
diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp
index 2ea725564937..9d1d5856f340 100644
--- a/src/gui/owncloudgui.cpp
+++ b/src/gui/owncloudgui.cpp
@@ -107,12 +107,8 @@ ownCloudGui::ownCloudGui(Application *parent)
connect(folderMan, &FolderMan::folderSyncStateChange,
this, &ownCloudGui::slotSyncStateChange);
- connect(Logger::instance(), &Logger::guiLog,
- this, &ownCloudGui::slotShowTrayMessage);
- connect(Logger::instance(), &Logger::optionalGuiLog,
- this, &ownCloudGui::slotShowOptionalTrayMessage);
- connect(Logger::instance(), &Logger::guiMessage,
- this, &ownCloudGui::slotShowGuiMessage);
+ connect(Logger::instance(), &Logger::guiLog, this, &ownCloudGui::slotShowTrayMessage);
+ connect(Logger::instance(), &Logger::guiMessage, this, &ownCloudGui::slotShowGuiMessage);
qmlRegisterType("com.nextcloud.desktopclient", 1, 0, "SyncStatusSummary");
qmlRegisterType("com.nextcloud.desktopclient", 1, 0, "EmojiModel");
@@ -426,11 +422,6 @@ void ownCloudGui::slotShowTrayUpdateMessage(const QString &title, const QString
}
}
-void ownCloudGui::slotShowOptionalTrayMessage(const QString &title, const QString &msg)
-{
- slotShowTrayMessage(title, msg);
-}
-
/*
* open the folder with the given Alias
*/
diff --git a/src/gui/owncloudgui.h b/src/gui/owncloudgui.h
index 41c7895ef686..8315fe228caa 100644
--- a/src/gui/owncloudgui.h
+++ b/src/gui/owncloudgui.h
@@ -76,7 +76,6 @@ public slots:
void slotComputeOverallSyncStatus();
void slotShowTrayMessage(const QString &title, const QString &msg);
void slotShowTrayUpdateMessage(const QString &title, const QString &msg, const QUrl &webUrl);
- void slotShowOptionalTrayMessage(const QString &title, const QString &msg);
void slotFolderOpenAction(const QString &alias);
void slotUpdateProgress(const QString &folder, const OCC::ProgressInfo &progress);
void slotShowGuiMessage(const QString &title, const QString &message);
diff --git a/src/gui/owncloudsetupwizard.cpp b/src/gui/owncloudsetupwizard.cpp
index 551fc487dc72..b0354b58e03b 100644
--- a/src/gui/owncloudsetupwizard.cpp
+++ b/src/gui/owncloudsetupwizard.cpp
@@ -20,19 +20,20 @@
#include
#include
-#include "wizard/owncloudwizardcommon.h"
-#include "wizard/owncloudwizard.h"
-#include "owncloudsetupwizard.h"
-#include "configfile.h"
-#include "folderman.h"
#include "accessmanager.h"
#include "account.h"
-#include "networkjobs.h"
-#include "sslerrordialog.h"
#include "accountmanager.h"
#include "clientproxy.h"
+#include "common/utility.h"
+#include "configfile.h"
#include "filesystem.h"
+#include "folderman.h"
+#include "networkjobs.h"
#include "owncloudgui.h"
+#include "owncloudsetupwizard.h"
+#include "sslerrordialog.h"
+#include "wizard/owncloudwizard.h"
+#include "wizard/owncloudwizardcommon.h"
#include "creds/credentialsfactory.h"
#include "creds/abstractcredentials.h"
@@ -125,13 +126,7 @@ void OwncloudSetupWizard::startWizard()
}
// remember the local folder to compare later if it changed, but clean first
- QString lf = QDir::fromNativeSeparators(localFolder);
- if (!lf.endsWith(QLatin1Char('/'))) {
- lf.append(QLatin1Char('/'));
- }
-
- _initLocalFolder = lf;
-
+ _initLocalFolder = Utility::trailingSlashPath(QDir::fromNativeSeparators(localFolder));
_ocWizard->setRemoteFolder(_remoteFolder);
const auto isEnforcedServerSetup =
diff --git a/src/gui/selectivesyncdialog.cpp b/src/gui/selectivesyncdialog.cpp
index 4f4ca1ec1fc2..d83d1b9cdf7b 100644
--- a/src/gui/selectivesyncdialog.cpp
+++ b/src/gui/selectivesyncdialog.cpp
@@ -12,23 +12,23 @@
* for more details.
*/
#include "selectivesyncdialog.h"
-#include "folder.h"
#include "account.h"
+#include "common/utility.h"
+#include "configfile.h"
+#include "folder.h"
+#include "folderman.h"
#include "networkjobs.h"
#include "theme.h"
-#include "folderman.h"
-#include "configfile.h"
#include
-#include
-#include
-#include
#include
#include
-#include
+#include
#include
+#include
+#include
#include
-#include
#include
+#include
namespace OCC {
@@ -203,10 +203,7 @@ void SelectiveSyncWidget::slotUpdateDirectories(QStringList list)
auto *root = dynamic_cast(_folderTree->topLevelItem(0));
QUrl url = _account->davUrl();
- QString pathToRemove = url.path();
- if (!pathToRemove.endsWith('/')) {
- pathToRemove.append('/');
- }
+ auto pathToRemove = Utility::trailingSlashPath(url.path());
pathToRemove.append(_folderPath);
if (!_folderPath.isEmpty())
pathToRemove.append('/');
diff --git a/src/gui/socketapi/socketuploadjob.cpp b/src/gui/socketapi/socketuploadjob.cpp
index b8ee787a1145..6e4ebc968909 100644
--- a/src/gui/socketapi/socketuploadjob.cpp
+++ b/src/gui/socketapi/socketuploadjob.cpp
@@ -13,6 +13,7 @@
*/
#include "socketuploadjob.h"
+#include "common/utility.h"
#include "socketapi_p.h"
#include "accountmanager.h"
@@ -55,7 +56,7 @@ SocketUploadJob::SocketUploadJob(const QSharedPointer &job)
SyncOptions opt;
opt.fillFromEnvironmentVariables();
opt.verifyChunkSizes();
- _engine = new SyncEngine(account->account(), _localPath.endsWith(QLatin1Char('/')) ? _localPath : _localPath + QLatin1Char('/'), opt, _remotePath, _db);
+ _engine = new SyncEngine(account->account(), Utility::trailingSlashPath(_localPath), opt, _remotePath, _db);
_engine->setParent(_db);
connect(_engine, &OCC::SyncEngine::itemCompleted, this, [this](const OCC::SyncFileItemPtr item) {
diff --git a/src/gui/tray/activitydata.h b/src/gui/tray/activitydata.h
index 29df3c878cc3..e613ad5bf5dd 100644
--- a/src/gui/tray/activitydata.h
+++ b/src/gui/tray/activitydata.h
@@ -45,6 +45,9 @@ class ActivityLink
public:
static ActivityLink createFomJsonObject(const QJsonObject &obj);
+ static constexpr auto WhitelistFolderVerb = "WHITELIST_FOLDER";
+ static constexpr auto BlacklistFolderVerb = "BLACKLIST_FOLDER";
+
public:
QString _imageSource;
QString _imageSourceHovered;
diff --git a/src/gui/tray/activitylistmodel.cpp b/src/gui/tray/activitylistmodel.cpp
index f6c28a85d359..b43cb45c016f 100644
--- a/src/gui/tray/activitylistmodel.cpp
+++ b/src/gui/tray/activitylistmodel.cpp
@@ -836,6 +836,14 @@ void ActivityListModel::slotTriggerAction(const int activityIndex, const int act
(activity._syncFileItemStatus == SyncFileItem::Conflict || activity._syncFileItemStatus == SyncFileItem::FileNameClash)) {
slotTriggerDefaultAction(activityIndex);
return;
+ } else if (action._verb == ActivityLink::WhitelistFolderVerb && !activity._file.isEmpty()) { // _folder == folder alias/name, _file == folder/file path
+ FolderMan::instance()->whitelistFolderPath(activity._file);
+ removeActivityFromActivityList(activity);
+ return;
+ } else if (action._verb == ActivityLink::BlacklistFolderVerb && !activity._file.isEmpty()) {
+ FolderMan::instance()->blacklistFolderPath(activity._file);
+ removeActivityFromActivityList(activity);
+ return;
}
emit sendNotificationRequest(activity._accName, action._link, action._verb, activityIndex);
@@ -863,13 +871,16 @@ QVariantList ActivityListModel::convertLinksToActionButtons(const Activity &acti
{
QVariantList customList;
- for (const auto &activityLink : activity._links) {
- if (!activityLink._primary) {
+ for (int i = 0; i < activity._links.size() && static_cast(i) <= maxActionButtons(); ++i) {
+ const auto activityLink = activity._links[i];
+
+ // Use the isDismissable model role to present custom dismiss button if needed
+ // Also don't show "View chat" for talk activities, default action will open chat anyway
+ if (activityLink._verb == "DELETE" || (activityLink._verb == "WEB" && activity._objectType == "chat")) {
continue;
}
customList << ActivityListModel::convertLinkToActionButton(activityLink);
- break;
}
return customList;
@@ -893,16 +904,16 @@ QVariant ActivityListModel::convertLinkToActionButton(const OCC::ActivityLink &a
QVariantList ActivityListModel::convertLinksToMenuEntries(const Activity &activity)
{
+ if (static_cast(activity._links.size()) <= maxActionButtons()) {
+ return {};
+ }
+
QVariantList customList;
- if (static_cast(activity._links.size()) > maxActionButtons()) {
- for (int i = 0; i < activity._links.size(); ++i) {
- const auto &activityLink = activity._links[i];
- if (!activityLink._primary) {
- customList << QVariantMap{
- {QStringLiteral("actionIndex"), i}, {QStringLiteral("label"), activityLink._label}};
- }
- }
+ for (int i = maxActionButtons(); i < activity._links.size(); ++i) {
+ const auto activityLinkLabel = activity._links[i]._label;
+ const auto menuEntry = QVariantMap{{"actionIndex", i}, {"label", activityLinkLabel}};
+ customList << menuEntry;
}
return customList;
diff --git a/src/gui/tray/notificationhandler.cpp b/src/gui/tray/notificationhandler.cpp
index 62ff2f32832b..a1321d4c7dc0 100644
--- a/src/gui/tray/notificationhandler.cpp
+++ b/src/gui/tray/notificationhandler.cpp
@@ -149,14 +149,6 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
a._links.insert(al._primary? 0 : a._links.size(), al);
}
- if (a._links.isEmpty()) {
- ActivityLink dismissLink;
- dismissLink._label = tr("Dismiss");
- dismissLink._verb = "DELETE";
- dismissLink._primary = false;
- a._links.insert(0, dismissLink);
- }
-
QUrl link(json.value("link").toString());
if (!link.isEmpty()) {
if (link.host().isEmpty()) {
diff --git a/src/gui/tray/usermodel.cpp b/src/gui/tray/usermodel.cpp
index e9a5ed73ae43..896ff85e4d86 100644
--- a/src/gui/tray/usermodel.cpp
+++ b/src/gui/tray/usermodel.cpp
@@ -733,6 +733,16 @@ void User::slotAddErrorToGui(const QString &folderAlias, const SyncFileItem::Sta
}
}
+void User::slotAddNotification(const Folder *folder, const Activity &activity)
+{
+ if (!isActivityOfCurrentAccount(folder) || _notifiedNotifications.contains(activity._id)) {
+ return;
+ }
+
+ _notifiedNotifications.insert(activity._id);
+ _activityModel->addNotificationToActivityList(activity);
+}
+
bool User::isActivityOfCurrentAccount(const Folder *folder) const
{
return folder->accountState() == _account.data();
@@ -1526,6 +1536,21 @@ User *UserModel::currentUser() const
return _users[currentUserId()];
}
+User *UserModel::findUserForAccount(AccountState *account) const
+{
+ Q_ASSERT(account);
+
+ const auto it = std::find_if(_users.cbegin(), _users.cend(), [account](const User *user) {
+ return user->account()->id() == account->account()->id();
+ });
+
+ if (it == _users.cend()) {
+ return nullptr;
+ }
+
+ return *it;
+}
+
int UserModel::findUserIdForAccount(AccountState *account) const
{
const auto it = std::find_if(std::cbegin(_users), std::cend(_users), [=](const User *user) {
diff --git a/src/gui/tray/usermodel.h b/src/gui/tray/usermodel.h
index bfeaff46d248..c92e98b7c938 100644
--- a/src/gui/tray/usermodel.h
+++ b/src/gui/tray/usermodel.h
@@ -8,12 +8,13 @@
#include
#include
-#include "activitylistmodel.h"
#include "accountfwd.h"
#include "accountmanager.h"
+#include "activitydata.h"
+#include "activitylistmodel.h"
#include "folderman.h"
-#include "userstatusselectormodel.h"
#include "userstatusconnector.h"
+#include "userstatusselectormodel.h"
#include
namespace OCC {
@@ -118,6 +119,7 @@ public slots:
void slotProgressInfo(const QString &folder, const OCC::ProgressInfo &progress);
void slotAddError(const QString &folderAlias, const QString &message, OCC::ErrorCategory category);
void slotAddErrorToGui(const QString &folderAlias, const OCC::SyncFileItem::Status status, const QString &errorMessage, const QString &subject, const OCC::ErrorCategory category);
+ void slotAddNotification(const OCC::Folder *folder, const OCC::Activity &activity);
void slotNotificationRequestFinished(int statusCode);
void slotNotifyNetworkError(QNetworkReply *reply);
void slotEndNotificationRequest(int replyCode);
@@ -210,8 +212,8 @@ class UserModel : public QAbstractListModel
[[nodiscard]] QImage avatarById(const int id) const;
[[nodiscard]] User *currentUser() const;
-
- int findUserIdForAccount(AccountState *account) const;
+ [[nodiscard]] User *findUserForAccount(AccountState *account) const;
+ [[nodiscard]] int findUserIdForAccount(AccountState *account) const;
Q_INVOKABLE int numUsers();
Q_INVOKABLE QString currentUserServer();
diff --git a/src/gui/wizard/webviewpage.cpp b/src/gui/wizard/webviewpage.cpp
index 0b5ccd9ca59f..8e492ea27605 100644
--- a/src/gui/wizard/webviewpage.cpp
+++ b/src/gui/wizard/webviewpage.cpp
@@ -6,10 +6,11 @@
#include
#include
-#include "owncloudwizard.h"
+#include "account.h"
+#include "common/utility.h"
#include "creds/webflowcredentials.h"
+#include "owncloudwizard.h"
#include "webview.h"
-#include "account.h"
namespace OCC {
@@ -46,11 +47,7 @@ void WebViewPage::initializePage() {
if (_ocWizard->registration()) {
url = "https://nextcloud.com/register";
} else {
- url = _ocWizard->ocUrl();
- if (!url.endsWith('/')) {
- url += "/";
- }
- url += "index.php/login/flow";
+ url = Utility::trailingSlashPath(_ocWizard->ocUrl()) + "index.php/login/flow";
}
qCInfo(lcWizardWebiewPage()) << "Url to auth at: " << url;
_webView->setUrl(QUrl(url));
diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp
index fd8d938d643f..5931929d559d 100644
--- a/src/libsync/configfile.cpp
+++ b/src/libsync/configfile.cpp
@@ -99,6 +99,8 @@ static constexpr char downloadLimitC[] = "BWLimit/downloadLimit";
static constexpr char newBigFolderSizeLimitC[] = "newBigFolderSizeLimit";
static constexpr char useNewBigFolderSizeLimitC[] = "useNewBigFolderSizeLimit";
+static constexpr char notifyExistingFoldersOverLimitC[] = "notifyExistingFoldersOverLimit";
+static constexpr char stopSyncingExistingFoldersOverLimitC[] = "stopSyncingExistingFoldersOverLimit";
static constexpr char confirmExternalStorageC[] = "confirmExternalStorage";
static constexpr char moveToTrashC[] = "moveToTrash";
@@ -362,11 +364,8 @@ QString ConfigFile::configPath() const
_confDir = newLocation;
}
}
- QString dir = _confDir;
- if (!dir.endsWith(QLatin1Char('/')))
- dir.append(QLatin1Char('/'));
- return dir;
+ return Utility::trailingSlashPath(_confDir);
}
static const QLatin1String exclFile("sync-exclude.lst");
@@ -959,6 +958,29 @@ bool ConfigFile::useNewBigFolderSizeLimit() const
return getPolicySetting(QLatin1String(useNewBigFolderSizeLimitC), fallback).toBool();
}
+bool ConfigFile::notifyExistingFoldersOverLimit() const
+{
+ const auto fallback = getValue(notifyExistingFoldersOverLimitC, {}, false);
+ return getPolicySetting(QString(notifyExistingFoldersOverLimitC), fallback).toBool();
+}
+
+void ConfigFile::setNotifyExistingFoldersOverLimit(const bool notify)
+{
+ setValue(notifyExistingFoldersOverLimitC, notify);
+}
+
+bool ConfigFile::stopSyncingExistingFoldersOverLimit() const
+{
+ const auto notifyExistingBigEnabled = notifyExistingFoldersOverLimit();
+ const auto fallback = getValue(stopSyncingExistingFoldersOverLimitC, {}, notifyExistingBigEnabled);
+ return getPolicySetting(QString(stopSyncingExistingFoldersOverLimitC), fallback).toBool();
+}
+
+void ConfigFile::setStopSyncingExistingFoldersOverLimit(const bool stopSyncing)
+{
+ setValue(stopSyncingExistingFoldersOverLimitC, stopSyncing);
+}
+
void ConfigFile::setConfirmExternalStorage(bool isChecked)
{
setValue(confirmExternalStorageC, isChecked);
diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h
index 5d0ee5460fce..6906c21b7600 100644
--- a/src/libsync/configfile.h
+++ b/src/libsync/configfile.h
@@ -141,6 +141,10 @@ class OWNCLOUDSYNC_EXPORT ConfigFile
/** [checked, size in MB] **/
[[nodiscard]] QPair newBigFolderSizeLimit() const;
void setNewBigFolderSizeLimit(bool isChecked, qint64 mbytes);
+ [[nodiscard]] bool notifyExistingFoldersOverLimit() const;
+ void setNotifyExistingFoldersOverLimit(const bool notify);
+ [[nodiscard]] bool stopSyncingExistingFoldersOverLimit() const;
+ void setStopSyncingExistingFoldersOverLimit(const bool stopSyncing);
[[nodiscard]] bool useNewBigFolderSizeLimit() const;
[[nodiscard]] bool confirmExternalStorage() const;
void setConfirmExternalStorage(bool);
diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp
index dcabd45e11c7..8d24b56f633a 100644
--- a/src/libsync/discovery.cpp
+++ b/src/libsync/discovery.cpp
@@ -437,7 +437,7 @@ void ProcessDirectoryJob::checkAndUpdateSelectiveSyncListsForE2eeFolders(const Q
{
bool ok = false;
- const auto pathWithTrailingSpace = path.endsWith(QLatin1Char('/')) ? path : path + QLatin1Char('/');
+ const auto pathWithTrailingSpace = Utility::trailingSlashPath(path);
auto blackListSet = _discoveryData->_statedb->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok).toSet();
blackListSet.insert(pathWithTrailingSpace);
@@ -457,8 +457,8 @@ void ProcessDirectoryJob::processFile(PathTuple path,
const LocalInfo &localEntry, const RemoteInfo &serverEntry,
const SyncJournalFileRecord &dbEntry)
{
- const char *hasServer = serverEntry.isValid() ? "true" : _queryServer == ParentNotChanged ? "db" : "false";
- const char *hasLocal = localEntry.isValid() ? "true" : _queryLocal == ParentNotChanged ? "db" : "false";
+ const auto hasServer = serverEntry.isValid() ? "true" : _queryServer == ParentNotChanged ? "db" : "false";
+ const auto hasLocal = localEntry.isValid() ? "true" : _queryLocal == ParentNotChanged ? "db" : "false";
const auto serverFileIsLocked = (serverEntry.isValid() ? (serverEntry.locked == SyncFileItem::LockStatus::LockedItem ? "locked" : "not locked") : "");
const auto localFileIsLocked = dbEntry._lockstate._locked ? "locked" : "not locked";
qCInfo(lcDisco).nospace() << "Processing " << path._original
@@ -488,7 +488,7 @@ void ProcessDirectoryJob::processFile(PathTuple path,
return; // Ignore this.
}
- auto item = SyncFileItem::fromSyncJournalFileRecord(dbEntry);
+ const auto item = SyncFileItem::fromSyncJournalFileRecord(dbEntry);
item->_file = path._target;
item->_originalFile = path._original;
item->_previousSize = dbEntry._fileSize;
@@ -559,9 +559,58 @@ static bool computeLocalChecksum(const QByteArray &header, const QString &path,
return false;
}
-void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
- const SyncFileItemPtr &item, PathTuple path, const LocalInfo &localEntry,
- const RemoteInfo &serverEntry, const SyncJournalFileRecord &dbEntry)
+void ProcessDirectoryJob::postProcessServerNew(const SyncFileItemPtr &item,
+ PathTuple &path,
+ const LocalInfo &localEntry,
+ const RemoteInfo &serverEntry,
+ const SyncJournalFileRecord &dbEntry)
+{
+ if (item->isDirectory()) {
+ _pendingAsyncJobs++;
+ _discoveryData->checkSelectiveSyncNewFolder(path._server,
+ serverEntry.remotePerm,
+ [=](bool result) {
+ --_pendingAsyncJobs;
+ if (!result) {
+ processFileAnalyzeLocalInfo(item, path, localEntry, serverEntry, dbEntry, _queryServer);
+ }
+ QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs);
+ });
+ return;
+ }
+
+ // Turn new remote files into virtual files if the option is enabled.
+ const auto opts = _discoveryData->_syncOptions;
+ if (!localEntry.isValid() &&
+ item->_type == ItemTypeFile &&
+ opts._vfs->mode() != Vfs::Off &&
+ !FileSystem::isLnkFile(item->_file) &&
+ _pinState != PinState::AlwaysLocal &&
+ !FileSystem::isExcludeFile(item->_file)) {
+
+ item->_type = ItemTypeVirtualFile;
+ if (isVfsWithSuffix()) {
+ addVirtualFileSuffix(path._original);
+ }
+ }
+
+ if (opts._vfs->mode() != Vfs::Off && !item->_encryptedFileName.isEmpty()) {
+ // We are syncing a file for the first time (local entry is invalid) and it is encrypted file that will be virtual once synced
+ // to avoid having error of "file has changed during sync" when trying to hydrate it explicitly - we must remove Constants::e2EeTagSize bytes from the end
+ // as explicit hydration does not care if these bytes are present in the placeholder or not, but, the size must not change in the middle of the sync
+ // this way it works for both implicit and explicit hydration by making a placeholder size that does not includes encryption tag Constants::e2EeTagSize bytes
+ // another scenario - we are syncing a file which is on disk but not in the database (database was removed or file was not written there yet)
+ item->_size = serverEntry.size - Constants::e2EeTagSize;
+ }
+
+ processFileAnalyzeLocalInfo(item, path, localEntry, serverEntry, dbEntry, _queryServer);
+}
+
+void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(const SyncFileItemPtr &item,
+ PathTuple path,
+ const LocalInfo &localEntry,
+ const RemoteInfo &serverEntry,
+ const SyncJournalFileRecord &dbEntry)
{
item->_checksumHeader = serverEntry.checksumHeader;
item->_fileId = serverEntry.fileId;
@@ -593,7 +642,15 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
item->_lockEditorApp = serverEntry.lockEditorApp;
item->_lockTime = serverEntry.lockTime;
item->_lockTimeout = serverEntry.lockTimeout;
- qCDebug(lcDisco()) << item->_locked << item->_lockOwnerDisplayName << item->_lockOwnerId << item->_lockOwnerType << item->_lockEditorApp << item->_lockTime << item->_lockTimeout;
+
+ qCDebug(lcDisco()) << "item lock for:" << item->_file
+ << item->_locked
+ << item->_lockOwnerDisplayName
+ << item->_lockOwnerId
+ << item->_lockOwnerType
+ << item->_lockEditorApp
+ << item->_lockTime
+ << item->_lockTimeout;
// Check for missing server data
{
@@ -640,11 +697,16 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
// The file is known in the db already
if (dbEntry.isValid()) {
- const bool isDbEntryAnE2EePlaceholder = dbEntry.isVirtualFile() && !dbEntry.e2eMangledName().isEmpty();
+ const auto isDbEntryAnE2EePlaceholder = dbEntry.isVirtualFile() && !dbEntry.e2eMangledName().isEmpty();
Q_ASSERT(!isDbEntryAnE2EePlaceholder || serverEntry.size >= Constants::e2EeTagSize);
- const bool isVirtualE2EePlaceholder = isDbEntryAnE2EePlaceholder && serverEntry.size >= Constants::e2EeTagSize;
- const qint64 sizeOnServer = isVirtualE2EePlaceholder ? serverEntry.size - Constants::e2EeTagSize : serverEntry.size;
- const bool metaDataSizeNeedsUpdateForE2EeFilePlaceholder = isVirtualE2EePlaceholder && dbEntry._fileSize == serverEntry.size;
+ const auto isVirtualE2EePlaceholder = isDbEntryAnE2EePlaceholder && serverEntry.size >= Constants::e2EeTagSize;
+ const auto sizeOnServer = isVirtualE2EePlaceholder ? serverEntry.size - Constants::e2EeTagSize : serverEntry.size;
+ const auto metaDataSizeNeedsUpdateForE2EeFilePlaceholder = isVirtualE2EePlaceholder && dbEntry._fileSize == serverEntry.size;
+
+ if (serverEntry.isDirectory) {
+ // Even if over quota, continue syncing as normal for now
+ _discoveryData->checkSelectiveSyncExistingFolder(path._server);
+ }
if (serverEntry.isDirectory != dbEntry.isDirectory()) {
// If the type of the entity changed, it's like NEW, but
@@ -664,6 +726,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
item->_direction = SyncFileItem::Down;
item->_modtime = serverEntry.modtime;
item->_size = sizeOnServer;
+
if (serverEntry.isDirectory) {
ENFORCE(dbEntry.isDirectory());
item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
@@ -678,7 +741,8 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
<< "serverEntry.isDirectory:" << serverEntry.isDirectory
<< "dbEntry.isDirectory:" << dbEntry.isDirectory();
}
- } else if (dbEntry._modtime != serverEntry.modtime && localEntry.size == serverEntry.size && dbEntry._fileSize == serverEntry.size && dbEntry._etag == serverEntry.etag) {
+ } else if (dbEntry._modtime != serverEntry.modtime && localEntry.size == serverEntry.size && dbEntry._fileSize == serverEntry.size
+ && dbEntry._etag == serverEntry.etag) {
item->_direction = SyncFileItem::Down;
item->_modtime = serverEntry.modtime;
item->_size = sizeOnServer;
@@ -696,7 +760,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
// to update a placeholder with corrected size (-16 Bytes)
// or, maybe, add a flag to the database - vfsE2eeSizeCorrected? if it is not set - subtract it from the placeholder's size and re-create/update a placeholder?
const QueryMode serverQueryMode = [this, &dbEntry, &serverEntry]() {
- const bool isVfsModeOn = _discoveryData && _discoveryData->_syncOptions._vfs && _discoveryData->_syncOptions._vfs->mode() != Vfs::Off;
+ const auto isVfsModeOn = _discoveryData && _discoveryData->_syncOptions._vfs && _discoveryData->_syncOptions._vfs->mode() != Vfs::Off;
if (isVfsModeOn && dbEntry.isDirectory() && dbEntry.isE2eEncrypted()) {
qint64 localFolderSize = 0;
const auto listFilesCallback = [&localFolderSize](const OCC::SyncJournalFileRecord &record) {
@@ -709,7 +773,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
}
};
- const bool listFilesSucceeded = _discoveryData->_statedb->listFilesInPath(dbEntry.path().toUtf8(), listFilesCallback);
+ const auto listFilesSucceeded = _discoveryData->_statedb->listFilesInPath(dbEntry.path().toUtf8(), listFilesCallback);
if (listFilesSucceeded && localFolderSize != 0 && localFolderSize == serverEntry.sizeOfFolder) {
qCInfo(lcDisco) << "Migration of E2EE folder " << dbEntry.path() << " from older version to the one, supporting the implicit VFS hydration.";
@@ -735,7 +799,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
item->_modtime = serverEntry.modtime;
item->_size = serverEntry.size;
- auto conflictRecord = _discoveryData->_statedb->caseConflictRecordByBasePath(item->_file);
+ const auto conflictRecord = _discoveryData->_statedb->caseConflictRecordByBasePath(item->_file);
if (conflictRecord.isValid() && QString::fromUtf8(conflictRecord.path).contains(QStringLiteral("(case clash from"))) {
qCInfo(lcDisco) << "should ignore" << item->_file << "has already a case clash conflict record" << conflictRecord.path;
@@ -744,46 +808,9 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
return;
}
- auto postProcessServerNew = [=]() mutable {
- if (item->isDirectory()) {
- _pendingAsyncJobs++;
- _discoveryData->checkSelectiveSyncNewFolder(path._server, serverEntry.remotePerm,
- [=](bool result) {
- --_pendingAsyncJobs;
- if (!result) {
- processFileAnalyzeLocalInfo(item, path, localEntry, serverEntry, dbEntry, _queryServer);
- }
- QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs);
- });
- return;
- }
- // Turn new remote files into virtual files if the option is enabled.
- auto &opts = _discoveryData->_syncOptions;
- if (!localEntry.isValid()
- && item->_type == ItemTypeFile
- && opts._vfs->mode() != Vfs::Off
- && !FileSystem::isLnkFile(item->_file)
- && _pinState != PinState::AlwaysLocal
- && !FileSystem::isExcludeFile(item->_file)) {
- item->_type = ItemTypeVirtualFile;
- if (isVfsWithSuffix())
- addVirtualFileSuffix(path._original);
- }
-
- if (opts._vfs->mode() != Vfs::Off && !item->_encryptedFileName.isEmpty()) {
- // We are syncing a file for the first time (local entry is invalid) and it is encrypted file that will be virtual once synced
- // to avoid having error of "file has changed during sync" when trying to hydrate it explicitly - we must remove Constants::e2EeTagSize bytes from the end
- // as explicit hydration does not care if these bytes are present in the placeholder or not, but, the size must not change in the middle of the sync
- // this way it works for both implicit and explicit hydration by making a placeholder size that does not includes encryption tag Constants::e2EeTagSize bytes
- // another scenario - we are syncing a file which is on disk but not in the database (database was removed or file was not written there yet)
- item->_size = serverEntry.size - Constants::e2EeTagSize;
- }
- processFileAnalyzeLocalInfo(item, path, localEntry, serverEntry, dbEntry, _queryServer);
- };
-
// Potential NEW/NEW conflict is handled in AnalyzeLocal
if (localEntry.isValid()) {
- postProcessServerNew();
+ postProcessServerNew(item, path, localEntry, serverEntry, dbEntry);
return;
}
@@ -831,7 +858,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
}
// Now we know there is a sane rename candidate.
- QString originalPath = base.path();
+ const auto originalPath = base.path();
if (_discoveryData->isRenamed(originalPath)) {
qCInfo(lcDisco, "folder already has a rename entry, skipping");
@@ -847,7 +874,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
return;
}
- QString originalPathAdjusted = _discoveryData->adjustRenamedPath(originalPath, SyncFileItem::Up);
+ const auto originalPathAdjusted = _discoveryData->adjustRenamedPath(originalPath, SyncFileItem::Up);
if (!base.isDirectory()) {
csync_file_stat_t buf;
@@ -873,10 +900,10 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
item->_type = ItemTypeVirtualFile;
}
- bool wasDeletedOnServer = _discoveryData->findAndCancelDeletedJob(originalPath).first;
+ const auto wasDeletedOnServer = _discoveryData->findAndCancelDeletedJob(originalPath).first;
auto postProcessRename = [this, item, base, originalPath](PathTuple &path) {
- auto adjustedOriginalPath = _discoveryData->adjustRenamedPath(originalPath, SyncFileItem::Up);
+ const auto adjustedOriginalPath = _discoveryData->adjustRenamedPath(originalPath, SyncFileItem::Up);
_discoveryData->_renamedItemsRemote.insert(originalPath, path._target);
item->_modtime = base._modtime;
item->_inode = base._inode;
@@ -896,7 +923,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
} else {
// we need to make a request to the server to know that the original file is deleted on the server
_pendingAsyncJobs++;
- auto job = new RequestEtagJob(_discoveryData->_account, _discoveryData->_remoteFolder + originalPath, this);
+ const auto job = new RequestEtagJob(_discoveryData->_account, _discoveryData->_remoteFolder + originalPath, this);
connect(job, &RequestEtagJob::finishedWithResult, this, [=](const HttpResult &etag) mutable {
_pendingAsyncJobs--;
QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs);
@@ -904,7 +931,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
// Somehow another item claimed this original path, consider as if it existed
_discoveryData->isRenamed(originalPath)) {
// If the file exist or if there is another error, consider it is a new file.
- postProcessServerNew();
+ postProcessServerNew(item, path, localEntry, serverEntry, dbEntry);
return;
}
@@ -930,7 +957,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
}
if (item->_instruction == CSYNC_INSTRUCTION_NEW) {
- postProcessServerNew();
+ postProcessServerNew(item, path, localEntry, serverEntry, dbEntry);
return;
}
processFileAnalyzeLocalInfo(item, path, localEntry, serverEntry, dbEntry, _queryServer);
diff --git a/src/libsync/discovery.h b/src/libsync/discovery.h
index debb4572de40..eaa2657697a9 100644
--- a/src/libsync/discovery.h
+++ b/src/libsync/discovery.h
@@ -162,6 +162,12 @@ class ProcessDirectoryJob : public QObject
*/
void processFile(PathTuple, const LocalInfo &, const RemoteInfo &, const SyncJournalFileRecord &);
+ void postProcessServerNew(const SyncFileItemPtr &item,
+ PathTuple &path,
+ const LocalInfo &localEntry,
+ const RemoteInfo &serverEntry,
+ const SyncJournalFileRecord &dbEntry);
+
/// processFile helper for when remote information is available, typically flows into AnalyzeLocalInfo when done
void processFileAnalyzeRemoteInfo(const SyncFileItemPtr &item, PathTuple, const LocalInfo &, const RemoteInfo &, const SyncJournalFileRecord &);
diff --git a/src/libsync/discoveryphase.cpp b/src/libsync/discoveryphase.cpp
index 3142a2968a74..27936ad866a2 100644
--- a/src/libsync/discoveryphase.cpp
+++ b/src/libsync/discoveryphase.cpp
@@ -13,6 +13,8 @@
*/
#include "discoveryphase.h"
+#include "common/utility.h"
+#include "configfile.h"
#include "discovery.h"
#include "helpers.h"
#include "progressdispatcher.h"
@@ -39,51 +41,58 @@ namespace OCC {
Q_LOGGING_CATEGORY(lcDiscovery, "nextcloud.sync.discovery", QtInfoMsg)
-/* Given a sorted list of paths ending with '/', return whether or not the given path is within one of the paths of the list*/
-static bool findPathInList(const QStringList &list, const QString &path)
+bool DiscoveryPhase::isInSelectiveSyncBlackList(const QString &path) const
{
- Q_ASSERT(std::is_sorted(list.begin(), list.end()));
+ if (_selectiveSyncBlackList.isEmpty()) {
+ // If there is no black list, everything is allowed
+ return false;
+ }
- if (list.size() == 1 && list.first() == QLatin1String("/")) {
- // Special case for the case "/" is there, it matches everything
+ // Block if it is in the black list
+ if (SyncJournalDb::findPathInSelectiveSyncList(_selectiveSyncBlackList, path)) {
return true;
}
- QString pathSlash = path + QLatin1Char('/');
-
- // Since the list is sorted, we can do a binary search.
- // If the path is a prefix of another item or right after in the lexical order.
- auto it = std::lower_bound(list.begin(), list.end(), pathSlash);
+ return false;
+}
- if (it != list.end() && *it == pathSlash) {
- return true;
- }
+bool DiscoveryPhase::activeFolderSizeLimit() const
+{
+ return _syncOptions._newBigFolderSizeLimit > 0 && _syncOptions._vfs->mode() == Vfs::Off;
+}
- if (it == list.begin()) {
- return false;
- }
- --it;
- Q_ASSERT(it->endsWith(QLatin1Char('/'))); // Folder::setSelectiveSyncBlackList makes sure of that
- return pathSlash.startsWith(*it);
+bool DiscoveryPhase::notifyExistingFolderOverLimit() const
+{
+ return activeFolderSizeLimit() && ConfigFile().notifyExistingFoldersOverLimit();
}
-bool DiscoveryPhase::isInSelectiveSyncBlackList(const QString &path) const
+void DiscoveryPhase::checkFolderSizeLimit(const QString &path, const std::function completionCallback)
{
- if (_selectiveSyncBlackList.isEmpty()) {
- // If there is no black list, everything is allowed
- return false;
+ if (!activeFolderSizeLimit()) {
+ // no limit, everything is allowed;
+ return completionCallback(false);
}
- // Block if it is in the black list
- if (findPathInList(_selectiveSyncBlackList, path)) {
- return true;
- }
+ // do a PROPFIND to know the size of this folder
+ const auto propfindJob = new PropfindJob(_account, _remoteFolder + path, this);
+ propfindJob->setProperties(QList() << "resourcetype"
+ << "http://owncloud.org/ns:size");
- return false;
+ connect(propfindJob, &PropfindJob::finishedWithError, this, [=] {
+ return completionCallback(false);
+ });
+ connect(propfindJob, &PropfindJob::result, this, [=](const QVariantMap &values) {
+ const auto result = values.value(QLatin1String("size")).toLongLong();
+ const auto limit = _syncOptions._newBigFolderSizeLimit;
+ qCDebug(lcDiscovery) << "Folder size check complete for" << path << "result:" << result << "limit:" << limit;
+ return completionCallback(result >= limit);
+ });
+ propfindJob->start();
}
-void DiscoveryPhase::checkSelectiveSyncNewFolder(const QString &path, RemotePermissions remotePerm,
- std::function callback)
+void DiscoveryPhase::checkSelectiveSyncNewFolder(const QString &path,
+ const RemotePermissions remotePerm,
+ const std::function callback)
{
if (_syncOptions._confirmExternalStorage && _syncOptions._vfs->mode() == Vfs::Off
&& remotePerm.hasPermission(RemotePermissions::IsMounted)) {
@@ -103,41 +112,38 @@ void DiscoveryPhase::checkSelectiveSyncNewFolder(const QString &path, RemotePerm
}
// If this path or the parent is in the white list, then we do not block this file
- if (findPathInList(_selectiveSyncWhiteList, path)) {
+ if (SyncJournalDb::findPathInSelectiveSyncList(_selectiveSyncWhiteList, path)) {
return callback(false);
}
- auto limit = _syncOptions._newBigFolderSizeLimit;
- if (limit < 0 || _syncOptions._vfs->mode() != Vfs::Off) {
- // no limit, everything is allowed;
- return callback(false);
- }
-
- // do a PROPFIND to know the size of this folder
- auto propfindJob = new PropfindJob(_account, _remoteFolder + path, this);
- propfindJob->setProperties(QList() << "resourcetype"
- << "http://owncloud.org/ns:size");
- QObject::connect(propfindJob, &PropfindJob::finishedWithError,
- this, [=] { return callback(false); });
- QObject::connect(propfindJob, &PropfindJob::result, this, [=](const QVariantMap &values) {
- auto result = values.value(QLatin1String("size")).toLongLong();
- if (result >= limit) {
+ checkFolderSizeLimit(path, [this, path, callback](const bool bigFolder) {
+ if (bigFolder) {
// we tell the UI there is a new folder
emit newBigFolder(path, false);
return callback(true);
- } else {
- // it is not too big, put it in the white list (so we will not do more query for the children)
- // and and do not block.
- auto p = path;
- if (!p.endsWith(QLatin1Char('/')))
- p += QLatin1Char('/');
- _selectiveSyncWhiteList.insert(
- std::upper_bound(_selectiveSyncWhiteList.begin(), _selectiveSyncWhiteList.end(), p),
- p);
- return callback(false);
+ }
+
+ // it is not too big, put it in the white list (so we will not do more query for the children) and and do not block.
+ const auto sanitisedPath = Utility::trailingSlashPath(path);
+ _selectiveSyncWhiteList.insert(std::upper_bound(_selectiveSyncWhiteList.begin(), _selectiveSyncWhiteList.end(), sanitisedPath), sanitisedPath);
+ return callback(false);
+ });
+}
+
+void DiscoveryPhase::checkSelectiveSyncExistingFolder(const QString &path)
+{
+ // If no size limit is enforced, or if is in whitelist (explicitly allowed) or in blacklist (explicitly disallowed), do nothing.
+ if (!notifyExistingFolderOverLimit() || SyncJournalDb::findPathInSelectiveSyncList(_selectiveSyncWhiteList, path)
+ || SyncJournalDb::findPathInSelectiveSyncList(_selectiveSyncBlackList, path)) {
+ return;
+ }
+
+ checkFolderSizeLimit(path, [this, path](const bool bigFolder) {
+ if (bigFolder) {
+ // Notify the user and prompt for response.
+ emit existingFolderNowBig(path);
}
});
- propfindJob->start();
}
/* Given a path on the remote, give the path as it is when the rename is done */
@@ -244,13 +250,13 @@ void DiscoveryPhase::startJob(ProcessDirectoryJob *job)
void DiscoveryPhase::setSelectiveSyncBlackList(const QStringList &list)
{
_selectiveSyncBlackList = list;
- std::sort(_selectiveSyncBlackList.begin(), _selectiveSyncBlackList.end());
+ _selectiveSyncBlackList.sort();
}
void DiscoveryPhase::setSelectiveSyncWhiteList(const QStringList &list)
{
_selectiveSyncWhiteList = list;
- std::sort(_selectiveSyncWhiteList.begin(), _selectiveSyncWhiteList.end());
+ _selectiveSyncWhiteList.sort();
}
void DiscoveryPhase::scheduleMoreJobs()
diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h
index 36b07b874daf..900584066cd0 100644
--- a/src/libsync/discoveryphase.h
+++ b/src/libsync/discoveryphase.h
@@ -255,10 +255,19 @@ class DiscoveryPhase : public QObject
[[nodiscard]] bool isInSelectiveSyncBlackList(const QString &path) const;
+ [[nodiscard]] bool activeFolderSizeLimit() const;
+ [[nodiscard]] bool notifyExistingFolderOverLimit() const;
+
+ void checkFolderSizeLimit(const QString &path,
+ const std::function callback);
+
// Check if the new folder should be deselected or not.
// May be async. "Return" via the callback, true if the item is blacklisted
- void checkSelectiveSyncNewFolder(const QString &path, RemotePermissions rp,
- std::function callback);
+ void checkSelectiveSyncNewFolder(const QString &path,
+ const RemotePermissions rp,
+ const std::function callback);
+
+ void checkSelectiveSyncExistingFolder(const QString &path);
/** Given an original path, return the target path obtained when renaming is done.
*
@@ -316,6 +325,7 @@ class DiscoveryPhase : public QObject
// A new folder was discovered and was not synced because of the confirmation feature
void newBigFolder(const QString &folder, bool isExternal);
+ void existingFolderNowBig(const QString &folder);
/** For excluded items that don't show up in itemDiscovered()
*
diff --git a/src/libsync/logger.cpp b/src/libsync/logger.cpp
index fffbbc14c636..8cd029cf9b1e 100644
--- a/src/libsync/logger.cpp
+++ b/src/libsync/logger.cpp
@@ -100,11 +100,6 @@ void Logger::postGuiLog(const QString &title, const QString &message)
emit guiLog(title, message);
}
-void Logger::postOptionalGuiLog(const QString &title, const QString &message)
-{
- emit optionalGuiLog(title, message);
-}
-
void Logger::postGuiMessage(const QString &title, const QString &message)
{
emit guiMessage(title, message);
diff --git a/src/libsync/logger.h b/src/libsync/logger.h
index 5fd14431e5ee..bfcb2989d5db 100644
--- a/src/libsync/logger.h
+++ b/src/libsync/logger.h
@@ -42,7 +42,6 @@ class OWNCLOUDSYNC_EXPORT Logger : public QObject
static Logger *instance();
void postGuiLog(const QString &title, const QString &message);
- void postOptionalGuiLog(const QString &title, const QString &message);
void postGuiMessage(const QString &title, const QString &message);
QString logFile() const;
@@ -87,7 +86,6 @@ class OWNCLOUDSYNC_EXPORT Logger : public QObject
void guiLog(const QString &, const QString &);
void guiMessage(const QString &, const QString &);
- void optionalGuiLog(const QString &, const QString &);
public slots:
void enterNextLogFile();
diff --git a/src/libsync/owncloudpropagator.h b/src/libsync/owncloudpropagator.h
index f38291b1ed46..e837641a49bd 100644
--- a/src/libsync/owncloudpropagator.h
+++ b/src/libsync/owncloudpropagator.h
@@ -25,13 +25,14 @@
#include
#include
+#include "accountfwd.h"
+#include "bandwidthmanager.h"
+#include "common/syncjournaldb.h"
+#include "common/utility.h"
#include "csync.h"
+#include "progressdispatcher.h"
#include "syncfileitem.h"
-#include "common/syncjournaldb.h"
-#include "bandwidthmanager.h"
-#include "accountfwd.h"
#include "syncoptions.h"
-#include "progressdispatcher.h"
#include
@@ -416,15 +417,13 @@ class OWNCLOUDSYNC_EXPORT OwncloudPropagator : public QObject
bool _finishedEmited = false; // used to ensure that finished is only emitted once
public:
- OwncloudPropagator(AccountPtr account, const QString &localDir,
- const QString &remoteFolder, SyncJournalDb *progressDb,
- QSet &bulkUploadBlackList)
+ OwncloudPropagator(AccountPtr account, const QString &localDir, const QString &remoteFolder, SyncJournalDb *progressDb, QSet &bulkUploadBlackList)
: _journal(progressDb)
, _bandwidthManager(this)
, _chunkSize(10 * 1000 * 1000) // 10 MB, overridden in setSyncOptions
, _account(account)
- , _localDir((localDir.endsWith(QChar('/'))) ? localDir : localDir + '/')
- , _remoteFolder((remoteFolder.endsWith(QChar('/'))) ? remoteFolder : remoteFolder + '/')
+ , _localDir(Utility::trailingSlashPath(localDir))
+ , _remoteFolder(Utility::trailingSlashPath(remoteFolder))
, _bulkUploadBlackList(bulkUploadBlackList)
{
qRegisterMetaType("PropagatorJob::AbortType");
diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp
index 7a13f7133f30..52985fd4454a 100644
--- a/src/libsync/syncengine.cpp
+++ b/src/libsync/syncengine.cpp
@@ -617,12 +617,8 @@ void SyncEngine::startSync()
_discoveryPhase->_excludes->reloadExcludeFiles();
}
_discoveryPhase->_statedb = _journal;
- _discoveryPhase->_localDir = _localPath;
- if (!_discoveryPhase->_localDir.endsWith('/'))
- _discoveryPhase->_localDir+='/';
- _discoveryPhase->_remoteFolder = _remotePath;
- if (!_discoveryPhase->_remoteFolder.endsWith('/'))
- _discoveryPhase->_remoteFolder+='/';
+ _discoveryPhase->_localDir = Utility::trailingSlashPath(_localPath);
+ _discoveryPhase->_remoteFolder = Utility::trailingSlashPath(_remotePath);
_discoveryPhase->_syncOptions = _syncOptions;
_discoveryPhase->_shouldDiscoverLocaly = [this](const QString &path) {
const auto result = shouldDiscoverLocally(path);
@@ -656,6 +652,7 @@ void SyncEngine::startSync()
connect(_discoveryPhase.data(), &DiscoveryPhase::itemDiscovered, this, &SyncEngine::slotItemDiscovered);
connect(_discoveryPhase.data(), &DiscoveryPhase::newBigFolder, this, &SyncEngine::newBigFolder);
+ connect(_discoveryPhase.data(), &DiscoveryPhase::existingFolderNowBig, this, &SyncEngine::existingFolderNowBig);
connect(_discoveryPhase.data(), &DiscoveryPhase::fatalError, this, [this](const QString &errorString, ErrorCategory errorCategory) {
Q_EMIT syncError(errorString, errorCategory);
finalize(false);
diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h
index fce3a96b6945..d1c4ab9d5169 100644
--- a/src/libsync/syncengine.h
+++ b/src/libsync/syncengine.h
@@ -187,6 +187,8 @@ public slots:
// A new folder was discovered and was not synced because of the confirmation feature
void newBigFolder(const QString &folder, bool isExternal);
+ void existingFolderNowBig(const QString &folder);
+
/** Emitted when propagation has problems with a locked file.
*
* Forwarded from OwncloudPropagator::seenLockedFile.
diff --git a/src/libsync/theme.cpp b/src/libsync/theme.cpp
index a8bcd127365b..fb589c29d601 100644
--- a/src/libsync/theme.cpp
+++ b/src/libsync/theme.cpp
@@ -408,12 +408,12 @@ QString Theme::helpUrl() const
QString Theme::conflictHelpUrl() const
{
- auto baseUrl = helpUrl();
- if (baseUrl.isEmpty())
+ const auto baseUrl = helpUrl();
+ if (baseUrl.isEmpty()) {
return QString();
- if (!baseUrl.endsWith('/'))
- baseUrl.append('/');
- return baseUrl + QStringLiteral("conflicts.html");
+ }
+
+ return Utility::trailingSlashPath(baseUrl) + QStringLiteral("conflicts.html");
}
QString Theme::overrideServerUrl() const
diff --git a/test/syncenginetestutils.cpp b/test/syncenginetestutils.cpp
index 15c7f5a48924..82aa701664d1 100644
--- a/test/syncenginetestutils.cpp
+++ b/test/syncenginetestutils.cpp
@@ -6,9 +6,10 @@
*/
#include "syncenginetestutils.h"
-#include "httplogger.h"
#include "accessmanager.h"
+#include "common/utility.h"
#include "gui/sharepermissions.h"
+#include "httplogger.h"
#include
#include
@@ -285,11 +286,7 @@ QString FileInfo::path() const
QString FileInfo::absolutePath() const
{
- if (parentPath.endsWith(QLatin1Char('/'))) {
- return parentPath + name;
- } else {
- return parentPath + QLatin1Char('/') + name;
- }
+ return OCC::Utility::trailingSlashPath(parentPath) + name;
}
void FileInfo::fixupParentPathRecursively()
@@ -339,10 +336,7 @@ FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAcces
auto writeFileResponse = [&](const FileInfo &fileInfo) {
xml.writeStartElement(davUri, QStringLiteral("response"));
- auto url = QString::fromUtf8(QUrl::toPercentEncoding(fileInfo.absolutePath(), "/"));
- if (!url.endsWith(QChar('/'))) {
- url.append(QChar('/'));
- }
+ const auto url = OCC::Utility::trailingSlashPath(QString::fromUtf8(QUrl::toPercentEncoding(fileInfo.absolutePath(), "/")));
const auto href = OCC::Utility::concatUrlPath(prefix, url).path();
xml.writeTextElement(davUri, QStringLiteral("href"), href);
xml.writeStartElement(davUri, QStringLiteral("propstat"));
@@ -352,6 +346,12 @@ FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAcces
xml.writeStartElement(davUri, QStringLiteral("resourcetype"));
xml.writeEmptyElement(davUri, QStringLiteral("collection"));
xml.writeEndElement(); // resourcetype
+
+ auto totalSize = 0;
+ for (const auto &child : fileInfo.children.values()) {
+ totalSize += child.size;
+ }
+ xml.writeTextElement(ocUri, QStringLiteral("size"), QString::number(totalSize));
} else
xml.writeEmptyElement(davUri, QStringLiteral("resourcetype"));
@@ -1178,9 +1178,7 @@ FileInfo FakeFolder::currentLocalState()
QString FakeFolder::localPath() const
{
// SyncEngine wants a trailing slash
- if (_tempDir.path().endsWith(QLatin1Char('/')))
- return _tempDir.path();
- return _tempDir.path() + QLatin1Char('/');
+ return OCC::Utility::trailingSlashPath(_tempDir.path());
}
void FakeFolder::scheduleSync()
diff --git a/test/testactivitylistmodel.cpp b/test/testactivitylistmodel.cpp
index 7e2a734e7532..f5a69b3ea8c5 100644
--- a/test/testactivitylistmodel.cpp
+++ b/test/testactivitylistmodel.cpp
@@ -268,8 +268,7 @@ private slots:
const auto actionsLinks = index.data(OCC::ActivityListModel::ActionsLinksRole).toList();
if (!actionsLinks.isEmpty()) {
- const auto actionsLinksContextMenu =
- index.data(OCC::ActivityListModel::ActionsLinksContextMenuRole).toList();
+ const auto actionsLinksContextMenu = index.data(OCC::ActivityListModel::ActionsLinksContextMenuRole).toList();
// context menu must be shorter than total action links
QVERIFY(actionsLinksContextMenu.isEmpty() || actionsLinksContextMenu.size() < actionsLinks.size());
@@ -281,8 +280,7 @@ private slots:
const auto objectType = index.data(OCC::ActivityListModel::ObjectTypeRole).toString();
- const auto actionButtonsLinks =
- index.data(OCC::ActivityListModel::ActionsLinksForActionButtonsRole).toList();
+ const auto actionButtonsLinks = index.data(OCC::ActivityListModel::ActionsLinksForActionButtonsRole).toList();
// Login attempt notification
if (objectType == QStringLiteral("2fa_id")) {
@@ -323,15 +321,12 @@ private slots:
QVERIFY(actionButtonsLinks[0].value()._label == QObject::tr("Reply"));
if (static_cast(actionsLinks.size()) > OCC::ActivityListModel::maxActionButtons()) {
- // in case total actions is longer than ActivityListModel::maxActionButtons, only one button must be present in a list of action buttons
- QVERIFY(actionButtonsLinks.size() == 1);
- const auto actionButtonsAndContextMenuEntries = actionButtonsLinks + actionsLinksContextMenu;
+ QCOMPARE(actionButtonsLinks.size(), OCC::ActivityListModel::maxActionButtons());
// in case total actions is longer than ActivityListModel::maxActionButtons, then a sum of action buttons and action menu entries must be equal to a total of action links
- QVERIFY(actionButtonsLinks.size() + actionsLinksContextMenu.size() == actionsLinks.size());
+ QCOMPARE(actionButtonsLinks.size() + actionsLinksContextMenu.size(), actionsLinks.size());
}
} else if ((objectType == QStringLiteral("call"))) {
- QVERIFY(
- actionButtonsLinks[0].value()._label == QStringLiteral("Call back"));
+ QVERIFY(actionButtonsLinks[0].value()._label == QStringLiteral("Call back"));
}
}
}
diff --git a/test/testsyncengine.cpp b/test/testsyncengine.cpp
index ec5aebeb2489..9516a05570dc 100644
--- a/test/testsyncengine.cpp
+++ b/test/testsyncengine.cpp
@@ -7,9 +7,10 @@
#include "syncenginetestutils.h"
-#include "syncengine.h"
-#include "propagatorjobs.h"
#include "caseclashconflictsolver.h"
+#include "configfile.h"
+#include "propagatorjobs.h"
+#include "syncengine.h"
#include
@@ -1663,6 +1664,35 @@ private slots:
fakeFolder.remoteModifier().remove(testUpperCaseFile);
QVERIFY(fakeFolder.syncOnce());
}
+
+ void testExistingFolderBecameBig()
+ {
+ constexpr auto testFolder = "folder";
+ constexpr auto testSmallFile = "folder/small_file.txt";
+ constexpr auto testLargeFile = "folder/large_file.txt";
+
+ QTemporaryDir dir;
+ ConfigFile::setConfDir(dir.path()); // we don't want to pollute the user's config file
+ auto config = ConfigFile();
+ config.setNotifyExistingFoldersOverLimit(true);
+
+ FakeFolder fakeFolder{FileInfo{}};
+ QSignalSpy spy(&fakeFolder.syncEngine(), &SyncEngine::existingFolderNowBig);
+
+ auto syncOptions = fakeFolder.syncEngine().syncOptions();
+ syncOptions._newBigFolderSizeLimit = 128; // 128 bytes
+ fakeFolder.syncEngine().setSyncOptions(syncOptions);
+
+ fakeFolder.remoteModifier().mkdir(testFolder);
+ fakeFolder.remoteModifier().insert(testSmallFile, 64);
+ fakeFolder.syncEngine().setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::DatabaseAndFilesystem);
+ QVERIFY(fakeFolder.syncOnce());
+ QCOMPARE(spy.count(), 0);
+
+ fakeFolder.remoteModifier().insert(testLargeFile, 256);
+ QVERIFY(fakeFolder.syncOnce());
+ QCOMPARE(spy.count(), 1);
+ }
};
QTEST_GUILESS_MAIN(TestSyncEngine)