SelectiveSyncWhiteList = 2,
/** List of big sync folders that have not been confirmed by the user yet and that the UI
* should notify about */
- SelectiveSyncUndecidedList = 3
+ SelectiveSyncUndecidedList = 3,
+ /** List of encrypted folders that will need to be removed from the blacklist when E2EE gets set up*/
+ SelectiveSyncE2eFoldersToRemoveFromBlacklist = 4,
};
/* return the specified list from the database */
QStringList getSelectiveSyncList(SelectiveSyncListType type, bool *ok);
CSYNC_FILE_EXCLUDE_SERVER_BLACKLISTED,
CSYNC_FILE_EXCLUDE_LEADING_SPACE,
CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE,
+ CSYNC_FILE_E2E_COULD_NOT_DECRYPT_EXCLUDED,
};
class ExcludedFilesTest;
}
}
+void AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync()
+{
+ if (_accountState->account()->e2e()->_mnemonic.isEmpty()) {
+ return;
+ }
+
+ disconnect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync);
+
+ for (const auto folder : FolderMan::instance()->map()) {
+ if (folder->accountState() != _accountState) {
+ continue;
+ }
+ bool ok = false;
+ const auto foldersToRemoveFromBlacklist = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncE2eFoldersToRemoveFromBlacklist, &ok);
+ if (foldersToRemoveFromBlacklist.isEmpty()) {
+ continue;
+ }
+ auto blackList = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok);
+ const auto blackListSize = blackList.size();
+ if (blackListSize == 0) {
+ continue;
+ }
+ for (const auto &pathToRemoveFromBlackList : foldersToRemoveFromBlacklist) {
+ blackList.removeAll(pathToRemoveFromBlackList);
+ }
+ if (blackList.size() != blackListSize) {
+ if (folder->isSyncRunning()) {
+ folderTerminateSyncAndUpdateBlackList(blackList, folder, foldersToRemoveFromBlacklist);
+ return;
+ }
+ updateBlackListAndScheduleFolderSync(blackList, folder, foldersToRemoveFromBlacklist);
+ }
+ }
+}
+
+void AccountSettings::updateBlackListAndScheduleFolderSync(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist) const
+{
+ folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList);
+ folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncE2eFoldersToRemoveFromBlacklist, {});
+ for (const auto &pathToRemoteDiscover : foldersToRemoveFromBlacklist) {
+ folder->journalDb()->schedulePathForRemoteDiscovery(pathToRemoteDiscover);
+ }
+ FolderMan::instance()->scheduleFolder(folder);
+}
+
+void AccountSettings::folderTerminateSyncAndUpdateBlackList(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist)
+{
+ if (_folderConnections.contains(folder->alias())) {
+ qCWarning(lcAccountSettings) << "Folder " << folder->alias() << "is already terminating the sync.";
+ return;
+ }
+ // in case sync is already running - terminate it and start a new one
+ const QMetaObject::Connection syncTerminatedConnection = connect(folder, &Folder::syncFinished, this, [this, blackList, folder, foldersToRemoveFromBlacklist]() {
+ const auto foundConnectionIt = _folderConnections.find(folder->alias());
+ if (foundConnectionIt != _folderConnections.end()) {
+ disconnect(*foundConnectionIt);
+ _folderConnections.erase(foundConnectionIt);
+ }
+ updateBlackListAndScheduleFolderSync(blackList, folder, foldersToRemoveFromBlacklist);
+ });
+ _folderConnections.insert(folder->alias(), syncTerminatedConnection);
+ folder->slotTerminateSync();
+}
+
void AccountSettings::refreshSelectiveSyncStatus()
{
QString msg;
void AccountSettings::initializeE2eEncryption()
{
+ connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync);
+
if (!_accountState->account()->e2e()->_mnemonic.isEmpty()) {
slotE2eEncryptionMnemonicReady();
} else {
if (!_accountState->account()->e2e()->_publicKey.isNull()) {
_ui->encryptionMessage->setText(tr("End-to-end encryption has been enabled on this account with another device."
"<br>"
- "It can be enabled on this device by entering your mnemonic."));
+ "It can be enabled on this device by entering your mnemonic."
+ "<br>"
+ "This will enable synchronisation of existing encrypted folders."));
}
});
_accountState->account()->setE2eEncryptionKeysGenerationAllowed(false);
void slotSelectiveSyncChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
const QVector<int> &roles);
+ void slotPossiblyUnblacklistE2EeFoldersAndRestartSync();
+
+private slots:
+ void updateBlackListAndScheduleFolderSync(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist) const;
+ void folderTerminateSyncAndUpdateBlackList(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist);
private:
void displayMnemonic(const QString &mnemonic);
QAction *_addAccountAction;
bool _menuShown;
+
+ QHash<QString, QMetaObject::Connection> _folderConnections;
};
} // namespace OCC
void FolderStatusModel::setAccountState(const AccountState *accountState)
{
+ connect(accountState->account()->e2e(), &OCC::ClientSideEncryption::initializationFinished, this, &FolderStatusModel::e2eInitializationFinished);
+
beginResetModel();
_dirty = false;
_folders.clear();
return QVariant();
}
case SubFolder: {
- const auto &x = static_cast<SubFolderInfo *>(index.internalPointer())->_subs.at(index.row());
- const auto supportsSelectiveSync = x._folder && x._folder->supportsSelectiveSync();
+ const auto &subfolderInfo = static_cast<SubFolderInfo *>(index.internalPointer())->_subs.at(index.row());
+ const auto supportsSelectiveSync = subfolderInfo._folder && subfolderInfo._folder->supportsSelectiveSync();
switch (role) {
- case Qt::DisplayRole:
+ case Qt::DisplayRole: {
//: Example text: "File.txt (23KB)"
- return x._size < 0 ? x._name : tr("%1 (%2)").arg(x._name, Utility::octetsToString(x._size));
+ const auto &xParent = static_cast<SubFolderInfo *>(index.internalPointer());
+ const auto suffix = (subfolderInfo._isNonDecryptable && subfolderInfo._checked && (!xParent || !xParent->_isEncrypted))
+ ? tr(" - %1").arg("Could not decrypt!")
+ : QString{};
+ return subfolderInfo._size < 0 ? QString(subfolderInfo._name + suffix) : QString(tr("%1 (%2)").arg(subfolderInfo._name, Utility::octetsToString(subfolderInfo._size)) + suffix);
+ }
case Qt::ToolTipRole:
- return QString(QLatin1String("<qt>") + Utility::escape(x._size < 0 ? x._name : tr("%1 (%2)").arg(x._name, Utility::octetsToString(x._size))) + QLatin1String("</qt>"));
+ return QString(QLatin1String("<qt>") + Utility::escape(subfolderInfo._size < 0 ? subfolderInfo._name : tr("%1 (%2)").arg(subfolderInfo._name, Utility::octetsToString(subfolderInfo._size))) + QLatin1String("</qt>"));
case Qt::CheckStateRole:
if (supportsSelectiveSync) {
- return x._checked;
+ return subfolderInfo._checked;
} else {
return QVariant();
}
case Qt::DecorationRole: {
- if (x._isEncrypted) {
+ if (subfolderInfo._isNonDecryptable && subfolderInfo._checked) {
+ return QIcon(QLatin1String(":/client/theme/lock-broken.svg"));
+ }
+ if (subfolderInfo._isEncrypted) {
return QIcon(QLatin1String(":/client/theme/lock-https.svg"));
- } else if (x._size > 0 && isAnyAncestorEncrypted(index)) {
+ } else if (subfolderInfo._size > 0 && isAnyAncestorEncrypted(index)) {
return QIcon(QLatin1String(":/client/theme/lock-broken.svg"));
}
- return QFileIconProvider().icon(x._isExternal ? QFileIconProvider::Network : QFileIconProvider::Folder);
+ return QFileIconProvider().icon(subfolderInfo._isExternal ? QFileIconProvider::Network : QFileIconProvider::Folder);
}
case Qt::ForegroundRole:
- if (x._isUndecided) {
+ if (subfolderInfo._isUndecided || (subfolderInfo._isNonDecryptable && subfolderInfo._checked)) {
return QColor(Qt::red);
}
break;
case FileIdRole:
- return x._fileId;
+ return subfolderInfo._fileId;
case FolderStatusDelegate::FolderPathRole: {
- auto f = x._folder;
+ auto f = subfolderInfo._folder;
if (!f)
return QVariant();
- return QVariant(f->path() + x._path);
+ return QVariant(f->path() + subfolderInfo._path);
}
}
}
newInfo._isEncrypted = encryptionMap.value(removeTrailingSlash(path)).toString() == QStringLiteral("1");
newInfo._path = relativePath;
+ newInfo._isNonDecryptable = newInfo._isEncrypted
+ && _accountState->account()->e2e() && !_accountState->account()->e2e()->_publicKey.isNull()
+ && _accountState->account()->e2e()->_privateKey.isNull();
+
SyncJournalFileRecord rec;
if (!parentInfo->_folder->journalDb()->getFileRecordByE2eMangledName(removeTrailingSlash(relativePath), &rec)) {
qCWarning(lcFolderStatus) << "Could not get file record by E2E Mangled Name from local DB" << removeTrailingSlash(relativePath);
emit dataChanged(index(folderIndex), index(folderIndex), roles);
}
+void FolderStatusModel::e2eInitializationFinished(bool isNewMnemonicGenerated)
+{
+ Q_UNUSED(isNewMnemonicGenerated);
+
+ for (int i = 0; i < _folders.count(); ++i) {
+ resetAndFetch(index(i));
+ }
+}
+
void FolderStatusModel::slotFolderSyncStateChange(Folder *f)
{
if (!f) {
Qt::CheckState _checked = Qt::Checked;
+ bool _isNonDecryptable = false;
+
// Whether this has a FetchLabel subrow
[[nodiscard]] bool hasLabel() const;
void slotSyncAllPendingBigFolders();
void slotSyncNoPendingBigFolders();
void slotSetProgress(const OCC::ProgressInfo &progress);
+ void e2eInitializationFinished(bool isNewMnemonicGenerated);
private slots:
void slotUpdateDirectories(const QStringList &);
* for more details.
*/
+#include "account.h"
#include "discovery.h"
#include "common/filesystembase.h"
#include "common/syncjournaldb.h"
excluded = CSYNC_FILE_EXCLUDE_TRAILING_SPACE;
} else if (startsWithSpace) {
excluded = CSYNC_FILE_EXCLUDE_LEADING_SPACE;
+ } else if (entries.serverEntry.isValid() && entries.serverEntry.isE2eEncrypted) {
+ const auto wasE2eEnabledButNotSetup = _discoveryData->_account->e2e()
+ && !_discoveryData->_account->e2e()->_publicKey.isNull()
+ && _discoveryData->_account->e2e()->_privateKey.isNull();
+ if (wasE2eEnabledButNotSetup) {
+ excluded = CSYNC_FILE_E2E_COULD_NOT_DECRYPT_EXCLUDED;
+ }
}
}
if (excluded == CSYNC_NOT_EXCLUDED && !entries.localEntry.isSymLink) {
return false;
- } else if (excluded == CSYNC_FILE_SILENTLY_EXCLUDED || excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE) {
+ } else if (excluded == CSYNC_FILE_SILENTLY_EXCLUDED || excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE || excluded == CSYNC_FILE_E2E_COULD_NOT_DECRYPT_EXCLUDED) {
+ if (excluded == CSYNC_FILE_E2E_COULD_NOT_DECRYPT_EXCLUDED && isDirectory && path != QStringLiteral("/")) {
+ checkAndUpdateSelectiveSyncListsForE2eeFolders(path);
+ }
emit _discoveryData->silentlyExcluded(path);
return true;
}
} else {
switch (excluded) {
case CSYNC_NOT_EXCLUDED:
+ case CSYNC_FILE_E2E_COULD_NOT_DECRYPT_EXCLUDED:
case CSYNC_FILE_SILENTLY_EXCLUDED:
case CSYNC_FILE_EXCLUDE_AND_REMOVE:
qFatal("These were handled earlier");
return true;
}
+void ProcessDirectoryJob::checkAndUpdateSelectiveSyncListsForE2eeFolders(const QString &path)
+{
+ bool ok = false;
+ auto blackList = _discoveryData->_statedb->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok);
+ auto selectiveSyncE2eFoldersToRemoveFromBlacklist =
+ _discoveryData->_statedb->getSelectiveSyncList(SyncJournalDb::SelectiveSyncE2eFoldersToRemoveFromBlacklist, &ok);
+ const auto pathWithTrailingSpace = path.endsWith(QLatin1Char('/')) ? path : path + QLatin1Char('/');
+ if (!blackList.contains(pathWithTrailingSpace)) {
+ blackList.push_back(pathWithTrailingSpace);
+ blackList.sort();
+ _discoveryData->_statedb->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList);
+ }
+ // record it into a separate list to automatically remove from blacklist once the e2EE gets set up
+ if (!selectiveSyncE2eFoldersToRemoveFromBlacklist.contains(pathWithTrailingSpace)) {
+ selectiveSyncE2eFoldersToRemoveFromBlacklist.push_back(pathWithTrailingSpace);
+ selectiveSyncE2eFoldersToRemoveFromBlacklist.sort();
+ _discoveryData->_statedb->setSelectiveSyncList(SyncJournalDb::SelectiveSyncE2eFoldersToRemoveFromBlacklist,
+ selectiveSyncE2eFoldersToRemoveFromBlacklist);
+ }
+}
+
void ProcessDirectoryJob::processFile(PathTuple path,
const LocalInfo &localEntry, const RemoteInfo &serverEntry,
const SyncJournalFileRecord &dbEntry)
// path is the full relative path of the file. localName is the base name of the local entry.
bool handleExcluded(const QString &path, const Entries &entries, bool isHidden);
+ // check if the path is an e2e encrypted and the e2ee is not set up, and insert it into a corresponding list in the sync journal
+ void checkAndUpdateSelectiveSyncListsForE2eeFolders(const QString &path);
+
/** Reconcile local/remote/db information for a single item.
*
* Can be a file or a directory.