Third interim merge of 'c++-backend'

This commit is contained in:
John Ralls 2016-11-28 12:26:26 -08:00
commit e592e00e10
37 changed files with 1868 additions and 2217 deletions

View File

@ -482,7 +482,7 @@ src/libqof/qof/gnc-timezone.cpp
src/libqof/qof/guid.cpp
src/libqof/qof/kvp_frame.cpp
src/libqof/qof/kvp-value.cpp
src/libqof/qof/qofbackend.cpp
src/libqof/qof/qof-backend.cpp
src/libqof/qof/qofbook.cpp
src/libqof/qof/qofchoice.cpp
src/libqof/qof/qofclass.cpp

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,13 +215,12 @@ 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,
template <DbType Type> bool
GncDbiBackend<Type>::set_standard_connection_options (dbi_conn conn,
const UriStrings& uri)
{
@ -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,
GncDbiBackend<DbType::DBI_SQLITE>::session_begin(QofSession* session,
const char* book_id,
gboolean ignore_lock,
gboolean create, gboolean force)
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,110 +856,44 @@ save_may_clobber_data (dbi_conn conn, const std::string& dbname)
* no errors. If there are errors, drop the new tables and restore the
* originals.
*
* @param qof_be: QofBackend for the session.
* @param book: QofBook to be saved in the database.
*/
void
gnc_dbi_safe_sync_all (QofBackend* qof_be, QofBook* book)
template <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, dbi_be->m_book);
auto table_list = conn->m_provider->get_table_list (conn->conn(), "");
if (!conn->table_operation (table_list, backup))
ENTER ("book=%p, primary=%p", book, m_book);
if (!conn->table_operation (TableOpType::backup))
{
qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR);
conn->table_operation (table_list, rollback);
set_error(ERR_BACKEND_SERVER_ERR);
conn->table_operation (TableOpType::rollback);
LEAVE ("Failed to rename tables");
return;
}
auto index_list = conn->m_provider->get_index_list (conn->m_conn);
for (auto index : index_list)
if (!conn->drop_indexes())
{
const char* errmsg;
conn->m_provider->drop_index (conn->m_conn, index);
if (DBI_ERROR_NONE != dbi_conn_error (conn->m_conn, &errmsg))
{
qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR);
conn->table_operation (table_list, rollback);
LEAVE ("Failed to drop indexes %s", errmsg);
conn->table_operation (TableOpType::rollback);
set_error (ERR_BACKEND_SERVER_ERR);
set_message("Failed to drop indexes");
LEAVE ("Failed to drop indexes");
return;
}
}
dbi_be->sync_all(book);
if (qof_backend_check_error (qof_be))
sync(m_book);
if (check_error())
{
conn->table_operation (table_list, rollback);
conn->table_operation (TableOpType::rollback);
LEAVE ("Failed to create new database tables");
return;
}
conn->table_operation (table_list, drop_backup);
LEAVE ("book=%p", book);
conn->table_operation (TableOpType::drop_backup);
LEAVE ("book=%p", m_book);
}
/* ================================================================= */
static void
gnc_dbi_begin_edit (QofBackend* qof_be, QofInstance* inst)
{
GncDbiBackend* dbi_be = reinterpret_cast<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
@ -1259,8 +1155,8 @@ dbi_library_test (dbi_conn conn)
return retval;
}
static bool
conn_test_dbi_library(dbi_conn conn, QofBackend* qof_be)
template <DbType Type> bool
GncDbiBackend<Type>::conn_test_dbi_library(dbi_conn conn)
{
auto result = dbi_library_test (conn);
switch (result)
@ -1269,15 +1165,13 @@ conn_test_dbi_library(dbi_conn conn, QofBackend* qof_be)
break;
case GNC_DBI_FAIL_SETUP:
qof_backend_set_error (qof_be, ERR_SQL_DBI_UNTESTABLE);
qof_backend_set_message (qof_be,
"DBI library large number test incomplete");
set_error(ERR_SQL_DBI_UNTESTABLE);
set_message ("DBI library large number test incomplete");
break;
case GNC_DBI_FAIL_TEST:
qof_backend_set_error (qof_be, ERR_SQL_BAD_DBI);
qof_backend_set_message (qof_be,
"DBI library fails large number test");
set_error (ERR_SQL_BAD_DBI);
set_message ("DBI library fails large number test");
break;
}
return result == GNC_DBI_PASS;

View File

@ -49,14 +49,14 @@ class GncSqlRow;
* @var rollback drop the name table if it exists and rename name_back to name
* @var drop_backup Drop the backup table
*/
typedef enum
enum TableOpType
{
drop = 0,
empty,
backup,
rollback,
drop_backup
} TableOpType;
};
/**
* Return values from conn_test_dbi_library
@ -84,14 +84,22 @@ enum class DbType
/**
* Implementations of GncSqlBackend.
*/
struct UriStrings;
template <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

@ -28,6 +28,10 @@ extern "C"
#include <platform.h>
#include <gnc-locale-utils.h>
}
#include <string>
#include <regex>
#include "gnc-dbisqlconnection.hpp"
static QofLogModule log_module = G_LOG_DOMAIN;
@ -120,7 +124,7 @@ GncDbiSqlConnection::lock_database (bool ignore_lock)
std::string err{"SQL Backend failed to obtain a transaction: "};
err += errstr;
qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
qof_backend_set_message (m_qbe, err.c_str());
m_qbe->set_message (err.c_str());
return false;
}
dbi_result_free(result);
@ -143,7 +147,7 @@ GncDbiSqlConnection::lock_database (bool ignore_lock)
if (!result)
{
qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
qof_backend_set_message (m_qbe, "Failed to delete lock record");
m_qbe->set_message("Failed to delete lock record");
result = dbi_conn_query (m_conn, "ROLLBACK");
if (result)
dbi_result_free (result);
@ -161,7 +165,7 @@ GncDbiSqlConnection::lock_database (bool ignore_lock)
if (!result)
{
qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
qof_backend_set_message (m_qbe, "Failed to create lock record");
m_qbe->set_message("Failed to create lock record");
result = dbi_conn_query (m_conn, "ROLLBACK");
if (result)
dbi_result_free (result);
@ -175,7 +179,7 @@ GncDbiSqlConnection::lock_database (bool ignore_lock)
qof_backend_set_error(m_qbe, ERR_BACKEND_SERVER_ERR);
std::string err{"Failed to commit transaction: "};
err += errstr;
qof_backend_set_message(m_qbe, err.c_str());
m_qbe->set_message(err.c_str());
return false;
}
dbi_result_free (result);
@ -185,8 +189,6 @@ GncDbiSqlConnection::lock_database (bool ignore_lock)
void
GncDbiSqlConnection::unlock_database ()
{
GncDbiBackend* qe = reinterpret_cast<decltype(qe)>(m_qbe);
if (m_conn == nullptr) return;
g_return_if_fail (dbi_conn_error (m_conn, nullptr) == 0);
@ -221,7 +223,7 @@ GncDbiSqlConnection::unlock_database ()
if (!result)
{
PERR ("Failed to delete the lock entry");
qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
m_qbe->set_error (ERR_BACKEND_SERVER_ERR);
result = dbi_conn_query (m_conn, "ROLLBACK");
if (result)
{
@ -258,7 +260,7 @@ GncDbiSqlConnection::unlock_database ()
result = nullptr;
}
PWARN ("Unable to get a lock on LOCK, so failed to clear the lock entry.");
qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
m_qbe->set_error (ERR_BACKEND_SERVER_ERR);
}
GncDbiSqlConnection::~GncDbiSqlConnection()
@ -586,16 +588,16 @@ GncDbiSqlConnection::table_manage_backup (const std::string& table_name,
dbi_result result = nullptr;
switch (op)
{
case backup:
case TableOpType::backup:
result = dbi_conn_queryf (m_conn, "ALTER TABLE %s RENAME TO %s",
table_name.c_str(), new_name.c_str());
break;
case rollback:
case TableOpType::rollback:
result = dbi_conn_queryf (m_conn,
"ALTER TABLE %s RENAME TO %s",
new_name.c_str(), table_name.c_str());
break;
case drop_backup:
case TableOpType::drop_backup:
result = dbi_conn_queryf (m_conn, "DROP TABLE %s",
new_name.c_str());
break;
@ -630,16 +632,20 @@ GncDbiSqlConnection::table_manage_backup (const std::string& table_name,
*/
bool
GncDbiSqlConnection::table_operation(const StrVec& table_names,
TableOpType op) noexcept
GncDbiSqlConnection::table_operation(TableOpType op) noexcept
{
g_return_val_if_fail (!table_names.empty(), FALSE);
static const std::regex backupre (".*_back");
bool retval{true};
for (auto table : table_names)
for (auto table : m_provider->get_table_list(m_conn, ""))
{
dbi_result result;
/* Ignore the lock table */
if (table == lock_table)
/* Skip the lock table and existing backup tables; the former we don't
* want to touch, the latter are handled by table_manage_backup. It
* would be nicer to handle this with the get_table_list query, but that
* can accept only SQL LIKE patterns (not even regexps) and there's no
* way to have a negative one.
*/
if (table == lock_table || std::regex_match(table, backupre))
{
continue;
}
@ -660,7 +666,7 @@ GncDbiSqlConnection::table_operation(const StrVec& table_names,
break;
}
}
/* Fall through */
/* Fall through to rename the _back tables back.*/
case backup:
case drop_backup:
result = table_manage_backup (table, op);
@ -689,6 +695,23 @@ GncDbiSqlConnection::table_operation(const StrVec& table_names,
return retval;
}
bool
GncDbiSqlConnection::drop_indexes() noexcept
{
auto index_list = m_provider->get_index_list (m_conn);
for (auto index : index_list)
{
const char* errmsg;
m_provider->drop_index (m_conn, index);
if (DBI_ERROR_NONE != dbi_conn_error (m_conn, &errmsg))
{
PERR("Failed to drop indexes %s", errmsg);
return false;
}
}
return true;
}
std::string
GncDbiSqlConnection::add_columns_ddl(const std::string& table_name,
const ColVec& info_vec) const noexcept

View File

@ -81,12 +81,10 @@ public:
bool verify() noexcept override;
bool retry_connection(const char* msg) noexcept override;
dbi_result table_manage_backup(const std::string& table_name, TableOpType op);
bool table_operation (const StrVec& table_name_list,
TableOpType op) noexcept;
bool table_operation (TableOpType op) noexcept;
std::string add_columns_ddl(const std::string& table_name,
const ColVec& info_vec) const noexcept;
friend void gnc_dbi_safe_sync_all (QofBackend* qbe, QofBook* book);
bool drop_indexes() noexcept;
private:
QofBackend* m_qbe;
dbi_conn m_conn;

View File

@ -80,9 +80,7 @@ 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},
QofBackend {}, m_conn{conn}, m_book{book}, m_loading{false},
m_in_query{false}, m_is_pristine_db{false}
{
if (conn != nullptr)
@ -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

@ -27,13 +27,14 @@
extern "C"
{
#include <qof.h>
#include <qofbackend-p.h>
#include <Account.h>
}
#include <memory>
#include <exception>
#include <sstream>
#include <vector>
#include <qof-backend.hpp>
class GncSqlColumnTableEntry;
using GncSqlColumnTableEntryPtr = std::shared_ptr<GncSqlColumnTableEntry>;
using EntryVec = std::vector<GncSqlColumnTableEntryPtr>;
@ -61,11 +62,41 @@ typedef enum
*
* Main SQL backend structure.
*/
class GncSqlBackend
class GncSqlBackend : public QofBackend
{
public:
GncSqlBackend(GncSqlConnection *conn, QofBook* book);
virtual ~GncSqlBackend() = default;
/**
* Load the contents of an SQL database into a book.
*
* @param book Book to be loaded
*/
void load(QofBook*, QofBackendLoadType) override;
/**
* Save the contents of a book to an SQL database.
*
* @param book Book to be saved
*/
void sync(QofBook*) override;
/**
* An object is about to be edited.
*
* @param inst Object being edited
*/
void begin(QofInstance*) override;
/**
* Object editting is complete and the object should be saved.
*
* @param inst Object being edited
*/
void commit(QofInstance*) override;
/**
* Object editing has been cancelled.
*
* @param inst Object being edited
*/
void rollback(QofInstance*) override;
/** Connect the backend to a GncSqlConnection.
* Sets up version info. Calling with nullptr clears the connection and
* destroys the version info.
@ -156,36 +187,6 @@ public:
*/
uint_t get_table_version(const std::string& table_name) const noexcept;
bool set_table_version (const std::string& table_name, uint_t version) noexcept;
/**
* Load the contents of an SQL database into a book.
*
* @param book Book to be loaded
*/
void load(QofBook*, QofBackendLoadType);
/**
* Save the contents of a book to an SQL database.
*
* @param book Book to be saved
*/
void sync_all(QofBook*);
/**
* An object is about to be edited.
*
* @param inst Object being edited
*/
void begin_edit(QofInstance*);
/**
* Object editting is complete and the object should be saved.
*
* @param inst Object being edited
*/
void commit_edit(QofInstance*);
/**
* Object editing has been cancelled.
*
* @param inst Object being edited
*/
void rollback_edit(QofInstance*);
/**
* Register a commodity to be committed after loading is complete.
*
@ -240,7 +241,6 @@ public:
void finish_progress() const noexcept;
protected:
QofBackend qof_be; /**< QOF backend. Not a pointer, nor really a member */
GncSqlConnection* m_conn; /**< SQL connection */
QofBook* m_book; /**< The primary, main open book */
bool m_loading; /**< We are performing an initial load */

View File

@ -27,7 +27,6 @@
extern "C"
{
#include <qof.h>
#include "qofbackend-p.h"
}
#include <memory>
#include <vector>

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

@ -20,6 +20,7 @@ SET (backend_xml_utils_noinst_HEADERS
gnc-owner-xml-v2.h
gnc-tax-table-xml-v2.h
gnc-vendor-xml-v2.h
gnc-xml-backend.hpp
gnc-xml-helper.h
io-example-account.h
io-gncxml-gen.h
@ -57,6 +58,7 @@ SET (backend_xml_utils_SOURCES
gnc-tax-table-xml-v2.cpp
gnc-transaction-xml-v2.cpp
gnc-vendor-xml-v2.cpp
gnc-xml-backend.cpp
gnc-xml-helper.cpp
io-example-account.cpp
io-gncxml-gen.cpp

View File

@ -41,6 +41,7 @@ libgnc_backend_xml_utils_la_SOURCES = \
gnc-tax-table-xml-v2.cpp \
gnc-transaction-xml-v2.cpp \
gnc-vendor-xml-v2.cpp \
gnc-xml-backend.cpp \
gnc-xml-helper.cpp \
io-example-account.cpp \
io-gncxml-gen.cpp \
@ -71,6 +72,7 @@ noinst_HEADERS = \
gnc-owner-xml-v2.h \
gnc-tax-table-xml-v2.h \
gnc-vendor-xml-v2.h \
gnc-xml-backend.hpp \
gnc-xml-helper.h \
io-example-account.h \
io-gncxml-gen.h \

File diff suppressed because it is too large Load Diff

View File

@ -36,7 +36,6 @@ extern "C"
#endif
#include <qof.h>
#include <gmodule.h>
#include <qofbackend-p.h>
typedef enum
{
@ -56,24 +55,6 @@ typedef enum
GNC_BOOK_POST_XML2_0_0_FILE
} QofBookFileType;
struct FileBackend_struct
{
QofBackend be;
char* dirname;
char* fullpath; /* Fully qualified path to book */
char* lockfile;
char* linkfile;
int lockfd;
QofBook* book; /* The primary, main open book */
};
typedef struct FileBackend_struct FileBackend;
// This is now a static inside the module
//QofBackend * libgncmod_backend_file_LTX_gnc_backend_new(void);
/** Initialization function which can be used when this module is
* statically linked into the application. */
void gnc_module_init_backend_xml (void);
@ -88,5 +69,6 @@ void qof_backend_module_init (void);
#endif
#ifdef __cplusplus
}
#include <qof-backend.hpp>
#endif
#endif /* GNC_BACKEND_XML_H_ */

View File

@ -0,0 +1,896 @@
/********************************************************************
* gnc-xml-backend.cpp: Implement XML file backend. *
* Copyright 2016 John Ralls <jralls@ceridwen.us> *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
\********************************************************************/
extern "C"
{
#include <config.h>
#include <platform.h>
#if PLATFORM(WINDOWS)
#include <windows.h>
#endif
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <regex.h>
#include <gnc-engine.h> //for GNC_MOD_BACKEND
#include <gnc-uri-utils.h>
#include <TransLog.h>
#include <gnc-prefs.h>
}
#include <sstream>
#include "gnc-xml-backend.hpp"
#include "gnc-backend-xml.h"
#include "io-gncxml-v2.h"
#include "io-gncxml.h"
#define XML_URI_PREFIX "xml://"
#define FILE_URI_PREFIX "file://"
static QofLogModule log_module = GNC_MOD_BACKEND;
bool
GncXmlBackend::check_path (const char* fullpath, bool create)
{
struct stat statbuf;
char* dirname = g_path_get_dirname (fullpath);
/* Again check whether the directory can be accessed */
auto rc = g_stat (dirname, &statbuf);
if (rc != 0
#if COMPILER(MSVC)
|| (statbuf.st_mode & _S_IFDIR) == 0
#else
|| !S_ISDIR (statbuf.st_mode)
#endif
)
{
/* Error on stat or if it isn't a directory means we
cannot find this filename */
set_error(ERR_FILEIO_FILE_NOT_FOUND);
std::string msg {"Couldn't find directory for "};
set_message(msg + fullpath);
PWARN ("Couldn't find directory for %s", fullpath);
g_free(dirname);
return false;
}
/* Now check whether we can g_stat the file itself */
rc = g_stat (fullpath, &statbuf);
if ((rc != 0) && (!create))
{
/* Error on stat means the file doesn't exist */
set_error(ERR_FILEIO_FILE_NOT_FOUND);
std::string msg {"Couldn't find "};
set_message(msg + fullpath);
PWARN ("Couldn't find %s", fullpath);
g_free(dirname);
return false;
}
if (rc == 0
#if COMPILER(MSVC)
&& (statbuf.st_mode & _S_IFDIR) != 0
#else
&& S_ISDIR (statbuf.st_mode)
#endif
)
{
set_error(ERR_FILEIO_UNKNOWN_FILE_TYPE);
std::string msg {"Path "};
msg += fullpath;
set_message(msg + " is a directory");
PWARN ("Path %s is a directory", fullpath);
g_free(dirname);
return false;
}
return true;
}
void
GncXmlBackend::session_begin(QofSession* session, const char* book_id,
bool ignore_lock, bool create, bool force)
{
/* Make sure the directory is there */
m_fullpath = gnc_uri_get_path (book_id);
if (m_fullpath.empty())
{
set_error(ERR_FILEIO_FILE_NOT_FOUND);
set_message("No path specified");
return;
}
if (create && !force && save_may_clobber_data())
{
set_error(ERR_BACKEND_STORE_EXISTS);
PWARN ("Might clobber, no force");
return;
}
if (!check_path(m_fullpath.c_str(), create))
return;
m_dirname = g_path_get_dirname (m_fullpath.c_str());
/* ---------------------------------------------------- */
/* We should now have a fully resolved path name.
* Let's start logging */
xaccLogSetBaseName (m_fullpath.c_str());
PINFO ("logpath=%s", m_fullpath.empty() ? "(null)" : m_fullpath.c_str());
/* And let's see if we can get a lock on it. */
m_lockfile = m_fullpath + ".LCK";
if (!ignore_lock && !get_file_lock())
{
// We should not ignore the lock, but couldn't get it. The
// be_get_file_lock() already set the appropriate backend_error in this
// case, so we just return here.
m_lockfile.clear();
if (force)
{
QofBackendError berror = get_error();
if (berror == ERR_BACKEND_LOCKED || berror == ERR_BACKEND_READONLY)
{
// Even though we couldn't get the lock, we were told to force
// the opening. This is ok because the FORCE argument is
// changed only if the caller wants a read-only book.
}
else
{
// Unknown error. Push it again on the error stack.
set_error(berror);
}
}
}
}
void
GncXmlBackend::session_end()
{
if (m_book && qof_book_is_readonly (m_book))
{
set_error(ERR_BACKEND_READONLY);
return;
}
if (!m_linkfile.empty())
g_unlink (m_linkfile.c_str());
if (m_lockfd > 0)
close (m_lockfd);
if (!m_lockfile.empty())
{
int rv;
#ifdef G_OS_WIN32
/* On windows, we need to allow write-access before
g_unlink() can succeed */
rv = g_chmod (m_lockfile.c_str(), S_IWRITE | S_IREAD);
#endif
rv = g_unlink (m_lockfile.c_str());
if (rv)
{
PWARN ("Error on g_unlink(%s): %d: %s", m_lockfile.c_str(),
errno, g_strerror (errno) ? g_strerror (errno) : "");
}
}
m_dirname.clear();
m_fullpath.clear();
m_lockfile.clear();
m_linkfile.clear();
}
static QofBookFileType
determine_file_type (const std::string& path)
{
gboolean with_encoding;
QofBookFileType v2type;
v2type = gnc_is_xml_data_file_v2 (path.c_str(), &with_encoding);
if (v2type == GNC_BOOK_XML2_FILE)
{
if (with_encoding)
{
return GNC_BOOK_XML2_FILE;
}
else
{
return GNC_BOOK_XML2_FILE_NO_ENCODING;
}
}
else if (v2type == GNC_BOOK_POST_XML2_0_0_FILE)
{
return GNC_BOOK_POST_XML2_0_0_FILE;
}
else if (v2type == GNC_BOOK_XML1_FILE)
{
return GNC_BOOK_XML1_FILE;
}
return GNC_BOOK_NOT_OURS;
}
void
GncXmlBackend::load(QofBook* book, QofBackendLoadType loadType)
{
QofBackendError error;
if (loadType != LOAD_TYPE_INITIAL_LOAD) return;
error = ERR_BACKEND_NO_ERR;
m_book = book;
int rc;
switch (determine_file_type (m_fullpath))
{
case GNC_BOOK_XML2_FILE:
rc = qof_session_load_from_xml_file_v2 (this, book,
GNC_BOOK_XML2_FILE);
if (rc == FALSE)
{
PWARN ("Syntax error in Xml File %s", m_fullpath.c_str());
error = ERR_FILEIO_PARSE_ERROR;
}
break;
case GNC_BOOK_XML2_FILE_NO_ENCODING:
error = ERR_FILEIO_NO_ENCODING;
PWARN ("No character encoding in Xml File %s", m_fullpath.c_str());
break;
case GNC_BOOK_XML1_FILE:
rc = qof_session_load_from_xml_file (book, m_fullpath.c_str());
if (rc == FALSE)
{
PWARN ("Syntax error in Xml File %s", m_fullpath.c_str());
error = ERR_FILEIO_PARSE_ERROR;
}
break;
case GNC_BOOK_POST_XML2_0_0_FILE:
error = ERR_BACKEND_TOO_NEW;
PWARN ("Version of Xml file %s is newer than what we can read",
m_fullpath.c_str());
break;
default:
/* If file type wasn't known, check errno again to give the
user some more useful feedback for some particular error
conditions. */
switch (errno)
{
case EACCES: /* No read permission */
PWARN ("No read permission to file");
error = ERR_FILEIO_FILE_EACCES;
break;
case EISDIR: /* File is a directory - but on this error we don't arrive here */
PWARN ("Filename is a directory");
error = ERR_FILEIO_FILE_NOT_FOUND;
break;
default:
PWARN ("File not any known type");
error = ERR_FILEIO_UNKNOWN_FILE_TYPE;
break;
}
break;
}
if (error != ERR_BACKEND_NO_ERR)
{
set_error(error);
}
/* We just got done loading, it can't possibly be dirty !! */
qof_book_mark_session_saved (book);
}
void
GncXmlBackend::sync(QofBook* book)
{
/* We make an important assumption here, that we might want to change
* in the future: when the user says 'save', we really save the one,
* the only, the current open book, and nothing else. In any case the plans
* for multiple books have been removed in the meantime and there is just one
* book, no more.
*/
if (m_book == nullptr) m_book = book;
if (book != m_book) return;
if (qof_book_is_readonly (m_book))
{
/* Are we read-only? Don't continue in this case. */
set_error(ERR_BACKEND_READONLY);
return;
}
write_to_file (true);
remove_old_files();
}
bool
GncXmlBackend::save_may_clobber_data()
{
if (m_fullpath.empty())
return false;
struct stat statbuf;
auto rc = g_stat (m_fullpath.c_str(), &statbuf);
return rc != 0;
}
void
GncXmlBackend::export_coa(QofBook* book)
{
auto out = fopen(m_fullpath.c_str(), "w");
if (out == NULL)
{
set_error(ERR_FILEIO_WRITE_ERROR);
set_message(strerror(errno));
return;
}
gnc_book_write_accounts_to_xml_filehandle_v2(this, book, out);
fclose(out);
}
bool
GncXmlBackend::write_to_file (bool make_backup)
{
QofBackendError be_err;
ENTER (" book=%p file=%s", m_book, m_fullpath.c_str());
if (m_book && qof_book_is_readonly (m_book))
{
/* Are we read-only? Don't continue in this case. */
set_error(ERR_BACKEND_READONLY);
LEAVE ("");
return FALSE;
}
/* If the book is 'clean', recently saved, then don't save again. */
/* XXX this is currently broken due to faulty 'Save As' logic. */
/* if (FALSE == qof_book_session_not_saved (book)) return FALSE; */
auto tmp_name = g_new (char, strlen (m_fullpath.c_str()) + 12);
strcpy (tmp_name, m_fullpath.c_str());
strcat (tmp_name, ".tmp-XXXXXX");
if (!mktemp (tmp_name))
{
set_error(ERR_BACKEND_MISC);
set_message("Failed to make temp file");
LEAVE ("");
return FALSE;
}
if (make_backup)
{
if (!backup_file ())
{
LEAVE ("");
return FALSE;
}
}
if (gnc_book_write_to_xml_file_v2 (m_book, tmp_name,
gnc_prefs_get_file_save_compressed ()))
{
/* Record the file's permissions before g_unlinking it */
struct stat statbuf;
auto rc = g_stat (m_fullpath.c_str(), &statbuf);
if (rc == 0)
{
/* We must never chmod the file /dev/null */
g_assert (g_strcmp0 (tmp_name, "/dev/null") != 0);
/* Use the permissions from the original data file */
if (g_chmod (tmp_name, statbuf.st_mode) != 0)
{
/* set_error(ERR_BACKEND_PERM); */
/* set_message("Failed to chmod filename %s", tmp_name ); */
/* Even if the chmod did fail, the save
nevertheless completed successfully. It is
therefore wrong to signal the ERR_BACKEND_PERM
error here which implies that the saving itself
failed. Instead, we simply ignore this. */
PWARN ("unable to chmod filename %s: %s",
tmp_name ? tmp_name : "(null)",
g_strerror (errno) ? g_strerror (errno) : "");
#if VFAT_DOESNT_SUCK /* chmod always fails on vfat/samba fs */
/* g_free(tmp_name); */
/* return FALSE; */
#endif
}
#ifdef HAVE_CHOWN
/* Don't try to change the owner. Only root can do
that. */
if (chown (tmp_name, -1, statbuf.st_gid) != 0)
{
/* set_error(ERR_BACKEND_PERM); */
/* set_message("Failed to chown filename %s", tmp_name ); */
/* A failed chown doesn't mean that the saving itself
failed. So don't abort with an error here! */
PWARN ("unable to chown filename %s: %s",
tmp_name ? tmp_name : "(null)",
strerror (errno) ? strerror (errno) : "");
#if VFAT_DOESNT_SUCK /* chown always fails on vfat fs */
/* g_free(tmp_name);
return FALSE; */
#endif
}
#endif
}
if (g_unlink (m_fullpath.c_str()) != 0 && errno != ENOENT)
{
set_error(ERR_BACKEND_READONLY);
PWARN ("unable to unlink filename %s: %s",
m_fullpath.empty() ? "(null)" : m_fullpath.c_str(),
g_strerror (errno) ? g_strerror (errno) : "");
g_free (tmp_name);
LEAVE ("");
return FALSE;
}
if (!link_or_make_backup (tmp_name, m_fullpath))
{
set_error(ERR_FILEIO_BACKUP_ERROR);
std::string msg{"Failed to make backup file "};
set_message(msg + (m_fullpath.empty() ? "NULL" : m_fullpath));
g_free (tmp_name);
LEAVE ("");
return FALSE;
}
if (g_unlink (tmp_name) != 0)
{
set_error(ERR_BACKEND_PERM);
PWARN ("unable to unlink temp filename %s: %s",
tmp_name ? tmp_name : "(null)",
g_strerror (errno) ? g_strerror (errno) : "");
g_free (tmp_name);
LEAVE ("");
return FALSE;
}
g_free (tmp_name);
/* Since we successfully saved the book,
* we should mark it clean. */
qof_book_mark_session_saved (m_book);
LEAVE (" successful save of book=%p to file=%s", m_book,
m_fullpath.c_str());
return TRUE;
}
else
{
if (g_unlink (tmp_name) != 0)
{
switch (errno)
{
case ENOENT: /* tmp_name doesn't exist? Assume "RO" error */
case EACCES:
case EPERM:
case ENOSYS:
case EROFS:
be_err = ERR_BACKEND_READONLY;
break;
default:
be_err = ERR_BACKEND_MISC;
break;
}
set_error(be_err);
PWARN ("unable to unlink temp_filename %s: %s",
tmp_name ? tmp_name : "(null)",
g_strerror (errno) ? g_strerror (errno) : "");
/* already in an error just flow on through */
}
else
{
/* Use a generic write error code */
set_error(ERR_FILEIO_WRITE_ERROR);
std::string msg{"Unable to write to temp file "};
set_message(msg + (tmp_name ? tmp_name : "NULL"));
}
g_free (tmp_name);
LEAVE ("");
return FALSE;
}
LEAVE ("");
return TRUE;
}
static bool
copy_file (const std::string& orig, const std::string& bkup)
{
constexpr size_t buf_size = 1024;
char buf[buf_size];
int flags = 0;
ssize_t count_write = 0;
ssize_t count_read = 0;
#ifdef G_OS_WIN32
flags = O_BINARY;
#endif
auto orig_fd = g_open (orig.c_str(), O_RDONLY | flags, 0);
if (orig_fd == -1)
{
return false;
}
auto bkup_fd = g_open (bkup.c_str(),
O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | flags, 0600);
if (bkup_fd == -1)
{
close (orig_fd);
return FALSE;
}
do
{
count_read = read (orig_fd, buf, buf_size);
if (count_read == -1 && errno != EINTR)
{
close (orig_fd);
close (bkup_fd);
return FALSE;
}
if (count_read > 0)
{
count_write = write (bkup_fd, buf, count_read);
if (count_write == -1)
{
close (orig_fd);
close (bkup_fd);
return FALSE;
}
}
}
while (count_read > 0);
close (orig_fd);
close (bkup_fd);
return TRUE;
}
bool
GncXmlBackend::link_or_make_backup (const std::string& orig,
const std::string& bkup)
{
gboolean copy_success = FALSE;
int err_ret =
#ifdef HAVE_LINK
link (orig.c_str(), bkup.c_str())
#else
- 1
#endif
;
if (err_ret != 0)
{
#ifdef HAVE_LINK
if (errno == EPERM || errno == ENOSYS
# ifdef EOPNOTSUPP
|| errno == EOPNOTSUPP
# endif
# ifdef ENOTSUP
|| errno == ENOTSUP
# endif
# ifdef ENOSYS
|| errno == ENOSYS
# endif
)
#endif
{
copy_success = copy_file (orig.c_str(), bkup);
}
if (!copy_success)
{
set_error(ERR_FILEIO_BACKUP_ERROR);
PWARN ("unable to make file backup from %s to %s: %s",
orig.c_str(), bkup.c_str(), g_strerror (errno) ? g_strerror (errno) : "");
return false;
}
}
return true;
}
bool
GncXmlBackend::get_file_lock ()
{
struct stat statbuf;
#ifndef G_OS_WIN32
char* pathbuf = NULL, *tmpbuf = NULL;
size_t pathbuf_size = 0;
#endif
QofBackendError be_err;
auto rc = g_stat (m_lockfile.c_str(), &statbuf);
if (!rc)
{
/* oops .. file is locked by another user .. */
set_error(ERR_BACKEND_LOCKED);
return false;
}
m_lockfd = g_open (m_lockfile.c_str(), O_RDWR | O_CREAT | O_EXCL ,
S_IRUSR | S_IWUSR);
if (m_lockfd < 0)
{
/* oops .. we can't create the lockfile .. */
switch (errno)
{
case EACCES:
case EROFS:
case ENOSPC:
PWARN ("Unable to create the lockfile %s; may not have write priv",
m_lockfile.c_str());
be_err = ERR_BACKEND_READONLY;
break;
default:
be_err = ERR_BACKEND_LOCKED;
break;
}
set_error(be_err);
return false;
}
/* OK, now work around some NFS atomic lock race condition
* mumbo-jumbo. We do this by linking a unique file, and
* then examining the link count. At least that's what the
* NFS programmers guide suggests.
* Note: the "unique filename" must be unique for the
* triplet filename-host-process, otherwise accidental
* aliases can occur.
*/
/* apparently, even this code may not work for some NFS
* implementations. In the long run, I am told that
* ftp.debian.org
* /pub/debian/dists/unstable/main/source/libs/liblockfile_0.1-6.tar.gz
* provides a better long-term solution.
*/
#ifndef G_OS_WIN32
auto path = m_lockfile.find_last_of('.');
std::stringstream linkfile;
if (path != std::string::npos)
linkfile << m_lockfile.substr(0, path);
else
linkfile << m_lockfile;
linkfile << "." << gethostid() << "." << getpid() << ".LNK";
rc = link (m_lockfile.c_str(), linkfile.str().c_str());
if (rc)
{
/* If hard links aren't supported, just allow the lock. */
if (errno == EPERM || errno == ENOSYS
# ifdef EOPNOTSUPP
|| errno == EOPNOTSUPP
# endif
# ifdef ENOTSUP
|| errno == ENOTSUP
# endif
)
{
return true;
}
/* Otherwise, something else is wrong. */
set_error(ERR_BACKEND_LOCKED);
g_unlink (linkfile.str().c_str());
close (m_lockfd);
g_unlink (m_lockfile.c_str());
return false;
}
rc = g_stat (m_lockfile.c_str(), &statbuf);
if (rc)
{
/* oops .. stat failed! This can't happen! */
set_error(ERR_BACKEND_LOCKED);
std::string msg{"Failed to stat lockfile "};
set_message(msg + m_lockfile);
g_unlink (linkfile.str().c_str());
close (m_lockfd);
g_unlink (m_lockfile.c_str());
return false;
}
if (statbuf.st_nlink != 2)
{
set_error(ERR_BACKEND_LOCKED);
g_unlink (linkfile.str().c_str());
close (m_lockfd);
g_unlink (m_lockfile.c_str());
return false;
}
m_linkfile = linkfile.str();
return true;
#else /* ifndef G_OS_WIN32 */
/* On windows, there is no NFS and the open(,O_CREAT | O_EXCL)
is sufficient for locking. */
return true;
#endif /* ifndef G_OS_WIN32 */
}
bool
GncXmlBackend::backup_file()
{
struct stat statbuf;
auto datafile = m_fullpath.c_str();
auto rc = g_stat (datafile, &statbuf);
if (rc)
return (errno == ENOENT);
if (determine_file_type (m_fullpath) == GNC_BOOK_BIN_FILE)
{
/* make a more permanent safer backup */
auto bin_bkup = m_fullpath + "-binfmt.bkup";
auto bkup_ret = link_or_make_backup (m_fullpath, bin_bkup);
if (!bkup_ret)
{
return false;
}
}
auto timestamp = gnc_date_timestamp ();
auto backup = m_fullpath + "." + timestamp + GNC_DATAFILE_EXT;
g_free (timestamp);
return link_or_make_backup (datafile, backup);
}
/*
* Clean up any lock files from prior crashes, and clean up old
* backup and log files.
*/
void
GncXmlBackend::remove_old_files ()
{
struct stat lockstatbuf, statbuf;
if (g_stat (m_lockfile.c_str(), &lockstatbuf) != 0)
return;
auto dir = g_dir_open (m_dirname.c_str(), 0, NULL);
if (!dir)
return;
auto now = gnc_time (NULL);
const char* dent;
while ((dent = g_dir_read_name (dir)) != NULL)
{
gchar* name;
/* Ensure we only evaluate GnuCash related files. */
if (! (g_str_has_suffix (dent, ".LNK") ||
g_str_has_suffix (dent, ".xac") /* old data file extension */ ||
g_str_has_suffix (dent, GNC_DATAFILE_EXT) ||
g_str_has_suffix (dent, GNC_LOGFILE_EXT)))
continue;
name = g_build_filename (m_dirname.c_str(), dent, (gchar*)NULL);
/* Only evaluate files associated with the current data file. */
if (!g_str_has_prefix (name, m_fullpath.c_str()))
{
g_free (name);
continue;
}
/* Never remove the current data file itself */
if (g_strcmp0 (name, m_fullpath.c_str()) == 0)
{
g_free (name);
continue;
}
/* Test if the current file is a lock file */
if (g_str_has_suffix (name, ".LNK"))
{
/* Is a lock file. Skip the active lock file */
if ((g_strcmp0 (name, m_linkfile.c_str()) != 0) &&
/* Only delete lock files older than the active one */
(g_stat (name, &statbuf) == 0) &&
(statbuf.st_mtime < lockstatbuf.st_mtime))
{
PINFO ("remove stale lock file: %s", name);
g_unlink (name);
}
g_free (name);
continue;
}
/* At this point we're sure the file's name is in one of these forms:
* <fullpath/to/datafile><anything>.gnucash
* <fullpath/to/datafile><anything>.xac
* <fullpath/to/datafile><anything>.log
*
* To be a file generated by GnuCash, the <anything> part should consist
* of 1 dot followed by 14 digits (0 to 9). Let's test this with a
* regular expression.
*/
{
/* Find the start of the date stamp. This takes some pointer
* juggling, but considering the above tests, this should always
* be safe */
regex_t pattern;
gchar* stamp_start = name + strlen (m_fullpath.c_str());
gchar* expression = g_strdup_printf ("^\\.[[:digit:]]{14}(\\%s|\\%s|\\.xac)$",
GNC_DATAFILE_EXT, GNC_LOGFILE_EXT);
gboolean got_date_stamp = FALSE;
if (regcomp (&pattern, expression, REG_EXTENDED | REG_ICASE) != 0)
PWARN ("Cannot compile regex for date stamp");
else if (regexec (&pattern, stamp_start, 0, NULL, 0) == 0)
got_date_stamp = TRUE;
regfree (&pattern);
g_free (expression);
if (!got_date_stamp) /* Not a gnucash created file after all... */
{
g_free (name);
continue;
}
}
/* The file is a backup or log file. Check the user's retention preference
* to determine if we should keep it or not
*/
if (gnc_prefs_get_file_retention_policy () == XML_RETAIN_NONE)
{
PINFO ("remove stale file: %s - reason: preference XML_RETAIN_NONE", name);
g_unlink (name);
}
else if ((gnc_prefs_get_file_retention_policy () == XML_RETAIN_DAYS) &&
(gnc_prefs_get_file_retention_days () > 0))
{
int days;
/* Is the backup file old enough to delete */
if (g_stat (name, &statbuf) != 0)
{
g_free (name);
continue;
}
days = (int) (difftime (now, statbuf.st_mtime) / 86400);
PINFO ("file retention = %d days", gnc_prefs_get_file_retention_days ());
if (days >= gnc_prefs_get_file_retention_days ())
{
PINFO ("remove stale file: %s - reason: more than %d days old", name, days);
g_unlink (name);
}
}
g_free (name);
}
g_dir_close (dir);
}

View File

@ -0,0 +1,66 @@
/********************************************************************
* gnc-xml-backend.hpp: Declare XML file backend. *
* Copyright 2016 John Ralls <jralls@ceridwen.us> *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
\********************************************************************/
#ifndef __GNC_XML_BACKEND_HPP__
#define __GNC_XML_BACKEND_HPP__
extern "C"
{
#include <qof.h>
}
#include <string>
#include <qof-backend.hpp>
class GncXmlBackend : public QofBackend
{
public:
GncXmlBackend() = default;
GncXmlBackend(const GncXmlBackend&) = delete;
GncXmlBackend operator=(const GncXmlBackend&) = delete;
GncXmlBackend(const GncXmlBackend&&) = delete;
GncXmlBackend operator=(const GncXmlBackend&&) = delete;
~GncXmlBackend() = default;
void session_begin(QofSession* session, const char* book_id,
bool ignore_lock, bool create, bool force) override;
void session_end() override;
void load(QofBook* book, QofBackendLoadType loadType) override;
/* The XML backend isn't able to do anything with individual instances. */
void export_coa(QofBook*) override;
void sync(QofBook* book) override;
void safe_sync(QofBook* book) override { sync(book); } // XML sync is inherently safe.
const char * get_filename() { return m_fullpath.c_str(); }
QofBook* get_book() { return m_book; }
private:
bool save_may_clobber_data();
bool get_file_lock();
bool link_or_make_backup(const std::string& orig, const std::string& bkup);
bool backup_file();
bool write_to_file(bool make_backup);
void remove_old_files();
void write_accounts(QofBook* book);
bool check_path(const char* fullpath, bool create);
std::string m_dirname;
std::string m_lockfile;
std::string m_linkfile;
int m_lockfd;
QofBook* m_book; /* The primary, main open book */
};
#endif // __GNC_XML_BACKEND_HPP__

View File

@ -66,6 +66,7 @@ extern "C"
#endif
}
#include "gnc-xml-backend.hpp"
#include "sixtp-parsers.h"
#include "sixtp-utils.h"
#include "gnc-xml.h"
@ -692,12 +693,11 @@ gnc_sixtp_gdv2_new (
static gboolean
qof_session_load_from_xml_file_v2_full (
FileBackend* fbe, QofBook* book,
GncXmlBackend* xml_be, QofBook* book,
sixtp_push_handler push_handler, gpointer push_user_data,
QofBookFileType type)
{
Account* root;
QofBackend* be = &fbe->be;
sixtp_gdv2* gd;
sixtp* top_parser;
sixtp* main_parser;
@ -706,7 +706,8 @@ qof_session_load_from_xml_file_v2_full (
gboolean retval;
char* v2type = NULL;
gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, be->percentage);
gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback,
xml_be->get_percentage());
top_parser = sixtp_new ();
main_parser = sixtp_new ();
@ -793,7 +794,7 @@ qof_session_load_from_xml_file_v2_full (
* https://bugzilla.gnome.org/show_bug.cgi?id=712528 for more
* info.
*/
gchar* filename = fbe->fullpath;
const char* filename = xml_be->get_filename();
FILE* file;
gboolean is_compressed = is_gzipped_file (filename);
file = try_gz_open (filename, "r", is_compressed, FALSE);
@ -864,10 +865,10 @@ bail:
}
gboolean
qof_session_load_from_xml_file_v2 (FileBackend* fbe, QofBook* book,
qof_session_load_from_xml_file_v2 (GncXmlBackend* xml_be, QofBook* book,
QofBookFileType type)
{
return qof_session_load_from_xml_file_v2_full (fbe, book, NULL, NULL, type);
return qof_session_load_from_xml_file_v2_full (xml_be, book, NULL, NULL, type);
}
/***********************************************************************/
@ -1257,21 +1258,21 @@ static void
write_budget (QofInstance* ent, gpointer data)
{
xmlNodePtr node;
struct file_backend* be = static_cast<decltype (be)> (data);
struct file_backend* file_be = static_cast<decltype (file_be)> (data);
GncBudget* bgt = GNC_BUDGET (ent);
if (ferror (be->out))
if (ferror (file_be->out))
return;
node = gnc_budget_dom_tree_create (bgt);
xmlElemDump (be->out, NULL, node);
xmlElemDump (file_be->out, NULL, node);
xmlFreeNode (node);
if (ferror (be->out) || fprintf (be->out, "\n") < 0)
if (ferror (file_be->out) || fprintf (file_be->out, "\n") < 0)
return;
be->gd->counter.budgets_loaded++;
sixtp_run_callback (be->gd, "budgets");
file_be->gd->counter.budgets_loaded++;
sixtp_run_callback (file_be->gd, "budgets");
}
gboolean
@ -1328,7 +1329,7 @@ write_v2_header (FILE* out)
gboolean
gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out)
{
QofBackend* be;
QofBackend* qof_be;
sixtp_gdv2* gd;
gboolean success = TRUE;
@ -1338,8 +1339,9 @@ gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out)
|| !write_counts (out, "book", 1, NULL))
return FALSE;
be = qof_book_get_backend (book);
gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, be->percentage);
qof_be = qof_book_get_backend (book);
gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback,
qof_be->get_percentage());
gd->counter.commodities_total =
gnc_commodity_table_get_size (gnc_commodity_table_get_table (book));
gd->counter.accounts_total = 1 +
@ -1364,7 +1366,7 @@ gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out)
* This function is called by the "export" code.
*/
gboolean
gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* be, QofBook* book,
gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* qof_be, QofBook* book,
FILE* out)
{
gnc_commodity_table* table;
@ -1385,7 +1387,8 @@ gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* be, QofBook* book,
|| !write_counts (out, "commodity", ncom, "account", nacc, NULL))
return FALSE;
gd = gnc_sixtp_gdv2_new (book, TRUE, file_rw_feedback, be->percentage);
gd = gnc_sixtp_gdv2_new (book, TRUE, file_rw_feedback,
qof_be->get_percentage());
gd->counter.commodities_total = ncom;
gd->counter.accounts_total = nacc;
@ -1650,9 +1653,7 @@ gnc_book_write_to_xml_file_v2 (
* postgress or anything else.
*/
gboolean
gnc_book_write_accounts_to_xml_file_v2 (
QofBackend* be,
QofBook* book,
gnc_book_write_accounts_to_xml_file_v2 (QofBackend* qof_be, QofBook* book,
const char* filename)
{
FILE* out;
@ -1662,7 +1663,7 @@ gnc_book_write_accounts_to_xml_file_v2 (
/* Try to write as much as possible */
if (!out
|| !gnc_book_write_accounts_to_xml_filehandle_v2 (be, book, out)
|| !gnc_book_write_accounts_to_xml_filehandle_v2 (qof_be, book, out)
|| !write_emacs_trailer (out))
success = FALSE;
@ -1670,11 +1671,11 @@ gnc_book_write_accounts_to_xml_file_v2 (
if (out && fclose (out))
success = FALSE;
if (!success && !qof_backend_check_error (be))
if (!success && !qof_be->check_error())
{
/* Use a generic write error code */
qof_backend_set_error (be, ERR_FILEIO_WRITE_ERROR);
qof_backend_set_error (qof_be, ERR_FILEIO_WRITE_ERROR);
}
return success;
@ -2031,7 +2032,7 @@ cleanup_find_ambs:
typedef struct
{
gchar* filename;
const char* filename;
GHashTable* subst;
} push_data_type;
@ -2168,17 +2169,17 @@ cleanup_push_handler:
}
gboolean
gnc_xml2_parse_with_subst (FileBackend* fbe, QofBook* book, GHashTable* subst)
gnc_xml2_parse_with_subst (GncXmlBackend* xml_be, QofBook* book, GHashTable* subst)
{
push_data_type* push_data;
gboolean success;
push_data = g_new (push_data_type, 1);
push_data->filename = fbe->fullpath;
push_data->filename = xml_be->get_filename();
push_data->subst = subst;
success = qof_session_load_from_xml_file_v2_full (
fbe, book, (sixtp_push_handler) parse_with_subst_push_handler,
xml_be, book, (sixtp_push_handler) parse_with_subst_push_handler,
push_data, GNC_BOOK_XML2_FILE);
if (success)

View File

@ -43,6 +43,7 @@ extern "C"
#include "sixtp.h"
#include <vector>
class GncXmlBackend;
/**
* Struct used to pass in a new data type for XML storage. This contains
@ -84,7 +85,7 @@ typedef struct
} gnc_template_xaction_data;
/** read in an account group from a file */
gboolean qof_session_load_from_xml_file_v2 (FileBackend*, QofBook*,
gboolean qof_session_load_from_xml_file_v2 (GncXmlBackend*, QofBook*,
QofBookFileType);
/* write all book info to a file */
@ -154,7 +155,7 @@ gint gnc_xml2_find_ambiguous (
* @param subst hash table with keys and values of type gchar*
*/
gboolean gnc_xml2_parse_with_subst (
FileBackend* fbe, QofBook* book, GHashTable* subst);
GncXmlBackend* xml_be, QofBook* book, GHashTable* subst);
#ifdef __cplusplus
}
typedef struct

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

@ -35,7 +35,6 @@ extern "C"
#include "../gnc-lot.h"
#include "../gnc-event.h"
#include <qof.h>
#include <qofbackend-p.h>
#ifdef HAVE_GLIB_2_38
#define _Q "'"
@ -50,6 +49,7 @@ static const gchar *suitename = "/engine/Transaction";
void test_suite_transaction ( void );
}
#include <qof-backend.hpp>
#include <kvp_frame.hpp>
/* Copied from Transaction.c. Changing these values will break
@ -86,47 +86,49 @@ typedef struct
Account *gains_acc;
} GainsFixture;
typedef struct
class MockBackend : public QofBackend
{
QofBackend be;
gchar last_call[12];
QofBackendError result_err;
} MockBackend;
static void
mock_backend_set_error (MockBackend *mbe, QofBackendError err)
{
mbe->result_err = err;
}
static void
mock_backend_rollback (QofBackend *be, QofInstance *foo)
{
MockBackend *mbe = (MockBackend *)be;
g_strlcpy (mbe->last_call, "rollback", sizeof (mbe->last_call));
mbe->be.last_err = mbe->result_err;
}
static MockBackend*
mock_backend_new (void)
{
MockBackend *mbe = g_new0 (MockBackend, 1);
mbe->be.rollback = mock_backend_rollback;
memset (mbe->last_call, 0, sizeof (mbe->last_call));
return mbe;
}
public:
MockBackend() : QofBackend(), m_last_call{"Constructor"},
m_result_err{ERR_BACKEND_NO_ERR} {}
void session_begin(QofSession*, const char*, bool, bool, bool) override {
m_last_call = "session_begin";
}
void session_end() override {
m_last_call = "session_end";
}
void load(QofBook*, QofBackendLoadType) override {
m_last_call = "load";
}
void sync(QofBook*) override {
m_last_call = "sync";
}
void safe_sync(QofBook*) override {
m_last_call = "safe_sync";
}
void rollback(QofInstance*) override {
set_error(m_result_err);
m_last_call = "rollback";
}
void inject_error(QofBackendError err) {
m_result_err = err;
}
std::string m_last_call;
private:
QofBackendError m_result_err;
};
static void
setup (Fixture *fixture, gconstpointer pData)
{
QofBook *book = qof_book_new ();
MockBackend *mbe = mock_backend_new ();
MockBackend *mbe = new MockBackend;
Transaction *txn;
Timespec entered = gnc_dmy2timespec (20, 4, 2012);
Timespec posted = gnc_dmy2timespec (21, 4, 2012);
auto frame = new KvpFrame ();
qof_book_set_backend (book, (QofBackend*)mbe);
qof_book_set_backend (book, mbe);
auto split1 = xaccMallocSplit (book);
auto split2 = xaccMallocSplit (book);
txn = xaccMallocTransaction (book);
@ -211,14 +213,14 @@ static void
teardown (Fixture *fixture, gconstpointer pData)
{
QofBook *book = qof_instance_get_book (QOF_INSTANCE (fixture->txn));
MockBackend *mbe = (MockBackend *)qof_book_get_backend (book);
auto mbe = static_cast<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

@ -35,8 +35,27 @@
#include "gnc-uri-utils.h"
#include "gnc-module.h"
#include "gnc-ui.h"
#include "io-gncxml-v2.h"
/* The following are copied from src/backend/xml/io-gncxml2-v2.h as a temporary
* measure to enable this to compile in the face of making changing struct
* FileBackend into C++ class XmlBackend, which can't be exposed to this C
* file. A future commit will separate the session code from the UI code in this
* file.
*/
typedef struct
{
GQuark encoding;
gchar* utf8_string;
} conv_type;
extern gint gnc_xml2_find_ambiguous (const gchar* filename,
GList* encodings,
GHashTable** unique,
GHashTable** ambiguous,
GList** impossible);
extern gboolean gnc_xml2_parse_with_subst (QofBackend* xml_be, QofBook* book,
GHashTable* subst);
/* NOTE: This file uses the term "encoding" even in places where it is not
* accurate. Please ignore that. Encodings occur in different forms:
* - as descriptive string, as in the list of system encodings
@ -1041,7 +1060,7 @@ gxi_parse_file (GncXmlImportData *data)
{
QofSession *session = NULL;
QofBook *book;
FileBackend *backend;
QofBackend *backend;
QofBackendError io_err = ERR_BACKEND_NO_ERR;
gchar *message = NULL;
gboolean success = FALSE;
@ -1091,7 +1110,7 @@ gxi_parse_file (GncXmlImportData *data)
qof_session_pop_error (session);
book = qof_session_get_book (session);
backend = (FileBackend*) qof_book_get_backend (book);
backend = qof_book_get_backend (book);
gxi_update_progress_bar (_("Parsing file..."), 0.0);
success = gnc_xml2_parse_with_subst (backend, book, data->subst);

View File

@ -14,7 +14,7 @@ SET (gnc_qof_HEADERS
qof/kvp_frame.hpp
qof/kvp-value.hpp
qof/qof.h
qof/qofbackend-p.h
qof/qof-backend.hpp
qof/qofbackend.h
qof/qofbook.h
qof/qofbookslots.h
@ -59,7 +59,7 @@ SET (gnc_qof_SOURCES
qof/guid.cpp
qof/kvp_frame.cpp
qof/kvp-value.cpp
qof/qofbackend.cpp
qof/qof-backend.cpp
qof/qofbook.cpp
qof/qofchoice.cpp
qof/qofclass.cpp

View File

@ -32,7 +32,7 @@ libgnc_qof_la_SOURCES = \
guid.cpp \
kvp_frame.cpp \
kvp-value.cpp \
qofbackend.cpp \
qof-backend.cpp \
qofbook.cpp \
qofchoice.cpp \
qofclass.cpp \
@ -61,7 +61,7 @@ qofinclude_HEADERS = \
kvp_frame.hpp \
kvp-value.hpp \
qof.h \
qofbackend-p.h \
qof-backend.hpp \
qofbackend.h \
qofbook.h \
qofbookslots.h \

View File

@ -0,0 +1,171 @@
/********************************************************************\
* qofbackend.c -- utility routines for dealing with the data backend *
* Copyright (C) 2000 Linas Vepstas <linas@linas.org> *
* Copyright (C) 2004-5 Neil Williams <linux@codehelp.co.uk> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
* *
\********************************************************************/
extern "C"
{
#include <config.h>
#include "qof.h"
}
#include <string>
#include <algorithm>
#include <vector>
#include "qof-backend.hpp"
G_GNUC_UNUSED static QofLogModule log_module = QOF_MOD_BACKEND;
#define QOF_CONFIG_DESC "desc"
#define QOF_CONFIG_TIP "tip"
/* *******************************************************************\
* error handling *
\********************************************************************/
GModuleVec QofBackend::c_be_registry{};
void
QofBackend::set_error(QofBackendError err)
{
/* use stack-push semantics. Only the earliest error counts */
if (m_last_err != ERR_BACKEND_NO_ERR) return;
m_last_err = err;
}
QofBackendError
QofBackend::get_error()
{
/* use 'stack-pop' semantics */
auto err = m_last_err;
m_last_err = ERR_BACKEND_NO_ERR;
return err;
}
bool
QofBackend::check_error()
{
return m_last_err != ERR_BACKEND_NO_ERR;
}
void
QofBackend::set_message (std::string&& msg)
{
m_error_msg = msg;
}
const std::string&&
QofBackend::get_message ()
{
return std::move(m_error_msg);
}
bool
QofBackend::register_backend(const char* directory, const char* module_name)
{
if (!g_module_supported ())
{
PWARN("Modules not supported.");
return false;
}
auto fullpath = g_module_build_path (directory, module_name);
/* Darwin modules can have either .so or .dylib for a suffix */
if (!g_file_test (fullpath, G_FILE_TEST_EXISTS) &&
g_strcmp0 (G_MODULE_SUFFIX, "so") == 0)
{
auto modname = g_strdup_printf ("lib%s.dylib", module_name);
g_free (fullpath);
fullpath = g_build_filename (directory, modname, NULL);
g_free (modname);
}
auto backend = g_module_open (fullpath, G_MODULE_BIND_LAZY);
g_free (fullpath);
if (!backend)
{
PINFO ("%s: %s\n", PACKAGE, g_module_error ());
return false;
}
void (*module_init_func)(void);
if (g_module_symbol (backend, "qof_backend_module_init",
reinterpret_cast<void**>(&module_init_func)))
module_init_func ();
g_module_make_resident (backend);
c_be_registry.push_back(backend);
return TRUE;
}
void
QofBackend::release_backends()
{
for (auto backend : c_be_registry)
{
void (*module_finalize_func)(void);
if (g_module_symbol(backend, "qof_backend_module_finalize",
reinterpret_cast<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)
{
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

@ -1,284 +0,0 @@
/********************************************************************\
* qofbackend.c -- utility routines for dealing with the data backend *
* Copyright (C) 2000 Linas Vepstas <linas@linas.org> *
* Copyright (C) 2004-5 Neil Williams <linux@codehelp.co.uk> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
* *
\********************************************************************/
extern "C"
{
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <regex.h>
#include <glib.h>
#include <gmodule.h>
#include <errno.h>
#include "qof.h"
#include "qofbackend-p.h"
}
G_GNUC_UNUSED static QofLogModule log_module = QOF_MOD_BACKEND;
#define QOF_CONFIG_DESC "desc"
#define QOF_CONFIG_TIP "tip"
/* *******************************************************************\
* error handling *
\********************************************************************/
void
qof_backend_set_error (QofBackend *be, QofBackendError err)
{
if (!be) return;
/* use stack-push semantics. Only the earliest error counts */
if (ERR_BACKEND_NO_ERR != be->last_err) return;
be->last_err = err;
}
QofBackendError
qof_backend_get_error (QofBackend *be)
{
QofBackendError err;
if (!be) return ERR_BACKEND_NO_BACKEND;
/* use 'stack-pop' semantics */
err = be->last_err;
be->last_err = ERR_BACKEND_NO_ERR;
return err;
}
gboolean
qof_backend_check_error (QofBackend *be)
{
g_return_val_if_fail (be != NULL, TRUE);
return be->last_err != ERR_BACKEND_NO_ERR;
}
gboolean
qof_backend_can_rollback (QofBackend* be)
{
if (be == nullptr) return FALSE;
return be->rollback != nullptr;
}
void
qof_backend_rollback_instance (QofBackend* be, QofInstance* inst)
{
if (be == nullptr || be->rollback == nullptr) return;
(be->rollback)(be, inst);
}
void
qof_backend_set_message (QofBackend *be, const char *format, ...)
{
va_list args;
char * buffer;
if (!be) return;
/* If there's already something here, free it */
if (be->error_msg) g_free(be->error_msg);
if (!format)
{
be->error_msg = NULL;
return;
}
va_start(args, format);
buffer = (char *)g_strdup_vprintf(format, args);
va_end(args);
be->error_msg = buffer;
}
char *
qof_backend_get_message (QofBackend *be)
{
char * msg;
if (!be) return g_strdup("ERR_BACKEND_NO_BACKEND");
if (!be->error_msg) return NULL;
/*
* Just return the contents of the error_msg and then set it to
* NULL. This is necessary, because the Backends don't seem to
* have a destroy_backend function to take care of freeing stuff
* up. The calling function should free the copy.
* Also, this is consistent with the qof_backend_get_error() popping.
*/
msg = be->error_msg;
be->error_msg = NULL;
return msg;
}
/***********************************************************************/
/* Get a clean backend */
void
qof_backend_init(QofBackend *be)
{
be->session_begin = NULL;
be->session_end = NULL;
be->destroy_backend = NULL;
be->load = NULL;
be->begin = NULL;
be->commit = NULL;
be->rollback = NULL;
be->sync = NULL;
be->safe_sync = NULL;
be->export_fn = NULL;
be->last_err = ERR_BACKEND_NO_ERR;
if (be->error_msg) g_free (be->error_msg);
be->error_msg = NULL;
be->percentage = NULL;
}
void
qof_backend_destroy(QofBackend *be)
{
g_free(be->error_msg);
be->error_msg = NULL;
}
void
qof_backend_run_begin(QofBackend *be, QofInstance *inst)
{
if (!be || !inst)
{
return;
}
if (!be->begin)
{
return;
}
(be->begin) (be, inst);
}
gboolean
qof_backend_begin_exists(const QofBackend *be)
{
if (be->begin)
{
return TRUE;
}
else
{
return FALSE;
}
}
void
qof_backend_run_commit(QofBackend *be, QofInstance *inst)
{
if (!be || !inst)
{
return;
}
if (!be->commit)
{
return;
}
(be->commit) (be, inst);
}
gboolean
qof_backend_commit_exists(const QofBackend *be)
{
if (!be)
{
return FALSE;
}
if (be->commit)
{
return TRUE;
}
else
{
return FALSE;
}
}
static GSList* backend_module_list = NULL;
gboolean
qof_load_backend_library (const char *directory, const char* module_name)
{
gchar *fullpath;
GModule *backend;
void (*module_init_func) (void);
g_return_val_if_fail(g_module_supported (), FALSE);
fullpath = g_module_build_path (directory, module_name);
/* Darwin modules can have either .so or .dylib for a suffix */
if (!g_file_test (fullpath, G_FILE_TEST_EXISTS) &&
g_strcmp0 (G_MODULE_SUFFIX, "so") == 0)
{
gchar *modname = g_strdup_printf ("lib%s.dylib", module_name);
g_free (fullpath);
fullpath = g_build_filename (directory, modname, NULL);
g_free (modname);
}
backend = g_module_open (fullpath, G_MODULE_BIND_LAZY);
g_free (fullpath);
if (!backend)
{
g_message ("%s: %s\n", PACKAGE, g_module_error ());
return FALSE;
}
if (g_module_symbol (backend, "qof_backend_module_init",
reinterpret_cast<void**>(&module_init_func)))
module_init_func ();
g_module_make_resident (backend);
backend_module_list = g_slist_prepend (backend_module_list, backend);
return TRUE;
}
void
qof_finalize_backend_libraries(void)
{
GSList* node;
GModule* backend;
void (*module_finalize_func) (void);
for (node = backend_module_list; node != NULL; node = node->next)
{
backend = (GModule*)node->data;
if (g_module_symbol(backend, "qof_backend_module_finalize",
reinterpret_cast<void**>(&module_finalize_func)))
module_finalize_func();
}
}
/************************* END OF FILE ********************************/

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

@ -52,7 +52,7 @@ extern "C"
#include "qof.h"
#include "qofevent-p.h"
#include "qofbackend-p.h"
#include "qofbackend.h"
#include "qofbook-p.h"
#include "qofid-p.h"
#include "qofobject-p.h"

View File

@ -41,6 +41,7 @@ extern "C"
#include "qofid-p.h"
#include "kvp_frame.hpp"
#include "qofinstance-p.h"
#include "qof-backend.hpp"
static QofLogModule log_module = QOF_MOD_ENGINE;
@ -957,7 +958,6 @@ gboolean
qof_begin_edit (QofInstance *inst)
{
QofInstancePrivate *priv;
QofBackend * be;
if (!inst) return FALSE;
@ -967,9 +967,9 @@ qof_begin_edit (QofInstance *inst)
if (0 >= priv->editlevel)
priv->editlevel = 1;
be = qof_book_get_backend(priv->book);
if (be && qof_backend_begin_exists(be))
qof_backend_run_begin(be, inst);
auto be = qof_book_get_backend(priv->book);
if (be)
be->begin(inst);
else
priv->dirty = TRUE;
@ -1001,7 +1001,6 @@ qof_commit_edit_part2(QofInstance *inst,
void (*on_free)(QofInstance *))
{
QofInstancePrivate *priv;
QofBackend * be;
priv = GET_PRIVATE(inst);
@ -1012,27 +1011,27 @@ qof_commit_edit_part2(QofInstance *inst,
}
/* See if there's a backend. If there is, invoke it. */
be = qof_book_get_backend(priv->book);
if (be && qof_backend_commit_exists(be))
auto be = qof_book_get_backend(priv->book);
if (be)
{
QofBackendError errcode;
/* clear errors */
do
{
errcode = qof_backend_get_error(be);
errcode = be->get_error();
}
while (ERR_BACKEND_NO_ERR != errcode);
while (errcode != ERR_BACKEND_NO_ERR);
qof_backend_run_commit(be, inst);
errcode = qof_backend_get_error(be);
if (ERR_BACKEND_NO_ERR != errcode)
be->commit(inst);
errcode = be->get_error();
if (errcode != ERR_BACKEND_NO_ERR)
{
/* XXX Should perform a rollback here */
priv->do_free = FALSE;
/* Push error back onto the stack */
qof_backend_set_error (be, errcode);
be->set_error (errcode);
if (on_error)
on_error(inst, errcode);
return FALSE;

View File

@ -33,7 +33,7 @@ extern "C"
}
#include "qof.h"
#include "qofbackend-p.h"
#include "qof-backend.hpp"
#include "qofbook-p.h"
#include "qofclass-p.h"
#include "qofquery-p.h"

View File

@ -56,7 +56,7 @@ extern "C"
static QofLogModule log_module = QOF_MOD_SESSION;
} //extern 'C'
#include "qofbackend-p.h"
#include "qof-backend.hpp"
#include "qofsession.hpp"
#include "gnc-backend-prov.hpp"
@ -122,6 +122,7 @@ QofSessionImpl::QofSessionImpl () noexcept
: m_book {qof_book_new ()},
m_book_id {},
m_saving {false},
m_last_err {},
m_error_message {}
{
clear_error ();
@ -157,10 +158,7 @@ QofSessionImpl::destroy_backend () noexcept
if (backend)
{
clear_error ();
if (backend->destroy_backend)
backend->destroy_backend (backend);
else
g_free(backend);
delete backend;
qof_book_set_backend (m_book, nullptr);
}
}
@ -218,7 +216,7 @@ QofSessionImpl::load (QofPercentageFunc percentage_func) noexcept
* top-level account group out of the backend, and that is a
* generic, backend-independent operation.
*/
QofBackend * be {qof_book_get_backend (oldbook)};
auto be (qof_book_get_backend (oldbook));
qof_book_set_backend (newbook, be);
/* Starting the session should result in a bunch of accounts
@ -227,12 +225,9 @@ QofSessionImpl::load (QofPercentageFunc percentage_func) noexcept
*/
if (be)
{
be->percentage = percentage_func;
if (be->load)
{
be->load (be, newbook, LOAD_TYPE_INITIAL_LOAD);
push_error (qof_backend_get_error(be), {});
}
be->set_percentage(percentage_func);
be->load (newbook, LOAD_TYPE_INITIAL_LOAD);
push_error (be->get_error(), {});
}
/* XXX if the load fails, then we try to restore the old set of books;
@ -262,7 +257,8 @@ QofSessionImpl::load (QofPercentageFunc percentage_func) noexcept
}
void
QofSessionImpl::begin (std::string new_book_id, bool ignore_lock, bool create, bool force) noexcept
QofSessionImpl::begin (std::string new_book_id, bool ignore_lock,
bool create, bool force) noexcept
{
ENTER (" sess=%p ignore_lock=%d, book-id=%s",
this, ignore_lock, new_book_id.c_str ());
@ -313,7 +309,8 @@ QofSessionImpl::begin (std::string new_book_id, bool ignore_lock, bool create, b
g_free (scheme);
/* No backend was found. That's bad. */
if (!qof_book_get_backend (m_book))
auto backend = qof_book_get_backend (m_book);
if (backend == nullptr)
{
m_book_id = {};
if (ERR_BACKEND_NO_ERR == get_error ())
@ -324,26 +321,22 @@ QofSessionImpl::begin (std::string new_book_id, bool ignore_lock, bool create, b
}
/* If there's a begin method, call that. */
if (qof_book_get_backend (m_book)->session_begin)
{
auto backend = qof_book_get_backend (m_book);
(backend->session_begin) (backend, this, m_book_id.c_str (), ignore_lock, create, force);
backend->session_begin(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)};
QofBackendError const err {backend->get_error()};
auto msg (backend->get_message());
if (err != ERR_BACKEND_NO_ERR)
{
m_book_id = {};
push_error (err, msg ? msg : "");
LEAVE (" backend error %d %s", err, msg ? msg : "(null)");
push_error (err, msg);
LEAVE (" backend error %d %s", err, msg.empty() ? "(null)" : msg.c_str());
return;
}
if (msg != nullptr)
if (!msg.empty())
{
PWARN("%s", msg);
g_free(msg);
}
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,18 +463,15 @@ 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->sync)(backend, m_book);
QofBackendError err {qof_backend_get_error (backend)};
if (ERR_BACKEND_NO_ERR != err)
backend->set_percentage(percentage_func);
backend->sync(m_book);
auto err = backend->get_error();
if (err != ERR_BACKEND_NO_ERR)
{
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. */
clear_error ();
@ -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

@ -32,7 +32,7 @@
#include <stdlib.h>
#include <string.h>
#include "qof.h"
#include "qofbackend-p.h"
#include "qof-backend.hpp"
G_GNUC_UNUSED static QofLogModule log_module = QOF_MOD_UTIL;
@ -273,7 +273,7 @@ qof_close(void)
{
qof_query_shutdown ();
qof_object_shutdown ();
qof_finalize_backend_libraries();
QofBackend::release_backends();
qof_string_cache_destroy ();
qof_log_shutdown();
}

View File

@ -14,9 +14,7 @@ test_qof_SOURCES = \
test-qofbook.c \
test-qofinstance.cpp \
test-qofobject.c \
test-qofsession-old.cpp \
test-qof-string-cache.c \
test-gnc-guid-old.cpp \
${top_srcdir}/src/test-core/unittest-support.c
test_qof_HEADERS = \

View File

@ -27,8 +27,8 @@ extern "C"
#include <glib.h>
#include <unittest-support.h>
#include "../qof.h"
#include "../qofbackend-p.h"
}
#include "../qof-backend.hpp"
#include "../kvp_frame.hpp"
static const gchar *suitename = "/qof/qofinstance";
extern "C" void test_suite_qofinstance ( void );
@ -41,11 +41,86 @@ static gboolean is_called;
#define _Q "`"
#endif
static struct
{
QofInstance *m_inst = nullptr;
QofBackend *m_be = nullptr;
bool m_commit_called = false;
bool m_commit_with_err_called = false;
bool m_on_error_called = false;
bool m_on_free_called = false;
bool m_on_done_called = false;
QofBackendError m_err = ERR_BACKEND_NO_ERR;
} commit_test;
class MockBackend : public QofBackend
{
public:
MockBackend() : m_qof_error{ERR_BACKEND_NO_ERR} {
commit_test.m_be = this;
}
void session_begin(QofSession* sess, const char* book_name,
bool ignore_lock, bool create, bool force) override {}
void session_end() override {}
void load(QofBook*, QofBackendLoadType) override {}
void sync(QofBook* book) override {}
void safe_sync(QofBook* book) override {}
void begin(QofInstance* inst) override {
g_assert(inst);
g_assert(QOF_IS_INSTANCE(inst));
commit_test.m_inst = inst;
}
void set_error(QofBackendError err) {
QofBackend::set_error(err);
commit_test.m_err = err;
}
void commit(QofInstance* inst) override {
g_assert( inst );
g_assert( QOF_IS_INSTANCE( inst ) );
g_assert( commit_test.m_inst == inst );
g_assert( commit_test.m_be == this );
commit_test.m_commit_called = true;
set_error(m_qof_error);
}
void rollback(QofInstance* inst) override {}
void inject_error(QofBackendError err) {
m_qof_error = err;
}
private:
QofBackendError m_qof_error;
};
typedef struct
{
QofInstance *inst;
} Fixture;
static void
on_error(QofInstance* inst, QofBackendError err) {
g_assert( inst );
g_assert( QOF_IS_INSTANCE( inst ) );
g_assert( commit_test.m_err == err );
commit_test.m_on_error_called = true;
}
static void
on_done(QofInstance* inst) {
g_assert( inst );
g_assert( QOF_IS_INSTANCE( inst ) );
g_assert( commit_test.m_inst == inst );
commit_test.m_on_done_called = true;
}
static void
on_free(QofInstance* inst) {
g_assert( inst );
g_assert( QOF_IS_INSTANCE( inst ) );
g_assert(commit_test.m_inst == inst );
commit_test.m_on_free_called = true;
}
/* use g_free on error_message after this function been called */
static gboolean
fatal_handler ( const char * log_domain,
@ -423,7 +498,6 @@ mock_backend_begin( QofBackend *be, QofInstance *inst )
{
g_assert( be );
g_assert( inst );
g_assert( be->begin == mock_backend_begin );
g_assert_cmpstr( inst->e_type, == , "test type" );
is_called = TRUE;
}
@ -431,14 +505,12 @@ mock_backend_begin( QofBackend *be, QofInstance *inst )
static void
test_instance_begin_edit( Fixture *fixture, gconstpointer pData )
{
QofBackend *be;
QofBook *book;
gboolean result;
/* setup */
be = g_new0( QofBackend, 1 );
auto be = new MockBackend;
g_assert( be );
qof_backend_init( be );
book = qof_book_new();
g_assert( book );
g_assert( QOF_IS_BOOK( book ) );
@ -467,22 +539,19 @@ test_instance_begin_edit( Fixture *fixture, gconstpointer pData )
g_test_message( "Test when instance's editlevel is <= 0 and backend is set" );
result = FALSE;
is_called = FALSE;
qof_instance_reset_editlevel( fixture->inst );
qof_instance_set_dirty_flag( fixture->inst, FALSE );
qof_instance_set_book( fixture->inst, book );
be->begin = mock_backend_begin;
result = qof_begin_edit( fixture->inst );
g_assert( result == TRUE );
g_assert_cmpint( qof_instance_get_editlevel( fixture->inst ), == , 1 );
g_assert( qof_instance_get_dirty_flag( fixture->inst ) == FALSE );
g_assert( is_called );
/* clean up */
qof_book_set_backend( book, NULL );
qof_book_destroy( book );
qof_backend_destroy( be );
g_free( be );
delete be;
}
static void
@ -527,82 +596,16 @@ test_instance_commit_edit( Fixture *fixture, gconstpointer pData )
/* backend commit test start */
static struct
{
gpointer inst;
gpointer be;
gboolean commit_called;
gboolean commit_with_err_called;
gboolean on_error_called;
gboolean on_free_called;
gboolean on_done_called;
QofBackendError err;
} commit_test_part2;
static void
mock_backend_commit( QofBackend *be, QofInstance *inst )
{
g_assert( inst );
g_assert( be );
g_assert( QOF_IS_INSTANCE( inst ) );
g_assert( commit_test_part2.inst == inst );
g_assert( commit_test_part2.be == be );
commit_test_part2.commit_called = TRUE;
}
static void
mock_backend_commit_with_error( QofBackend *be, QofInstance *inst )
{
g_assert( inst );
g_assert( be );
g_assert( QOF_IS_INSTANCE( inst ) );
g_assert( commit_test_part2.inst == inst );
g_assert( commit_test_part2.be == be );
qof_backend_set_error( be, ERR_BACKEND_NO_HANDLER );
commit_test_part2.err = ERR_BACKEND_NO_HANDLER;
commit_test_part2.commit_with_err_called = TRUE;
}
static void
mock_on_error( QofInstance *inst, QofBackendError be_error )
{
g_assert( inst );
g_assert( QOF_IS_INSTANCE( inst ) );
g_assert( commit_test_part2.err == be_error );
commit_test_part2.on_error_called = TRUE;
}
static void
mock_on_done( QofInstance *inst )
{
g_assert( inst );
g_assert( QOF_IS_INSTANCE( inst ) );
g_assert( commit_test_part2.inst == inst );
commit_test_part2.on_done_called = TRUE;
}
static void
mock_on_free( QofInstance *inst )
{
g_assert( inst );
g_assert( QOF_IS_INSTANCE( inst ) );
g_assert( commit_test_part2.inst == inst );
commit_test_part2.on_free_called = TRUE;
}
static void
test_instance_commit_edit_part2( Fixture *fixture, gconstpointer pData )
{
QofBackend *be;
QofBook *book;
gboolean result;
/* setup */
be = g_new0( QofBackend, 1 );
auto be = new MockBackend;
g_assert( be );
qof_backend_init( be );
book = qof_book_new();
g_assert( book );
g_assert( QOF_IS_BOOK( book ) );
@ -610,13 +613,13 @@ test_instance_commit_edit_part2( Fixture *fixture, gconstpointer pData )
/* init */
result = FALSE;
commit_test_part2.commit_called = FALSE;
commit_test_part2.commit_with_err_called = FALSE;
commit_test_part2.on_error_called = FALSE;
commit_test_part2.on_free_called = FALSE;
commit_test_part2.on_done_called = FALSE;
commit_test_part2.inst = fixture->inst;
commit_test_part2.be = be;
commit_test.m_commit_called = false;
commit_test.m_commit_with_err_called = false;
commit_test.m_on_error_called = false;
commit_test.m_on_free_called = false;
commit_test.m_on_done_called = false;
commit_test.m_inst = fixture->inst;
commit_test.m_be = be;
qof_instance_set_dirty_flag( fixture->inst, TRUE );
g_test_message( "Test when instance's backend not set, callbacks not set" );
@ -626,70 +629,67 @@ test_instance_commit_edit_part2( Fixture *fixture, gconstpointer pData )
g_assert( result );
g_assert( qof_instance_get_dirty_flag( fixture->inst ) );
g_assert( !qof_instance_get_infant( fixture->inst ) );
g_assert( !commit_test_part2.commit_called );
g_assert( !commit_test_part2.commit_with_err_called );
g_assert( !commit_test_part2.on_error_called );
g_assert( !commit_test_part2.on_free_called );
g_assert( !commit_test_part2.on_done_called );
g_assert( !commit_test.m_commit_called );
g_assert( !commit_test.m_commit_with_err_called );
g_assert( !commit_test.m_on_error_called );
g_assert( !commit_test.m_on_free_called );
g_assert( !commit_test.m_on_done_called );
g_test_message( "Test when instance's backend not set, do_free is true" );
g_test_message( "Test when instance's backend not set, do_free is false" );
qof_instance_set_destroying( fixture->inst, TRUE );
result = qof_commit_edit_part2( fixture->inst, mock_on_error, mock_on_done, mock_on_free );
result = qof_commit_edit_part2( fixture->inst, on_error, on_done, on_free );
g_assert( result );
g_assert( qof_instance_get_dirty_flag( fixture->inst ) );
g_assert( !commit_test_part2.commit_called );
g_assert( !commit_test_part2.commit_with_err_called );
g_assert( !commit_test_part2.on_error_called );
g_assert( commit_test_part2.on_free_called );
g_assert( !commit_test_part2.on_done_called );
g_assert( !commit_test.m_commit_called );
g_assert( !commit_test.m_commit_with_err_called );
g_assert( !commit_test.m_on_error_called );
g_assert( commit_test.m_on_free_called );
g_assert( !commit_test.m_on_done_called );
g_test_message( "Test when instance's backend not set, do_free is false" );
qof_instance_set_destroying( fixture->inst, FALSE );
commit_test_part2.on_free_called = FALSE;
result = qof_commit_edit_part2( fixture->inst, mock_on_error, mock_on_done, mock_on_free );
commit_test.m_on_free_called = false;
result = qof_commit_edit_part2( fixture->inst, on_error, on_done, on_free );
g_assert( result );
g_assert( qof_instance_get_dirty_flag( fixture->inst ) );
g_assert( !commit_test_part2.commit_called );
g_assert( !commit_test_part2.commit_with_err_called );
g_assert( !commit_test_part2.on_error_called );
g_assert( !commit_test_part2.on_free_called );
g_assert( commit_test_part2.on_done_called );
g_assert( !commit_test.m_commit_called );
g_assert( !commit_test.m_commit_with_err_called );
g_assert( !commit_test.m_on_error_called );
g_assert( !commit_test.m_on_free_called );
g_assert( commit_test.m_on_done_called );
g_test_message( "Test when instance's backend is set, all cb set, no error produced" );
qof_instance_set_book( fixture->inst, book );
qof_instance_set_destroying( fixture->inst, FALSE );
commit_test_part2.on_done_called = FALSE;
be->commit = mock_backend_commit;
result = qof_commit_edit_part2( fixture->inst, mock_on_error, mock_on_done, mock_on_free );
commit_test.m_on_done_called = false;
result = qof_commit_edit_part2( fixture->inst, on_error, on_done, on_free );
g_assert( result );
g_assert( !qof_instance_get_dirty_flag( fixture->inst ) );
g_assert( commit_test_part2.commit_called );
g_assert( !commit_test_part2.commit_with_err_called );
g_assert( !commit_test_part2.on_error_called );
g_assert( !commit_test_part2.on_free_called );
g_assert( commit_test_part2.on_done_called );
g_assert( commit_test.m_commit_called );
g_assert( !commit_test.m_commit_with_err_called );
g_assert( !commit_test.m_on_error_called );
g_assert( !commit_test.m_on_free_called );
g_assert( commit_test.m_on_done_called );
g_test_message( "Test when instance's backend is set, all cb set, error produced" );
commit_test_part2.commit_called = FALSE;
commit_test_part2.on_done_called = FALSE;
be->commit = mock_backend_commit_with_error;
commit_test.m_commit_called = false;
commit_test.m_on_done_called = false;
be->inject_error(ERR_BACKEND_NO_HANDLER);
qof_instance_set_dirty_flag( fixture->inst, TRUE );
qof_instance_set_destroying( fixture->inst, TRUE );
result = qof_commit_edit_part2( fixture->inst, mock_on_error, mock_on_done, mock_on_free );
result = qof_commit_edit_part2( fixture->inst, on_error, on_done, on_free );
g_assert( !result );
g_assert( qof_instance_get_dirty_flag( fixture->inst ) );
g_assert( !qof_instance_get_destroying( fixture->inst ) );
g_assert( !commit_test_part2.commit_called );
g_assert( commit_test_part2.commit_with_err_called );
g_assert( commit_test_part2.on_error_called );
g_assert( !commit_test_part2.on_free_called );
g_assert( !commit_test_part2.on_done_called );
g_assert( commit_test.m_commit_called );
g_assert( commit_test.m_on_error_called );
g_assert( !commit_test.m_on_free_called );
g_assert( !commit_test.m_on_done_called );
/* clean up */
qof_book_set_backend( book, NULL );
qof_book_destroy( book );
qof_backend_destroy( be );
g_free( be );
delete be;
}
/* backend commit test end */

View File

@ -25,7 +25,7 @@
#include <gtest/gtest.h>
#include "../guid.hpp"
#include <qofsession.hpp>
#include "qofbackend-p.h"
#include <qof-backend.hpp>
#include <cstdlib>
#include "../gnc-backend-prov.hpp"
@ -35,51 +35,51 @@ static bool sync_called {false};
static bool load_error {true};
static bool hook_called {false};
static bool data_loaded {false};
class MockBackend : public QofBackend
{
public:
MockBackend() = default;
MockBackend(const MockBackend&) = delete;
MockBackend(const MockBackend&&) = delete;
virtual ~MockBackend() = default;
void session_begin(QofSession*, const char*, bool, bool, bool) {}
void session_end() {}
void load(QofBook*, QofBackendLoadType);
void sync(QofBook*);
void safe_sync(QofBook*);
void export_coa(QofBook*);
};
void example_hook (QofSession & session)
{
hook_called = true;
}
void test_load (QofBackend * be, QofBook *, QofBackendLoadType)
void MockBackend::load (QofBook *, QofBackendLoadType)
{
if (load_error) be->last_err = ERR_BACKEND_NO_BACKEND;
if (load_error) set_error(ERR_BACKEND_NO_BACKEND);
data_loaded = true;
}
void test_safe_sync (QofBackend *, QofBook *)
void MockBackend::safe_sync (QofBook *)
{
safe_sync_called = true;
}
void test_sync (QofBackend *, QofBook *)
void MockBackend::sync (QofBook *)
{
sync_called = true;
}
void test_export_fn (QofBackend *, QofBook * book)
void MockBackend::export_coa(QofBook * book)
{
exported_book = book;
}
QofBackend * test_backend_factory ()
{
QofBackend * ret = (QofBackend*) std::malloc (sizeof (QofBackend));
ret->session_begin = nullptr;
ret->session_end = nullptr;
ret->destroy_backend = nullptr;
ret->load = &test_load;
ret->sync = &test_sync;
ret->safe_sync = &test_safe_sync;
ret->export_fn = &test_export_fn;
ret->error_msg = nullptr;
ret->fullpath = nullptr;
ret->last_err = ERR_BACKEND_NO_ERR;
ret->begin = nullptr;
ret->commit = nullptr;
ret->rollback = nullptr;
ret->percentage = nullptr;
ret->config_count = 0;
return ret;
return new MockBackend;
}
struct MockProvider : public QofBackendProvider