diff --git a/include/openspace/interaction/sessionrecording.h b/include/openspace/interaction/sessionrecording.h index 2d3f705bfd..0c89dbbb9d 100644 --- a/include/openspace/interaction/sessionrecording.h +++ b/include/openspace/interaction/sessionrecording.h @@ -70,7 +70,7 @@ public: }; static const size_t FileHeaderVersionLength = 5; - static constexpr char FileHeaderVersion[] = "00.85"; + char FileHeaderVersion[FileHeaderVersionLength+1] = "01.00"; static const char DataFormatAsciiTag = 'A'; static const char DataFormatBinaryTag = 'B'; static const size_t keyframeHeaderSize_bytes = 33; @@ -455,10 +455,21 @@ public: * (will determine the file format conversion to convert from based on the file's * header version number). * + * \param filename name of the file to convert + * \param root (optional) the 5-character version string that represents the version + * of the original file to convert (before recursion started) */ - static bool convertFile(std::string filename); + bool convertFile(std::string filename, std::string root = "root"); -private: + /** + * Returns pointer to SessionRecording object that is the previous legacy version + * of the current version. + * + * \return Pointer to the previous version of the SessionRecording + */ + SessionRecording* getLegacy(); + +protected: properties::BoolProperty _renderPlaybackInformation; enum class RecordedType { @@ -532,15 +543,18 @@ private: double getNextTimestamp(); double getPrevTimestamp(); void cleanUpPlayback(); - static bool convertEntries(std::string& inFilename, std::ifstream& inFile, + bool convertEntries(std::string& inFilename, std::ifstream& inFile, DataMode mode, int lineNum, std::ofstream& outFile); - static bool convertCamera(std::ifstream& inFile, DataMode mode, int lineNum, + virtual bool convertCamera(std::ifstream& inFile, DataMode mode, int lineNum, std::string& inputLine, std::ofstream& outFile, unsigned char* buff); - static bool convertTimeChange(std::ifstream& inFile, DataMode mode, int lineNum, + virtual bool convertTimeChange(std::ifstream& inFile, DataMode mode, int lineNum, std::string& inputLine, std::ofstream& outFile, unsigned char* buff); - static bool convertScript(std::ifstream& inFile, DataMode mode, int lineNum, + virtual bool convertScript(std::ifstream& inFile, DataMode mode, int lineNum, std::string& inputLine, std::ofstream& outFile, unsigned char* buff); - static std::string determineConversionOutFilename(const std::string filename); + std::string determineConversionOutFilename(const std::string filename); + void readPlaybackFileHeader(const std::string filename, + std::string& conversionInFilename, std::ifstream& conversionInFile, + std::string& version, DataMode& mode); static void writeToFileBuffer(unsigned char* buf, size_t& idx, double src); static void writeToFileBuffer(unsigned char* buf, size_t& idx, std::vector& cv); @@ -592,8 +606,10 @@ private: int _conversionLineNum = 1; }; -class SessionRecording_legacy_0085 : SessionRecording { - struct ScriptMessage_legacy_0085 : datamessagestructures::ScriptMessage { +class SessionRecording_legacy_0085 : public SessionRecording { + char FileHeaderVersion[FileHeaderVersionLength+1] = "00.85"; + + struct ScriptMessage_legacy_0085 : public datamessagestructures::ScriptMessage { void read(std::istream* in) { size_t strLen; //Read string length from file @@ -608,9 +624,8 @@ class SessionRecording_legacy_0085 : SessionRecording { }; }; - void convertUp(std::string header, std::string fileToConvert); - - static constexpr char FileHeaderVersion[] = "01.00"; + bool convertScript(std::ifstream& inFile, DataMode mode, int lineNum, + std::string& inputLine, std::ofstream& outFile, unsigned char* buffer); }; } // namespace openspace diff --git a/include/openspace/interaction/tasks/convertrecformattask.h b/include/openspace/interaction/tasks/convertrecformattask.h index ba9f4f5ada..eb8107f494 100644 --- a/include/openspace/interaction/tasks/convertrecformattask.h +++ b/include/openspace/interaction/tasks/convertrecformattask.h @@ -59,6 +59,7 @@ private: std::ifstream _iFile; std::ofstream _oFile; SessionRecording::DataMode _fileFormatType; + std::string _version; std::string _valueFunctionLua; }; diff --git a/src/interaction/sessionrecording.cpp b/src/interaction/sessionrecording.cpp index b8a307366f..503d0f89fa 100644 --- a/src/interaction/sessionrecording.cpp +++ b/src/interaction/sessionrecording.cpp @@ -67,6 +67,10 @@ namespace { namespace openspace::interaction { +ConversionError::ConversionError(std::string msg) + : ghoul::RuntimeError(std::move(msg), "conversionError") +{} + SessionRecording::SessionRecording() : properties::PropertyOwner({ "SessionRecording", "Session Recording" }) , _renderPlaybackInformation(RenderPlaybackInfo, false) @@ -74,10 +78,15 @@ SessionRecording::SessionRecording() auto fTask = FactoryManager::ref().factory(); ghoul_assert(fTask, "No task factory existed"); fTask->registerClass("ConvertRecFormatTask"); + fTask->registerClass("ConvertRecFileVersionTask"); addProperty(_renderPlaybackInformation); } -SessionRecording::~SessionRecording() {} // NOLINT +SessionRecording::~SessionRecording() { // NOLINT + if (legacyVersion != nullptr) { + delete legacyVersion; + } +} void SessionRecording::deinitialize() { stopRecording(); @@ -1619,7 +1628,7 @@ void SessionRecording::saveKeyframeToFileBinary(unsigned char* buffer, size_t size, std::ofstream& file) { - file.write(reinterpret_cast(buffer), size); + file.write(reinterpret_cast(buffer), size); } void SessionRecording::saveKeyframeToFile(std::string entry, std::ofstream& file) { @@ -1664,140 +1673,82 @@ std::vector SessionRecording::playbackList() const { return fileList; } -scripting::LuaLibrary SessionRecording::luaLibrary() { - return { - "sessionRecording", - { - { - "startRecording", - &luascriptfunctions::startRecording, - {}, - "string", - "Starts a recording session. The string argument is the filename used " - "for the file where the recorded keyframes are saved. " - "The file data format is binary." - }, - { - "startRecordingAscii", - &luascriptfunctions::startRecordingAscii, - {}, - "string", - "Starts a recording session. The string argument is the filename used " - "for the file where the recorded keyframes are saved. " - "The file data format is ASCII." - }, - { - "stopRecording", - &luascriptfunctions::stopRecording, - {}, - "void", - "Stops a recording session" - }, - { - "startPlayback", - &luascriptfunctions::startPlaybackDefault, - {}, - "string", - "Starts a playback session with keyframe times that are relative to " - "the time since the recording was started (the same relative time " - "applies to the playback). When playback starts, the simulation time " - "is automatically set to what it was at recording time. The string " - "argument is the filename to pull playback keyframes from." - }, - { - "startPlaybackApplicationTime", - &luascriptfunctions::startPlaybackApplicationTime, - {}, - "string", - "Starts a playback session with keyframe times that are relative to " - "application time (seconds since OpenSpace application started). " - "The string argument is the filename to pull playback keyframes from." - }, - { - "startPlaybackRecordedTime", - &luascriptfunctions::startPlaybackRecordedTime, - {}, - "string", - "Starts a playback session with keyframe times that are relative to " - "the time since the recording was started (the same relative time " - "applies to the playback). The string argument is the filename to pull " - "playback keyframes from." - }, - { - "startPlaybackSimulationTime", - &luascriptfunctions::startPlaybackSimulationTime, - {}, - "string", - "Starts a playback session with keyframe times that are relative to " - "the simulated date & time. The string argument is the filename to pull " - "playback keyframes from." - }, - { - "stopPlayback", - &luascriptfunctions::stopPlayback, - {}, - "void", - "Stops a playback session before playback of all keyframes is complete" - }, - { - "enableTakeScreenShotDuringPlayback", - &luascriptfunctions::enableTakeScreenShotDuringPlayback, - {}, - "[int]", - "Enables that rendered frames should be saved during playback. The " - "parameter determines the number of frames that are exported per second " - "if this value is not provided, 60 frames per second will be exported." - }, - { - "disableTakeScreenShotDuringPlayback", - &luascriptfunctions::disableTakeScreenShotDuringPlayback, - {}, - "void", - "Used to disable that renderings are saved during playback" - } - } - }; +void SessionRecording::readPlaybackFileHeader(const std::string filename, + std::string& conversionInFilename, + std::ifstream& conversionInFile, + std::string& version, + DataMode& mode) +{ + if (filename.find("/") != std::string::npos) { + throw ConversionError("Playback filename musn't contain path (/) elements"); + } + conversionInFilename = absPath("${RECORDINGS}/" + filename); + if (!FileSys.fileExists(conversionInFilename)) { + throw ConversionError("Cannot find the specified playback file to convert."); + } + + + // Open in ASCII first + conversionInFile.open(conversionInFilename, std::ifstream::in); + // Read header + std::string readBackHeaderString = readHeaderElement( + conversionInFile, + FileHeaderTitle.length() + ); + + if (readBackHeaderString != FileHeaderTitle) { + throw ConversionError("File to convert does not contain expected header."); + } + version = readHeaderElement(conversionInFile, FileHeaderVersionLength); + std::string readDataMode = readHeaderElement(conversionInFile, 1); + if (readDataMode[0] == DataFormatAsciiTag) { + mode = DataMode::Ascii; + } + else if (readDataMode[0] == DataFormatBinaryTag) { + mode = DataMode::Binary; + } + else { + throw ConversionError("Unknown data type in header (needs Ascii or Binary)"); + } } -bool SessionRecording::convertFile(std::string filename) { +bool SessionRecording::convertFile(std::string filename, std::string version) { bool success = true; + std::string conversionInFilename; + std::ifstream conversionInFile; + DataMode mode; + bool preRecursion = (version == "root") ? true : false; + std::string throwOut; try { - if (filename.find("/") != std::string::npos) { - throw ConversionError("Playback filename musn't contain path (/) elements"); - } - std::string conversionInFilename = absPath("${RECORDINGS}/" + filename); - if (!FileSys.fileExists(conversionInFilename)) { - throw ConversionError("Cannot find the specified playback file to convert."); - } - - int conversionLineNum = 1; - // Open in ASCII first - std::ifstream conversionInFile; - conversionInFile.open(conversionInFilename, std::ifstream::in); - // Read header - std::string readBackHeaderString = readHeaderElement( - conversionInFile, - FileHeaderTitle.length() + readPlaybackFileHeader( + filename, + conversionInFilename, + conversionInFile, + preRecursion ? version : throwOut, + mode ); + int conversionLineNum = 1; + LINFO(fmt::format( + "Starting conversion on rec file {}, version {} in {} mode.", + filename, version, (mode == DataMode::Ascii) ? "ascii" : "binary" + )); - if (readBackHeaderString != FileHeaderTitle) { - throw ConversionError("File to convert does not contain expected header."); - } - if (readBackHeaderString != FileHeaderTitle) { - throw ConversionError("File to convert does not contain expected header."); - } - readHeaderElement(conversionInFile, FileHeaderVersionLength); - std::string readDataMode = readHeaderElement(conversionInFile, 1); - DataMode mode; - if (readDataMode[0] == DataFormatAsciiTag) { - mode = DataMode::Ascii; - } - else if (readDataMode[0] == DataFormatBinaryTag) { - mode = DataMode::Binary; - } - else { - throw ConversionError("Unknown data type in header (needs Ascii or Binary)"); + //If this instance of the SessionRecording class isn't the instance with the + // correct version of the file to be converted, then call getLegacy() to recurse + // to the next level down in the legacy subclasses until we get the right + // version, then proceed with conversion from there. + if (version.compare(FileHeaderVersion) != 0) { + conversionInFile.close(); + SessionRecording* old = getLegacy(); + old->convertFile(filename, version); + readPlaybackFileHeader( + filename, + conversionInFilename, + conversionInFile, + version, + mode + ); } if (!conversionInFile.is_open() || !conversionInFile.good()) { @@ -1829,6 +1780,8 @@ bool SessionRecording::convertFile(std::string filename) { conversionLineNum, conversionOutFile ); + conversionInFile.close(); + conversionOutFile.close(); } catch (ConversionError& c) { LERROR(c.message); @@ -1843,7 +1796,8 @@ bool SessionRecording::convertEntries(std::string& inFilename, std::ifstream& in { bool conversionStatusOk = true; std::string lineParsing; - std::shared_ptr buffer(new char[_saveBufferMaxSize_bytes]); + //std::shared_ptr buffer(new unsigned char[_saveBufferMaxSize_bytes]); + unsigned char* buffer = new unsigned char[_saveBufferMaxSize_bytes]; if (mode == DataMode::Binary) { unsigned char frameType; @@ -1867,7 +1821,7 @@ bool SessionRecording::convertEntries(std::string& inFilename, std::ifstream& in lineNum, lineParsing, outFile, - buffer + reinterpret_cast(buffer) ); } else if (frameType == HeaderTimeBinary) { @@ -1962,9 +1916,16 @@ bool SessionRecording::convertEntries(std::string& inFilename, std::ifstream& in lineNum, inFilename )); } + if (buffer != nullptr) { + delete buffer; + } return conversionStatusOk; } +SessionRecording* SessionRecording::getLegacy() { + legacyVersion = new SessionRecording_legacy_0085(); +} + std::string SessionRecording::determineConversionOutFilename(const std::string filename) { std::string conversionOutFilename; if (filename.substr(filename.find_last_of(".")) == FileExtensionBinary) { @@ -1981,14 +1942,135 @@ std::string SessionRecording::determineConversionOutFilename(const std::string f return absPath("${RECORDINGS}/convert/" + conversionOutFilename); } -void SessionRecording_legacy_0085::convertUp(std::string header, std::string fileToConvert) +bool SessionRecording_legacy_0085::convertScript(std::ifstream& inFile, DataMode mode, + int lineNum, std::string& inputLine, + std::ofstream& outFile, + unsigned char* buffer) { - if (strcmp(legacyVersion->FileHeaderVersion, FileHeaderVersion) == 0) { - convertFile(fileToConvert); - } - else { - //Oldest known version + Timestamps times; + ScriptMessage_legacy_0085 kf; + + bool success = readSingleKeyframeScript( + kf, + times, + mode, + inFile, + inputLine, + lineNum + ); + if (success) { + saveSingleKeyframeScript( + kf, + times, + mode, + outFile, + buffer + ); } + return success; +} + +scripting::LuaLibrary SessionRecording::luaLibrary() { + return { + "sessionRecording", + { + { + "startRecording", + &luascriptfunctions::startRecording, + {}, + "string", + "Starts a recording session. The string argument is the filename used " + "for the file where the recorded keyframes are saved. " + "The file data format is binary." + }, + { + "startRecordingAscii", + &luascriptfunctions::startRecordingAscii, + {}, + "string", + "Starts a recording session. The string argument is the filename used " + "for the file where the recorded keyframes are saved. " + "The file data format is ASCII." + }, + { + "stopRecording", + &luascriptfunctions::stopRecording, + {}, + "void", + "Stops a recording session" + }, + { + "startPlayback", + &luascriptfunctions::startPlaybackDefault, + {}, + "string", + "Starts a playback session with keyframe times that are relative to " + "the time since the recording was started (the same relative time " + "applies to the playback). When playback starts, the simulation time " + "is automatically set to what it was at recording time. The string " + "argument is the filename to pull playback keyframes from." + }, + { + "startPlaybackApplicationTime", + &luascriptfunctions::startPlaybackApplicationTime, + {}, + "string", + "Starts a playback session with keyframe times that are relative to " + "application time (seconds since OpenSpace application started). " + "The string argument is the filename to pull playback keyframes from." + }, + { + "startPlaybackRecordedTime", + &luascriptfunctions::startPlaybackRecordedTime, + {}, + "string", + "Starts a playback session with keyframe times that are relative to " + "the time since the recording was started (the same relative time " + "applies to the playback). The string argument is the filename to pull " + "playback keyframes from." + }, + { + "startPlaybackSimulationTime", + &luascriptfunctions::startPlaybackSimulationTime, + {}, + "string", + "Starts a playback session with keyframe times that are relative to " + "the simulated date & time. The string argument is the filename to pull " + "playback keyframes from." + }, + { + "stopPlayback", + &luascriptfunctions::stopPlayback, + {}, + "void", + "Stops a playback session before playback of all keyframes is complete" + }, + { + "enableTakeScreenShotDuringPlayback", + &luascriptfunctions::enableTakeScreenShotDuringPlayback, + {}, + "[int]", + "Enables that rendered frames should be saved during playback. The " + "parameter determines the number of frames that are exported per second " + "if this value is not provided, 60 frames per second will be exported." + }, + { + "disableTakeScreenShotDuringPlayback", + &luascriptfunctions::disableTakeScreenShotDuringPlayback, + {}, + "void", + "Used to disable that renderings are saved during playback" + }, + { + "fileFormatConversion", + &luascriptfunctions::fileFormatConversion, + {}, + "string", + "Performs a conversion of the specified file to the most most recent " + "file format, creating a copy of the recording file." + }, + } + }; } } // namespace openspace::interaction diff --git a/src/interaction/sessionrecording_lua.inl b/src/interaction/sessionrecording_lua.inl index 303e647326..79c59889b7 100644 --- a/src/interaction/sessionrecording_lua.inl +++ b/src/interaction/sessionrecording_lua.inl @@ -167,4 +167,23 @@ int disableTakeScreenShotDuringPlayback(lua_State* L) { return 0; } +int fileFormatConversion(lua_State* L) { + using ghoul::lua::luaTypeToString; + + const std::string convertFilePath = ghoul::lua::value( + L, + 1, + ghoul::lua::PopValue::Yes + ); + + if (convertFilePath.empty()) { + return luaL_error(L, "filepath string is empty"); + } + + global::sessionRecording.convertFile(convertFilePath); + + ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack"); + return 0; +} + } // namespace openspace::luascriptfunctions diff --git a/src/interaction/tasks/convertrecformattask.cpp b/src/interaction/tasks/convertrecformattask.cpp index 956639443b..75945477d0 100644 --- a/src/interaction/tasks/convertrecformattask.cpp +++ b/src/interaction/tasks/convertrecformattask.cpp @@ -128,10 +128,7 @@ void ConvertRecFormatTask::convert() { SessionRecording::FileHeaderTitle.c_str(), SessionRecording::FileHeaderTitle.length() ); - _oFile.write( - SessionRecording::FileHeaderVersion, - SessionRecording::FileHeaderVersionLength - ); + _oFile.write(_version.c_str(), SessionRecording::FileHeaderVersionLength); _oFile.close(); if (_fileFormatType == SessionRecording::DataMode::Ascii) { @@ -161,7 +158,7 @@ void ConvertRecFormatTask::determineFormatType() { } else { //Read version string and throw it away (and also line feed character at end) - SessionRecording::readHeaderElement(_iFile, + _version = SessionRecording::readHeaderElement(_iFile, SessionRecording::FileHeaderVersionLength); line = SessionRecording::readHeaderElement(_iFile, 1); SessionRecording::readHeaderElement(_iFile, 1);