Permalink
Browse files

Add logging inside saves which remembers which other saves material w…

…as taken from

The logging is saved inside the bson data in all online saves, local saves, stamps, and clipboard pieces. It is loaded back when reloading each of those.

See #474 for the format of the data. It is the same format for the bson data. Note that "links" is an array of objects. It can be recursive.

There is some effort to not duplicate information, we don't care if you loaded a stamp 10 times or if you are using the clipboard in your own save. Extra information is mostly not saved for your own stuff, only when you take material from other saves.

Press ctrl+a in debug builds to show what info it is currently saving in Client. Also enabled in snapshots for now.

There is one unrelated change in here, which fixes a crash pointed out by QuanTech. It was also save related and it was too close to the other changes to separate it into another commit. It fixes a crash when saving signs with invalid unicode. the BSON library doesn't like this, it was returning an error but we ignored it, which caused a crash. It now notices those errors. I also had to update several Serialize calls to check if it actually returned save data, or else it then would have started crashing there instead.
Also some debug prints were removed
  • Loading branch information...
jacob1 committed Jul 13, 2017
1 parent be6ac1d commit 8e5b0c760e08eaca84b77d2ce839d8c72c8eea4a
@@ -1011,13 +1011,14 @@ RequestStatus Client::UploadSave(SaveInfo & save)
lastError = "Empty game save";
return RequestFailure;
}
save.SetID(0);
gameData = save.GetGameSave()->Serialise(gameDataLength);
if (!gameData)
{
lastError = "Cannot upload game save";
lastError = "Cannot serialize game save";
return RequestFailure;
}
#ifdef SNAPSHOT
@@ -1141,14 +1142,29 @@ std::string Client::AddStamp(GameSave * saveData)
saveID
<< std::setw(8) << std::setfill('0') << std::hex << lastStampTime
<< std::setw(2) << std::setfill('0') << std::hex << lastStampName;
std::string filename = std::string(STAMPS_DIR PATH_SEP + saveID.str()+".stm").c_str();
MakeDirectory(STAMPS_DIR);
Json::Value stampInfo;
stampInfo["type"] = "stamp";
stampInfo["username"] = authUser.Username;
stampInfo["name"] = filename;
stampInfo["date"] = (Json::Value::UInt64)time(NULL);
if (authors.size() != 0)
{
// This is a stamp, always append full authorship info (even if same user)
stampInfo["links"].append(Client::Ref().authors);
}
saveData->authors = stampInfo;
unsigned int gameDataLength;
char * gameData = saveData->Serialise(gameDataLength);
if (gameData == NULL)
return "";
std::ofstream stampStream;
stampStream.open(std::string(STAMPS_DIR PATH_SEP + saveID.str()+".stm").c_str(), std::ios::binary);
stampStream.open(filename.c_str(), std::ios::binary);
stampStream.write((const char *)gameData, gameDataLength);
stampStream.close();
@@ -1997,6 +2013,73 @@ std::list<std::string> * Client::AddTag(int saveID, std::string tag)
return tags;
}
// stamp-specific wrapper for MergeAuthorInfo
// also used for clipboard and lua stamps
void Client::MergeStampAuthorInfo(Json::Value stampAuthors)
{
if (stampAuthors.size())
{
// when loading stamp/clipboard, only append info to authorship info (since we aren't replacing the save)
// unless there is nothing loaded currently, then set authors directly
if (authors.size())
{
if (authors["username"] != stampAuthors["username"])
{
// Don't add if it's exactly the same
if (stampAuthors["links"].size() != 1 || stampAuthors["links"][0] != Client::Ref().authors)
{
// 2nd arg of MergeAuthorInfo needs to be an array
Json::Value toAdd;
toAdd.append(stampAuthors);
MergeAuthorInfo(toAdd);
}
}
else if (stampAuthors["links"].size())
{
MergeAuthorInfo(stampAuthors["links"]);
}
}
else
authors = stampAuthors;
}
}
// linksToAdd is an array (NOT an object) of links to add to authors["links"]
void Client::MergeAuthorInfo(Json::Value linksToAdd)
{
for (Json::Value::ArrayIndex i = 0; i < linksToAdd.size(); i++)
{
// link is the same exact json we have open, don't do anything
if (linksToAdd[i] == authors)
return;
bool hasLink = false;
for (Json::Value::ArrayIndex j = 0; j < authors["links"].size(); j++)
{
// check everything in authors["links"] to see if it's the same json as what we are already adding
if (authors["links"][j] == linksToAdd[i])
hasLink = true;
}
if (!hasLink)
authors["links"].append(linksToAdd[i]);
}
}
// load current authors information into a json value (when saving everything: stamps, clipboard, local saves, and online saves)
void Client::SaveAuthorInfo(Json::Value *saveInto)
{
if (authors.size() != 0)
{
// Different username? Save full original save info
if (authors["username"] != (*saveInto)["username"])
(*saveInto)["links"].append(authors);
// This is probalby the same save
// Don't append another layer of links, just keep existing links
else if (authors["links"].size())
(*saveInto)["links"] = authors["links"];
}
}
// powder.pref preference getting / setting functions
// Recursively go down the json to get the setting we want
@@ -85,10 +85,25 @@ class Client: public Singleton<Client> {
Json::Value preferences;
Json::Value GetPref(Json::Value root, std::string prop, Json::Value defaultValue = Json::nullValue);
Json::Value SetPrefHelper(Json::Value root, std::string prop, Json::Value value);
// Save stealing info
Json::Value authors;
public:
std::vector<ClientListener*> listeners;
// Save stealing info
void MergeStampAuthorInfo(Json::Value linksToAdd);
void MergeAuthorInfo(Json::Value linksToAdd);
void OverwriteAuthorInfo(Json::Value overwrite) { authors = overwrite; }
void SaveAuthorInfo(Json::Value *saveInto);
void ClearAuthorInfo() { authors.clear(); }
bool IsAuthorsEmpty() { return authors.size() == 0; }
#if defined(DEBUG) || defined(SNAPSHOT)
std::string GetAuthorString() { return authors.toStyledString(); }
#endif
UpdateInfo GetUpdateInfo();
Client();
@@ -52,6 +52,7 @@ originalData(save.originalData)
blockHeight = save.blockHeight;
}
particlesCount = save.particlesCount;
authors = save.authors;
}
GameSave::GameSave(int width, int height)
@@ -73,9 +74,6 @@ GameSave::GameSave(std::vector<char> data)
expanded = false;
hasOriginalData = true;
originalData = data;
#ifdef DEBUG
std::cout << "Creating Collapsed save from data" << std::endl;
#endif
try
{
Expand();
@@ -99,9 +97,6 @@ GameSave::GameSave(std::vector<unsigned char> data)
expanded = false;
hasOriginalData = true;
originalData = std::vector<char>(data.begin(), data.end());
#ifdef DEBUG
std::cout << "Creating Collapsed save from data" << std::endl;
#endif
try
{
Expand();
@@ -153,6 +148,7 @@ void GameSave::InitData()
ambientHeat = NULL;
fromNewerVersion = false;
hasAmbientHeat = false;
authors.clear();
}
void GameSave::InitVars()
@@ -198,16 +194,10 @@ void GameSave::read(char * data, int dataSize)
{
if ((data[0]==0x66 && data[1]==0x75 && data[2]==0x43) || (data[0]==0x50 && data[1]==0x53 && data[2]==0x76))
{
#ifdef DEBUG
std::cout << "Reading PSv..." << std::endl;
#endif
readPSv(data, dataSize);
}
else if(data[0] == 'O' && data[1] == 'P' && data[2] == 'S')
{
#ifdef DEBUG
std::cout << "Reading OPS..." << std::endl;
#endif
if (data[3] != '1')
throw ParseException(ParseException::WrongVersion, "Save format from newer version");
readOPS(data, dataSize);
@@ -257,6 +247,8 @@ std::vector<char> GameSave::Serialise()
{
unsigned int dataSize;
char * data = Serialise(dataSize);
if (data == NULL)
return std::vector<char>();
std::vector<char> dataVect(data, data+dataSize);
delete[] data;
return dataVect;
@@ -644,6 +636,20 @@ void GameSave::readOPS(char * data, int dataLength)
fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter));
}
}
else if (!strcmp(bson_iterator_key(&iter), "authors"))
{
if (bson_iterator_type(&iter) == BSON_OBJECT)
{
// we need to clear authors because the save may be read multiple times in the stamp browser (loading and rendering twice)
// seems inefficient ...
authors.clear();
ConvertBsonToJson(&iter, &authors);
}
else
{
fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter));
}
}
}
//Read wall and fan data
@@ -1130,6 +1136,38 @@ void GameSave::readOPS(char * data, int dataLength)
free(partsSimIndex);
}
void GameSave::ConvertBsonToJson(bson_iterator *iter, Json::Value *j)
{
bson_iterator subiter;
bson_iterator_subiterator(iter, &subiter);
while (bson_iterator_next(&subiter))
{
std::string key = bson_iterator_key(&subiter);
if (bson_iterator_type(&subiter) == BSON_STRING)
(*j)[key] = bson_iterator_string(&subiter);
else if (bson_iterator_type(&subiter) == BSON_BOOL)
(*j)[key] = bson_iterator_bool(&subiter);
else if (bson_iterator_type(&subiter) == BSON_INT)
(*j)[key] = bson_iterator_int(&subiter);
else if (bson_iterator_type(&subiter) == BSON_LONG)
(*j)[key] = (Json::Value::Int64)bson_iterator_long(&subiter);
else if (bson_iterator_type(&subiter) == BSON_ARRAY)
{
bson_iterator arrayiter;
bson_iterator_subiterator(&subiter, &arrayiter);
while (bson_iterator_next(&arrayiter))
{
if (bson_iterator_type(&arrayiter) == BSON_OBJECT && !strcmp(bson_iterator_key(&arrayiter), "part"))
{
Json::Value tempPart;
ConvertBsonToJson(&arrayiter, &tempPart);
(*j)["links"].append(tempPart);
}
}
}
}
}
void GameSave::readPSv(char * data, int dataLength)
{
unsigned char * d = NULL, * c = (unsigned char *)data;
@@ -2277,10 +2315,14 @@ char * GameSave::serialiseOPS(unsigned int & dataLength)
}
bson_append_finish_array(&b);
}
bson_finish(&b);
#ifdef DEBUG
bson_print(&b);
#endif
if (authors.size())
{
bson_append_start_object(&b, "authors");
ConvertJsonToBson(&b, authors);
bson_append_finish_object(&b);
}
if (bson_finish(&b) == BSON_ERROR)
goto fin;
finalData = (unsigned char *)bson_data(&b);
finalDataLen = bson_size(&b);
@@ -2335,6 +2377,38 @@ char * GameSave::serialiseOPS(unsigned int & dataLength)
return (char*)outputData;
}
// converts a json object to bson
void GameSave::ConvertJsonToBson(bson *b, Json::Value j)
{
Json::Value::Members members = j.getMemberNames();
for (Json::Value::Members::iterator iter = members.begin(), end = members.end(); iter != end; ++iter)
{
std::string member = *iter;
if (j[member].isString())
bson_append_string(b, member.c_str(), j[member].asCString());
else if (j[member].isBool())
bson_append_bool(b, member.c_str(), j[member].asBool());
else if (j[member].isInt() || j[member].isUInt())
bson_append_int(b, member.c_str(), j[member].asInt());
else if (j[member].isInt64() || j[member].isUInt64())
bson_append_long(b, member.c_str(), j[member].asInt64());
else if (j[member].isArray())
{
bson_append_start_array(b, member.c_str());
for (Json::Value::ArrayIndex i = 0; i < j[member].size(); i++)
{
// only supports objects here because that is all we need
if (!j[member][i].isObject())
continue;
bson_append_start_object(b, "part");
ConvertJsonToBson(b, j[member][i]);
bson_append_finish_object(b);
}
bson_append_finish_array(b);
}
}
}
// deallocates a pointer to a 2D array and sets it to NULL
template <typename T>
void GameSave::Deallocate2DArray(T ***array, int blockHeight)
@@ -7,6 +7,7 @@
#include "Misc.h"
#include "bson/BSON.h"
#include "json/json.h"
#include "simulation/Sign.h"
#include "simulation/Particle.h"
@@ -61,7 +62,10 @@ class GameSave
//Element palette
typedef std::pair<std::string, int> PaletteItem;
std::vector<PaletteItem> palette;
// author information
Json::Value authors;
GameSave();
GameSave(GameSave & save);
GameSave(int width, int height);
@@ -112,7 +116,8 @@ class GameSave
void readOPS(char * data, int dataLength);
void readPSv(char * data, int dataLength);
char * serialiseOPS(unsigned int & dataSize);
//serialisePSv();
void ConvertJsonToBson(bson *b, Json::Value j);
void ConvertBsonToJson(bson_iterator *b, Json::Value *j);
};
#endif
Oops, something went wrong.

0 comments on commit 8e5b0c7

Please sign in to comment.