gnucash/libgnucash/engine/qofsession.cpp
Geert Janssens 1238b9d8cd Prevent gcc from searching config.h in the current directory
This will avoid a ninja-build from picking up a config.h generated by the autotools build
(in the root build directory). Picking up the wrong config.h may lead to all kinds of
subtle issues if the autotools run was done with different options than the cmake run.
2017-10-26 14:05:17 +02:00

742 lines
20 KiB
C++

/********************************************************************\
* qofsesssion.cpp -- session access (connection to backend) *
* *
* 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 *
\********************************************************************/
/**
* @file qofsession.c
* @brief Encapsulate a connection to a storage backend.
*
* HISTORY:
* Created by Linas Vepstas December 1998
@author Copyright (c) 1998-2004 Linas Vepstas <linas@linas.org>
@author Copyright (c) 2000 Dave Peticolas
@author Copyright (c) 2005 Neil Williams <linux@codehelp.co.uk>
@author Copyright (c) 2016 Aaron Laws
*/
extern "C"
{
#include <config.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#else
# ifdef __GNUC__
# warning "<unistd.h> required."
# endif
#endif
#include <glib.h>
#include "qof.h"
#include "qofbook-p.h"
#include "qofobject-p.h"
static QofLogModule log_module = QOF_MOD_SESSION;
} //extern 'C'
#include "qof-backend.hpp"
#include "qofsession.hpp"
#include "gnc-backend-prov.hpp"
#include <vector>
#include <boost/algorithm/string.hpp>
#include <vector>
#include <algorithm>
#include <string>
#include <sstream>
using ProviderVec = std::vector<QofBackendProvider_ptr>;
static ProviderVec s_providers;
/*
* These getters are used in tests to reach static vars from outside
* They should be removed when no longer needed
*/
ProviderVec& get_providers (void );
bool get_providers_initialized (void );
ProviderVec&
get_providers (void)
{
return s_providers;
}
bool
get_providers_initialized (void)
{
return !s_providers.empty();
}
void
qof_backend_register_provider (QofBackendProvider_ptr&& prov)
{
s_providers.emplace_back(std::move(prov));
}
void
qof_backend_unregister_all_providers ()
{
s_providers.clear ();
}
/* Called from C so we have to keep the GList for now. */
GList*
qof_backend_get_registered_access_method_list(void)
{
GList* list = NULL;
std::for_each(s_providers.begin(), s_providers.end(),
[&list](QofBackendProvider_ptr& provider) {
gpointer method = reinterpret_cast<gpointer>(const_cast<char*>(provider->access_method));
list = g_list_prepend(list, method);
});
return list;
}
/* QofSessionImpl */
/* ====================================================================== */
/* Constructor/Destructor ----------------------------------*/
QofSessionImpl::QofSessionImpl () noexcept
: m_book {qof_book_new ()},
m_book_id {},
m_saving {false},
m_last_err {},
m_error_message {}
{
clear_error ();
}
QofSessionImpl::~QofSessionImpl () noexcept
{
ENTER ("sess=%p book_id=%s", this, m_book_id.c_str ());
end ();
destroy_backend ();
qof_book_set_backend (m_book, nullptr);
qof_book_destroy (m_book);
m_book = nullptr;
LEAVE ("sess=%p", this);
}
void
qof_session_destroy (QofSession * session)
{
delete session;
}
QofSession *
qof_session_new (void)
{
return new QofSessionImpl;
}
void
QofSessionImpl::destroy_backend () noexcept
{
auto backend = qof_book_get_backend (m_book);
if (backend)
{
clear_error ();
delete backend;
qof_book_set_backend (m_book, nullptr);
}
}
/* ====================================================================== */
void
QofSessionImpl::load_backend (std::string access_method) noexcept
{
std::ostringstream s;
s << " list=" << s_providers.size();
ENTER ("%s", s.str().c_str());
for (auto const & prov : s_providers)
{
if (!boost::iequals (access_method, prov->access_method))
{
PINFO ("The provider providers access_method, %s, but we're loading for access_method, %s. Skipping.",
prov->access_method, access_method.c_str ());
continue;
}
PINFO (" Selected provider %s", prov->provider_name);
if (!prov->type_check (m_book_id.c_str ()))
{
PINFO("Provider, %s, reported not being usable for book, %s.",
prov->provider_name, m_book_id.c_str ());
continue;
}
auto backend = prov->create_backend();
qof_book_set_backend (m_book, backend);
LEAVE (" ");
return;
}
std::string msg {"failed to get_backend using access method \"" + access_method + "\""};
push_error (ERR_BACKEND_NO_HANDLER, msg);
LEAVE (" ");
}
void
QofSessionImpl::load (QofPercentageFunc percentage_func) noexcept
{
if (!m_book_id.size ()) return;
ENTER ("sess=%p book_id=%s", this, m_book_id.c_str ());
/* At this point, we should are supposed to have a valid book
* id and a lock on the file. */
QofBook * oldbook {m_book};
QofBook * newbook {qof_book_new ()};
m_book = newbook;
PINFO ("new book=%p", newbook);
clear_error ();
/* This code should be sufficient to initialize *any* backend,
* whether http, postgres, or anything else that might come along.
* Basically, the idea is that by now, a backend has already been
* created & set up. At this point, we only need to get the
* top-level account group out of the backend, and that is a
* generic, backend-independent operation.
*/
auto be (qof_book_get_backend (oldbook));
qof_book_set_backend (newbook, be);
/* Starting the session should result in a bunch of accounts
* and currencies being downloaded, but probably no transactions;
* The GUI will need to do a query for that.
*/
if (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;
* however, we don't undo the session id (the URL). Thus if the
* user attempts to save after a failed load, they weill be trying to
* save to some bogus URL. This is wrong. XXX FIXME.
*/
auto err = get_error ();
if ((err != ERR_BACKEND_NO_ERR) &&
(err != ERR_FILEIO_FILE_TOO_OLD) &&
(err != ERR_FILEIO_NO_ENCODING) &&
(err != ERR_FILEIO_FILE_UPGRADE) &&
(err != ERR_SQL_DB_TOO_OLD) &&
(err != ERR_SQL_DB_TOO_NEW))
{
/* Something broke, put back the old stuff */
qof_book_set_backend (newbook, NULL);
qof_book_destroy (newbook);
m_book = oldbook;
LEAVE ("error from backend %d", get_error ());
return;
}
qof_book_set_backend (oldbook, NULL);
qof_book_destroy (oldbook);
LEAVE ("sess = %p, book_id=%s", this, m_book_id.c_str ());
}
void
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 ());
clear_error ();
/* Check to see if this session is already open */
if (m_book_id.size ())
{
if (ERR_BACKEND_NO_ERR != get_error ())
push_error (ERR_BACKEND_LOCKED, {});
LEAVE("push error book is already open ");
return;
}
/* seriously invalid */
if (!new_book_id.size ())
{
if (ERR_BACKEND_NO_ERR != get_error ())
push_error (ERR_BACKEND_BAD_URL, {});
LEAVE("push error missing new_book_id");
return;
}
char * scheme {g_uri_parse_scheme (new_book_id.c_str ())};
char * filename {nullptr};
if (g_strcmp0 (scheme, "file") == 0)
filename = g_filename_from_uri (new_book_id.c_str (), nullptr, nullptr);
else if (!scheme)
filename = g_strdup (new_book_id.c_str ());
if (filename && g_file_test (filename, G_FILE_TEST_IS_DIR))
{
if (ERR_BACKEND_NO_ERR == get_error ())
push_error (ERR_BACKEND_BAD_URL, {});
g_free (filename);
g_free (scheme);
LEAVE("Can't open a directory");
return;
}
/* destroy the old backend */
destroy_backend ();
/* Store the session URL */
m_book_id = new_book_id;
if (filename)
load_backend ("file");
else /* access method found, load appropriate backend */
load_backend (scheme);
g_free (filename);
g_free (scheme);
/* No backend was found. That's bad. */
auto backend = qof_book_get_backend (m_book);
if (backend == nullptr)
{
m_book_id = {};
if (ERR_BACKEND_NO_ERR == get_error ())
push_error (ERR_BACKEND_BAD_URL, {});
LEAVE (" BAD: no backend: sess=%p book-id=%s",
this, new_book_id.c_str ());
return;
}
/* If there's a begin method, call that. */
backend->session_begin(this, m_book_id.c_str(), ignore_lock, create, force);
PINFO ("Done running session_begin on backend");
QofBackendError const err {backend->get_error()};
auto msg (backend->get_message());
if (err != ERR_BACKEND_NO_ERR)
{
m_book_id = {};
push_error (err, msg);
LEAVE (" backend error %d %s", err, msg.empty() ? "(null)" : msg.c_str());
return;
}
if (!msg.empty())
{
PWARN("%s", msg.c_str());
}
LEAVE (" sess=%p book-id=%s", this, new_book_id.c_str ());
}
void
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 != nullptr)
backend->session_end();
clear_error ();
m_book_id.clear();
LEAVE ("sess=%p book_id=%s", this, m_book_id.c_str ());
}
/* error handling functions --------------------------------*/
void
QofSessionImpl::clear_error () noexcept
{
m_last_err = ERR_BACKEND_NO_ERR;
m_error_message = {};
/* pop the stack on the backend as well. */
if (auto backend = qof_book_get_backend (m_book))
{
QofBackendError err = ERR_BACKEND_NO_ERR;
do
err = backend->get_error();
while (err != ERR_BACKEND_NO_ERR);
}
}
void
QofSessionImpl::push_error (QofBackendError const err, std::string message) noexcept
{
m_last_err = err;
m_error_message = message;
}
QofBackendError
QofSessionImpl::get_error () noexcept
{
/* if we have a local error, return that. */
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;
m_last_err = qof_be->get_error();
return m_last_err;
}
std::string
QofSessionImpl::get_error_message () const noexcept
{
return m_error_message;
}
QofBackendError
QofSessionImpl::pop_error () noexcept
{
QofBackendError err {get_error ()};
clear_error ();
return err;
}
/* Accessors (getters/setters) -----------------------------*/
QofBook *
QofSessionImpl::get_book () const noexcept
{
if (!m_book) return nullptr;
if ('y' == m_book->book_open)
return m_book;
return nullptr;
}
QofBackend *
QofSession::get_backend () const noexcept
{
return qof_book_get_backend (m_book);
}
std::string
QofSessionImpl::get_file_path () const noexcept
{
auto backend = qof_book_get_backend (m_book);
if (!backend) return nullptr;
return backend->get_uri();
}
std::string const &
QofSessionImpl::get_book_id () const noexcept
{
return m_book_id;
}
bool
QofSessionImpl::is_saving () const noexcept
{
return m_saving;
}
/* Manipulators (save, load, etc.) -------------------------*/
void
QofSessionImpl::save (QofPercentageFunc percentage_func) noexcept
{
m_saving = true;
ENTER ("sess=%p book_id=%s", this, m_book_id.c_str ());
/* If there is a backend, and the backend is reachable
* (i.e. we can communicate with it), then synchronize with
* the backend. If we cannot contact the backend (e.g.
* because we've gone offline, the network has crashed, etc.)
* then give the user the option to save to the local disk.
*
* hack alert -- FIXME -- XXX the code below no longer
* does what the words above say. This needs fixing.
*/
auto backend = qof_book_get_backend (m_book);
if (backend)
{
/* if invoked as SaveAs(), then backend not yet set */
qof_book_set_backend (m_book, backend);
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 ();
LEAVE("Success");
}
else
{
push_error (ERR_BACKEND_NO_HANDLER, "failod to load backend");
LEAVE("error -- No backend!");
}
m_saving = false;
}
void
QofSessionImpl::safe_save (QofPercentageFunc percentage_func) noexcept
{
auto backend = qof_book_get_backend (m_book);
if (!backend) return;
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 = nullptr;
push_error (err, msg);
}
}
void
QofSessionImpl::ensure_all_data_loaded () noexcept
{
auto backend = qof_book_get_backend (m_book);
if (!backend) return;
backend->load(m_book, LOAD_TYPE_LOAD_ALL);
push_error (backend->get_error(), {});
}
void
QofSessionImpl::swap_books (QofSessionImpl & other) noexcept
{
ENTER ("sess1=%p sess2=%p", this, &other);
// don't swap (that is, double-swap) read_only flags
std::swap (m_book->read_only, other.m_book->read_only);
std::swap (m_book, other.m_book);
auto mybackend = qof_book_get_backend (m_book);
qof_book_set_backend (m_book, qof_book_get_backend (other.m_book));
qof_book_set_backend (other.m_book, mybackend);
LEAVE (" ");
}
bool
QofSessionImpl::events_pending () const noexcept
{
return false;
}
bool
QofSessionImpl::process_events () const noexcept
{
return false;
}
/* XXX This exports the list of accounts to a file. It does not
* export any transactions. It's a place-holder until full
* book-closing is implemented.
*/
bool
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",
this, &real_session, real_book, m_book_id.c_str ());
/* There must be a backend or else. (It should always be the file
* backend too.)
*/
auto backend2 = qof_book_get_backend(m_book);
if (!backend2) return false;
backend2->set_percentage(percentage_func);
backend2->export_coa(real_book);
auto err = backend2->get_error();
if (err != ERR_BACKEND_NO_ERR)
return false;
return true;
}
/* C Wrapper Functions */
/* ====================================================================== */
const char *
qof_session_get_error_message (const QofSession * session)
{
if (!session) return "";
return session->get_error_message ().c_str ();
}
QofBackendError
qof_session_pop_error (QofSession * session)
{
if (!session) return ERR_BACKEND_NO_BACKEND;
return session->pop_error ();
}
QofBook *
qof_session_get_book (const QofSession *session)
{
if (!session) return NULL;
return session->get_book ();
}
const char *
qof_session_get_file_path (const QofSession *session)
{
if (!session) return NULL;
return session->get_file_path ().c_str ();
}
void
qof_session_ensure_all_data_loaded (QofSession *session)
{
if (session == nullptr) return;
return session->ensure_all_data_loaded ();
}
const char *
qof_session_get_url (const QofSession *session)
{
if (!session) return NULL;
return session->get_book_id ().c_str ();
}
QofBackend *
qof_session_get_backend (const QofSession *session)
{
if (!session) return NULL;
return session->get_backend ();
}
void
qof_session_begin (QofSession *session, const char * book_id,
gboolean ignore_lock, gboolean create, gboolean force)
{
if (!session) return;
session->begin((book_id ? book_id : ""), ignore_lock, create, force);
}
void
qof_session_load (QofSession *session,
QofPercentageFunc percentage_func)
{
if (!session) return;
session->load (percentage_func);
}
void
qof_session_save (QofSession *session,
QofPercentageFunc percentage_func)
{
if (!session) return;
session->save (percentage_func);
}
void
qof_session_safe_save(QofSession *session, QofPercentageFunc percentage_func)
{
if (!session) return;
session->safe_save (percentage_func);
}
gboolean
qof_session_save_in_progress(const QofSession *session)
{
if (!session) return false;
return session->is_saving ();
}
void
qof_session_end (QofSession *session)
{
if (!session) return;
session->end ();
}
void
qof_session_swap_data (QofSession *session_1, QofSession *session_2)
{
if (session_1 == session_2) return;
if (!session_1 || !session_2) return;
session_1->swap_books (*session_2);
}
gboolean
qof_session_events_pending (const QofSession *session)
{
if (!session) return false;
return session->events_pending ();
}
gboolean
qof_session_process_events (QofSession *session)
{
if (!session) return FALSE;
return session->process_events ();
}
gboolean
qof_session_export (QofSession *tmp_session,
QofSession *real_session,
QofPercentageFunc percentage_func)
{
if ((!tmp_session) || (!real_session)) return FALSE;
return tmp_session->export_session (*real_session, percentage_func);
}
/* ================= Static function access for testing ================= */
void init_static_qofsession_pointers (void);
void qof_session_load_backend (QofSession * session, const char * access_method)
{
session->load_backend (access_method);
}
void qof_session_clear_error (QofSession * session)
{
session->clear_error ();
}
void qof_session_destroy_backend (QofSession * session)
{
session->destroy_backend ();
}
void qof_session_set_book_id (QofSession * session, char const * book_id)
{
if (!book_id)
session->m_book_id = "";
else
session->m_book_id = book_id;
}
void (*p_qof_session_load_backend) (QofSession *, const char * access_method);
void (*p_qof_session_clear_error) (QofSession *);
void (*p_qof_session_destroy_backend) (QofSession *);
void (*p_qof_session_set_book_id) (QofSession *, char const * book_id);
void
init_static_qofsession_pointers (void)
{
p_qof_session_load_backend = &qof_session_load_backend;
p_qof_session_clear_error = &qof_session_clear_error;
p_qof_session_destroy_backend = &qof_session_destroy_backend;
p_qof_session_set_book_id = &qof_session_set_book_id;
}
QofBackendError
qof_session_get_error (QofSession * session)
{
if (!session) return ERR_BACKEND_NO_BACKEND;
return session->get_error();
}