Skip to content

Commit ec45815

Browse files
committed
refactor(socketapi): show conflict and move item dialogs via gui signals.
SocketApi built the conflict and move dialogs inline inside the socket read loop. This keeps UI out of SocketApi and removes the modal loop that ran under slotReadSocket. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Camila Ayres <hello@camilasan.com>
1 parent b76a4f5 commit ec45815

5 files changed

Lines changed: 62 additions & 50 deletions

File tree

src/gui/application.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,12 @@ Application::Application(int &argc, char **argv)
491491
connect(FolderMan::instance()->socketApi(), &SocketApi::fileActionsCommandReceived,
492492
_gui.data(), &ownCloudGui::slotShowFileActionsDialog);
493493

494+
connect(FolderMan::instance()->socketApi(), &SocketApi::resolveConflictCommandReceived,
495+
_gui.data(), &ownCloudGui::slotResolveConflict);
496+
497+
connect(FolderMan::instance()->socketApi(), &SocketApi::moveItemCommandReceived,
498+
_gui.data(), &ownCloudGui::slotMoveItem);
499+
494500
// startup procedure.
495501
connect(&_checkConnectionTimer, &QTimer::timeout, this, &Application::slotCheckConnection);
496502
_checkConnectionTimer.setInterval(ConnectionValidator::DefaultCallingIntervalMsec); // check for connection every 32 seconds.

src/gui/owncloudgui.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include "theme.h"
2626
#include "wheelhandler.h"
2727
#include "syncconflictsmodel.h"
28+
#include "conflictdialog.h"
29+
#include "conflictsolver.h"
2830
#include "syncengine.h"
2931
#include "wizard/accountwizardcontroller.h"
3032
#include "filedetails/datefieldbackend.h"
@@ -44,6 +46,7 @@
4446
#include "governance/getgovernancelabels.h"
4547
#include "governance/governancelabelslistmodel.h"
4648
#include "filesystem.h"
49+
#include "common/utility_mac_sandbox.h"
4750

4851
#ifdef WITH_LIBCLOUDPROVIDERS
4952
#include "cloudproviders/cloudprovidermanager.h"
@@ -52,6 +55,7 @@
5255
#include <QClipboard>
5356
#include <QDesktopServices>
5457
#include <QDir>
58+
#include <QFileDialog>
5559
#include <QGuiApplication>
5660
#include <QMessageBox>
5761
#include <QQmlApplicationEngine>
@@ -806,6 +810,52 @@ void ownCloudGui::slotShowFileActionsDialog(const QString &localPath) const
806810
_tray->showFileActionsDialog(localPath);
807811
}
808812

813+
void ownCloudGui::slotResolveConflict(const QString &conflictedPath, const QString &basePath, const QString &baseName, const QString &folderAlias) const
814+
{
815+
// Show with open(), never exec(): this runs inside SocketApi::slotReadSocket and a modal loop would crash on a nullptr socket.
816+
auto dialog = new ConflictDialog;
817+
dialog->setAttribute(Qt::WA_DeleteOnClose);
818+
dialog->setBaseFilename(baseName);
819+
dialog->setLocalVersionFilename(conflictedPath);
820+
dialog->setRemoteVersionFilename(basePath);
821+
connect(dialog, &ConflictDialog::accepted, dialog, [folderAlias] {
822+
if (const auto folder = FolderMan::instance()->folder(folderAlias)) {
823+
folder->scheduleThisFolderSoon();
824+
}
825+
});
826+
dialog->open();
827+
raiseDialog(dialog);
828+
}
829+
830+
void ownCloudGui::slotMoveItem(const QString &localPath, const QString &defaultTarget) const
831+
{
832+
// Show with open(), never exec(): this runs inside SocketApi::slotReadSocket and a modal loop would crash on a freed socket.
833+
auto dialog = new QFileDialog(nullptr, tr("Select new location …"), QFileInfo(defaultTarget).absolutePath());
834+
dialog->setAttribute(Qt::WA_DeleteOnClose);
835+
dialog->setAcceptMode(QFileDialog::AcceptSave);
836+
dialog->setOptions(QFileDialog::HideNameFilterDetails);
837+
dialog->selectUrl(QUrl::fromLocalFile(defaultTarget));
838+
connect(dialog, &QFileDialog::urlSelected, dialog, [localPath](const QUrl &targetUrl) {
839+
if (targetUrl.isEmpty()) {
840+
return;
841+
}
842+
843+
#ifdef Q_OS_MACOS
844+
const auto scopedAccess = Utility::MacSandboxSecurityScopedAccess::create(targetUrl);
845+
if (!scopedAccess->isValid()) {
846+
qCWarning(lcOwnCloudGui) << "Could not access resource for conflict resolution:" << targetUrl;
847+
return;
848+
}
849+
#endif
850+
851+
ConflictSolver solver;
852+
solver.setLocalVersionFilename(localPath);
853+
solver.setRemoteVersionFilename(targetUrl.toLocalFile());
854+
});
855+
dialog->open();
856+
raiseDialog(dialog);
857+
}
858+
809859
#ifdef BUILD_FILE_PROVIDER_MODULE
810860
void ownCloudGui::slotOpenItemInBrowserFromFileProvider(const QString &fileId, const QString &remoteItemPath, const QString &fileProviderDomainIdentifier)
811861
{

src/gui/owncloudgui.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ public slots:
102102
const QString &fileId) const;
103103
void slotShowFileActivityDialog(const QString &localPath) const;
104104
void slotShowFileActionsDialog(const QString &localPath) const;
105+
void slotResolveConflict(const QString &conflictedPath, const QString &basePath, const QString &baseName, const QString &folderAlias) const;
106+
void slotMoveItem(const QString &localPath, const QString &defaultTarget) const;
105107
#ifdef BUILD_FILE_PROVIDER_MODULE
106108
/**
107109
* @brief Open an item's web page in the user's browser on behalf of the macOS file provider extension.

src/gui/socketapi/socketapi.cpp

Lines changed: 2 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
#include "socketapi.h"
88
#include "socketapi_p.h"
99

10-
#include "conflictdialog.h"
1110
#include "conflictsolver.h"
1211

1312
#include "config.h"
@@ -51,8 +50,6 @@
5150
#include <QStringBuilder>
5251
#include <QMessageBox>
5352
#include <QInputDialog>
54-
#include <QFileDialog>
55-
#include <QTimer>
5653

5754

5855
#include <QAction>
@@ -1072,26 +1069,8 @@ void SocketApi::command_RESOLVE_CONFLICT(const QString &localFile, SocketListene
10721069
const auto basePath = dir.filePath(baseRelativePath);
10731070

10741071
const auto baseName = QFileInfo(basePath).fileName();
1075-
const auto folderAlias = fileData.folder->alias();
10761072

1077-
#ifndef OWNCLOUD_TEST
1078-
// Show the dialog outside of the socket read loop. This handler runs via a
1079-
// Qt::DirectConnection while SocketApi is still iterating the socket, so a nested
1080-
// modal loop here can let the socket or the Folder be destroyed underneath us.
1081-
// Defer to the next event loop iteration and look up the folder again by alias.
1082-
QTimer::singleShot(0, this, [conflictedPath, basePath, baseName, folderAlias] {
1083-
ConflictDialog dialog;
1084-
dialog.setBaseFilename(baseName);
1085-
dialog.setLocalVersionFilename(conflictedPath);
1086-
dialog.setRemoteVersionFilename(basePath);
1087-
if (dialog.exec() != ConflictDialog::Accepted) {
1088-
return;
1089-
}
1090-
if (const auto folder = FolderMan::instance()->folder(folderAlias)) {
1091-
folder->scheduleThisFolderSoon();
1092-
}
1093-
});
1094-
#endif
1073+
emit resolveConflictCommandReceived(conflictedPath, basePath, baseName, fileData.folder->alias());
10951074
}
10961075

10971076
void SocketApi::command_DELETE_ITEM(const QString &localFile, SocketListener *)
@@ -1132,34 +1111,7 @@ void SocketApi::command_MOVE_ITEM(const QString &localFile, SocketListener *)
11321111
// Add back the folder path
11331112
defaultDirAndName = QDir(fileData.folder->path()).filePath(defaultDirAndName);
11341113

1135-
// Show the file dialog outside of the socket read loop. As in command_RESOLVE_CONFLICT,
1136-
// a nested modal loop from the Qt::DirectConnection dispatch can free the socket under us.
1137-
QTimer::singleShot(0, this, [localFile, defaultDirAndName] {
1138-
// Use getSaveFileUrl for sandbox compatibility
1139-
const auto targetUrl = QFileDialog::getSaveFileUrl(
1140-
nullptr,
1141-
SocketApi::tr("Select new location …"),
1142-
QUrl::fromLocalFile(defaultDirAndName),
1143-
QString(), nullptr, QFileDialog::HideNameFilterDetails);
1144-
if (targetUrl.isEmpty())
1145-
return;
1146-
1147-
#ifdef Q_OS_MACOS
1148-
// On macOS with app sandbox, we need to explicitly access the security-scoped resource
1149-
auto scopedAccess = Utility::MacSandboxSecurityScopedAccess::create(targetUrl);
1150-
1151-
if (!scopedAccess->isValid()) {
1152-
qCWarning(lcSocketApi) << "Could not access security-scoped resource for conflict resolution:" << targetUrl;
1153-
return;
1154-
}
1155-
#endif
1156-
1157-
const auto target = targetUrl.toLocalFile();
1158-
1159-
ConflictSolver solver;
1160-
solver.setLocalVersionFilename(localFile);
1161-
solver.setRemoteVersionFilename(target);
1162-
});
1114+
emit moveItemCommandReceived(localFile, defaultDirAndName);
11631115
}
11641116

11651117
void SocketApi::command_LOCK_FILE(const QString &localFile, SocketListener *listener)

src/gui/socketapi/socketapi.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ public slots:
7474
void fileActivityCommandReceived(const QString &localPath);
7575
void fileActionsCommandReceived(const QString &localPath);
7676
void governanceLabelsCommandReceived(OCC::AccountPtr account, const QString &filePath, const QString &fileId);
77+
void resolveConflictCommandReceived(const QString &conflictedPath, const QString &basePath, const QString &baseName, const QString &folderAlias);
78+
void moveItemCommandReceived(const QString &localPath, const QString &defaultTarget);
7779

7880
private slots:
7981
void slotNewConnection();

0 commit comments

Comments
 (0)