From 23781af1f0f533db9a494af18e4ae6534cffd753 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 10 Nov 2026 12:16:23 +0100 Subject: [PATCH] [Server] Move deck folder writes into the database interface (#7833) Setting a deck or folder public, creating a folder, and deleting a folder tree were SQL statements in the socket handler. Declare them on Server_DatabaseInterface with a DeckStorageResult so the caller can still tell a missing row from a failed write, implement them in Servatrice_DatabaseInterface with the same statements, and let the three folder commands ask the injected interface. Readiness probing keeps its previous ignore-the-result shape. --- .../server/remote/server_database_interface.h | 23 ++++++ .../src/servatrice_database_interface.cpp | 75 ++++++++++++++++++ .../src/servatrice_database_interface.h | 5 ++ servatrice/src/serversocketinterface.cpp | 76 +++++-------------- servatrice/src/serversocketinterface.h | 1 - 5 files changed, 121 insertions(+), 59 deletions(-) diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h b/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h index 506620bc8..88d79f882 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h @@ -195,6 +195,29 @@ public: { return false; } + /** @brief Outcome of a deck-storage write, so callers can map it to a response code. */ + enum class DeckStorageResult + { + Ok, + NotFound, + Failed + }; + virtual DeckStorageResult setDeckVisibility(int /* deckId */, int /* userId */, bool /* isPublic */) + { + return DeckStorageResult::Failed; + } + virtual DeckStorageResult setDeckFolderVisibility(int /* folderId */, int /* userId */, bool /* isPublic */) + { + return DeckStorageResult::Failed; + } + virtual DeckStorageResult createDeckFolder(int /* parentId */, int /* userId */, const QString & /* name */) + { + return DeckStorageResult::Failed; + } + /** @brief Delete a folder, its deck files, and every nested folder. */ + virtual void deleteDeckFolder(int /* folderId */) + { + } virtual bool removeForgotPassword(const QString & /* user */) { return false; diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index 14b66ddcb..cb0ba24f3 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -1023,6 +1023,81 @@ int Servatrice_DatabaseInterface::getDeckPathId(const QString &path, int userId) return getDeckPathIdAt(0, path.split("/"), userId); } +Servatrice_DatabaseInterface::DeckStorageResult +Servatrice_DatabaseInterface::setDeckVisibility(int deckId, int userId, bool isPublic) +{ + QSqlQuery *query = prepareQuery("select 1 from {prefix}_decklist_files where id = :id and id_user = :id_user"); + query->bindValue(":id", deckId); + query->bindValue(":id_user", userId); + execSqlQuery(query); + if (!query->next()) { + return DeckStorageResult::NotFound; + } + + query = prepareQuery("update {prefix}_decklist_files set is_public = :is_public where id = :id " + "and id_user = :id_user"); + query->bindValue(":is_public", isPublic ? 1 : 0); + query->bindValue(":id", deckId); + query->bindValue(":id_user", userId); + if (!execSqlQuery(query)) { + return DeckStorageResult::Failed; + } + return DeckStorageResult::Ok; +} + +Servatrice_DatabaseInterface::DeckStorageResult +Servatrice_DatabaseInterface::setDeckFolderVisibility(int folderId, int userId, bool isPublic) +{ + QSqlQuery *query = prepareQuery( + "update {prefix}_decklist_folders set is_public = :is_public where id = :id and id_user = :id_user"); + query->bindValue(":is_public", isPublic ? 1 : 0); + query->bindValue(":id", folderId); + query->bindValue(":id_user", userId); + if (!execSqlQuery(query)) { + return DeckStorageResult::Failed; + } + return DeckStorageResult::Ok; +} + +Servatrice_DatabaseInterface::DeckStorageResult +Servatrice_DatabaseInterface::createDeckFolder(int parentId, int userId, const QString &name) +{ + QSqlQuery *query = prepareQuery( + "insert into {prefix}_decklist_folders (id_parent, id_user, name) values(:id_parent, :id_user, :name)"); + query->bindValue(":id_parent", parentId); + query->bindValue(":id_user", userId); + query->bindValue(":name", name); + if (!execSqlQuery(query)) { + return DeckStorageResult::Failed; + } + return DeckStorageResult::Ok; +} + +void Servatrice_DatabaseInterface::deleteDeckFolderTree(int basePathId) +{ + checkSql(); + + QSqlQuery *query = prepareQuery("select id from {prefix}_decklist_folders where id_parent = :id_parent"); + query->bindValue(":id_parent", basePathId); + execSqlQuery(query); + while (query->next()) { + deleteDeckFolderTree(query->value(0).toInt()); + } + + query = prepareQuery("delete from {prefix}_decklist_files where id_folder = :id_folder"); + query->bindValue(":id_folder", basePathId); + execSqlQuery(query); + + query = prepareQuery("delete from {prefix}_decklist_folders where id = :id"); + query->bindValue(":id", basePathId); + execSqlQuery(query); +} + +void Servatrice_DatabaseInterface::deleteDeckFolder(int folderId) +{ + deleteDeckFolderTree(folderId); +} + int Servatrice_DatabaseInterface::getDeckOwnerId(int deckId) { QSqlQuery *query = prepareQuery("select id_user from {prefix}_decklist_files where id = :id"); diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index e463c520e..1f30c3eb5 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -68,6 +68,7 @@ private: QString sealLegacyBackup(const QString &storedName, const QString &credential); bool isStrictModeEnabled(bool &ok) const; int getDeckPathIdAt(int basePathId, QStringList path, int userId); + void deleteDeckFolderTree(int basePathId); bool addToList(const QString &table, int userId, int targetId); bool removeFromList(const QString &table, int userId, int targetId); @@ -124,6 +125,10 @@ public: int getDeckPathId(const QString &path, int userId) override; int getDeckOwnerId(int deckId) override; bool isDeckEffectivelyPublic(int deckId) override; + DeckStorageResult setDeckVisibility(int deckId, int userId, bool isPublic) override; + DeckStorageResult setDeckFolderVisibility(int folderId, int userId, bool isPublic) override; + DeckStorageResult createDeckFolder(int parentId, int userId, const QString &name) override; + void deleteDeckFolder(int folderId) override; QMap getBuddyList(const QString &name) override; QMap getIgnoreList(const QString &name) override; bool isInBuddyList(const QString &whoseList, const QString &who) override; diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index abecab545..1af48c7e6 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -762,46 +762,30 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckSetVisibility(const return Response::RespFunctionNotAllowed; } - sqlInterface->checkSql(); + databaseInterface->checkSql(); + Server_DatabaseInterface::DeckStorageResult result; if (cmd.has_deck_id()) { - QSqlQuery *query = - sqlInterface->prepareQuery("select 1 from {prefix}_decklist_files where id = :id and id_user = :id_user"); - query->bindValue(":id", cmd.deck_id()); - query->bindValue(":id_user", userInfo->id()); - sqlInterface->execSqlQuery(query); - if (!query->next()) { - return Response::RespNameNotFound; - } - - query = sqlInterface->prepareQuery("update {prefix}_decklist_files set is_public = :is_public where id = :id " - "and id_user = :id_user"); - query->bindValue(":is_public", cmd.is_public() ? 1 : 0); - query->bindValue(":id", cmd.deck_id()); - query->bindValue(":id_user", userInfo->id()); - if (!sqlInterface->execSqlQuery(query)) { - return Response::RespContextError; - } + result = databaseInterface->setDeckVisibility(cmd.deck_id(), userInfo->id(), cmd.is_public()); } else if (cmd.has_folder_path()) { const int folderId = databaseInterface->getDeckPathId(nameFromStdString(cmd.folder_path()), userInfo->id()); if (folderId == -1 || folderId == 0) { return Response::RespNameNotFound; } - - QSqlQuery *query = - sqlInterface->prepareQuery("update {prefix}_decklist_folders set is_public = :is_public where id = :id " - "and id_user = :id_user"); - query->bindValue(":is_public", cmd.is_public() ? 1 : 0); - query->bindValue(":id", folderId); - query->bindValue(":id_user", userInfo->id()); - if (!sqlInterface->execSqlQuery(query)) { - return Response::RespContextError; - } + result = databaseInterface->setDeckFolderVisibility(folderId, userInfo->id(), cmd.is_public()); } else { return Response::RespInvalidData; } - return Response::RespOk; + switch (result) { + case Server_DatabaseInterface::DeckStorageResult::Ok: + return Response::RespOk; + case Server_DatabaseInterface::DeckStorageResult::NotFound: + return Response::RespNameNotFound; + case Server_DatabaseInterface::DeckStorageResult::Failed: + return Response::RespContextError; + } + return Response::RespContextError; } Response::ResponseCode AbstractServerSocketInterface::cmdDeckDownloadPublic(const Command_DeckDownloadPublic &cmd, @@ -841,7 +825,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckNewDir(const Comman return Response::RespFunctionNotAllowed; } - sqlInterface->checkSql(); + databaseInterface->checkSql(); QString path = nameFromStdString(cmd.path()); int folderId = databaseInterface->getDeckPathId(path, userInfo->id()); @@ -854,37 +838,13 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckNewDir(const Comman return Response::RespContextError; // do not allow creation of paths that would be too long to delete } - QSqlQuery *query = sqlInterface->prepareQuery( - "insert into {prefix}_decklist_folders (id_parent, id_user, name) values(:id_parent, :id_user, :name)"); - query->bindValue(":id_parent", folderId); - query->bindValue(":id_user", userInfo->id()); - query->bindValue(":name", name); - if (!sqlInterface->execSqlQuery(query)) { + if (databaseInterface->createDeckFolder(folderId, userInfo->id(), name) != + Server_DatabaseInterface::DeckStorageResult::Ok) { return Response::RespContextError; } return Response::RespOk; } -void AbstractServerSocketInterface::deckDelDirHelper(int basePathId) -{ - sqlInterface->checkSql(); - QSqlQuery *query = - sqlInterface->prepareQuery("select id from {prefix}_decklist_folders where id_parent = :id_parent"); - query->bindValue(":id_parent", basePathId); - sqlInterface->execSqlQuery(query); - while (query->next()) { - deckDelDirHelper(query->value(0).toInt()); - } - - query = sqlInterface->prepareQuery("delete from {prefix}_decklist_files where id_folder = :id_folder"); - query->bindValue(":id_folder", basePathId); - sqlInterface->execSqlQuery(query); - - query = sqlInterface->prepareQuery("delete from {prefix}_decklist_folders where id = :id"); - query->bindValue(":id", basePathId); - sqlInterface->execSqlQuery(query); -} - void AbstractServerSocketInterface::sendServerMessage(const QString userName, const QString message) { AbstractServerSocketInterface *user = @@ -909,13 +869,13 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckDelDir(const Comman return Response::RespFunctionNotAllowed; } - sqlInterface->checkSql(); + databaseInterface->checkSql(); int basePathId = databaseInterface->getDeckPathId(nameFromStdString(cmd.path()), userInfo->id()); if ((basePathId == -1) || (basePathId == 0)) { return Response::RespNameNotFound; } - deckDelDirHelper(basePathId); + databaseInterface->deleteDeckFolder(basePathId); return Response::RespOk; } diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h index 63f799e36..a3c71d15b 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -153,7 +153,6 @@ private: Response::ResponseCode cmdDeckListOtherUser(const Command_DeckListOtherUser &cmd, ResponseContainer &rc); Response::ResponseCode cmdDeckSetVisibility(const Command_DeckSetVisibility &cmd, ResponseContainer &rc); Response::ResponseCode cmdDeckNewDir(const Command_DeckNewDir &cmd, ResponseContainer &rc); - void deckDelDirHelper(int basePathId); void sendServerMessage(const QString userName, const QString message); Response::ResponseCode cmdDeckDelDir(const Command_DeckDelDir &cmd, ResponseContainer &rc); Response::ResponseCode cmdDeckDel(const Command_DeckDel &cmd, ResponseContainer &rc);