2023-06-20 04:33:09 +00:00
# include "StarVersioningDatabase.hpp"
# include "StarDataStreamExtra.hpp"
# include "StarFormat.hpp"
# include "StarLexicalCast.hpp"
# include "StarFile.hpp"
# include "StarLogging.hpp"
# include "StarWorldLuaBindings.hpp"
# include "StarRootLuaBindings.hpp"
# include "StarUtilityLuaBindings.hpp"
# include "StarAssets.hpp"
# include "StarStoredFunctions.hpp"
# include "StarNpcDatabase.hpp"
# include "StarRoot.hpp"
# include "StarCelestialDatabase.hpp"
# include "StarJsonExtra.hpp"
namespace Star {
char const * const VersionedJson : : Magic = " SBVJ01 " ;
size_t const VersionedJson : : MagicStringSize = 6 ;
VersionedJson VersionedJson : : readFile ( String const & filename ) {
DataStreamIODevice ds ( File : : open ( filename , IOMode : : Read ) ) ;
if ( ds . readBytes ( MagicStringSize ) ! = ByteArray ( Magic , MagicStringSize ) )
2023-06-27 10:23:44 +00:00
throw IOException ( strf ( " Wrong magic bytes at start of versioned json file, expected '{}' " , Magic ) ) ;
2023-06-20 04:33:09 +00:00
return ds . read < VersionedJson > ( ) ;
}
void VersionedJson : : writeFile ( VersionedJson const & versionedJson , String const & filename ) {
DataStreamBuffer ds ;
ds . writeData ( Magic , MagicStringSize ) ;
ds . write ( versionedJson ) ;
File : : overwriteFileWithRename ( ds . takeData ( ) , filename ) ;
}
Json VersionedJson : : toJson ( ) const {
return JsonObject {
{ " id " , identifier } ,
{ " version " , version } ,
{ " content " , content }
} ;
}
VersionedJson VersionedJson : : fromJson ( Json const & source ) {
// Old versions of VersionedJson used '__' to distinguish between actual
// content and versioned content, but this is no longer necessary or
// relevant.
auto id = source . optString ( " id " ) . orMaybe ( source . optString ( " __id " ) ) ;
auto version = source . optUInt ( " version " ) . orMaybe ( source . optUInt ( " __version " ) ) ;
auto content = source . opt ( " content " ) . orMaybe ( source . opt ( " __content " ) ) ;
return { * id , ( VersionNumber ) * version , * content } ;
}
bool VersionedJson : : empty ( ) const {
return content . isNull ( ) ;
}
void VersionedJson : : expectIdentifier ( String const & expectedIdentifier ) const {
if ( identifier ! = expectedIdentifier )
2023-06-27 10:23:44 +00:00
throw VersionedJsonException : : format ( " VersionedJson identifier mismatch, expected '{}' but got '{}' " , expectedIdentifier , identifier ) ;
2023-06-20 04:33:09 +00:00
}
DataStream & operator > > ( DataStream & ds , VersionedJson & versionedJson ) {
ds . read ( versionedJson . identifier ) ;
// This is a holdover from when the verison number was optional in
// VersionedJson. We should convert versioned json binary files and the
// celestial chunk database and world storage to a new format eventually
versionedJson . version = ds . read < Maybe < VersionNumber > > ( ) . value ( ) ;
ds . read ( versionedJson . content ) ;
return ds ;
}
DataStream & operator < < ( DataStream & ds , VersionedJson const & versionedJson ) {
ds . write ( versionedJson . identifier ) ;
ds . write ( Maybe < VersionNumber > ( versionedJson . version ) ) ;
ds . write ( versionedJson . content ) ;
return ds ;
}
VersioningDatabase : : VersioningDatabase ( ) {
auto assets = Root : : singleton ( ) . assets ( ) ;
for ( auto const & pair : assets - > json ( " /versioning.config " ) . iterateObject ( ) )
m_currentVersions [ pair . first ] = pair . second . toUInt ( ) ;
for ( auto const & scriptFile : assets - > scan ( " /versioning/ " , " .lua " ) ) {
try {
auto scriptParts = File : : baseName ( scriptFile ) . splitAny ( " _. " ) ;
if ( scriptParts . size ( ) ! = 4 )
2023-06-27 10:23:44 +00:00
throw VersioningDatabaseException : : format ( " Script file '{}' filename not of the form <identifier>_<fromversion>_<toversion>.lua " , scriptFile ) ;
2023-06-20 04:33:09 +00:00
String identifier = scriptParts . at ( 0 ) ;
VersionNumber fromVersion = lexicalCast < VersionNumber > ( scriptParts . at ( 1 ) ) ;
VersionNumber toVersion = lexicalCast < VersionNumber > ( scriptParts . at ( 2 ) ) ;
m_versionUpdateScripts [ identifier . toLower ( ) ] . append ( { scriptFile , fromVersion , toVersion } ) ;
} catch ( StarException const & ) {
2023-06-27 10:23:44 +00:00
throw VersioningDatabaseException : : format ( " Error parsing version information from versioning script '{}' " , scriptFile ) ;
2023-06-20 04:33:09 +00:00
}
}
// Sort each set of update scripts first by fromVersion, and then in
// *reverse* order of toVersion. This way, the first matching script for a
// given fromVersion should take the json to the *furthest* toVersion.
for ( auto & pair : m_versionUpdateScripts ) {
pair . second . sort ( [ ] ( VersionUpdateScript const & lhs , VersionUpdateScript const & rhs ) {
if ( lhs . fromVersion ! = rhs . fromVersion )
return lhs . fromVersion < rhs . fromVersion ;
else
return lhs . toVersion < rhs . toVersion ;
} ) ;
}
}
VersionedJson VersioningDatabase : : makeCurrentVersionedJson ( String const & identifier , Json const & content ) const {
RecursiveMutexLocker locker ( m_mutex ) ;
return VersionedJson { identifier , m_currentVersions . get ( identifier ) , content } ;
}
bool VersioningDatabase : : versionedJsonCurrent ( VersionedJson const & versionedJson ) const {
RecursiveMutexLocker locker ( m_mutex ) ;
return versionedJson . version = = m_currentVersions . get ( versionedJson . identifier ) ;
}
VersionedJson VersioningDatabase : : updateVersionedJson ( VersionedJson const & versionedJson ) const {
RecursiveMutexLocker locker ( m_mutex ) ;
auto & root = Root : : singleton ( ) ;
CelestialMasterDatabase celestialDatabase ;
VersionedJson result = versionedJson ;
Maybe < VersionNumber > targetVersion = m_currentVersions . maybe ( versionedJson . identifier ) ;
if ( ! targetVersion )
2023-06-27 10:23:44 +00:00
throw VersioningDatabaseException : : format ( " Versioned JSON has an unregistered identifier '{}' " , versionedJson . identifier ) ;
2023-06-20 04:33:09 +00:00
LuaCallbacks celestialCallbacks ;
celestialCallbacks . registerCallback ( " parameters " , [ & celestialDatabase ] ( Json const & coord ) {
return celestialDatabase . parameters ( CelestialCoordinate ( coord ) ) - > diskStore ( ) ;
} ) ;
try {
for ( auto const & updateScript : m_versionUpdateScripts . value ( versionedJson . identifier . toLower ( ) ) ) {
if ( result . version > = * targetVersion )
break ;
if ( updateScript . fromVersion = = result . version ) {
auto scriptContext = m_luaRoot . createContext ( ) ;
scriptContext . load ( * root . assets ( ) - > bytes ( updateScript . script ) , updateScript . script ) ;
scriptContext . setCallbacks ( " root " , LuaBindings : : makeRootCallbacks ( ) ) ;
scriptContext . setCallbacks ( " sb " , LuaBindings : : makeUtilityCallbacks ( ) ) ;
scriptContext . setCallbacks ( " celestial " , celestialCallbacks ) ;
scriptContext . setCallbacks ( " versioning " , makeVersioningCallbacks ( ) ) ;
result . content = scriptContext . invokePath < Json > ( " update " , result . content ) ;
if ( ! result . content ) {
throw VersioningDatabaseException : : format (
2023-06-27 10:23:44 +00:00
" Could not bring versionedJson with identifier '{}' and version {} forward to current version of {}, conversion script from {} to {} returned null (un-upgradeable) " ,
2023-06-20 04:33:09 +00:00
versionedJson . identifier , result . version , targetVersion , updateScript . fromVersion , updateScript . toVersion ) ;
}
2023-06-27 10:23:44 +00:00
Logger : : debug ( " Brought versionedJson '{}' from version {} to {} " ,
2023-06-20 04:33:09 +00:00
versionedJson . identifier , result . version , updateScript . toVersion ) ;
result . version = updateScript . toVersion ;
}
}
} catch ( std : : exception const & e ) {
2023-06-27 10:23:44 +00:00
throw VersioningDatabaseException ( strf ( " Could not bring versionedJson with identifier '{}' and version {} forward to current version of {} " ,
2023-06-20 04:33:09 +00:00
versionedJson . identifier , result . version , targetVersion ) , e ) ;
}
if ( result . version > * targetVersion ) {
throw VersioningDatabaseException : : format (
2023-06-27 10:23:44 +00:00
" VersionedJson with identifier '{}' and version {} is newer than current version of {}, cannot load " ,
2023-06-20 04:33:09 +00:00
versionedJson . identifier , result . version , targetVersion ) ;
}
if ( result . version ! = * targetVersion ) {
throw VersioningDatabaseException : : format (
2023-06-27 10:23:44 +00:00
" Could not bring VersionedJson with identifier '{}' and version {} forward to current version of {}, best version was {} " ,
2023-06-20 04:33:09 +00:00
versionedJson . identifier , result . version , targetVersion , result . version ) ;
}
return result ;
}
Json VersioningDatabase : : loadVersionedJson ( VersionedJson const & versionedJson , String const & expectedIdentifier ) const {
versionedJson . expectIdentifier ( expectedIdentifier ) ;
if ( versionedJsonCurrent ( versionedJson ) )
return versionedJson . content ;
return updateVersionedJson ( versionedJson ) . content ;
}
LuaCallbacks VersioningDatabase : : makeVersioningCallbacks ( ) const {
LuaCallbacks versioningCallbacks ;
versioningCallbacks . registerCallback ( " loadVersionedJson " , [ this ] ( String const & storagePath ) {
try {
auto & root = Root : : singleton ( ) ;
String filePath = File : : fullPath ( root . toStoragePath ( storagePath ) ) ;
String basePath = File : : fullPath ( root . toStoragePath ( " . " ) ) ;
if ( ! filePath . beginsWith ( basePath ) )
throw VersioningDatabaseException : : format (
" Cannot load external VersionedJson outside of the Root storage path " ) ;
auto loadedJson = VersionedJson : : readFile ( filePath ) ;
return updateVersionedJson ( loadedJson ) . content ;
} catch ( IOException const & e ) {
Logger : : debug (
2023-06-27 10:23:44 +00:00
" Unable to load versioned JSON file {} in versioning script: {} " , storagePath , outputException ( e , false ) ) ;
2023-06-20 04:33:09 +00:00
return Json ( ) ;
}
} ) ;
return versioningCallbacks ;
}
}