Convert QofBackend to a C++ class and the backend class hierarchy into C++.

Getting rid of all of the casting and different flavors of backend pointers
and adopting the C++ member-function calling conventions.
This commit is contained in:
John Ralls 2016-11-28 10:27:09 -08:00
parent 06af7d794f
commit abb66016bc
23 changed files with 763 additions and 1120 deletions

View File

@ -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 <DbType T> void gnc_dbi_session_begin(QofBackend* qof_be,
QofSession* session,
const char* book_id,
gboolean ignore_lock,
gboolean create, gboolean force);
template <DbType Type> QofBackend*
new_backend ()
{
auto dbi_be = new GncDbiBackend(nullptr, nullptr);
assert (dbi_be != nullptr);
QofBackend* qof_be = reinterpret_cast<decltype(qof_be)>(dbi_be);
qof_backend_init (qof_be);
qof_be->session_begin = gnc_dbi_session_begin<Type>;
init_sql_backend (dbi_be);
return qof_be;
}
template <DbType T>
template <DbType Type>
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<T>(); }
QofBackend* create_backend(void)
{
return new GncDbiBackend<Type>(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 <DbType Type> bool
GncDbiBackend<Type>::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 <DbType Type> void error_handler(void* conn, void* data);
void error_handler(void* conn, void* data);
template <DbType Type> dbi_conn
conn_setup (QofBackend* qof_be, PairVec& options,
UriStrings& uri)
GncDbiBackend<Type>::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<Type>, qof_be);
dbi_conn_error_handler (conn, error_handler<Type>, 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 <DbType Type>bool
GncDbiBackend<Type>::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<DbType::DBI_SQLITE> (dbi_conn conn, void* user_data)
{
const char* msg;
GncDbiBackend *dbi_be = static_cast<decltype(dbi_be)>(user_data);
GncDbiBackend<DbType::DBI_SQLITE> *dbi_be =
static_cast<decltype(dbi_be)>(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<DbType::DBI_SQLITE>(QofBackend* qof_be,
QofSession* session,
const char* book_id,
gboolean ignore_lock,
gboolean create, gboolean force)
GncDbiBackend<DbType::DBI_SQLITE>::session_begin(QofSession* session,
const char* book_id,
bool ignore_lock,
bool create, bool force)
{
GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(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<DbType::DBI_SQLITE>(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<DbType::DBI_SQLITE>(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<DbType::DBI_SQLITE>(QofBackend* qof_be,
if (basename != nullptr) g_free (basename);
if (dirname != nullptr) g_free (dirname);
UriStrings uri;
auto conn = conn_setup<DbType::DBI_SQLITE>(qof_be, options, uri);
auto conn = conn_setup(options, uri);
if (conn == nullptr)
{
LEAVE("Error");
@ -454,12 +429,12 @@ gnc_dbi_session_begin<DbType::DBI_SQLITE>(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<DbType::DBI_SQLITE>(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<DbType::DBI_SQLITE>(QofBackend* qof_be,
template <> void
error_handler<DbType::DBI_MYSQL> (dbi_conn conn, void* user_data)
{
GncDbiBackend* dbi_be = static_cast<decltype(dbi_be)>(user_data);
GncDbiBackend<DbType::DBI_MYSQL>* dbi_be =
static_cast<decltype(dbi_be)>(user_data);
const char* msg;
auto err_num = dbi_conn_error (conn, &msg);
@ -531,18 +507,18 @@ error_handler<DbType::DBI_MYSQL> (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 <DbType T> void
gnc_dbi_session_begin (QofBackend* qof_be, QofSession* session,
const char* book_id, gboolean ignore_lock,
gboolean create, gboolean force)
template <DbType Type> void
GncDbiBackend<Type>::session_begin (QofSession* session, const char* book_id,
bool ignore_lock, bool create, bool force)
{
GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(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<T>(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<T>(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<DbType::DBI_PGSQL> (dbi_conn conn, void* user_data)
{
GncDbiBackend* dbi_be = static_cast<decltype(dbi_be)>(user_data);
GncDbiBackend<DbType::DBI_PGSQL>* dbi_be =
static_cast<decltype(dbi_be)>(user_data);
const char* msg;
(void)dbi_conn_error (conn, &msg);
@ -764,7 +739,7 @@ error_handler<DbType::DBI_PGSQL> (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<DbType::DBI_PGSQL> (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<DbType::DBI_PGSQL> (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 <DbType Type> void
GncDbiBackend<Type>::session_end ()
{
GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(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 <DbType Type>
GncDbiBackend<Type>::~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 <DbType Type> void
GncDbiBackend<Type>::load (QofBook* book, QofBackendLoadType loadType)
{
GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(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,17 +856,14 @@ 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 <DbType Type> void
GncDbiBackend<Type>::safe_sync (QofBook* book)
{
GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(qof_be);
auto conn = dynamic_cast<GncDbiSqlConnection*>(dbi_be->m_conn);
auto conn = dynamic_cast<GncDbiSqlConnection*>(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, m_book);
@ -921,12 +880,11 @@ gnc_dbi_safe_sync_all (QofBackend* qof_be, QofBook* book)
set_error (ERR_BACKEND_SERVER_ERR);
set_message("Failed to drop indexes");
LEAVE ("Failed to drop indexes");
return;
}
return;
}
dbi_be->sync_all(book);
if (qof_backend_check_error (qof_be))
sync(m_book);
if (check_error())
{
conn->table_operation (TableOpType::rollback);
LEAVE ("Failed to create new database tables");
@ -936,63 +894,6 @@ gnc_dbi_safe_sync_all (QofBackend* qof_be, QofBook* book)
LEAVE ("book=%p", m_book);
}
/* ================================================================= */
static void
gnc_dbi_begin_edit (QofBackend* qof_be, QofInstance* inst)
{
GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(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<decltype(dbi_be)>(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<decltype(dbi_be)>(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<decltype(qof_be)>(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
@ -1254,8 +1155,8 @@ dbi_library_test (dbi_conn conn)
return retval;
}
static bool
conn_test_dbi_library(dbi_conn conn, QofBackend* qof_be)
template <DbType Type> bool
GncDbiBackend<Type>::conn_test_dbi_library(dbi_conn conn)
{
auto result = dbi_library_test (conn);
switch (result)
@ -1264,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;

View File

@ -84,14 +84,22 @@ enum class DbType
/**
* Implementations of GncSqlBackend.
*/
struct UriStrings;
template <DbType Type>
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&);

View File

@ -124,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);
@ -147,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);
@ -165,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);
@ -179,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);
@ -189,8 +189,6 @@ GncDbiSqlConnection::lock_database (bool ignore_lock)
void
GncDbiSqlConnection::unlock_database ()
{
GncDbiBackend* qe = reinterpret_cast<decltype(qe)>(m_qbe);
if (m_conn == nullptr) return;
g_return_if_fail (dbi_conn_error (m_conn, nullptr) == 0);
@ -225,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)
{
@ -262,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()

View File

@ -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;
}

View File

@ -62,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.
@ -157,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.
*
@ -241,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 */

View File

@ -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<decltype (loglevel)>
(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<decltype (inst)> (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

View File

@ -108,42 +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 void
xml_session_begin (QofBackend* qof_be, QofSession* session,
const char* book_id, gboolean ignore_lock,
gboolean create, gboolean force)
{
GncXmlBackend* xml_be = (GncXmlBackend*) qof_be;
ENTER (" ");
xml_be->session_begin(session, book_id, ignore_lock, create, force);
LEAVE (" ");
return;
}
/* ================================================================= */
static void
xml_session_end (QofBackend* qof_be)
{
GncXmlBackend* xml_be = (GncXmlBackend*)qof_be;
ENTER (" ");
xml_be->session_end();
LEAVE (" ");
}
static void
xml_destroy_backend (QofBackend* qof_be)
{
delete reinterpret_cast<GncXmlBackend*>(qof_be);
}
bool
QofXmlBackendProvider::type_check (const char *uri)
{
@ -201,86 +170,8 @@ det_exit:
return result;
}
static void
xml_sync_all (QofBackend* qof_be, QofBook* book)
{
GncXmlBackend* xml_be = reinterpret_cast<decltype(xml_be)>(qof_be);
xml_be->sync(book);
ENTER ("book=%p, xml_be->m_book=%p", book, xml_be->get_book());
LEAVE ("book=%p", book);
}
static void
xml_begin_edit (QofBackend* qof_be, QofInstance* inst)
{
GncXmlBackend* xml_be = (GncXmlBackend*) qof_be;
xml_be->begin(inst);
}
static void
xml_rollback_edit (QofBackend* qof_be, QofInstance* inst)
{
GncXmlBackend* xml_be = (GncXmlBackend*) qof_be;
xml_be->rollback(inst);
}
/* ---------------------------------------------------------------------- */
/* 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* qof_be, QofBook* book,
QofBackendLoadType loadType)
{
GncXmlBackend* xml_be = (GncXmlBackend*) qof_be;
xml_be->load(book, loadType);
}
/* ---------------------------------------------------------------------- */
static void
gnc_xml_be_write_accounts_to_file (QofBackend* qof_be, QofBook* book)
{
auto datafile = ((GncXmlBackend*)qof_be)->get_filename();
gnc_book_write_accounts_to_xml_file_v2 (qof_be, book, datafile);
}
/* ================================================================= */
QofBackend*
QofXmlBackendProvider::create_backend(void)
{
auto xml_be = new GncXmlBackend;
auto qof_be = xml_be->get_qof_be();
qof_be->session_begin = xml_session_begin;
qof_be->session_end = xml_session_end;
qof_be->destroy_backend = xml_destroy_backend;
qof_be->load = gnc_xml_be_load_from_file;
/* The file backend treats accounting periods transactionally. */
qof_be->begin = xml_begin_edit;
qof_be->commit = NULL;
qof_be->rollback = xml_rollback_edit;
qof_be->sync = xml_sync_all;
qof_be->export_fn = gnc_xml_be_write_accounts_to_file;
return qof_be;
}
static void
business_core_xml_init (void)
{

View File

@ -23,6 +23,7 @@ extern "C"
#include <windows.h>
#endif
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
@ -48,21 +49,8 @@ extern "C"
#define FILE_URI_PREFIX "file://"
static QofLogModule log_module = GNC_MOD_BACKEND;
GncXmlBackend::GncXmlBackend()
{
memset(&qof_be, 0, sizeof(qof_be));
qof_backend_init(&qof_be);
}
GncXmlBackend::~GncXmlBackend()
{
/* Stop transaction logging */
xaccLogSetBaseName (NULL);
qof_backend_destroy (&qof_be);
}
bool
check_path (const char* fullpath, QofBackend* qof_be, bool create)
GncXmlBackend::check_path (const char* fullpath, bool create)
{
struct stat statbuf;
char* dirname = g_path_get_dirname (fullpath);
@ -78,9 +66,9 @@ check_path (const char* fullpath, QofBackend* qof_be, bool create)
{
/* Error on stat or if it isn't a directory means we
cannot find this filename */
qof_backend_set_error (qof_be, ERR_FILEIO_FILE_NOT_FOUND);
qof_backend_set_message (qof_be, "Couldn't find directory for %s",
fullpath);
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;
@ -91,8 +79,9 @@ check_path (const char* fullpath, QofBackend* qof_be, bool create)
if ((rc != 0) && (!create))
{
/* Error on stat means the file doesn't exist */
qof_backend_set_error (qof_be, ERR_FILEIO_FILE_NOT_FOUND);
qof_backend_set_message (qof_be, "Couldn't find %s", fullpath);
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;
@ -105,8 +94,10 @@ check_path (const char* fullpath, QofBackend* qof_be, bool create)
#endif
)
{
qof_backend_set_error (qof_be, ERR_FILEIO_UNKNOWN_FILE_TYPE);
qof_backend_set_message (qof_be, "Path %s is a directory", fullpath);
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;
@ -123,20 +114,19 @@ GncXmlBackend::session_begin(QofSession* session, const char* book_id,
if (m_fullpath.empty())
{
qof_backend_set_error (&qof_be, ERR_FILEIO_FILE_NOT_FOUND);
qof_backend_set_message (&qof_be, "No path specified");
set_error(ERR_FILEIO_FILE_NOT_FOUND);
set_message("No path specified");
return;
}
if (create && !force && save_may_clobber_data())
{
qof_backend_set_error (&qof_be, ERR_BACKEND_STORE_EXISTS);
set_error(ERR_BACKEND_STORE_EXISTS);
PWARN ("Might clobber, no force");
return;
}
if (!check_path(m_fullpath.c_str(), &qof_be, create))
if (!check_path(m_fullpath.c_str(), create))
return;
qof_be.fullpath = const_cast<char*>(m_fullpath.c_str());
m_dirname = g_path_get_dirname (m_fullpath.c_str());
@ -159,7 +149,7 @@ GncXmlBackend::session_begin(QofSession* session, const char* book_id,
if (force)
{
QofBackendError berror = qof_backend_get_error (&qof_be);
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
@ -169,7 +159,7 @@ GncXmlBackend::session_begin(QofSession* session, const char* book_id,
else
{
// Unknown error. Push it again on the error stack.
qof_backend_set_error (&qof_be, berror);
set_error(berror);
}
}
}
@ -181,7 +171,7 @@ GncXmlBackend::session_end()
{
if (m_book && qof_book_is_readonly (m_book))
{
qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY);
set_error(ERR_BACKEND_READONLY);
return;
}
@ -307,7 +297,7 @@ GncXmlBackend::load(QofBook* book, QofBackendLoadType loadType)
if (error != ERR_BACKEND_NO_ERR)
{
qof_backend_set_error (&qof_be, error);
set_error(error);
}
/* We just got done loading, it can't possibly be dirty !! */
@ -329,7 +319,7 @@ GncXmlBackend::sync(QofBook* book)
if (qof_book_is_readonly (m_book))
{
/* Are we read-only? Don't continue in this case. */
qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY);
set_error(ERR_BACKEND_READONLY);
return;
}
@ -348,6 +338,20 @@ GncXmlBackend::save_may_clobber_data()
}
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)
{
@ -358,7 +362,7 @@ GncXmlBackend::write_to_file (bool make_backup)
if (m_book && qof_book_is_readonly (m_book))
{
/* Are we read-only? Don't continue in this case. */
qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY);
set_error(ERR_BACKEND_READONLY);
LEAVE ("");
return FALSE;
}
@ -374,8 +378,8 @@ GncXmlBackend::write_to_file (bool make_backup)
if (!mktemp (tmp_name))
{
qof_backend_set_error (&qof_be, ERR_BACKEND_MISC);
qof_backend_set_message (&qof_be, "Failed to make temp file");
set_error(ERR_BACKEND_MISC);
set_message("Failed to make temp file");
LEAVE ("");
return FALSE;
}
@ -403,8 +407,8 @@ GncXmlBackend::write_to_file (bool make_backup)
/* Use the permissions from the original data file */
if (g_chmod (tmp_name, statbuf.st_mode) != 0)
{
/* qof_backend_set_error(&qof_be, ERR_BACKEND_PERM); */
/* qof_backend_set_message(&qof_be, "Failed to chmod filename %s", tmp_name ); */
/* 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
@ -423,8 +427,8 @@ GncXmlBackend::write_to_file (bool make_backup)
that. */
if (chown (tmp_name, -1, statbuf.st_gid) != 0)
{
/* qof_backend_set_error(&qof_be, ERR_BACKEND_PERM); */
/* qof_backend_set_message(&qof_be, "Failed to chown filename %s", tmp_name ); */
/* 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",
@ -439,7 +443,7 @@ GncXmlBackend::write_to_file (bool make_backup)
}
if (g_unlink (m_fullpath.c_str()) != 0 && errno != ENOENT)
{
qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY);
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) : "");
@ -449,16 +453,16 @@ GncXmlBackend::write_to_file (bool make_backup)
}
if (!link_or_make_backup (tmp_name, m_fullpath))
{
qof_backend_set_error (&qof_be, ERR_FILEIO_BACKUP_ERROR);
qof_backend_set_message (&qof_be, "Failed to make backup file %s",
m_fullpath.empty() ? "NULL" : m_fullpath.c_str());
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)
{
qof_backend_set_error (&qof_be, ERR_BACKEND_PERM);
set_error(ERR_BACKEND_PERM);
PWARN ("unable to unlink temp filename %s: %s",
tmp_name ? tmp_name : "(null)",
g_strerror (errno) ? g_strerror (errno) : "");
@ -492,7 +496,7 @@ GncXmlBackend::write_to_file (bool make_backup)
be_err = ERR_BACKEND_MISC;
break;
}
qof_backend_set_error (&qof_be, be_err);
set_error(be_err);
PWARN ("unable to unlink temp_filename %s: %s",
tmp_name ? tmp_name : "(null)",
g_strerror (errno) ? g_strerror (errno) : "");
@ -501,9 +505,9 @@ GncXmlBackend::write_to_file (bool make_backup)
else
{
/* Use a generic write error code */
qof_backend_set_error (&qof_be, ERR_FILEIO_WRITE_ERROR);
qof_backend_set_message (&qof_be, "Unable to write to temp file %s",
tmp_name ? tmp_name : "NULL");
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 ("");
@ -519,8 +523,8 @@ 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;
ssize_t count_read;
ssize_t count_write = 0;
ssize_t count_read = 0;
#ifdef G_OS_WIN32
@ -542,7 +546,7 @@ copy_file (const std::string& orig, const std::string& bkup)
do
{
auto count_read = read (orig_fd, buf, buf_size);
count_read = read (orig_fd, buf, buf_size);
if (count_read == -1 && errno != EINTR)
{
close (orig_fd);
@ -602,7 +606,7 @@ GncXmlBackend::link_or_make_backup (const std::string& orig,
if (!copy_success)
{
qof_backend_set_error (&qof_be, ERR_FILEIO_BACKUP_ERROR);
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;
@ -626,7 +630,7 @@ GncXmlBackend::get_file_lock ()
if (!rc)
{
/* oops .. file is locked by another user .. */
qof_backend_set_error (&qof_be, ERR_BACKEND_LOCKED);
set_error(ERR_BACKEND_LOCKED);
return false;
}
@ -648,7 +652,7 @@ GncXmlBackend::get_file_lock ()
be_err = ERR_BACKEND_LOCKED;
break;
}
qof_backend_set_error (&qof_be, be_err);
set_error(be_err);
return false;
}
@ -693,7 +697,7 @@ GncXmlBackend::get_file_lock ()
}
/* Otherwise, something else is wrong. */
qof_backend_set_error (&qof_be, ERR_BACKEND_LOCKED);
set_error(ERR_BACKEND_LOCKED);
g_unlink (linkfile.str().c_str());
close (m_lockfd);
g_unlink (m_lockfile.c_str());
@ -704,9 +708,9 @@ GncXmlBackend::get_file_lock ()
if (rc)
{
/* oops .. stat failed! This can't happen! */
qof_backend_set_error (&qof_be, ERR_BACKEND_LOCKED);
qof_backend_set_message (&qof_be, "Failed to stat lockfile %s",
m_lockfile.c_str());
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());
@ -715,7 +719,7 @@ GncXmlBackend::get_file_lock ()
if (statbuf.st_nlink != 2)
{
qof_backend_set_error (&qof_be, ERR_BACKEND_LOCKED);
set_error(ERR_BACKEND_LOCKED);
g_unlink (linkfile.str().c_str());
close (m_lockfd);
g_unlink (m_lockfile.c_str());

View File

@ -26,28 +26,26 @@ extern "C"
#include <string>
#include <qof-backend.hpp>
class GncXmlBackend
class GncXmlBackend : public QofBackend
{
public:
GncXmlBackend();
GncXmlBackend() = default;
GncXmlBackend(const GncXmlBackend&) = delete;
GncXmlBackend operator=(const GncXmlBackend&) = delete;
GncXmlBackend(const GncXmlBackend&&) = delete;
GncXmlBackend operator=(const GncXmlBackend&&) = delete;
~GncXmlBackend();
~GncXmlBackend() = default;
void session_begin(QofSession* session, const char* book_id,
bool ignore_lock, bool create, bool force);
void session_end();
void load(QofBook* book, QofBackendLoadType loadType);
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 begin(QofInstance* inst) {}
void commit(QofInstance* inst) {}
void rollback(QofInstance* inst) {}
void sync(QofBook* book);
QofBackend* get_qof_be() { return &qof_be; }
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();
@ -56,10 +54,9 @@ private:
bool write_to_file(bool make_backup);
void remove_old_files();
void write_accounts(QofBook* book);
QofBackend qof_be;
bool check_path(const char* fullpath, bool create);
std::string m_dirname;
std::string m_fullpath; /* Fully qualified path to book */
std::string m_lockfile;
std::string m_linkfile;
int m_lockfd;

View File

@ -698,7 +698,6 @@ qof_session_load_from_xml_file_v2_full (
QofBookFileType type)
{
Account* root;
QofBackend* qof_be = reinterpret_cast<decltype(qof_be)>(xml_be);
sixtp_gdv2* gd;
sixtp* top_parser;
sixtp* main_parser;
@ -707,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, qof_be->percentage);
gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback,
xml_be->get_percentage());
top_parser = sixtp_new ();
main_parser = sixtp_new ();
@ -1340,7 +1340,8 @@ gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out)
return FALSE;
qof_be = qof_book_get_backend (book);
gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, qof_be->percentage);
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 +
@ -1386,7 +1387,8 @@ gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* qof_be, QofBook* book,
|| !write_counts (out, "commodity", ncom, "account", nacc, NULL))
return FALSE;
gd = gnc_sixtp_gdv2_new (book, TRUE, file_rw_feedback, qof_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;
@ -1651,10 +1653,8 @@ gnc_book_write_to_xml_file_v2 (
* postgress or anything else.
*/
gboolean
gnc_book_write_accounts_to_xml_file_v2 (
QofBackend* qof_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;
@ -1671,7 +1671,7 @@ gnc_book_write_accounts_to_xml_file_v2 (
if (out && fclose (out))
success = FALSE;
if (!success && !qof_backend_check_error (qof_be))
if (!success && !qof_be->check_error())
{
/* Use a generic write error code */

View File

@ -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"

View File

@ -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[] =

View File

@ -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"

View File

@ -52,7 +52,7 @@ extern "C"
#include "../sixtp-parsers.h"
#include "../sixtp-dom-parsers.h"
#include "../io-gncxml-gen.h"
#include <test-file-stuff.h>
#include "test-file-stuff.h"
static QofBook* book;

View File

@ -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<MockBackend*>(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<MockBackend*>(qof_book_get_backend (book));
auto split_00 = static_cast<Split*>(txn->splits->data);
auto split_01 = static_cast<Split*>(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<Split*>(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<MockBackend*>(qof_book_get_backend (qof_instance_get_book (fixture->txn)));
auto loglevel = static_cast<GLogLevelFlags>(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<GncGUID*>(),
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,

View File

@ -25,17 +25,14 @@
extern "C"
{
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <regex.h>
#include <glib.h>
#include <gmodule.h>
#include <errno.h>
#include <config.h>
#include "qof.h"
}
#include <string>
#include <algorithm>
#include <vector>
#include "qof-backend.hpp"
G_GNUC_UNUSED static QofLogModule log_module = QOF_MOD_BACKEND;
@ -47,238 +44,128 @@ G_GNUC_UNUSED static QofLogModule log_module = QOF_MOD_BACKEND;
* error handling *
\********************************************************************/
void
qof_backend_set_error (QofBackend *be, QofBackendError err)
{
if (!be) return;
GModuleVec QofBackend::c_be_registry{};
void
QofBackend::set_error(QofBackendError err)
{
/* use stack-push semantics. Only the earliest error counts */
if (ERR_BACKEND_NO_ERR != be->last_err) return;
be->last_err = err;
if (m_last_err != ERR_BACKEND_NO_ERR) return;
m_last_err = err;
}
QofBackendError
qof_backend_get_error (QofBackend *be)
QofBackend::get_error()
{
QofBackendError err;
if (!be) return ERR_BACKEND_NO_BACKEND;
/* use 'stack-pop' semantics */
err = be->last_err;
be->last_err = ERR_BACKEND_NO_ERR;
auto err = m_last_err;
m_last_err = ERR_BACKEND_NO_ERR;
return err;
}
gboolean
qof_backend_check_error (QofBackend *be)
bool
QofBackend::check_error()
{
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;
return m_last_err != ERR_BACKEND_NO_ERR;
}
void
qof_backend_rollback_instance (QofBackend* be, QofInstance* inst)
QofBackend::set_message (std::string&& msg)
{
if (be == nullptr || be->rollback == nullptr) return;
(be->rollback)(be, inst);
m_error_msg = msg;
}
void
qof_backend_set_message (QofBackend *be, const char *format, ...)
const std::string&&
QofBackend::get_message ()
{
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;
return std::move(m_error_msg);
}
char *
qof_backend_get_message (QofBackend *be)
bool
QofBackend::register_backend(const char* directory, const char* module_name)
{
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)
if (!g_module_supported ())
{
return;
PWARN("Modules not supported.");
return false;
}
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);
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)
{
gchar *modname = g_strdup_printf ("lib%s.dylib", module_name);
auto 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);
auto 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;
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<void**>(&module_init_func)))
reinterpret_cast<void**>(&module_init_func)))
module_init_func ();
g_module_make_resident (backend);
backend_module_list = g_slist_prepend (backend_module_list, 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<void**>(&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)
{
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<void**>(&module_finalize_func)))
module_finalize_func();
}
QofBackend::release_backends();
}
/************************* END OF FILE ********************************/

View File

@ -1,5 +1,6 @@
/********************************************************************\
* qofbackend-p.h -- private api for data storage backend *
* qof-backend.hpp Declare QofBackend class *
* Copyright 2016 John Ralls <jralls@ceridwen.us> *
* *
* 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 <linux@codehelp.co.uk>
@{ */
#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 <gmodule.h>
}
/**
* 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 <string>
#include <algorithm>
#include <vector>
/* 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<GModule*>;
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__ */

View File

@ -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 */
/** @} */
/** @} */

View File

@ -958,7 +958,6 @@ gboolean
qof_begin_edit (QofInstance *inst)
{
QofInstancePrivate *priv;
QofBackend * be;
if (!inst) return FALSE;
@ -968,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;
@ -1002,7 +1001,6 @@ qof_commit_edit_part2(QofInstance *inst,
void (*on_free)(QofInstance *))
{
QofInstancePrivate *priv;
QofBackend * be;
priv = GET_PRIVATE(inst);
@ -1013,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;

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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 */

View File

@ -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