"), Qt::CaseInsensitive))
result = true;
// count only lines that contain some non white space chars
if (!line.isEmpty())
lineCount--;
}
f.close();
}
return result;
}
bool OFXImporter::import(const QString& filename)
{
d->m_fatalerror = i18n("Unable to parse file");
d->m_valid = false;
d->m_errors.clear();
d->m_warnings.clear();
d->m_infos.clear();
d->m_statementlist.clear();
d->m_securitylist.clear();
QByteArray filename_deep = QFile::encodeName(filename);
ofx_STATUS_msg = true;
ofx_INFO_msg = true;
ofx_WARNING_msg = true;
ofx_ERROR_msg = true;
#ifdef DEBUG_LIBOFX
ofx_PARSER_msg = true;
ofx_DEBUG_msg = true;
ofx_DEBUG1_msg = true;
ofx_DEBUG2_msg = true;
ofx_DEBUG3_msg = true;
ofx_DEBUG4_msg = true;
ofx_DEBUG5_msg = true;
#endif
LibofxContextPtr ctx = libofx_get_new_context();
Q_CHECK_PTR(ctx);
// Don't show the position that caused a message to be shown
// This has no setter (see libofx.h)
ofx_show_position = false;
qDebug("setup callback routines");
ofx_set_transaction_cb(ctx, ofxTransactionCallback, this);
ofx_set_statement_cb(ctx, ofxStatementCallback, this);
ofx_set_account_cb(ctx, ofxAccountCallback, this);
ofx_set_security_cb(ctx, ofxSecurityCallback, this);
ofx_set_status_cb(ctx, ofxStatusCallback, this);
qDebug("process data");
libofx_proc_file(ctx, filename_deep, AUTODETECT);
qDebug("process data done");
libofx_free_context(ctx);
if (d->m_valid) {
d->m_fatalerror.clear();
d->m_valid = storeStatements(d->m_statementlist);
}
return d->m_valid;
}
QString OFXImporter::lastError() const
{
if (d->m_errors.count() == 0)
return d->m_fatalerror;
return d->m_errors.join(QStringLiteral(""));
}
/* __________________________________________________________________________
* AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
*
* Static callbacks for LibOFX
*
* YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
*/
int OFXImporter::ofxTransactionCallback(struct OfxTransactionData data, void * pv)
{
// kDebug(2) << Q_FUNC_INFO;
OFXImporter* pofx = reinterpret_cast(pv);
MyMoneyStatement& s = pofx->back();
MyMoneyStatement::Transaction t;
if (data.date_posted_valid) {
QDateTime dt;
dt.setTime_t(data.date_posted - pofx->d->m_timestampOffset * 60);
t.m_datePosted = dt.date();
} else if (data.date_initiated_valid) {
QDateTime dt;
dt.setTime_t(data.date_initiated - pofx->d->m_timestampOffset * 60);
t.m_datePosted = dt.date();
}
if (t.m_datePosted.isValid()) {
// verify the transaction date is one we want
if (t.m_datePosted < pofx->d->m_updateStartDate) {
//kDebug(0) << "discarding transaction dated" << qPrintable(t.m_datePosted.toString(Qt::ISODate));
return 0;
}
}
if (data.amount_valid) {
t.m_amount = MyMoneyMoney(data.amount, 1000);
}
if (data.check_number_valid) {
t.m_strNumber = QString::fromUtf8(data.check_number);
}
if (data.fi_id_valid) {
t.m_strBankID = QStringLiteral("ID ") + QString::fromUtf8(data.fi_id);
} else if (data.reference_number_valid) {
t.m_strBankID = QStringLiteral("REF ") + QString::fromUtf8(data.reference_number);
}
// Decide whether to use NAME, PAYEEID or MEMO to construct the payee
bool validity[3] = {false, false, false};
QStringList values;
switch (pofx->d->m_preferName) {
case OFXImporter::Private::PreferId: // PAYEEID
default:
validity[0] = data.payee_id_valid;
validity[1] = data.name_valid;
validity[2] = data.memo_valid;
values += QString::fromUtf8(data.payee_id);
values += QString::fromUtf8(data.name);
values += QString::fromUtf8(data.memo);
break;
case OFXImporter::Private::PreferName: // NAME
validity[0] = data.name_valid;
validity[1] = data.payee_id_valid;
validity[2] = data.memo_valid;
values += QString::fromUtf8(data.name);
values += QString::fromUtf8(data.payee_id);
values += QString::fromUtf8(data.memo);
break;
case OFXImporter::Private::PreferMemo: // MEMO
validity[0] = data.memo_valid;
validity[1] = data.payee_id_valid;
validity[2] = data.name_valid;
values += QString::fromUtf8(data.memo);
values += QString::fromUtf8(data.payee_id);
values += QString::fromUtf8(data.name);
break;
}
// for investment transactions we don't use the meme as payee
if (data.invtransactiontype_valid) {
values.clear();
validity[0] = data.payee_id_valid;
validity[1] = data.name_valid;
validity[2] = false;
values += QString::fromUtf8(data.payee_id);
values += QString::fromUtf8(data.name);
}
for (int idx = 0; idx < 3; ++idx) {
if (validity[idx]) {
t.m_strPayee = values[idx];
break;
}
}
// extract memo field if we haven't used it as payee
if ((data.memo_valid)
&& (pofx->d->m_preferName != OFXImporter::Private::PreferMemo)) {
t.m_strMemo = QString::fromUtf8(data.memo);
}
// If the payee or memo fields are blank, set them to
// the other one which is NOT blank. (acejones)
if (t.m_strPayee.isEmpty()) {
// But we only create a payee for non-investment transactions (ipwizard)
if (! t.m_strMemo.isEmpty() && data.invtransactiontype_valid == false)
t.m_strPayee = t.m_strMemo;
} else {
if (t.m_strMemo.isEmpty())
t.m_strMemo = t.m_strPayee;
}
if (data.security_data_valid) {
struct OfxSecurityData* secdata = data.security_data_ptr;
if (secdata->ticker_valid) {
t.m_strSymbol = QString::fromUtf8(secdata->ticker);
}
if (secdata->secname_valid) {
t.m_strSecurity = QString::fromUtf8(secdata->secname);
}
}
t.m_shares = MyMoneyMoney();
if (data.units_valid) {
t.m_shares = MyMoneyMoney(data.units, 100000).reduce();
}
t.m_price = MyMoneyMoney();
if (data.unitprice_valid) {
t.m_price = MyMoneyMoney(data.unitprice, 100000).reduce();
}
t.m_fees = MyMoneyMoney();
if (data.fees_valid) {
t.m_fees += MyMoneyMoney(data.fees, 1000).reduce();
}
if (data.commission_valid) {
t.m_fees += MyMoneyMoney(data.commission, 1000).reduce();
}
bool unhandledtype = false;
QString type;
if (data.invtransactiontype_valid) {
switch (data.invtransactiontype) {
case OFX_BUYDEBT:
case OFX_BUYMF:
case OFX_BUYOPT:
case OFX_BUYOTHER:
case OFX_BUYSTOCK:
t.m_eAction = eMyMoney::Transaction::Action::Buy;
break;
case OFX_REINVEST:
t.m_eAction = eMyMoney::Transaction::Action::ReinvestDividend;
break;
case OFX_SELLDEBT:
case OFX_SELLMF:
case OFX_SELLOPT:
case OFX_SELLOTHER:
case OFX_SELLSTOCK:
t.m_eAction = eMyMoney::Transaction::Action::Sell;
break;
case OFX_INCOME:
t.m_eAction = eMyMoney::Transaction::Action::CashDividend;
// NOTE: With CashDividend, the amount of the dividend should
// be in data.amount. Since I've never seen an OFX file with
// cash dividends, this is an assumption on my part. (acejones)
break;
//
// These types are all not handled. We will generate a warning for them.
//
case OFX_CLOSUREOPT:
unhandledtype = true;
type = QStringLiteral("CLOSUREOPT (Close a position for an option)");
break;
case OFX_INVEXPENSE:
unhandledtype = true;
type = QStringLiteral("INVEXPENSE (Misc investment expense that is associated with a specific security)");
break;
case OFX_JRNLFUND:
unhandledtype = true;
type = QStringLiteral("JRNLFUND (Journaling cash holdings between subaccounts within the same investment account)");
break;
case OFX_MARGININTEREST:
unhandledtype = true;
type = QStringLiteral("MARGININTEREST (Margin interest expense)");
break;
case OFX_RETOFCAP:
unhandledtype = true;
type = QStringLiteral("RETOFCAP (Return of capital)");
break;
case OFX_SPLIT:
unhandledtype = true;
type = QStringLiteral("SPLIT (Stock or mutial fund split)");
break;
case OFX_TRANSFER:
unhandledtype = true;
type = QStringLiteral("TRANSFER (Transfer holdings in and out of the investment account)");
break;
default:
unhandledtype = true;
type = QString("UNKNOWN %1").arg(data.invtransactiontype);
break;
}
} else
t.m_eAction = eMyMoney::Transaction::Action::None;
// In the case of investment transactions, the 'total' is supposed to the total amount
// of the transaction. units * unitprice +/- commission. Easy, right? Sadly, it seems
// some ofx creators do not follow this in all circumstances. Therefore, we have to double-
// check the total here and adjust it if it's wrong.
#if 0
// Even more sadly, this logic is BROKEN. It consistently results in bogus total
// values, because of rounding errors in the price. A more through solution would
// be to test if the commission alone is causing a discrepancy, and adjust in that case.
if (data.invtransactiontype_valid && data.unitprice_valid) {
double proper_total = t.m_dShares * data.unitprice + t.m_moneyFees;
if (proper_total != t.m_moneyAmount) {
pofx->addWarning(QString("Transaction %1 has an incorrect total of %2. Using calculated total of %3 instead.").arg(t.m_strBankID).arg(t.m_moneyAmount).arg(proper_total));
t.m_moneyAmount = proper_total;
}
}
#endif
if (unhandledtype)
pofx->addWarning(QString("Transaction %1 has an unsupported type (%2).").arg(t.m_strBankID, type));
else
s.m_listTransactions += t;
// kDebug(2) << Q_FUNC_INFO << "return 0 ";
return 0;
}
int OFXImporter::ofxStatementCallback(struct OfxStatementData data, void* pv)
{
// kDebug(2) << Q_FUNC_INFO;
OFXImporter* pofx = reinterpret_cast(pv);
MyMoneyStatement& s = pofx->back();
pofx->setValid();
if (data.currency_valid) {
s.m_strCurrency = QString::fromUtf8(data.currency);
}
if (data.account_id_valid) {
s.m_strAccountNumber = QString::fromUtf8(data.account_id);
}
if (data.date_start_valid) {
QDateTime dt;
dt.setTime_t(data.date_start - pofx->d->m_timestampOffset * 60);
s.m_dateBegin = dt.date();
}
if (data.date_end_valid) {
QDateTime dt;
dt.setTime_t(data.date_end - pofx->d->m_timestampOffset * 60);
s.m_dateEnd = dt.date();
}
if (data.ledger_balance_valid && data.ledger_balance_date_valid) {
s.m_closingBalance = MyMoneyMoney(data.ledger_balance);
QDateTime dt;
dt.setTime_t(data.ledger_balance_date);
s.m_dateEnd = dt.date();
}
// kDebug(2) << Q_FUNC_INFO << " return 0";
return 0;
}
int OFXImporter::ofxAccountCallback(struct OfxAccountData data, void * pv)
{
// kDebug(2) << Q_FUNC_INFO;
OFXImporter* pofx = reinterpret_cast(pv);
pofx->addnew();
MyMoneyStatement& s = pofx->back();
// Having any account at all makes an ofx statement valid
pofx->d->m_valid = true;
if (data.account_id_valid) {
s.m_strAccountName = QString::fromUtf8(data.account_name);
s.m_strAccountNumber = QString::fromUtf8(data.account_id);
}
if (data.bank_id_valid) {
s.m_strRoutingNumber = QString::fromUtf8(data.bank_id);
}
if (data.broker_id_valid) {
s.m_strRoutingNumber = QString::fromUtf8(data.broker_id);
}
if (data.currency_valid) {
s.m_strCurrency = QString::fromUtf8(data.currency);
}
if (data.account_type_valid) {
switch (data.account_type) {
case OfxAccountData::OFX_CHECKING : s.m_eType = eMyMoney::Statement::Type::Checkings;
break;
case OfxAccountData::OFX_SAVINGS : s.m_eType = eMyMoney::Statement::Type::Savings;
break;
case OfxAccountData::OFX_MONEYMRKT : s.m_eType = eMyMoney::Statement::Type::Investment;
break;
case OfxAccountData::OFX_CREDITLINE : s.m_eType = eMyMoney::Statement::Type::CreditCard;
break;
case OfxAccountData::OFX_CMA : s.m_eType = eMyMoney::Statement::Type::CreditCard;
break;
case OfxAccountData::OFX_CREDITCARD : s.m_eType = eMyMoney::Statement::Type::CreditCard;
break;
case OfxAccountData::OFX_INVESTMENT : s.m_eType = eMyMoney::Statement::Type::Investment;
break;
}
}
// ask KMyMoney for an account id
s.m_accountId = pofx->account(QStringLiteral("kmmofx-acc-ref"), QString("%1-%2").arg(s.m_strRoutingNumber, s.m_strAccountNumber)).id();
// copy over the securities
s.m_listSecurities = pofx->d->m_securitylist;
// kDebug(2) << Q_FUNC_INFO << " return 0";
return 0;
}
int OFXImporter::ofxSecurityCallback(struct OfxSecurityData data, void* pv)
{
// kDebug(2) << Q_FUNC_INFO;
OFXImporter* pofx = reinterpret_cast(pv);
MyMoneyStatement::Security sec;
if (data.unique_id_valid) {
sec.m_strId = QString::fromUtf8(data.unique_id);
}
if (data.secname_valid) {
sec.m_strName = QString::fromUtf8(data.secname);
}
if (data.ticker_valid) {
sec.m_strSymbol = QString::fromUtf8(data.ticker);
}
pofx->d->m_securitylist += sec;
return 0;
}
int OFXImporter::ofxStatusCallback(struct OfxStatusData data, void * pv)
{
// kDebug(2) << Q_FUNC_INFO;
OFXImporter* pofx = reinterpret_cast(pv);
QString message;
// if we got this far, we know we were able to parse the file.
// so if it fails after here it can only because there were no actual
// accounts in the file!
pofx->d->m_fatalerror = i18n("No accounts found.");
if (data.ofx_element_name_valid)
message.prepend(QString("%1: ").arg(QString::fromUtf8(data.ofx_element_name)));
if (data.code_valid)
message += QString("%1 (Code %2): %3").arg(QString::fromUtf8(data.name)).arg(data.code).arg(QString::fromUtf8(data.description));
if (data.server_message_valid)
message += QString(" (%1)").arg(QString::fromUtf8(data.server_message));
if (data.severity_valid) {
switch (data.severity) {
case OfxStatusData::INFO:
pofx->addInfo(message);
break;
case OfxStatusData::ERROR:
pofx->addError(message);
break;
case OfxStatusData::WARN:
pofx->addWarning(message);
break;
default:
pofx->addWarning(message);
pofx->addWarning(QStringLiteral("Previous message was an unknown type. 'WARNING' was assumed."));
break;
}
}
// kDebug(2) << Q_FUNC_INFO << " return 0 ";
return 0;
}
QStringList OFXImporter::importStatement(const MyMoneyStatement &s)
{
qDebug("OfxImporterPlugin::importStatement start");
return statementInterface()->import(s, false);
}
MyMoneyAccount OFXImporter::account(const QString& key, const QString& value) const
{
return statementInterface()->account(key, value);
}
void OFXImporter::protocols(QStringList& protocolList) const
{
protocolList.clear();
protocolList << QStringLiteral("OFX");
}
QWidget* OFXImporter::accountConfigTab(const MyMoneyAccount& acc, QString& name)
{
name = i18n("Online settings");
d->m_statusDlg = new KOnlineBankingStatus(acc, 0);
return d->m_statusDlg;
}
MyMoneyKeyValueContainer OFXImporter::onlineBankingSettings(const MyMoneyKeyValueContainer& current)
{
MyMoneyKeyValueContainer kvp(current);
// keep the provider name in sync with the one found in kmm_ofximport.desktop
kvp[QStringLiteral("provider")] = objectName().toLower();
if (d->m_statusDlg) {
kvp.deletePair(QStringLiteral("appId"));
kvp.deletePair(QStringLiteral("kmmofx-headerVersion"));
kvp.deletePair(QStringLiteral("password"));
d->m_wallet = openSynchronousWallet();
if (d->m_wallet && (d->m_wallet->hasFolder(KWallet::Wallet::PasswordFolder()) ||
d->m_wallet->createFolder(KWallet::Wallet::PasswordFolder())) &&
d->m_wallet->setFolder(KWallet::Wallet::PasswordFolder())) {
QString key = OFX_PASSWORD_KEY(kvp.value(QStringLiteral("url")), kvp.value(QStringLiteral("uniqueId")));
if (d->m_statusDlg->m_storePassword->isChecked()) {
d->m_wallet->writePassword(key, d->m_statusDlg->m_password->text());
} else {
if (d->m_wallet->hasEntry(key)) {
d->m_wallet->removeEntry(key);
}
}
} else {
if (d->m_statusDlg->m_storePassword->isChecked()) {
kvp.setValue(QStringLiteral("password"), d->m_statusDlg->m_password->text());
}
}
if (!d->m_statusDlg->appId().isEmpty())
kvp.setValue(QStringLiteral("appId"), d->m_statusDlg->appId());
kvp.setValue(QStringLiteral("kmmofx-headerVersion"), d->m_statusDlg->headerVersion());
kvp.setValue(QStringLiteral("kmmofx-numRequestDays"), QString::number(d->m_statusDlg->m_numdaysSpin->value()));
kvp.setValue(QStringLiteral("kmmofx-todayMinus"), QString::number(d->m_statusDlg->m_todayRB->isChecked()));
kvp.setValue(QStringLiteral("kmmofx-lastUpdate"), QString::number(d->m_statusDlg->m_lastUpdateRB->isChecked()));
kvp.setValue(QStringLiteral("kmmofx-pickDate"), QString::number(d->m_statusDlg->m_pickDateRB->isChecked()));
kvp.setValue(QStringLiteral("kmmofx-specificDate"), d->m_statusDlg->m_specificDate->date().toString());
kvp.setValue(QStringLiteral("kmmofx-preferName"), QString::number(d->m_statusDlg->m_preferredPayee->currentIndex()));
if (!d->m_statusDlg->m_clientUidEdit->text().isEmpty())
kvp.setValue(QStringLiteral("clientUid"), d->m_statusDlg->m_clientUidEdit->text());
else
kvp.deletePair(QStringLiteral("clientUid"));
if (d->m_statusDlg->m_timestampOffset->time().msecsSinceStartOfDay() == 0) {
kvp.deletePair(QStringLiteral("kmmofx-timestampOffset"));
} else {
// get offset in minutes
int offset = d->m_statusDlg->m_timestampOffset->time().msecsSinceStartOfDay() / 1000 / 60;
if (d->m_statusDlg->m_timestampOffsetSign->currentText() == QStringLiteral("-")) {
offset = -offset;
}
kvp.setValue(QStringLiteral("kmmofx-timestampOffset"), QString::number(offset));
}
// get rid of pre 4.6 values
kvp.deletePair(QStringLiteral("kmmofx-preferPayeeid"));
}
return kvp;
}
bool OFXImporter::mapAccount(const MyMoneyAccount& acc, MyMoneyKeyValueContainer& settings)
{
Q_UNUSED(acc);
bool rc = false;
QPointer wiz = new KOnlineBankingSetupWizard(0);
if (wiz->isInit()) {
if (wiz->exec() == QDialog::Accepted) {
rc = wiz->chosenSettings(settings);
}
}
delete wiz;
return rc;
}
bool OFXImporter::updateAccount(const MyMoneyAccount& acc, bool moreAccounts)
{
Q_UNUSED(moreAccounts);
qDebug("OfxImporterPlugin::updateAccount");
try {
if (!acc.id().isEmpty()) {
// Save the value of preferName to be used by ofxTransactionCallback
d->m_preferName = static_cast(acc.onlineBankingSettings().value(QStringLiteral("kmmofx-preferName")).toInt());
QPointer dlg = new KOfxDirectConnectDlg(acc);
connect(dlg.data(), &KOfxDirectConnectDlg::statementReady, this, static_cast(&OFXImporter::slotImportFile));
// get the date of the earliest transaction that we are interested in
// from the settings for this account
MyMoneyKeyValueContainer settings = acc.onlineBankingSettings();
if (!settings.value(QStringLiteral("provider")).isEmpty()) {
if ((settings.value(QStringLiteral("kmmofx-todayMinus")).toInt() != 0) && !settings.value(QStringLiteral("kmmofx-numRequestDays")).isEmpty()) {
//kDebug(0) << "start date = today minus";
d->m_updateStartDate = QDate::currentDate().addDays(-settings.value(QStringLiteral("kmmofx-numRequestDays")).toInt());
} else if ((settings.value(QStringLiteral("kmmofx-lastUpdate")).toInt() != 0) && !acc.value(QStringLiteral("lastImportedTransactionDate")).isEmpty()) {
//kDebug(0) << "start date = last update";
d->m_updateStartDate = QDate::fromString(acc.value(QStringLiteral("lastImportedTransactionDate")), Qt::ISODate);
} else if ((settings.value(QStringLiteral("kmmofx-pickDate")).toInt() != 0) && !settings.value(QStringLiteral("kmmofx-specificDate")).isEmpty()) {
//kDebug(0) << "start date = pick date";
d->m_updateStartDate = QDate::fromString(settings.value(QStringLiteral("kmmofx-specificDate")));
}
else {
//kDebug(0) << "start date = today - 2 months";
d->m_updateStartDate = QDate::currentDate().addMonths(-2);
}
}
d->m_timestampOffset = settings.value("kmmofx-timestampOffset").toInt();
//kDebug(0) << "ofx plugin: account" << acc.name() << "earliest transaction date to process =" << qPrintable(d->m_updateStartDate.toString(Qt::ISODate));
if (dlg->init())
dlg->exec();
delete dlg;
// reset the earliest-interesting-transaction date to the non-specific account setting
d->m_updateStartDate = QDate(1900,1,1);
d->m_timestampOffset = 0;
}
} catch (const MyMoneyException &e) {
KMessageBox::information(0 , i18n("Error connecting to bank: %1", QString::fromLatin1(e.what())));
}
return false;
}
void OFXImporter::slotImportFile(const QString& url)
{
qDebug("OfxImporterPlugin::slotImportFile");
if (!import(url)) {
KMessageBox::error(0, QString("%1").arg(i18n("Unable to import '%1' using the OFX importer plugin. The plugin returned the following error:
%2
", url, lastError())), i18n("Importing error"));
}
}
bool OFXImporter::storeStatements(const QList &statements)
{
if (statements.isEmpty())
return true;
auto ok = true;
auto abort = false;
// FIXME Deal with warnings/errors coming back from plugins
/*if ( ofx.errors().count() )
{
if ( KMessageBox::warningContinueCancelList(this,i18n("The following errors were returned from your bank"),ofx.errors(),i18n("OFX Errors")) == KMessageBox::Cancel )
abort = true;
}
if ( ofx.warnings().count() )
{
if ( KMessageBox::warningContinueCancelList(this,i18n("The following warnings were returned from your bank"),ofx.warnings(),i18n("OFX Warnings"),KStandardGuiItem::cont(),"ofxwarnings") == KMessageBox::Cancel )
abort = true;
}*/
qDebug("OfxImporterPlugin::storeStatements() with %d statements called", statements.count());
for (const auto& statement : statements) {
if (abort)
break;
if (importStatement(statement).isEmpty())
ok = false;
}
if (!ok)
KMessageBox::error(nullptr, i18n("Importing process terminated unexpectedly."), i18n("Failed to import all statements."));
return ok;
}
void OFXImporter::addnew()
{
d->m_statementlist.push_back(MyMoneyStatement());
}
MyMoneyStatement& OFXImporter::back()
{
return d->m_statementlist.back();
}
bool OFXImporter::isValid() const
{
return d->m_valid;
}
void OFXImporter::setValid()
{
d->m_valid = true;
}
void OFXImporter::addInfo(const QString& _msg)
{
d->m_infos += _msg;
}
void OFXImporter::addWarning(const QString& _msg)
{
d->m_warnings += _msg;
}
void OFXImporter::addError(const QString& _msg)
{
d->m_errors += _msg;
}
const QStringList& OFXImporter::infos() const // krazy:exclude=spelling
{
return d->m_infos;
}
const QStringList& OFXImporter::warnings() const
{
return d->m_warnings;
}
const QStringList& OFXImporter::errors() const
{
return d->m_errors;
}
K_PLUGIN_FACTORY_WITH_JSON(OFXImporterFactory, "ofximporter.json", registerPlugin();)
#include "ofximporter.moc"
diff --git a/kmymoney/plugins/qif/export/qifexporter.cpp b/kmymoney/plugins/qif/export/qifexporter.cpp
index ca14ab9a6..847fed300 100644
--- a/kmymoney/plugins/qif/export/qifexporter.cpp
+++ b/kmymoney/plugins/qif/export/qifexporter.cpp
@@ -1,85 +1,103 @@
/***************************************************************************
qifexporter.cpp
-------------------
copyright : (C) 2017 by Łukasz Wojniłowicz
email : lukasz.wojnilowicz@gmail.com
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
+#include
#include "qifexporter.h"
// ----------------------------------------------------------------------------
// QT Includes
// ----------------------------------------------------------------------------
// KDE Includes
#include
#include
#include
// ----------------------------------------------------------------------------
// Project Includes
#include "kexportdlg.h"
#include "mymoneyqifwriter.h"
#include "viewinterface.h"
+#ifdef IS_APPIMAGE
+#include
+#include
+#endif
+
QIFExporter::QIFExporter(QObject *parent, const QVariantList &args) :
KMyMoneyPlugin::Plugin(parent, "qifexporter"/*must be the same as X-KDE-PluginInfo-Name*/)
{
Q_UNUSED(args);
- setComponentName("qifexporter", i18n("QIF exporter"));
- setXMLFile("qifexporter.rc");
+ const auto componentName = QLatin1String("qifexporter");
+ const auto rcFileName = QLatin1String("qifexporter.rc");
+ setComponentName(componentName, i18n("QIF exporter"));
+
+#ifdef IS_APPIMAGE
+ const QString rcFilePath = QCoreApplication::applicationDirPath() + QLatin1String("/../share/kxmlgui5/") + componentName + QLatin1Char('/') + rcFileName;
+ setXMLFile(rcFilePath);
+
+ const QString localRcFilePath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).first() + QLatin1Char('/') + componentName + QLatin1Char('/') + rcFileName;
+ setLocalXMLFile(localRcFilePath);
+#else
+ setXMLFile(rcFileName);
+#endif
+
createActions();
// For information, announce that we have been loaded.
qDebug("Plugins: qifexporter loaded");
}
QIFExporter::~QIFExporter()
{
qDebug("Plugins: qifexporter unloaded");
}
void QIFExporter::createActions()
{
const auto &kpartgui = QStringLiteral("file_export_qif");
m_action = actionCollection()->addAction(kpartgui);
m_action->setText(i18n("QIF..."));
connect(m_action, &QAction::triggered, this, &QIFExporter::slotQifExport);
connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled);
}
void QIFExporter::slotQifExport()
{
m_action->setEnabled(false);
QPointer dlg = new KExportDlg(nullptr);
if (dlg->exec() == QDialog::Accepted && dlg != nullptr) {
// if (okToWriteFile(QUrl::fromLocalFile(dlg->filename()))) {
MyMoneyQifWriter writer;
connect(&writer, SIGNAL(signalProgress(int,int)), this, SLOT(slotStatusProgressBar(int,int)));
writer.write(dlg->filename(), dlg->profile(), dlg->accountId(),
dlg->accountSelected(), dlg->categorySelected(),
dlg->startDate(), dlg->endDate());
// }
}
delete dlg;
m_action->setEnabled(true);
}
K_PLUGIN_FACTORY_WITH_JSON(QIFExporterFactory, "qifexporter.json", registerPlugin();)
#include "qifexporter.moc"
diff --git a/kmymoney/plugins/qif/import/qifimporter.cpp b/kmymoney/plugins/qif/import/qifimporter.cpp
index bbef21357..fb0557bde 100644
--- a/kmymoney/plugins/qif/import/qifimporter.cpp
+++ b/kmymoney/plugins/qif/import/qifimporter.cpp
@@ -1,111 +1,129 @@
/***************************************************************************
qifimporter.cpp
-------------------
copyright : (C) 2017 by Łukasz Wojniłowicz
email : lukasz.wojnilowicz@gmail.com
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
+#include
#include "qifimporter.h"
// ----------------------------------------------------------------------------
// QT Includes
// ----------------------------------------------------------------------------
// KDE Includes
#include
#include
#include
#include
// ----------------------------------------------------------------------------
// Project Includes
#include "kimportdlg.h"
#include "mymoneyqifreader.h"
#include "statementinterface.h"
#include "viewinterface.h"
+#ifdef IS_APPIMAGE
+#include
+#include
+#endif
+
class MyMoneyStatement;
QIFImporter::QIFImporter(QObject *parent, const QVariantList &args) :
KMyMoneyPlugin::Plugin(parent, "qifimporter"/*must be the same as X-KDE-PluginInfo-Name*/)
{
Q_UNUSED(args);
- setComponentName("qifimporter", i18n("QIF importer"));
- setXMLFile("qifimporter.rc");
+ const auto componentName = QLatin1String("qifimporter");
+ const auto rcFileName = QLatin1String("qifimporter.rc");
+ setComponentName(componentName, i18n("QIF importer"));
+
+#ifdef IS_APPIMAGE
+ const QString rcFilePath = QCoreApplication::applicationDirPath() + QLatin1String("/../share/kxmlgui5/csvexporter/") + rcFileName;
+ setXMLFile(rcFilePath);
+
+ const QString localRcFilePath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).first() + QLatin1Char('/') + rcFileName;
+ setLocalXMLFile(localRcFilePath);
+#else
+ setXMLFile(rcFileName);
+#endif
+
createActions();
// For information, announce that we have been loaded.
qDebug("Plugins: qifimporter loaded");
}
QIFImporter::~QIFImporter()
{
qDebug("Plugins: qifimporter unloaded");
}
void QIFImporter::createActions()
{
const auto &kpartgui = QStringLiteral("file_import_qif");
m_action = actionCollection()->addAction(kpartgui);
m_action->setText(i18n("QIF..."));
connect(m_action, &QAction::triggered, this, &QIFImporter::slotQifImport);
connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled);
}
void QIFImporter::slotQifImport()
{
m_action->setEnabled(false);
QPointer dlg = new KImportDlg(nullptr);
if (dlg->exec() == QDialog::Accepted && dlg != nullptr) {
m_qifReader = new MyMoneyQifReader;
statementInterface()->resetMessages();
connect(m_qifReader, &MyMoneyQifReader::statementsReady, this, &QIFImporter::slotGetStatements);
m_qifReader->setURL(dlg->file());
m_qifReader->setProfile(dlg->profile());
m_qifReader->setCategoryMapping(dlg->m_typeComboBox->currentIndex() == 0);
const auto statementCount = m_qifReader->statementCount();
if (!m_qifReader->startImport())
delete m_qifReader;
statementInterface()->showMessages(statementCount);
}
delete dlg;
m_action->setEnabled(true);
}
bool QIFImporter::slotGetStatements(const QList &statements)
{
auto ret = true;
QStringList importSummary;
for (const auto& statement : statements) {
const auto singleImportSummary = statementInterface()->import(statement);
if (singleImportSummary.isEmpty())
ret = false;
importSummary.append(singleImportSummary);
}
delete m_qifReader;
if (!importSummary.isEmpty())
KMessageBox::informationList(nullptr,
i18n("The statement has been processed with the following results:"), importSummary, i18n("Statement stats"));
return ret;
}
K_PLUGIN_FACTORY_WITH_JSON(QIFImporterFactory, "qifimporter.json", registerPlugin();)
#include "qifimporter.moc"
diff --git a/kmymoney/plugins/sql/sqlstorage.cpp b/kmymoney/plugins/sql/sqlstorage.cpp
index 1d6a0f6e3..b01b1278b 100644
--- a/kmymoney/plugins/sql/sqlstorage.cpp
+++ b/kmymoney/plugins/sql/sqlstorage.cpp
@@ -1,358 +1,376 @@
/***************************************************************************
sqlstorage.cpp
-------------------
copyright : (C) 2018 by Łukasz Wojniłowicz
email : lukasz.wojnilowicz@gmail.com
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
+#include
#include "sqlstorage.h"
#include
#include
// ----------------------------------------------------------------------------
// QT Includes
#include
#include
#include
#include
#include
// ----------------------------------------------------------------------------
// KDE Includes
#include
#include
#include
#include
// ----------------------------------------------------------------------------
// Project Includes
#include "appinterface.h"
#include "viewinterface.h"
#include "kselectdatabasedlg.h"
#include "kgeneratesqldlg.h"
#include "mymoneyfile.h"
#include "mymoneystoragesql.h"
#include "mymoneyexception.h"
#include "mymoneystoragemgr.h"
#include "icons.h"
#include "kmymoneysettings.h"
#include "kmymoneyenums.h"
+#ifdef IS_APPIMAGE
+#include
+#include
+#endif
+
using namespace Icons;
QUrlQuery SQLStorage::convertOldUrl(const QUrl& url)
{
const auto key = QLatin1String("driver");
// take care and convert some old url to their new counterpart
QUrlQuery query(url);
if (query.queryItemValue(key) == QLatin1String("QMYSQL3")) { // fix any old urls
query.removeQueryItem(key);
query.addQueryItem(key, QLatin1String("QMYSQL"));
} else if (query.queryItemValue(key) == QLatin1String("QSQLITE3")) {
query.removeQueryItem(key);
query.addQueryItem(key, QLatin1String("QSQLITE"));
}
#ifdef ENABLE_SQLCIPHER
// Reading unencrypted database with QSQLITE
// while QSQLCIPHER is available causes crash.
// QSQLCIPHER can read QSQLITE
if (query.queryItemValue(key) == QLatin1String("QSQLITE")) {
query.removeQueryItem(key);
query.addQueryItem(key, QLatin1String("QSQLCIPHER"));
}
#endif
return query;
}
SQLStorage::SQLStorage(QObject *parent, const QVariantList &args) :
KMyMoneyPlugin::Plugin(parent, "sqlstorage"/*must be the same as X-KDE-PluginInfo-Name*/)
{
Q_UNUSED(args)
- setComponentName("sqlstorage", i18n("SQL storage"));
- setXMLFile("sqlstorage.rc");
+ const auto componentName = QLatin1String("sqlstorage");
+ const auto rcFileName = QLatin1String("sqlstorage.rc");
+ setComponentName(componentName, i18n("SQL storage"));
+
+#ifdef IS_APPIMAGE
+ const QString rcFilePath = QCoreApplication::applicationDirPath() + QLatin1String("/../share/kxmlgui5/") + componentName + QLatin1Char('/') + rcFileName;
+ setXMLFile(rcFilePath);
+
+ const QString localRcFilePath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).first() + QLatin1Char('/') + componentName + QLatin1Char('/') + rcFileName;
+ setLocalXMLFile(localRcFilePath);
+#else
+ setXMLFile(rcFileName);
+#endif
+
createActions();
// For information, announce that we have been loaded.
qDebug("Plugins: sqlstorage loaded");
}
SQLStorage::~SQLStorage()
{
qDebug("Plugins: sqlstorage unloaded");
}
MyMoneyStorageMgr *SQLStorage::open(const QUrl &url)
{
if (url.scheme() != QLatin1String("sql"))
return nullptr;
auto storage = new MyMoneyStorageMgr;
auto reader = std::make_unique(storage, url);
QUrl dbURL(url);
if (dbURL.password().isEmpty()) {
// check if a password is needed. it may be if the URL came from the last/recent file list
QPointer dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite, dbURL);
if (!dialog->checkDrivers()) {
delete dialog;
return nullptr;
}
QUrlQuery query = convertOldUrl(dbURL);
// if we need to supply a password, then show the dialog
// otherwise it isn't needed
if ((query.queryItemValue("secure").toLower() == QLatin1String("yes")) && dbURL.password().isEmpty()) {
if (dialog->exec() == QDialog::Accepted && dialog != nullptr) {
dbURL = dialog->selectedURL();
} else {
delete dialog;
return nullptr;
}
}
delete dialog;
}
bool retry = true;
while (retry) {
switch (reader->open(dbURL, QIODevice::ReadWrite)) {
case 0: // opened okay
retry = false;
break;
case 1: // permanent error
KMessageBox::detailedError(nullptr,
i18n("Cannot open database %1\n", dbURL.toDisplayString()),
reader->lastError());
delete storage;
return nullptr;
case -1: // retryable error
if (KMessageBox::warningYesNo(nullptr, reader->lastError(), PACKAGE) == KMessageBox::No) {
delete storage;
return nullptr;
} else {
QUrlQuery query(dbURL);
const QString optionKey = QLatin1String("options");
QString options = query.queryItemValue(optionKey);
if(!options.isEmpty()) {
options += QLatin1Char(',');
}
options += QLatin1String("override");
query.removeQueryItem(QLatin1String("mode"));
query.removeQueryItem(optionKey);
query.addQueryItem(optionKey, options);
dbURL.setQuery(query);
}
break;
case 2: // bad password
case 3: // unsupported operation
delete storage;
return nullptr;
}
}
// single user mode; read some of the data into memory
// FIXME - readFile no longer relevant?
// tried removing it but then got no indication that loading was complete
// also, didn't show home page
// reader->setProgressCallback(&KMyMoneyView::progressCallback);
if (!reader->readFile()) {
KMessageBox::detailedError(nullptr,
i18n("An unrecoverable error occurred while reading the database"),
reader->lastError().toLatin1(),
i18n("Database malfunction"));
delete storage;
return nullptr;
}
// reader->setProgressCallback(0);
return storage;
}
bool SQLStorage::save(const QUrl &url)
{
auto rc = false;
if (!appInterface()->fileOpen()) {
KMessageBox::error(nullptr, i18n("Tried to access a file when it has not been opened"));
return (rc);
}
auto writer = new MyMoneyStorageSql(MyMoneyFile::instance()->storage(), url);
writer->open(url, QIODevice::WriteOnly);
// writer->setProgressCallback(&KMyMoneyView::progressCallback);
if (!writer->writeFile()) {
KMessageBox::detailedError(nullptr,
i18n("An unrecoverable error occurred while writing to the database.\n"
"It may well be corrupt."),
writer->lastError().toLatin1(),
i18n("Database malfunction"));
rc = false;
} else {
rc = true;
}
writer->setProgressCallback(0);
delete writer;
return rc;
}
bool SQLStorage::saveAs()
{
auto rc = false;
QUrl oldUrl;
// in event of it being a database, ensure that all data is read into storage for saveas
if (appInterface()->isDatabase())
oldUrl = appInterface()->filenameURL().isEmpty() ? appInterface()->lastOpenedURL() : appInterface()->filenameURL();
QPointer dialog = new KSelectDatabaseDlg(QIODevice::WriteOnly);
QUrl url = oldUrl;
if (!dialog->checkDrivers()) {
delete dialog;
return rc;
}
while (oldUrl == url && dialog->exec() == QDialog::Accepted && dialog != 0) {
url = dialog->selectedURL();
// If the protocol is SQL for the old and new, and the hostname and database names match
// Let the user know that the current database cannot be saved on top of itself.
if (url.scheme() == "sql" && oldUrl.scheme() == "sql"
&& oldUrl.host() == url.host()
&& QUrlQuery(oldUrl).queryItemValue("driver") == QUrlQuery(url).queryItemValue("driver")
&& oldUrl.path().right(oldUrl.path().length() - 1) == url.path().right(url.path().length() - 1)) {
KMessageBox::sorry(nullptr, i18n("Cannot save to current database."));
} else {
try {
rc = saveAsDatabase(url);
} catch (const MyMoneyException &e) {
KMessageBox::sorry(nullptr, i18n("Cannot save to current database: %1", QString::fromLatin1(e.what())));
}
}
}
delete dialog;
if (rc) {
//KRecentFilesAction *p = dynamic_cast(action("file_open_recent"));
//if(p)
appInterface()->addToRecentFiles(url);
appInterface()->writeLastUsedFile(url.toDisplayString(QUrl::PreferLocalFile));
appInterface()->writeFilenameURL(url);
}
return rc;
}
eKMyMoney::StorageType SQLStorage::storageType() const
{
return eKMyMoney::StorageType::SQL;
}
QString SQLStorage::fileExtension() const
{
return i18n("Database files (*.db *.sql)");
}
void SQLStorage::createActions()
{
m_openDBaction = actionCollection()->addAction("open_database");
m_openDBaction->setText(i18n("Open database..."));
m_openDBaction->setIcon(Icons::get(Icon::SVNUpdate));
connect(m_openDBaction, &QAction::triggered, this, &SQLStorage::slotOpenDatabase);
m_generateDB = actionCollection()->addAction("tools_generate_sql");
m_generateDB->setText(i18n("Generate Database SQL"));
connect(m_generateDB, &QAction::triggered, this, &SQLStorage::slotGenerateSql);
}
void SQLStorage::slotOpenDatabase()
{
QPointer dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite);
if (!dialog->checkDrivers()) {
delete dialog;
return;
}
if (dialog->exec() == QDialog::Accepted && dialog != 0) {
auto url = dialog->selectedURL();
QUrl newurl = url;
if ((newurl.scheme() == QLatin1String("sql"))) {
QUrlQuery query = convertOldUrl(newurl);
newurl.setQuery(query);
// check if a password is needed. it may be if the URL came from the last/recent file list
dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite, newurl);
if (!dialog->checkDrivers()) {
delete dialog;
return;
}
// if we need to supply a password, then show the dialog
// otherwise it isn't needed
if ((query.queryItemValue("secure").toLower() == QLatin1String("yes")) && newurl.password().isEmpty()) {
if (dialog->exec() == QDialog::Accepted && dialog != nullptr) {
newurl = dialog->selectedURL();
} else {
delete dialog;
return;
}
}
delete dialog;
}
appInterface()->slotFileOpenRecent(newurl);
}
delete dialog;
}
void SQLStorage::slotGenerateSql()
{
QPointer editor = new KGenerateSqlDlg(nullptr);
editor->setObjectName("Generate Database SQL");
editor->exec();
delete editor;
}
bool SQLStorage::saveAsDatabase(const QUrl &url)
{
auto writer = new MyMoneyStorageSql(MyMoneyFile::instance()->storage(), url);
auto canWrite = false;
switch (writer->open(url, QIODevice::WriteOnly)) {
case 0:
canWrite = true;
break;
case -1: // dbase already has data, see if he wants to clear it out
if (KMessageBox::warningContinueCancel(nullptr,
i18n("Database contains data which must be removed before using Save As.\n"
"Do you wish to continue?"), "Database not empty") == KMessageBox::Continue) {
if (writer->open(url, QIODevice::WriteOnly, true) == 0)
canWrite = true;
} else {
delete writer;
return false;
}
break;
case 2: // bad password
case 3: // unsupported operation
delete writer;
return false;
}
if (canWrite) {
delete writer;
save(url);
return true;
} else {
KMessageBox::detailedError(nullptr,
i18n("Cannot open or create database %1.\n"
"Retry Save As Database and click Help"
" for further info.", url.toDisplayString()), writer->lastError());
delete writer;
return false;
}
}
K_PLUGIN_FACTORY_WITH_JSON(SQLStorageFactory, "sqlstorage.json", registerPlugin();)
#include "sqlstorage.moc"
diff --git a/kmymoney/plugins/weboob/weboob.cpp b/kmymoney/plugins/weboob/weboob.cpp
index c6a89e6ca..7262c2541 100644
--- a/kmymoney/plugins/weboob/weboob.cpp
+++ b/kmymoney/plugins/weboob/weboob.cpp
@@ -1,223 +1,240 @@
/*
* This file is part of KMyMoney, A Personal Finance Manager by KDE
* Copyright (C) 2014-2015 Romain Bignon
* Copyright (C) 2014-2015 Florent Fourcot
* Copyright (C) 2016 Christian David
* (C) 2017 by Łukasz Wojniłowicz
*
* 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, see .
*/
+#include
#include "weboob.h"
#include
// ----------------------------------------------------------------------------
// QT Includes
#include
#include
#include
// ----------------------------------------------------------------------------
// KDE Includes
#include
#include
// ----------------------------------------------------------------------------
// Project Includes
#include "mapaccountwizard.h"
#include "accountsettings.h"
#include "weboobinterface.h"
#include "mymoneyaccount.h"
#include "mymoneykeyvaluecontainer.h"
#include "mymoneystatement.h"
#include "statementinterface.h"
+#ifdef IS_APPIMAGE
+#include
+#include
+#endif
+
class WeboobPrivate
{
public:
WeboobPrivate()
{
}
~WeboobPrivate()
{
}
WeboobInterface weboob;
QFutureWatcher watcher;
std::unique_ptr progress;
AccountSettings* accountSettings;
};
Weboob::Weboob(QObject *parent, const QVariantList &args) :
KMyMoneyPlugin::Plugin(parent, "weboob"),
d_ptr(new WeboobPrivate)
{
Q_UNUSED(args)
- setComponentName("weboob", i18n("Weboob"));
- setXMLFile("weboob.rc");
+ const auto componentName = QLatin1String("weboob");
+ const auto rcFileName = QLatin1String("weboob.rc");
+ setComponentName(componentName, i18n("Weboob"));
+
+#ifdef IS_APPIMAGE
+ const QString rcFilePath = QCoreApplication::applicationDirPath() + QLatin1String("/../share/kxmlgui5/") + componentName + QLatin1Char('/') + rcFileName;
+ setXMLFile(rcFilePath);
+
+ const QString localRcFilePath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).first() + QLatin1Char('/') + componentName + QLatin1Char('/') + rcFileName;
+ setLocalXMLFile(localRcFilePath);
+#else
+ setXMLFile(rcFileName);
+#endif
qDebug("Plugins: weboob loaded");
}
Weboob::~Weboob()
{
Q_D(Weboob);
delete d;
qDebug("Plugins: weboob unloaded");
}
void Weboob::plug()
{
Q_D(Weboob);
connect(&d->watcher, &QFutureWatcher::finished, this, &Weboob::gotAccount);
}
void Weboob::unplug()
{
Q_D(Weboob);
disconnect(&d->watcher, &QFutureWatcher::finished, this, &Weboob::gotAccount);
}
void Weboob::protocols(QStringList& protocolList) const
{
protocolList << "weboob";
}
QWidget* Weboob::accountConfigTab(const MyMoneyAccount& account, QString& tabName)
{
Q_D(Weboob);
const MyMoneyKeyValueContainer& kvp = account.onlineBankingSettings();
tabName = i18n("Weboob configuration");
d->accountSettings = new AccountSettings(account, 0);
d->accountSettings->loadUi(kvp);
return d->accountSettings;
}
MyMoneyKeyValueContainer Weboob::onlineBankingSettings(const MyMoneyKeyValueContainer& current)
{
Q_D(Weboob);
MyMoneyKeyValueContainer kvp(current);
kvp["provider"] = objectName().toLower();
if (d->accountSettings) {
d->accountSettings->loadKvp(kvp);
}
return kvp;
}
bool Weboob::mapAccount(const MyMoneyAccount& acc, MyMoneyKeyValueContainer& onlineBankingSettings)
{
Q_D(Weboob);
Q_UNUSED(acc);
bool rc = false;
QPointer w = new MapAccountWizard(nullptr, &d->weboob);
if (w->exec() == QDialog::Accepted && w != nullptr) {
onlineBankingSettings.setValue("wb-backend", w->currentBackend());
onlineBankingSettings.setValue("wb-id", w->currentAccount());
onlineBankingSettings.setValue("wb-max", "0");
rc = true;
}
delete w;
return rc;
}
bool Weboob::updateAccount(const MyMoneyAccount& kacc, bool moreAccounts)
{
Q_D(Weboob);
Q_UNUSED(moreAccounts);
QString bname = kacc.onlineBankingSettings().value("wb-backend");
QString id = kacc.onlineBankingSettings().value("wb-id");
QString max = kacc.onlineBankingSettings().value("wb-max");
d->progress = std::make_unique(nullptr);
d->progress->setWindowTitle(i18n("Connecting to bank..."));
d->progress->setLabelText(i18n("Retrieving transactions..."));
d->progress->setModal(true);
d->progress->setCancelButton(nullptr);
d->progress->setMinimum(0);
d->progress->setMaximum(0);
d->progress->setMinimumDuration(0);
QFuture future = QtConcurrent::run(&d->weboob, &WeboobInterface::getAccount, bname, id, max);
d->watcher.setFuture(future);
d->progress->exec();
d->progress.reset();
return true;
}
void Weboob::gotAccount()
{
Q_D(Weboob);
WeboobInterface::Account acc = d->watcher.result();
MyMoneyAccount kacc = statementInterface()->account("wb-id", acc.id);
MyMoneyStatement ks;
ks.m_accountId = kacc.id();
ks.m_strAccountName = acc.name;
ks.m_closingBalance = acc.balance;
if (acc.transactions.length() > 0)
ks.m_dateEnd = acc.transactions.front().date;
#if 0
switch (acc.type) {
case Weboob::Account::TYPE_CHECKING:
ks.m_eType = MyMoneyStatement::etCheckings;
break;
case Weboob::Account::TYPE_SAVINGS:
ks.m_eType = MyMoneyStatement::etSavings;
break;
case Weboob::Account::TYPE_MARKET:
ks.m_eType = MyMoneyStatement::etInvestment;
break;
case Weboob::Account::TYPE_DEPOSIT:
case Weboob::Account::TYPE_LOAN:
case Weboob::Account::TYPE_JOINT:
case Weboob::Account::TYPE_UNKNOWN:
break;
}
#endif
for (QListIterator it(acc.transactions); it.hasNext();) {
WeboobInterface::Transaction tr = it.next();
MyMoneyStatement::Transaction kt;
kt.m_strBankID = QLatin1String("ID ") + tr.id;
kt.m_datePosted = tr.rdate;
kt.m_amount = tr.amount;
kt.m_strMemo = tr.raw;
kt.m_strPayee = tr.label;
ks.m_listTransactions += kt;
}
statementInterface()->import(ks);
d->progress->hide();
}
K_PLUGIN_FACTORY_WITH_JSON(WeboobFactory, "weboob.json", registerPlugin();)
#include "weboob.moc"
diff --git a/packaging/linux/appimage/build-image.sh b/packaging/linux/appimage/build-image.sh
index bda3bfd29..46fbc8c34 100755
--- a/packaging/linux/appimage/build-image.sh
+++ b/packaging/linux/appimage/build-image.sh
@@ -1,85 +1,95 @@
#!/bin/bash
# Halt on errors and be verbose about what we are doing
set -e
set -x
# Read in our parameters
export BUILD_PREFIX=$1
export KMYMONEY_SOURCES=$2
# Save some frequently referenced locations in variables for ease of use / updating
export APPDIR=$BUILD_PREFIX/kmymoney.appdir
-export PLUGINS=$APPDIR/usr/lib/plugins/kmymoney/
+export PLUGINS=$APPDIR/usr/lib/plugins/
+export APPIMAGEPLUGINS=$APPDIR/usr/plugins/
# qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment.
# That's not always the case, so make sure it is
export LC_ALL=en_US.UTF-8
export LANG=en_us.UTF-8
# We want to use $prefix/deps/usr/ for all our dependencies
export DEPS_INSTALL_PREFIX=$BUILD_PREFIX/deps/usr/
export DOWNLOADS_DIR=$BUILD_PREFIX/downloads/
# Setup variables needed to help everything find what we built
export LD_LIBRARY_PATH=$DEPS_INSTALL_PREFIX/lib/:$DEPS_INSTALL_PREFIX/lib/x86_64-linux-gnu/:$APPDIR/usr/lib/:$LD_LIBRARY_PATH
export PATH=$DEPS_INSTALL_PREFIX/bin/:$PATH
export PKG_CONFIG_PATH=$DEPS_INSTALL_PREFIX/share/pkgconfig/:$DEPS_INSTALL_PREFIX/lib/pkgconfig/:/usr/lib/pkgconfig/:$PKG_CONFIG_PATH
export CMAKE_PREFIX_PATH=$DEPS_INSTALL_PREFIX:$CMAKE_PREFIX_PATH
# Switch over to our build prefix
cd $BUILD_PREFIX
#
# Now we can get the process started!
#
# Step 0: place the translations where ki18n and Qt look for them
if [ -d $APPDIR/usr/share/locale ] ; then
mv $APPDIR/usr/share/locale $APPDIR/usr/share/kmymoney
fi
# Step 1: Copy over all the resources provided by dependencies that we need
cp -r $DEPS_INSTALL_PREFIX/share/locale $APPDIR/usr/share/kmymoney
cp -r $DEPS_INSTALL_PREFIX/share/kf5 $APPDIR/usr/share
cp -r $DEPS_INSTALL_PREFIX/share/mime $APPDIR/usr/share
cp -r $DEPS_INSTALL_PREFIX/translations $APPDIR/usr/
# Step 2: Relocate x64 binaries from the architecture specific directory as required for Appimages
mv $APPDIR/usr/lib/x86_64-linux-gnu/* $APPDIR/usr/lib
rm -rf $APPDIR/usr/lib/x86_64-linux-gnu/
# Step 3: Update the rpath in the various plugins we have to make sure they'll be loadable in an Appimage context
-for lib in $PLUGINS/*.so*; do
- patchelf --set-rpath '$ORIGIN/../..' $lib;
+for lib in $PLUGINS/kmymoney/*.so*; do
+ patchelf --set-rpath '$ORIGIN/../../lib' $lib;
done
-# Step 4: Build the image!!!
+# Step 4: Move plugins to loadable location in AppImage
+
+# Make sure our plugin directory already exists
+if [ ! -d $APPIMAGEPLUGINS ] ; then
+ mkdir -p $APPIMAGEPLUGINS
+fi
+
+mv $PLUGINS/* $APPIMAGEPLUGINS
+
+# Step 5: Build the image!!!
linuxdeployqt $APPDIR/usr/share/applications/org.kde.kmymoney.desktop \
-executable=$APPDIR/usr/bin/kmymoney \
-qmldir=$DEPS_INSTALL_PREFIX/qml \
-verbose=2 \
-bundle-non-qt-libs \
-appimage \
-exclude-libs=libnss3.so,libnssutil3.so
# Step 5: Find out what version of KMyMoney we built and give the Appimage a proper name
cd $BUILD_PREFIX/kmymoney-build
KMYMONEY_VERSION=$(grep "KMyMoney VERSION" CMakeLists.txt | cut -d '"' -f 2)
# Also find out the revision of Git we built
# Then use that to generate a combined name we'll distribute
cd $KMYMONEY_SOURCES
if [[ -d .git ]]; then
GIT_REVISION=$(git rev-parse --short HEAD)
VERSION=$KMYMONEY_VERSION-$GIT_REVISION
else
VERSION=$KMYMONEY_VERSION
fi
# Return to our build root
cd $BUILD_PREFIX
# Generate a new name for the Appimage file and rename it accordingly
APPIMAGE=kmymoney-"$VERSION"-x86_64.appimage
mv KMyMoney-x86_64.AppImage $APPIMAGE