Implement flat bayes kvp

The bayes data are stored in the KVP store. Before this commit, they are
stored under /import-map-bayes/<token>/<account guid>/count (where count
is the datum that "matters" in bayes matching).

The problem with this is that any token including the kvp delimiter
(currently '/') gets divided, and is not found correctly during bayes
kvp searching. The quickest solution to this is to replace all "/"
characters with some other character. That has been done, along with a
re-structuring of the bayes matching code to take advantage of c++
features to make the code more concise and readable.

Also modified some test functions to fix leaks and double-frees: the
same kvp value can't be in the kvp tree twice.

Also, when I added code to clean up after the tests, some things started
breaking due to double-delete. Apparently const_cast was hiding some
programming errors. Really? You don't say? When giving a GUID* to KvpValue,
the latter takes ownership of the former.
This commit is contained in:
lmat
2017-10-19 15:42:32 -04:00
parent 08aa0104ef
commit b3667c76fc
11 changed files with 595 additions and 533 deletions

View File

@@ -1026,6 +1026,7 @@ RESTART:
// Convert imap mappings from account full name to guid strings
qof_event_suspend();
gnc_account_imap_convert_bayes (gnc_get_current_book());
gnc_account_imap_convert_flat (gnc_get_current_book());
qof_event_resume();
return TRUE;

View File

@@ -41,6 +41,7 @@
#include "gnc-pricedb.h"
#include "qofinstance-p.h"
#include "gnc-features.h"
#include "guid.hpp"
static QofLogModule log_module = GNC_MOD_ACCOUNT;
@@ -5181,48 +5182,6 @@ gnc_account_imap_delete_account (GncImportMatchMap *imap,
--------------------------------------------------------------------------*/
struct account_token_count
{
char* account_guid;
gint64 token_count; /**< occurrences of a given token for this account_guid */
};
/** total_count and the token_count for a given account let us calculate the
* probability of a given account with any single token
*/
struct token_accounts_info
{
GList *accounts; /**< array of struct account_token_count */
gint64 total_count;
};
/** gpointer is a pointer to a struct token_accounts_info
* \note Can always assume that keys are unique, reduces code in this function
*/
static void
buildTokenInfo(const char *key, const GValue *value, gpointer data)
{
struct token_accounts_info *tokenInfo = (struct token_accounts_info*)data;
struct account_token_count* this_account;
// PINFO("buildTokenInfo: account '%s', token_count: '%" G_GINT64_FORMAT "'", (char*)key,
// g_value_get_int64(value));
/* add the count to the total_count */
tokenInfo->total_count += g_value_get_int64(value);
/* allocate a new structure for this account and it's token count */
this_account = (struct account_token_count*)
g_new0(struct account_token_count, 1);
/* fill in the account guid and number of tokens found for this account guid */
this_account->account_guid = (char*)key;
this_account->token_count = g_value_get_int64(value);
/* append onto the glist a pointer to the new account_token_count structure */
tokenInfo->accounts = g_list_prepend(tokenInfo->accounts, this_account);
}
/** intermediate values used to calculate the bayes probability of a given account
where p(AB) = (a*b)/[a*b + (1-a)(1-b)], product is (a*b),
product_difference is (1-a) * (1-b)
@@ -5233,70 +5192,114 @@ struct account_probability
double product_difference; /* product of (1-probabilities) */
};
/** convert a hash table of account names and (struct account_probability*)
into a hash table of 100000x the percentage match value, ie. 10% would be
0.10 * 100000 = 10000
struct account_token_count
{
std::string account_guid;
int64_t token_count; /** occurrences of a given token for this account_guid */
};
/** total_count and the token_count for a given account let us calculate the
* probability of a given account with any single token
*/
#define PROBABILITY_FACTOR 100000
static void
buildProbabilities(gpointer key, gpointer value, gpointer data)
struct token_accounts_info
{
GHashTable *final_probabilities = (GHashTable*)data;
struct account_probability *account_p = (struct account_probability*)value;
/* P(AB) = A*B / [A*B + (1-A)*(1-B)]
* NOTE: so we only keep track of a running product(A*B*C...)
* and product difference ((1-A)(1-B)...)
*/
gint32 probability =
(account_p->product /
(account_p->product + account_p->product_difference))
* PROBABILITY_FACTOR;
PINFO("P('%s') = '%d'", (char*)key, probability);
g_hash_table_insert(final_probabilities, key, GINT_TO_POINTER(probability));
}
/** Frees an array of the same time that buildProperties built */
static void
freeProbabilities(gpointer key, gpointer value, gpointer data)
{
/* free up the struct account_probability that was allocated
* in gnc_account_find_account_bayes()
*/
g_free(value);
}
std::vector<account_token_count> accounts;
int64_t total_count;
};
/** holds an account guid and its corresponding integer probability
the integer probability is some factor of 10
*/
struct account_info
{
char* account_guid;
gint32 probability;
std::string account_guid;
int32_t probability;
};
/** Find the highest probability and the corresponding account guid
store in data, a (struct account_info*)
NOTE: this is a g_hash_table_foreach() function for a hash table of entries
key is a pointer to the account guid, value is a gint32, 100000x
the probability for this account
*/
static void
highestProbability(gpointer key, gpointer value, gpointer data)
build_token_info(char const * key, KvpValue * value, token_accounts_info & tokenInfo)
{
struct account_info *account_i = (struct account_info*)data;
/* if the current probability is greater than the stored, store the current */
if (GPOINTER_TO_INT(value) > account_i->probability)
{
/* Save the new highest probability and the assoaciated account guid */
account_i->probability = GPOINTER_TO_INT(value);
account_i->account_guid = static_cast <char*> (key);
}
tokenInfo.total_count += value->get<int64_t>();
account_token_count this_account;
std::string account_guid {key};
/*By convention, the key ends with the account GUID.*/
this_account.account_guid = account_guid.substr(account_guid.size() - GUID_ENCODING_LENGTH);
this_account.token_count = value->get<int64_t>();
tokenInfo.accounts.push_back(this_account);
}
/** We scale the probability values by PROBABILITY_FACTOR.
ie. with PROBABILITY_FACTOR of 100000, 10% would be
0.10 * 100000 = 10000 */
#define PROBABILITY_FACTOR 100000
static std::vector<std::pair<std::string, int32_t>>
build_probabilities(std::vector<std::pair<std::string, account_probability>> const & first_pass)
{
std::vector<std::pair<std::string, int32_t>> ret;
for (auto const & first_pass_prob : first_pass)
{
auto const & account_probability = first_pass_prob.second;
/* P(AB) = A*B / [A*B + (1-A)*(1-B)]
* NOTE: so we only keep track of a running product(A*B*C...)
* and product difference ((1-A)(1-B)...)
*/
int32_t probability = (account_probability.product /
(account_probability.product + account_probability.product_difference)) * PROBABILITY_FACTOR;
ret.push_back({first_pass_prob.first, probability});
}
return ret;
}
static account_info
highest_probability(std::vector<std::pair<std::string, int32_t>> const & probabilities)
{
account_info ret {"", std::numeric_limits<int32_t>::min()};
for (auto const & prob : probabilities)
if (prob.second > ret.probability)
ret = account_info{prob.first, prob.second};
return ret;
}
static std::vector<std::pair<std::string, account_probability>>
get_first_pass_probabilities(GncImportMatchMap * imap, GList * tokens)
{
std::vector<std::pair<std::string, account_probability>> ret;
/* find the probability for each account that contains any of the tokens
* in the input tokens list. */
for (auto current_token = tokens; current_token; current_token = current_token->next)
{
auto translated_token = std::string{static_cast<char const *>(current_token->data)};
std::replace(translated_token.begin(), translated_token.end(), '/', '-');
token_accounts_info tokenInfo{};
auto path = std::string{IMAP_FRAME_BAYES "-"} + translated_token;
qof_instance_foreach_slot_prefix (QOF_INSTANCE (imap->acc), path, &build_token_info, tokenInfo);
for (auto const & current_account_token : tokenInfo.accounts)
{
auto item = std::find_if(ret.begin(), ret.end(), [&current_account_token]
(std::pair<std::string, account_probability> const & a) {
return current_account_token.account_guid == a.first;
});
if (item != ret.end())
{/* This account is already in the map */
item->second.product = ((double)current_account_token.token_count /
(double)tokenInfo.total_count) * item->second.product;
item->second.product_difference = ((double)1 - ((double)current_account_token.token_count /
(double)tokenInfo.total_count)) * item->second.product_difference;
}
else
{
/* add a new entry */
account_probability new_probability;
new_probability.product = ((double)current_account_token.token_count /
(double)tokenInfo.total_count);
new_probability.product_difference = 1 - (new_probability.product);
ret.push_back({current_account_token.account_guid, std::move(new_probability)});
}
} /* for all accounts in tokenInfo */
}
return ret;
}
#define threshold (.90 * PROBABILITY_FACTOR) /* 90% */
@@ -5304,174 +5307,29 @@ highestProbability(gpointer key, gpointer value, gpointer data)
Account*
gnc_account_imap_find_account_bayes (GncImportMatchMap *imap, GList *tokens)
{
struct token_accounts_info tokenInfo; /**< holds the accounts and total
* token count for a single token */
GList *current_token; /**< pointer to the current
* token from the input GList
* tokens */
GList *current_account_token; /**< pointer to the struct
* account_token_count */
struct account_token_count *account_c; /**< an account name and the number
* of times a token has appeared
* for the account */
struct account_probability *account_p; /**< intermediate storage of values
* to compute the bayes probability
* of an account */
GHashTable *running_probabilities = g_hash_table_new(g_str_hash,
g_str_equal);
GHashTable *final_probabilities = g_hash_table_new(g_str_hash,
g_str_equal);
struct account_info account_i;
ENTER(" ");
/* check to see if the imap is NULL */
if (!imap)
{
PINFO("imap is null, returning null");
LEAVE(" ");
return NULL;
return nullptr;
auto first_pass = get_first_pass_probabilities(imap, tokens);
if (!first_pass.size())
return nullptr;
auto final_probabilities = build_probabilities(first_pass);
if (!final_probabilities.size())
return nullptr;
auto best = highest_probability(final_probabilities);
if (best.account_guid == "")
return nullptr;
if (best.probability < threshold)
return nullptr;
gnc::GUID guid;
try {
guid = gnc::GUID::from_string(best.account_guid);
} catch (gnc::guid_syntax_exception) {
return nullptr;
}
/* find the probability for each account that contains any of the tokens
* in the input tokens list
*/
for (current_token = tokens; current_token;
current_token = current_token->next)
{
char* path = g_strdup_printf (IMAP_FRAME_BAYES "/%s",
(char*)current_token->data);
/* zero out the token_accounts_info structure */
memset(&tokenInfo, 0, sizeof(struct token_accounts_info));
PINFO("token: '%s'", (char*)current_token->data);
/* process the accounts for this token, adding the account if it
* doesn't already exist or adding to the existing accounts token
* count if it does
*/
qof_instance_foreach_slot(QOF_INSTANCE (imap->acc), path,
buildTokenInfo, &tokenInfo);
g_free (path);
/* for each account we have just found, see if the account
* already exists in the list of account probabilities, if not
* add it
*/
for (current_account_token = tokenInfo.accounts; current_account_token;
current_account_token = current_account_token->next)
{
/* get the account name and corresponding token count */
account_c = (struct account_token_count*)current_account_token->data;
PINFO("account_c->account_guid('%s'), "
"account_c->token_count('%" G_GINT64_FORMAT
"')/total_count('%" G_GINT64_FORMAT "')",
account_c->account_guid, account_c->token_count,
tokenInfo.total_count);
account_p = static_cast <account_probability*> (
g_hash_table_lookup(running_probabilities, account_c->account_guid));
/* if the account exists in the list then continue
* the running probablities
*/
if (account_p)
{
account_p->product = (((double)account_c->token_count /
(double)tokenInfo.total_count)
* account_p->product);
account_p->product_difference =
((double)1 - ((double)account_c->token_count /
(double)tokenInfo.total_count))
* account_p->product_difference;
PINFO("product == %f, product_difference == %f",
account_p->product, account_p->product_difference);
}
else
{
/* add a new entry */
PINFO("adding a new entry for this account");
account_p = (struct account_probability*)
g_new0(struct account_probability, 1);
/* set the product and product difference values */
account_p->product = ((double)account_c->token_count /
(double)tokenInfo.total_count);
account_p->product_difference =
(double)1 - ((double)account_c->token_count /
(double)tokenInfo.total_count);
PINFO("product == %f, product_difference == %f",
account_p->product, account_p->product_difference);
/* add the account guid and (struct account_probability*)
* to the hash table */
g_hash_table_insert(running_probabilities,
account_c->account_guid, account_p);
}
} /* for all accounts in tokenInfo */
/* free the data in tokenInfo */
for (current_account_token = tokenInfo.accounts; current_account_token;
current_account_token = current_account_token->next)
{
/* free up each struct account_token_count we allocated */
g_free((struct account_token_count*)current_account_token->data);
}
g_list_free(tokenInfo.accounts); /* free the accounts GList */
}
/* build a hash table of account names and their final probabilities
* from each entry in the running_probabilties hash table
*/
g_hash_table_foreach(running_probabilities, buildProbabilities,
final_probabilities);
/* find the highest probabilty and the corresponding account */
memset(&account_i, 0, sizeof(struct account_info));
g_hash_table_foreach(final_probabilities, highestProbability, &account_i);
/* free each element of the running_probabilities hash */
g_hash_table_foreach(running_probabilities, freeProbabilities, NULL);
/* free the hash tables */
g_hash_table_destroy(running_probabilities);
g_hash_table_destroy(final_probabilities);
PINFO("highest P('%s') = '%d'",
account_i.account_guid ? account_i.account_guid : "(null)",
account_i.probability);
/* has this probability met our threshold? */
if (account_i.probability >= threshold)
{
GncGUID *guid;
Account *account = NULL;
PINFO("Probability has met threshold");
guid = g_new (GncGUID, 1);
if (string_to_guid (account_i.account_guid, guid))
account = xaccAccountLookup (guid, imap->book);
g_free (guid);
if (account != NULL)
LEAVE("Return account is '%s'", xaccAccountGetName (account));
else
LEAVE("Return NULL, account for Guid '%s' can not be found", account_i.account_guid);
return account;
}
PINFO("Probability has not met threshold");
LEAVE("Return NULL");
return NULL; /* we didn't meet our threshold, return NULL for an account */
auto account = xaccAccountLookup (reinterpret_cast<GncGUID*>(&guid), imap->book);
return account;
}
static void
change_imap_entry (GncImportMatchMap *imap, gchar *kvp_path, int64_t token_count)
{
@@ -5553,13 +5411,13 @@ gnc_account_imap_add_account_bayes (GncImportMatchMap *imap,
PINFO("adding token '%s'", (char*)current_token->data);
kvp_path = g_strdup_printf (IMAP_FRAME_BAYES "/%s/%s",
(char*)current_token->data,
std::string translated_token {static_cast<char*>(current_token->data)};
std::replace(translated_token.begin(), translated_token.end(), '/', '-');
kvp_path = g_strdup_printf (IMAP_FRAME_BAYES "-%s-%s",
translated_token.c_str(),
guid_string);
/* change the imap entry for the account */
change_imap_entry (imap, kvp_path, token_count);
g_free (kvp_path);
}
@@ -5575,13 +5433,15 @@ gnc_account_imap_add_account_bayes (GncImportMatchMap *imap,
/*******************************************************************************/
static void
build_bayes_layer_two (const char *key, const GValue *value, gpointer user_data)
build_non_bayes (const char *key, const GValue *value, gpointer user_data)
{
if (!G_VALUE_HOLDS_BOXED (value))
return;
QofBook *book;
Account *map_account = NULL;
GncGUID *guid;
GncGUID *guid = NULL;
gchar *kvp_path;
gchar *count;
gchar *guid_string = NULL;
struct imap_info *imapInfo_node;
@@ -5590,134 +5450,94 @@ build_bayes_layer_two (const char *key, const GValue *value, gpointer user_data)
// Get the book
book = qof_instance_get_book (imapInfo->source_account);
if (G_VALUE_HOLDS_INT64 (value))
{
PINFO("build_bayes_layer_two: account '%s', token_count: '%" G_GINT64_FORMAT "'",
(char*)key, g_value_get_int64(value));
guid = (GncGUID*)g_value_get_boxed (value);
guid_string = guid_to_string (guid);
count = g_strdup_printf ("%" G_GINT64_FORMAT, g_value_get_int64 (value));
}
else
count = g_strdup ("0");
PINFO("build_non_bayes: account '%s', match account guid: '%s'",
(char*)key, guid_string);
kvp_path = g_strconcat (imapInfo->category_head, "/", key, NULL);
PINFO("build_bayes_layer_two: kvp_path is '%s'", kvp_path);
guid = g_new (GncGUID, 1);
if (string_to_guid (key, guid))
map_account = xaccAccountLookup (guid, book);
g_free (guid);
PINFO("build_non_bayes: kvp_path is '%s'", kvp_path);
imapInfo_node = static_cast <imap_info*> (g_malloc(sizeof(*imapInfo_node)));
imapInfo_node->source_account = imapInfo->source_account;
imapInfo_node->map_account = map_account;
imapInfo_node->map_account = xaccAccountLookup (guid, book);
imapInfo_node->full_category = g_strdup (kvp_path);
imapInfo_node->match_string = g_strdup (imapInfo->match_string);
imapInfo_node->match_string = g_strdup (key);
imapInfo_node->category_head = g_strdup (imapInfo->category_head);
imapInfo_node->count = g_strdup (count);
imapInfo_node->count = g_strdup (" ");
imapInfo->list = g_list_append (imapInfo->list, imapInfo_node);
g_free (kvp_path);
g_free (guid_string);
}
static void
build_bayes_layer_two (const char *key, KvpValue * val, imap_info imapInfo)
{
QofBook *book;
Account *map_account = NULL;
GncGUID *guid;
gchar *kvp_path;
gchar *count;
struct imap_info *imapInfo_node;
// Get the book
book = qof_instance_get_book (imapInfo.source_account);
if (val->get_type() == KvpValue::Type::INT64)
{
PINFO("build_bayes_layer_two: account '%s', token_count: '%" G_GINT64_FORMAT "'",
key, val->get<int64_t>());
count = g_strdup_printf ("%" G_GINT64_FORMAT, val->get<int64_t>());
}
else
count = g_strdup ("0");
kvp_path = g_strconcat (imapInfo.category_head, "/", key, NULL);
PINFO("build_bayes_layer_two: kvp_path is '%s'", kvp_path);
guid = g_new (GncGUID, 1);
if (string_to_guid (key, guid))
map_account = xaccAccountLookup (guid, book);
g_free (guid);
imapInfo_node = static_cast <imap_info*> (g_malloc(sizeof(*imapInfo_node)));
imapInfo_node->source_account = imapInfo.source_account;
imapInfo_node->map_account = map_account;
imapInfo_node->full_category = g_strdup (kvp_path);
imapInfo_node->match_string = g_strdup (imapInfo.match_string);
imapInfo_node->category_head = g_strdup (imapInfo.category_head);
imapInfo_node->count = g_strdup (count);
imapInfo.list = g_list_append (imapInfo.list, imapInfo_node);
g_free (kvp_path);
g_free (count);
}
static void
build_bayes (const char *key, const GValue *value, gpointer user_data)
build_bayes (const char *key, KvpValue * value, imap_info & imapInfo)
{
gchar *kvp_path;
struct imap_info *imapInfo = (struct imap_info*)user_data;
struct imap_info imapInfol2;
struct imap_info imapInfol2;
PINFO("build_bayes: match string '%s'", (char*)key);
if (G_VALUE_HOLDS (value, G_TYPE_STRING) && g_value_get_string (value) == NULL)
{
kvp_path = g_strdup_printf (IMAP_FRAME_BAYES "/%s", key);
if (qof_instance_has_slot (QOF_INSTANCE(imapInfo->source_account), kvp_path))
{
PINFO("build_bayes: kvp_path is '%s', key '%s'", kvp_path, key);
imapInfol2.source_account = imapInfo->source_account;
imapInfol2.match_string = g_strdup (key);
imapInfol2.category_head = g_strdup (kvp_path);
imapInfol2.list = imapInfo->list;
qof_instance_foreach_slot (QOF_INSTANCE(imapInfo->source_account), kvp_path,
build_bayes_layer_two, &imapInfol2);
imapInfo->list = imapInfol2.list;
g_free (imapInfol2.match_string);
g_free (imapInfol2.category_head);
}
g_free (kvp_path);
}
std::string prefix {g_strdup_printf (IMAP_FRAME_BAYES "-%s", key)};
PINFO("build_bayes: prefix is '%s', key '%s'", prefix.c_str(), key);
imapInfol2.source_account = imapInfo.source_account;
imapInfol2.match_string = g_strdup (key);
imapInfol2.category_head = g_strdup (prefix.c_str());
imapInfol2.list = imapInfo.list;
qof_instance_foreach_slot_prefix (QOF_INSTANCE(imapInfo.source_account), prefix,
build_bayes_layer_two, imapInfol2);
imapInfo.list = imapInfol2.list;
g_free (imapInfol2.match_string);
g_free (imapInfol2.category_head);
}
static void
build_non_bayes (const char *key, const GValue *value, gpointer user_data)
{
if (G_VALUE_HOLDS_BOXED (value))
{
QofBook *book;
GncGUID *guid = NULL;
gchar *kvp_path;
gchar *guid_string = NULL;
struct imap_info *imapInfo_node;
struct imap_info *imapInfo = (struct imap_info*)user_data;
// Get the book
book = qof_instance_get_book (imapInfo->source_account);
guid = (GncGUID*)g_value_get_boxed (value);
guid_string = guid_to_string (guid);
PINFO("build_non_bayes: account '%s', match account guid: '%s'",
(char*)key, guid_string);
kvp_path = g_strconcat (imapInfo->category_head, "/", key, NULL);
PINFO("build_non_bayes: kvp_path is '%s'", kvp_path);
imapInfo_node = static_cast <imap_info*> (g_malloc(sizeof(*imapInfo_node)));
imapInfo_node->source_account = imapInfo->source_account;
imapInfo_node->map_account = xaccAccountLookup (guid, book);
imapInfo_node->full_category = g_strdup (kvp_path);
imapInfo_node->match_string = g_strdup (key);
imapInfo_node->category_head = g_strdup (imapInfo->category_head);
imapInfo_node->count = g_strdup (" ");
imapInfo->list = g_list_append (imapInfo->list, imapInfo_node);
g_free (kvp_path);
g_free (guid_string);
}
}
GList *
gnc_account_imap_get_info_bayes (Account *acc)
{
GList *list = NULL;
GncImapInfo imapInfo;
imapInfo.source_account = acc;
imapInfo.list = list;
if (qof_instance_has_slot (QOF_INSTANCE(acc), IMAP_FRAME_BAYES))
qof_instance_foreach_slot (QOF_INSTANCE(acc), IMAP_FRAME_BAYES,
build_bayes, &imapInfo);
/* A dummy object which is used to hold the specified account, and the list
* of data about which we care. */
GncImapInfo imapInfo {acc, nullptr};
qof_instance_foreach_slot_prefix (QOF_INSTANCE (acc), IMAP_FRAME_BAYES, &build_bayes, imapInfo);
return imapInfo.list;
}
@@ -5774,18 +5594,14 @@ void
gnc_account_delete_map_entry (Account *acc, char *full_category, gboolean empty)
{
gchar *kvp_path = g_strdup (full_category);
if ((acc != NULL) && qof_instance_has_slot (QOF_INSTANCE(acc), kvp_path))
{
xaccAccountBeginEdit (acc);
if (empty)
qof_instance_slot_delete_if_empty (QOF_INSTANCE(acc), kvp_path);
else
qof_instance_slot_delete (QOF_INSTANCE(acc), kvp_path);
PINFO("Account is '%s', path is '%s'", xaccAccountGetName (acc), kvp_path);
qof_instance_set_dirty (QOF_INSTANCE(acc));
xaccAccountCommitEdit (acc);
}
@@ -5796,11 +5612,12 @@ gnc_account_delete_map_entry (Account *acc, char *full_category, gboolean empty)
/*******************************************************************************/
static gchar *
look_for_old_separator_descendants (Account *root, gchar *full_name, const gchar *separator)
look_for_old_separator_descendants (Account *root, gchar const *full_name, const gchar *separator)
{
GList *top_accounts, *ptr;
gint found_len = 0;
gchar found_sep;
gchar * new_name = nullptr;
top_accounts = gnc_account_get_descendants (root);
@@ -5828,60 +5645,16 @@ look_for_old_separator_descendants (Account *root, gchar *full_name, const gchar
}
}
g_list_free (top_accounts); // Free the List
new_name = g_strdup (full_name);
if (found_len > 1)
full_name = g_strdelimit (full_name, &found_sep, *separator);
g_strdelimit (new_name, &found_sep, *separator);
PINFO("Return full_name is '%s'", full_name);
PINFO("Return full_name is '%s'", new_name);
return full_name;
return new_name;
}
static void
convert_imap_entry (GncImapInfo *imapInfo, Account *map_account)
{
GncImportMatchMap *imap;
gchar *guid_string;
gchar *kvp_path;
int64_t token_count = 1;
GValue value = G_VALUE_INIT;
// Create an ImportMatchMap object
imap = gnc_account_imap_create_imap (imapInfo->source_account);
xaccAccountBeginEdit (imapInfo->source_account);
guid_string = guid_to_string (xaccAccountGetGUID (map_account));
PINFO("Map Account is '%s', GUID is '%s', Count is %s", xaccAccountGetName (map_account),
guid_string, imapInfo->count);
// save converting string, get the count value
qof_instance_get_kvp (QOF_INSTANCE (imapInfo->source_account), imapInfo->full_category, &value);
if (G_VALUE_HOLDS_INT64 (&value))
token_count = g_value_get_int64 (&value);
// Delete the old entry based on full account name
kvp_path = g_strdup (imapInfo->full_category);
gnc_account_delete_map_entry (imapInfo->source_account, kvp_path, FALSE);
// create path based on guid
kvp_path = g_strdup_printf ("/%s/%s", imapInfo->category_head, guid_string);
// change the imap entry of source_account
change_imap_entry (imap, kvp_path, token_count);
qof_instance_set_dirty (QOF_INSTANCE (imapInfo->source_account));
xaccAccountCommitEdit (imapInfo->source_account);
g_free (kvp_path);
g_free (guid_string);
}
static Account *
look_for_old_mapping (GncImapInfo *imapInfo)
{
@@ -5905,7 +5678,9 @@ look_for_old_mapping (GncImapInfo *imapInfo)
// do we have a valid account, if not, look for old separator
if (map_account == NULL)
{
full_name = look_for_old_separator_descendants (root, full_name, sep);
gchar * temp_name = look_for_old_separator_descendants (root, full_name, sep);
g_free(full_name);
full_name = temp_name;
map_account = gnc_account_lookup_by_full_name (root, full_name); // lets try again
}
@@ -5916,85 +5691,151 @@ look_for_old_mapping (GncImapInfo *imapInfo)
return map_account;
}
static void
convert_imap_account (Account *acc)
static std::vector<std::pair<std::string, KvpValue*>>
get_new_guid_imap (Account * acc)
{
GList *imap_list, *node;
gchar *acc_name = NULL;
auto frame = qof_instance_get_slots (QOF_INSTANCE (acc));
auto slot = frame->get_slot(IMAP_FRAME_BAYES);
if (!slot)
return {};
std::string const imap_frame_str {IMAP_FRAME_BAYES};
std::vector<std::pair<std::string, KvpValue*>> ret;
auto root = gnc_account_get_root (acc);
auto imap_frame = slot->get<KvpFrame*>();
imap_frame->for_each_slot_temp ([&ret, root, &imap_frame_str] (char const * token, KvpValue* val) {
auto token_frame = val->get<KvpFrame*>();
token_frame->for_each_slot_temp ([&ret, root, &imap_frame_str, token] (char const * account_name, KvpValue* val) {
auto map_account = gnc_account_lookup_by_full_name (root, account_name);
if (!map_account)
{
auto temp_account_name = look_for_old_separator_descendants (root, account_name, gnc_get_account_separator_string());
map_account = gnc_account_lookup_by_full_name (root, temp_account_name);
g_free (temp_account_name);
}
auto temp_guid = gnc::GUID{*xaccAccountGetGUID (map_account)};
auto guid_str = temp_guid.to_string();
ret.push_back({imap_frame_str + "-" + token + "-" + guid_str, val});
});
});
return ret;
}
acc_name = gnc_account_get_full_name (acc);
PINFO("Source Acc '%s'", acc_name);
static void
convert_imap_account_bayes_to_guid (Account *acc)
{
auto frame = qof_instance_get_slots (QOF_INSTANCE (acc));
if (!frame->get_keys().size())
return;
auto new_imap = get_new_guid_imap(acc);
xaccAccountBeginEdit(acc);
frame->set(IMAP_FRAME_BAYES, nullptr);
std::for_each(new_imap.begin(), new_imap.end(), [&frame] (std::pair<std::string, KvpValue*> const & entry) {
frame->set(entry.first.c_str(), entry.second);
});
qof_instance_set_dirty (QOF_INSTANCE (acc));
xaccAccountCommitEdit(acc);
}
imap_list = gnc_account_imap_get_info_bayes (acc);
char const * run_once_key_to_guid {"changed-bayesian-to-guid"};
char const * run_once_key_to_flat {"changed-bayesian-to-flat"};
if (g_list_length (imap_list) > 0) // we have mappings
static void
imap_convert_bayes_to_guid (QofBook * book)
{
auto root = gnc_book_get_root_account (book);
auto accts = gnc_account_get_descendants_sorted (root);
for (auto ptr = accts; ptr; ptr = g_list_next (ptr))
{
PINFO("List length is %d", g_list_length (imap_list));
xaccAccountBeginEdit(acc);
for (node = imap_list; node; node = g_list_next (node))
{
Account *map_account = NULL;
GncImapInfo *imapInfo = static_cast <GncImapInfo *> (node->data);
// Lets start doing stuff
map_account = look_for_old_mapping (imapInfo);
if (map_account != NULL) // we have an account, try and update it
convert_imap_entry (imapInfo, map_account);
// Free the members and structure
g_free (imapInfo->category_head);
g_free (imapInfo->full_category);
g_free (imapInfo->match_string);
g_free (imapInfo->count);
g_free (imapInfo);
}
xaccAccountCommitEdit(acc);
Account *acc = static_cast <Account*> (ptr->data);
convert_imap_account_bayes_to_guid (acc);
}
g_free (acc_name);
g_list_free (imap_list); // Free the List
g_list_free (accts);
}
static std::vector<std::pair<std::string, KvpValue*>>
get_new_flat_imap (Account * acc)
{
auto frame = qof_instance_get_slots (QOF_INSTANCE (acc));
auto slot = frame->get_slot(IMAP_FRAME_BAYES);
if (!slot)
return {};
std::string const imap_frame_str {IMAP_FRAME_BAYES};
std::vector<std::pair<std::string, KvpValue*>> ret;
auto root = gnc_account_get_root (acc);
auto imap_frame = slot->get<KvpFrame*>();
imap_frame->for_each_slot_temp ([&ret, &imap_frame_str] (char const * token, KvpValue* val) {
auto token_frame = val->get<KvpFrame*>();
token_frame->for_each_slot_temp ([&ret, &imap_frame_str, token] (char const * account_guid, KvpValue* val) {
ret.push_back({imap_frame_str + "-" + token + "-" + account_guid, val});
});
});
return ret;
}
static void
convert_imap_account_bayes_to_flat (Account * acc)
{
auto flat_imap = get_new_flat_imap (acc);
if (!flat_imap.size())
return;
auto frame = qof_instance_get_slots (QOF_INSTANCE (acc));
xaccAccountBeginEdit(acc);
frame->set(IMAP_FRAME_BAYES, nullptr);
std::for_each(flat_imap.begin(), flat_imap.end(), [&frame] (std::pair<std::string, KvpValue*> const & entry) {
frame->set(entry.first.c_str(), entry.second);
});
qof_instance_set_dirty (QOF_INSTANCE (acc));
xaccAccountCommitEdit(acc);
}
static void
imap_convert_bayes_to_flat (QofBook * book)
{
auto root = gnc_book_get_root_account (book);
auto accts = gnc_account_get_descendants_sorted (root);
for (auto ptr = accts; ptr; ptr = g_list_next (ptr))
{
Account *acc = static_cast <Account*> (ptr->data);
convert_imap_account_bayes_to_flat (acc);
}
g_list_free (accts);
}
static bool
run_once_key_set (char const * key, QofBook * book)
{
GValue value G_VALUE_INIT;
qof_instance_get_kvp (QOF_INSTANCE(book), key, &value);
return G_VALUE_HOLDS_STRING(&value) && strcmp(g_value_get_string(&value), "true");
}
static void
set_run_once_key (char const * key, QofBook * book)
{
GValue value G_VALUE_INIT;
g_value_init(&value, G_TYPE_BOOLEAN);
g_value_set_boolean(&value, TRUE);
qof_instance_set_kvp(QOF_INSTANCE (book), key, &value);
}
void
gnc_account_imap_convert_flat (QofBook *book)
{
if (run_once_key_set(run_once_key_to_flat, book))
return;
imap_convert_bayes_to_flat (book);
set_run_once_key(run_once_key_to_flat, book);
}
void
gnc_account_imap_convert_bayes (QofBook *book)
{
Account *root;
GList *accts, *ptr;
gboolean run_once = FALSE;
GValue value_s = G_VALUE_INIT;
// get the run-once value
qof_instance_get_kvp (QOF_INSTANCE (book), "changed-bayesian-to-guid", &value_s);
if (G_VALUE_HOLDS_STRING (&value_s) && (strcmp(g_value_get_string (&value_s), "true") == 0))
run_once = TRUE;
if (run_once == FALSE)
{
GValue value_b = G_VALUE_INIT;
/* Get list of Accounts */
root = gnc_book_get_root_account (book);
accts = gnc_account_get_descendants_sorted (root);
/* Go through list of accounts */
for (ptr = accts; ptr; ptr = g_list_next (ptr))
{
Account *acc = static_cast <Account*> (ptr->data);
convert_imap_account (acc);
}
g_list_free (accts);
g_value_init (&value_b, G_TYPE_BOOLEAN);
g_value_set_boolean (&value_b, TRUE);
// set the run-once value
qof_instance_set_kvp (QOF_INSTANCE (book), "changed-bayesian-to-guid", &value_b);
}
if (run_once_key_set(run_once_key_to_guid, book))
return;
imap_convert_bayes_to_guid(book);
set_run_once_key(run_once_key_to_guid, book);
}
/* ================================================================ */
/* QofObject function implementation and registration */

View File

@@ -1454,6 +1454,10 @@ void gnc_account_delete_map_entry (Account *acc, char *full_category, gboolean e
*/
void gnc_account_imap_convert_bayes (QofBook *book);
/** Change the bayes imap entries from a nested representation to a flat representation.
*/
void gnc_account_imap_convert_flat (QofBook *);
/** @} */

View File

@@ -356,6 +356,21 @@ GUID::from_string (std::string const & str)
}
}
bool
GUID::is_valid_guid (std::string const & str)
{
try
{
static boost::uuids::string_generator strgen;
auto a = strgen (str);
return true;
}
catch (...)
{
return false;
}
}
guid_syntax_exception::guid_syntax_exception () noexcept
: invalid_argument {"Invalid syntax for guid."}
{

View File

@@ -50,6 +50,7 @@ struct GUID
static GUID create_random () noexcept;
static GUID const & null_guid () noexcept;
static GUID from_string (std::string const &);
static bool is_valid_guid (std::string const &);
std::string to_string () const noexcept;
auto begin () const noexcept -> decltype (implementation.begin ());
auto end () const noexcept -> decltype (implementation.end ());

View File

@@ -472,4 +472,30 @@ gnc_value_list_get_type (void)
return type;
}
/* ========================== END OF FILE ======================= */
void
KvpFrame::flatten_kvp_impl(std::vector<std::string> path, std::vector<std::pair<std::string, KvpValue*>> & entries) const
{
for (auto const & entry : m_valuemap)
{
if (entry.second->get_type() == KvpValue::Type::FRAME)
{
std::vector<std::string> send_path {path};
send_path.push_back("/");
send_path.push_back(entry.first);
entry.second->get<KvpFrame*>()->flatten_kvp_impl(send_path, entries);
}
else
{
std::string flat_path {std::accumulate(path.begin(), path.end(), std::string{})};
entries.emplace_back(flat_path + "/" + entry.first, entry.second);
}
}
}
std::vector<std::pair<std::string, KvpValue*>>
KvpFrame::flatten_kvp(void) const
{
std::vector<std::pair<std::string, KvpValue*>> ret;
flatten_kvp_impl({}, ret);
return ret;
}

View File

@@ -93,6 +93,8 @@
#include <string>
#include <vector>
#include <cstring>
#include <algorithm>
#include <iostream>
using Path = std::vector<std::string>;
/** Implements KvpFrame.
@@ -192,7 +194,7 @@ struct KvpFrameImpl
* prefixed to every item in the frame.
* @return A std::string representing all the children of the frame.
*/
std::string to_string(std::string const & prefix) const noexcept;
std::string to_string(std::string const &) const noexcept;
/**
* Report the keys in the immediate frame. Be sensible about using this, it
* isn't a very efficient way to iterate.
@@ -211,14 +213,34 @@ struct KvpFrameImpl
*/
KvpValue* get_slot(Path keys) const noexcept;
void for_each_slot(void (*)(char const *key, KvpValue*, void*data), void*data) const noexcept;
void for_each_slot(void (*proc)(const char *key, KvpValue *, void *data), void* data) const noexcept;
/** The function should be of the form:
* <anything> func (char const *, KvpValue *, data_type &);
* Do not pass nullptr for the function.
* Do not pass nullptr as the function.
*/
template <typename func_type, typename data_type>
void for_each_slot_temp (func_type const &, data_type &) const noexcept;
void for_each_slot_temp(func_type const &, data_type &) const noexcept;
template <typename func_type>
void for_each_slot_temp(func_type const &) const noexcept;
/**
* Like for_each_slot, but doesn't traverse nested values. This will only loop
* over root-level values whose keys match the specified prefix.
*/
template <typename func_type, typename data_type>
void for_each_slot_prefix(std::string const & prefix, func_type const &, data_type &) const noexcept;
template <typename func_type>
void for_each_slot_prefix(std::string const & prefix, func_type const &) const noexcept;
/**
* Returns all keys and values of this frame recursively, flattening
* the frame-containing values.
*/
std::vector<std::pair<std::string, KvpValue*>>
flatten_kvp(void) const;
/** Test for emptiness
* @return true if the frame contains nothing.
@@ -228,8 +250,55 @@ struct KvpFrameImpl
private:
map_type m_valuemap;
void flatten_kvp_impl(std::vector<std::string>, std::vector<std::pair<std::string, KvpValue*>> &) const;
};
template<typename func_type>
void KvpFrame::for_each_slot_prefix(std::string const & prefix,
func_type const & func) const noexcept
{
std::for_each (m_valuemap.begin(), m_valuemap.end(),
[&prefix,&func](const KvpFrameImpl::map_type::value_type & a)
{
std::string temp_key {a.first};
if (temp_key.size() < prefix.size())
return;
/* Testing for prefix matching */
if (std::mismatch(prefix.begin(), prefix.end(), temp_key.begin()).first == prefix.end())
func (a.first, a.second);
}
);
}
template<typename func_type, typename data_type>
void KvpFrame::for_each_slot_prefix(std::string const & prefix,
func_type const & func, data_type & data) const noexcept
{
std::for_each (m_valuemap.begin(), m_valuemap.end(),
[&prefix,&func,&data](const KvpFrameImpl::map_type::value_type & a)
{
std::string temp_key {a.first};
if (temp_key.size() < prefix.size())
return;
/* Testing for prefix matching */
if (std::mismatch(prefix.begin(), prefix.end(), temp_key.begin()).first == prefix.end())
func (a.first, a.second, data);
}
);
}
template <typename func_type>
void KvpFrame::for_each_slot_temp(func_type const & func) const noexcept
{
std::for_each (m_valuemap.begin(), m_valuemap.end(),
[&func](const KvpFrameImpl::map_type::value_type & a)
{
func (a.first, a.second);
}
);
}
template <typename func_type, typename data_type>
void KvpFrame::for_each_slot_temp(func_type const & func, data_type & data) const noexcept
{
@@ -241,7 +310,6 @@ void KvpFrame::for_each_slot_temp(func_type const & func, data_type & data) cons
);
}
int compare (const KvpFrameImpl &, const KvpFrameImpl &) noexcept;
int compare (const KvpFrameImpl *, const KvpFrameImpl *) noexcept;
/** @} Doxygen Group */

View File

@@ -160,6 +160,10 @@ void qof_instance_foreach_slot (const QofInstance *inst, const char *path,
#ifdef __cplusplus
} /* extern "C" */
/** Returns all keys that match the given prefix and their corresponding values.*/
std::map<std::string, KvpValue*>
qof_instance_get_slots_prefix (QofInstance const *, std::string const & prefix);
/* Don't pass nullptr as the function */
template<typename func_type, typename data_type>
void qof_instance_foreach_slot_temp (QofInstance const * inst, std::string const & path,
@@ -171,6 +175,18 @@ void qof_instance_foreach_slot_temp (QofInstance const * inst, std::string const
auto frame = slot->get<KvpFrame*>();
frame->for_each_slot(func, data);
}
/**
* Similar to qof_instance_foreach_slot, but we don't traverse the depth of the key value frame,
* we only check the root level for keys that match the specified prefix.
*/
template<typename func_type, typename data_type>
void qof_instance_foreach_slot_prefix(QofInstance const * inst, std::string const & path_prefix,
func_type const & func, data_type & data)
{
inst->kvp_data->for_each_slot_prefix(path_prefix, func, data);
}
#endif
#endif /* QOF_INSTANCE_P_H */

View File

@@ -30,6 +30,7 @@ extern "C"
#include <qofinstance-p.h>
#include <kvp-frame.hpp>
#include <gtest/gtest.h>
#include <string>
class ImapTest : public testing::Test
{
@@ -67,7 +68,11 @@ protected:
gnc_account_append_child(t_expense_account, t_expense_account2);
}
void TearDown() {
qof_book_destroy (gnc_account_get_book (t_bank_account));
auto root = gnc_account_get_root (t_bank_account);
auto book = gnc_account_get_book (root);
xaccAccountBeginEdit (root);
xaccAccountDestroy (root);
qof_book_destroy (book);
}
Account *t_bank_account {};
Account *t_sav_account {};
@@ -110,18 +115,16 @@ protected:
TEST_F(ImapPlainTest, FindAccount)
{
auto root = qof_instance_get_slots(QOF_INSTANCE(t_bank_account));
auto acc1_val = new KvpValue(const_cast<GncGUID*>(xaccAccountGetGUID(t_expense_account1)));
auto acc2_val = new KvpValue(const_cast<GncGUID*>(xaccAccountGetGUID(t_expense_account2)));
auto acc1_val = new KvpValue(guid_copy(xaccAccountGetGUID(t_expense_account1)));
auto acc2_val = new KvpValue(guid_copy(xaccAccountGetGUID(t_expense_account2)));
root->set_path({IMAP_FRAME, "foo", "bar"}, acc1_val);
root->set_path({IMAP_FRAME, "baz", "waldo"}, acc2_val);
root->set_path({IMAP_FRAME, "pepper"}, acc1_val);
root->set_path({IMAP_FRAME, "salt"}, acc2_val);
root->set_path({IMAP_FRAME, "pepper"}, new KvpValue{*acc1_val});
root->set_path({IMAP_FRAME, "salt"}, new KvpValue{*acc2_val});
EXPECT_EQ(t_expense_account1, gnc_account_imap_find_account(t_imap, "foo", "bar"));
EXPECT_EQ(t_expense_account2,
gnc_account_imap_find_account(t_imap, "baz", "waldo"));
EXPECT_EQ(t_expense_account1,
gnc_account_imap_find_account(t_imap, NULL, "pepper"));
EXPECT_EQ(t_expense_account2, gnc_account_imap_find_account(t_imap, "baz", "waldo"));
EXPECT_EQ(t_expense_account1, gnc_account_imap_find_account(t_imap, NULL, "pepper"));
EXPECT_EQ(t_expense_account2, gnc_account_imap_find_account(t_imap, NULL, "salt"));
EXPECT_EQ(nullptr, gnc_account_imap_find_account(t_imap, "salt", NULL));
}
@@ -251,12 +254,12 @@ TEST_F(ImapBayesTest, FindAccountBayes)
auto acct2_guid = guid_to_string (xaccAccountGetGUID(t_expense_account2));
auto value = new KvpValue(INT64_C(42));
root->set_path({IMAP_FRAME_BAYES, foo, acct1_guid}, value);
root->set_path({IMAP_FRAME_BAYES, bar, acct1_guid}, value);
root->set_path({IMAP_FRAME_BAYES, baz, acct2_guid}, value);
root->set_path({IMAP_FRAME_BAYES, waldo, acct2_guid}, value);
root->set_path({IMAP_FRAME_BAYES, pepper, acct1_guid}, value);
root->set_path({IMAP_FRAME_BAYES, salt, acct2_guid}, value);
root->set_path((std::string{IMAP_FRAME_BAYES} + "-" + foo + "-" + acct1_guid).c_str(), value);
root->set_path((std::string{IMAP_FRAME_BAYES} + "-" + bar + "-" + acct1_guid).c_str(), new KvpValue{*value});
root->set_path((std::string{IMAP_FRAME_BAYES} + "-" + baz + "-" + acct2_guid).c_str(), new KvpValue{*value});
root->set_path((std::string{IMAP_FRAME_BAYES} + "-" + waldo + "-" + acct2_guid).c_str(), new KvpValue{*value});
root->set_path((std::string{IMAP_FRAME_BAYES} + "-" + pepper + "-" + acct1_guid).c_str(), new KvpValue{*value});
root->set_path((std::string{IMAP_FRAME_BAYES} + "-" + salt + "-" + acct2_guid).c_str(), new KvpValue{*value});
auto account = gnc_account_imap_find_account_bayes(t_imap, t_list1);
EXPECT_EQ(t_expense_account1, account);
@@ -289,29 +292,29 @@ TEST_F(ImapBayesTest, AddAccountBayes)
auto root = qof_instance_get_slots(QOF_INSTANCE(t_bank_account));
auto acct1_guid = guid_to_string (xaccAccountGetGUID(t_expense_account1));
auto acct2_guid = guid_to_string (xaccAccountGetGUID(t_expense_account2));
auto value = root->get_slot({IMAP_FRAME_BAYES, "foo", "bar"});
auto value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-foo-bar").c_str());
auto check_account = [this](KvpValue* v) {
return (v->get<const char*>(), this->t_imap->book); };
value = root->get_slot({IMAP_FRAME_BAYES, foo, acct1_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + foo + "-" + acct1_guid).c_str());
EXPECT_EQ(1, value->get<int64_t>());
value = root->get_slot({IMAP_FRAME_BAYES, bar, acct1_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + bar + "-" + acct1_guid).c_str());
EXPECT_EQ(1, value->get<int64_t>());
value = root->get_slot({IMAP_FRAME_BAYES, baz, acct2_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + baz + "-" + acct2_guid).c_str());
EXPECT_EQ(1, value->get<int64_t>());
value = root->get_slot({IMAP_FRAME_BAYES, waldo, acct2_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + waldo + "-" + acct2_guid).c_str());
EXPECT_EQ(1, value->get<int64_t>());
value = root->get_slot({IMAP_FRAME_BAYES, pepper, acct1_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + pepper + "-" + acct1_guid).c_str());
EXPECT_EQ(1, value->get<int64_t>());
value = root->get_slot({IMAP_FRAME_BAYES, salt, acct2_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + salt + "-" + acct2_guid).c_str());
EXPECT_EQ(1, value->get<int64_t>());
value = root->get_slot({IMAP_FRAME_BAYES, baz, acct1_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + baz + "-" + acct1_guid).c_str());
EXPECT_EQ(nullptr, value);
qof_instance_increase_editlevel(QOF_INSTANCE(t_bank_account));
gnc_account_imap_add_account_bayes(t_imap, t_list2, t_expense_account2);
qof_instance_mark_clean(QOF_INSTANCE(t_bank_account));
qof_instance_reset_editlevel(QOF_INSTANCE(t_bank_account));
value = root->get_slot({IMAP_FRAME_BAYES, baz, acct2_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + baz + "-" + acct2_guid).c_str());
EXPECT_EQ(2, value->get<int64_t>());
}
@@ -336,22 +339,22 @@ TEST_F(ImapBayesTest, ConvertAccountBayes)
auto val3 = new KvpValue(static_cast<int64_t>(2));
// Test for existing entries, all will be 1
auto value = root->get_slot({IMAP_FRAME_BAYES, foo, acct1_guid});
auto value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + foo + "-" + acct1_guid).c_str());
EXPECT_EQ(1, value->get<int64_t>());
value = root->get_slot({IMAP_FRAME_BAYES, bar, acct1_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + bar + "-" + acct1_guid).c_str());
EXPECT_EQ(1, value->get<int64_t>());
value = root->get_slot({IMAP_FRAME_BAYES, baz, acct2_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + baz + "-" + acct2_guid).c_str());
EXPECT_EQ(1, value->get<int64_t>());
value = root->get_slot({IMAP_FRAME_BAYES, waldo, acct2_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + waldo + "-" + acct2_guid).c_str());
EXPECT_EQ(1, value->get<int64_t>());
// Set up some old entries
root->set_path({IMAP_FRAME_BAYES, pepper, "Asset-Bank"}, val1);
root->set_path({IMAP_FRAME_BAYES, salt, "Asset-Bank#Bank"}, val1);
root->set_path({IMAP_FRAME_BAYES, salt, "Asset>Bank#Bank"}, val2);
root->set_path({IMAP_FRAME_BAYES, pork, "Expense#Food"}, val2);
root->set_path({IMAP_FRAME_BAYES, sausage, "Expense#Drink"}, val3);
root->set_path({IMAP_FRAME_BAYES, foo, "Expense#Food"}, val2);
root->set_path((std::string{IMAP_FRAME_BAYES} + "/" + pepper + "/" + "Asset-Bank").c_str(), val1);
root->set_path((std::string{IMAP_FRAME_BAYES} + "/" + salt + "/" + "Asset-Bank#Bank").c_str(), new KvpValue{*val1});
root->set_path((std::string{IMAP_FRAME_BAYES} + "/" + salt + "/" + "Asset>Bank#Bank").c_str(), val2);
root->set_path((std::string{IMAP_FRAME_BAYES} + "/" + pork + "/" + "Expense#Food").c_str(), new KvpValue{*val2});
root->set_path((std::string{IMAP_FRAME_BAYES} + "/" + sausage + "/" + "Expense#Drink").c_str(), val3);
root->set_path((std::string{IMAP_FRAME_BAYES} + "/" + foo + "/" + "Expense#Food").c_str(), new KvpValue{*val2});
EXPECT_EQ(1, qof_instance_get_editlevel(QOF_INSTANCE(t_bank_account)));
EXPECT_TRUE(qof_instance_get_dirty_flag(QOF_INSTANCE(t_bank_account)));
@@ -361,24 +364,24 @@ TEST_F(ImapBayesTest, ConvertAccountBayes)
gnc_account_imap_convert_bayes (t_imap->book);
// convert from 'Asset-Bank' to 'Asset-Bank' guid
value = root->get_slot({IMAP_FRAME_BAYES, pepper, acct3_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + pepper + "-" + acct3_guid).c_str());
EXPECT_EQ(10, value->get<int64_t>());
// convert from 'Asset-Bank#Bank' to 'Sav Bank' guid
value = root->get_slot({IMAP_FRAME_BAYES, salt, acct4_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + salt + "-" + acct4_guid).c_str());
EXPECT_EQ(10, value->get<int64_t>());
// convert from 'Expense#Food' to 'Food' guid
value = root->get_slot({IMAP_FRAME_BAYES, pork, acct1_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + pork + "-" + acct1_guid).c_str());
EXPECT_EQ(5, value->get<int64_t>());
// convert from 'Expense#Drink' to 'Drink' guid
value = root->get_slot({IMAP_FRAME_BAYES, sausage, acct2_guid});
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + sausage + "-" + acct2_guid).c_str());
EXPECT_EQ(2, value->get<int64_t>());
// convert from 'Expense#Food' to 'Food' guid but add to original value
value = root->get_slot({IMAP_FRAME_BAYES, foo, acct1_guid});
EXPECT_EQ(6, value->get<int64_t>());
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + foo + "-" + acct1_guid).c_str());
EXPECT_EQ(5, value->get<int64_t>());
// Check for run once flag
auto vals = book->get_slot("changed-bayesian-to-guid");
@@ -388,4 +391,53 @@ TEST_F(ImapBayesTest, ConvertAccountBayes)
EXPECT_TRUE(qof_instance_get_dirty_flag(QOF_INSTANCE(t_bank_account)));
}
TEST_F (ImapBayesTest, convert_map_flat)
{
// prevent the embedded beginedit/committedit from doing anything
qof_instance_increase_editlevel(QOF_INSTANCE(t_bank_account));
qof_instance_mark_clean(QOF_INSTANCE(t_bank_account));
//gnc_account_imap_add_account_bayes(t_imap, t_list1, t_expense_account1); //Food
//gnc_account_imap_add_account_bayes(t_imap, t_list2, t_expense_account2); //Drink
auto root = qof_instance_get_slots(QOF_INSTANCE(t_bank_account));
auto acct1_guid = guid_to_string (xaccAccountGetGUID(t_expense_account1)); //Food
auto acct2_guid = guid_to_string (xaccAccountGetGUID(t_expense_account2)); //Drink
auto acct3_guid = guid_to_string (xaccAccountGetGUID(t_asset_account2)); //Asset-Bank
auto acct4_guid = guid_to_string (xaccAccountGetGUID(t_sav_account)); //Sav Bank
auto val1 = new KvpValue(static_cast<int64_t>(10));
auto val2 = new KvpValue(static_cast<int64_t>(5));
auto val3 = new KvpValue(static_cast<int64_t>(2));
// Set up some old entries
root->set_path((std::string{IMAP_FRAME_BAYES} + "/" + pepper + "/" + acct1_guid).c_str(), val1);
root->set_path((std::string{IMAP_FRAME_BAYES} + "/" + salt + "/" + acct2_guid).c_str(), new KvpValue{*val1});
root->set_path((std::string{IMAP_FRAME_BAYES} + "/" + foo + "/" + acct3_guid).c_str(), val2);
root->set_path((std::string{IMAP_FRAME_BAYES} + "/" + pork + "/" + acct4_guid).c_str(), val3);
EXPECT_EQ(1, qof_instance_get_editlevel(QOF_INSTANCE(t_bank_account)));
qof_instance_mark_clean(QOF_INSTANCE(t_bank_account));
gnc_account_imap_convert_flat (t_imap->book);
auto value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + pepper + "-" + acct1_guid).c_str());
EXPECT_EQ(10, value->get<int64_t>());
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + salt + "-" + acct2_guid).c_str());
EXPECT_EQ(10, value->get<int64_t>());
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + foo + "-" + acct3_guid).c_str());
EXPECT_EQ(5, value->get<int64_t>());
value = root->get_slot((std::string{IMAP_FRAME_BAYES} + "-" + pork + "-" + acct4_guid).c_str());
EXPECT_EQ(2, value->get<int64_t>());
auto book = qof_instance_get_slots(QOF_INSTANCE(t_imap->book));
auto vals = book->get_slot("changed-bayesian-to-flat");
EXPECT_STREQ("true", vals->get<const char*>());
EXPECT_EQ(1, qof_instance_get_editlevel(QOF_INSTANCE(t_bank_account)));
EXPECT_TRUE(qof_instance_get_dirty_flag(QOF_INSTANCE(t_bank_account)));
}
/* Tests the import map's handling of KVP delimiters */
TEST_F (ImapBayesTest, import_map_with_delimiters)
{
GList * tokens {nullptr};
tokens = g_list_prepend(tokens, const_cast<char*>("one/two/three"));
gnc_account_imap_add_account_bayes(t_imap, tokens, t_expense_account1);
gnc_account_imap_add_account_bayes(t_imap, tokens, t_expense_account1);
gnc_account_imap_add_account_bayes(t_imap, tokens, t_expense_account1);
auto account = gnc_account_imap_find_account_bayes (t_imap, tokens);
EXPECT_EQ (account, t_expense_account1);
}

View File

@@ -202,3 +202,41 @@ TEST_F (KvpFrameTest, Empty)
EXPECT_TRUE(f1.empty());
EXPECT_FALSE(f2.empty());
}
TEST (KvpFrameTestForEachPrefix, for_each_prefix_1)
{
KvpFrame fr;
fr.set("one", new KvpValue{new KvpFrame});
fr.set("one/two", new KvpValue{new KvpFrame});
fr.set("top/two/three", new KvpValue {15.0});
fr.set("onetwo", new KvpValue{new KvpFrame});
fr.set("onetwo/three", new KvpValue {15.0});
fr.set("onetwothree", new KvpValue {(int64_t)52});
unsigned count {};
auto counter = [] (char const *, KvpValue*, unsigned & count) { ++count; };
fr.for_each_slot_prefix("one", counter, count);
EXPECT_EQ(count, 3);
count = 0;
fr.for_each_slot_prefix("onetwo", counter, count);
EXPECT_EQ(count, 2);
count = 0;
fr.for_each_slot_prefix("onetwothree", counter, count);
EXPECT_EQ(count, 1);
count = 0;
fr.for_each_slot_prefix("two", counter, count);
EXPECT_EQ(count, 0);
}
TEST (KvpFrameTestForEachPrefix, for_each_prefix_2)
{
KvpFrame fr;
fr.set("onetwo/three", new KvpValue {15.0});
fr.set("onethree", new KvpValue {(int64_t)52});
unsigned count;
fr.for_each_slot_prefix("onetwo", [](char const *, KvpValue * value, unsigned) {
EXPECT_EQ(value->get_type(), KvpValue::Type::FRAME);
}, count);
fr.for_each_slot_prefix("onetwo", [](char const *, KvpValue * value, unsigned) {
EXPECT_EQ(value->get_type(), KvpValue::Type::INT64);
}, count);
}

View File

@@ -740,7 +740,7 @@ test_xaccSplitDetermineGainStatus (Fixture *fixture, gconstpointer pData)
g_assert (fixture->split->gains_split == NULL);
g_assert_cmpint (fixture->split->gains, ==, GAINS_STATUS_A_VDIRTY | GAINS_STATUS_DATE_DIRTY);
fixture->split->inst.kvp_data->set("gains-source", new KvpValue(const_cast<GncGUID*>(guid_copy(g_guid))));
fixture->split->inst.kvp_data->set("gains-source", new KvpValue(guid_copy(g_guid)));
g_assert (fixture->split->gains_split == NULL);
fixture->split->gains = GAINS_STATUS_UNKNOWN;
xaccSplitDetermineGainStatus (fixture->split);