diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp index fb82fe40575fb..41ad48b59823d 100644 --- a/src/gui/folderstatusmodel.cpp +++ b/src/gui/folderstatusmodel.cpp @@ -27,6 +27,7 @@ Q_LOGGING_CATEGORY(lcFolderStatus, "nextcloud.gui.folder.model", QtInfoMsg) static const char propertyParentIndexC[] = "oc_parentIndex"; static const char propertyPermissionMap[] = "oc_permissionMap"; static const char propertyEncryptionMap[] = "nc_encryptionMap"; +static const char propertySyncEnabledMap[] = "nc_syncEnabledMap"; static QString removeTrailingSlash(const QString &path) { @@ -634,7 +635,8 @@ void FolderStatusModel::fetchMore(const QModelIndex &parent) << "http://owncloud.org/ns:permissions" << "http://nextcloud.org/ns:is-mount-root" << "http://owncloud.org/ns:fileid" - << "http://nextcloud.org/ns:is-encrypted"; + << "http://nextcloud.org/ns:is-encrypted" + << "http://nextcloud.org/ns:sync-enabled"; job->setProperties(props); job->setTimeout(60 * 1000); @@ -646,6 +648,8 @@ void FolderStatusModel::fetchMore(const QModelIndex &parent) this, &FolderStatusModel::slotGatherPermissions); connect(job, &LsColJob::directoryListingIterated, this, &FolderStatusModel::slotGatherEncryptionStatus); + connect(job, &LsColJob::directoryListingIterated, + this, &FolderStatusModel::slotGatherSyncEnabledStatus); job->start(); @@ -694,6 +698,21 @@ void FolderStatusModel::slotGatherEncryptionStatus(const QString &href, const QM job->setProperty(propertyEncryptionMap, encryptionMap); } +void FolderStatusModel::slotGatherSyncEnabledStatus(const QString &href, const QMap &properties) +{ + const auto it = properties.find("sync-enabled"); + if (it == properties.end()) { + return; + } + + const auto job = sender(); + auto syncEnabledMap = job->property(propertySyncEnabledMap).toMap(); + job->setProperty(propertySyncEnabledMap, QVariant()); // avoid a detach of the map while it is modified + ASSERT(!href.endsWith(QLatin1Char('/')), "LsColXMLParser::parse should remove the trailing slash before calling us."); + syncEnabledMap[href] = *it; + job->setProperty(propertySyncEnabledMap, syncEnabledMap); +} + void FolderStatusModel::slotUpdateDirectories(const QStringList &list) { const auto job = qobject_cast(sender()); @@ -741,6 +760,7 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list) } const auto permissionMap = job->property(propertyPermissionMap).toMap(); const auto encryptionMap = job->property(propertyEncryptionMap).toMap(); + const auto syncEnabledMap = job->property(propertySyncEnabledMap).toMap(); auto sortedSubfolders = list; if (!sortedSubfolders.isEmpty()) { @@ -757,6 +777,14 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list) if (parentInfo->_folder->isFileExcludedRelative(relativePath)) { continue; } + // Server-side external storage mounts can opt out of client sync + // entirely (nc:sync-enabled == "false") while remaining browsable + // via web/mobile. Never surface them in the "choose what to sync" + // tree so they can't be selected for automatic sync, whether the + // account was just connected or already existed. + if (syncEnabledMap.value(removeTrailingSlash(path)).toString() == "false"_L1) { + continue; + } SubFolderInfo newInfo; newInfo._folder = parentInfo->_folder; diff --git a/src/gui/folderstatusmodel.h b/src/gui/folderstatusmodel.h index 63d4afbbfe55a..c3df4405c7d25 100644 --- a/src/gui/folderstatusmodel.h +++ b/src/gui/folderstatusmodel.h @@ -128,6 +128,7 @@ private Q_SLOTS: void slotUpdateDirectories(const QStringList &); void slotGatherPermissions(const QString &name, const QMap &properties); void slotGatherEncryptionStatus(const QString &href, const QMap &properties); + void slotGatherSyncEnabledStatus(const QString &href, const QMap &properties); void slotLscolFinishedWithError(QNetworkReply *r); void slotFolderSyncStateChange(OCC::Folder *f); void slotFolderScheduleQueueChanged(); diff --git a/src/gui/folderwizard.cpp b/src/gui/folderwizard.cpp index 40a2080aa74f6..5cbcf4acaa96c 100644 --- a/src/gui/folderwizard.cpp +++ b/src/gui/folderwizard.cpp @@ -387,6 +387,18 @@ void FolderWizardRemotePath::slotUpdateDirectories(const QStringList &list) continue; } + // Server-side external storage mounts can opt out of client sync + // entirely (nc:sync-enabled == "false") while remaining browsable + // on the web/mobile apps. Exclude the mount itself and everything + // beneath it from the folder picker so it can never be selected + // for automatic sync. + const auto isSyncDisabled = std::any_of(std::cbegin(_syncDisabledPaths), std::cend(_syncDisabledPaths), [=](const QString &disabledPath) { + return path.size() >= disabledPath.size() && path.startsWith(disabledPath); + }); + if (isSyncDisabled) { + continue; + } + QStringList paths = path.split('/'); if (paths.last().isEmpty()) { paths.removeLast(); @@ -408,6 +420,18 @@ void FolderWizardRemotePath::slotGatherEncryptedPaths(const QString &path, const _encryptedPaths << path.mid(webdavFolder.size()); } +void FolderWizardRemotePath::slotGatherSyncDisabledPaths(const QString &path, const QMap &properties) +{ + const auto it = properties.find("sync-enabled"); + if (it == properties.cend() || *it != QStringLiteral("false")) { + return; + } + + const auto webdavFolder = QUrl(_account->davUrl()).path(); + Q_ASSERT(path.startsWith(webdavFolder)); + _syncDisabledPaths << path.mid(webdavFolder.size()); +} + void FolderWizardRemotePath::slotRefreshFolders() { _encryptedPaths.clear(); @@ -478,7 +502,8 @@ LsColJob *FolderWizardRemotePath::runLsColJob(const QString &path) { auto *job = new LsColJob(_account, path); const auto props = QList() << "resourcetype" - << "http://nextcloud.org/ns:is-encrypted"; + << "http://nextcloud.org/ns:is-encrypted" + << "http://nextcloud.org/ns:sync-enabled"; job->setProperties(props); connect(job, &LsColJob::directoryListingSubfolders, this, &FolderWizardRemotePath::slotUpdateDirectories); @@ -486,6 +511,8 @@ LsColJob *FolderWizardRemotePath::runLsColJob(const QString &path) this, &FolderWizardRemotePath::slotHandleLsColNetworkError); connect(job, &LsColJob::directoryListingIterated, this, &FolderWizardRemotePath::slotGatherEncryptedPaths); + connect(job, &LsColJob::directoryListingIterated, + this, &FolderWizardRemotePath::slotGatherSyncDisabledPaths); job->start(); return job; diff --git a/src/gui/folderwizard.h b/src/gui/folderwizard.h index e79c880ed2a43..bb5b881245515 100644 --- a/src/gui/folderwizard.h +++ b/src/gui/folderwizard.h @@ -98,6 +98,7 @@ protected Q_SLOTS: void slotHandleLsColNetworkError(QNetworkReply *); void slotUpdateDirectories(const QStringList &); void slotGatherEncryptedPaths(const QString &, const QMap &); + void slotGatherSyncDisabledPaths(const QString &, const QMap &); void slotRefreshFolders(); void slotItemExpanded(QTreeWidgetItem *); void slotCurrentItemChanged(QTreeWidgetItem *); @@ -120,6 +121,11 @@ private Q_SLOTS: AccountPtr _account; QTimer _lscolTimer; QStringList _encryptedPaths; + // Paths whose external storage mount has server-side sync disabled + // (nc:sync-enabled == "false"). Excluded from the folder picker the + // same way encrypted paths are, since they are not meant to be + // auto-synced to begin with, only browsed/downloaded on demand. + QStringList _syncDisabledPaths; }; /**