mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 09:33:25 -08:00
[Logs] Browse room history without filters with paginated infinite scroll (#7531)
* [Logs] Browse room history without filters with paginated infinite scroll * [Logs] Continue pagination when switching log tabs --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
3cf8bdb00e
commit
94e6281d87
7 changed files with 321 additions and 91 deletions
|
|
@ -33,6 +33,7 @@
|
|||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
#include <QVariantList>
|
||||
#include <QtGlobal>
|
||||
#include <google/protobuf/stubs/common.h>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <libcockatrice/protocol/pb/game_replay.pb.h>
|
||||
|
|
@ -2249,8 +2250,14 @@ QList<ServerInfo_ChatMessage> Servatrice_DatabaseInterface::getMessageLogHistory
|
|||
bool &game,
|
||||
bool &room,
|
||||
int &range,
|
||||
int &maxresults)
|
||||
int &maxresults,
|
||||
int offset,
|
||||
const QString &beforeTime,
|
||||
bool *hasMore)
|
||||
{
|
||||
if (hasMore != nullptr) {
|
||||
*hasMore = false;
|
||||
}
|
||||
|
||||
QList<ServerInfo_ChatMessage> results;
|
||||
ServerInfo_ChatMessage chatMessage;
|
||||
|
|
@ -2259,8 +2266,31 @@ QList<ServerInfo_ChatMessage> Servatrice_DatabaseInterface::getMessageLogHistory
|
|||
return results;
|
||||
}
|
||||
|
||||
if (user.isEmpty() && ipaddress.isEmpty() && gameid.isEmpty() && gamename.isEmpty()) {
|
||||
// To ensure quick results and minimal lag, require an indexed field
|
||||
// Page size guard: always bound the scan so a filter-less browse of room
|
||||
// history cannot become a 50M-row fetch. Zero means "client did not set a
|
||||
// size", so fall back to one smooth-scroll page.
|
||||
const int pageSize = maxresults <= 0 ? 200 : qBound(1, maxresults, 1000);
|
||||
maxresults = pageSize;
|
||||
const int safeOffset = qMax(0, offset);
|
||||
|
||||
const bool hasIndexedFilter = !user.isEmpty() || !ipaddress.isEmpty() || !gameid.isEmpty() || !gamename.isEmpty();
|
||||
const bool hasLocationFilter = chat || game || room;
|
||||
if (!hasLocationFilter) {
|
||||
// No target_type restriction would mix private chat into a room/game
|
||||
// browse, so refuse rather than leak.
|
||||
return results;
|
||||
}
|
||||
// A browse narrowed only by a message substring has no index to serve it, and `range`
|
||||
// has no upper bound on the wire: the client offers 20 days, but a hand-built command
|
||||
// can ask for any value and every LIKE candidate in it is a row the server examines.
|
||||
// Clamping here keeps the guard independent of what the client happens to send.
|
||||
constexpr int maxFilterlessRangeHours = 20 * 24;
|
||||
if (!hasIndexedFilter && range > maxFilterlessRangeHours) {
|
||||
range = maxFilterlessRangeHours;
|
||||
}
|
||||
if (!hasIndexedFilter && range <= 0) {
|
||||
// Filter-less browse must stay time-bounded (idx_log_time) so the
|
||||
// query stays an index range scan instead of a full table scan.
|
||||
return results;
|
||||
}
|
||||
|
||||
|
|
@ -2315,10 +2345,23 @@ QList<ServerInfo_ChatMessage> Servatrice_DatabaseInterface::getMessageLogHistory
|
|||
queryString.append(" AND log_time >= DATE_SUB(now(), INTERVAL :range_time HOUR)");
|
||||
}
|
||||
|
||||
if (maxresults) {
|
||||
queryString.append(" LIMIT :limit_size");
|
||||
if (!beforeTime.isEmpty()) {
|
||||
// Pages after the first run against the snapshot fixed by the first one, so rows
|
||||
// logged while the moderator scrolls can no longer push the OFFSET window down and
|
||||
// repeat a row already on screen.
|
||||
queryString.append(" AND log_time <= :before_time");
|
||||
}
|
||||
|
||||
// Newest-first keeps "going back from today" stable across pages and lets
|
||||
// the client append older rows at the bottom for smooth infinite scroll.
|
||||
queryString.append(" ORDER BY log_time DESC");
|
||||
|
||||
// Fetch one extra row when the caller wants a has_more cursor so a full
|
||||
// page does not need a second empty round-trip to prove exhaustion.
|
||||
const bool detectMore = (hasMore != nullptr);
|
||||
const int fetchSize = detectMore ? pageSize + 1 : pageSize;
|
||||
queryString.append(" LIMIT :limit_size OFFSET :offset_size");
|
||||
|
||||
QSqlQuery *query = prepareQuery(queryString);
|
||||
if (!user.isEmpty()) {
|
||||
query->bindValue(":user_name", user);
|
||||
|
|
@ -2338,9 +2381,11 @@ QList<ServerInfo_ChatMessage> Servatrice_DatabaseInterface::getMessageLogHistory
|
|||
if (range) {
|
||||
query->bindValue(":range_time", range);
|
||||
}
|
||||
if (maxresults) {
|
||||
query->bindValue(":limit_size", maxresults);
|
||||
if (!beforeTime.isEmpty()) {
|
||||
query->bindValue(":before_time", beforeTime);
|
||||
}
|
||||
query->bindValue(":limit_size", fetchSize);
|
||||
query->bindValue(":offset_size", safeOffset);
|
||||
|
||||
if (!execSqlQuery(query)) {
|
||||
qCWarning(DatabaseInterfaceLog) << "Failed to collect log history information: SQL Error";
|
||||
|
|
@ -2360,6 +2405,11 @@ QList<ServerInfo_ChatMessage> Servatrice_DatabaseInterface::getMessageLogHistory
|
|||
results << chatMessage;
|
||||
}
|
||||
|
||||
if (detectMore && results.size() > pageSize) {
|
||||
results.removeLast();
|
||||
*hasMore = true;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -219,7 +219,10 @@ public:
|
|||
bool &game,
|
||||
bool &room,
|
||||
int &range,
|
||||
int &maxresults);
|
||||
int &maxresults,
|
||||
int offset = 0,
|
||||
const QString &beforeTime = QString(),
|
||||
bool *hasMore = nullptr);
|
||||
QList<ServerInfo_UserSession> getUserSessions(const QString &userName, int limit);
|
||||
QList<ServerInfo_UserAlt> getUserAlts(const QString &userName);
|
||||
QList<ServerInfo_ModeratorLogin> getModeratorLastLogins();
|
||||
|
|
|
|||
|
|
@ -1743,12 +1743,17 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Com
|
|||
|
||||
int dateRange = cmd.date_range();
|
||||
int maximumResults = cmd.maximum_results();
|
||||
const int offset = cmd.has_offset() ? static_cast<int>(cmd.offset()) : 0;
|
||||
const QString beforeTime = cmd.has_before_time() ? QString::fromStdString(cmd.before_time()) : QString();
|
||||
|
||||
Response_ViewLogHistory *re = new Response_ViewLogHistory;
|
||||
|
||||
if (servatrice->getEnableLogQuery()) {
|
||||
QListIterator<ServerInfo_ChatMessage> messageIterator(sqlInterface->getMessageLogHistory(
|
||||
userName, ipAddress, gameName, gameID, message, chatType, gameType, roomType, dateRange, maximumResults));
|
||||
bool hasMore = false;
|
||||
QList<ServerInfo_ChatMessage> history =
|
||||
sqlInterface->getMessageLogHistory(userName, ipAddress, gameName, gameID, message, chatType, gameType,
|
||||
roomType, dateRange, maximumResults, offset, beforeTime, &hasMore);
|
||||
QListIterator<ServerInfo_ChatMessage> messageIterator(history);
|
||||
while (messageIterator.hasNext()) {
|
||||
ServerInfo_ChatMessage chatMessage = messageIterator.next();
|
||||
if (!allowPrivateChat) {
|
||||
|
|
@ -1756,6 +1761,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Com
|
|||
}
|
||||
re->add_log_message()->CopyFrom(chatMessage);
|
||||
}
|
||||
re->set_has_more(hasMore);
|
||||
} else {
|
||||
ServerInfo_ChatMessage chatMessage;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue