mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-09 03:23:24 -08:00
Compare commits
2 commits
11fdfb4610
...
a03f4c2fa4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a03f4c2fa4 | ||
|
|
10d46bafbd |
11 changed files with 230 additions and 18 deletions
|
|
@ -14,4 +14,7 @@ message Command_DeckUpload {
|
|||
reserved 5, 6, 8;
|
||||
reserved "banner_card_name", "banner_card_provider", "tags";
|
||||
optional string color_identity = 7;
|
||||
// Stable per-deck identity for two-way collection sync. Sent on first
|
||||
// synced upload, echoed back in ServerInfo_DeckStorage_File afterwards.
|
||||
optional string guid = 9;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,4 +6,9 @@ message Response_DeckDownload {
|
|||
optional Response_DeckDownload ext = 1007;
|
||||
}
|
||||
optional string deck = 1;
|
||||
// Sync identity echoed so the client can update its sidecar without an
|
||||
// extra list fetch.
|
||||
optional string guid = 2;
|
||||
optional string hash = 3;
|
||||
optional uint64 last_modified = 4;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,15 @@ message ServerInfo_DeckStorage_File {
|
|||
// Tag names associated with the deck. Empty for decks uploaded before the
|
||||
// tags column existed.
|
||||
repeated string tags = 6;
|
||||
// Stable per-deck identity for two-way collection sync. Empty for decks
|
||||
// uploaded before the sync migration until they are re-uploaded.
|
||||
optional string guid = 7;
|
||||
// Content hash (DeckList::getDeckHash) for change detection without
|
||||
// downloading the full list.
|
||||
optional string hash = 8;
|
||||
// Last modification time, seconds since Unix epoch. Drives newest-wins
|
||||
// default and conflict-dialog recency highlight.
|
||||
optional uint64 last_modified = 9;
|
||||
}
|
||||
|
||||
message ServerInfo_DeckStorage_Folder {
|
||||
|
|
|
|||
32
servatrice/migrations/servatrice_0037_to_0038.sql
Normal file
32
servatrice/migrations/servatrice_0037_to_0038.sql
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
-- Servatrice db migration from version 37 to version 38
|
||||
|
||||
-- Deck collection sync + versioning.
|
||||
--
|
||||
-- 1. Stable per-deck identity and change detection for two-way sync:
|
||||
-- guid links a local file to its server row across renames, hash detects
|
||||
-- content changes without downloading, last_modified drives newest-wins
|
||||
-- default and conflict highlighting, deleted implements tombstones so a
|
||||
-- delete on one device does not resurrect from the other.
|
||||
-- 2. Limited per-deck version history: every hash-changing upload inserts a
|
||||
-- row; the server prunes beyond the configured retention (default 20).
|
||||
|
||||
ALTER TABLE `cockatrice_decklist_files`
|
||||
ADD COLUMN `guid` char(36) NULL AFTER `tags`,
|
||||
ADD COLUMN `hash` char(40) NULL AFTER `guid`,
|
||||
ADD COLUMN `last_modified` datetime NULL AFTER `hash`,
|
||||
ADD COLUMN `deleted` tinyint(1) NOT NULL DEFAULT 0 AFTER `last_modified`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_deck_versions` (
|
||||
`id` int(7) unsigned zerofill NOT NULL auto_increment,
|
||||
`deck_id` int(7) unsigned zerofill NOT NULL,
|
||||
`version_no` int(7) unsigned NOT NULL,
|
||||
`content` text NOT NULL,
|
||||
`hash` char(40) NULL,
|
||||
`created_at` datetime NOT NULL,
|
||||
`created_by_clientid` varchar(15) NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `deck_id` (`deck_id`),
|
||||
FOREIGN KEY(`deck_id`) REFERENCES `cockatrice_decklist_files`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
UPDATE cockatrice_schema_version SET version=38 WHERE version=37;
|
||||
|
|
@ -479,3 +479,12 @@ max_decks_per_share=50
|
|||
; A value of 0 disables the limit.
|
||||
; Default: 50
|
||||
max_shares_per_day=50
|
||||
|
||||
|
||||
[deck_sync]
|
||||
|
||||
; Maximum number of stored versions kept per deck. Each upload with changed
|
||||
; content adds a version; older versions beyond this limit are pruned.
|
||||
; Minimum enforced: 1.
|
||||
; Default: 20
|
||||
max_versions=20
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_schema_version` (
|
|||
PRIMARY KEY (`version`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
INSERT INTO cockatrice_schema_version VALUES(37);
|
||||
INSERT INTO cockatrice_schema_version VALUES(38);
|
||||
|
||||
-- users and user data tables
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_users` (
|
||||
|
|
@ -71,11 +71,30 @@ CREATE TABLE IF NOT EXISTS `cockatrice_decklist_files` (
|
|||
`banner_card_provider` varchar(32) NULL,
|
||||
`color_identity` varchar(5) NULL,
|
||||
`tags` text NULL,
|
||||
`guid` char(36) NULL,
|
||||
`hash` char(40) NULL,
|
||||
`last_modified` datetime NULL,
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `FolderPlusUser` (`id_folder`,`id_user`),
|
||||
FOREIGN KEY(`id_user`) REFERENCES `cockatrice_users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- Per-deck version history for collection sync.
|
||||
-- One row per hash-changing upload; pruned beyond retention (default 20).
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_deck_versions` (
|
||||
`id` int(7) unsigned zerofill NOT NULL auto_increment,
|
||||
`deck_id` int(7) unsigned zerofill NOT NULL,
|
||||
`version_no` int(7) unsigned NOT NULL,
|
||||
`content` text NOT NULL,
|
||||
`hash` char(40) NULL,
|
||||
`created_at` datetime NOT NULL,
|
||||
`created_by_clientid` varchar(15) NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `deck_id` (`deck_id`),
|
||||
FOREIGN KEY(`deck_id`) REFERENCES `cockatrice_decklist_files`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- Temporary deck shares: a named bundle of decks that can be fetched by
|
||||
-- anyone who knows the (unguessable) token, until the share expires.
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_deck_share` (
|
||||
|
|
|
|||
|
|
@ -1122,6 +1122,11 @@ int Servatrice::getDeckShareMaxSharesPerDay() const
|
|||
return settingsCache->value("deck_share/max_shares_per_day", 50).toInt();
|
||||
}
|
||||
|
||||
int Servatrice::getDeckSyncMaxVersions() const
|
||||
{
|
||||
return qMax(1, settingsCache->value("deck_sync/max_versions", 20).toInt());
|
||||
}
|
||||
|
||||
int Servatrice::getNumberOfTCPPools() const
|
||||
{
|
||||
return settingsCache->value("server/number_pools", 1).toInt();
|
||||
|
|
|
|||
|
|
@ -292,6 +292,7 @@ public:
|
|||
int getDeckShareCleanupInterval() const;
|
||||
int getDeckShareMaxDecksPerShare() const;
|
||||
int getDeckShareMaxSharesPerDay() const;
|
||||
int getDeckSyncMaxVersions() const;
|
||||
int getMaxUsersPerAddress() const;
|
||||
int getMessageCountingInterval() const override;
|
||||
int getMaxMessageCountPerInterval() const override;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
class QSqlQuery;
|
||||
|
||||
#define DATABASE_SCHEMA_VERSION 37
|
||||
#define DATABASE_SCHEMA_VERSION 38
|
||||
|
||||
class Servatrice;
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@
|
|||
#include <QString>
|
||||
#include <QStringLiteral>
|
||||
#include <QTextStream>
|
||||
#include <QUuid>
|
||||
#include <QVariant>
|
||||
#include <QtGlobal>
|
||||
#include <algorithm>
|
||||
|
|
@ -681,8 +682,8 @@ bool AbstractServerSocketInterface::deckListHelper(int folderId,
|
|||
}
|
||||
|
||||
query = sqlInterface->prepareQuery("select id, name, upload_time, is_public, banner_card_name, "
|
||||
"banner_card_provider, color_identity, tags from {prefix}_decklist_files where "
|
||||
"id_folder = :id_folder and id_user = :id_user");
|
||||
"banner_card_provider, color_identity, tags, guid, hash, last_modified from "
|
||||
"{prefix}_decklist_files where id_folder = :id_folder and id_user = :id_user");
|
||||
query->bindValue(":id_folder", folderId);
|
||||
query->bindValue(":id_user", userId);
|
||||
if (!sqlInterface->execSqlQuery(query)) {
|
||||
|
|
@ -708,6 +709,12 @@ bool AbstractServerSocketInterface::deckListHelper(int folderId,
|
|||
for (const QString &tag : deserializeDeckTags(query->value(7).toString())) {
|
||||
newFile->add_tags(tag.toStdString());
|
||||
}
|
||||
newFile->set_guid(query->value(8).toString().toStdString());
|
||||
newFile->set_hash(query->value(9).toString().toStdString());
|
||||
const QDateTime lastModified = query->value(10).toDateTime();
|
||||
if (lastModified.isValid()) {
|
||||
newFile->set_last_modified(lastModified.toSecsSinceEpoch());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -1014,6 +1021,38 @@ QString sanitizeColorIdentity(const QString &colorIdentity)
|
|||
}
|
||||
} // namespace
|
||||
|
||||
bool AbstractServerSocketInterface::storeDeckVersion(int deckId, const QString &content, const QString &hash)
|
||||
{
|
||||
QSqlQuery *maxQuery =
|
||||
sqlInterface->prepareQuery("select coalesce(max(version_no), 0) from {prefix}_deck_versions where deck_id = "
|
||||
":deck_id");
|
||||
maxQuery->bindValue(":deck_id", deckId);
|
||||
if (!sqlInterface->execSqlQuery(maxQuery) || !maxQuery->next()) {
|
||||
return false;
|
||||
}
|
||||
const int nextVersion = maxQuery->value(0).toInt() + 1;
|
||||
|
||||
QSqlQuery *insertQuery =
|
||||
sqlInterface->prepareQuery("insert into {prefix}_deck_versions (deck_id, version_no, content, hash, "
|
||||
"created_at, created_by_clientid) values(:deck_id, :version_no, :content, :hash, "
|
||||
"NOW(), :created_by_clientid)");
|
||||
insertQuery->bindValue(":deck_id", deckId);
|
||||
insertQuery->bindValue(":version_no", nextVersion);
|
||||
insertQuery->bindValue(":content", content);
|
||||
insertQuery->bindValue(":hash", hash);
|
||||
insertQuery->bindValue(":created_by_clientid", QString::fromStdString(userInfo->clientid()));
|
||||
if (!sqlInterface->execSqlQuery(insertQuery)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlQuery *pruneQuery = sqlInterface->prepareQuery("delete from {prefix}_deck_versions where deck_id = :deck_id "
|
||||
"and version_no <= :min_kept_version");
|
||||
pruneQuery->bindValue(":deck_id", deckId);
|
||||
pruneQuery->bindValue(":min_kept_version", nextVersion - servatrice->getDeckSyncMaxVersions());
|
||||
sqlInterface->execSqlQuery(pruneQuery);
|
||||
return true;
|
||||
}
|
||||
|
||||
Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Command_DeckUpload &cmd,
|
||||
ResponseContainer &rc)
|
||||
{
|
||||
|
|
@ -1049,6 +1088,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman
|
|||
const QString bannerCardProvider = deck.getBannerCard().providerId.left(32);
|
||||
const QString tagsJson = serializeDeckTags(deck.getTags());
|
||||
const QString colorIdentity = sanitizeColorIdentity(nameFromStdString(cmd.color_identity()));
|
||||
const QString deckHash = deck.getDeckHash();
|
||||
QString guid = cmd.has_guid() ? nameFromStdString(cmd.guid()).left(36) : QString();
|
||||
if (QUuid::fromString(guid).isNull()) {
|
||||
guid = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
}
|
||||
|
||||
if (cmd.has_path()) {
|
||||
int folderId = getDeckPathId(nameFromStdString(cmd.path()));
|
||||
|
|
@ -1059,8 +1103,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman
|
|||
QSqlQuery *query =
|
||||
sqlInterface->prepareQuery("insert into {prefix}_decklist_files (id_folder, id_user, name, upload_time, "
|
||||
"content, is_public, banner_card_name, banner_card_provider, color_identity, "
|
||||
"tags) values(:id_folder, :id_user, :name, NOW(), :content, :is_public, "
|
||||
":banner_card_name, :banner_card_provider, :color_identity, :tags)");
|
||||
"tags, guid, hash, last_modified) values(:id_folder, :id_user, :name, NOW(), "
|
||||
":content, :is_public, :banner_card_name, :banner_card_provider, "
|
||||
":color_identity, :tags, :guid, :hash, NOW())");
|
||||
query->bindValue(":id_folder", folderId);
|
||||
query->bindValue(":id_user", userInfo->id());
|
||||
query->bindValue(":name", deckName);
|
||||
|
|
@ -1070,21 +1115,63 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman
|
|||
query->bindValue(":banner_card_provider", bannerCardProvider);
|
||||
query->bindValue(":color_identity", colorIdentity);
|
||||
query->bindValue(":tags", tagsJson);
|
||||
query->bindValue(":guid", guid);
|
||||
query->bindValue(":hash", deckHash);
|
||||
if (!sqlInterface->execSqlQuery(query)) {
|
||||
return Response::RespContextError;
|
||||
}
|
||||
const int deckId = query->lastInsertId().toInt();
|
||||
if (!storeDeckVersion(deckId, deckStr, deckHash)) {
|
||||
QSqlQuery *cleanupQuery = sqlInterface->prepareQuery("delete from {prefix}_decklist_files where id = :id");
|
||||
cleanupQuery->bindValue(":id", deckId);
|
||||
sqlInterface->execSqlQuery(cleanupQuery);
|
||||
return Response::RespContextError;
|
||||
}
|
||||
|
||||
QSqlQuery *stampQuery =
|
||||
sqlInterface->prepareQuery("select unix_timestamp(upload_time), unix_timestamp(last_modified) from "
|
||||
"{prefix}_decklist_files where id = :id and id_user = :id_user");
|
||||
stampQuery->bindValue(":id", deckId);
|
||||
stampQuery->bindValue(":id_user", userInfo->id());
|
||||
if (!sqlInterface->execSqlQuery(stampQuery) || !stampQuery->next()) {
|
||||
QSqlQuery *cleanupQuery = sqlInterface->prepareQuery("delete from {prefix}_decklist_files where id = :id");
|
||||
cleanupQuery->bindValue(":id", deckId);
|
||||
sqlInterface->execSqlQuery(cleanupQuery);
|
||||
return Response::RespContextError;
|
||||
}
|
||||
|
||||
Response_DeckUpload *re = new Response_DeckUpload;
|
||||
ServerInfo_DeckStorage_TreeItem *fileInfo = re->mutable_new_file();
|
||||
fileInfo->set_id(query->lastInsertId().toInt());
|
||||
fileInfo->set_id(deckId);
|
||||
fileInfo->set_name(deckName.toStdString());
|
||||
fileInfo->mutable_file()->set_creation_time(QDateTime::currentDateTime().toSecsSinceEpoch());
|
||||
fileInfo->mutable_file()->set_creation_time(stampQuery->value(0).toUInt());
|
||||
fileInfo->mutable_file()->set_is_public(cmd.has_is_public() && cmd.is_public());
|
||||
fileInfo->mutable_file()->set_guid(guid.toStdString());
|
||||
fileInfo->mutable_file()->set_hash(deckHash.toStdString());
|
||||
fileInfo->mutable_file()->set_last_modified(stampQuery->value(1).toULongLong());
|
||||
rc.setResponseExtension(re);
|
||||
} else if (cmd.has_deck_id()) {
|
||||
QSqlQuery *currentQuery =
|
||||
sqlInterface->prepareQuery("select guid, hash from {prefix}_decklist_files where id = :id and id_user = "
|
||||
":id_user");
|
||||
currentQuery->bindValue(":id", cmd.deck_id());
|
||||
currentQuery->bindValue(":id_user", userInfo->id());
|
||||
if (!sqlInterface->execSqlQuery(currentQuery)) {
|
||||
return Response::RespContextError;
|
||||
}
|
||||
if (!currentQuery->next()) {
|
||||
return Response::RespNameNotFound;
|
||||
}
|
||||
if (!QUuid::fromString(currentQuery->value(0).toString()).isNull()) {
|
||||
guid = currentQuery->value(0).toString();
|
||||
} else if (cmd.has_guid() && !QUuid::fromString(nameFromStdString(cmd.guid()).left(36)).isNull()) {
|
||||
guid = nameFromStdString(cmd.guid()).left(36);
|
||||
}
|
||||
const QString oldHash = currentQuery->value(1).toString();
|
||||
|
||||
QString updateQuery = "update {prefix}_decklist_files set name=:name, upload_time=NOW(), content=:content, "
|
||||
"banner_card_name=:banner_card_name, banner_card_provider=:banner_card_provider, "
|
||||
"color_identity=:color_identity, tags=:tags";
|
||||
"color_identity=:color_identity, tags=:tags, guid=:guid, hash=:hash, last_modified=NOW()";
|
||||
if (cmd.has_is_public()) {
|
||||
updateQuery += ", is_public=:is_public";
|
||||
}
|
||||
|
|
@ -1099,33 +1186,56 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman
|
|||
query->bindValue(":banner_card_provider", bannerCardProvider);
|
||||
query->bindValue(":color_identity", colorIdentity);
|
||||
query->bindValue(":tags", tagsJson);
|
||||
query->bindValue(":guid", guid);
|
||||
query->bindValue(":hash", deckHash);
|
||||
if (cmd.has_is_public()) {
|
||||
query->bindValue(":is_public", cmd.is_public() ? 1 : 0);
|
||||
}
|
||||
if (!sqlInterface->execSqlQuery(query)) {
|
||||
return Response::RespContextError;
|
||||
}
|
||||
|
||||
if (query->numRowsAffected() == 0) {
|
||||
return Response::RespNameNotFound;
|
||||
}
|
||||
|
||||
if (oldHash != deckHash) {
|
||||
if (!storeDeckVersion(cmd.deck_id(), deckStr, deckHash)) {
|
||||
return Response::RespContextError;
|
||||
}
|
||||
} else {
|
||||
// Self-heal a version gap left by an earlier failed version write:
|
||||
// a same-content re-upload must still leave a version behind.
|
||||
QSqlQuery *versionQuery =
|
||||
sqlInterface->prepareQuery("select count(*) from {prefix}_deck_versions where deck_id = :deck_id and "
|
||||
"hash = :hash");
|
||||
versionQuery->bindValue(":deck_id", cmd.deck_id());
|
||||
versionQuery->bindValue(":hash", deckHash);
|
||||
if (!sqlInterface->execSqlQuery(versionQuery) || !versionQuery->next()) {
|
||||
return Response::RespContextError;
|
||||
}
|
||||
if (versionQuery->value(0).toInt() == 0 && !storeDeckVersion(cmd.deck_id(), deckStr, deckHash)) {
|
||||
return Response::RespContextError;
|
||||
}
|
||||
}
|
||||
|
||||
QSqlQuery *visibilityQuery =
|
||||
sqlInterface->prepareQuery("select is_public from {prefix}_decklist_files where id = :id and "
|
||||
"id_user = :id_user");
|
||||
sqlInterface->prepareQuery("select is_public, unix_timestamp(upload_time), unix_timestamp(last_modified) "
|
||||
"from {prefix}_decklist_files where id = :id and id_user = :id_user");
|
||||
visibilityQuery->bindValue(":id", cmd.deck_id());
|
||||
visibilityQuery->bindValue(":id_user", userInfo->id());
|
||||
if (!sqlInterface->execSqlQuery(visibilityQuery)) {
|
||||
if (!sqlInterface->execSqlQuery(visibilityQuery) || !visibilityQuery->next()) {
|
||||
return Response::RespContextError;
|
||||
}
|
||||
const bool isPublic = visibilityQuery->next() && visibilityQuery->value(0).toBool();
|
||||
|
||||
Response_DeckUpload *re = new Response_DeckUpload;
|
||||
ServerInfo_DeckStorage_TreeItem *fileInfo = re->mutable_new_file();
|
||||
fileInfo->set_id(cmd.deck_id());
|
||||
fileInfo->set_name(deckName.toStdString());
|
||||
fileInfo->mutable_file()->set_creation_time(QDateTime::currentDateTime().toSecsSinceEpoch());
|
||||
fileInfo->mutable_file()->set_is_public(isPublic);
|
||||
fileInfo->mutable_file()->set_creation_time(visibilityQuery->value(1).toUInt());
|
||||
fileInfo->mutable_file()->set_is_public(visibilityQuery->value(0).toBool());
|
||||
fileInfo->mutable_file()->set_guid(guid.toStdString());
|
||||
fileInfo->mutable_file()->set_hash(deckHash.toStdString());
|
||||
fileInfo->mutable_file()->set_last_modified(visibilityQuery->value(2).toULongLong());
|
||||
rc.setResponseExtension(re);
|
||||
} else {
|
||||
return Response::RespInvalidData;
|
||||
|
|
@ -1147,11 +1257,29 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckDownload(const Comm
|
|||
} catch (Response::ResponseCode &r) {
|
||||
return r;
|
||||
}
|
||||
const QString deckNative = deck->writeToString_Native();
|
||||
delete deck;
|
||||
|
||||
QSqlQuery *syncQuery =
|
||||
sqlInterface->prepareQuery("select guid, hash, last_modified from {prefix}_decklist_files where id = :id and "
|
||||
"id_user = :id_user");
|
||||
syncQuery->bindValue(":id", cmd.deck_id());
|
||||
syncQuery->bindValue(":id_user", userInfo->id());
|
||||
if (!sqlInterface->execSqlQuery(syncQuery)) {
|
||||
return Response::RespContextError;
|
||||
}
|
||||
|
||||
Response_DeckDownload *re = new Response_DeckDownload;
|
||||
re->set_deck(deck->writeToString_Native().toStdString());
|
||||
re->set_deck(deckNative.toStdString());
|
||||
if (syncQuery->next()) {
|
||||
re->set_guid(syncQuery->value(0).toString().toStdString());
|
||||
re->set_hash(syncQuery->value(1).toString().toStdString());
|
||||
const QDateTime lastModified = syncQuery->value(2).toDateTime();
|
||||
if (lastModified.isValid()) {
|
||||
re->set_last_modified(lastModified.toSecsSinceEpoch());
|
||||
}
|
||||
}
|
||||
rc.setResponseExtension(re);
|
||||
delete deck;
|
||||
|
||||
return Response::RespOk;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ private:
|
|||
Response::ResponseCode cmdDeckDelDir(const Command_DeckDelDir &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdDeckDel(const Command_DeckDel &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdDeckUpload(const Command_DeckUpload &cmd, ResponseContainer &rc);
|
||||
bool storeDeckVersion(int deckId, const QString &content, const QString &hash);
|
||||
DeckList *getDeckFromDatabase(int deckId);
|
||||
Response::ResponseCode cmdDeckDownload(const Command_DeckDownload &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdDeckDownloadPublic(const Command_DeckDownloadPublic &cmd, ResponseContainer &rc);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue