From 934be50984388368e7d0322b316eb0ecd49adf12 Mon Sep 17 00:00:00 2001 From: David Li Date: Wed, 27 Nov 2024 20:52:22 -0800 Subject: [PATCH 1/2] feat: Proof-of-concept Implementation for new save format (#5771) Signed-off-by: David Li --- CMakeLists.txt | 1 + src/CMakeLists.txt | 4 + src/diary.cpp | 16 +++- src/game.cpp | 5 + src/game.h | 6 ++ src/mapbuffer.cpp | 17 ++-- src/mapbuffer.h | 2 +- src/overmap.cpp | 4 +- src/overmapbuffer.cpp | 4 +- src/worlddb.cpp | 214 ++++++++++++++++++++++++++++++++++++++++++ src/worlddb.h | 36 +++++++ src/worldfactory.cpp | 17 +++- src/worldfactory.h | 3 + 13 files changed, 308 insertions(+), 21 deletions(-) create mode 100644 src/worlddb.cpp create mode 100644 src/worlddb.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 80337602f1a9..859859ee3cb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,6 +113,7 @@ include(CheckCXXCompilerFlag) #SET(CMAKE_SHARED_LIBRARY_CXX_FLAGS "${CMAKE_SHARED_LIBRARY_CXX_FLAGS} -m32") find_package(PkgConfig) +find_package(SQLite3) if (NOT DYNAMIC_LINKING) if(NOT MSVC) set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.dll.a") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 474a0f9a10c6..4e5f79608ef4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -69,6 +69,8 @@ if (TILES) ${CMAKE_SOURCE_DIR}/pch/main-pch.hpp) endif () + target_link_libraries(cataclysm-bn-tiles-common PUBLIC SQLite::SQLite3) + if (CMAKE_USE_PTHREADS_INIT) target_compile_options(cataclysm-bn-tiles-common PUBLIC "-pthread") endif () @@ -181,6 +183,8 @@ if (CURSES) target_include_directories(cataclysm-bn-common PUBLIC ${CURSES_INCLUDE_DIR}) target_link_libraries(cataclysm-bn-common PUBLIC ${CURSES_LIBRARIES}) + target_link_libraries(cataclysm-bn-tiles-common PUBLIC SQLite::SQLite3) + if (CMAKE_USE_PTHREADS_INIT) target_compile_options(cataclysm-bn-common PUBLIC "-pthread") endif () diff --git a/src/diary.cpp b/src/diary.cpp index 50364ef6fae7..740f00917060 100644 --- a/src/diary.cpp +++ b/src/diary.cpp @@ -775,9 +775,12 @@ void diary::export_to_md( bool last_export ) bool diary::store() { + if ( !g->get_world_db() ) { + return false; + } + std::string name = base64_encode( get_avatar().get_save_id() + "_diary" ); - std::string path = g->get_world_base_save_path() + "/" + name + ".json"; - const bool is_writen = write_to_file( path, [&]( std::ostream & fout ) { + const bool is_writen = g->get_world_db()->write_to_file( name + ".json", [&]( std::ostream & fout ) { serialize( fout ); }, _( "diary data" ) ); return is_writen; @@ -826,10 +829,13 @@ void diary::serialize( JsonOut &jsout ) void diary::load() { + if ( !g->get_world_db() ) { + return; + } + std::string name = base64_encode( get_avatar().get_save_id() + "_diary" ); - std::string path = g->get_world_base_save_path() + "/" + name + ".json"; - if( file_exist( path ) ) { - read_from_file( path, [&]( std::istream & fin ) { + if( g->get_world_db()->file_exist( name + ".json" ) ) { + g->get_world_db()->read_from_file( name + ".json", [&]( std::istream & fin ) { deserialize( fin ); } ); } diff --git a/src/game.cpp b/src/game.cpp index 460c6f458566..b800bdbc0a12 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -12141,6 +12141,11 @@ std::string game::get_world_base_save_path() const return world_generator->active_world->folder_path(); } +shared_ptr_fast game::get_world_db() +{ + return world_generator->active_world_db; +} + void game::shift_destination_preview( point delta ) { for( tripoint &p : destination_preview ) { diff --git a/src/game.h b/src/game.h index 5ce0b31a05e0..f659245b32b7 100644 --- a/src/game.h +++ b/src/game.h @@ -29,6 +29,7 @@ #include "pimpl.h" #include "point.h" #include "type_id.h" +#include "worlddb.h" class Character; class Creature_tracker; @@ -168,6 +169,11 @@ class game */ std::string get_world_base_save_path() const; + /** + * @return The current world database, or nullptr if no world is loaded. + */ + shared_ptr_fast get_world_db(); + /** * @brief Should be invoked whenever options change. */ diff --git a/src/mapbuffer.cpp b/src/mapbuffer.cpp index bc017f9a7e3d..5698b6421bb4 100644 --- a/src/mapbuffer.cpp +++ b/src/mapbuffer.cpp @@ -33,7 +33,7 @@ static std::string find_quad_path( const std::string &dirname, const tripoint &o static std::string find_dirname( const tripoint &om_addr ) { const tripoint segment_addr = omt_to_seg_copy( om_addr ); - return string_format( "%s/maps/%d.%d.%d", g->get_world_base_save_path(), segment_addr.x, + return string_format( "maps/%d.%d.%d", segment_addr.x, segment_addr.y, segment_addr.z ); } @@ -97,7 +97,7 @@ submap *mapbuffer::lookup_submap( const tripoint &p ) void mapbuffer::save( bool delete_after_save ) { - assure_dir_exist( g->get_world_base_save_path() + "/maps" ); + // assure_dir_exist( g->get_world_base_save_path() + "/maps" ); int num_saved_submaps = 0; int num_total_submaps = submaps.size(); @@ -144,7 +144,7 @@ void mapbuffer::save( bool delete_after_save ) // delete_on_save deletes everything, otherwise delete submaps // outside the current map. const bool zlev_del = !map_has_zlevels && om_addr.z != g->get_levz(); - save_quad( dirname, quad_path, om_addr, submaps_to_delete, + save_quad( quad_path, om_addr, submaps_to_delete, delete_after_save || zlev_del || om_addr.x < map_origin.x || om_addr.y < map_origin.y || om_addr.x > map_origin.x + HALF_MAPSIZE || @@ -158,7 +158,7 @@ void mapbuffer::save( bool delete_after_save ) get_distribution_grid_tracker().on_saved(); } -void mapbuffer::save_quad( const std::string &dirname, const std::string &filename, +void mapbuffer::save_quad( const std::string &filename, const tripoint &om_addr, std::list &submaps_to_delete, bool delete_after_save ) { @@ -199,8 +199,7 @@ void mapbuffer::save_quad( const std::string &dirname, const std::string &filena } // Don't create the directory if it would be empty - assure_dir_exist( dirname ); - write_to_file( filename, [&]( std::ostream & fout ) { + g->get_world_db()->write_to_file( filename, [&]( std::ostream & fout ) { JsonOut jsout( fout ); jsout.start_array(); for( auto &submap_addr : submap_addrs ) { @@ -247,20 +246,20 @@ submap *mapbuffer::unserialize_submaps( const tripoint &p ) const std::string dirname = find_dirname( om_addr ); std::string quad_path = find_quad_path( dirname, om_addr ); - if( !file_exist( quad_path ) ) { + if( !g->get_world_db()->file_exist( quad_path ) ) { // Fix for old saves where the path was generated using std::stringstream, which // did format the number using the current locale. That formatting may insert // thousands separators, so the resulting path is "map/1,234.7.8.map" instead // of "map/1234.7.8.map". std::ostringstream buffer; buffer << dirname << "/" << om_addr.x << "." << om_addr.y << "." << om_addr.z << ".map"; - if( file_exist( buffer.str() ) ) { + if( g->get_world_db()->file_exist( buffer.str() ) ) { quad_path = buffer.str(); } } using namespace std::placeholders; - if( !read_from_file_optional_json( quad_path, std::bind( &mapbuffer::deserialize, this, _1 ) ) ) { + if( !g->get_world_db()->read_from_file_optional_json( quad_path, std::bind( &mapbuffer::deserialize, this, _1 ) ) ) { // If it doesn't exist, trigger generating it. return nullptr; } diff --git a/src/mapbuffer.h b/src/mapbuffer.h index 0b18b6c7efa5..3c737e2f8c89 100644 --- a/src/mapbuffer.h +++ b/src/mapbuffer.h @@ -79,7 +79,7 @@ class mapbuffer void remove_submap( tripoint addr ); submap *unserialize_submaps( const tripoint &p ); void deserialize( JsonIn &jsin ); - void save_quad( const std::string &dirname, const std::string &filename, + void save_quad( const std::string &filename, const tripoint &om_addr, std::list &submaps_to_delete, bool delete_after_save ); submap_map_t submaps; diff --git a/src/overmap.cpp b/src/overmap.cpp index 07f2c2d8135a..d2bbd6992e5f 100644 --- a/src/overmap.cpp +++ b/src/overmap.cpp @@ -6086,7 +6086,7 @@ void overmap::open( overmap_special_batch &enabled_specials ) overmap::unserialize( fin, terfilename ); }; - if( read_from_file_optional( terfilename, ter_reader ) ) { + if( g->get_world_db()->read_from_file_optional( terfilename, ter_reader ) ) { const std::string plrfilename = overmapbuffer::player_filename( loc ); const auto plr_reader = [&]( std::istream & fin ) { overmap::unserialize_view( fin, plrfilename ); @@ -6115,7 +6115,7 @@ void overmap::save() const serialize_view( stream ); } ); - write_to_file( overmapbuffer::terrain_filename( loc ), [&]( std::ostream & stream ) { + g->get_world_db()->write_to_file( overmapbuffer::terrain_filename( loc ), [&]( std::ostream & stream ) { serialize( stream ); } ); } diff --git a/src/overmapbuffer.cpp b/src/overmapbuffer.cpp index 69eeeea1a076..e510cde6ae00 100644 --- a/src/overmapbuffer.cpp +++ b/src/overmapbuffer.cpp @@ -68,7 +68,7 @@ omt_route_params::~omt_route_params() = default; std::string overmapbuffer::terrain_filename( const point_abs_om &p ) { - return string_format( "%s/o.%d.%d", g->get_world_base_save_path(), p.x(), p.y() ); + return string_format( "o.%d.%d", p.x(), p.y() ); } std::string overmapbuffer::player_filename( const point_abs_om &p ) @@ -267,7 +267,7 @@ overmap *overmapbuffer::get_existing( const point_abs_om &p ) // checked in a previous call of this function). return nullptr; } - if( file_exist( terrain_filename( p ) ) ) { + if( g->get_world_db() && g->get_world_db()->file_exist( terrain_filename( p ) ) ) { // File exists, load it normally (the get function // indirectly call overmap::open to do so). return &get( p ); diff --git a/src/worlddb.cpp b/src/worlddb.cpp new file mode 100644 index 000000000000..d512e0f98863 --- /dev/null +++ b/src/worlddb.cpp @@ -0,0 +1,214 @@ +#include "worlddb.h" + +#include +#include + +#include "debug.h" +#include "cata_utility.h" +#include "filesystem.h" +#include "json.h" +#include "output.h" +#include "fstream_utils.h" + +#define dbg(x) DebugLogFL((x),DC::Main) + +worlddb::worlddb(const std::string &path) + : db( nullptr ) + ,worlddir( path ) +{ + // DLP: DEBUG + std::cerr << "Opening Database" << std::endl; + if( !assure_dir_exist( path ) ) { + dbg( DL::Error ) << "Unable to create or open world directory for saving: " << path; + } + + std::string db_path = path + "/world.db"; + int ret; + + if (SQLITE_OK != (ret = sqlite3_initialize())) { + dbg( DL::Error ) << "Failed to initialize sqlite3 (Error " << ret << ")"; + throw std::runtime_error( "Failed to initialize sqlite3" ); + } + + if (SQLITE_OK != (ret = sqlite3_open_v2(db_path.c_str(), &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL))) { + dbg( DL::Error ) << "Failed to open db" << db_path << " (Error " << ret << ")"; + throw std::runtime_error( "Failed to open db" ); + } + + auto sql = "CREATE TABLE IF NOT EXISTS files (" \ + "path TEXT PRIMARY KEY NOT NULL," \ + "parent TEXT NOT NULL," \ + "compression TEXT DEFAULT NULL," \ + "data BLOB NOT NULL" \ + ");"; + + + char *sqlErrMsg = 0; + if (SQLITE_OK != (ret = sqlite3_exec(db, sql, NULL, NULL, &sqlErrMsg))) { + dbg( DL::Error ) << "Failed to init db" << db_path << " (" << sqlErrMsg << ")"; + throw std::runtime_error( "Failed to open db" ); + } +} + +worlddb::~worlddb() +{ + // DLP: DEBUG + std::cerr << "Closing Database" << std::endl; + sqlite3_close( db ); +} + +bool worlddb::file_exist_in_db( const std::string &path ) { + int fileCount = 0; + const char* sql = "SELECT count() FROM files WHERE path = :path"; + sqlite3_stmt* stmt = nullptr; + + if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) { + dbg( DL::Error ) << "Failed to prepare statement: " << sqlite3_errmsg(db) << std::endl; + throw std::runtime_error( "DB query failed" ); + } + + if (sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":path"), path.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK) { + dbg( DL::Error ) << "Failed to bind parameter: " << sqlite3_errmsg(db) << std::endl; + sqlite3_finalize(stmt); + throw std::runtime_error( "DB query failed" ); + } + + if (sqlite3_step(stmt) == SQLITE_ROW) { + // Retrieve the count result + fileCount = sqlite3_column_int(stmt, 0); + } else { + dbg( DL::Error ) << "Failed to execute query: " << sqlite3_errmsg(db) << std::endl; + sqlite3_finalize(stmt); + throw std::runtime_error( "DB query failed" ); + } + + sqlite3_finalize(stmt); + + return fileCount > 0; +} + +bool worlddb::file_exist( const std::string &path ) { + // Fall back to old logic if the file was not found for backwards compatibility + return file_exist_in_db(path) || ::file_exist(worlddir + "/" + path); +} + +void worlddb::write_to_file( const std::string &path, const std::function &writer ) +{ + + std::ostringstream oss; + writer(oss); + auto data = oss.str(); + + size_t basePos = path.find_last_of("/\\"); + auto parent = (basePos == std::string::npos) ? "" : path.substr(0, basePos); + + // DLP: DEBUG + std::cerr << "SQL Write: " << path << " ..." << parent << " ..."; + + // sqlite3_exec( db, "BEGIN TRANSACTION", 0, 0, 0 ); + const char* sql = "INSERT INTO files(path, parent, data) VALUES (:path, :parent, :data)"; + + sqlite3_stmt* stmt = nullptr; + + if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) { + dbg( DL::Error ) << "Failed to prepare statement: " << sqlite3_errmsg(db) << std::endl; + throw std::runtime_error( "DB query failed" ); + } + + if (sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":path"), path.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK || + sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":parent"), parent.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK || + sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":data"), data.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK) { + dbg( DL::Error ) << "Failed to bind parameters: " << sqlite3_errmsg(db) << std::endl; + sqlite3_finalize(stmt); + throw std::runtime_error( "DB query failed" ); + } + + if (sqlite3_step(stmt) != SQLITE_DONE) { + dbg( DL::Error ) << "Failed to execute query: " << sqlite3_errmsg(db) << std::endl; + } + sqlite3_finalize(stmt); + // sqlite3_exec( db, "COMMIT", 0, 0, 0 ); + + // DLP: DEBUG + std::cerr << " ... DONE" << std::endl; +} + +bool worlddb::write_to_file( const std::string &path, const std::function &writer, + const char *const fail_message ) +{ + try { + write_to_file( path, writer ); + return true; + + } catch( const std::exception &err ) { + if( fail_message ) { + popup( _( "Failed to write %1$s to \"%2$s\": %3$s" ), fail_message, path.c_str(), err.what() ); + } + return false; + } +} + +bool worlddb::read_from_file( const std::string &path, const std::function &reader ) +{ + if ( file_exist_in_db(path) ) { + // DLP: DEBUG + std::cerr << "SQL Read: " << path << std::endl; + const char* sql = "SELECT data FROM files WHERE path = :path LIMIT 1"; + + sqlite3_stmt* stmt = nullptr; + + if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) { + dbg( DL::Error ) << "Failed to prepare statement: " << sqlite3_errmsg(db) << std::endl; + throw std::runtime_error( "DB query failed" ); + } + + if (sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":path"), path.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK) { + dbg( DL::Error ) << "Failed to bind parameter: " << sqlite3_errmsg(db) << std::endl; + sqlite3_finalize(stmt); + throw std::runtime_error( "DB query failed" ); + } + + if (sqlite3_step(stmt) == SQLITE_ROW) { + // Retrieve the count result + const void* blobData = sqlite3_column_blob(stmt, 0); + int blobSize = sqlite3_column_bytes(stmt, 0); + + if (blobData == nullptr) { + return false; // Return an empty string if there's no data + } + + std::string blobString(static_cast(blobData), blobSize); + std::istringstream stream(blobString); + reader(stream); + sqlite3_finalize(stmt); + } else { + dbg( DL::Error ) << "Failed to execute query: " << sqlite3_errmsg(db) << std::endl; + sqlite3_finalize(stmt); + throw std::runtime_error( "DB query failed" ); + } + + return true; + } else { + // DLP: DEBUG + std::cerr << "Redirected "; + return ::read_from_file(worlddir + "/" + path, reader); + } +} + +bool worlddb::read_from_file_optional( const std::string &path, + const std::function &reader ) +{ + // Note: slight race condition here, but we'll ignore it. Worst case: the file + // exists and got removed before reading it -> reading fails with a message + // Or file does not exists, than everything works fine because it's optional anyway. + return file_exist( path ) && read_from_file( path, reader ); +} + +bool worlddb::read_from_file_optional_json( const std::string &path, + const std::function &reader ) +{ + return read_from_file_optional( path, [&]( std::istream & fin ) { + JsonIn jsin( fin, path ); + reader( jsin ); + } ); +} diff --git a/src/worlddb.h b/src/worlddb.h new file mode 100644 index 000000000000..8f4c7db4471d --- /dev/null +++ b/src/worlddb.h @@ -0,0 +1,36 @@ +#pragma once +#ifndef CATA_SRC_WORLDDB_H +#define CATA_SRC_WORLDDB_H + +#include +#include +#include +#include "json.h" + +class worlddb +{ + public: + worlddb(const std::string &path); + ~worlddb(); + + bool file_exist_in_db( const std::string &path ); + bool file_exist( const std::string &path); + + void write_to_file( const std::string &path, + const std::function &writer); + + bool write_to_file( const std::string &path, const std::function &writer, + const char *const fail_message ); + + bool read_from_file( const std::string &path, + const std::function &reader ); + bool read_from_file_optional( const std::string &path, + const std::function &reader ); + bool read_from_file_optional_json( const std::string &path, + const std::function &reader ); + private: + sqlite3* db; + const std::string worlddir; +}; + +#endif // CATA_SRC_WORLDDB_H diff --git a/src/worldfactory.cpp b/src/worldfactory.cpp index 2eff91ba1f95..193de9aabdb2 100644 --- a/src/worldfactory.cpp +++ b/src/worldfactory.cpp @@ -121,6 +121,7 @@ void WORLD::add_save( const save_t &name ) worldfactory::worldfactory() : active_world( nullptr ) + , active_world_db( nullptr ) , mman_ui( *mman ) { // prepare tab display order @@ -225,9 +226,17 @@ WORLDPTR worldfactory::make_new_world( special_game_type special_type ) void worldfactory::set_active_world( WORLDPTR world ) { + // DLP: DEBUG + std::cerr << "Setting active world to " << ( world ? world->folder_path() : "NULL" ) << std::endl; + + if ( active_world_db ) { + active_world_db = nullptr; + } + world_generator->active_world = world; if( world ) { get_options().set_world_options( &world->WORLD_OPTIONS ); + active_world_db = std::make_unique(world->folder_path()); } else { get_options().set_world_options( nullptr ); } @@ -591,8 +600,7 @@ void worldfactory::remove_world( const std::string &worldname ) if( it != all_worlds.end() ) { WORLDPTR wptr = it->second.get(); if( active_world == wptr ) { - get_options().set_world_options( nullptr ); - active_world = nullptr; + set_active_world( nullptr ); } all_worlds.erase( it ); } @@ -1729,6 +1737,11 @@ static bool isForbidden( const std::string &candidate ) void worldfactory::delete_world( const std::string &worldname, const bool delete_folder ) { + // Disconnect to the database if we're somehow trying to delete the currently active world + if ( active_world->world_name == worldname ) { + set_active_world( nullptr ); + } + std::string worldpath = get_world( worldname )->folder_path(); std::set directory_paths; diff --git a/src/worldfactory.h b/src/worldfactory.h index 5820db39bd69..a2ddd241056e 100644 --- a/src/worldfactory.h +++ b/src/worldfactory.h @@ -13,6 +13,8 @@ #include "options.h" #include "pimpl.h" #include "type_id.h" +#include "worlddb.h" +#include "memory_fast.h" enum class special_game_type; @@ -111,6 +113,7 @@ class worldfactory WORLDPTR pick_world( bool show_prompt = true, bool empty_only = false ); WORLDPTR active_world; + shared_ptr_fast active_world_db; std::vector all_worldnames() const; From f04e133a8f637569db492434f0b2fd4260f2dca0 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 28 Nov 2024 04:59:37 +0000 Subject: [PATCH 2/2] style(autofix.ci): automated formatting --- src/diary.cpp | 11 ++-- src/mapbuffer.cpp | 3 +- src/overmap.cpp | 3 +- src/worlddb.cpp | 145 +++++++++++++++++++++++-------------------- src/worlddb.h | 16 ++--- src/worldfactory.cpp | 6 +- 6 files changed, 99 insertions(+), 85 deletions(-) diff --git a/src/diary.cpp b/src/diary.cpp index 740f00917060..0a6573782f7c 100644 --- a/src/diary.cpp +++ b/src/diary.cpp @@ -775,12 +775,13 @@ void diary::export_to_md( bool last_export ) bool diary::store() { - if ( !g->get_world_db() ) { + if( !g->get_world_db() ) { return false; } - + std::string name = base64_encode( get_avatar().get_save_id() + "_diary" ); - const bool is_writen = g->get_world_db()->write_to_file( name + ".json", [&]( std::ostream & fout ) { + const bool is_writen = g->get_world_db()->write_to_file( name + ".json", [&]( + std::ostream & fout ) { serialize( fout ); }, _( "diary data" ) ); return is_writen; @@ -829,10 +830,10 @@ void diary::serialize( JsonOut &jsout ) void diary::load() { - if ( !g->get_world_db() ) { + if( !g->get_world_db() ) { return; } - + std::string name = base64_encode( get_avatar().get_save_id() + "_diary" ); if( g->get_world_db()->file_exist( name + ".json" ) ) { g->get_world_db()->read_from_file( name + ".json", [&]( std::istream & fin ) { diff --git a/src/mapbuffer.cpp b/src/mapbuffer.cpp index 5698b6421bb4..3207a3511e6d 100644 --- a/src/mapbuffer.cpp +++ b/src/mapbuffer.cpp @@ -259,7 +259,8 @@ submap *mapbuffer::unserialize_submaps( const tripoint &p ) } using namespace std::placeholders; - if( !g->get_world_db()->read_from_file_optional_json( quad_path, std::bind( &mapbuffer::deserialize, this, _1 ) ) ) { + if( !g->get_world_db()->read_from_file_optional_json( quad_path, std::bind( &mapbuffer::deserialize, + this, _1 ) ) ) { // If it doesn't exist, trigger generating it. return nullptr; } diff --git a/src/overmap.cpp b/src/overmap.cpp index d2bbd6992e5f..7c478795aced 100644 --- a/src/overmap.cpp +++ b/src/overmap.cpp @@ -6115,7 +6115,8 @@ void overmap::save() const serialize_view( stream ); } ); - g->get_world_db()->write_to_file( overmapbuffer::terrain_filename( loc ), [&]( std::ostream & stream ) { + g->get_world_db()->write_to_file( overmapbuffer::terrain_filename( loc ), [&]( + std::ostream & stream ) { serialize( stream ); } ); } diff --git a/src/worlddb.cpp b/src/worlddb.cpp index d512e0f98863..3de7ae69b4b6 100644 --- a/src/worlddb.cpp +++ b/src/worlddb.cpp @@ -12,9 +12,9 @@ #define dbg(x) DebugLogFL((x),DC::Main) -worlddb::worlddb(const std::string &path) +worlddb::worlddb( const std::string &path ) : db( nullptr ) - ,worlddir( path ) + , worlddir( path ) { // DLP: DEBUG std::cerr << "Opening Database" << std::endl; @@ -25,26 +25,27 @@ worlddb::worlddb(const std::string &path) std::string db_path = path + "/world.db"; int ret; - if (SQLITE_OK != (ret = sqlite3_initialize())) { + if( SQLITE_OK != ( ret = sqlite3_initialize() ) ) { dbg( DL::Error ) << "Failed to initialize sqlite3 (Error " << ret << ")"; throw std::runtime_error( "Failed to initialize sqlite3" ); } - if (SQLITE_OK != (ret = sqlite3_open_v2(db_path.c_str(), &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL))) { + if( SQLITE_OK != ( ret = sqlite3_open_v2( db_path.c_str(), &db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL ) ) ) { dbg( DL::Error ) << "Failed to open db" << db_path << " (Error " << ret << ")"; throw std::runtime_error( "Failed to open db" ); } auto sql = "CREATE TABLE IF NOT EXISTS files (" \ - "path TEXT PRIMARY KEY NOT NULL," \ - "parent TEXT NOT NULL," \ - "compression TEXT DEFAULT NULL," \ - "data BLOB NOT NULL" \ - ");"; + "path TEXT PRIMARY KEY NOT NULL," \ + "parent TEXT NOT NULL," \ + "compression TEXT DEFAULT NULL," \ + "data BLOB NOT NULL" \ + ");"; char *sqlErrMsg = 0; - if (SQLITE_OK != (ret = sqlite3_exec(db, sql, NULL, NULL, &sqlErrMsg))) { + if( SQLITE_OK != ( ret = sqlite3_exec( db, sql, NULL, NULL, &sqlErrMsg ) ) ) { dbg( DL::Error ) << "Failed to init db" << db_path << " (" << sqlErrMsg << ")"; throw std::runtime_error( "Failed to open db" ); } @@ -57,83 +58,91 @@ worlddb::~worlddb() sqlite3_close( db ); } -bool worlddb::file_exist_in_db( const std::string &path ) { +bool worlddb::file_exist_in_db( const std::string &path ) +{ int fileCount = 0; - const char* sql = "SELECT count() FROM files WHERE path = :path"; - sqlite3_stmt* stmt = nullptr; + const char *sql = "SELECT count() FROM files WHERE path = :path"; + sqlite3_stmt *stmt = nullptr; - if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) { - dbg( DL::Error ) << "Failed to prepare statement: " << sqlite3_errmsg(db) << std::endl; + if( sqlite3_prepare_v2( db, sql, -1, &stmt, nullptr ) != SQLITE_OK ) { + dbg( DL::Error ) << "Failed to prepare statement: " << sqlite3_errmsg( db ) << std::endl; throw std::runtime_error( "DB query failed" ); } - if (sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":path"), path.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK) { - dbg( DL::Error ) << "Failed to bind parameter: " << sqlite3_errmsg(db) << std::endl; - sqlite3_finalize(stmt); + if( sqlite3_bind_text( stmt, sqlite3_bind_parameter_index( stmt, ":path" ), path.c_str(), -1, + SQLITE_TRANSIENT ) != SQLITE_OK ) { + dbg( DL::Error ) << "Failed to bind parameter: " << sqlite3_errmsg( db ) << std::endl; + sqlite3_finalize( stmt ); throw std::runtime_error( "DB query failed" ); } - if (sqlite3_step(stmt) == SQLITE_ROW) { + if( sqlite3_step( stmt ) == SQLITE_ROW ) { // Retrieve the count result - fileCount = sqlite3_column_int(stmt, 0); + fileCount = sqlite3_column_int( stmt, 0 ); } else { - dbg( DL::Error ) << "Failed to execute query: " << sqlite3_errmsg(db) << std::endl; - sqlite3_finalize(stmt); + dbg( DL::Error ) << "Failed to execute query: " << sqlite3_errmsg( db ) << std::endl; + sqlite3_finalize( stmt ); throw std::runtime_error( "DB query failed" ); } - sqlite3_finalize(stmt); + sqlite3_finalize( stmt ); return fileCount > 0; } -bool worlddb::file_exist( const std::string &path ) { +bool worlddb::file_exist( const std::string &path ) +{ // Fall back to old logic if the file was not found for backwards compatibility - return file_exist_in_db(path) || ::file_exist(worlddir + "/" + path); + return file_exist_in_db( path ) || ::file_exist( worlddir + "/" + path ); } -void worlddb::write_to_file( const std::string &path, const std::function &writer ) +void worlddb::write_to_file( const std::string &path, + const std::function &writer ) { std::ostringstream oss; - writer(oss); + writer( oss ); auto data = oss.str(); - size_t basePos = path.find_last_of("/\\"); - auto parent = (basePos == std::string::npos) ? "" : path.substr(0, basePos); - + size_t basePos = path.find_last_of( "/\\" ); + auto parent = ( basePos == std::string::npos ) ? "" : path.substr( 0, basePos ); + // DLP: DEBUG std::cerr << "SQL Write: " << path << " ..." << parent << " ..."; - // sqlite3_exec( db, "BEGIN TRANSACTION", 0, 0, 0 ); - const char* sql = "INSERT INTO files(path, parent, data) VALUES (:path, :parent, :data)"; + // sqlite3_exec( db, "BEGIN TRANSACTION", 0, 0, 0 ); + const char *sql = "INSERT INTO files(path, parent, data) VALUES (:path, :parent, :data)"; + + sqlite3_stmt *stmt = nullptr; - sqlite3_stmt* stmt = nullptr; - - if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) { - dbg( DL::Error ) << "Failed to prepare statement: " << sqlite3_errmsg(db) << std::endl; + if( sqlite3_prepare_v2( db, sql, -1, &stmt, nullptr ) != SQLITE_OK ) { + dbg( DL::Error ) << "Failed to prepare statement: " << sqlite3_errmsg( db ) << std::endl; throw std::runtime_error( "DB query failed" ); } - if (sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":path"), path.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK || - sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":parent"), parent.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK || - sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":data"), data.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK) { - dbg( DL::Error ) << "Failed to bind parameters: " << sqlite3_errmsg(db) << std::endl; - sqlite3_finalize(stmt); + if( sqlite3_bind_text( stmt, sqlite3_bind_parameter_index( stmt, ":path" ), path.c_str(), -1, + SQLITE_TRANSIENT ) != SQLITE_OK || + sqlite3_bind_text( stmt, sqlite3_bind_parameter_index( stmt, ":parent" ), parent.c_str(), -1, + SQLITE_TRANSIENT ) != SQLITE_OK || + sqlite3_bind_text( stmt, sqlite3_bind_parameter_index( stmt, ":data" ), data.c_str(), -1, + SQLITE_TRANSIENT ) != SQLITE_OK ) { + dbg( DL::Error ) << "Failed to bind parameters: " << sqlite3_errmsg( db ) << std::endl; + sqlite3_finalize( stmt ); throw std::runtime_error( "DB query failed" ); } - if (sqlite3_step(stmt) != SQLITE_DONE) { - dbg( DL::Error ) << "Failed to execute query: " << sqlite3_errmsg(db) << std::endl; + if( sqlite3_step( stmt ) != SQLITE_DONE ) { + dbg( DL::Error ) << "Failed to execute query: " << sqlite3_errmsg( db ) << std::endl; } - sqlite3_finalize(stmt); - // sqlite3_exec( db, "COMMIT", 0, 0, 0 ); + sqlite3_finalize( stmt ); + // sqlite3_exec( db, "COMMIT", 0, 0, 0 ); // DLP: DEBUG std::cerr << " ... DONE" << std::endl; } -bool worlddb::write_to_file( const std::string &path, const std::function &writer, +bool worlddb::write_to_file( const std::string &path, + const std::function &writer, const char *const fail_message ) { try { @@ -148,42 +157,44 @@ bool worlddb::write_to_file( const std::string &path, const std::function &reader ) +bool worlddb::read_from_file( const std::string &path, + const std::function &reader ) { - if ( file_exist_in_db(path) ) { + if( file_exist_in_db( path ) ) { // DLP: DEBUG std::cerr << "SQL Read: " << path << std::endl; - const char* sql = "SELECT data FROM files WHERE path = :path LIMIT 1"; + const char *sql = "SELECT data FROM files WHERE path = :path LIMIT 1"; - sqlite3_stmt* stmt = nullptr; + sqlite3_stmt *stmt = nullptr; - if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) { - dbg( DL::Error ) << "Failed to prepare statement: " << sqlite3_errmsg(db) << std::endl; + if( sqlite3_prepare_v2( db, sql, -1, &stmt, nullptr ) != SQLITE_OK ) { + dbg( DL::Error ) << "Failed to prepare statement: " << sqlite3_errmsg( db ) << std::endl; throw std::runtime_error( "DB query failed" ); } - if (sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":path"), path.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK) { - dbg( DL::Error ) << "Failed to bind parameter: " << sqlite3_errmsg(db) << std::endl; - sqlite3_finalize(stmt); + if( sqlite3_bind_text( stmt, sqlite3_bind_parameter_index( stmt, ":path" ), path.c_str(), -1, + SQLITE_TRANSIENT ) != SQLITE_OK ) { + dbg( DL::Error ) << "Failed to bind parameter: " << sqlite3_errmsg( db ) << std::endl; + sqlite3_finalize( stmt ); throw std::runtime_error( "DB query failed" ); } - if (sqlite3_step(stmt) == SQLITE_ROW) { + if( sqlite3_step( stmt ) == SQLITE_ROW ) { // Retrieve the count result - const void* blobData = sqlite3_column_blob(stmt, 0); - int blobSize = sqlite3_column_bytes(stmt, 0); + const void *blobData = sqlite3_column_blob( stmt, 0 ); + int blobSize = sqlite3_column_bytes( stmt, 0 ); - if (blobData == nullptr) { + if( blobData == nullptr ) { return false; // Return an empty string if there's no data } - std::string blobString(static_cast(blobData), blobSize); - std::istringstream stream(blobString); - reader(stream); - sqlite3_finalize(stmt); + std::string blobString( static_cast( blobData ), blobSize ); + std::istringstream stream( blobString ); + reader( stream ); + sqlite3_finalize( stmt ); } else { - dbg( DL::Error ) << "Failed to execute query: " << sqlite3_errmsg(db) << std::endl; - sqlite3_finalize(stmt); + dbg( DL::Error ) << "Failed to execute query: " << sqlite3_errmsg( db ) << std::endl; + sqlite3_finalize( stmt ); throw std::runtime_error( "DB query failed" ); } @@ -191,7 +202,7 @@ bool worlddb::read_from_file( const std::string &path, const std::function &reader ) + const std::function &reader ) { return read_from_file_optional( path, [&]( std::istream & fin ) { JsonIn jsin( fin, path ); diff --git a/src/worlddb.h b/src/worlddb.h index 8f4c7db4471d..d41a46d1c622 100644 --- a/src/worlddb.h +++ b/src/worlddb.h @@ -10,26 +10,26 @@ class worlddb { public: - worlddb(const std::string &path); + worlddb( const std::string &path ); ~worlddb(); bool file_exist_in_db( const std::string &path ); - bool file_exist( const std::string &path); - + bool file_exist( const std::string &path ); + void write_to_file( const std::string &path, - const std::function &writer); + const std::function &writer ); bool write_to_file( const std::string &path, const std::function &writer, - const char *const fail_message ); + const char *const fail_message ); bool read_from_file( const std::string &path, const std::function &reader ); bool read_from_file_optional( const std::string &path, - const std::function &reader ); + const std::function &reader ); bool read_from_file_optional_json( const std::string &path, - const std::function &reader ); + const std::function &reader ); private: - sqlite3* db; + sqlite3 *db; const std::string worlddir; }; diff --git a/src/worldfactory.cpp b/src/worldfactory.cpp index 193de9aabdb2..7b7a9d7449a3 100644 --- a/src/worldfactory.cpp +++ b/src/worldfactory.cpp @@ -229,14 +229,14 @@ void worldfactory::set_active_world( WORLDPTR world ) // DLP: DEBUG std::cerr << "Setting active world to " << ( world ? world->folder_path() : "NULL" ) << std::endl; - if ( active_world_db ) { + if( active_world_db ) { active_world_db = nullptr; } world_generator->active_world = world; if( world ) { get_options().set_world_options( &world->WORLD_OPTIONS ); - active_world_db = std::make_unique(world->folder_path()); + active_world_db = std::make_unique( world->folder_path() ); } else { get_options().set_world_options( nullptr ); } @@ -1738,7 +1738,7 @@ static bool isForbidden( const std::string &candidate ) void worldfactory::delete_world( const std::string &worldname, const bool delete_folder ) { // Disconnect to the database if we're somehow trying to delete the currently active world - if ( active_world->world_name == worldname ) { + if( active_world->world_name == worldname ) { set_active_world( nullptr ); }