diff --git a/po/POTFILES.in b/po/POTFILES.in index 0adb5c16b7..dc5145cd9a 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -482,7 +482,7 @@ src/libqof/qof/gnc-timezone.cpp src/libqof/qof/guid.cpp src/libqof/qof/kvp_frame.cpp src/libqof/qof/kvp-value.cpp -src/libqof/qof/qofbackend.cpp +src/libqof/qof/qof-backend.cpp src/libqof/qof/qofbook.cpp src/libqof/qof/qofchoice.cpp src/libqof/qof/qofclass.cpp diff --git a/src/backend/dbi/gnc-backend-dbi.cpp b/src/backend/dbi/gnc-backend-dbi.cpp index 8c00ec5d62..5e00451b44 100644 --- a/src/backend/dbi/gnc-backend-dbi.cpp +++ b/src/backend/dbi/gnc-backend-dbi.cpp @@ -111,31 +111,8 @@ static QofLogModule log_module = G_LOG_DOMAIN; static void adjust_sql_options (dbi_conn connection); static bool save_may_clobber_data (dbi_conn conn, const std::string& dbname); -static void init_sql_backend (GncDbiBackend* dbi_be); -static bool conn_test_dbi_library (dbi_conn conn, QofBackend* qof_be); - -template void gnc_dbi_session_begin(QofBackend* qof_be, - QofSession* session, - const char* book_id, - gboolean ignore_lock, - gboolean create, gboolean force); -template QofBackend* -new_backend () -{ - auto dbi_be = new GncDbiBackend(nullptr, nullptr); - assert (dbi_be != nullptr); - - QofBackend* qof_be = reinterpret_cast(dbi_be); - qof_backend_init (qof_be); - - qof_be->session_begin = gnc_dbi_session_begin; - init_sql_backend (dbi_be); - - return qof_be; -} - -template +template class QofDbiBackendProvider : public QofBackendProvider { public: @@ -146,7 +123,10 @@ public: QofDbiBackendProvider(QofDbiBackendProvider&&) = delete; QofDbiBackendProvider operator=(QofDbiBackendProvider&&) = delete; ~QofDbiBackendProvider () = default; - QofBackend* create_backend(void) { return new_backend(); } + QofBackend* create_backend(void) + { + return new GncDbiBackend(nullptr, nullptr); + } bool type_check(const char* type) { return true; } }; @@ -235,14 +215,13 @@ set_options(dbi_conn conn, const PairVec& options) /** * Sets standard db options in a dbi_conn. * - * @param qof_be QOF backend * @param conn dbi_conn connection * @param uri UriStrings containing the needed paramters. * @return TRUE if successful, FALSE if error */ -static bool -set_standard_connection_options (QofBackend* qof_be, dbi_conn conn, - const UriStrings& uri) +template bool +GncDbiBackend::set_standard_connection_options (dbi_conn conn, + const UriStrings& uri) { gint result; @@ -266,7 +245,7 @@ set_standard_connection_options (QofBackend* qof_be, dbi_conn conn, } catch (std::runtime_error& err) { - qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR); + set_error (ERR_BACKEND_SERVER_ERR); return false; } @@ -277,8 +256,7 @@ template void error_handler(void* conn, void* data); void error_handler(void* conn, void* data); template dbi_conn -conn_setup (QofBackend* qof_be, PairVec& options, - UriStrings& uri) +GncDbiBackend::conn_setup (PairVec& options, UriStrings& uri) { const char* dbstr = (Type == DbType::DBI_SQLITE ? "sqlite3" : Type == DbType::DBI_MYSQL ? "mysql" : "pgsql"); @@ -295,13 +273,13 @@ conn_setup (QofBackend* qof_be, PairVec& options, if (conn == nullptr) { PERR ("Unable to create %s dbi connection", dbstr); - qof_backend_set_error (qof_be, ERR_BACKEND_BAD_URL); + set_error (ERR_BACKEND_BAD_URL); return nullptr; } - dbi_conn_error_handler (conn, error_handler, qof_be); + dbi_conn_error_handler (conn, error_handler, this); if (!uri.m_dbname.empty() && - !set_standard_connection_options(qof_be, conn, uri)) + !set_standard_connection_options(conn, uri)) { dbi_conn_close(conn); return nullptr; @@ -314,7 +292,7 @@ conn_setup (QofBackend* qof_be, PairVec& options, catch (std::runtime_error& err) { dbi_conn_close(conn); - qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR); + set_error (ERR_BACKEND_SERVER_ERR); return nullptr; } } @@ -322,12 +300,12 @@ conn_setup (QofBackend* qof_be, PairVec& options, return conn; } -static bool -create_database(DbType type, QofBackend *qof_be, dbi_conn conn, const char* db) +template bool +GncDbiBackend::create_database(dbi_conn conn, const char* db) { const char *dbname; const char *dbcreate; - if (type == DbType::DBI_MYSQL) + if (Type == DbType::DBI_MYSQL) { dbname = "mysql"; dbcreate = "CREATE DATABASE %s CHARACTER SET utf8"; @@ -345,7 +323,7 @@ create_database(DbType type, QofBackend *qof_be, dbi_conn conn, const char* db) } catch (std::runtime_error& err) { - qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR); + set_error (ERR_BACKEND_SERVER_ERR); return false; } @@ -353,19 +331,19 @@ create_database(DbType type, QofBackend *qof_be, dbi_conn conn, const char* db) if (result < 0) { PERR ("Unable to connect to %s database", dbname); - qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR); + set_error(ERR_BACKEND_SERVER_ERR); return false; } - if (type == DbType::DBI_MYSQL) + if (Type == DbType::DBI_MYSQL) adjust_sql_options(conn); auto dresult = dbi_conn_queryf (conn, dbcreate, db); if (dresult == nullptr) { PERR ("Unable to create database '%s'\n", db); - qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR); + set_error (ERR_BACKEND_SERVER_ERR); return false; } - if (type == DbType::DBI_PGSQL) + if (Type == DbType::DBI_PGSQL) { const char *alterdb = "ALTER DATABASE %s SET " "standard_conforming_strings TO on"; @@ -380,26 +358,23 @@ template <> void error_handler (dbi_conn conn, void* user_data) { const char* msg; - GncDbiBackend *dbi_be = static_cast(user_data); + GncDbiBackend *dbi_be = + static_cast(user_data); int errnum = dbi_conn_error (conn, &msg); PERR ("DBI error: %s\n", msg); if (dbi_be->connected()) - dbi_be->set_error (ERR_BACKEND_MISC, 0, false); + dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, false); } template <> void -gnc_dbi_session_begin(QofBackend* qof_be, - QofSession* session, - const char* book_id, - gboolean ignore_lock, - gboolean create, gboolean force) +GncDbiBackend::session_begin(QofSession* session, + const char* book_id, + bool ignore_lock, + bool create, bool force) { - GncDbiBackend* dbi_be = reinterpret_cast(qof_be); - const char* msg = nullptr; gboolean file_exists; PairVec options; - g_return_if_fail (qof_be != nullptr); g_return_if_fail (session != nullptr); g_return_if_fail (book_id != nullptr); @@ -414,9 +389,9 @@ gnc_dbi_session_begin(QofBackend* qof_be, file_exists = g_file_test (filepath.c_str(), ftest); if (!create && !file_exists) { - qof_backend_set_error (qof_be, ERR_FILEIO_FILE_NOT_FOUND); - qof_backend_set_message (qof_be, "Sqlite3 file %s not found", - filepath.c_str()); + set_error (ERR_FILEIO_FILE_NOT_FOUND); + std::string msg{"Sqlite3 file "}; + set_message (msg + filepath + " not found"); PWARN ("Sqlite3 file %s not found", filepath.c_str()); LEAVE("Error"); return; @@ -424,14 +399,14 @@ gnc_dbi_session_begin(QofBackend* qof_be, if (create && !force && file_exists) { - qof_backend_set_error (qof_be, ERR_BACKEND_STORE_EXISTS); - msg = "Might clobber, no force"; + set_error (ERR_BACKEND_STORE_EXISTS); + auto msg = "Might clobber, no force"; PWARN ("%s", msg); LEAVE("Error"); return; } - dbi_be->connect(nullptr); + connect(nullptr); /* dbi-sqlite3 documentation says that sqlite3 doesn't take a "host" option */ options.push_back(std::make_pair("host", "localhost")); auto dirname = g_path_get_dirname (filepath.c_str()); @@ -441,7 +416,7 @@ gnc_dbi_session_begin(QofBackend* qof_be, if (basename != nullptr) g_free (basename); if (dirname != nullptr) g_free (dirname); UriStrings uri; - auto conn = conn_setup(qof_be, options, uri); + auto conn = conn_setup(options, uri); if (conn == nullptr) { LEAVE("Error"); @@ -454,12 +429,12 @@ gnc_dbi_session_begin(QofBackend* qof_be, { dbi_conn_close(conn); PERR ("Unable to connect to %s: %d\n", book_id, result); - qof_backend_set_error (qof_be, ERR_BACKEND_BAD_URL); + set_error (ERR_BACKEND_BAD_URL); LEAVE("Error"); return; } - if (!conn_test_dbi_library(conn, qof_be)) + if (!conn_test_dbi_library(conn)) { if (create && !file_exists) { @@ -477,8 +452,8 @@ gnc_dbi_session_begin(QofBackend* qof_be, try { - dbi_be->connect(new GncDbiSqlConnection(DbType::DBI_SQLITE, - qof_be, conn, ignore_lock)); + connect(new GncDbiSqlConnection(DbType::DBI_SQLITE, + this, conn, ignore_lock)); } catch (std::runtime_error& err) { @@ -497,7 +472,8 @@ gnc_dbi_session_begin(QofBackend* qof_be, template <> void error_handler (dbi_conn conn, void* user_data) { - GncDbiBackend* dbi_be = static_cast(user_data); + GncDbiBackend* dbi_be = + static_cast(user_data); const char* msg; auto err_num = dbi_conn_error (conn, &msg); @@ -531,18 +507,18 @@ error_handler (dbi_conn conn, void* user_data) if (err_num == 2006) // Server has gone away { PINFO ("DBI error: %s - Reconnecting...\n", msg); - dbi_be->set_error (ERR_BACKEND_CONN_LOST, 1, true); + dbi_be->set_dbi_error (ERR_BACKEND_CONN_LOST, 1, true); dbi_be->retry_connection(msg); } else if (err_num == 2003) // Unable to connect { - dbi_be->set_error (ERR_BACKEND_CANT_CONNECT, 1, true); + dbi_be->set_dbi_error (ERR_BACKEND_CANT_CONNECT, 1, true); dbi_be->retry_connection (msg); } else // Any other error { PERR ("DBI error: %s\n", msg); - dbi_be->set_error (ERR_BACKEND_MISC, 0, FALSE); + dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, FALSE); } } @@ -610,16 +586,13 @@ adjust_sql_options (dbi_conn connection) } -template void -gnc_dbi_session_begin (QofBackend* qof_be, QofSession* session, - const char* book_id, gboolean ignore_lock, - gboolean create, gboolean force) +template void +GncDbiBackend::session_begin (QofSession* session, const char* book_id, + bool ignore_lock, bool create, bool force) { - GncDbiBackend* dbi_be = reinterpret_cast(qof_be); GncDbiTestResult dbi_test_result = GNC_DBI_PASS; PairVec options; - g_return_if_fail (qof_be != nullptr); g_return_if_fail (session != nullptr); g_return_if_fail (book_id != nullptr); @@ -630,7 +603,7 @@ gnc_dbi_session_begin (QofBackend* qof_be, QofSession* session, where username, password and port are optional) */ UriStrings uri(book_id); - if (T == DbType::DBI_PGSQL) + if (Type == DbType::DBI_PGSQL) { if (uri.m_portnum == 0) uri.m_portnum = PGSQL_DEFAULT_PORT; @@ -642,31 +615,31 @@ gnc_dbi_session_begin (QofBackend* qof_be, QofSession* session, uri.m_dbname = std::string{lcname}; g_free(lcname); } - dbi_be->connect(nullptr); + connect(nullptr); - auto conn = conn_setup(qof_be, options, uri); + auto conn = conn_setup(options, uri); if (conn == nullptr) { LEAVE("Error"); return; } - dbi_be->set_exists(true); //May be unset in the error handler. + m_exists = true; //May be unset in the error handler. auto result = dbi_conn_connect (conn); if (result == 0) { - if (T == DbType::DBI_MYSQL) + if (Type == DbType::DBI_MYSQL) adjust_sql_options (conn); - if(!conn_test_dbi_library(conn, qof_be)) + if(!conn_test_dbi_library(conn)) { dbi_conn_close(conn); LEAVE("Error"); return; } if (create && !force && save_may_clobber_data (conn, - uri.quote_dbname(T))) + uri.quote_dbname(Type))) { - qof_backend_set_error (qof_be, ERR_BACKEND_STORE_EXISTS); + set_error (ERR_BACKEND_STORE_EXISTS); PWARN ("Databse already exists, Might clobber it."); dbi_conn_close(conn); LEAVE("Error"); @@ -677,10 +650,10 @@ gnc_dbi_session_begin (QofBackend* qof_be, QofSession* session, else { - if (dbi_be->exists()) + if (m_exists) { PERR ("Unable to connect to database '%s'\n", uri.dbname()); - qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR); + set_error (ERR_BACKEND_SERVER_ERR); dbi_conn_close(conn); LEAVE("Error"); return; @@ -688,45 +661,46 @@ gnc_dbi_session_begin (QofBackend* qof_be, QofSession* session, if (create) { - if (!create_database(T, qof_be, conn, uri.quote_dbname(T).c_str())) + if (!create_database(conn, uri.quote_dbname(Type).c_str())) { dbi_conn_close(conn); LEAVE("Error"); return; } - conn = conn_setup(qof_be, options, uri); + conn = conn_setup(options, uri); result = dbi_conn_connect (conn); if (result < 0) { PERR ("Unable to create database '%s'\n", uri.dbname()); - qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR); + set_error (ERR_BACKEND_SERVER_ERR); dbi_conn_close(conn); LEAVE("Error"); return; } - if (T == DbType::DBI_MYSQL) + if (Type == DbType::DBI_MYSQL) adjust_sql_options (conn); - if (!conn_test_dbi_library(conn, qof_be)) + if (!conn_test_dbi_library(conn)) { - if (T == DbType::DBI_PGSQL) + if (Type == DbType::DBI_PGSQL) dbi_conn_select_db (conn, "template1"); dbi_conn_queryf (conn, "DROP DATABASE %s", - uri.quote_dbname(T).c_str()); + uri.quote_dbname(Type).c_str()); dbi_conn_close(conn); return; } } else { - qof_backend_set_error (qof_be, ERR_BACKEND_NO_SUCH_DB); - qof_backend_set_message (qof_be, "Database %s not found", uri.dbname()); + set_error(ERR_BACKEND_NO_SUCH_DB); + std::string msg{"Database "}; + set_message(msg + uri.dbname() + " not found"); } } - dbi_be->connect(nullptr); + connect(nullptr); try { - dbi_be->connect(new GncDbiSqlConnection(T, qof_be, conn, ignore_lock)); + connect(new GncDbiSqlConnection(Type, this, conn, ignore_lock)); } catch (std::runtime_error& err) { @@ -745,7 +719,8 @@ gnc_dbi_session_begin (QofBackend* qof_be, QofSession* session, template<> void error_handler (dbi_conn conn, void* user_data) { - GncDbiBackend* dbi_be = static_cast(user_data); + GncDbiBackend* dbi_be = + static_cast(user_data); const char* msg; (void)dbi_conn_error (conn, &msg); @@ -764,7 +739,7 @@ error_handler (dbi_conn conn, void* user_data) return; } PINFO ("DBI error: %s - Reconnecting...\n", msg); - dbi_be->set_error (ERR_BACKEND_CONN_LOST, 1, true); + dbi_be->set_dbi_error (ERR_BACKEND_CONN_LOST, 1, true); dbi_be->retry_connection(msg); } else if (g_str_has_prefix (msg, "connection pointer is NULL") || @@ -776,7 +751,7 @@ error_handler (dbi_conn conn, void* user_data) ERR_BACKEND_CANT_CONNECT); else { - dbi_be->set_error(ERR_BACKEND_CANT_CONNECT, 1, true); + dbi_be->set_dbi_error(ERR_BACKEND_CANT_CONNECT, 1, true); dbi_be->retry_connection (msg); } } @@ -784,38 +759,28 @@ error_handler (dbi_conn conn, void* user_data) { PERR ("DBI error: %s\n", msg); if (dbi_be->connected()) - dbi_be->set_error (ERR_BACKEND_MISC, 0, false); + dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, false); } } /* ================================================================= */ -static void -gnc_dbi_session_end (QofBackend* qof_be) +template void +GncDbiBackend::session_end () { - GncDbiBackend* dbi_be = reinterpret_cast(qof_be); - - g_return_if_fail (dbi_be != nullptr); - ENTER (" "); - dbi_be->finalize_version_info (); - dbi_be->connect(nullptr); + finalize_version_info (); + connect(nullptr); LEAVE (" "); } -static void -gnc_dbi_destroy_backend (QofBackend* qof_be) +template +GncDbiBackend::~GncDbiBackend() { - g_return_if_fail (qof_be != nullptr); - /* Stop transaction logging */ xaccLogSetBaseName (nullptr); - - qof_backend_destroy (qof_be); - - g_free (qof_be); } /* ================================================================= */ @@ -829,41 +794,38 @@ gnc_dbi_destroy_backend (QofBackend* qof_be) * then the database will be loaded read-only. A resave will update * both values to match this version of Gnucash. */ -void -gnc_dbi_load (QofBackend* qof_be, QofBook* book, QofBackendLoadType loadType) +template void +GncDbiBackend::load (QofBook* book, QofBackendLoadType loadType) { - GncDbiBackend* dbi_be = reinterpret_cast(qof_be); - - g_return_if_fail (qof_be != nullptr); g_return_if_fail (book != nullptr); - ENTER ("dbi_be=%p, book=%p", dbi_be, book); + ENTER ("dbi_be=%p, book=%p", this, book); if (loadType == LOAD_TYPE_INITIAL_LOAD) { // Set up table version information - dbi_be->init_version_info (); - assert (dbi_be->m_book == nullptr); - dbi_be->create_tables(); + init_version_info (); + assert (m_book == nullptr); + create_tables(); } - dbi_be->load(book, loadType); + GncSqlBackend::load(book, loadType); - if (GNUCASH_RESAVE_VERSION > dbi_be->get_table_version("Gnucash")) + if (GNUCASH_RESAVE_VERSION > get_table_version("Gnucash")) { /* The database was loaded with an older database schema or * data semantics. In order to ensure consistency, the whole * thing needs to be saved anew. */ - qof_backend_set_error (qof_be, ERR_SQL_DB_TOO_OLD); + set_error(ERR_SQL_DB_TOO_OLD); } - else if (GNUCASH_RESAVE_VERSION < dbi_be->get_table_version("Gnucash-Resave")) + else if (GNUCASH_RESAVE_VERSION < get_table_version("Gnucash-Resave")) { /* Worse, the database was created with a newer version. We * can't safely write to this database, so the user will have * to do a "save as" to make one that we can write to. */ - qof_backend_set_error (qof_be, ERR_SQL_DB_TOO_NEW); + set_error(ERR_SQL_DB_TOO_NEW); } @@ -894,110 +856,44 @@ save_may_clobber_data (dbi_conn conn, const std::string& dbname) * no errors. If there are errors, drop the new tables and restore the * originals. * - * @param qof_be: QofBackend for the session. * @param book: QofBook to be saved in the database. */ -void -gnc_dbi_safe_sync_all (QofBackend* qof_be, QofBook* book) +template void +GncDbiBackend::safe_sync (QofBook* book) { - GncDbiBackend* dbi_be = reinterpret_cast(qof_be); - auto conn = dynamic_cast(dbi_be->m_conn); + auto conn = dynamic_cast(m_conn); g_return_if_fail (conn != nullptr); - g_return_if_fail (dbi_be != nullptr); g_return_if_fail (book != nullptr); - ENTER ("book=%p, primary=%p", book, dbi_be->m_book); - auto table_list = conn->m_provider->get_table_list (conn->conn(), ""); - if (!conn->table_operation (table_list, backup)) + ENTER ("book=%p, primary=%p", book, m_book); + if (!conn->table_operation (TableOpType::backup)) { - qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR); - conn->table_operation (table_list, rollback); + set_error(ERR_BACKEND_SERVER_ERR); + conn->table_operation (TableOpType::rollback); LEAVE ("Failed to rename tables"); return; } - auto index_list = conn->m_provider->get_index_list (conn->m_conn); - for (auto index : index_list) + if (!conn->drop_indexes()) { - const char* errmsg; - conn->m_provider->drop_index (conn->m_conn, index); - if (DBI_ERROR_NONE != dbi_conn_error (conn->m_conn, &errmsg)) - { - qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR); - conn->table_operation (table_list, rollback); - LEAVE ("Failed to drop indexes %s", errmsg); - return; - } + conn->table_operation (TableOpType::rollback); + set_error (ERR_BACKEND_SERVER_ERR); + set_message("Failed to drop indexes"); + LEAVE ("Failed to drop indexes"); + return; } - dbi_be->sync_all(book); - if (qof_backend_check_error (qof_be)) + sync(m_book); + if (check_error()) { - conn->table_operation (table_list, rollback); + conn->table_operation (TableOpType::rollback); LEAVE ("Failed to create new database tables"); return; } - conn->table_operation (table_list, drop_backup); - LEAVE ("book=%p", book); + conn->table_operation (TableOpType::drop_backup); + LEAVE ("book=%p", m_book); } /* ================================================================= */ -static void -gnc_dbi_begin_edit (QofBackend* qof_be, QofInstance* inst) -{ - GncDbiBackend* dbi_be = reinterpret_cast(qof_be); - - g_return_if_fail (dbi_be != nullptr); - g_return_if_fail (inst != nullptr); - - dbi_be->begin_edit(inst); -} - -static void -gnc_dbi_rollback_edit (QofBackend* qof_be, QofInstance* inst) -{ - GncDbiBackend* dbi_be = reinterpret_cast(qof_be); - - g_return_if_fail (dbi_be != nullptr); - g_return_if_fail (inst != nullptr); - - dbi_be->rollback_edit(inst); -} - -static void -gnc_dbi_commit_edit (QofBackend* qof_be, QofInstance* inst) -{ - GncDbiBackend* dbi_be = reinterpret_cast(qof_be); - - g_return_if_fail (dbi_be != nullptr); - g_return_if_fail (inst != nullptr); - - dbi_be->commit_edit(inst); -} - -/* ================================================================= */ - -static void -init_sql_backend (GncDbiBackend* dbi_be) -{ - QofBackend* qof_be = reinterpret_cast(dbi_be); - - qof_be->session_end = gnc_dbi_session_end; - qof_be->destroy_backend = gnc_dbi_destroy_backend; - - qof_be->load = gnc_dbi_load; - - /* The gda backend treats accounting periods transactionally. */ - qof_be->begin = gnc_dbi_begin_edit; - qof_be->commit = gnc_dbi_commit_edit; - qof_be->rollback = gnc_dbi_rollback_edit; - - /* The SQL/DBI backend doesn't need to be synced until it is - * configured for multiuser access. */ - qof_be->sync = gnc_dbi_safe_sync_all; - qof_be->safe_sync = gnc_dbi_safe_sync_all; - /* CoA Export function not implemented for the SQL backend. */ - qof_be->export_fn = nullptr; -} /* * Checks to see whether the file is an sqlite file or not @@ -1259,8 +1155,8 @@ dbi_library_test (dbi_conn conn) return retval; } -static bool -conn_test_dbi_library(dbi_conn conn, QofBackend* qof_be) +template bool +GncDbiBackend::conn_test_dbi_library(dbi_conn conn) { auto result = dbi_library_test (conn); switch (result) @@ -1269,15 +1165,13 @@ conn_test_dbi_library(dbi_conn conn, QofBackend* qof_be) break; case GNC_DBI_FAIL_SETUP: - qof_backend_set_error (qof_be, ERR_SQL_DBI_UNTESTABLE); - qof_backend_set_message (qof_be, - "DBI library large number test incomplete"); + set_error(ERR_SQL_DBI_UNTESTABLE); + set_message ("DBI library large number test incomplete"); break; case GNC_DBI_FAIL_TEST: - qof_backend_set_error (qof_be, ERR_SQL_BAD_DBI); - qof_backend_set_message (qof_be, - "DBI library fails large number test"); + set_error (ERR_SQL_BAD_DBI); + set_message ("DBI library fails large number test"); break; } return result == GNC_DBI_PASS; diff --git a/src/backend/dbi/gnc-backend-dbi.hpp b/src/backend/dbi/gnc-backend-dbi.hpp index 0a571d1649..5d027324b2 100644 --- a/src/backend/dbi/gnc-backend-dbi.hpp +++ b/src/backend/dbi/gnc-backend-dbi.hpp @@ -49,14 +49,14 @@ class GncSqlRow; * @var rollback drop the name table if it exists and rename name_back to name * @var drop_backup Drop the backup table */ -typedef enum +enum TableOpType { drop = 0, empty, backup, rollback, drop_backup -} TableOpType; +}; /** * Return values from conn_test_dbi_library @@ -84,14 +84,22 @@ enum class DbType /** * Implementations of GncSqlBackend. */ +struct UriStrings; + +template class GncDbiBackend : public GncSqlBackend { public: GncDbiBackend(GncSqlConnection *conn, QofBook* book) : GncSqlBackend(conn, book), m_exists{false} {} + ~GncDbiBackend(); + void session_begin(QofSession*, const char*, bool, bool, bool) override; + void session_end() override; + void load(QofBook*, QofBackendLoadType) override; + void safe_sync(QofBook*) override; bool connected() const noexcept { return m_conn != nullptr; } /** FIXME: Just a pass-through to m_conn: */ - void set_error(int error, unsigned int repeat, bool retry) noexcept + void set_dbi_error(int error, unsigned int repeat, bool retry) noexcept { m_conn->set_error(error, repeat, retry); } @@ -102,15 +110,14 @@ public: /*-----*/ bool exists() { return m_exists; } void set_exists(bool exists) { m_exists = exists; } - friend void gnc_dbi_load(QofBackend*, QofBook*, QofBackendLoadType); - friend void gnc_dbi_safe_sync_all(QofBackend*, QofBook*); private: + dbi_conn conn_setup(PairVec& options, UriStrings& uri); + bool conn_test_dbi_library(dbi_conn conn); + bool set_standard_connection_options(dbi_conn conn, const UriStrings& uri); + bool create_database(dbi_conn conn, const char* db); bool m_exists; // Does the database exist? }; - -void gnc_dbi_safe_sync_all (QofBackend* qbe, QofBook* book); - /* external access required for tests */ std::string adjust_sql_options_string(const std::string&); diff --git a/src/backend/dbi/gnc-dbisqlconnection.cpp b/src/backend/dbi/gnc-dbisqlconnection.cpp index 8731bdb976..b3f56d49aa 100644 --- a/src/backend/dbi/gnc-dbisqlconnection.cpp +++ b/src/backend/dbi/gnc-dbisqlconnection.cpp @@ -28,6 +28,10 @@ extern "C" #include #include } + +#include +#include + #include "gnc-dbisqlconnection.hpp" static QofLogModule log_module = G_LOG_DOMAIN; @@ -120,7 +124,7 @@ GncDbiSqlConnection::lock_database (bool ignore_lock) std::string err{"SQL Backend failed to obtain a transaction: "}; err += errstr; qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR); - qof_backend_set_message (m_qbe, err.c_str()); + m_qbe->set_message (err.c_str()); return false; } dbi_result_free(result); @@ -143,7 +147,7 @@ GncDbiSqlConnection::lock_database (bool ignore_lock) if (!result) { qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR); - qof_backend_set_message (m_qbe, "Failed to delete lock record"); + m_qbe->set_message("Failed to delete lock record"); result = dbi_conn_query (m_conn, "ROLLBACK"); if (result) dbi_result_free (result); @@ -161,7 +165,7 @@ GncDbiSqlConnection::lock_database (bool ignore_lock) if (!result) { qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR); - qof_backend_set_message (m_qbe, "Failed to create lock record"); + m_qbe->set_message("Failed to create lock record"); result = dbi_conn_query (m_conn, "ROLLBACK"); if (result) dbi_result_free (result); @@ -175,7 +179,7 @@ GncDbiSqlConnection::lock_database (bool ignore_lock) qof_backend_set_error(m_qbe, ERR_BACKEND_SERVER_ERR); std::string err{"Failed to commit transaction: "}; err += errstr; - qof_backend_set_message(m_qbe, err.c_str()); + m_qbe->set_message(err.c_str()); return false; } dbi_result_free (result); @@ -185,8 +189,6 @@ GncDbiSqlConnection::lock_database (bool ignore_lock) void GncDbiSqlConnection::unlock_database () { - GncDbiBackend* qe = reinterpret_cast(m_qbe); - if (m_conn == nullptr) return; g_return_if_fail (dbi_conn_error (m_conn, nullptr) == 0); @@ -221,7 +223,7 @@ GncDbiSqlConnection::unlock_database () if (!result) { PERR ("Failed to delete the lock entry"); - qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR); + m_qbe->set_error (ERR_BACKEND_SERVER_ERR); result = dbi_conn_query (m_conn, "ROLLBACK"); if (result) { @@ -258,7 +260,7 @@ GncDbiSqlConnection::unlock_database () result = nullptr; } PWARN ("Unable to get a lock on LOCK, so failed to clear the lock entry."); - qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR); + m_qbe->set_error (ERR_BACKEND_SERVER_ERR); } GncDbiSqlConnection::~GncDbiSqlConnection() @@ -586,16 +588,16 @@ GncDbiSqlConnection::table_manage_backup (const std::string& table_name, dbi_result result = nullptr; switch (op) { - case backup: + case TableOpType::backup: result = dbi_conn_queryf (m_conn, "ALTER TABLE %s RENAME TO %s", table_name.c_str(), new_name.c_str()); break; - case rollback: + case TableOpType::rollback: result = dbi_conn_queryf (m_conn, "ALTER TABLE %s RENAME TO %s", new_name.c_str(), table_name.c_str()); break; - case drop_backup: + case TableOpType::drop_backup: result = dbi_conn_queryf (m_conn, "DROP TABLE %s", new_name.c_str()); break; @@ -630,16 +632,20 @@ GncDbiSqlConnection::table_manage_backup (const std::string& table_name, */ bool -GncDbiSqlConnection::table_operation(const StrVec& table_names, - TableOpType op) noexcept +GncDbiSqlConnection::table_operation(TableOpType op) noexcept { - g_return_val_if_fail (!table_names.empty(), FALSE); + static const std::regex backupre (".*_back"); bool retval{true}; - for (auto table : table_names) + for (auto table : m_provider->get_table_list(m_conn, "")) { dbi_result result; - /* Ignore the lock table */ - if (table == lock_table) + /* Skip the lock table and existing backup tables; the former we don't + * want to touch, the latter are handled by table_manage_backup. It + * would be nicer to handle this with the get_table_list query, but that + * can accept only SQL LIKE patterns (not even regexps) and there's no + * way to have a negative one. + */ + if (table == lock_table || std::regex_match(table, backupre)) { continue; } @@ -660,7 +666,7 @@ GncDbiSqlConnection::table_operation(const StrVec& table_names, break; } } - /* Fall through */ + /* Fall through to rename the _back tables back.*/ case backup: case drop_backup: result = table_manage_backup (table, op); @@ -689,6 +695,23 @@ GncDbiSqlConnection::table_operation(const StrVec& table_names, return retval; } +bool +GncDbiSqlConnection::drop_indexes() noexcept +{ + auto index_list = m_provider->get_index_list (m_conn); + for (auto index : index_list) + { + const char* errmsg; + m_provider->drop_index (m_conn, index); + if (DBI_ERROR_NONE != dbi_conn_error (m_conn, &errmsg)) + { + PERR("Failed to drop indexes %s", errmsg); + return false; + } + } + return true; +} + std::string GncDbiSqlConnection::add_columns_ddl(const std::string& table_name, const ColVec& info_vec) const noexcept diff --git a/src/backend/dbi/gnc-dbisqlconnection.hpp b/src/backend/dbi/gnc-dbisqlconnection.hpp index 466e5d97ae..05973c8779 100644 --- a/src/backend/dbi/gnc-dbisqlconnection.hpp +++ b/src/backend/dbi/gnc-dbisqlconnection.hpp @@ -81,12 +81,10 @@ public: bool verify() noexcept override; bool retry_connection(const char* msg) noexcept override; dbi_result table_manage_backup(const std::string& table_name, TableOpType op); - bool table_operation (const StrVec& table_name_list, - TableOpType op) noexcept; + bool table_operation (TableOpType op) noexcept; std::string add_columns_ddl(const std::string& table_name, const ColVec& info_vec) const noexcept; - friend void gnc_dbi_safe_sync_all (QofBackend* qbe, QofBook* book); - + bool drop_indexes() noexcept; private: QofBackend* m_qbe; dbi_conn m_conn; diff --git a/src/backend/sql/gnc-sql-backend.cpp b/src/backend/sql/gnc-sql-backend.cpp index 9dd8064584..6447746b2c 100644 --- a/src/backend/sql/gnc-sql-backend.cpp +++ b/src/backend/sql/gnc-sql-backend.cpp @@ -80,10 +80,8 @@ static EntryVec version_table }; GncSqlBackend::GncSqlBackend(GncSqlConnection *conn, QofBook* book) : - qof_be {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, ERR_BACKEND_NO_ERR, nullptr, 0, - nullptr}, m_conn{conn}, m_book{book}, m_loading{false}, - m_in_query{false}, m_is_pristine_db{false} + QofBackend {}, m_conn{conn}, m_book{book}, m_loading{false}, + m_in_query{false}, m_is_pristine_db{false} { if (conn != nullptr) connect (conn); @@ -188,15 +186,15 @@ GncSqlBackend::add_columns_to_table(const std::string& table_name, void GncSqlBackend::update_progress() const noexcept { - if (qof_be.percentage != nullptr) - (qof_be.percentage) (nullptr, 101.0); + if (m_percentage != nullptr) + (m_percentage) (nullptr, 101.0); } void GncSqlBackend::finish_progress() const noexcept { - if (qof_be.percentage != nullptr) - (qof_be.percentage) (nullptr, -1.0); + if (m_percentage != nullptr) + (m_percentage) (nullptr, -1.0); } void @@ -441,7 +439,7 @@ GncSqlBackend::write_schedXactions() #pragma GCC diagnostic warning "-Wformat-nonliteral" void -GncSqlBackend::sync_all(QofBook* book) +GncSqlBackend::sync(QofBook* book) { g_return_if_fail (book != NULL); @@ -500,8 +498,7 @@ GncSqlBackend::sync_all(QofBook* book) } else { - if (!qof_backend_check_error (&qof_be)) - qof_backend_set_error (&qof_be, ERR_BACKEND_SERVER_ERR); + set_error (ERR_BACKEND_SERVER_ERR); is_ok = m_conn->rollback_transaction (); } finish_progress(); @@ -512,7 +509,7 @@ GncSqlBackend::sync_all(QofBook* book) /* Routines to deal with the creation of multiple books. */ void -GncSqlBackend::begin_edit (QofInstance* inst) +GncSqlBackend::begin(QofInstance* inst) { g_return_if_fail (inst != NULL); @@ -521,7 +518,7 @@ GncSqlBackend::begin_edit (QofInstance* inst) } void -GncSqlBackend::rollback_edit(QofInstance* inst) +GncSqlBackend::rollback(QofInstance* inst) { g_return_if_fail (inst != NULL); @@ -546,7 +543,7 @@ GncSqlBackend::get_object_backend(const std::string& type) const noexcept * type and call its commit handler */ void -GncSqlBackend::commit_edit (QofInstance* inst) +GncSqlBackend::commit (QofInstance* inst) { sql_backend be_data; gboolean is_dirty; @@ -557,7 +554,7 @@ GncSqlBackend::commit_edit (QofInstance* inst) if (qof_book_is_readonly(m_book)) { - qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY); + set_error (ERR_BACKEND_READONLY); (void)m_conn->rollback_transaction (); return; } diff --git a/src/backend/sql/gnc-sql-backend.hpp b/src/backend/sql/gnc-sql-backend.hpp index 3146960dc9..31a908f5f0 100644 --- a/src/backend/sql/gnc-sql-backend.hpp +++ b/src/backend/sql/gnc-sql-backend.hpp @@ -27,13 +27,14 @@ extern "C" { #include -#include #include } #include #include #include #include +#include + class GncSqlColumnTableEntry; using GncSqlColumnTableEntryPtr = std::shared_ptr; using EntryVec = std::vector; @@ -61,11 +62,41 @@ typedef enum * * Main SQL backend structure. */ -class GncSqlBackend +class GncSqlBackend : public QofBackend { public: GncSqlBackend(GncSqlConnection *conn, QofBook* book); virtual ~GncSqlBackend() = default; + /** + * Load the contents of an SQL database into a book. + * + * @param book Book to be loaded + */ + void load(QofBook*, QofBackendLoadType) override; + /** + * Save the contents of a book to an SQL database. + * + * @param book Book to be saved + */ + void sync(QofBook*) override; + /** + * An object is about to be edited. + * + * @param inst Object being edited + */ + void begin(QofInstance*) override; + /** + * Object editting is complete and the object should be saved. + * + * @param inst Object being edited + */ + void commit(QofInstance*) override; + /** + * Object editing has been cancelled. + * + * @param inst Object being edited + */ + void rollback(QofInstance*) override; /** Connect the backend to a GncSqlConnection. * Sets up version info. Calling with nullptr clears the connection and * destroys the version info. @@ -156,36 +187,6 @@ public: */ uint_t get_table_version(const std::string& table_name) const noexcept; bool set_table_version (const std::string& table_name, uint_t version) noexcept; - /** - * Load the contents of an SQL database into a book. - * - * @param book Book to be loaded - */ - void load(QofBook*, QofBackendLoadType); - /** - * Save the contents of a book to an SQL database. - * - * @param book Book to be saved - */ - void sync_all(QofBook*); - /** - * An object is about to be edited. - * - * @param inst Object being edited - */ - void begin_edit(QofInstance*); - /** - * Object editting is complete and the object should be saved. - * - * @param inst Object being edited - */ - void commit_edit(QofInstance*); - /** - * Object editing has been cancelled. - * - * @param inst Object being edited - */ - void rollback_edit(QofInstance*); /** * Register a commodity to be committed after loading is complete. * @@ -240,7 +241,6 @@ public: void finish_progress() const noexcept; protected: - QofBackend qof_be; /**< QOF backend. Not a pointer, nor really a member */ GncSqlConnection* m_conn; /**< SQL connection */ QofBook* m_book; /**< The primary, main open book */ bool m_loading; /**< We are performing an initial load */ diff --git a/src/backend/sql/gnc-sql-column-table-entry.hpp b/src/backend/sql/gnc-sql-column-table-entry.hpp index 26b9271e22..0dceddf01d 100644 --- a/src/backend/sql/gnc-sql-column-table-entry.hpp +++ b/src/backend/sql/gnc-sql-column-table-entry.hpp @@ -27,7 +27,6 @@ extern "C" { #include -#include "qofbackend-p.h" } #include #include diff --git a/src/backend/sql/test/utest-gnc-backend-sql.cpp b/src/backend/sql/test/utest-gnc-backend-sql.cpp index cd7107b952..9e8154cdcd 100644 --- a/src/backend/sql/test/utest-gnc-backend-sql.cpp +++ b/src/backend/sql/test/utest-gnc-backend-sql.cpp @@ -34,6 +34,17 @@ extern "C" static const gchar* suitename = "/backend/sql/gnc-backend-sql"; void test_suite_gnc_backend_sql (void); + +class GncMockSqlBackend : public GncSqlBackend +{ +public: + GncMockSqlBackend(GncSqlConnection* conn, QofBook* book) : + GncSqlBackend(conn, book) {} + void session_begin(QofSession*, const char*, bool, bool, bool) override {} + void session_end() override {} + void safe_sync(QofBook* book) override { sync(book); } +}; + class GncMockSqlConnection; class GncMockSqlResult : public GncSqlResult @@ -265,7 +276,7 @@ test_gnc_sql_commit_edit (void) guint dirty_called = 0; GncMockSqlConnection conn; const char* msg1 = - "[GncSqlBackend::commit_edit()] Unknown object type 'null'\n"; + "[GncSqlBackend::commit()] Unknown object type 'null'\n"; GLogLevelFlags loglevel = static_cast (G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL); const char* logdomain = "gnc.backend.sql"; @@ -281,7 +292,8 @@ test_gnc_sql_commit_edit (void) qof_object_initialize (); auto book = qof_book_new(); - GncSqlBackend sql_be (&conn, book); + auto sql_be = new GncMockSqlBackend + (&conn, book); inst = static_cast (g_object_new (QOF_TYPE_INSTANCE, NULL)); qof_instance_init_data (inst, QOF_ID_NULL, book); qof_book_set_dirty_cb (book, test_dirty_cb, &dirty_called); @@ -291,7 +303,7 @@ test_gnc_sql_commit_edit (void) g_assert (qof_instance_get_dirty_flag (inst)); g_assert (qof_book_session_not_saved (book)); g_assert_cmpint (dirty_called, == , 1); - sql_be.commit_edit (inst); + sql_be->commit(inst); g_assert (!qof_instance_get_dirty_flag (inst)); g_assert (!qof_book_session_not_saved (book)); g_assert_cmpint (dirty_called, == , 0); @@ -302,7 +314,7 @@ test_gnc_sql_commit_edit (void) g_assert (!qof_instance_get_dirty_flag (QOF_INSTANCE (book))); g_assert (qof_book_session_not_saved (book)); g_assert_cmpint (dirty_called, == , 1); - sql_be.commit_edit (QOF_INSTANCE (book)); + sql_be->commit(QOF_INSTANCE (book)); g_assert (!qof_instance_get_dirty_flag (QOF_INSTANCE (book))); g_assert (qof_book_session_not_saved (book)); g_assert_cmpint (dirty_called, == , 1); @@ -313,7 +325,7 @@ test_gnc_sql_commit_edit (void) g_assert (qof_instance_get_dirty_flag (QOF_INSTANCE (book))); g_assert (qof_book_session_not_saved (book)); g_assert_cmpint (dirty_called, == , 1); - sql_be.commit_edit(QOF_INSTANCE (book)); + sql_be->commit(QOF_INSTANCE (book)); g_assert (!qof_instance_get_dirty_flag (QOF_INSTANCE (book))); g_assert (!qof_book_session_not_saved (book)); g_assert_cmpint (dirty_called, == , 0); @@ -322,6 +334,7 @@ test_gnc_sql_commit_edit (void) g_log_remove_handler (logdomain, hdlr1); g_object_unref (inst); g_object_unref (book); + delete sql_be; } /* handle_and_term static void diff --git a/src/backend/xml/CMakeLists.txt b/src/backend/xml/CMakeLists.txt index 26f4642dad..3094beccca 100644 --- a/src/backend/xml/CMakeLists.txt +++ b/src/backend/xml/CMakeLists.txt @@ -20,6 +20,7 @@ SET (backend_xml_utils_noinst_HEADERS gnc-owner-xml-v2.h gnc-tax-table-xml-v2.h gnc-vendor-xml-v2.h + gnc-xml-backend.hpp gnc-xml-helper.h io-example-account.h io-gncxml-gen.h @@ -57,6 +58,7 @@ SET (backend_xml_utils_SOURCES gnc-tax-table-xml-v2.cpp gnc-transaction-xml-v2.cpp gnc-vendor-xml-v2.cpp + gnc-xml-backend.cpp gnc-xml-helper.cpp io-example-account.cpp io-gncxml-gen.cpp diff --git a/src/backend/xml/Makefile.am b/src/backend/xml/Makefile.am index e84e58932a..e64da12dec 100644 --- a/src/backend/xml/Makefile.am +++ b/src/backend/xml/Makefile.am @@ -41,6 +41,7 @@ libgnc_backend_xml_utils_la_SOURCES = \ gnc-tax-table-xml-v2.cpp \ gnc-transaction-xml-v2.cpp \ gnc-vendor-xml-v2.cpp \ + gnc-xml-backend.cpp \ gnc-xml-helper.cpp \ io-example-account.cpp \ io-gncxml-gen.cpp \ @@ -71,6 +72,7 @@ noinst_HEADERS = \ gnc-owner-xml-v2.h \ gnc-tax-table-xml-v2.h \ gnc-vendor-xml-v2.h \ + gnc-xml-backend.hpp \ gnc-xml-helper.h \ io-example-account.h \ io-gncxml-gen.h \ diff --git a/src/backend/xml/gnc-backend-xml.cpp b/src/backend/xml/gnc-backend-xml.cpp index 40ab3cfa35..d3e2cb208e 100644 --- a/src/backend/xml/gnc-backend-xml.cpp +++ b/src/backend/xml/gnc-backend-xml.cpp @@ -31,10 +31,6 @@ extern "C" { #include "config.h" -#include -#if PLATFORM(WINDOWS) -#include -#endif #include #include @@ -43,9 +39,7 @@ extern "C" #include #include #include -#include #include -#include #ifdef HAVE_UNISTD_H # include #else @@ -73,10 +67,8 @@ extern "C" #endif #include "qof.h" -#include "TransLog.h" #include "gnc-engine.h" - -#include "gnc-uri-utils.h" +#include #include "gnc-prefs.h" #ifndef HAVE_STRPTIME @@ -86,7 +78,8 @@ extern "C" #include #include "gnc-backend-xml.h" -#include +#include +#include "gnc-xml-backend.hpp" #include "gnc-xml-helper.h" #include "io-gncxml-v2.h" #include "io-gncxml.h" @@ -105,7 +98,6 @@ extern "C" static QofLogModule log_module = GNC_MOD_BACKEND; -static gboolean save_may_clobber_data (FileBackend *bend); struct QofXmlBackendProvider : public QofBackendProvider { @@ -116,504 +108,11 @@ struct QofXmlBackendProvider : public QofBackendProvider QofXmlBackendProvider(QofXmlBackendProvider&&) = delete; QofXmlBackendProvider operator=(QofXmlBackendProvider&&) = delete; ~QofXmlBackendProvider () = default; - QofBackend* create_backend(void); + QofBackend* create_backend(void) { return new GncXmlBackend; } bool type_check(const char* type); }; -/* ================================================================= */ - -static gboolean -gnc_xml_be_get_file_lock (FileBackend* be) -{ - struct stat statbuf; -#ifndef G_OS_WIN32 - char* pathbuf = NULL, *path = NULL, *tmpbuf = NULL; - size_t pathbuf_size = 0; -#endif - int rc; - QofBackendError be_err; - - rc = g_stat (be->lockfile, &statbuf); - if (!rc) - { - /* oops .. file is locked by another user .. */ - qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED); - return FALSE; - } - - be->lockfd = g_open (be->lockfile, O_RDWR | O_CREAT | O_EXCL , - S_IRUSR | S_IWUSR); - if (be->lockfd < 0) - { - /* oops .. we can't create the lockfile .. */ - switch (errno) - { - case EACCES: - case EROFS: - case ENOSPC: - PWARN ("Unable to create the lockfile %s; may not have write priv", - be->lockfile); - be_err = ERR_BACKEND_READONLY; - break; - default: - be_err = ERR_BACKEND_LOCKED; - break; - } - qof_backend_set_error ((QofBackend*)be, be_err); - return FALSE; - } - - /* OK, now work around some NFS atomic lock race condition - * mumbo-jumbo. We do this by linking a unique file, and - * then examining the link count. At least that's what the - * NFS programmers guide suggests. - * Note: the "unique filename" must be unique for the - * triplet filename-host-process, otherwise accidental - * aliases can occur. - */ - - /* apparently, even this code may not work for some NFS - * implementations. In the long run, I am told that - * ftp.debian.org - * /pub/debian/dists/unstable/main/source/libs/liblockfile_0.1-6.tar.gz - * provides a better long-term solution. - */ - -#ifndef G_OS_WIN32 - pathbuf_size = strlen (be->lockfile) + 100; - pathbuf = (char*) malloc (pathbuf_size); - if (pathbuf == NULL) - { - return FALSE; - } - strcpy (pathbuf, be->lockfile); - path = strrchr (pathbuf, '.'); - while (snprintf (path, pathbuf_size - (path - pathbuf), ".%lx.%d.LNK", - gethostid (), getpid ()) - >= static_cast (pathbuf_size - (path - pathbuf))) - { - pathbuf_size += 100; - tmpbuf = (char*) realloc (pathbuf, pathbuf_size); - if (tmpbuf == NULL) - { - free (pathbuf); - return FALSE; - } - else - { - pathbuf = tmpbuf; - } - } - - rc = link (be->lockfile, pathbuf); - if (rc) - { - /* If hard links aren't supported, just allow the lock. */ - if (errno == EPERM || errno == ENOSYS -# ifdef EOPNOTSUPP - || errno == EOPNOTSUPP -# endif -# ifdef ENOTSUP - || errno == ENOTSUP -# endif - ) - { - be->linkfile = NULL; - free (pathbuf); - return TRUE; - } - - /* Otherwise, something else is wrong. */ - qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED); - g_unlink (pathbuf); - free (pathbuf); - close (be->lockfd); - g_unlink (be->lockfile); - return FALSE; - } - - rc = g_stat (be->lockfile, &statbuf); - if (rc) - { - /* oops .. stat failed! This can't happen! */ - qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED); - qof_backend_set_message ((QofBackend*)be, "Failed to stat lockfile %s", - be->lockfile); - g_unlink (pathbuf); - free (pathbuf); - close (be->lockfd); - g_unlink (be->lockfile); - return FALSE; - } - - if (statbuf.st_nlink != 2) - { - qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED); - g_unlink (pathbuf); - free (pathbuf); - close (be->lockfd); - g_unlink (be->lockfile); - return FALSE; - } - - be->linkfile = g_strdup (pathbuf); - free (pathbuf); - - return TRUE; - -#else /* ifndef G_OS_WIN32 */ - /* On windows, there is no NFS and the open(,O_CREAT | O_EXCL) - is sufficient for locking. */ - be->linkfile = NULL; - return TRUE; -#endif /* ifndef G_OS_WIN32 */ -} - -/* ================================================================= */ -#define XML_URI_PREFIX "xml://" -#define FILE_URI_PREFIX "file://" - -static void -xml_session_begin (QofBackend* be_start, QofSession* session, - const char* book_id, gboolean ignore_lock, - gboolean create, gboolean force) -{ - FileBackend* be = (FileBackend*) be_start; - - ENTER (" "); - - /* Make sure the directory is there */ - be->fullpath = gnc_uri_get_path (book_id); - - if (NULL == be->fullpath) - { - qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND); - qof_backend_set_message (be_start, "No path specified"); - LEAVE (""); - return; - } - if (create && !force && save_may_clobber_data (be)) - { - qof_backend_set_error (be_start, ERR_BACKEND_STORE_EXISTS); - LEAVE ("Might clobber, no force"); - return; - } - - be->be.fullpath = be->fullpath; - be->dirname = g_path_get_dirname (be->fullpath); - - { - struct stat statbuf; - int rc; - - /* Again check whether the directory can be accessed */ - rc = g_stat (be->dirname, &statbuf); - if (rc != 0 -#if COMPILER(MSVC) - || (statbuf.st_mode & _S_IFDIR) == 0 -#else - || !S_ISDIR (statbuf.st_mode) -#endif - ) - { - /* Error on stat or if it isn't a directory means we - cannot find this filename */ - qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND); - qof_backend_set_message (be_start, "Couldn't find directory for %s", - be->fullpath); - PWARN ("Couldn't find directory for %s", be->fullpath); - g_free (be->fullpath); - be->fullpath = NULL; - g_free (be->dirname); - be->dirname = NULL; - LEAVE (""); - return; - } - - /* Now check whether we can g_stat the file itself */ - rc = g_stat (be->fullpath, &statbuf); - if ((rc != 0) && (!create)) - { - /* Error on stat means the file doesn't exist */ - qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND); - qof_backend_set_message (be_start, "Couldn't find %s", be->fullpath); - PWARN ("Couldn't find %s", be->fullpath); - g_free (be->fullpath); - be->fullpath = NULL; - g_free (be->dirname); - be->dirname = NULL; - LEAVE (""); - return; - } - if (rc == 0 -#if COMPILER(MSVC) - && (statbuf.st_mode & _S_IFDIR) != 0 -#else - && S_ISDIR (statbuf.st_mode) -#endif - ) - { - qof_backend_set_error (be_start, ERR_FILEIO_UNKNOWN_FILE_TYPE); - qof_backend_set_message (be_start, "Path %s is a directory", - be->fullpath); - PWARN ("Path %s is a directory", be->fullpath); - g_free (be->fullpath); - be->fullpath = NULL; - g_free (be->dirname); - be->dirname = NULL; - LEAVE (""); - return; - } - } - - - /* ---------------------------------------------------- */ - /* We should now have a fully resolved path name. - * Let's start logging */ - xaccLogSetBaseName (be->fullpath); - PINFO ("logpath=%s", be->fullpath ? be->fullpath : "(null)"); - - /* And let's see if we can get a lock on it. */ - be->lockfile = g_strconcat (be->fullpath, ".LCK", NULL); - - if (!ignore_lock && !gnc_xml_be_get_file_lock (be)) - { - // We should not ignore the lock, but couldn't get it. The - // be_get_file_lock() already set the appropriate backend_error in this - // case, so we just return here. - g_free (be->lockfile); - be->lockfile = NULL; - - if (force) - { - QofBackendError berror = qof_backend_get_error (be_start); - if (berror == ERR_BACKEND_LOCKED || berror == ERR_BACKEND_READONLY) - { - // Even though we couldn't get the lock, we were told to force - // the opening. This is ok because the FORCE argument is - // changed only if the caller wants a read-only book. - } - else - { - // Unknown error. Push it again on the error stack. - qof_backend_set_error (be_start, berror); - } - } - - LEAVE (""); - return; - } - - LEAVE (" "); - return; -} - -/* ================================================================= */ - -static void -xml_session_end (QofBackend* be_start) -{ - FileBackend* be = (FileBackend*)be_start; - ENTER (" "); - - if (be->book && qof_book_is_readonly (be->book)) - { - qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_READONLY); - return; - } - - if (be->linkfile) - g_unlink (be->linkfile); - - if (be->lockfd > 0) - close (be->lockfd); - - if (be->lockfile) - { - int rv; -#ifdef G_OS_WIN32 - /* On windows, we need to allow write-access before - g_unlink() can succeed */ - rv = g_chmod (be->lockfile, S_IWRITE | S_IREAD); -#endif - rv = g_unlink (be->lockfile); - if (rv) - { - PWARN ("Error on g_unlink(%s): %d: %s", be->lockfile, - errno, g_strerror (errno) ? g_strerror (errno) : ""); - } - } - - g_free (be->dirname); - be->dirname = NULL; - - g_free (be->fullpath); - be->fullpath = NULL; - - g_free (be->lockfile); - be->lockfile = NULL; - - g_free (be->linkfile); - be->linkfile = NULL; - LEAVE (" "); -} - -static void -xml_destroy_backend (QofBackend* be) -{ - /* Stop transaction logging */ - xaccLogSetBaseName (NULL); - - qof_backend_destroy (be); - g_free (be); -} - -/* ================================================================= */ -/* Write the financial data in a book to a file, returning FALSE on - error and setting the error_result to indicate what went wrong if - it's not NULL. This function does not manage file locks in any - way. - - If make_backup is true, write out a time-stamped copy of the file - into the same directory as the indicated file, with a filename of - "file.YYYYMMDDHHMMSS.gnucash" where YYYYMMDDHHMMSS is replaced with the - current year/month/day/hour/minute/second. */ - -/* The variable buf_size must be a compile-time constant */ -#define buf_size 1024 - -static gboolean -copy_file (const char* orig, const char* bkup) -{ - char buf[buf_size]; - int orig_fd; - int bkup_fd; - int flags = 0; - ssize_t count_write; - ssize_t count_read; - -#ifdef G_OS_WIN32 - flags = O_BINARY; -#endif - - orig_fd = g_open (orig, O_RDONLY | flags, 0); - if (orig_fd == -1) - { - return FALSE; - } - bkup_fd = g_open (bkup, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | flags, 0600); - if (bkup_fd == -1) - { - close (orig_fd); - return FALSE; - } - - do - { - count_read = read (orig_fd, buf, buf_size); - if (count_read == -1 && errno != EINTR) - { - close (orig_fd); - close (bkup_fd); - return FALSE; - } - - if (count_read > 0) - { - count_write = write (bkup_fd, buf, count_read); - if (count_write == -1) - { - close (orig_fd); - close (bkup_fd); - return FALSE; - } - } - } - while (count_read > 0); - - close (orig_fd); - close (bkup_fd); - - return TRUE; -} - -/* ================================================================= */ - -static gboolean -gnc_int_link_or_make_backup (FileBackend* be, const char* orig, - const char* bkup) -{ - gboolean copy_success = FALSE; - int err_ret = -#ifdef HAVE_LINK - link (orig, bkup) -#else - - 1 -#endif - ; - if (err_ret != 0) - { -#ifdef HAVE_LINK - if (errno == EPERM || errno == ENOSYS -# ifdef EOPNOTSUPP - || errno == EOPNOTSUPP -# endif -# ifdef ENOTSUP - || errno == ENOTSUP -# endif -# ifdef ENOSYS - || errno == ENOSYS -# endif - ) -#endif - { - copy_success = copy_file (orig, bkup); - } - - if (!copy_success) - { - qof_backend_set_error ((QofBackend*)be, ERR_FILEIO_BACKUP_ERROR); - PWARN ("unable to make file backup from %s to %s: %s", - orig, bkup, g_strerror (errno) ? g_strerror (errno) : ""); - return FALSE; - } - } - - return TRUE; -} - -/* ================================================================= */ - -static QofBookFileType -gnc_xml_be_determine_file_type (const char* path) -{ - gboolean with_encoding; - QofBookFileType v2type; - - v2type = gnc_is_xml_data_file_v2 (path, &with_encoding); - if (v2type == GNC_BOOK_XML2_FILE) - { - if (with_encoding) - { - return GNC_BOOK_XML2_FILE; - } - else - { - return GNC_BOOK_XML2_FILE_NO_ENCODING; - } - } - else if (v2type == GNC_BOOK_POST_XML2_0_0_FILE) - { - return GNC_BOOK_POST_XML2_0_0_FILE; - } - else if (v2type == GNC_BOOK_XML1_FILE) - { - return GNC_BOOK_XML1_FILE; - } - return GNC_BOOK_NOT_OURS; -} - bool QofXmlBackendProvider::type_check (const char *uri) { @@ -671,593 +170,8 @@ det_exit: return result; } -static gboolean -gnc_xml_be_backup_file (FileBackend* be) -{ - gboolean bkup_ret; - char* timestamp; - char* backup; - const char* datafile; - struct stat statbuf; - int rc; - - datafile = be->fullpath; - - rc = g_stat (datafile, &statbuf); - if (rc) - return (errno == ENOENT); - - if (gnc_xml_be_determine_file_type (datafile) == GNC_BOOK_BIN_FILE) - { - /* make a more permanent safer backup */ - const char* back = "-binfmt.bkup"; - char* bin_bkup = g_new (char, strlen (datafile) + strlen (back) + 1); - strcpy (bin_bkup, datafile); - strcat (bin_bkup, back); - bkup_ret = gnc_int_link_or_make_backup (be, datafile, bin_bkup); - g_free (bin_bkup); - if (!bkup_ret) - { - return FALSE; - } - } - - timestamp = gnc_date_timestamp (); - backup = g_strconcat (datafile, ".", timestamp, GNC_DATAFILE_EXT, NULL); - g_free (timestamp); - - bkup_ret = gnc_int_link_or_make_backup (be, datafile, backup); - g_free (backup); - - return bkup_ret; -} - /* ================================================================= */ -static gboolean -gnc_xml_be_write_to_file (FileBackend* fbe, - QofBook* book, - const gchar* datafile, - gboolean make_backup) -{ - QofBackend* be = &fbe->be; - char* tmp_name; - struct stat statbuf; - int rc; - QofBackendError be_err; - - ENTER (" book=%p file=%s", book, datafile); - - if (book && qof_book_is_readonly (book)) - { - /* Are we read-only? Don't continue in this case. */ - qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_READONLY); - LEAVE (""); - return FALSE; - } - - /* If the book is 'clean', recently saved, then don't save again. */ - /* XXX this is currently broken due to faulty 'Save As' logic. */ - /* if (FALSE == qof_book_session_not_saved (book)) return FALSE; */ - - tmp_name = g_new (char, strlen (datafile) + 12); - strcpy (tmp_name, datafile); - strcat (tmp_name, ".tmp-XXXXXX"); - - if (!mktemp (tmp_name)) - { - qof_backend_set_error (be, ERR_BACKEND_MISC); - qof_backend_set_message (be, "Failed to make temp file"); - LEAVE (""); - return FALSE; - } - - if (make_backup) - { - if (!gnc_xml_be_backup_file (fbe)) - { - LEAVE (""); - return FALSE; - } - } - - if (gnc_book_write_to_xml_file_v2 (book, tmp_name, - gnc_prefs_get_file_save_compressed ())) - { - /* Record the file's permissions before g_unlinking it */ - rc = g_stat (datafile, &statbuf); - if (rc == 0) - { - /* We must never chmod the file /dev/null */ - g_assert (g_strcmp0 (tmp_name, "/dev/null") != 0); - - /* Use the permissions from the original data file */ - if (g_chmod (tmp_name, statbuf.st_mode) != 0) - { - /* qof_backend_set_error(be, ERR_BACKEND_PERM); */ - /* qof_backend_set_message( be, "Failed to chmod filename %s", tmp_name ); */ - /* Even if the chmod did fail, the save - nevertheless completed successfully. It is - therefore wrong to signal the ERR_BACKEND_PERM - error here which implies that the saving itself - failed. Instead, we simply ignore this. */ - PWARN ("unable to chmod filename %s: %s", - tmp_name ? tmp_name : "(null)", - g_strerror (errno) ? g_strerror (errno) : ""); -#if VFAT_DOESNT_SUCK /* chmod always fails on vfat/samba fs */ - /* g_free(tmp_name); */ - /* return FALSE; */ -#endif - } -#ifdef HAVE_CHOWN - /* Don't try to change the owner. Only root can do - that. */ - if (chown (tmp_name, -1, statbuf.st_gid) != 0) - { - /* qof_backend_set_error(be, ERR_BACKEND_PERM); */ - /* qof_backend_set_message( be, "Failed to chown filename %s", tmp_name ); */ - /* A failed chown doesn't mean that the saving itself - failed. So don't abort with an error here! */ - PWARN ("unable to chown filename %s: %s", - tmp_name ? tmp_name : "(null)", - strerror (errno) ? strerror (errno) : ""); -#if VFAT_DOESNT_SUCK /* chown always fails on vfat fs */ - /* g_free(tmp_name); - return FALSE; */ -#endif - } -#endif - } - if (g_unlink (datafile) != 0 && errno != ENOENT) - { - qof_backend_set_error (be, ERR_BACKEND_READONLY); - PWARN ("unable to unlink filename %s: %s", - datafile ? datafile : "(null)", - g_strerror (errno) ? g_strerror (errno) : ""); - g_free (tmp_name); - LEAVE (""); - return FALSE; - } - if (!gnc_int_link_or_make_backup (fbe, tmp_name, datafile)) - { - qof_backend_set_error (be, ERR_FILEIO_BACKUP_ERROR); - qof_backend_set_message (be, "Failed to make backup file %s", - datafile ? datafile : "NULL"); - g_free (tmp_name); - LEAVE (""); - return FALSE; - } - if (g_unlink (tmp_name) != 0) - { - qof_backend_set_error (be, ERR_BACKEND_PERM); - PWARN ("unable to unlink temp filename %s: %s", - tmp_name ? tmp_name : "(null)", - g_strerror (errno) ? g_strerror (errno) : ""); - g_free (tmp_name); - LEAVE (""); - return FALSE; - } - g_free (tmp_name); - - /* Since we successfully saved the book, - * we should mark it clean. */ - qof_book_mark_session_saved (book); - LEAVE (" successful save of book=%p to file=%s", book, datafile); - return TRUE; - } - else - { - if (g_unlink (tmp_name) != 0) - { - switch (errno) - { - case ENOENT: /* tmp_name doesn't exist? Assume "RO" error */ - case EACCES: - case EPERM: - case ENOSYS: - case EROFS: - be_err = ERR_BACKEND_READONLY; - break; - default: - be_err = ERR_BACKEND_MISC; - break; - } - qof_backend_set_error (be, be_err); - PWARN ("unable to unlink temp_filename %s: %s", - tmp_name ? tmp_name : "(null)", - g_strerror (errno) ? g_strerror (errno) : ""); - /* already in an error just flow on through */ - } - else - { - /* Use a generic write error code */ - qof_backend_set_error (be, ERR_FILEIO_WRITE_ERROR); - qof_backend_set_message (be, "Unable to write to temp file %s", - tmp_name ? tmp_name : "NULL"); - } - g_free (tmp_name); - LEAVE (""); - return FALSE; - } - LEAVE (""); - return TRUE; -} - -/* ================================================================= */ - -/* - * Clean up any lock files from prior crashes, and clean up old - * backup and log files. - */ - -static void -gnc_xml_be_remove_old_files (FileBackend* be) -{ - const gchar* dent; - GDir* dir; - struct stat lockstatbuf, statbuf; - time64 now; - - if (g_stat (be->lockfile, &lockstatbuf) != 0) - return; - - dir = g_dir_open (be->dirname, 0, NULL); - if (!dir) - return; - - now = gnc_time (NULL); - while ((dent = g_dir_read_name (dir)) != NULL) - { - gchar* name; - - /* Ensure we only evaluate GnuCash related files. */ - if (! (g_str_has_suffix (dent, ".LNK") || - g_str_has_suffix (dent, ".xac") /* old data file extension */ || - g_str_has_suffix (dent, GNC_DATAFILE_EXT) || - g_str_has_suffix (dent, GNC_LOGFILE_EXT))) - continue; - - name = g_build_filename (be->dirname, dent, (gchar*)NULL); - - /* Only evaluate files associated with the current data file. */ - if (!g_str_has_prefix (name, be->fullpath)) - { - g_free (name); - continue; - } - - /* Never remove the current data file itself */ - if (g_strcmp0 (name, be->fullpath) == 0) - { - g_free (name); - continue; - } - - /* Test if the current file is a lock file */ - if (g_str_has_suffix (name, ".LNK")) - { - /* Is a lock file. Skip the active lock file */ - if ((g_strcmp0 (name, be->linkfile) != 0) && - /* Only delete lock files older than the active one */ - (g_stat (name, &statbuf) == 0) && - (statbuf.st_mtime < lockstatbuf.st_mtime)) - { - PINFO ("remove stale lock file: %s", name); - g_unlink (name); - } - - g_free (name); - continue; - } - - /* At this point we're sure the file's name is in one of these forms: - * .gnucash - * .xac - * .log - * - * To be a file generated by GnuCash, the part should consist - * of 1 dot followed by 14 digits (0 to 9). Let's test this with a - * regular expression. - */ - { - /* Find the start of the date stamp. This takes some pointer - * juggling, but considering the above tests, this should always - * be safe */ - regex_t pattern; - gchar* stamp_start = name + strlen (be->fullpath); - gchar* expression = g_strdup_printf ("^\\.[[:digit:]]{14}(\\%s|\\%s|\\.xac)$", - GNC_DATAFILE_EXT, GNC_LOGFILE_EXT); - gboolean got_date_stamp = FALSE; - - if (regcomp (&pattern, expression, REG_EXTENDED | REG_ICASE) != 0) - PWARN ("Cannot compile regex for date stamp"); - else if (regexec (&pattern, stamp_start, 0, NULL, 0) == 0) - got_date_stamp = TRUE; - - regfree (&pattern); - g_free (expression); - - if (!got_date_stamp) /* Not a gnucash created file after all... */ - { - g_free (name); - continue; - } - } - - /* The file is a backup or log file. Check the user's retention preference - * to determine if we should keep it or not - */ - if (gnc_prefs_get_file_retention_policy () == XML_RETAIN_NONE) - { - PINFO ("remove stale file: %s - reason: preference XML_RETAIN_NONE", name); - g_unlink (name); - } - else if ((gnc_prefs_get_file_retention_policy () == XML_RETAIN_DAYS) && - (gnc_prefs_get_file_retention_days () > 0)) - { - int days; - - /* Is the backup file old enough to delete */ - if (g_stat (name, &statbuf) != 0) - { - g_free (name); - continue; - } - days = (int) (difftime (now, statbuf.st_mtime) / 86400); - - PINFO ("file retention = %d days", gnc_prefs_get_file_retention_days ()); - if (days >= gnc_prefs_get_file_retention_days ()) - { - PINFO ("remove stale file: %s - reason: more than %d days old", name, days); - g_unlink (name); - } - } - g_free (name); - } - g_dir_close (dir); -} - -static void -xml_sync_all (QofBackend* be, QofBook* book) -{ - FileBackend* fbe = (FileBackend*) be; - ENTER ("book=%p, fbe->book=%p", book, fbe->book); - - /* We make an important assumption here, that we might want to change - * in the future: when the user says 'save', we really save the one, - * the only, the current open book, and nothing else. In any case the plans - * for multiple books have been removed in the meantime and there is just one - * book, no more. - */ - if (NULL == fbe->book) fbe->book = book; - if (book != fbe->book) return; - - if (qof_book_is_readonly (fbe->book)) - { - /* Are we read-only? Don't continue in this case. */ - qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_READONLY); - return; - } - - gnc_xml_be_write_to_file (fbe, book, fbe->fullpath, TRUE); - gnc_xml_be_remove_old_files (fbe); - LEAVE ("book=%p", book); -} - -/* ================================================================= */ -/* Routines to deal with the creation of multiple books. - * The core design assumption here is that the book - * begin-edit/commit-edit routines are used solely to write out - * closed accounting periods to files. They're not currently - * designed to do anything other than this. (Although they could be). - */ - -static char* -build_period_filepath (FileBackend* fbe, QofBook* book) -{ - int len; - char* str, *p, *q; - - len = strlen (fbe->fullpath) + GUID_ENCODING_LENGTH + 14; - str = g_new (char, len); - strcpy (str, fbe->fullpath); - - /* XXX it would be nice for the user if we made the book - * closing date and/or title part of the file-name. */ - p = strrchr (str, G_DIR_SEPARATOR); - p++; - p = stpcpy (p, "book-"); - p = guid_to_string_buff (qof_book_get_guid (book), p); - p = stpcpy (p, "-"); - q = strrchr (fbe->fullpath, G_DIR_SEPARATOR); - q++; - p = stpcpy (p, q); - p = stpcpy (p, ".gml"); - - return str; -} - -static void -xml_begin_edit (QofBackend* be, QofInstance* inst) -{ - if (0) build_period_filepath (0, 0); -#if BORKEN_FOR_NOW - FileBackend* fbe = (FileBackend*) be; - QofBook* book = gp; - const char* filepath; - - QofIdTypeConst typ = QOF_INSTANCE (inst)->e_type; - if (strcmp (GNC_ID_PERIOD, typ)) return; - filepath = build_period_filepath (fbe, book); - PINFO (" ====================== book=%p filepath=%s\n", book, filepath); - - if (NULL == fbe->primary_book) - { - PERR ("You should have saved the data " - "at least once before closing the books!\n"); - } - /* XXX To be anal about it, we should really be checking to see - * if there already is a file with this book GncGUID, and disallowing - * further progress. This is because we are not allowed to - * modify books that are closed (They should be treated as - * 'read-only'). - */ -#endif -} - -static void -xml_rollback_edit (QofBackend* be, QofInstance* inst) -{ -#if BORKEN_FOR_NOW - QofBook* book = gp; - - if (strcmp (GNC_ID_PERIOD, typ)) return; - PINFO ("book=%p", book); -#endif -} - -/* ---------------------------------------------------------------------- */ - - -/* Load financial data from a file into the book, automatically - detecting the format of the file, if possible. Return FALSE on - error, and set the error parameter to indicate what went wrong if - it's not NULL. This function does not manage file locks in any - way. */ - -static void -gnc_xml_be_load_from_file (QofBackend* bend, QofBook* book, - QofBackendLoadType loadType) -{ - QofBackendError error; - gboolean rc; - FileBackend* be = (FileBackend*) bend; - - if (loadType != LOAD_TYPE_INITIAL_LOAD) return; - - error = ERR_BACKEND_NO_ERR; - be->book = book; - - switch (gnc_xml_be_determine_file_type (be->fullpath)) - { - case GNC_BOOK_XML2_FILE: - rc = qof_session_load_from_xml_file_v2 (be, book, GNC_BOOK_XML2_FILE); - if (FALSE == rc) - { - PWARN ("Syntax error in Xml File %s", be->fullpath); - error = ERR_FILEIO_PARSE_ERROR; - } - break; - - case GNC_BOOK_XML2_FILE_NO_ENCODING: - error = ERR_FILEIO_NO_ENCODING; - PWARN ("No character encoding in Xml File %s", be->fullpath); - break; - case GNC_BOOK_XML1_FILE: - rc = qof_session_load_from_xml_file (book, be->fullpath); - if (FALSE == rc) - { - PWARN ("Syntax error in Xml File %s", be->fullpath); - error = ERR_FILEIO_PARSE_ERROR; - } - break; - case GNC_BOOK_POST_XML2_0_0_FILE: - error = ERR_BACKEND_TOO_NEW; - PWARN ("Version of Xml file %s is newer than what we can read", be->fullpath); - break; - default: - /* If file type wasn't known, check errno again to give the - user some more useful feedback for some particular error - conditions. */ - switch (errno) - { - case EACCES: /* No read permission */ - PWARN ("No read permission to file"); - error = ERR_FILEIO_FILE_EACCES; - break; - case EISDIR: /* File is a directory - but on this error we don't arrive here */ - PWARN ("Filename is a directory"); - error = ERR_FILEIO_FILE_NOT_FOUND; - break; - default: - PWARN ("File not any known type"); - error = ERR_FILEIO_UNKNOWN_FILE_TYPE; - break; - } - break; - } - - if (error != ERR_BACKEND_NO_ERR) - { - qof_backend_set_error (bend, error); - } - - /* We just got done loading, it can't possibly be dirty !! */ - qof_book_mark_session_saved (book); -} - -/* ---------------------------------------------------------------------- */ - -static gboolean -save_may_clobber_data (FileBackend *bend) -{ - struct stat statbuf; - if (!bend->fullpath) return FALSE; - - /* FIXME: Make sure this doesn't need more sophisticated semantics - * in the face of special file, devices, pipes, symlinks, etc. */ - if (g_stat (bend->fullpath, &statbuf) == 0) return TRUE; - return FALSE; -} - - -static void -gnc_xml_be_write_accounts_to_file (QofBackend* be, QofBook* book) -{ - const gchar* datafile; - - datafile = ((FileBackend*)be)->fullpath; - gnc_book_write_accounts_to_xml_file_v2 (be, book, datafile); -} - -/* ================================================================= */ - -QofBackend* -QofXmlBackendProvider::create_backend(void) -{ - FileBackend* gnc_be; - QofBackend* be; - - gnc_be = g_new0 (FileBackend, 1); - be = (QofBackend*) gnc_be; - qof_backend_init (be); - - be->session_begin = xml_session_begin; - be->session_end = xml_session_end; - be->destroy_backend = xml_destroy_backend; - - be->load = gnc_xml_be_load_from_file; - - /* The file backend treats accounting periods transactionally. */ - be->begin = xml_begin_edit; - be->commit = NULL; - be->rollback = xml_rollback_edit; - - be->sync = xml_sync_all; - - be->export_fn = gnc_xml_be_write_accounts_to_file; - - gnc_be->dirname = NULL; - gnc_be->fullpath = NULL; - gnc_be->lockfile = NULL; - gnc_be->linkfile = NULL; - gnc_be->lockfd = -1; - - gnc_be->book = NULL; - - return be; -} - static void business_core_xml_init (void) { diff --git a/src/backend/xml/gnc-backend-xml.h b/src/backend/xml/gnc-backend-xml.h index 2f021cffb9..a9532d0195 100644 --- a/src/backend/xml/gnc-backend-xml.h +++ b/src/backend/xml/gnc-backend-xml.h @@ -36,7 +36,6 @@ extern "C" #endif #include #include -#include typedef enum { @@ -56,24 +55,6 @@ typedef enum GNC_BOOK_POST_XML2_0_0_FILE } QofBookFileType; -struct FileBackend_struct -{ - QofBackend be; - - char* dirname; - char* fullpath; /* Fully qualified path to book */ - char* lockfile; - char* linkfile; - int lockfd; - - QofBook* book; /* The primary, main open book */ -}; - -typedef struct FileBackend_struct FileBackend; - -// This is now a static inside the module -//QofBackend * libgncmod_backend_file_LTX_gnc_backend_new(void); - /** Initialization function which can be used when this module is * statically linked into the application. */ void gnc_module_init_backend_xml (void); @@ -88,5 +69,6 @@ void qof_backend_module_init (void); #endif #ifdef __cplusplus } +#include #endif #endif /* GNC_BACKEND_XML_H_ */ diff --git a/src/backend/xml/gnc-xml-backend.cpp b/src/backend/xml/gnc-xml-backend.cpp new file mode 100644 index 0000000000..e63b31b6d6 --- /dev/null +++ b/src/backend/xml/gnc-xml-backend.cpp @@ -0,0 +1,896 @@ +/******************************************************************** + * gnc-xml-backend.cpp: Implement XML file backend. * + * Copyright 2016 John Ralls * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * + * Boston, MA 02110-1301, USA gnu@gnu.org * +\********************************************************************/ + +extern "C" +{ +#include +#include +#if PLATFORM(WINDOWS) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#include //for GNC_MOD_BACKEND +#include +#include +#include + +} + +#include + +#include "gnc-xml-backend.hpp" +#include "gnc-backend-xml.h" +#include "io-gncxml-v2.h" +#include "io-gncxml.h" + +#define XML_URI_PREFIX "xml://" +#define FILE_URI_PREFIX "file://" +static QofLogModule log_module = GNC_MOD_BACKEND; + +bool +GncXmlBackend::check_path (const char* fullpath, bool create) +{ + struct stat statbuf; + char* dirname = g_path_get_dirname (fullpath); + /* Again check whether the directory can be accessed */ + auto rc = g_stat (dirname, &statbuf); + if (rc != 0 +#if COMPILER(MSVC) + || (statbuf.st_mode & _S_IFDIR) == 0 +#else + || !S_ISDIR (statbuf.st_mode) +#endif + ) + { + /* Error on stat or if it isn't a directory means we + cannot find this filename */ + set_error(ERR_FILEIO_FILE_NOT_FOUND); + std::string msg {"Couldn't find directory for "}; + set_message(msg + fullpath); + PWARN ("Couldn't find directory for %s", fullpath); + g_free(dirname); + return false; + } + + /* Now check whether we can g_stat the file itself */ + rc = g_stat (fullpath, &statbuf); + if ((rc != 0) && (!create)) + { + /* Error on stat means the file doesn't exist */ + set_error(ERR_FILEIO_FILE_NOT_FOUND); + std::string msg {"Couldn't find "}; + set_message(msg + fullpath); + PWARN ("Couldn't find %s", fullpath); + g_free(dirname); + return false; + } + if (rc == 0 +#if COMPILER(MSVC) + && (statbuf.st_mode & _S_IFDIR) != 0 +#else + && S_ISDIR (statbuf.st_mode) +#endif + ) + { + set_error(ERR_FILEIO_UNKNOWN_FILE_TYPE); + std::string msg {"Path "}; + msg += fullpath; + set_message(msg + " is a directory"); + PWARN ("Path %s is a directory", fullpath); + g_free(dirname); + return false; + } + return true; +} + +void +GncXmlBackend::session_begin(QofSession* session, const char* book_id, + bool ignore_lock, bool create, bool force) +{ + /* Make sure the directory is there */ + m_fullpath = gnc_uri_get_path (book_id); + + if (m_fullpath.empty()) + { + set_error(ERR_FILEIO_FILE_NOT_FOUND); + set_message("No path specified"); + return; + } + if (create && !force && save_may_clobber_data()) + { + set_error(ERR_BACKEND_STORE_EXISTS); + PWARN ("Might clobber, no force"); + return; + } + + if (!check_path(m_fullpath.c_str(), create)) + return; + m_dirname = g_path_get_dirname (m_fullpath.c_str()); + + + + /* ---------------------------------------------------- */ + /* We should now have a fully resolved path name. + * Let's start logging */ + xaccLogSetBaseName (m_fullpath.c_str()); + PINFO ("logpath=%s", m_fullpath.empty() ? "(null)" : m_fullpath.c_str()); + + /* And let's see if we can get a lock on it. */ + m_lockfile = m_fullpath + ".LCK"; + + if (!ignore_lock && !get_file_lock()) + { + // We should not ignore the lock, but couldn't get it. The + // be_get_file_lock() already set the appropriate backend_error in this + // case, so we just return here. + m_lockfile.clear(); + + if (force) + { + QofBackendError berror = get_error(); + if (berror == ERR_BACKEND_LOCKED || berror == ERR_BACKEND_READONLY) + { + // Even though we couldn't get the lock, we were told to force + // the opening. This is ok because the FORCE argument is + // changed only if the caller wants a read-only book. + } + else + { + // Unknown error. Push it again on the error stack. + set_error(berror); + } + } + } + +} + +void +GncXmlBackend::session_end() +{ + if (m_book && qof_book_is_readonly (m_book)) + { + set_error(ERR_BACKEND_READONLY); + return; + } + + if (!m_linkfile.empty()) + g_unlink (m_linkfile.c_str()); + + if (m_lockfd > 0) + close (m_lockfd); + + if (!m_lockfile.empty()) + { + int rv; +#ifdef G_OS_WIN32 + /* On windows, we need to allow write-access before + g_unlink() can succeed */ + rv = g_chmod (m_lockfile.c_str(), S_IWRITE | S_IREAD); +#endif + rv = g_unlink (m_lockfile.c_str()); + if (rv) + { + PWARN ("Error on g_unlink(%s): %d: %s", m_lockfile.c_str(), + errno, g_strerror (errno) ? g_strerror (errno) : ""); + } + } + + m_dirname.clear(); + m_fullpath.clear(); + m_lockfile.clear(); + m_linkfile.clear(); +} + +static QofBookFileType +determine_file_type (const std::string& path) +{ + gboolean with_encoding; + QofBookFileType v2type; + + v2type = gnc_is_xml_data_file_v2 (path.c_str(), &with_encoding); + if (v2type == GNC_BOOK_XML2_FILE) + { + if (with_encoding) + { + return GNC_BOOK_XML2_FILE; + } + else + { + return GNC_BOOK_XML2_FILE_NO_ENCODING; + } + } + else if (v2type == GNC_BOOK_POST_XML2_0_0_FILE) + { + return GNC_BOOK_POST_XML2_0_0_FILE; + } + else if (v2type == GNC_BOOK_XML1_FILE) + { + return GNC_BOOK_XML1_FILE; + } + return GNC_BOOK_NOT_OURS; +} + +void +GncXmlBackend::load(QofBook* book, QofBackendLoadType loadType) +{ + + QofBackendError error; + + if (loadType != LOAD_TYPE_INITIAL_LOAD) return; + + error = ERR_BACKEND_NO_ERR; + m_book = book; + + int rc; + switch (determine_file_type (m_fullpath)) + { + case GNC_BOOK_XML2_FILE: + rc = qof_session_load_from_xml_file_v2 (this, book, + GNC_BOOK_XML2_FILE); + if (rc == FALSE) + { + PWARN ("Syntax error in Xml File %s", m_fullpath.c_str()); + error = ERR_FILEIO_PARSE_ERROR; + } + break; + + case GNC_BOOK_XML2_FILE_NO_ENCODING: + error = ERR_FILEIO_NO_ENCODING; + PWARN ("No character encoding in Xml File %s", m_fullpath.c_str()); + break; + case GNC_BOOK_XML1_FILE: + rc = qof_session_load_from_xml_file (book, m_fullpath.c_str()); + if (rc == FALSE) + { + PWARN ("Syntax error in Xml File %s", m_fullpath.c_str()); + error = ERR_FILEIO_PARSE_ERROR; + } + break; + case GNC_BOOK_POST_XML2_0_0_FILE: + error = ERR_BACKEND_TOO_NEW; + PWARN ("Version of Xml file %s is newer than what we can read", + m_fullpath.c_str()); + break; + default: + /* If file type wasn't known, check errno again to give the + user some more useful feedback for some particular error + conditions. */ + switch (errno) + { + case EACCES: /* No read permission */ + PWARN ("No read permission to file"); + error = ERR_FILEIO_FILE_EACCES; + break; + case EISDIR: /* File is a directory - but on this error we don't arrive here */ + PWARN ("Filename is a directory"); + error = ERR_FILEIO_FILE_NOT_FOUND; + break; + default: + PWARN ("File not any known type"); + error = ERR_FILEIO_UNKNOWN_FILE_TYPE; + break; + } + break; + } + + if (error != ERR_BACKEND_NO_ERR) + { + set_error(error); + } + + /* We just got done loading, it can't possibly be dirty !! */ + qof_book_mark_session_saved (book); +} + +void +GncXmlBackend::sync(QofBook* book) +{ + /* We make an important assumption here, that we might want to change + * in the future: when the user says 'save', we really save the one, + * the only, the current open book, and nothing else. In any case the plans + * for multiple books have been removed in the meantime and there is just one + * book, no more. + */ + if (m_book == nullptr) m_book = book; + if (book != m_book) return; + + if (qof_book_is_readonly (m_book)) + { + /* Are we read-only? Don't continue in this case. */ + set_error(ERR_BACKEND_READONLY); + return; + } + + write_to_file (true); + remove_old_files(); +} + +bool +GncXmlBackend::save_may_clobber_data() +{ + if (m_fullpath.empty()) + return false; + struct stat statbuf; + auto rc = g_stat (m_fullpath.c_str(), &statbuf); + return rc != 0; + +} + +void +GncXmlBackend::export_coa(QofBook* book) +{ + auto out = fopen(m_fullpath.c_str(), "w"); + if (out == NULL) + { + set_error(ERR_FILEIO_WRITE_ERROR); + set_message(strerror(errno)); + return; + } + gnc_book_write_accounts_to_xml_filehandle_v2(this, book, out); + fclose(out); +} + +bool +GncXmlBackend::write_to_file (bool make_backup) +{ + QofBackendError be_err; + + ENTER (" book=%p file=%s", m_book, m_fullpath.c_str()); + + if (m_book && qof_book_is_readonly (m_book)) + { + /* Are we read-only? Don't continue in this case. */ + set_error(ERR_BACKEND_READONLY); + LEAVE (""); + return FALSE; + } + + /* If the book is 'clean', recently saved, then don't save again. */ + /* XXX this is currently broken due to faulty 'Save As' logic. */ + /* if (FALSE == qof_book_session_not_saved (book)) return FALSE; */ + + + auto tmp_name = g_new (char, strlen (m_fullpath.c_str()) + 12); + strcpy (tmp_name, m_fullpath.c_str()); + strcat (tmp_name, ".tmp-XXXXXX"); + + if (!mktemp (tmp_name)) + { + set_error(ERR_BACKEND_MISC); + set_message("Failed to make temp file"); + LEAVE (""); + return FALSE; + } + + if (make_backup) + { + if (!backup_file ()) + { + LEAVE (""); + return FALSE; + } + } + + if (gnc_book_write_to_xml_file_v2 (m_book, tmp_name, + gnc_prefs_get_file_save_compressed ())) + { + /* Record the file's permissions before g_unlinking it */ + struct stat statbuf; + auto rc = g_stat (m_fullpath.c_str(), &statbuf); + if (rc == 0) + { + /* We must never chmod the file /dev/null */ + g_assert (g_strcmp0 (tmp_name, "/dev/null") != 0); + + /* Use the permissions from the original data file */ + if (g_chmod (tmp_name, statbuf.st_mode) != 0) + { + /* set_error(ERR_BACKEND_PERM); */ + /* set_message("Failed to chmod filename %s", tmp_name ); */ + /* Even if the chmod did fail, the save + nevertheless completed successfully. It is + therefore wrong to signal the ERR_BACKEND_PERM + error here which implies that the saving itself + failed. Instead, we simply ignore this. */ + PWARN ("unable to chmod filename %s: %s", + tmp_name ? tmp_name : "(null)", + g_strerror (errno) ? g_strerror (errno) : ""); +#if VFAT_DOESNT_SUCK /* chmod always fails on vfat/samba fs */ + /* g_free(tmp_name); */ + /* return FALSE; */ +#endif + } +#ifdef HAVE_CHOWN + /* Don't try to change the owner. Only root can do + that. */ + if (chown (tmp_name, -1, statbuf.st_gid) != 0) + { + /* set_error(ERR_BACKEND_PERM); */ + /* set_message("Failed to chown filename %s", tmp_name ); */ + /* A failed chown doesn't mean that the saving itself + failed. So don't abort with an error here! */ + PWARN ("unable to chown filename %s: %s", + tmp_name ? tmp_name : "(null)", + strerror (errno) ? strerror (errno) : ""); +#if VFAT_DOESNT_SUCK /* chown always fails on vfat fs */ + /* g_free(tmp_name); + return FALSE; */ +#endif + } +#endif + } + if (g_unlink (m_fullpath.c_str()) != 0 && errno != ENOENT) + { + set_error(ERR_BACKEND_READONLY); + PWARN ("unable to unlink filename %s: %s", + m_fullpath.empty() ? "(null)" : m_fullpath.c_str(), + g_strerror (errno) ? g_strerror (errno) : ""); + g_free (tmp_name); + LEAVE (""); + return FALSE; + } + if (!link_or_make_backup (tmp_name, m_fullpath)) + { + set_error(ERR_FILEIO_BACKUP_ERROR); + std::string msg{"Failed to make backup file "}; + set_message(msg + (m_fullpath.empty() ? "NULL" : m_fullpath)); + g_free (tmp_name); + LEAVE (""); + return FALSE; + } + if (g_unlink (tmp_name) != 0) + { + set_error(ERR_BACKEND_PERM); + PWARN ("unable to unlink temp filename %s: %s", + tmp_name ? tmp_name : "(null)", + g_strerror (errno) ? g_strerror (errno) : ""); + g_free (tmp_name); + LEAVE (""); + return FALSE; + } + g_free (tmp_name); + + /* Since we successfully saved the book, + * we should mark it clean. */ + qof_book_mark_session_saved (m_book); + LEAVE (" successful save of book=%p to file=%s", m_book, + m_fullpath.c_str()); + return TRUE; + } + else + { + if (g_unlink (tmp_name) != 0) + { + switch (errno) + { + case ENOENT: /* tmp_name doesn't exist? Assume "RO" error */ + case EACCES: + case EPERM: + case ENOSYS: + case EROFS: + be_err = ERR_BACKEND_READONLY; + break; + default: + be_err = ERR_BACKEND_MISC; + break; + } + set_error(be_err); + PWARN ("unable to unlink temp_filename %s: %s", + tmp_name ? tmp_name : "(null)", + g_strerror (errno) ? g_strerror (errno) : ""); + /* already in an error just flow on through */ + } + else + { + /* Use a generic write error code */ + set_error(ERR_FILEIO_WRITE_ERROR); + std::string msg{"Unable to write to temp file "}; + set_message(msg + (tmp_name ? tmp_name : "NULL")); + } + g_free (tmp_name); + LEAVE (""); + return FALSE; + } + LEAVE (""); + return TRUE; +} + +static bool +copy_file (const std::string& orig, const std::string& bkup) +{ + constexpr size_t buf_size = 1024; + char buf[buf_size]; + int flags = 0; + ssize_t count_write = 0; + ssize_t count_read = 0; + + +#ifdef G_OS_WIN32 + flags = O_BINARY; +#endif + + auto orig_fd = g_open (orig.c_str(), O_RDONLY | flags, 0); + if (orig_fd == -1) + { + return false; + } + auto bkup_fd = g_open (bkup.c_str(), + O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | flags, 0600); + if (bkup_fd == -1) + { + close (orig_fd); + return FALSE; + } + + do + { + count_read = read (orig_fd, buf, buf_size); + if (count_read == -1 && errno != EINTR) + { + close (orig_fd); + close (bkup_fd); + return FALSE; + } + + if (count_read > 0) + { + count_write = write (bkup_fd, buf, count_read); + if (count_write == -1) + { + close (orig_fd); + close (bkup_fd); + return FALSE; + } + } + } + while (count_read > 0); + + close (orig_fd); + close (bkup_fd); + + return TRUE; +} + +bool +GncXmlBackend::link_or_make_backup (const std::string& orig, + const std::string& bkup) +{ + gboolean copy_success = FALSE; + int err_ret = +#ifdef HAVE_LINK + link (orig.c_str(), bkup.c_str()) +#else + - 1 +#endif + ; + if (err_ret != 0) + { +#ifdef HAVE_LINK + if (errno == EPERM || errno == ENOSYS +# ifdef EOPNOTSUPP + || errno == EOPNOTSUPP +# endif +# ifdef ENOTSUP + || errno == ENOTSUP +# endif +# ifdef ENOSYS + || errno == ENOSYS +# endif + ) +#endif + { + copy_success = copy_file (orig.c_str(), bkup); + } + + if (!copy_success) + { + set_error(ERR_FILEIO_BACKUP_ERROR); + PWARN ("unable to make file backup from %s to %s: %s", + orig.c_str(), bkup.c_str(), g_strerror (errno) ? g_strerror (errno) : ""); + return false; + } + } + + return true; +} + +bool +GncXmlBackend::get_file_lock () +{ + struct stat statbuf; +#ifndef G_OS_WIN32 + char* pathbuf = NULL, *tmpbuf = NULL; + size_t pathbuf_size = 0; +#endif + QofBackendError be_err; + + auto rc = g_stat (m_lockfile.c_str(), &statbuf); + if (!rc) + { + /* oops .. file is locked by another user .. */ + set_error(ERR_BACKEND_LOCKED); + return false; + } + + m_lockfd = g_open (m_lockfile.c_str(), O_RDWR | O_CREAT | O_EXCL , + S_IRUSR | S_IWUSR); + if (m_lockfd < 0) + { + /* oops .. we can't create the lockfile .. */ + switch (errno) + { + case EACCES: + case EROFS: + case ENOSPC: + PWARN ("Unable to create the lockfile %s; may not have write priv", + m_lockfile.c_str()); + be_err = ERR_BACKEND_READONLY; + break; + default: + be_err = ERR_BACKEND_LOCKED; + break; + } + set_error(be_err); + return false; + } + + /* OK, now work around some NFS atomic lock race condition + * mumbo-jumbo. We do this by linking a unique file, and + * then examining the link count. At least that's what the + * NFS programmers guide suggests. + * Note: the "unique filename" must be unique for the + * triplet filename-host-process, otherwise accidental + * aliases can occur. + */ + + /* apparently, even this code may not work for some NFS + * implementations. In the long run, I am told that + * ftp.debian.org + * /pub/debian/dists/unstable/main/source/libs/liblockfile_0.1-6.tar.gz + * provides a better long-term solution. + */ + +#ifndef G_OS_WIN32 + auto path = m_lockfile.find_last_of('.'); + std::stringstream linkfile; + if (path != std::string::npos) + linkfile << m_lockfile.substr(0, path); + else + linkfile << m_lockfile; + linkfile << "." << gethostid() << "." << getpid() << ".LNK"; + rc = link (m_lockfile.c_str(), linkfile.str().c_str()); + if (rc) + { + /* If hard links aren't supported, just allow the lock. */ + if (errno == EPERM || errno == ENOSYS +# ifdef EOPNOTSUPP + || errno == EOPNOTSUPP +# endif +# ifdef ENOTSUP + || errno == ENOTSUP +# endif + ) + { + return true; + } + + /* Otherwise, something else is wrong. */ + set_error(ERR_BACKEND_LOCKED); + g_unlink (linkfile.str().c_str()); + close (m_lockfd); + g_unlink (m_lockfile.c_str()); + return false; + } + + rc = g_stat (m_lockfile.c_str(), &statbuf); + if (rc) + { + /* oops .. stat failed! This can't happen! */ + set_error(ERR_BACKEND_LOCKED); + std::string msg{"Failed to stat lockfile "}; + set_message(msg + m_lockfile); + g_unlink (linkfile.str().c_str()); + close (m_lockfd); + g_unlink (m_lockfile.c_str()); + return false; + } + + if (statbuf.st_nlink != 2) + { + set_error(ERR_BACKEND_LOCKED); + g_unlink (linkfile.str().c_str()); + close (m_lockfd); + g_unlink (m_lockfile.c_str()); + return false; + } + + m_linkfile = linkfile.str(); + return true; + +#else /* ifndef G_OS_WIN32 */ + /* On windows, there is no NFS and the open(,O_CREAT | O_EXCL) + is sufficient for locking. */ + return true; +#endif /* ifndef G_OS_WIN32 */ +} + +bool +GncXmlBackend::backup_file() +{ + struct stat statbuf; + + auto datafile = m_fullpath.c_str(); + + auto rc = g_stat (datafile, &statbuf); + if (rc) + return (errno == ENOENT); + + if (determine_file_type (m_fullpath) == GNC_BOOK_BIN_FILE) + { + /* make a more permanent safer backup */ + auto bin_bkup = m_fullpath + "-binfmt.bkup"; + auto bkup_ret = link_or_make_backup (m_fullpath, bin_bkup); + if (!bkup_ret) + { + return false; + } + } + + auto timestamp = gnc_date_timestamp (); + auto backup = m_fullpath + "." + timestamp + GNC_DATAFILE_EXT; + g_free (timestamp); + + return link_or_make_backup (datafile, backup); +} + +/* + * Clean up any lock files from prior crashes, and clean up old + * backup and log files. + */ + +void +GncXmlBackend::remove_old_files () +{ + struct stat lockstatbuf, statbuf; + + if (g_stat (m_lockfile.c_str(), &lockstatbuf) != 0) + return; + + auto dir = g_dir_open (m_dirname.c_str(), 0, NULL); + if (!dir) + return; + + auto now = gnc_time (NULL); + const char* dent; + while ((dent = g_dir_read_name (dir)) != NULL) + { + gchar* name; + + /* Ensure we only evaluate GnuCash related files. */ + if (! (g_str_has_suffix (dent, ".LNK") || + g_str_has_suffix (dent, ".xac") /* old data file extension */ || + g_str_has_suffix (dent, GNC_DATAFILE_EXT) || + g_str_has_suffix (dent, GNC_LOGFILE_EXT))) + continue; + + name = g_build_filename (m_dirname.c_str(), dent, (gchar*)NULL); + + /* Only evaluate files associated with the current data file. */ + if (!g_str_has_prefix (name, m_fullpath.c_str())) + { + g_free (name); + continue; + } + + /* Never remove the current data file itself */ + if (g_strcmp0 (name, m_fullpath.c_str()) == 0) + { + g_free (name); + continue; + } + + /* Test if the current file is a lock file */ + if (g_str_has_suffix (name, ".LNK")) + { + /* Is a lock file. Skip the active lock file */ + if ((g_strcmp0 (name, m_linkfile.c_str()) != 0) && + /* Only delete lock files older than the active one */ + (g_stat (name, &statbuf) == 0) && + (statbuf.st_mtime < lockstatbuf.st_mtime)) + { + PINFO ("remove stale lock file: %s", name); + g_unlink (name); + } + + g_free (name); + continue; + } + + /* At this point we're sure the file's name is in one of these forms: + * .gnucash + * .xac + * .log + * + * To be a file generated by GnuCash, the part should consist + * of 1 dot followed by 14 digits (0 to 9). Let's test this with a + * regular expression. + */ + { + /* Find the start of the date stamp. This takes some pointer + * juggling, but considering the above tests, this should always + * be safe */ + regex_t pattern; + gchar* stamp_start = name + strlen (m_fullpath.c_str()); + gchar* expression = g_strdup_printf ("^\\.[[:digit:]]{14}(\\%s|\\%s|\\.xac)$", + GNC_DATAFILE_EXT, GNC_LOGFILE_EXT); + gboolean got_date_stamp = FALSE; + + if (regcomp (&pattern, expression, REG_EXTENDED | REG_ICASE) != 0) + PWARN ("Cannot compile regex for date stamp"); + else if (regexec (&pattern, stamp_start, 0, NULL, 0) == 0) + got_date_stamp = TRUE; + + regfree (&pattern); + g_free (expression); + + if (!got_date_stamp) /* Not a gnucash created file after all... */ + { + g_free (name); + continue; + } + } + + /* The file is a backup or log file. Check the user's retention preference + * to determine if we should keep it or not + */ + if (gnc_prefs_get_file_retention_policy () == XML_RETAIN_NONE) + { + PINFO ("remove stale file: %s - reason: preference XML_RETAIN_NONE", name); + g_unlink (name); + } + else if ((gnc_prefs_get_file_retention_policy () == XML_RETAIN_DAYS) && + (gnc_prefs_get_file_retention_days () > 0)) + { + int days; + + /* Is the backup file old enough to delete */ + if (g_stat (name, &statbuf) != 0) + { + g_free (name); + continue; + } + days = (int) (difftime (now, statbuf.st_mtime) / 86400); + + PINFO ("file retention = %d days", gnc_prefs_get_file_retention_days ()); + if (days >= gnc_prefs_get_file_retention_days ()) + { + PINFO ("remove stale file: %s - reason: more than %d days old", name, days); + g_unlink (name); + } + } + g_free (name); + } + g_dir_close (dir); +} diff --git a/src/backend/xml/gnc-xml-backend.hpp b/src/backend/xml/gnc-xml-backend.hpp new file mode 100644 index 0000000000..2710115829 --- /dev/null +++ b/src/backend/xml/gnc-xml-backend.hpp @@ -0,0 +1,66 @@ +/******************************************************************** + * gnc-xml-backend.hpp: Declare XML file backend. * + * Copyright 2016 John Ralls * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * + * Boston, MA 02110-1301, USA gnu@gnu.org * +\********************************************************************/ + +#ifndef __GNC_XML_BACKEND_HPP__ +#define __GNC_XML_BACKEND_HPP__ + +extern "C" +{ +#include +} + +#include +#include + +class GncXmlBackend : public QofBackend +{ +public: + GncXmlBackend() = default; + GncXmlBackend(const GncXmlBackend&) = delete; + GncXmlBackend operator=(const GncXmlBackend&) = delete; + GncXmlBackend(const GncXmlBackend&&) = delete; + GncXmlBackend operator=(const GncXmlBackend&&) = delete; + ~GncXmlBackend() = default; + void session_begin(QofSession* session, const char* book_id, + bool ignore_lock, bool create, bool force) override; + void session_end() override; + void load(QofBook* book, QofBackendLoadType loadType) override; + /* The XML backend isn't able to do anything with individual instances. */ + void export_coa(QofBook*) override; + void sync(QofBook* book) override; + void safe_sync(QofBook* book) override { sync(book); } // XML sync is inherently safe. + const char * get_filename() { return m_fullpath.c_str(); } + QofBook* get_book() { return m_book; } + +private: + bool save_may_clobber_data(); + bool get_file_lock(); + bool link_or_make_backup(const std::string& orig, const std::string& bkup); + bool backup_file(); + bool write_to_file(bool make_backup); + void remove_old_files(); + void write_accounts(QofBook* book); + bool check_path(const char* fullpath, bool create); + + std::string m_dirname; + std::string m_lockfile; + std::string m_linkfile; + int m_lockfd; + + QofBook* m_book; /* The primary, main open book */ +}; +#endif // __GNC_XML_BACKEND_HPP__ diff --git a/src/backend/xml/io-gncxml-v2.cpp b/src/backend/xml/io-gncxml-v2.cpp index 614c4fd5c9..c097af893c 100644 --- a/src/backend/xml/io-gncxml-v2.cpp +++ b/src/backend/xml/io-gncxml-v2.cpp @@ -66,6 +66,7 @@ extern "C" #endif } +#include "gnc-xml-backend.hpp" #include "sixtp-parsers.h" #include "sixtp-utils.h" #include "gnc-xml.h" @@ -692,12 +693,11 @@ gnc_sixtp_gdv2_new ( static gboolean qof_session_load_from_xml_file_v2_full ( - FileBackend* fbe, QofBook* book, + GncXmlBackend* xml_be, QofBook* book, sixtp_push_handler push_handler, gpointer push_user_data, QofBookFileType type) { Account* root; - QofBackend* be = &fbe->be; sixtp_gdv2* gd; sixtp* top_parser; sixtp* main_parser; @@ -706,7 +706,8 @@ qof_session_load_from_xml_file_v2_full ( gboolean retval; char* v2type = NULL; - gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, be->percentage); + gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, + xml_be->get_percentage()); top_parser = sixtp_new (); main_parser = sixtp_new (); @@ -793,7 +794,7 @@ qof_session_load_from_xml_file_v2_full ( * https://bugzilla.gnome.org/show_bug.cgi?id=712528 for more * info. */ - gchar* filename = fbe->fullpath; + const char* filename = xml_be->get_filename(); FILE* file; gboolean is_compressed = is_gzipped_file (filename); file = try_gz_open (filename, "r", is_compressed, FALSE); @@ -864,10 +865,10 @@ bail: } gboolean -qof_session_load_from_xml_file_v2 (FileBackend* fbe, QofBook* book, +qof_session_load_from_xml_file_v2 (GncXmlBackend* xml_be, QofBook* book, QofBookFileType type) { - return qof_session_load_from_xml_file_v2_full (fbe, book, NULL, NULL, type); + return qof_session_load_from_xml_file_v2_full (xml_be, book, NULL, NULL, type); } /***********************************************************************/ @@ -1257,21 +1258,21 @@ static void write_budget (QofInstance* ent, gpointer data) { xmlNodePtr node; - struct file_backend* be = static_cast (data); + struct file_backend* file_be = static_cast (data); GncBudget* bgt = GNC_BUDGET (ent); - if (ferror (be->out)) + if (ferror (file_be->out)) return; node = gnc_budget_dom_tree_create (bgt); - xmlElemDump (be->out, NULL, node); + xmlElemDump (file_be->out, NULL, node); xmlFreeNode (node); - if (ferror (be->out) || fprintf (be->out, "\n") < 0) + if (ferror (file_be->out) || fprintf (file_be->out, "\n") < 0) return; - be->gd->counter.budgets_loaded++; - sixtp_run_callback (be->gd, "budgets"); + file_be->gd->counter.budgets_loaded++; + sixtp_run_callback (file_be->gd, "budgets"); } gboolean @@ -1328,7 +1329,7 @@ write_v2_header (FILE* out) gboolean gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out) { - QofBackend* be; + QofBackend* qof_be; sixtp_gdv2* gd; gboolean success = TRUE; @@ -1338,8 +1339,9 @@ gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out) || !write_counts (out, "book", 1, NULL)) return FALSE; - be = qof_book_get_backend (book); - gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, be->percentage); + qof_be = qof_book_get_backend (book); + gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, + qof_be->get_percentage()); gd->counter.commodities_total = gnc_commodity_table_get_size (gnc_commodity_table_get_table (book)); gd->counter.accounts_total = 1 + @@ -1364,7 +1366,7 @@ gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out) * This function is called by the "export" code. */ gboolean -gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* be, QofBook* book, +gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* qof_be, QofBook* book, FILE* out) { gnc_commodity_table* table; @@ -1385,7 +1387,8 @@ gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* be, QofBook* book, || !write_counts (out, "commodity", ncom, "account", nacc, NULL)) return FALSE; - gd = gnc_sixtp_gdv2_new (book, TRUE, file_rw_feedback, be->percentage); + gd = gnc_sixtp_gdv2_new (book, TRUE, file_rw_feedback, + qof_be->get_percentage()); gd->counter.commodities_total = ncom; gd->counter.accounts_total = nacc; @@ -1650,10 +1653,8 @@ gnc_book_write_to_xml_file_v2 ( * postgress or anything else. */ gboolean -gnc_book_write_accounts_to_xml_file_v2 ( - QofBackend* be, - QofBook* book, - const char* filename) +gnc_book_write_accounts_to_xml_file_v2 (QofBackend* qof_be, QofBook* book, + const char* filename) { FILE* out; gboolean success = TRUE; @@ -1662,7 +1663,7 @@ gnc_book_write_accounts_to_xml_file_v2 ( /* Try to write as much as possible */ if (!out - || !gnc_book_write_accounts_to_xml_filehandle_v2 (be, book, out) + || !gnc_book_write_accounts_to_xml_filehandle_v2 (qof_be, book, out) || !write_emacs_trailer (out)) success = FALSE; @@ -1670,11 +1671,11 @@ gnc_book_write_accounts_to_xml_file_v2 ( if (out && fclose (out)) success = FALSE; - if (!success && !qof_backend_check_error (be)) + if (!success && !qof_be->check_error()) { /* Use a generic write error code */ - qof_backend_set_error (be, ERR_FILEIO_WRITE_ERROR); + qof_backend_set_error (qof_be, ERR_FILEIO_WRITE_ERROR); } return success; @@ -2031,7 +2032,7 @@ cleanup_find_ambs: typedef struct { - gchar* filename; + const char* filename; GHashTable* subst; } push_data_type; @@ -2168,17 +2169,17 @@ cleanup_push_handler: } gboolean -gnc_xml2_parse_with_subst (FileBackend* fbe, QofBook* book, GHashTable* subst) +gnc_xml2_parse_with_subst (GncXmlBackend* xml_be, QofBook* book, GHashTable* subst) { push_data_type* push_data; gboolean success; push_data = g_new (push_data_type, 1); - push_data->filename = fbe->fullpath; + push_data->filename = xml_be->get_filename(); push_data->subst = subst; success = qof_session_load_from_xml_file_v2_full ( - fbe, book, (sixtp_push_handler) parse_with_subst_push_handler, + xml_be, book, (sixtp_push_handler) parse_with_subst_push_handler, push_data, GNC_BOOK_XML2_FILE); if (success) diff --git a/src/backend/xml/io-gncxml-v2.h b/src/backend/xml/io-gncxml-v2.h index 0457c8c857..ccfb4b3a0b 100644 --- a/src/backend/xml/io-gncxml-v2.h +++ b/src/backend/xml/io-gncxml-v2.h @@ -43,6 +43,7 @@ extern "C" #include "sixtp.h" #include +class GncXmlBackend; /** * Struct used to pass in a new data type for XML storage. This contains @@ -84,7 +85,7 @@ typedef struct } gnc_template_xaction_data; /** read in an account group from a file */ -gboolean qof_session_load_from_xml_file_v2 (FileBackend*, QofBook*, +gboolean qof_session_load_from_xml_file_v2 (GncXmlBackend*, QofBook*, QofBookFileType); /* write all book info to a file */ @@ -154,20 +155,20 @@ gint gnc_xml2_find_ambiguous ( * @param subst hash table with keys and values of type gchar* */ gboolean gnc_xml2_parse_with_subst ( - FileBackend* fbe, QofBook* book, GHashTable* subst); + GncXmlBackend* xml_be, QofBook* book, GHashTable* subst); #ifdef __cplusplus } typedef struct { - int version; /* backend version number */ - const char * type_name; /* The XML tag for this type */ + int version; /* backend version number */ + const char * type_name; /* The XML tag for this type */ - sixtp * (*create_parser) (void); - gboolean (*add_item)(sixtp_gdv2 *, gpointer obj); - int (*get_count) (QofBook *); - gboolean (*write) (FILE*, QofBook*); - void (*scrub) (QofBook *); - gboolean (*ns) (FILE*); + sixtp * (*create_parser) (void); + gboolean (*add_item)(sixtp_gdv2 *, gpointer obj); + int (*get_count) (QofBook *); + gboolean (*write) (FILE*, QofBook*); + void (*scrub) (QofBook *); + gboolean (*ns) (FILE*); } GncXmlDataType_t; void gnc_xml_register_backend(GncXmlDataType_t&); diff --git a/src/backend/xml/test/test-dom-converters1.cpp b/src/backend/xml/test/test-dom-converters1.cpp index c577f9a693..5c1d44b000 100644 --- a/src/backend/xml/test/test-dom-converters1.cpp +++ b/src/backend/xml/test/test-dom-converters1.cpp @@ -32,12 +32,12 @@ extern "C" #include "test-stuff.h" #include "test-engine-stuff.h" -#include "test-file-stuff.h" #include "cashobjects.h" #include "gnc-engine.h" #include "gnc-commodity.h" } +#include "test-file-stuff.h" #include "gnc-xml-helper.h" #include "sixtp.h" #include "sixtp-parsers.h" diff --git a/src/backend/xml/test/test-save-in-lang.cpp b/src/backend/xml/test/test-save-in-lang.cpp index 04214d0573..c9af56e95f 100644 --- a/src/backend/xml/test/test-save-in-lang.cpp +++ b/src/backend/xml/test/test-save-in-lang.cpp @@ -32,11 +32,12 @@ extern "C" #include "test-stuff.h" #include "test-engine-stuff.h" -#include "test-file-stuff.h" #include "gnc-engine.h" #include "TransLog.h" } + +#include "test-file-stuff.h" #include "io-gncxml-v2.h" const char* possible_envs[] = diff --git a/src/backend/xml/test/test-string-converters.cpp b/src/backend/xml/test/test-string-converters.cpp index 8cbf726cce..66b7572eee 100644 --- a/src/backend/xml/test/test-string-converters.cpp +++ b/src/backend/xml/test/test-string-converters.cpp @@ -26,9 +26,9 @@ extern "C" #include "test-stuff.h" #include "test-engine-stuff.h" -#include "test-file-stuff.h" } +#include "test-file-stuff.h" #include "sixtp-dom-parsers.h" #include "sixtp-dom-generators.h" diff --git a/src/backend/xml/test/test-xml-transaction.cpp b/src/backend/xml/test/test-xml-transaction.cpp index 9247fdabd3..bfe87f7bff 100644 --- a/src/backend/xml/test/test-xml-transaction.cpp +++ b/src/backend/xml/test/test-xml-transaction.cpp @@ -52,7 +52,7 @@ extern "C" #include "../sixtp-parsers.h" #include "../sixtp-dom-parsers.h" #include "../io-gncxml-gen.h" -#include +#include "test-file-stuff.h" static QofBook* book; diff --git a/src/engine/test/utest-Transaction.cpp b/src/engine/test/utest-Transaction.cpp index dcdac4d595..5982ba81f0 100644 --- a/src/engine/test/utest-Transaction.cpp +++ b/src/engine/test/utest-Transaction.cpp @@ -35,7 +35,6 @@ extern "C" #include "../gnc-lot.h" #include "../gnc-event.h" #include -#include #ifdef HAVE_GLIB_2_38 #define _Q "'" @@ -50,6 +49,7 @@ static const gchar *suitename = "/engine/Transaction"; void test_suite_transaction ( void ); } +#include #include /* Copied from Transaction.c. Changing these values will break @@ -86,47 +86,49 @@ typedef struct Account *gains_acc; } GainsFixture; -typedef struct +class MockBackend : public QofBackend { - QofBackend be; - gchar last_call[12]; - QofBackendError result_err; -} MockBackend; - -static void -mock_backend_set_error (MockBackend *mbe, QofBackendError err) -{ - mbe->result_err = err; -} - -static void -mock_backend_rollback (QofBackend *be, QofInstance *foo) -{ - MockBackend *mbe = (MockBackend *)be; - g_strlcpy (mbe->last_call, "rollback", sizeof (mbe->last_call)); - mbe->be.last_err = mbe->result_err; -} - -static MockBackend* -mock_backend_new (void) -{ - MockBackend *mbe = g_new0 (MockBackend, 1); - mbe->be.rollback = mock_backend_rollback; - memset (mbe->last_call, 0, sizeof (mbe->last_call)); - return mbe; -} +public: + MockBackend() : QofBackend(), m_last_call{"Constructor"}, + m_result_err{ERR_BACKEND_NO_ERR} {} + void session_begin(QofSession*, const char*, bool, bool, bool) override { + m_last_call = "session_begin"; + } + void session_end() override { + m_last_call = "session_end"; + } + void load(QofBook*, QofBackendLoadType) override { + m_last_call = "load"; + } + void sync(QofBook*) override { + m_last_call = "sync"; + } + void safe_sync(QofBook*) override { + m_last_call = "safe_sync"; + } + void rollback(QofInstance*) override { + set_error(m_result_err); + m_last_call = "rollback"; + } + void inject_error(QofBackendError err) { + m_result_err = err; + } + std::string m_last_call; +private: + QofBackendError m_result_err; +}; static void setup (Fixture *fixture, gconstpointer pData) { QofBook *book = qof_book_new (); - MockBackend *mbe = mock_backend_new (); + MockBackend *mbe = new MockBackend; Transaction *txn; Timespec entered = gnc_dmy2timespec (20, 4, 2012); Timespec posted = gnc_dmy2timespec (21, 4, 2012); auto frame = new KvpFrame (); - qof_book_set_backend (book, (QofBackend*)mbe); + qof_book_set_backend (book, mbe); auto split1 = xaccMallocSplit (book); auto split2 = xaccMallocSplit (book); txn = xaccMallocTransaction (book); @@ -211,14 +213,14 @@ static void teardown (Fixture *fixture, gconstpointer pData) { QofBook *book = qof_instance_get_book (QOF_INSTANCE (fixture->txn)); - MockBackend *mbe = (MockBackend *)qof_book_get_backend (book); + auto mbe = static_cast(qof_book_get_backend (book)); test_destroy (fixture->txn); test_destroy (fixture->acc1); test_destroy (fixture->acc2); test_destroy (fixture->curr); test_destroy (fixture->comm); - g_free (mbe); + delete mbe; qof_book_destroy(book); g_slist_free_full (fixture->hdlrs, test_free_log_handler); test_clear_error_list(); @@ -1695,7 +1697,7 @@ test_xaccTransRollbackEdit (Fixture *fixture, gconstpointer pData) KvpFrame *base_frame = NULL; auto sig_account = test_signal_new (QOF_INSTANCE (fixture->acc1), GNC_EVENT_ITEM_CHANGED, NULL); - MockBackend *mbe = (MockBackend*)qof_book_get_backend (book); + auto mbe = static_cast(qof_book_get_backend (book)); auto split_00 = static_cast(txn->splits->data); auto split_01 = static_cast(txn->splits->next->data); auto split_02 = xaccMallocSplit (book); @@ -1743,7 +1745,7 @@ test_xaccTransRollbackEdit (Fixture *fixture, gconstpointer pData) FALSE, FALSE, FALSE)); g_assert (xaccSplitEqual (static_cast(txn->splits->next->data), split_10, FALSE, FALSE, FALSE)); - g_assert_cmpstr (mbe->last_call, ==, "rollback"); + g_assert_cmpstr (mbe->m_last_call.c_str(), ==, "rollback"); g_assert_cmpuint (qof_instance_get_editlevel (QOF_INSTANCE (txn)), ==, 0); g_assert (qof_instance_get_destroying (txn) == FALSE); test_signal_free (sig_account); @@ -1757,7 +1759,7 @@ test_xaccTransRollbackEdit (Fixture *fixture, gconstpointer pData) static void test_xaccTransRollbackEdit_BackendErrors (Fixture *fixture, gconstpointer pData) { - MockBackend *mbe = (MockBackend*)qof_book_get_backend (qof_instance_get_book (fixture->txn)); + auto mbe = static_cast(qof_book_get_backend (qof_instance_get_book (fixture->txn))); auto loglevel = static_cast(G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL); auto msg = "[xaccTransRollbackEdit()] Rollback Failed. Ouch!"; auto check = test_error_struct_new ("gnc.engine", loglevel, msg); @@ -1765,16 +1767,16 @@ test_xaccTransRollbackEdit_BackendErrors (Fixture *fixture, gconstpointer pData) (GLogFunc)test_checked_handler); g_object_ref (fixture->txn); xaccTransBeginEdit (fixture->txn); - mock_backend_set_error (mbe, ERR_BACKEND_MODIFIED); + mbe->inject_error(ERR_BACKEND_MODIFIED); xaccTransRollbackEdit (fixture->txn); g_assert_cmpint (check->hits, ==, 1); - g_assert_cmpstr (mbe->last_call, ==, "rollback"); - memset (mbe->last_call, 0, sizeof (mbe->last_call)); + g_assert_cmpstr (mbe->m_last_call.c_str(), ==, "rollback"); + mbe->m_last_call.clear(); xaccTransBeginEdit (fixture->txn); - mock_backend_set_error (mbe, ERR_BACKEND_MOD_DESTROY); + mbe->inject_error (ERR_BACKEND_MOD_DESTROY); xaccTransRollbackEdit (fixture->txn); g_assert_cmpint (GPOINTER_TO_INT(fixture->txn->num), ==, 1); - g_assert_cmpstr (mbe->last_call, ==, "rollback"); + g_assert_cmpstr (mbe->m_last_call.c_str(), ==, "rollback"); } /* xaccTransIsOpen C: 23 in 7 SCM: 1 Local: 0:0:0 @@ -1941,7 +1943,7 @@ test_xaccTransReverse (Fixture *fixture, gconstpointer pData) g_assert (guid_equal (frame->get_slot(TRANS_REVERSED_BY)->get(), xaccTransGetGUID (rev))); - g_assert (qof_instance_is_dirty (QOF_INSTANCE (rev))); + g_assert (!qof_instance_is_dirty (QOF_INSTANCE (rev))); //Cleared by commit g_assert_cmpint (g_list_length (fixture->txn->splits), ==, g_list_length (rev->splits)); for (orig_splits = fixture->txn->splits, diff --git a/src/gnome-utils/assistant-xml-encoding.c b/src/gnome-utils/assistant-xml-encoding.c index 5f20b105c7..adf7ee73b3 100644 --- a/src/gnome-utils/assistant-xml-encoding.c +++ b/src/gnome-utils/assistant-xml-encoding.c @@ -35,8 +35,27 @@ #include "gnc-uri-utils.h" #include "gnc-module.h" #include "gnc-ui.h" -#include "io-gncxml-v2.h" +/* The following are copied from src/backend/xml/io-gncxml2-v2.h as a temporary + * measure to enable this to compile in the face of making changing struct + * FileBackend into C++ class XmlBackend, which can't be exposed to this C + * file. A future commit will separate the session code from the UI code in this + * file. + */ +typedef struct +{ + GQuark encoding; + gchar* utf8_string; +} conv_type; + +extern gint gnc_xml2_find_ambiguous (const gchar* filename, + GList* encodings, + GHashTable** unique, + GHashTable** ambiguous, + GList** impossible); + +extern gboolean gnc_xml2_parse_with_subst (QofBackend* xml_be, QofBook* book, + GHashTable* subst); /* NOTE: This file uses the term "encoding" even in places where it is not * accurate. Please ignore that. Encodings occur in different forms: * - as descriptive string, as in the list of system encodings @@ -1041,7 +1060,7 @@ gxi_parse_file (GncXmlImportData *data) { QofSession *session = NULL; QofBook *book; - FileBackend *backend; + QofBackend *backend; QofBackendError io_err = ERR_BACKEND_NO_ERR; gchar *message = NULL; gboolean success = FALSE; @@ -1091,7 +1110,7 @@ gxi_parse_file (GncXmlImportData *data) qof_session_pop_error (session); book = qof_session_get_book (session); - backend = (FileBackend*) qof_book_get_backend (book); + backend = qof_book_get_backend (book); gxi_update_progress_bar (_("Parsing file..."), 0.0); success = gnc_xml2_parse_with_subst (backend, book, data->subst); diff --git a/src/libqof/CMakeLists.txt b/src/libqof/CMakeLists.txt index 90aa60eb0d..05a03da654 100644 --- a/src/libqof/CMakeLists.txt +++ b/src/libqof/CMakeLists.txt @@ -14,7 +14,7 @@ SET (gnc_qof_HEADERS qof/kvp_frame.hpp qof/kvp-value.hpp qof/qof.h - qof/qofbackend-p.h + qof/qof-backend.hpp qof/qofbackend.h qof/qofbook.h qof/qofbookslots.h @@ -59,7 +59,7 @@ SET (gnc_qof_SOURCES qof/guid.cpp qof/kvp_frame.cpp qof/kvp-value.cpp - qof/qofbackend.cpp + qof/qof-backend.cpp qof/qofbook.cpp qof/qofchoice.cpp qof/qofclass.cpp diff --git a/src/libqof/qof/Makefile.am b/src/libqof/qof/Makefile.am index 5e831596fc..c2fec7f35d 100644 --- a/src/libqof/qof/Makefile.am +++ b/src/libqof/qof/Makefile.am @@ -32,7 +32,7 @@ libgnc_qof_la_SOURCES = \ guid.cpp \ kvp_frame.cpp \ kvp-value.cpp \ - qofbackend.cpp \ + qof-backend.cpp \ qofbook.cpp \ qofchoice.cpp \ qofclass.cpp \ @@ -61,7 +61,7 @@ qofinclude_HEADERS = \ kvp_frame.hpp \ kvp-value.hpp \ qof.h \ - qofbackend-p.h \ + qof-backend.hpp \ qofbackend.h \ qofbook.h \ qofbookslots.h \ diff --git a/src/libqof/qof/qof-backend.cpp b/src/libqof/qof/qof-backend.cpp new file mode 100644 index 0000000000..e779fc0089 --- /dev/null +++ b/src/libqof/qof/qof-backend.cpp @@ -0,0 +1,171 @@ +/********************************************************************\ + * qofbackend.c -- utility routines for dealing with the data backend * + * Copyright (C) 2000 Linas Vepstas * + * Copyright (C) 2004-5 Neil Williams * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * + * Boston, MA 02110-1301, USA gnu@gnu.org * + * * +\********************************************************************/ + +extern "C" +{ + +#include +#include "qof.h" +} + +#include +#include +#include + +#include "qof-backend.hpp" + +G_GNUC_UNUSED static QofLogModule log_module = QOF_MOD_BACKEND; + +#define QOF_CONFIG_DESC "desc" +#define QOF_CONFIG_TIP "tip" + +/* *******************************************************************\ + * error handling * +\********************************************************************/ + +GModuleVec QofBackend::c_be_registry{}; + +void +QofBackend::set_error(QofBackendError err) +{ + /* use stack-push semantics. Only the earliest error counts */ + if (m_last_err != ERR_BACKEND_NO_ERR) return; + m_last_err = err; +} + +QofBackendError +QofBackend::get_error() +{ + /* use 'stack-pop' semantics */ + auto err = m_last_err; + m_last_err = ERR_BACKEND_NO_ERR; + return err; +} + +bool +QofBackend::check_error() +{ + return m_last_err != ERR_BACKEND_NO_ERR; +} + +void +QofBackend::set_message (std::string&& msg) +{ + m_error_msg = msg; +} + +const std::string&& +QofBackend::get_message () +{ + return std::move(m_error_msg); +} + +bool +QofBackend::register_backend(const char* directory, const char* module_name) +{ + if (!g_module_supported ()) + { + PWARN("Modules not supported."); + return false; + } + auto fullpath = g_module_build_path (directory, module_name); +/* Darwin modules can have either .so or .dylib for a suffix */ + if (!g_file_test (fullpath, G_FILE_TEST_EXISTS) && + g_strcmp0 (G_MODULE_SUFFIX, "so") == 0) + { + auto modname = g_strdup_printf ("lib%s.dylib", module_name); + g_free (fullpath); + fullpath = g_build_filename (directory, modname, NULL); + g_free (modname); + } + auto backend = g_module_open (fullpath, G_MODULE_BIND_LAZY); + g_free (fullpath); + if (!backend) + { + PINFO ("%s: %s\n", PACKAGE, g_module_error ()); + return false; + } + void (*module_init_func)(void); + if (g_module_symbol (backend, "qof_backend_module_init", + reinterpret_cast(&module_init_func))) + module_init_func (); + + g_module_make_resident (backend); + c_be_registry.push_back(backend); + return TRUE; +} + +void +QofBackend::release_backends() +{ + for (auto backend : c_be_registry) + { + void (*module_finalize_func)(void); + if (g_module_symbol(backend, "qof_backend_module_finalize", + reinterpret_cast(&module_finalize_func))) + module_finalize_func(); + } +} +/***********************************************************************/ +QofBackendError +qof_backend_get_error (QofBackend* qof_be) +{ + if (qof_be == nullptr) return ERR_BACKEND_NO_ERR; + return ((QofBackend*)qof_be)->get_error(); +} + +void +qof_backend_set_error (QofBackend* qof_be, QofBackendError err) +{ + if (qof_be == nullptr) return; + ((QofBackend*)qof_be)->set_error(err); +} + +gboolean +qof_backend_can_rollback (QofBackend* qof_be) +{ + if (qof_be == nullptr) return FALSE; + return true; +} + +void +qof_backend_rollback_instance (QofBackend* qof_be, QofInstance* inst) +{ + if (qof_be == nullptr) return; + ((QofBackend*)qof_be)->rollback(inst); +} + +gboolean +qof_load_backend_library (const char *directory, const char* module_name) +{ + return QofBackend::register_backend(directory, module_name); +} + +void +qof_finalize_backend_libraries(void) +{ + QofBackend::release_backends(); +} + +/************************* END OF FILE ********************************/ diff --git a/src/libqof/qof/qofbackend-p.h b/src/libqof/qof/qof-backend.hpp similarity index 62% rename from src/libqof/qof/qofbackend-p.h rename to src/libqof/qof/qof-backend.hpp index 9eb0e6545f..aebec9a683 100644 --- a/src/libqof/qof/qofbackend-p.h +++ b/src/libqof/qof/qof-backend.hpp @@ -1,5 +1,6 @@ /********************************************************************\ - * qofbackend-p.h -- private api for data storage backend * + * qof-backend.hpp Declare QofBackend class * + * Copyright 2016 John Ralls * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * @@ -38,81 +39,26 @@ @author Copyright (c) 2005 Neil Williams @{ */ -#ifndef QOF_BACKEND_P_H -#define QOF_BACKEND_P_H - +#ifndef __QOF_BACKEND_HPP__ +#define __QOF_BACKEND_HPP__ +extern "C" +{ #include "qofbackend.h" #include "qofbook.h" #include "qofinstance-p.h" #include "qofquery.h" #include "qofsession.h" +#include +} -/** - * The backend_new routine sets the functions that will be used - * by the backend to perform the actions required by QOF. A - * basic minimum is session_begin, session_end, load and - * sync. Any unused functions should be set to NULL. If the - * backend uses configuration options, backend_new must ensure - * that these are set to usable defaults before returning. To use - * configuration options, load_config and get_config must also - * be defined. - * - * The session_begin() routine gives the backend a second initialization - * opportunity. It is suggested that the backend check that - * the URL is syntactically correct, and that it is actually - * reachable. This is probably(?) a good time to initialize - * the actual network connection. - * - * The 'ignore_lock' argument indicates whether the single-user - * lock on the backend should be cleared. The typical GUI sequence - * leading to this is: (1) GUI attempts to open the backend - * by calling this routine with FALSE==ignore_lock. (2) If backend - * error'ed BACKEND_LOCK, then GUI asks user what to do. (3) if user - * answers 'break & enter' then this routine is called again with - * TRUE==ignore_lock. - * - * The 'create_if_nonexistent' argument indicates whether this - * routine should create a new 'database', if it doesn't already - * exist. For example, for a file-backend, this would create the - * file, if it didn't already exist. For an SQL backend, this - * would create the database (the schema) if it didn't already - * exist. This flag is used to implement the 'SaveAs' GUI, where - * the user requests to save data to a new backend. - * - * The load() routine should load the minimal set of application data - * needed for the application to be operable at initial startup. - * It is assumed that the application will perform a 'run_query()' - * to obtain any additional data that it needs. For file-based - * backends, it is acceptable for the backend to return all data - * at load time; for SQL-based backends, it is acceptable for the - * backend to return no data. - * - * Thus, for example, the GnuCash postgres backend returned - * the account tree, all currencies, and the pricedb, as these - * were needed at startup. It did not have to return any - * transactions whatsoever, as these were obtained at a later stage - * when a user opened a register, resulting in a query being sent to - * the backend. - * - * (Its OK to send over entities at this point, but one should - * be careful of the network load; also, its possible that whatever - * is sent is not what the user wanted anyway, which is why its - * better to wait for the query). - * - * The begin() routine is called when the engine is about to - * make a change to a data structure. It can provide an advisory - * lock on data. - * - * The commit() routine commits the changes from the engine to the - * backend data storage. - * - * The rollback() routine is used to revert changes in the engine - * and unlock the backend. - * - * If the second user tries to modify an entity that - * the first user deleted, then the backend should set the error - * to ERR_BACKEND_MOD_DESTROY from this routine, so that the - * engine can properly clean up. +#include +#include +#include +/* NOTE: The following comments were musings by the original developer about how + * some additional API might work. The compile/free/run_query functions were + * implemented for the DBI backend but never put into use; the rest were never + * implemented. They're here as something to consider if we ever decide to + * implement them. * * The compile_query() method compiles a QOF query object into * a backend-specific data structure and returns the compiled @@ -140,22 +86,6 @@ * continue functioning even when disconnected from the server: * this is because it will have its local cache of data from which to work. * - * The sync() routine synchronizes the engine contents to the backend. - * This should done by using version numbers (hack alert -- the engine - * does not currently contain version numbers). - * If the engine contents are newer than what is in the backend, the - * data is stored to the backend. If the engine contents are older, - * then the engine contents are updated. - * - * Note that this sync operation is only meant to apply to the - * current contents of the engine. This routine is not intended - * to be used to fetch entity data from the backend. - * - * File based backends tend to use sync as if it was called dump. - * Data is written out into the backend, overwriting the previous - * data. Database backends should implement a more intelligent - * solution. - * * The events_pending() routines should return true if there are * external events which need to be processed to bring the * engine up to date with the backend. @@ -164,10 +94,6 @@ * by the events_pending() routine. It should return TRUE if * the engine was changed while engine events were suspended. * - * The last_err member indicates the last error that occurred. - * It should probably be implemented as an array (actually, - * a stack) of all the errors that have occurred. - * * For support of book partitioning, use special "Book" begin_edit() * and commit_edit() QOF_ID types. * @@ -242,80 +168,147 @@ typedef enum LOAD_TYPE_LOAD_ALL } QofBackendLoadType; -struct QofBackend_s +using GModuleVec = std::vector; +struct QofBackend { - void (*session_begin) (QofBackend *be, - QofSession *session, - const char *book_id, - gboolean ignore_lock, - gboolean create, - gboolean force); - void (*session_end) (QofBackend *); - void (*destroy_backend) (/*@ only @*/ QofBackend *); - - void (*load) (QofBackend *, /*@ dependent @*/ QofBook *, QofBackendLoadType); - - void (*begin) (QofBackend *, QofInstance *); - void (*commit) (QofBackend *, QofInstance *); - void (*rollback) (QofBackend *, QofInstance *); - - void (*sync) (QofBackend *, /*@ dependent @*/ QofBook *); - void (*safe_sync) (QofBackend *, /*@ dependent @*/ QofBook *); - /* This is implented only in the XML backend where it exports only a chart - * of accounts. +public: + /* For reasons that aren't a bit clear, using the default constructor + * sometimes initializes m_last_err incorrectly with Xcode8 and a 32-bit + * build unless the initialization is stepped-through in a debugger. */ - void (*export_fn) (QofBackend *, QofBook *); - QofBePercentageFunc percentage; - - QofBackendError last_err; - char * error_msg; - - gint config_count; + QofBackend() : + m_percentage{nullptr}, m_fullpath{}, m_last_err{ERR_BACKEND_NO_ERR}, + m_error_msg{} {} + QofBackend(const QofBackend&) = delete; + QofBackend(const QofBackend&&) = delete; + virtual ~QofBackend() = default; +/** + * Open the file or connect to the server. + * @param session The QofSession that will control the backend. + * @param book_id The book's string identifier. + * @param ignore_lock indicates whether the single-user lock on the backend + * should be cleared. The typical GUI sequence leading to this is: + * (1) GUI attempts to open the backend by calling this routine with + * ignore_lock false. + * (2) If backend error'ed BACKEND_LOCK, then GUI asks user what to do. + * (3) if user answers 'break & enter' then this routine is called again with + * ignore_lock true. + * @param create indicates whether this routine should create a new + * 'database', if it doesn't already exist. For example, for a file-backend, + * this would create the file, if it didn't already exist. For an SQL + * backend, this would create the database (the schema) if it didn't already + * exist. This flag is used to implement the 'SaveAs' GUI, where the user + * requests to save data to a new backend. + * + * @param force works with create to force creating a new database even if + * one already exists at the same URI. + */ + virtual void session_begin(QofSession *session, const char* book_id, + bool ignore_lock, bool create, bool force) = 0; + virtual void session_end() = 0; +/** + * Load the minimal set of application data needed for the application to be + * operable at initial startup. It is assumed that the application will + * perform a 'run_query()' to obtain any additional data that it needs. For + * file-based backends, it is acceptable for the backend to return all data + * at load time; for SQL-based backends, it is acceptable for the backend to + * return no data. + * + * Thus, for example, the old GnuCash postgres backend returned the account + * tree, all currencies, and the pricedb, as these were needed at startup. + * It did not have to return any transactions whatsoever, as these were + * obtained at a later stage when a user opened a register, resulting in a + * query being sent to the backend. The current DBI backend on the other hand + * loads the entire database into memory. + * + * (Its OK to send over entities at this point, but one should + * be careful of the network load; also, its possible that whatever + * is sent is not what the user wanted anyway, which is why its + * better to wait for the query). + */ + virtual void load (QofBook*, QofBackendLoadType) = 0; +/** + * Called when the engine is about to make a change to a data structure. It + * could provide an advisory lock on data, but no backend does this. + */ + virtual void begin(QofInstance*) {} +/** + * Commits the changes from the engine to the backend data storage. + */ + virtual void commit (QofInstance*) {} +/** + * Revert changes in the engine and unlock the backend. + */ + virtual void rollback(QofInstance*) {} +/** + * Synchronizes the engine contents to the backend. + * This should done by using version numbers (hack alert -- the engine + * does not currently contain version numbers). + * If the engine contents are newer than what is in the backend, the + * data is stored to the backend. If the engine contents are older, + * then the engine contents are updated. + * + * Note that this sync operation is only meant to apply to the + * current contents of the engine. This routine is not intended + * to be used to fetch entity data from the backend. + * + * File based backends tend to use sync as if it was called dump. + * Data is written out into the backend, overwriting the previous + * data. Database backends should implement a more intelligent + * solution. + */ + virtual void sync(QofBook *) = 0; +/** Perform a sync in a way that prevents data loss on a DBI backend. + */ + virtual void safe_sync(QofBook *) = 0; +/** Extract the chart of accounts from the current database and create a new + * database with it. Implemented only in the XML backend at present. + */ + virtual void export_coa(QofBook *) {} +/** Set the error value only if there isn't already an error already. + */ + void set_error(QofBackendError err); +/** Retrieve the currently-stored error and clear it. + */ + QofBackendError get_error(); +/** Report if there is an error. + */ + bool check_error(); +/** Set a descriptive message that can be displayed to the user when there's an + * error. + */ + void set_message(std::string&&); +/** Retrieve and clear the stored error message. + */ + const std::string&& get_message(); +/** Store and retrieve a backend-specific function for determining the progress + * in completing a long operation, for use with a progress meter. + */ + void set_percentage(QofBePercentageFunc pctfn) { m_percentage = pctfn; } + QofBePercentageFunc get_percentage() { return m_percentage; } +/** Retrieve the backend's storage URI. + */ + std::string get_uri() { return m_fullpath; } +/** + * Class methods for dynamically loading the several backends and for freeing + * them at shutdown. + */ + static bool register_backend(const char*, const char*); + static void release_backends(); +protected: + QofBePercentageFunc m_percentage; /** Each backend resolves a fully-qualified file path. * This holds the filepath and communicates it to the frontends. */ - char * fullpath; + std::string m_fullpath; +private: + static GModuleVec c_be_registry; + QofBackendError m_last_err; + std::string m_error_msg; }; -#ifdef __cplusplus -extern "C" -{ -#endif - -/** The qof_backend_set_message() assigns a string to the backend error message. - */ -void qof_backend_set_message(QofBackend *be, const char *format, ...); - -/** The qof_backend_get_message() pops the error message string from - * the Backend. This string should be freed with g_free(). - */ -char * qof_backend_get_message(QofBackend *be); - -void qof_backend_init(QofBackend *be); -void qof_backend_destroy(QofBackend *be); - -/** Allow backends to see if the book is open - -@return 'y' if book is open, otherwise 'n'. -*/ -gchar qof_book_get_open_marker(const QofBook *book); - -/** get the book version - -used for tracking multiuser updates in backends. - -@return -1 if no book exists, 0 if the book is -new, otherwise the book version number. -*/ -gint32 qof_book_get_version (const QofBook *book); - -void qof_book_set_version (QofBook *book, gint32 version); - /* @} */ /* @} */ /* @} */ -#ifdef __cplusplus -} -#endif -#endif /* QOF_BACKEND_P_H */ +#endif /* __QOF_BACKEND_HPP__ */ diff --git a/src/libqof/qof/qofbackend.cpp b/src/libqof/qof/qofbackend.cpp deleted file mode 100644 index 6613feda6b..0000000000 --- a/src/libqof/qof/qofbackend.cpp +++ /dev/null @@ -1,284 +0,0 @@ -/********************************************************************\ - * qofbackend.c -- utility routines for dealing with the data backend * - * Copyright (C) 2000 Linas Vepstas * - * Copyright (C) 2004-5 Neil Williams * - * * - * This program is free software; you can redistribute it and/or * - * modify it under the terms of the GNU General Public License as * - * published by the Free Software Foundation; either version 2 of * - * the License, or (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License* - * along with this program; if not, contact: * - * * - * Free Software Foundation Voice: +1-617-542-5942 * - * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * - * Boston, MA 02110-1301, USA gnu@gnu.org * - * * -\********************************************************************/ - -extern "C" -{ - -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include "qof.h" -#include "qofbackend-p.h" - -} - -G_GNUC_UNUSED static QofLogModule log_module = QOF_MOD_BACKEND; - -#define QOF_CONFIG_DESC "desc" -#define QOF_CONFIG_TIP "tip" - -/* *******************************************************************\ - * error handling * -\********************************************************************/ - -void -qof_backend_set_error (QofBackend *be, QofBackendError err) -{ - if (!be) return; - - /* use stack-push semantics. Only the earliest error counts */ - if (ERR_BACKEND_NO_ERR != be->last_err) return; - be->last_err = err; -} - -QofBackendError -qof_backend_get_error (QofBackend *be) -{ - QofBackendError err; - if (!be) return ERR_BACKEND_NO_BACKEND; - - /* use 'stack-pop' semantics */ - err = be->last_err; - be->last_err = ERR_BACKEND_NO_ERR; - return err; -} - -gboolean -qof_backend_check_error (QofBackend *be) -{ - g_return_val_if_fail (be != NULL, TRUE); - return be->last_err != ERR_BACKEND_NO_ERR; -} - -gboolean -qof_backend_can_rollback (QofBackend* be) -{ - if (be == nullptr) return FALSE; - return be->rollback != nullptr; -} - -void -qof_backend_rollback_instance (QofBackend* be, QofInstance* inst) -{ - if (be == nullptr || be->rollback == nullptr) return; - (be->rollback)(be, inst); -} - -void -qof_backend_set_message (QofBackend *be, const char *format, ...) -{ - va_list args; - char * buffer; - - if (!be) return; - - /* If there's already something here, free it */ - if (be->error_msg) g_free(be->error_msg); - - if (!format) - { - be->error_msg = NULL; - return; - } - - va_start(args, format); - buffer = (char *)g_strdup_vprintf(format, args); - va_end(args); - - be->error_msg = buffer; -} - -char * -qof_backend_get_message (QofBackend *be) -{ - char * msg; - - if (!be) return g_strdup("ERR_BACKEND_NO_BACKEND"); - if (!be->error_msg) return NULL; - - /* - * Just return the contents of the error_msg and then set it to - * NULL. This is necessary, because the Backends don't seem to - * have a destroy_backend function to take care of freeing stuff - * up. The calling function should free the copy. - * Also, this is consistent with the qof_backend_get_error() popping. - */ - - msg = be->error_msg; - be->error_msg = NULL; - return msg; -} - -/***********************************************************************/ -/* Get a clean backend */ -void -qof_backend_init(QofBackend *be) -{ - be->session_begin = NULL; - be->session_end = NULL; - be->destroy_backend = NULL; - - be->load = NULL; - - be->begin = NULL; - be->commit = NULL; - be->rollback = NULL; - - be->sync = NULL; - be->safe_sync = NULL; - - be->export_fn = NULL; - - be->last_err = ERR_BACKEND_NO_ERR; - if (be->error_msg) g_free (be->error_msg); - be->error_msg = NULL; - be->percentage = NULL; -} - -void -qof_backend_destroy(QofBackend *be) -{ - g_free(be->error_msg); - be->error_msg = NULL; -} - -void -qof_backend_run_begin(QofBackend *be, QofInstance *inst) -{ - if (!be || !inst) - { - return; - } - if (!be->begin) - { - return; - } - (be->begin) (be, inst); -} - -gboolean -qof_backend_begin_exists(const QofBackend *be) -{ - if (be->begin) - { - return TRUE; - } - else - { - return FALSE; - } -} - -void -qof_backend_run_commit(QofBackend *be, QofInstance *inst) -{ - if (!be || !inst) - { - return; - } - if (!be->commit) - { - return; - } - (be->commit) (be, inst); -} - - -gboolean -qof_backend_commit_exists(const QofBackend *be) -{ - if (!be) - { - return FALSE; - } - if (be->commit) - { - return TRUE; - } - else - { - return FALSE; - } -} - -static GSList* backend_module_list = NULL; - -gboolean -qof_load_backend_library (const char *directory, const char* module_name) -{ - gchar *fullpath; - GModule *backend; - void (*module_init_func) (void); - - g_return_val_if_fail(g_module_supported (), FALSE); - fullpath = g_module_build_path (directory, module_name); -/* Darwin modules can have either .so or .dylib for a suffix */ - if (!g_file_test (fullpath, G_FILE_TEST_EXISTS) && - g_strcmp0 (G_MODULE_SUFFIX, "so") == 0) - { - gchar *modname = g_strdup_printf ("lib%s.dylib", module_name); - g_free (fullpath); - fullpath = g_build_filename (directory, modname, NULL); - g_free (modname); - } - backend = g_module_open (fullpath, G_MODULE_BIND_LAZY); - g_free (fullpath); - if (!backend) - { - g_message ("%s: %s\n", PACKAGE, g_module_error ()); - return FALSE; - } - if (g_module_symbol (backend, "qof_backend_module_init", - reinterpret_cast(&module_init_func))) - module_init_func (); - - g_module_make_resident (backend); - backend_module_list = g_slist_prepend (backend_module_list, backend); - return TRUE; -} - -void -qof_finalize_backend_libraries(void) -{ - GSList* node; - GModule* backend; - void (*module_finalize_func) (void); - - for (node = backend_module_list; node != NULL; node = node->next) - { - backend = (GModule*)node->data; - - if (g_module_symbol(backend, "qof_backend_module_finalize", - reinterpret_cast(&module_finalize_func))) - module_finalize_func(); - - } -} - -/************************* END OF FILE ********************************/ diff --git a/src/libqof/qof/qofbackend.h b/src/libqof/qof/qofbackend.h index 1fa87c98f9..98e9a9a1d0 100644 --- a/src/libqof/qof/qofbackend.h +++ b/src/libqof/qof/qofbackend.h @@ -127,15 +127,8 @@ typedef enum ERR_RPC_FAILED, /**< Operation failed */ ERR_RPC_NOT_ADDED, /**< object not added */ } QofBackendError; -/** \brief Pseudo-object providing an interface between the - * engine and a persistant data store (e.g. a server, a database, - * or a file). - * - * There are no backend functions that are 'public' to users of the - * engine. The backend can, however, report errors to the GUI & other - * front-end users. - */ - typedef struct QofBackend_s QofBackend; + +typedef struct QofBackend QofBackend; /* The following functions are used in C files. */ /** Get the last backend error. */ @@ -143,7 +136,7 @@ typedef enum /** Set the error on the specified QofBackend. */ void qof_backend_set_error (QofBackend*, QofBackendError); -/* Temporary wrapper so that we don't have to expose qofbackend-p.h to Transaction.c */ +/* Temporary wrapper so that we don't have to expose qof-backend.hpp to Transaction.c */ gboolean qof_backend_can_rollback (QofBackend*); void qof_backend_rollback_instance (QofBackend*, QofInstance*); @@ -173,30 +166,7 @@ typedef void (*QofBePercentageFunc) (/*@ null @*/ const char *message, double pe #ifdef __cplusplus } - -/** @name Allow access to the begin routine for this backend. */ -//@{ - -void qof_backend_run_begin(QofBackend *be, QofInstance *inst); - -gboolean qof_backend_begin_exists(const QofBackend *be); - -void qof_backend_run_commit(QofBackend *be, QofInstance *inst); - -gboolean qof_backend_commit_exists(const QofBackend *be); -//@} - -/** Report if the backend is in an error state. - * Since get_error resets the error state, its use for branching as the backend - * bubbles back up to the session would make the session think that there was - * no error. - * \param be The backend being tested. - * \return TRUE if the backend has an error set. - */ -gboolean qof_backend_check_error (QofBackend *be); - #endif - #endif /* QOF_BACKEND_H */ /** @} */ /** @} */ diff --git a/src/libqof/qof/qofbook.cpp b/src/libqof/qof/qofbook.cpp index e7701f6452..270fc8b045 100644 --- a/src/libqof/qof/qofbook.cpp +++ b/src/libqof/qof/qofbook.cpp @@ -52,7 +52,7 @@ extern "C" #include "qof.h" #include "qofevent-p.h" -#include "qofbackend-p.h" +#include "qofbackend.h" #include "qofbook-p.h" #include "qofid-p.h" #include "qofobject-p.h" diff --git a/src/libqof/qof/qofinstance.cpp b/src/libqof/qof/qofinstance.cpp index 9f3f542670..64feeef304 100644 --- a/src/libqof/qof/qofinstance.cpp +++ b/src/libqof/qof/qofinstance.cpp @@ -41,6 +41,7 @@ extern "C" #include "qofid-p.h" #include "kvp_frame.hpp" #include "qofinstance-p.h" +#include "qof-backend.hpp" static QofLogModule log_module = QOF_MOD_ENGINE; @@ -957,7 +958,6 @@ gboolean qof_begin_edit (QofInstance *inst) { QofInstancePrivate *priv; - QofBackend * be; if (!inst) return FALSE; @@ -967,9 +967,9 @@ qof_begin_edit (QofInstance *inst) if (0 >= priv->editlevel) priv->editlevel = 1; - be = qof_book_get_backend(priv->book); - if (be && qof_backend_begin_exists(be)) - qof_backend_run_begin(be, inst); + auto be = qof_book_get_backend(priv->book); + if (be) + be->begin(inst); else priv->dirty = TRUE; @@ -1001,7 +1001,6 @@ qof_commit_edit_part2(QofInstance *inst, void (*on_free)(QofInstance *)) { QofInstancePrivate *priv; - QofBackend * be; priv = GET_PRIVATE(inst); @@ -1012,27 +1011,27 @@ qof_commit_edit_part2(QofInstance *inst, } /* See if there's a backend. If there is, invoke it. */ - be = qof_book_get_backend(priv->book); - if (be && qof_backend_commit_exists(be)) + auto be = qof_book_get_backend(priv->book); + if (be) { QofBackendError errcode; /* clear errors */ do { - errcode = qof_backend_get_error(be); + errcode = be->get_error(); } - while (ERR_BACKEND_NO_ERR != errcode); + while (errcode != ERR_BACKEND_NO_ERR); - qof_backend_run_commit(be, inst); - errcode = qof_backend_get_error(be); - if (ERR_BACKEND_NO_ERR != errcode) + be->commit(inst); + errcode = be->get_error(); + if (errcode != ERR_BACKEND_NO_ERR) { /* XXX Should perform a rollback here */ priv->do_free = FALSE; /* Push error back onto the stack */ - qof_backend_set_error (be, errcode); + be->set_error (errcode); if (on_error) on_error(inst, errcode); return FALSE; diff --git a/src/libqof/qof/qofquery.cpp b/src/libqof/qof/qofquery.cpp index c6cad34a66..1fac61c040 100644 --- a/src/libqof/qof/qofquery.cpp +++ b/src/libqof/qof/qofquery.cpp @@ -33,7 +33,7 @@ extern "C" } #include "qof.h" -#include "qofbackend-p.h" +#include "qof-backend.hpp" #include "qofbook-p.h" #include "qofclass-p.h" #include "qofquery-p.h" diff --git a/src/libqof/qof/qofsession.cpp b/src/libqof/qof/qofsession.cpp index cf9c31fa81..9939c67f44 100644 --- a/src/libqof/qof/qofsession.cpp +++ b/src/libqof/qof/qofsession.cpp @@ -56,7 +56,7 @@ extern "C" static QofLogModule log_module = QOF_MOD_SESSION; } //extern 'C' -#include "qofbackend-p.h" +#include "qof-backend.hpp" #include "qofsession.hpp" #include "gnc-backend-prov.hpp" @@ -122,6 +122,7 @@ QofSessionImpl::QofSessionImpl () noexcept : m_book {qof_book_new ()}, m_book_id {}, m_saving {false}, + m_last_err {}, m_error_message {} { clear_error (); @@ -157,10 +158,7 @@ QofSessionImpl::destroy_backend () noexcept if (backend) { clear_error (); - if (backend->destroy_backend) - backend->destroy_backend (backend); - else - g_free(backend); + delete backend; qof_book_set_backend (m_book, nullptr); } } @@ -218,7 +216,7 @@ QofSessionImpl::load (QofPercentageFunc percentage_func) noexcept * top-level account group out of the backend, and that is a * generic, backend-independent operation. */ - QofBackend * be {qof_book_get_backend (oldbook)}; + auto be (qof_book_get_backend (oldbook)); qof_book_set_backend (newbook, be); /* Starting the session should result in a bunch of accounts @@ -227,12 +225,9 @@ QofSessionImpl::load (QofPercentageFunc percentage_func) noexcept */ if (be) { - be->percentage = percentage_func; - if (be->load) - { - be->load (be, newbook, LOAD_TYPE_INITIAL_LOAD); - push_error (qof_backend_get_error(be), {}); - } + be->set_percentage(percentage_func); + be->load (newbook, LOAD_TYPE_INITIAL_LOAD); + push_error (be->get_error(), {}); } /* XXX if the load fails, then we try to restore the old set of books; @@ -262,7 +257,8 @@ QofSessionImpl::load (QofPercentageFunc percentage_func) noexcept } void -QofSessionImpl::begin (std::string new_book_id, bool ignore_lock, bool create, bool force) noexcept +QofSessionImpl::begin (std::string new_book_id, bool ignore_lock, + bool create, bool force) noexcept { ENTER (" sess=%p ignore_lock=%d, book-id=%s", this, ignore_lock, new_book_id.c_str ()); @@ -313,7 +309,8 @@ QofSessionImpl::begin (std::string new_book_id, bool ignore_lock, bool create, b g_free (scheme); /* No backend was found. That's bad. */ - if (!qof_book_get_backend (m_book)) + auto backend = qof_book_get_backend (m_book); + if (backend == nullptr) { m_book_id = {}; if (ERR_BACKEND_NO_ERR == get_error ()) @@ -324,26 +321,22 @@ QofSessionImpl::begin (std::string new_book_id, bool ignore_lock, bool create, b } /* If there's a begin method, call that. */ - if (qof_book_get_backend (m_book)->session_begin) + backend->session_begin(this, m_book_id.c_str(), ignore_lock, create, force); + PINFO ("Done running session_begin on backend"); + QofBackendError const err {backend->get_error()}; + auto msg (backend->get_message()); + if (err != ERR_BACKEND_NO_ERR) { - auto backend = qof_book_get_backend (m_book); - (backend->session_begin) (backend, this, m_book_id.c_str (), ignore_lock, create, force); - PINFO ("Done running session_begin on backend"); - QofBackendError const err {qof_backend_get_error (backend)}; - char * msg {qof_backend_get_message (backend)}; - if (err != ERR_BACKEND_NO_ERR) - { - m_book_id = {}; - push_error (err, msg ? msg : ""); - LEAVE (" backend error %d %s", err, msg ? msg : "(null)"); - return; - } - if (msg != nullptr) - { - PWARN("%s", msg); - g_free(msg); - } + m_book_id = {}; + push_error (err, msg); + LEAVE (" backend error %d %s", err, msg.empty() ? "(null)" : msg.c_str()); + return; } + if (!msg.empty()) + { + PWARN("%s", msg.c_str()); + } + LEAVE (" sess=%p book-id=%s", this, new_book_id.c_str ()); } @@ -352,10 +345,10 @@ QofSessionImpl::end () noexcept { ENTER ("sess=%p book_id=%s", this, m_book_id.c_str ()); auto backend = qof_book_get_backend (m_book); - if (backend && backend->session_end) - (backend->session_end) (backend); + if (backend != nullptr) + backend->session_end(); clear_error (); - m_book_id = {}; + m_book_id.clear(); LEAVE ("sess=%p book_id=%s", this, m_book_id.c_str ()); } @@ -368,12 +361,12 @@ QofSessionImpl::clear_error () noexcept m_error_message = {}; /* pop the stack on the backend as well. */ - if (qof_book_get_backend (m_book)) + if (auto backend = qof_book_get_backend (m_book)) { - QofBackendError err; + QofBackendError err = ERR_BACKEND_NO_ERR; do - err = qof_backend_get_error (qof_book_get_backend (m_book)); - while (ERR_BACKEND_NO_ERR != err); + err = backend->get_error(); + while (err != ERR_BACKEND_NO_ERR); } } @@ -388,12 +381,12 @@ QofBackendError QofSessionImpl::get_error () noexcept { /* if we have a local error, return that. */ - if (ERR_BACKEND_NO_ERR != m_last_err) + if (m_last_err != ERR_BACKEND_NO_ERR) return m_last_err; + auto qof_be = qof_book_get_backend (m_book); + if (qof_be == nullptr) return ERR_BACKEND_NO_ERR; - if (!qof_book_get_backend (m_book)) return ERR_BACKEND_NO_ERR; - - m_last_err = qof_backend_get_error (qof_book_get_backend (m_book)); + m_last_err = qof_be->get_error(); return m_last_err; } @@ -431,8 +424,9 @@ QofSession::get_backend () const noexcept std::string QofSessionImpl::get_file_path () const noexcept { - if (!qof_book_get_backend (m_book)) return nullptr; - return qof_book_get_backend (m_book)->fullpath; + auto backend = qof_book_get_backend (m_book); + if (!backend) return nullptr; + return backend->get_uri(); } std::string const & @@ -469,17 +463,14 @@ QofSessionImpl::save (QofPercentageFunc percentage_func) noexcept { /* if invoked as SaveAs(), then backend not yet set */ qof_book_set_backend (m_book, backend); - backend->percentage = percentage_func; - if (backend->sync) + backend->set_percentage(percentage_func); + backend->sync(m_book); + auto err = backend->get_error(); + if (err != ERR_BACKEND_NO_ERR) { - (backend->sync)(backend, m_book); - QofBackendError err {qof_backend_get_error (backend)}; - if (ERR_BACKEND_NO_ERR != err) - { - push_error (err, {}); - m_saving = false; - return; - } + push_error (err, {}); + m_saving = false; + return; } /* If we got to here, then the backend saved everything * just fine, and we are done. So return. */ @@ -499,17 +490,15 @@ QofSessionImpl::safe_save (QofPercentageFunc percentage_func) noexcept { auto backend = qof_book_get_backend (m_book); if (!backend) return; - if (!backend->safe_sync) return; - backend->percentage = percentage_func; - (backend->safe_sync) (backend, get_book ()); - auto err = qof_backend_get_error (qof_book_get_backend (m_book)); - auto msg = qof_backend_get_message (qof_book_get_backend (m_book)); + backend->set_percentage(percentage_func); + backend->safe_sync(get_book ()); + auto err = backend->get_error(); + auto msg = backend->get_message(); if (err != ERR_BACKEND_NO_ERR) { - m_book_id = {}; - push_error (err, msg ? msg : ""); + m_book_id = nullptr; + push_error (err, msg); } - g_free (msg); } void @@ -517,9 +506,8 @@ QofSessionImpl::ensure_all_data_loaded () noexcept { auto backend = qof_book_get_backend (m_book); if (!backend) return; - if (!backend->load) return; - backend->load(backend, get_book (), LOAD_TYPE_LOAD_ALL); - push_error (qof_backend_get_error (backend), {}); + backend->load(m_book, LOAD_TYPE_LOAD_ALL); + push_error (backend->get_error(), {}); } void @@ -552,7 +540,8 @@ QofSessionImpl::process_events () const noexcept * book-closing is implemented. */ bool -QofSessionImpl::export_session (QofSessionImpl & real_session, QofPercentageFunc percentage_func) noexcept +QofSessionImpl::export_session (QofSessionImpl & real_session, + QofPercentageFunc percentage_func) noexcept { auto real_book = real_session.get_book (); ENTER ("tmp_session=%p real_session=%p book=%p book_id=%s", @@ -564,12 +553,11 @@ QofSessionImpl::export_session (QofSessionImpl & real_session, QofPercentageFunc auto backend2 = qof_book_get_backend(m_book); if (!backend2) return false; - backend2->percentage = percentage_func; - if (!backend2->export_fn) return true; + backend2->set_percentage(percentage_func); - (backend2->export_fn)(backend2, real_book); - auto err = qof_backend_get_error(backend2); - if (ERR_BACKEND_NO_ERR != err) + backend2->export_coa(real_book); + auto err = backend2->get_error(); + if (err != ERR_BACKEND_NO_ERR) return false; return true; } @@ -749,4 +737,3 @@ qof_session_get_error (QofSession * session) if (!session) return ERR_BACKEND_NO_BACKEND; return session->get_error(); } - diff --git a/src/libqof/qof/qofutil.cpp b/src/libqof/qof/qofutil.cpp index 2881fd73d8..0e988b8dae 100644 --- a/src/libqof/qof/qofutil.cpp +++ b/src/libqof/qof/qofutil.cpp @@ -32,7 +32,7 @@ #include #include #include "qof.h" -#include "qofbackend-p.h" +#include "qof-backend.hpp" G_GNUC_UNUSED static QofLogModule log_module = QOF_MOD_UTIL; @@ -273,7 +273,7 @@ qof_close(void) { qof_query_shutdown (); qof_object_shutdown (); - qof_finalize_backend_libraries(); + QofBackend::release_backends(); qof_string_cache_destroy (); qof_log_shutdown(); } diff --git a/src/libqof/qof/test/Makefile.am b/src/libqof/qof/test/Makefile.am index f5f6507d53..5c43d20700 100644 --- a/src/libqof/qof/test/Makefile.am +++ b/src/libqof/qof/test/Makefile.am @@ -14,9 +14,7 @@ test_qof_SOURCES = \ test-qofbook.c \ test-qofinstance.cpp \ test-qofobject.c \ - test-qofsession-old.cpp \ test-qof-string-cache.c \ - test-gnc-guid-old.cpp \ ${top_srcdir}/src/test-core/unittest-support.c test_qof_HEADERS = \ diff --git a/src/libqof/qof/test/test-qofinstance.cpp b/src/libqof/qof/test/test-qofinstance.cpp index 955be9caeb..e9e3c6b56c 100644 --- a/src/libqof/qof/test/test-qofinstance.cpp +++ b/src/libqof/qof/test/test-qofinstance.cpp @@ -27,8 +27,8 @@ extern "C" #include #include #include "../qof.h" -#include "../qofbackend-p.h" } +#include "../qof-backend.hpp" #include "../kvp_frame.hpp" static const gchar *suitename = "/qof/qofinstance"; extern "C" void test_suite_qofinstance ( void ); @@ -41,11 +41,86 @@ static gboolean is_called; #define _Q "`" #endif +static struct +{ + QofInstance *m_inst = nullptr; + QofBackend *m_be = nullptr; + + bool m_commit_called = false; + bool m_commit_with_err_called = false; + bool m_on_error_called = false; + bool m_on_free_called = false; + bool m_on_done_called = false; + QofBackendError m_err = ERR_BACKEND_NO_ERR; +} commit_test; + + +class MockBackend : public QofBackend +{ +public: + MockBackend() : m_qof_error{ERR_BACKEND_NO_ERR} { + commit_test.m_be = this; + } + void session_begin(QofSession* sess, const char* book_name, + bool ignore_lock, bool create, bool force) override {} + void session_end() override {} + void load(QofBook*, QofBackendLoadType) override {} + void sync(QofBook* book) override {} + void safe_sync(QofBook* book) override {} + void begin(QofInstance* inst) override { + g_assert(inst); + g_assert(QOF_IS_INSTANCE(inst)); + commit_test.m_inst = inst; + } + void set_error(QofBackendError err) { + QofBackend::set_error(err); + commit_test.m_err = err; + } + void commit(QofInstance* inst) override { + g_assert( inst ); + g_assert( QOF_IS_INSTANCE( inst ) ); + g_assert( commit_test.m_inst == inst ); + g_assert( commit_test.m_be == this ); + commit_test.m_commit_called = true; + set_error(m_qof_error); + + } + void rollback(QofInstance* inst) override {} + void inject_error(QofBackendError err) { + m_qof_error = err; + } +private: + QofBackendError m_qof_error; + +}; + typedef struct { QofInstance *inst; } Fixture; +static void +on_error(QofInstance* inst, QofBackendError err) { + g_assert( inst ); + g_assert( QOF_IS_INSTANCE( inst ) ); + g_assert( commit_test.m_err == err ); + commit_test.m_on_error_called = true; +} +static void +on_done(QofInstance* inst) { + g_assert( inst ); + g_assert( QOF_IS_INSTANCE( inst ) ); + g_assert( commit_test.m_inst == inst ); + commit_test.m_on_done_called = true; +} +static void +on_free(QofInstance* inst) { + g_assert( inst ); + g_assert( QOF_IS_INSTANCE( inst ) ); + g_assert(commit_test.m_inst == inst ); + commit_test.m_on_free_called = true; +} + /* use g_free on error_message after this function been called */ static gboolean fatal_handler ( const char * log_domain, @@ -423,7 +498,6 @@ mock_backend_begin( QofBackend *be, QofInstance *inst ) { g_assert( be ); g_assert( inst ); - g_assert( be->begin == mock_backend_begin ); g_assert_cmpstr( inst->e_type, == , "test type" ); is_called = TRUE; } @@ -431,14 +505,12 @@ mock_backend_begin( QofBackend *be, QofInstance *inst ) static void test_instance_begin_edit( Fixture *fixture, gconstpointer pData ) { - QofBackend *be; QofBook *book; gboolean result; /* setup */ - be = g_new0( QofBackend, 1 ); + auto be = new MockBackend; g_assert( be ); - qof_backend_init( be ); book = qof_book_new(); g_assert( book ); g_assert( QOF_IS_BOOK( book ) ); @@ -467,22 +539,19 @@ test_instance_begin_edit( Fixture *fixture, gconstpointer pData ) g_test_message( "Test when instance's editlevel is <= 0 and backend is set" ); result = FALSE; - is_called = FALSE; qof_instance_reset_editlevel( fixture->inst ); qof_instance_set_dirty_flag( fixture->inst, FALSE ); qof_instance_set_book( fixture->inst, book ); - be->begin = mock_backend_begin; result = qof_begin_edit( fixture->inst ); g_assert( result == TRUE ); g_assert_cmpint( qof_instance_get_editlevel( fixture->inst ), == , 1 ); g_assert( qof_instance_get_dirty_flag( fixture->inst ) == FALSE ); - g_assert( is_called ); /* clean up */ qof_book_set_backend( book, NULL ); qof_book_destroy( book ); - qof_backend_destroy( be ); - g_free( be ); + delete be; + } static void @@ -527,82 +596,16 @@ test_instance_commit_edit( Fixture *fixture, gconstpointer pData ) /* backend commit test start */ -static struct -{ - gpointer inst; - gpointer be; - - gboolean commit_called; - gboolean commit_with_err_called; - gboolean on_error_called; - gboolean on_free_called; - gboolean on_done_called; - QofBackendError err; -} commit_test_part2; - -static void -mock_backend_commit( QofBackend *be, QofInstance *inst ) -{ - g_assert( inst ); - g_assert( be ); - g_assert( QOF_IS_INSTANCE( inst ) ); - g_assert( commit_test_part2.inst == inst ); - g_assert( commit_test_part2.be == be ); - commit_test_part2.commit_called = TRUE; -} - -static void -mock_backend_commit_with_error( QofBackend *be, QofInstance *inst ) -{ - g_assert( inst ); - g_assert( be ); - g_assert( QOF_IS_INSTANCE( inst ) ); - g_assert( commit_test_part2.inst == inst ); - g_assert( commit_test_part2.be == be ); - qof_backend_set_error( be, ERR_BACKEND_NO_HANDLER ); - commit_test_part2.err = ERR_BACKEND_NO_HANDLER; - commit_test_part2.commit_with_err_called = TRUE; -} - - -static void -mock_on_error( QofInstance *inst, QofBackendError be_error ) -{ - g_assert( inst ); - g_assert( QOF_IS_INSTANCE( inst ) ); - g_assert( commit_test_part2.err == be_error ); - commit_test_part2.on_error_called = TRUE; -} - -static void -mock_on_done( QofInstance *inst ) -{ - g_assert( inst ); - g_assert( QOF_IS_INSTANCE( inst ) ); - g_assert( commit_test_part2.inst == inst ); - commit_test_part2.on_done_called = TRUE; -} - -static void -mock_on_free( QofInstance *inst ) -{ - g_assert( inst ); - g_assert( QOF_IS_INSTANCE( inst ) ); - g_assert( commit_test_part2.inst == inst ); - commit_test_part2.on_free_called = TRUE; -} static void test_instance_commit_edit_part2( Fixture *fixture, gconstpointer pData ) { - QofBackend *be; QofBook *book; gboolean result; /* setup */ - be = g_new0( QofBackend, 1 ); + auto be = new MockBackend; g_assert( be ); - qof_backend_init( be ); book = qof_book_new(); g_assert( book ); g_assert( QOF_IS_BOOK( book ) ); @@ -610,13 +613,13 @@ test_instance_commit_edit_part2( Fixture *fixture, gconstpointer pData ) /* init */ result = FALSE; - commit_test_part2.commit_called = FALSE; - commit_test_part2.commit_with_err_called = FALSE; - commit_test_part2.on_error_called = FALSE; - commit_test_part2.on_free_called = FALSE; - commit_test_part2.on_done_called = FALSE; - commit_test_part2.inst = fixture->inst; - commit_test_part2.be = be; + commit_test.m_commit_called = false; + commit_test.m_commit_with_err_called = false; + commit_test.m_on_error_called = false; + commit_test.m_on_free_called = false; + commit_test.m_on_done_called = false; + commit_test.m_inst = fixture->inst; + commit_test.m_be = be; qof_instance_set_dirty_flag( fixture->inst, TRUE ); g_test_message( "Test when instance's backend not set, callbacks not set" ); @@ -626,70 +629,67 @@ test_instance_commit_edit_part2( Fixture *fixture, gconstpointer pData ) g_assert( result ); g_assert( qof_instance_get_dirty_flag( fixture->inst ) ); g_assert( !qof_instance_get_infant( fixture->inst ) ); - g_assert( !commit_test_part2.commit_called ); - g_assert( !commit_test_part2.commit_with_err_called ); - g_assert( !commit_test_part2.on_error_called ); - g_assert( !commit_test_part2.on_free_called ); - g_assert( !commit_test_part2.on_done_called ); + g_assert( !commit_test.m_commit_called ); + g_assert( !commit_test.m_commit_with_err_called ); + g_assert( !commit_test.m_on_error_called ); + g_assert( !commit_test.m_on_free_called ); + g_assert( !commit_test.m_on_done_called ); - g_test_message( "Test when instance's backend not set, do_free is true" ); + g_test_message( "Test when instance's backend not set, do_free is false" ); qof_instance_set_destroying( fixture->inst, TRUE ); - result = qof_commit_edit_part2( fixture->inst, mock_on_error, mock_on_done, mock_on_free ); + result = qof_commit_edit_part2( fixture->inst, on_error, on_done, on_free ); g_assert( result ); g_assert( qof_instance_get_dirty_flag( fixture->inst ) ); - g_assert( !commit_test_part2.commit_called ); - g_assert( !commit_test_part2.commit_with_err_called ); - g_assert( !commit_test_part2.on_error_called ); - g_assert( commit_test_part2.on_free_called ); - g_assert( !commit_test_part2.on_done_called ); + g_assert( !commit_test.m_commit_called ); + g_assert( !commit_test.m_commit_with_err_called ); + g_assert( !commit_test.m_on_error_called ); + g_assert( commit_test.m_on_free_called ); + g_assert( !commit_test.m_on_done_called ); g_test_message( "Test when instance's backend not set, do_free is false" ); qof_instance_set_destroying( fixture->inst, FALSE ); - commit_test_part2.on_free_called = FALSE; - result = qof_commit_edit_part2( fixture->inst, mock_on_error, mock_on_done, mock_on_free ); + commit_test.m_on_free_called = false; + result = qof_commit_edit_part2( fixture->inst, on_error, on_done, on_free ); g_assert( result ); g_assert( qof_instance_get_dirty_flag( fixture->inst ) ); - g_assert( !commit_test_part2.commit_called ); - g_assert( !commit_test_part2.commit_with_err_called ); - g_assert( !commit_test_part2.on_error_called ); - g_assert( !commit_test_part2.on_free_called ); - g_assert( commit_test_part2.on_done_called ); + g_assert( !commit_test.m_commit_called ); + g_assert( !commit_test.m_commit_with_err_called ); + g_assert( !commit_test.m_on_error_called ); + g_assert( !commit_test.m_on_free_called ); + g_assert( commit_test.m_on_done_called ); g_test_message( "Test when instance's backend is set, all cb set, no error produced" ); qof_instance_set_book( fixture->inst, book ); qof_instance_set_destroying( fixture->inst, FALSE ); - commit_test_part2.on_done_called = FALSE; - be->commit = mock_backend_commit; - result = qof_commit_edit_part2( fixture->inst, mock_on_error, mock_on_done, mock_on_free ); + commit_test.m_on_done_called = false; + result = qof_commit_edit_part2( fixture->inst, on_error, on_done, on_free ); g_assert( result ); g_assert( !qof_instance_get_dirty_flag( fixture->inst ) ); - g_assert( commit_test_part2.commit_called ); - g_assert( !commit_test_part2.commit_with_err_called ); - g_assert( !commit_test_part2.on_error_called ); - g_assert( !commit_test_part2.on_free_called ); - g_assert( commit_test_part2.on_done_called ); + g_assert( commit_test.m_commit_called ); + g_assert( !commit_test.m_commit_with_err_called ); + g_assert( !commit_test.m_on_error_called ); + g_assert( !commit_test.m_on_free_called ); + g_assert( commit_test.m_on_done_called ); g_test_message( "Test when instance's backend is set, all cb set, error produced" ); - commit_test_part2.commit_called = FALSE; - commit_test_part2.on_done_called = FALSE; - be->commit = mock_backend_commit_with_error; + commit_test.m_commit_called = false; + commit_test.m_on_done_called = false; + be->inject_error(ERR_BACKEND_NO_HANDLER); qof_instance_set_dirty_flag( fixture->inst, TRUE ); qof_instance_set_destroying( fixture->inst, TRUE ); - result = qof_commit_edit_part2( fixture->inst, mock_on_error, mock_on_done, mock_on_free ); + result = qof_commit_edit_part2( fixture->inst, on_error, on_done, on_free ); g_assert( !result ); g_assert( qof_instance_get_dirty_flag( fixture->inst ) ); g_assert( !qof_instance_get_destroying( fixture->inst ) ); - g_assert( !commit_test_part2.commit_called ); - g_assert( commit_test_part2.commit_with_err_called ); - g_assert( commit_test_part2.on_error_called ); - g_assert( !commit_test_part2.on_free_called ); - g_assert( !commit_test_part2.on_done_called ); + g_assert( commit_test.m_commit_called ); + g_assert( commit_test.m_on_error_called ); + g_assert( !commit_test.m_on_free_called ); + g_assert( !commit_test.m_on_done_called ); /* clean up */ qof_book_set_backend( book, NULL ); qof_book_destroy( book ); - qof_backend_destroy( be ); - g_free( be ); + delete be; } /* backend commit test end */ diff --git a/src/libqof/qof/test/test-qofsession.cpp b/src/libqof/qof/test/test-qofsession.cpp index 7a30cde46e..863316d864 100644 --- a/src/libqof/qof/test/test-qofsession.cpp +++ b/src/libqof/qof/test/test-qofsession.cpp @@ -25,7 +25,7 @@ #include #include "../guid.hpp" #include -#include "qofbackend-p.h" +#include #include #include "../gnc-backend-prov.hpp" @@ -35,51 +35,51 @@ static bool sync_called {false}; static bool load_error {true}; static bool hook_called {false}; static bool data_loaded {false}; + +class MockBackend : public QofBackend +{ +public: + MockBackend() = default; + MockBackend(const MockBackend&) = delete; + MockBackend(const MockBackend&&) = delete; + virtual ~MockBackend() = default; + void session_begin(QofSession*, const char*, bool, bool, bool) {} + void session_end() {} + void load(QofBook*, QofBackendLoadType); + void sync(QofBook*); + void safe_sync(QofBook*); + void export_coa(QofBook*); +}; + void example_hook (QofSession & session) { hook_called = true; } -void test_load (QofBackend * be, QofBook *, QofBackendLoadType) +void MockBackend::load (QofBook *, QofBackendLoadType) { - if (load_error) be->last_err = ERR_BACKEND_NO_BACKEND; + if (load_error) set_error(ERR_BACKEND_NO_BACKEND); data_loaded = true; } -void test_safe_sync (QofBackend *, QofBook *) +void MockBackend::safe_sync (QofBook *) { safe_sync_called = true; } -void test_sync (QofBackend *, QofBook *) +void MockBackend::sync (QofBook *) { sync_called = true; } -void test_export_fn (QofBackend *, QofBook * book) +void MockBackend::export_coa(QofBook * book) { exported_book = book; } QofBackend * test_backend_factory () { - QofBackend * ret = (QofBackend*) std::malloc (sizeof (QofBackend)); - ret->session_begin = nullptr; - ret->session_end = nullptr; - ret->destroy_backend = nullptr; - ret->load = &test_load; - ret->sync = &test_sync; - ret->safe_sync = &test_safe_sync; - ret->export_fn = &test_export_fn; - ret->error_msg = nullptr; - ret->fullpath = nullptr; - ret->last_err = ERR_BACKEND_NO_ERR; - ret->begin = nullptr; - ret->commit = nullptr; - ret->rollback = nullptr; - ret->percentage = nullptr; - ret->config_count = 0; - return ret; + return new MockBackend; } struct MockProvider : public QofBackendProvider