osb/source/game/StarPlayerStorage.cpp

271 lines
8.8 KiB
C++
Raw Normal View History

2023-06-20 04:33:09 +00:00
#include "StarPlayerStorage.hpp"
#include "StarFile.hpp"
#include "StarLogging.hpp"
#include "StarIterator.hpp"
#include "StarTime.hpp"
#include "StarConfiguration.hpp"
#include "StarPlayer.hpp"
#include "StarAssets.hpp"
#include "StarEntityFactory.hpp"
#include "StarRoot.hpp"
2023-07-22 12:31:04 +00:00
#include "StarText.hpp"
2023-06-20 04:33:09 +00:00
namespace Star {
PlayerStorage::PlayerStorage(String const& storageDir) {
m_storageDirectory = storageDir;
if (!File::isDirectory(m_storageDirectory)) {
Logger::info("Creating player storage directory");
File::makeDirectory(m_storageDirectory);
return;
}
auto configuration = Root::singleton().configuration();
if (configuration->get("clearPlayerFiles").toBool()) {
Logger::info("Clearing all player files");
for (auto file : File::dirList(m_storageDirectory)) {
if (!file.second)
File::remove(File::relativeTo(m_storageDirectory, file.first));
}
} else {
auto versioningDatabase = Root::singleton().versioningDatabase();
auto entityFactory = Root::singleton().entityFactory();
for (auto file : File::dirList(m_storageDirectory)) {
if (file.second)
continue;
String filename = File::relativeTo(m_storageDirectory, file.first);
if (filename.endsWith(".player")) {
try {
Uuid uuid(file.first.rsplit('.').at(0));
auto& playerCacheData = m_savedPlayersCache[uuid];
playerCacheData = entityFactory->loadVersionedJson(VersionedJson::readFile(filename), EntityType::Player);
} catch (std::exception const& e) {
2023-06-27 10:23:44 +00:00
Logger::error("Error loading player file, ignoring! {} : {}", filename, outputException(e, false));
2023-06-20 04:33:09 +00:00
}
}
}
// Remove all the player entries that are missing player data or fail to
// load.
auto it = makeSMutableMapIterator(m_savedPlayersCache);
while (it.hasNext()) {
auto& entry = it.next();
if (entry.second.isNull()) {
it.remove();
} else {
try {
auto entityFactory = Root::singleton().entityFactory();
auto player = as<Player>(entityFactory->diskLoadEntity(EntityType::Player, entry.second));
if (player->uuid() != entry.first)
2023-06-27 10:23:44 +00:00
throw PlayerException(strf("Uuid mismatch in loaded player with filename uuid '{}'", entry.first.hex()));
2023-06-20 04:33:09 +00:00
} catch (StarException const& e) {
2023-06-27 10:23:44 +00:00
Logger::error("Failed to valid player with uuid {} : {}", entry.first.hex(), outputException(e, true));
2023-06-20 04:33:09 +00:00
it.remove();
}
}
}
}
try {
String filename = File::relativeTo(m_storageDirectory, "metadata");
m_metadata = Json::parseJson(File::readFileString(filename)).toObject();
if (auto order = m_metadata.value("order")) {
for (auto const& uuid : order.iterateArray())
m_savedPlayersCache.toBack(Uuid(uuid.toString()));
}
} catch (std::exception const& e) {
2023-06-27 10:23:44 +00:00
Logger::warn("Error loading player storage metadata file, resetting: {}", outputException(e, false));
2023-06-20 04:33:09 +00:00
}
}
PlayerStorage::~PlayerStorage() {
writeMetadata();
}
size_t PlayerStorage::playerCount() const {
RecursiveMutexLocker locker(m_mutex);
return m_savedPlayersCache.size();
}
Maybe<Uuid> PlayerStorage::playerUuidAt(size_t index) {
RecursiveMutexLocker locker(m_mutex);
if (index < m_savedPlayersCache.size())
return m_savedPlayersCache.keyAt(index);
else
return {};
}
Maybe<Uuid> PlayerStorage::playerUuidByName(String const& name, Maybe<Uuid> except) {
2023-07-22 12:31:04 +00:00
String cleanMatch = Text::stripEscapeCodes(name).toLower();
Maybe<Uuid> uuid;
RecursiveMutexLocker locker(m_mutex);
size_t longest = SIZE_MAX;
for (auto& cache : m_savedPlayersCache) {
if (except && *except == cache.first)
continue;
else if (auto name = cache.second.optQueryString("identity.name")) {
2023-07-22 12:31:04 +00:00
auto cleanName = Text::stripEscapeCodes(*name).toLower();
auto len = cleanName.size();
if (len < longest && cleanName.utf8().rfind(cleanMatch.utf8()) == 0) {
longest = len;
uuid = cache.first;
}
}
}
return uuid;
}
Json PlayerStorage::savePlayer(PlayerPtr const& player) {
2023-06-20 04:33:09 +00:00
auto entityFactory = Root::singleton().entityFactory();
auto versioningDatabase = Root::singleton().versioningDatabase();
RecursiveMutexLocker locker(m_mutex);
auto uuid = player->uuid();
auto& playerCacheData = m_savedPlayersCache[uuid];
auto newPlayerData = player->diskStore();
if (playerCacheData != newPlayerData) {
playerCacheData = newPlayerData;
VersionedJson versionedJson = entityFactory->storeVersionedJson(EntityType::Player, playerCacheData);
2023-06-27 10:23:44 +00:00
VersionedJson::writeFile(versionedJson, File::relativeTo(m_storageDirectory, strf("{}.player", uuid.hex())));
2023-06-20 04:33:09 +00:00
}
2023-07-22 12:31:04 +00:00
return newPlayerData;
2023-06-20 04:33:09 +00:00
}
2023-07-22 12:31:04 +00:00
Maybe<Json> PlayerStorage::maybeGetPlayerData(Uuid const& uuid) {
2023-06-20 04:33:09 +00:00
RecursiveMutexLocker locker(m_mutex);
2023-07-22 12:31:04 +00:00
if (auto cache = m_savedPlayersCache.ptr(uuid))
return *cache;
else
return {};
}
Json PlayerStorage::getPlayerData(Uuid const& uuid) {
auto data = maybeGetPlayerData(uuid);
if (!data)
2023-06-27 10:23:44 +00:00
throw PlayerException(strf("No such stored player with uuid '{}'", uuid.hex()));
2023-07-22 12:31:04 +00:00
else
return *data;
}
2023-06-20 04:33:09 +00:00
2023-07-22 12:31:04 +00:00
PlayerPtr PlayerStorage::loadPlayer(Uuid const& uuid) {
auto playerCacheData = getPlayerData(uuid);
2023-06-20 04:33:09 +00:00
auto entityFactory = Root::singleton().entityFactory();
try {
auto player = convert<Player>(entityFactory->diskLoadEntity(EntityType::Player, playerCacheData));
if (player->uuid() != uuid)
2023-06-27 10:23:44 +00:00
throw PlayerException(strf("Uuid mismatch in loaded player with filename uuid '{}'", uuid.hex()));
2023-06-20 04:33:09 +00:00
return player;
} catch (std::exception const& e) {
2023-06-27 10:23:44 +00:00
Logger::error("Error loading player file, ignoring! {}", outputException(e, false));
2023-07-22 12:31:04 +00:00
RecursiveMutexLocker locker(m_mutex);
2023-06-20 04:33:09 +00:00
m_savedPlayersCache.remove(uuid);
return {};
}
}
void PlayerStorage::deletePlayer(Uuid const& uuid) {
RecursiveMutexLocker locker(m_mutex);
if (!m_savedPlayersCache.contains(uuid))
2023-06-27 10:23:44 +00:00
throw PlayerException(strf("No such stored player with uuid '{}'", uuid.hex()));
2023-06-20 04:33:09 +00:00
m_savedPlayersCache.remove(uuid);
auto filePrefix = File::relativeTo(m_storageDirectory, uuid.hex());
auto removeIfExists = [&filePrefix](String suffix) {
if (File::exists(filePrefix + suffix)) {
File::remove(filePrefix + suffix);
}
};
removeIfExists(".player");
removeIfExists(".shipworld");
auto configuration = Root::singleton().configuration();
unsigned playerBackupFileCount = configuration->get("playerBackupFileCount").toUInt();
for (unsigned i = 1; i <= playerBackupFileCount; ++i) {
2023-06-27 10:23:44 +00:00
removeIfExists(strf(".player.bak{}", i));
removeIfExists(strf(".shipworld.bak{}", i));
2023-06-20 04:33:09 +00:00
}
}
WorldChunks PlayerStorage::loadShipData(Uuid const& uuid) {
RecursiveMutexLocker locker(m_mutex);
if (!m_savedPlayersCache.contains(uuid))
2023-06-27 10:23:44 +00:00
throw PlayerException(strf("No such stored player with uuid '{}'", uuid.hex()));
2023-06-20 04:33:09 +00:00
2023-06-27 10:23:44 +00:00
String filename = File::relativeTo(m_storageDirectory, strf("{}.shipworld", uuid.hex()));
2023-06-20 04:33:09 +00:00
try {
if (File::exists(filename))
return WorldStorage::getWorldChunksFromFile(filename);
} catch (StarException const& e) {
2023-06-27 10:23:44 +00:00
Logger::error("Failed to load shipworld file, removing {} : {}", filename, outputException(e, false));
2023-06-20 04:33:09 +00:00
File::remove(filename);
}
return {};
}
void PlayerStorage::applyShipUpdates(Uuid const& uuid, WorldChunks const& updates) {
RecursiveMutexLocker locker(m_mutex);
if (!m_savedPlayersCache.contains(uuid))
2023-06-27 10:23:44 +00:00
throw PlayerException(strf("No such stored player with uuid '{}'", uuid.hex()));
2023-06-20 04:33:09 +00:00
if (updates.empty())
return;
2023-06-27 10:23:44 +00:00
String filename = File::relativeTo(m_storageDirectory, strf("{}.shipworld", uuid.hex()));
2023-06-20 04:33:09 +00:00
WorldStorage::applyWorldChunksUpdateToFile(filename, updates);
}
void PlayerStorage::moveToFront(Uuid const& uuid) {
m_savedPlayersCache.toFront(uuid);
writeMetadata();
}
void PlayerStorage::backupCycle(Uuid const& uuid) {
RecursiveMutexLocker locker(m_mutex);
auto configuration = Root::singleton().configuration();
unsigned playerBackupFileCount = configuration->get("playerBackupFileCount").toUInt();
2023-06-27 10:23:44 +00:00
File::backupFileInSequence(File::relativeTo(m_storageDirectory, strf("{}.player", uuid.hex())), playerBackupFileCount, ".bak");
File::backupFileInSequence(File::relativeTo(m_storageDirectory, strf("{}.shipworld", uuid.hex())), playerBackupFileCount, ".bak");
File::backupFileInSequence(File::relativeTo(m_storageDirectory, strf("{}.metadata", uuid.hex())), playerBackupFileCount, ".bak");
2023-06-20 04:33:09 +00:00
}
void PlayerStorage::setMetadata(String key, Json value) {
auto& val = m_metadata[move(key)];
if (val != value) {
val = move(value);
writeMetadata();
}
}
Json PlayerStorage::getMetadata(String const& key) {
return m_metadata.value(key);
}
void PlayerStorage::writeMetadata() {
JsonArray order;
for (auto const& p : m_savedPlayersCache)
order.append(p.first.hex());
m_metadata["order"] = move(order);
String filename = File::relativeTo(m_storageDirectory, "metadata");
File::overwriteFileWithRename(Json(m_metadata).printJson(0), filename);
}
}