gnucash/libgnucash/engine/qofsession.cpp

719 lines
19 KiB
C++
Raw Normal View History

/********************************************************************\
* 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
*/
2016-02-16 19:11:55 -06:00
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 "qofobject-p.h"
static QofLogModule log_module = QOF_MOD_SESSION;
2016-02-16 19:11:55 -06:00
} //extern 'C'
#include "qofbook-p.h"
#include "qof-backend.hpp"
#include "qofsession.hpp"
2016-02-16 19:11:55 -06:00
#include "gnc-backend-prov.hpp"
#include <vector>
#include <boost/algorithm/string.hpp>
2016-02-16 19:11:55 -06:00
#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
*/
2016-02-16 19:11:55 -06:00
ProviderVec& get_providers (void );
bool get_providers_initialized (void );
2016-02-16 19:11:55 -06:00
ProviderVec&
get_providers (void)
{
2016-02-16 19:11:55 -06:00
return s_providers;
}
2016-02-16 19:11:55 -06:00
bool
get_providers_initialized (void)
{
2016-02-16 19:11:55 -06:00
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 ();
}
2016-02-16 19:11:55 -06:00
/* Called from C so we have to keep the GList for now. */
GList*
qof_backend_get_registered_access_method_list(void)
{
GList* list = NULL;
2016-02-16 19:11:55 -06:00
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 {}
{
}
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 ());
2016-02-16 19:11:55 -06:00
continue;
}
PINFO (" Selected provider %s", prov->provider_name);
// Only do a type check when trying to open an existing file
// When saving over an existing file the contents of the original file don't matter
if (!m_creating && !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 ());
2016-02-16 19:11:55 -06:00
continue;
}
auto backend = prov->create_backend();
qof_book_set_backend (m_book, backend);
2016-02-16 19:11:55 -06:00
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
{
/* We must have an empty book to load into or bad things will happen. */
g_return_if_fail(m_book && qof_book_empty(m_book));
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. */
clear_error ();
auto be (qof_book_get_backend(m_book));
if (be)
{
be->set_percentage(percentage_func);
be->load (m_book, LOAD_TYPE_INITIAL_LOAD);
push_error (be->get_error(), {});
}
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))
{
auto old_book = m_book;
m_book = qof_book_new();
qof_book_destroy(old_book);
LEAVE ("error from backend %d", get_error ());
return;
}
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;
m_creating = create;
if (filename)
load_backend ("file");
Use a normalized uri format internally to refer to data stores. Data stores for GC can be a file (xml or sqlite3) or a database one some server (mysql or postgres). Wherever it makes sense internally, data stores will be referred to via a normalized uri: protocol://user:password@host:port/path Depending on the context and story type some of these parts are optional or unused. To achieve this, a new utility interface has been setup: gnc_uri_<xxx>_<yyy> that can be used to manipulate the uris or convert from non-normalized formats to normalized and back. For example, when the user selects a file in the Open or Save As dialog, gnc_uri_get_normalized_uri will convert the file into a normalized uri. Or when the actual filename is needed this can be extracted with gnc_uri_get_path. You can also test if a uri defines a file or something else with gnc_uri_is_file_uri. For the complete documentation, see src/core-utils/gnc-uri-uitls.h This commit installs gnc-uri-utils and modifies the source where it makes sense to use its convenience functions. This concerns all functions that had to deal with file access in some way or another, the history module and the functions that generate the history menu list and the window titles. Note that gnc-uri-utils replaces xaccResolveFilePath and xaccResolveUrl in all cases. xaccResolveUrl has been removed, because gnc-uri-utils fully replaces its functionality. xaccResolveFilePath is used internally in gnc-uri-utils to ensure an absolute path is always returned (in case of a file uri, not for db uris). But it has been renamed to gnc_resolve_file_path to be more consistent with the other functions. Lastly, this commit also adds a first implementation to work with a keyring to store and retrieve passwords, althoug git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@18842 57a11ea4-9604-0410-9ed3-97b8803252fd
2010-03-05 14:15:31 -06:00
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
{
if (!qof_book_session_not_saved (m_book)) //Clean book, nothing to do.
return;
m_saving = true;
ENTER ("sess=%p book_id=%s", this, m_book_id.c_str ());
2014-12-30 18:19:21 -06:00
/* If there is a backend, the book is dirty, 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 raise an error so that the controlling
* dialog can offer the user a chance to save in a different way.
*/
auto backend = qof_book_get_backend (m_book);
if (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, "failed 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);
}
static void
qof_session_clear_error (QofSession * session)
{
session->clear_error ();
}
static 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();
}