diff --git a/kmymoney/converter/tests/converter-test.cpp b/kmymoney/converter/tests/converter-test.cpp --- a/kmymoney/converter/tests/converter-test.cpp +++ b/kmymoney/converter/tests/converter-test.cpp @@ -31,7 +31,6 @@ #include "mymoneypayee.h" #include "mymoneystatement.h" #include "mymoneyexception.h" -#include "storage/mymoneystoragexml.h" #include "storage/mymoneystoragedump.h" #include "webpricequote.h" diff --git a/kmymoney/kmymoney.h b/kmymoney/kmymoney.h --- a/kmymoney/kmymoney.h +++ b/kmymoney/kmymoney.h @@ -62,6 +62,7 @@ class IMyMoneyOperationsFormat; template class onlineJobTyped; +typedef void (*KMyMoneyAppCallback)(int, int, const QString &); namespace eDialogs { enum class ScheduleResultCode; } namespace eMenu { enum class Action; @@ -330,8 +331,9 @@ QString filename() const; QUrl filenameURL() const; + void writeFilenameURL(const QUrl &url); - void addToRecentFiles(const QUrl& url); + void addToRecentFiles(const QUrl &url); QTimer* autosaveTimer(); /** @@ -365,6 +367,10 @@ bool fileOpen() const; + KMyMoneyAppCallback progressCallback(); + + void consistencyCheck(bool alwaysDisplayResult); + protected: /** save general Options like all bar positions and status as well as the geometry and the recent file list to the configuration * file @@ -447,14 +453,6 @@ */ bool slotFileSave(); - /** - * ask the user for the filename and save the current document - * - * @retval false save operation failed - * @retval true save operation was successful - */ - bool slotFileSaveAs(); - /** asks for saving if the file is modified, then closes the actual file and window */ void slotFileCloseWindow(); diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -160,9 +160,7 @@ #include "misc/webconnect.h" #include "storage/mymoneystoragemgr.h" -#include "storage/mymoneystoragexml.h" -#include "storage/mymoneystoragebin.h" -#include "storage/mymoneystorageanon.h" +#include "imymoneystorageformat.h" #include @@ -708,173 +706,125 @@ * * @return Whether the read was successful. */ - bool openNondatabase(const QUrl &url) + bool openXMLFile(const QUrl &url) { - if (!url.isValid()) - throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid URL %1").arg(qPrintable(url.url()))); + // open the database + auto pStorage = MyMoneyFile::instance()->storage(); + if (!pStorage) + pStorage = new MyMoneyStorageMgr; - QString fileName; - auto downloadedFile = false; - if (url.isLocalFile()) { - fileName = url.toLocalFile(); - } else { - fileName = KMyMoneyUtils::downloadFile(url); - downloadedFile = true; + auto rc = false; + auto pluginFound = false; + for (const auto& plugin : pPlugins.storage) { + if (plugin->formatName().compare(QLatin1String("XML")) == 0) { + rc = plugin->open(pStorage, url); + pluginFound = true; + break; + } + } + + if(!pluginFound) + KMessageBox::error(q, i18n("Couldn't find suitable plugin to read your storage.")); + + if(!rc) { + removeStorage(); + return false; + } + + if (pStorage) { + MyMoneyFile::instance()->detachStorage(); + MyMoneyFile::instance()->attachStorage(pStorage); } - if (!KMyMoneyUtils::fileExists(QUrl::fromLocalFile(fileName))) - throw MYMONEYEXCEPTION(QString::fromLatin1("Error opening the file.\n" - "Requested file: '%1'.\n" - "Downloaded file: '%2'").arg(qPrintable(url.url()), fileName)); + m_fileType = KMyMoneyApp::KmmXML; + return true; + } + bool isGNCFile(const QUrl &url) + { + if (!url.isValid()) + throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid URL %1").arg(qPrintable(url.url()))); + if (!url.isLocalFile()) + return false; + + const auto fileName = url.toLocalFile(); + const auto sFileToShort = QString::fromLatin1("File %1 is too short.").arg(fileName); QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot read the file: %1").arg(fileName)); QByteArray qbaFileHeader(2, '\0'); - const auto sFileToShort = QString::fromLatin1("File %1 is too short.").arg(fileName); if (file.read(qbaFileHeader.data(), 2) != 2) throw MYMONEYEXCEPTION(sFileToShort); file.close(); - // There's a problem with the KFilterDev and KGPGFile classes: - // One supports the at(n) member but not ungetch() together with - // read() and the other does not provide an at(n) method but - // supports read() that considers the ungetch() buffer. QFile - // supports everything so this is not a problem. We solve the problem - // for now by keeping track of which method can be used. - auto haveAt = true; - auto isEncrypted = false; - - emit q->kmmFilePlugin(preOpen); - QIODevice* qfile = nullptr; QString sFileHeader(qbaFileHeader); - if (sFileHeader == QString("\037\213")) { // gzipped? + if (sFileHeader == QString("\037\213")) // gzipped? qfile = new KCompressionDevice(fileName, COMPRESSION_TYPE); - } else if (sFileHeader == QString("--") || // PGP ASCII armored? - sFileHeader == QString("\205\001") || // PGP binary? - sFileHeader == QString("\205\002")) { // PGP binary? - if (KGPGFile::GPGAvailable()) { - qfile = new KGPGFile(fileName); - haveAt = false; - isEncrypted = true; - } else { - throw MYMONEYEXCEPTION(QString::fromLatin1("GPG is not available for decryption of file %1").arg(fileName)); - } - } else { - // we can't use file directly, as we delete qfile later on - qfile = new QFile(file.fileName()); - } + else + return false; if (!qfile->open(QIODevice::ReadOnly)) { delete qfile; throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot read the file: %1").arg(fileName)); } - qbaFileHeader.resize(8); - if (qfile->read(qbaFileHeader.data(), 8) != 8) - throw MYMONEYEXCEPTION(sFileToShort); - - if (haveAt) - qfile->seek(0); - else - ungetString(qfile, qbaFileHeader.data(), 8); - - // Ok, we got the first block of 8 bytes. Read in the two - // unsigned long int's by preserving endianess. This is - // achieved by reading them through a QDataStream object - qint32 magic0, magic1; - QDataStream s(&qbaFileHeader, QIODevice::ReadOnly); - s >> magic0; - s >> magic1; - - // If both magic numbers match (we actually read in the - // text 'KMyMoney' then we assume a binary file and - // construct a reader for it. Otherwise, we construct - // an XML reader object. - // - // The expression magic0 < 30 is only used to create - // a binary reader if we assume an old binary file. This - // should be removed at some point. An alternative is to - // check the beginning of the file against an pattern - // of the XML file (e.g. '?File %1 contains the old binary format used by KMyMoney. Please use an older version of KMyMoney (0.8.x) that still supports this format to convert it to the new XML based format.").arg(fileName)); - } - // Scan the first 70 bytes to see if we find something // we know. For now, we support our own XML format and // GNUCash XML format. If the file is smaller, then it // contains no valid data and we reject it anyway. qbaFileHeader.resize(70); if (qfile->read(qbaFileHeader.data(), 70) != 70) throw MYMONEYEXCEPTION(sFileToShort); - if (haveAt) - qfile->seek(0); - else - ungetString(qfile, qbaFileHeader.data(), 70); + QString txt(qbaFileHeader); + + qfile->close(); + delete qfile; - IMyMoneyOperationsFormat* pReader = nullptr; - QRegExp kmyexp(""); QRegExp gncexp("formatName().compare(QLatin1String("GNC")) == 0) { - pReader = plugin->reader(); - break; - } - } - if (!pReader) { - KMessageBox::error(q, i18n("Couldn't find suitable plugin to read your storage.")); - return false; + if (!(gncexp.indexIn(txt) != -1)) + return false; + return true; + } + + bool openGNCFile(const QUrl &url) + { + IMyMoneyOperationsFormat* pReader = nullptr; + for (const auto& plugin : pPlugins.storage) { + if (plugin->formatName().compare(QLatin1String("GNC")) == 0) { + pReader = plugin->reader(); + break; } - m_fileType = KMyMoneyApp::GncXML; - } else { - throw MYMONEYEXCEPTION(QString::fromLatin1("%1").arg(i18n("File %1 contains an unknown file format.", fileName))); } + if (!pReader) { + KMessageBox::error(q, i18n("Couldn't find suitable plugin to read your storage.")); + return false; + } + m_fileType = KMyMoneyApp::GncXML; // disconnect the current storga manager from the engine MyMoneyFile::instance()->detachStorage(); // create a new empty storage object auto storage = new MyMoneyStorageMgr; - // attach the storage before reading the file, since the online - // onlineJobAdministration object queries the engine during - // loading. - MyMoneyFile::instance()->attachStorage(storage); - + QIODevice* qfile = new KCompressionDevice(url.toLocalFile(), COMPRESSION_TYPE); pReader->setProgressCallback(&KMyMoneyApp::progressCallback); pReader->readFile(qfile, storage); pReader->setProgressCallback(0); delete pReader; + // attach the storage before reading the file, since the online + // onlineJobAdministration object queries the engine during + // loading. + MyMoneyFile::instance()->attachStorage(storage); + qfile->close(); delete qfile; - - // if a temporary file was downloaded, then it will be removed - // with the next call. Otherwise, it stays untouched on the local - // filesystem. - if (downloadedFile) - QFile::remove(fileName); - - // encapsulate transactions to the engine to be able to commit/rollback - MyMoneyFileTransaction ft; - // make sure we setup the encryption key correctly - if (isEncrypted && MyMoneyFile::instance()->value("kmm-encryption-key").isEmpty()) - MyMoneyFile::instance()->setValue("kmm-encryption-key", KMyMoneySettings::gpgRecipientList().join(",")); - ft.commit(); return true; } @@ -916,6 +866,8 @@ MyMoneyFile::instance()->detachStorage(); MyMoneyFile::instance()->attachStorage(pStorage); } + + m_fileType = KMyMoneyApp::KmmDb; return true; } @@ -931,224 +883,6 @@ m_fileOpen = true; } - /** - * Saves the data into permanent storage using the XML format. - * - * @param url The URL to save into. - * If no protocol is specified, file:// is assumed. - * @param keyList QString containing a comma separated list of keys - * to be used for encryption. If @p keyList is empty, - * the file will be saved unencrypted (the default) - * - * @retval false save operation failed - * @retval true save operation was successful - */ - bool saveFile(const QUrl &url, const QString& keyList = QString()) - { - QString filename = url.path(); - - if (!m_fileOpen) { - KMessageBox::error(q, i18n("Tried to access a file when it has not been opened")); - return false; - } - - emit q->kmmFilePlugin(KMyMoneyApp::preSave); - std::unique_ptr storageWriter; - - // If this file ends in ".ANON.XML" then this should be written using the - // anonymous writer. - bool plaintext = filename.right(4).toLower() == ".xml"; - if (filename.right(9).toLower() == ".anon.xml") - storageWriter = std::make_unique(); - else - storageWriter = std::make_unique(); - - // actually, url should be the parameter to this function - // but for now, this would involve too many changes - auto rc = true; - try { - if (! url.isValid()) { - throw MYMONEYEXCEPTION(QString::fromLatin1("Malformed URL '%1'").arg(url.url())); - } - - if (url.scheme() == QLatin1String("sql")) { - rc = false; - auto pluginFound = false; - for (const auto& plugin : pPlugins.storage) { - if (plugin->formatName().compare(QLatin1String("SQL")) == 0) { - rc = plugin->save(url); - pluginFound = true; - break; - } - } - - if(!pluginFound) - throw MYMONEYEXCEPTION(QString::fromLatin1("Couldn't find suitable plugin to save your storage.")); - - } else if (url.isLocalFile()) { - filename = url.toLocalFile(); - try { - const unsigned int nbak = KMyMoneySettings::autoBackupCopies(); - if (nbak) { - KBackup::numberedBackupFile(filename, QString(), QStringLiteral("~"), nbak); - } - saveToLocalFile(filename, storageWriter.get(), plaintext, keyList); - } catch (const MyMoneyException &) { - throw MYMONEYEXCEPTION(QString::fromLatin1("Unable to write changes to '%1'").arg(filename)); - } - } else { - - QTemporaryFile tmpfile; - tmpfile.open(); // to obtain the name - tmpfile.close(); - saveToLocalFile(tmpfile.fileName(), storageWriter.get(), plaintext, keyList); - - Q_CONSTEXPR int permission = -1; - QFile file(tmpfile.fileName()); - file.open(QIODevice::ReadOnly); - KIO::StoredTransferJob *putjob = KIO::storedPut(file.readAll(), url, permission, KIO::JobFlag::Overwrite); - if (!putjob->exec()) { - throw MYMONEYEXCEPTION(QString::fromLatin1("Unable to upload to '%1'.
%2").arg(url.toDisplayString(), putjob->errorString())); - } - file.close(); - } - m_fileType = KMyMoneyApp::KmmXML; - } catch (const MyMoneyException &e) { - KMessageBox::error(q, QString::fromLatin1(e.what())); - MyMoneyFile::instance()->setDirty(); - rc = false; - } - emit q->kmmFilePlugin(postSave); - return rc; - } - - /** - * This method is used by saveFile() to store the data - * either directly in the destination file if it is on - * the local file system or in a temporary file when - * the final destination is reached over a network - * protocol (e.g. FTP) - * - * @param localFile the name of the local file - * @param writer pointer to the formatter - * @param plaintext whether to override any compression & encryption settings - * @param keyList QString containing a comma separated list of keys to be used for encryption - * If @p keyList is empty, the file will be saved unencrypted - * - * @note This method will close the file when it is written. - */ - void saveToLocalFile(const QString& localFile, IMyMoneyOperationsFormat* pWriter, bool plaintext, const QString& keyList) - { - // Check GPG encryption - bool encryptFile = true; - bool encryptRecover = false; - if (!keyList.isEmpty()) { - if (!KGPGFile::GPGAvailable()) { - KMessageBox::sorry(q, i18n("GPG does not seem to be installed on your system. Please make sure that GPG can be found using the standard search path. This time, encryption is disabled."), i18n("GPG not found")); - encryptFile = false; - } else { - if (KMyMoneySettings::encryptRecover()) { - encryptRecover = true; - if (!KGPGFile::keyAvailable(QString(recoveryKeyId))) { - KMessageBox::sorry(q, i18n("

You have selected to encrypt your data also with the KMyMoney recover key, but the key with id

%1

has not been found in your keyring at this time. Please make sure to import this key into your keyring. You can find it on the KMyMoney web-site. This time your data will not be encrypted with the KMyMoney recover key.

", QString(recoveryKeyId)), i18n("GPG Key not found")); - encryptRecover = false; - } - } - - for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { - if (!KGPGFile::keyAvailable(key)) { - KMessageBox::sorry(q, i18n("

You have specified to encrypt your data for the user-id

%1.

Unfortunately, a valid key for this user-id was not found in your keyring. Please make sure to import a valid key for this user-id. This time, encryption is disabled.

", key), i18n("GPG Key not found")); - encryptFile = false; - break; - } - } - - if (encryptFile == true) { - QString msg = i18n("

You have configured to save your data in encrypted form using GPG. Make sure you understand that you might lose all your data if you encrypt it, but cannot decrypt it later on. If unsure, answer No.

"); - if (KMessageBox::questionYesNo(q, msg, i18n("Store GPG encrypted"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "StoreEncrypted") == KMessageBox::No) { - encryptFile = false; - } - } - } - } - - - // Create a temporary file if needed - QString writeFile = localFile; - QTemporaryFile tmpFile; - if (QFile::exists(localFile)) { - tmpFile.open(); - writeFile = tmpFile.fileName(); - tmpFile.close(); - } - - /** - * @brief Automatically restore settings when scope is left - */ - struct restorePreviousSettingsHelper { - restorePreviousSettingsHelper() - : m_signalsWereBlocked{MyMoneyFile::instance()->signalsBlocked()} - { - MyMoneyFile::instance()->blockSignals(true); - } - - ~restorePreviousSettingsHelper() - { - MyMoneyFile::instance()->blockSignals(m_signalsWereBlocked); - } - const bool m_signalsWereBlocked; - } restoreHelper; - - MyMoneyFileTransaction ft; - MyMoneyFile::instance()->deletePair("kmm-encryption-key"); - std::unique_ptr device; - - if (!keyList.isEmpty() && encryptFile && !plaintext) { - std::unique_ptr kgpg = std::unique_ptr(new KGPGFile{writeFile}); - if (kgpg) { - for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { - kgpg->addRecipient(key.toLatin1()); - } - - if (encryptRecover) { - kgpg->addRecipient(recoveryKeyId); - } - MyMoneyFile::instance()->setValue("kmm-encryption-key", keyList); - device = std::unique_ptr(kgpg.release()); - } - } else { - QFile *file = new QFile(writeFile); - // The second parameter of KCompressionDevice means that KCompressionDevice will delete the QFile object - device = std::unique_ptr(new KCompressionDevice{file, true, (plaintext) ? KCompressionDevice::None : COMPRESSION_TYPE}); - } - - ft.commit(); - - if (!device || !device->open(QIODevice::WriteOnly)) { - throw MYMONEYEXCEPTION(QString::fromLatin1("Unable to open file '%1' for writing.").arg(localFile)); - } - - pWriter->setProgressCallback(&KMyMoneyApp::progressCallback); - pWriter->writeFile(device.get(), MyMoneyFile::instance()->storage()); - device->close(); - - // Check for errors if possible, only possible for KGPGFile - QFileDevice *fileDevice = qobject_cast(device.get()); - if (fileDevice && fileDevice->error() != QFileDevice::NoError) { - throw MYMONEYEXCEPTION(QString::fromLatin1("Failure while writing to '%1'").arg(localFile)); - } - - if (writeFile != localFile) { - // This simple comparison is possible because the strings are equal if no temporary file was created. - // If a temporary file was created, it is made in a way that the name is definitely different. So no - // symlinks etc. have to be evaluated. - if (!QFile::remove(localFile) || !QFile::rename(writeFile, localFile)) - throw MYMONEYEXCEPTION(QString::fromLatin1("Failure while writing to '%1'").arg(localFile)); - } - QFile::setPermissions(localFile, m_fmode); - pWriter->setProgressCallback(0); - } - /** * Call this to see if the MyMoneyFile contains any unsaved data. * @@ -1849,7 +1583,7 @@ KStandardAction::open(this, &KMyMoneyApp::slotFileOpen, aC); d->m_recentFiles = KStandardAction::openRecent(this, &KMyMoneyApp::slotFileOpenRecent, aC); KStandardAction::save(this, &KMyMoneyApp::slotFileSave, aC); - KStandardAction::saveAs(this, &KMyMoneyApp::slotFileSaveAs, aC); +// KStandardAction::saveAs(this, &KMyMoneyApp::slotFileSaveAs, aC); KStandardAction::close(this, &KMyMoneyApp::slotFileClose, aC); KStandardAction::quit(this, &KMyMoneyApp::slotFileQuit, aC); lutActions.insert(Action::Print, KStandardAction::print(this, &KMyMoneyApp::slotPrintView, aC)); @@ -2461,7 +2195,7 @@ // fixup logic and then save it to keep the modified // flag off. slotFileSave(); - if (d->openNondatabase(d->m_fileName)) { + if (d->openXMLFile(d->m_fileName)) { d->m_fileOpen = true; d->initializeStorage(); } @@ -2505,6 +2239,16 @@ return d->m_fileOpen; } +KMyMoneyAppCallback KMyMoneyApp::progressCallback() +{ + return &KMyMoneyApp::progressCallback; +} + +void KMyMoneyApp::consistencyCheck(bool alwaysDisplayResult) +{ + d->consistencyCheck(alwaysDisplayResult); +} + // General open void KMyMoneyApp::slotFileOpen() { @@ -2604,8 +2348,10 @@ auto isOpened = false; if (url.scheme() == QLatin1String("sql")) isOpened = d->openDatabase(url); + else if (d->isGNCFile(url)) + isOpened = d->openGNCFile(url); else - isOpened = d->openNondatabase(url); + isOpened = d->openXMLFile(url); if (!isOpened) return; @@ -2647,111 +2393,39 @@ KMSTATUS(i18n("Saving file...")); if (d->m_fileName.isEmpty()) - return slotFileSaveAs(); + return false; d->consistencyCheck(false); setEnabled(false); - if (isDatabase()) { - auto pluginFound = false; - for (const auto& plugin : pPlugins.storage) { - if (plugin->formatName().compare(QLatin1String("SQL")) == 0) { - rc = plugin->save(d->m_fileName); - pluginFound = true; - break; - } - } - if(!pluginFound) - KMessageBox::error(this, i18n("Couldn't find suitable plugin to save your storage.")); - } else { - rc = d->saveFile(d->m_fileName, MyMoneyFile::instance()->value("kmm-encryption-key")); - } - setEnabled(true); - - d->m_autoSaveTimer->stop(); - - updateCaption(); - return rc; -} - -bool KMyMoneyApp::slotFileSaveAs() -{ - bool rc = false; - KMSTATUS(i18n("Saving file with a new filename...")); - - QString selectedKeyName; - if (KGPGFile::GPGAvailable() && KMyMoneySettings::writeDataEncrypted()) { - // fill the secret key list and combo box - QStringList keyList; - KGPGFile::secretKeyList(keyList); - - QPointer dlg = new KGpgKeySelectionDlg(this); - dlg->setSecretKeys(keyList, KMyMoneySettings::gpgRecipient()); - dlg->setAdditionalKeys(KMyMoneySettings::gpgRecipientList()); - rc = dlg->exec(); - if ((rc == QDialog::Accepted) && (dlg != 0)) { - d->m_additionalGpgKeys = dlg->additionalKeys(); - selectedKeyName = dlg->secretKey(); - } - delete dlg; - if (rc != QDialog::Accepted) { + QString format; + switch (d->m_fileType) { + case KMyMoneyApp::KmmXML: + case KMyMoneyApp::GncXML: + format = QStringLiteral("XML"); + break; + case KMyMoneyApp::KmmDb: + format = QStringLiteral("SQL"); + break; + default: return false; - } } - QString prevDir; // don't prompt file name if not a native file - if (isNativeFile()) - prevDir = readLastUsedDir(); - - QPointer dlg = - new QFileDialog(this, i18n("Save As"), prevDir, - QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.kmy")).arg(i18nc("KMyMoney (Filefilter)", "KMyMoney files")) + - QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.xml")).arg(i18nc("XML (Filefilter)", "XML files")) + - QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.anon.xml")).arg(i18nc("Anonymous (Filefilter)", "Anonymous files")) + - QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*")).arg(i18nc("All files (Filefilter)", "All files"))); - dlg->setAcceptMode(QFileDialog::AcceptSave); - - if (dlg->exec() == QDialog::Accepted && dlg != 0) { - QUrl newURL = dlg->selectedUrls().first(); - if (!newURL.fileName().isEmpty()) { - d->consistencyCheck(false); - QString newName = newURL.toDisplayString(QUrl::PreferLocalFile); - - // append extension if not present - if (!newName.endsWith(QLatin1String(".kmy"), Qt::CaseInsensitive) && - !newName.endsWith(QLatin1String(".xml"), Qt::CaseInsensitive)) - newName.append(QLatin1String(".kmy")); - newURL = QUrl::fromUserInput(newName); - d->m_recentFiles->addUrl(newURL); - - setEnabled(false); - // If this is the anonymous file export, just save it, don't actually take the - // name, or remember it! Don't even try to encrypt it - if (newName.endsWith(QLatin1String(".anon.xml"), Qt::CaseInsensitive)) - rc = d->saveFile(newURL); - else { - d->m_fileName = newURL; - QString encryptionKeys; - QRegExp keyExp(".* \\((.*)\\)"); - if (keyExp.indexIn(selectedKeyName) != -1) { - encryptionKeys = keyExp.cap(1); - if (!d->m_additionalGpgKeys.isEmpty()) { - if (!encryptionKeys.isEmpty()) - encryptionKeys.append(QLatin1Char(',')); - encryptionKeys.append(d->m_additionalGpgKeys.join(QLatin1Char(','))); - } - } - rc = d->saveFile(d->m_fileName, encryptionKeys); - //write the directory used for this file as the default one for next time. - writeLastUsedDir(newURL.toDisplayString(QUrl::RemoveFilename | QUrl::PreferLocalFile | QUrl::StripTrailingSlash)); - writeLastUsedFile(newName); - } - d->m_autoSaveTimer->stop(); - setEnabled(true); + auto pluginFound = false; + for (const auto& plugin : pPlugins.storage) { + if (plugin->formatName().compare(format) == 0) { + rc = plugin->save(d->m_fileName); + pluginFound = true; + break; } } + if(!pluginFound) + KMessageBox::error(this, i18n("Couldn't find suitable plugin to save your storage.")); + + setEnabled(true); + + d->m_autoSaveTimer->stop(); - delete dlg; updateCaption(); return rc; } @@ -3756,7 +3430,7 @@ // Disabling standard actions based on conditions // ************* aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(modified /*&& !d->m_myMoneyView->isDatabase()*/); - aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(fileOpen); +// aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(fileOpen); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Close)))->setEnabled(fileOpen); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Print)))->setEnabled(fileOpen && d->m_myMoneyView->canPrint()); } @@ -4013,6 +3687,11 @@ return d->m_fileName; } +void KMyMoneyApp::writeFilenameURL(const QUrl &url) +{ + d->m_fileName = url; +} + void KMyMoneyApp::addToRecentFiles(const QUrl& url) { d->m_recentFiles->addUrl(url); diff --git a/kmymoney/mymoney/CMakeLists.txt b/kmymoney/mymoney/CMakeLists.txt --- a/kmymoney/mymoney/CMakeLists.txt +++ b/kmymoney/mymoney/CMakeLists.txt @@ -33,9 +33,7 @@ # which are in fact available in kmm_mymoney set(storage_a_SOURCES ./storage/imymoneystorageformat.cpp - ./storage/mymoneystoragexml.cpp ./storage/mymoneystoragemgr.cpp - ./storage/mymoneystorageanon.cpp ./storage/mymoneystoragenames.cpp ) diff --git a/kmymoney/mymoney/tests/mymoneyforecast-test.cpp b/kmymoney/mymoney/tests/mymoneyforecast-test.cpp --- a/kmymoney/mymoney/tests/mymoneyforecast-test.cpp +++ b/kmymoney/mymoney/tests/mymoneyforecast-test.cpp @@ -26,7 +26,6 @@ #include "mymoneyexception.h" #include "mymoneystoragedump.h" -#include "mymoneystoragexml.h" #include "reportstestcommon.h" #include "mymoneyinstitution.h" diff --git a/kmymoney/plugins/CMakeLists.txt b/kmymoney/plugins/CMakeLists.txt --- a/kmymoney/plugins/CMakeLists.txt +++ b/kmymoney/plugins/CMakeLists.txt @@ -13,6 +13,10 @@ add_subdirectory(sql) endif() +# if(ENABLE_XMLSTORAGE) + add_subdirectory(xml) +# endif() + if(ENABLE_IBANBICDATA) add_subdirectory(ibanbicdata) endif() diff --git a/kmymoney/plugins/appinterface.h b/kmymoney/plugins/appinterface.h --- a/kmymoney/plugins/appinterface.h +++ b/kmymoney/plugins/appinterface.h @@ -33,6 +33,7 @@ class QTimer; class IMyMoneyOperationsFormat; +typedef void (*KMyMoneyAppCallback)(int, int, const QString &); namespace KMyMoneyPlugin { @@ -52,13 +53,20 @@ virtual bool fileOpen() = 0; virtual bool isDatabase() = 0; + virtual bool isNativeFile() = 0; virtual QUrl filenameURL() const = 0; + virtual void writeFilenameURL(const QUrl &url) = 0; virtual QUrl lastOpenedURL() = 0; virtual void writeLastUsedFile(const QString& fileName) = 0; virtual void slotFileOpenRecent(const QUrl &url) = 0; virtual void addToRecentFiles(const QUrl& url) = 0; virtual void updateCaption(bool skipActions = false) = 0; virtual QTimer* autosaveTimer() = 0; + virtual KMyMoneyAppCallback progressCallback() = 0; + virtual void writeLastUsedDir(const QString &directory) = 0; + virtual QString readLastUsedDir() const = 0; + virtual void consistencyCheck(bool alwaysDisplayResult) = 0; + Q_SIGNALS: void kmmFilePlugin(unsigned int); diff --git a/kmymoney/plugins/interfaces/kmmappinterface.h b/kmymoney/plugins/interfaces/kmmappinterface.h --- a/kmymoney/plugins/interfaces/kmmappinterface.h +++ b/kmymoney/plugins/interfaces/kmmappinterface.h @@ -56,13 +56,19 @@ bool fileOpen() override; bool isDatabase() override; + bool isNativeFile() override; QUrl filenameURL() const override; + void writeFilenameURL(const QUrl &url) override; QUrl lastOpenedURL() override; void writeLastUsedFile(const QString& fileName) override; void slotFileOpenRecent(const QUrl &url) override; void addToRecentFiles(const QUrl& url) override; void updateCaption(bool skipActions = false) override; QTimer* autosaveTimer() override; + KMyMoneyAppCallback progressCallback() override; + void writeLastUsedDir(const QString &directory) override; + QString readLastUsedDir() const override; + void consistencyCheck(bool alwaysDisplayResult) override; private: KMyMoneyApp* m_app; diff --git a/kmymoney/plugins/interfaces/kmmappinterface.cpp b/kmymoney/plugins/interfaces/kmmappinterface.cpp --- a/kmymoney/plugins/interfaces/kmmappinterface.cpp +++ b/kmymoney/plugins/interfaces/kmmappinterface.cpp @@ -46,11 +46,21 @@ return m_app->isDatabase(); } +bool KMyMoneyPlugin::KMMAppInterface::isNativeFile() +{ + return m_app->isNativeFile(); +} + QUrl KMyMoneyPlugin::KMMAppInterface::filenameURL() const { return m_app->filenameURL(); } +void KMyMoneyPlugin::KMMAppInterface::writeFilenameURL(const QUrl &url) +{ + m_app->writeFilenameURL(url); +} + QUrl KMyMoneyPlugin::KMMAppInterface::lastOpenedURL() { return m_app->lastOpenedURL(); @@ -80,3 +90,23 @@ { return m_app->autosaveTimer(); } + +KMyMoneyAppCallback KMyMoneyPlugin::KMMAppInterface::progressCallback() +{ + return m_app->progressCallback(); +} + +void KMyMoneyPlugin::KMMAppInterface::writeLastUsedDir(const QString &directory) +{ + m_app->writeLastUsedDir(directory); +} + +QString KMyMoneyPlugin::KMMAppInterface::readLastUsedDir() const +{ + return m_app->readLastUsedDir(); +} + +void KMyMoneyPlugin::KMMAppInterface::consistencyCheck(bool alwaysDisplayResult) +{ + m_app->consistencyCheck(alwaysDisplayResult); +} diff --git a/kmymoney/plugins/xml/CMakeLists.txt b/kmymoney/plugins/xml/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/xml/CMakeLists.txt @@ -0,0 +1,46 @@ +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/xmlstorage.json.in ${CMAKE_CURRENT_BINARY_DIR}/xmlstorage.json @ONLY) + +set(xmlstorage_SOURCES + xmlstorage.cpp + mymoneystoragexml.cpp + mymoneystoragenames.cpp + mymoneystorageanon.cpp + kgpgkeyselectiondlg.cpp + ) + +ki18n_wrap_ui(xmlstorage_SOURCES kgpgkeyselectiondlg.ui) + +kcoreaddons_add_plugin(xmlstorage + SOURCES ${xmlstorage_SOURCES} + JSON "${CMAKE_CURRENT_BINARY_DIR}/xmlstorage.json" + INSTALL_NAMESPACE "kmymoney") + +#kcoreaddons_add_plugin sets LIBRARY_OUTPUT_DIRECTORY to ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${INSTALL_NAMESPACE} +set_target_properties(xmlstorage + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") + +# kde_target_enable_exceptions(xmlstorage PUBLIC) + +target_link_libraries(xmlstorage + PUBLIC + kmm_plugin + PRIVATE + Qt5::Xml + KF5::Archive + KF5::I18n + KF5::CoreAddons + kgpgfile + kmymoney_common +) + +install(FILES xmlstorage.rc + DESTINATION "${KXMLGUI_INSTALL_DIR}/xmlstorage") + +# install(FILES kmymoney-xmlstorageplugin.desktop +# DESTINATION ${SERVICETYPES_INSTALL_DIR} +# ) + +# if(BUILD_TESTING) +# add_subdirectory(tests) +# endif() diff --git a/kmymoney/plugins/interfaces/kmmappinterface.h b/kmymoney/plugins/xml/kgpgkeyselectiondlg.h copy from kmymoney/plugins/interfaces/kmmappinterface.h copy to kmymoney/plugins/xml/kgpgkeyselectiondlg.h --- a/kmymoney/plugins/interfaces/kmmappinterface.h +++ b/kmymoney/plugins/xml/kgpgkeyselectiondlg.h @@ -1,8 +1,7 @@ /*************************************************************************** - kmmappinterface.h + kgpgkeyselectiondlg.h ------------------- - begin : Mon Apr 14 2008 - copyright : (C) 2008 Thomas Baumgart + copyright : (C) 2008 by Thomas Baumgart email : ipwizard@users.sourceforge.net (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ @@ -16,58 +15,63 @@ * * ***************************************************************************/ -#ifndef KMMAPPINTERFACE_H -#define KMMAPPINTERFACE_H +#ifndef KGPGKEYSELECTIONDLG_H +#define KGPGKEYSELECTIONDLG_H // ---------------------------------------------------------------------------- // QT Includes -#include +#include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes -#include "appinterface.h" +/** + * @author Thomas Baumgart + */ +class KGpgKeySelectionDlgPrivate; +class KGpgKeySelectionDlg : public QDialog +{ + Q_OBJECT + Q_DISABLE_COPY(KGpgKeySelectionDlg) -class KMyMoneyApp; +public: + + explicit KGpgKeySelectionDlg(QWidget* parent = nullptr); + ~KGpgKeySelectionDlg(); -namespace KMyMoneyPlugin -{ /** - * This class represents the implementation of the - * AppInterface. - */ - class KMMAppInterface : public AppInterface - { - Q_OBJECT - - public: - explicit KMMAppInterface(KMyMoneyApp* app, QObject* parent, const char* name = 0); - ~KMMAppInterface() override = default; - - /** - * Makes sure that a MyMoneyFile is open and has been created successfully. - * - * @return Whether the file is open and initialised - */ - bool fileOpen() override; - - bool isDatabase() override; - QUrl filenameURL() const override; - QUrl lastOpenedURL() override; - void writeLastUsedFile(const QString& fileName) override; - void slotFileOpenRecent(const QUrl &url) override; - void addToRecentFiles(const QUrl& url) override; - void updateCaption(bool skipActions = false) override; - QTimer* autosaveTimer() override; - - private: - KMyMoneyApp* m_app; - }; - -} + * preset the key selector with the keys contained in @a keyList. + * The key contained in @a defaultKey is made the current selection. + */ + void setSecretKeys(const QStringList& keyList, const QString& defaultKey); + + /** + * preset the additional key list with the given key ids in @a list + */ + void setAdditionalKeys(const QStringList& list); + + /** + * Returns the selected secret key. In case "No encryption" is selected, + * the string is empty. + */ + QString secretKey() const; + + /** + * Returns the list of keys currently listed in the KEditListWidget + */ + QStringList additionalKeys() const; + +protected Q_SLOTS: + void slotIdChanged(); + void slotKeyListChanged(); + +private: + KGpgKeySelectionDlgPrivate * const d_ptr; + Q_DECLARE_PRIVATE(KGpgKeySelectionDlg) +}; #endif diff --git a/kmymoney/plugins/xml/kgpgkeyselectiondlg.cpp b/kmymoney/plugins/xml/kgpgkeyselectiondlg.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/xml/kgpgkeyselectiondlg.cpp @@ -0,0 +1,200 @@ +/*************************************************************************** + kgpgkeyselectiondlg.cpp + ------------------- + copyright : (C) 2008 by Thomas Baumgart + email : ipwizard@users.sourceforge.net + (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. * + * * + ***************************************************************************/ + +#include "kgpgkeyselectiondlg.h" + +// ---------------------------------------------------------------------------- +// QT Includes +#include +#include + +// ---------------------------------------------------------------------------- +// KDE Includes + +// ---------------------------------------------------------------------------- +// Project Includes + +#include +#include + +class KGpgKeySelectionDlgPrivate +{ + Q_DISABLE_COPY(KGpgKeySelectionDlgPrivate) + +public: + KGpgKeySelectionDlgPrivate() + : ui(new Ui::KGpgKeySelectionDlg) + , needCheckList(true) + , listOk(false) + , checkCount(0) + { + } + + ~KGpgKeySelectionDlgPrivate() + { + delete ui; + } + + Ui::KGpgKeySelectionDlg* ui; + bool needCheckList; + bool listOk; + int checkCount; +}; + + +KGpgKeySelectionDlg::KGpgKeySelectionDlg(QWidget *parent) : + QDialog(parent), + d_ptr(new KGpgKeySelectionDlgPrivate) +{ + Q_D(KGpgKeySelectionDlg); + d->ui->setupUi(this); + connect(d->ui->m_secretKey, SIGNAL(currentIndexChanged(int)), this, SLOT(slotIdChanged())); + connect(d->ui->m_listWidget, &KEditListWidget::changed, this, &KGpgKeySelectionDlg::slotIdChanged); + connect(d->ui->m_listWidget, &KEditListWidget::added, this, &KGpgKeySelectionDlg::slotKeyListChanged); + connect(d->ui->m_listWidget, &KEditListWidget::removed, this, &KGpgKeySelectionDlg::slotKeyListChanged); +} + +KGpgKeySelectionDlg::~KGpgKeySelectionDlg() +{ + Q_D(KGpgKeySelectionDlg); + delete d; +} + +void KGpgKeySelectionDlg::setSecretKeys(const QStringList& keyList, const QString& defaultKey) +{ + static constexpr char recoveryKeyId[] = "59B0F826D2B08440"; + + Q_D(KGpgKeySelectionDlg); + d->ui->m_secretKey->addItem(i18n("No encryption")); + + foreach(auto key, keyList) { + QStringList fields = key.split(':', QString::SkipEmptyParts); + if (fields[0] != recoveryKeyId) { + // replace parenthesis in name field with brackets + auto name = fields[1]; + name.replace('(', "["); + name.replace(')', "]"); + name = QString("%1 (0x%2)").arg(name).arg(fields[0]); + d->ui->m_secretKey->addItem(name); + if (name.contains(defaultKey)) { + d->ui->m_secretKey->setCurrentText(name); + } + } + } +} + +QString KGpgKeySelectionDlg::secretKey() const +{ + Q_D(const KGpgKeySelectionDlg); + const bool enabled = (d->ui->m_secretKey->currentIndex() != 0); + QString key; + if (enabled) { + key = d->ui->m_secretKey->currentText(); + } + return key; +} + +void KGpgKeySelectionDlg::setAdditionalKeys(const QStringList& list) +{ + Q_D(KGpgKeySelectionDlg); + d->ui->m_listWidget->clear(); + d->ui->m_listWidget->insertStringList(list); + slotKeyListChanged(); +} + +QStringList KGpgKeySelectionDlg::additionalKeys() const +{ + Q_D(const KGpgKeySelectionDlg); + return d->ui->m_listWidget->items(); +} + +#if 0 +void KGpgKeySelectionDlg::slotShowHelp() +{ + QString anchor = m_helpAnchor[m_criteriaTab->currentPage()]; + if (anchor.isEmpty()) + anchor = QString("details.search"); + + KHelpClient::invokeHelp(anchor); +} +#endif + +void KGpgKeySelectionDlg::slotKeyListChanged() +{ + Q_D(KGpgKeySelectionDlg); + d->needCheckList = true; + slotIdChanged(); +} + +void KGpgKeySelectionDlg::slotIdChanged() +{ + Q_D(KGpgKeySelectionDlg); + // this looks a bit awkward. Here's why: KGPGFile::keyAvailable() starts + // an external task and processes UI events while it waits for the external + // process to finish. Thus, the first time we get here, the external process + // is started and the user may press a second key which calls this routine + // again. + // + // The second invocation is counted, but the check is not started until the + // first one finishes. Once the external process finishes, we check if we + // were called in the meantime and restart the check. + if (++d->checkCount == 1) { + const bool enabled = (d->ui->m_secretKey->currentIndex() != 0); + d->ui->m_listWidget->setEnabled(enabled); + d->ui->m_keyLed->setState(enabled ? KLed::On : KLed::Off); + while (enabled) { + // first we check the current edit field if filled + bool keysOk = true; + if (!d->ui->m_listWidget->currentText().isEmpty()) { + keysOk = KGPGFile::keyAvailable(d->ui->m_listWidget->currentText()); + } + + // if it is available, then scan the current list if we need to + if (keysOk) { + if (d->needCheckList) { + QStringList keys = d->ui->m_listWidget->items(); + QStringList::const_iterator it_s; + for (it_s = keys.constBegin(); keysOk && it_s != keys.constEnd(); ++it_s) { + if (!KGPGFile::keyAvailable(*it_s)) + keysOk = false; + } + d->listOk = keysOk; + d->needCheckList = false; + + } else { + keysOk = d->listOk; + } + } + + // did we receive some more requests to check? + if (d->checkCount > 1) { + d->checkCount = 1; + continue; + } + + if (!d->ui->m_listWidget->items().isEmpty()) { + d->ui->m_keyLed->setState(static_cast(keysOk ? KLed::On : KLed::Off)); + } else { + d->ui->m_keyLed->setState(KLed::On); + } + break; + } + + --d->checkCount; + d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!enabled || (d->ui->m_keyLed->state() == KLed::On)); + } +} diff --git a/kmymoney/plugins/xml/kgpgkeyselectiondlg.ui b/kmymoney/plugins/xml/kgpgkeyselectiondlg.ui new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/xml/kgpgkeyselectiondlg.ui @@ -0,0 +1,139 @@ + + + KGpgKeySelectionDlg + + + + 0 + 0 + 575 + 480 + + + + Select additional keys + + + true + + + true + + + + + + You have configured KMyMoney to save your data secured with GPG. Please choose the key you want to use for encryption of your data. + + + true + + + + + + + + + + Add additional keys here + + + + + + + Enter the id of the key you want to use for data encryption. This can either be an e-mail address or the hexadecimal key id. In case of the key id, do not forget the leading 0x. + + + + + + + + + + + + Keys for all of the above user ids found + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + KEditListWidget + QWidget +
keditlistwidget.h
+
+ + KLed + QWidget +
kled.h
+
+
+ + buttonBox + + + + + buttonBox + accepted() + KGpgKeySelectionDlg + accept() + + + 244 + 415 + + + 157 + 274 + + + + + buttonBox + rejected() + KGpgKeySelectionDlg + reject() + + + 312 + 415 + + + 286 + 274 + + + + +
diff --git a/kmymoney/mymoney/storage/mymoneystorageanon.h b/kmymoney/plugins/xml/mymoneystorageanon.h rename from kmymoney/mymoney/storage/mymoneystorageanon.h rename to kmymoney/plugins/xml/mymoneystorageanon.h diff --git a/kmymoney/mymoney/storage/mymoneystorageanon.cpp b/kmymoney/plugins/xml/mymoneystorageanon.cpp rename from kmymoney/mymoney/storage/mymoneystorageanon.cpp rename to kmymoney/plugins/xml/mymoneystorageanon.cpp diff --git a/kmymoney/mymoney/storage/mymoneystoragebin.h b/kmymoney/plugins/xml/mymoneystoragebin.h rename from kmymoney/mymoney/storage/mymoneystoragebin.h rename to kmymoney/plugins/xml/mymoneystoragebin.h diff --git a/kmymoney/plugins/xml/mymoneystoragenames.h b/kmymoney/plugins/xml/mymoneystoragenames.h new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/xml/mymoneystoragenames.h @@ -0,0 +1,71 @@ +/* + * Copyright 2017-2018 Ł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 . + */ + +#ifndef MYMONEYSTORAGENAMES_H +#define MYMONEYSTORAGENAMES_H + +// ---------------------------------------------------------------------------- +// QT Includes + +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +namespace MyMoneyStorageTags { + +enum tagNameE { tnInstitutions, tnPayees, tnCostCenters, + tnTags, tnAccounts, tnTransactions, + tnSchedules, tnSecurities, tnCurrencies, + tnPrices, tnReports, tnBudgets, tnOnlineJobs, + tnKMMFile, tnFileInfo, tnUser + }; + +extern const QHash tagNames; +} + +namespace MyMoneyStorageNodes { + +enum ndNameE { nnInstitution, nnPayee, nnCostCenter, + nnTag, nnAccount, nnTransaction, + nnScheduleTX, nnSecurity, nnCurrency, + nnPrice, nnPricePair, nnReport, nnBudget, nnOnlineJob, + nnKeyValuePairs, nnEquity + }; + +extern const QHash nodeNames; +} + +namespace MyMoneyStorageAttributes { + +enum attrNameE { anID, anDate, anCount, anFrom, anTo, + anSource, anKey, anValue, anPrice, + anName, anEmail, anCountry, anCity, + anZipCode, anStreet, anTelephone + }; + +extern const QHash attrNames; +} + +namespace MyMoneyStandardAccounts { + +enum idNameE { stdAccLiability, stdAccAsset, stdAccExpense, stdAccIncome, stdAccEquity }; + +extern const QHash stdAccNames; +} + +#endif diff --git a/kmymoney/plugins/xml/mymoneystoragenames.cpp b/kmymoney/plugins/xml/mymoneystoragenames.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/xml/mymoneystoragenames.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2017-2018 Ł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 "mymoneystoragenames.h" + +namespace MyMoneyStorageTags { + +const QHash tagNames = { + {tnInstitutions, QStringLiteral("INSTITUTIONS")}, + {tnPayees, QStringLiteral("PAYEES")}, + {tnCostCenters, QStringLiteral("COSTCENTERS")}, + {tnTags, QStringLiteral("TAGS")}, + {tnAccounts, QStringLiteral("ACCOUNTS")}, + {tnTransactions, QStringLiteral("TRANSACTIONS")}, + {tnSchedules, QStringLiteral("SCHEDULES")}, + {tnSecurities, QStringLiteral("SECURITIES")}, + {tnCurrencies, QStringLiteral("CURRENCIES")}, + {tnPrices, QStringLiteral("PRICES")}, + {tnReports, QStringLiteral("REPORTS")}, + {tnBudgets, QStringLiteral("BUDGETS")}, + {tnOnlineJobs, QStringLiteral("ONLINEJOBS")}, + {tnKMMFile, QStringLiteral("KMYMONEY-FILE")}, + {tnFileInfo, QStringLiteral("FILEINFO")}, + {tnUser, QStringLiteral("USER")} +}; + +} + +namespace MyMoneyStorageNodes { + +const QHash nodeNames = { + {nnInstitution, QStringLiteral("INSTITUTION")}, + {nnPayee, QStringLiteral("PAYEE")}, + {nnCostCenter, QStringLiteral("COSTCENTER")}, + {nnTag, QStringLiteral("TAG")}, + {nnAccount, QStringLiteral("ACCOUNT")}, + {nnTransaction, QStringLiteral("TRANSACTION")}, + {nnScheduleTX, QStringLiteral("SCHEDULED_TX")}, + {nnSecurity, QStringLiteral("SECURITY")}, + {nnCurrency, QStringLiteral("CURRENCY")}, + {nnPrice, QStringLiteral("PRICE")}, + {nnPricePair, QStringLiteral("PRICEPAIR")}, + {nnReport, QStringLiteral("REPORT")}, + {nnBudget, QStringLiteral("BUDGET")}, + {nnOnlineJob, QStringLiteral("ONLINEJOB")}, + {nnKeyValuePairs, QStringLiteral("KEYVALUEPAIRS")}, + {nnEquity, QStringLiteral("EQUITY")}, +}; + +} + +namespace MyMoneyStorageAttributes { + +const QHash attrNames = { + {anID, QStringLiteral("id")}, + {anDate, QStringLiteral("date")}, + {anCount, QStringLiteral("count")}, + {anFrom, QStringLiteral("from")}, + {anTo, QStringLiteral("to")}, + {anSource, QStringLiteral("source")}, + {anKey, QStringLiteral("key")}, + {anValue, QStringLiteral("value")}, + {anPrice, QStringLiteral("price")}, + {anName, QStringLiteral("name")}, + {anEmail, QStringLiteral("email")}, + {anCountry, QStringLiteral("county")}, + {anCity, QStringLiteral("city")}, + {anZipCode, QStringLiteral("zipcode")}, + {anStreet, QStringLiteral("street")}, + {anTelephone, QStringLiteral("telephone")} +}; + +} + +namespace MyMoneyStandardAccounts { + + // definitions for the ID's of the standard accounts + const QHash stdAccNames { + {stdAccLiability, QStringLiteral("AStd::Liability")}, + {stdAccAsset, QStringLiteral("AStd::Asset")}, + {stdAccExpense, QStringLiteral("AStd::Expense")}, + {stdAccIncome, QStringLiteral("AStd::Income")}, + {stdAccEquity, QStringLiteral("AStd::Equity")}, + }; + +} diff --git a/kmymoney/mymoney/storage/mymoneystoragexml.h b/kmymoney/plugins/xml/mymoneystoragexml.h rename from kmymoney/mymoney/storage/mymoneystoragexml.h rename to kmymoney/plugins/xml/mymoneystoragexml.h --- a/kmymoney/mymoney/storage/mymoneystoragexml.h +++ b/kmymoney/plugins/xml/mymoneystoragexml.h @@ -22,8 +22,6 @@ #ifndef MYMONEYSTORAGEXML_H #define MYMONEYSTORAGEXML_H -#include "kmm_mymoney_export.h" - // ---------------------------------------------------------------------------- // QT Includes @@ -67,7 +65,7 @@ typedef QMap MyMoneyPriceEntries; typedef QMap MyMoneyPriceList; -class KMM_MYMONEY_EXPORT MyMoneyStorageXML : public IMyMoneyOperationsFormat +class MyMoneyStorageXML : public IMyMoneyOperationsFormat { friend class MyMoneyXmlContentHandler; public: diff --git a/kmymoney/mymoney/storage/mymoneystoragexml.cpp b/kmymoney/plugins/xml/mymoneystoragexml.cpp rename from kmymoney/mymoney/storage/mymoneystoragexml.cpp rename to kmymoney/plugins/xml/mymoneystoragexml.cpp diff --git a/kmymoney/plugins/xml/xmlstorage.h b/kmymoney/plugins/xml/xmlstorage.h new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/xml/xmlstorage.h @@ -0,0 +1,79 @@ +/* + * Copyright 2018 Ł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 . + */ + +#ifndef XMLSTORAGE_H +#define XMLSTORAGE_H + +// ---------------------------------------------------------------------------- +// KDE Includes + +// ---------------------------------------------------------------------------- +// QT Includes + +// Project Includes + +#include "kmymoneyplugin.h" + +class QIODevice; + +class MyMoneyStorageMgr; + +class XMLStorage : public KMyMoneyPlugin::Plugin, public KMyMoneyPlugin::StoragePlugin +{ + Q_OBJECT + Q_INTERFACES(KMyMoneyPlugin::StoragePlugin) + +public: + explicit XMLStorage(QObject *parent, const QVariantList &args); + ~XMLStorage() override; + + QAction *m_saveAsXMLaction; + + bool open(MyMoneyStorageMgr *storage, const QUrl &url) override; + bool save(const QUrl &url) override; + QString formatName() const override; + QString fileExtension() const override; + +private: + void createActions(); + void ungetString(QIODevice *qfile, char *buf, int len); + + /** + * This method is used by saveFile() to store the data + * either directly in the destination file if it is on + * the local file system or in a temporary file when + * the final destination is reached over a network + * protocol (e.g. FTP) + * + * @param localFile the name of the local file + * @param writer pointer to the formatter + * @param plaintext whether to override any compression & encryption settings + * @param keyList QString containing a comma separated list of keys to be used for encryption + * If @p keyList is empty, the file will be saved unencrypted + * + * @note This method will close the file when it is written. + */ + void saveToLocalFile(const QString& localFile, IMyMoneyOperationsFormat* pWriter, bool plaintext, const QString& keyList); + + QString m_encryptionKeys; +private Q_SLOTS: + void slotSaveAsXML(); + + +}; + +#endif diff --git a/kmymoney/plugins/xml/xmlstorage.cpp b/kmymoney/plugins/xml/xmlstorage.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/xml/xmlstorage.cpp @@ -0,0 +1,517 @@ +/* + * Copyright 2018 Ł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 "xmlstorage.h" + +#include +#include + +// ---------------------------------------------------------------------------- +// QT Includes + +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "appinterface.h" +#include "viewinterface.h" +#include "mymoneyfile.h" +#include "mymoneyexception.h" +#include "mymoneystoragebin.h" +#include "mymoneystoragexml.h" +#include "mymoneystorageanon.h" +#include "icons.h" +#include "kmymoneysettings.h" +#include "kmymoneyutils.h" +#include "kgpgfile.h" +#include "kgpgkeyselectiondlg.h" + +using namespace Icons; + +static constexpr KCompressionDevice::CompressionType const& COMPRESSION_TYPE = KCompressionDevice::GZip; +static constexpr char recoveryKeyId[] = "0xD2B08440"; +static constexpr char recoveryKeyId2[] = "59B0F826D2B08440"; + +XMLStorage::XMLStorage(QObject *parent, const QVariantList &args) : + KMyMoneyPlugin::Plugin(parent, "xmlstorage"/*must be the same as X-KDE-PluginInfo-Name*/) +{ + Q_UNUSED(args) + setComponentName("xmlstorage", i18n("XML storage")); + setXMLFile("xmlstorage.rc"); + createActions(); + // For information, announce that we have been loaded. + qDebug("Plugins: xmlstorage loaded"); +} + +XMLStorage::~XMLStorage() +{ + qDebug("Plugins: xmlstorage unloaded"); +} + +bool XMLStorage::open(MyMoneyStorageMgr *storage, const QUrl &url) +{ + if (!url.isValid()) + throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid URL %1").arg(qPrintable(url.url()))); + + QString fileName; + auto downloadedFile = false; + if (url.isLocalFile()) { + fileName = url.toLocalFile(); + } else { + fileName = KMyMoneyUtils::downloadFile(url); + downloadedFile = true; + } + + if (!KMyMoneyUtils::fileExists(QUrl::fromLocalFile(fileName))) + throw MYMONEYEXCEPTION(QString::fromLatin1("Error opening the file.\n" + "Requested file: '%1'.\n" + "Downloaded file: '%2'").arg(qPrintable(url.url()), fileName)); + + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) + throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot read the file: %1").arg(fileName)); + + QByteArray qbaFileHeader(2, '\0'); + const auto sFileToShort = QString::fromLatin1("File %1 is too short.").arg(fileName); + if (file.read(qbaFileHeader.data(), 2) != 2) + throw MYMONEYEXCEPTION(sFileToShort); + + file.close(); + + // There's a problem with the KFilterDev and KGPGFile classes: + // One supports the at(n) member but not ungetch() together with + // read() and the other does not provide an at(n) method but + // supports read() that considers the ungetch() buffer. QFile + // supports everything so this is not a problem. We solve the problem + // for now by keeping track of which method can be used. + auto haveAt = true; + auto isEncrypted = false; + + QIODevice* qfile = nullptr; + QString sFileHeader(qbaFileHeader); + if (sFileHeader == QString("\037\213")) { // gzipped? + qfile = new KCompressionDevice(fileName, COMPRESSION_TYPE); + } else if (sFileHeader == QString("--") || // PGP ASCII armored? + sFileHeader == QString("\205\001") || // PGP binary? + sFileHeader == QString("\205\002")) { // PGP binary? + if (KGPGFile::GPGAvailable()) { + qfile = new KGPGFile(fileName); + haveAt = false; + isEncrypted = true; + } else { + throw MYMONEYEXCEPTION(QString::fromLatin1("GPG is not available for decryption of file %1").arg(fileName)); + } + } else { + // we can't use file directly, as we delete qfile later on + qfile = new QFile(file.fileName()); + } + + if (!qfile->open(QIODevice::ReadOnly)) { + delete qfile; + throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot read the file: %1").arg(fileName)); + } + + qbaFileHeader.resize(8); + if (qfile->read(qbaFileHeader.data(), 8) != 8) + throw MYMONEYEXCEPTION(sFileToShort); + + if (haveAt) + qfile->seek(0); + else + ungetString(qfile, qbaFileHeader.data(), 8); + + // Ok, we got the first block of 8 bytes. Read in the two + // unsigned long int's by preserving endianess. This is + // achieved by reading them through a QDataStream object + qint32 magic0, magic1; + QDataStream s(&qbaFileHeader, QIODevice::ReadOnly); + s >> magic0; + s >> magic1; + + // If both magic numbers match (we actually read in the + // text 'KMyMoney' then we assume a binary file and + // construct a reader for it. Otherwise, we construct + // an XML reader object. + // + // The expression magic0 < 30 is only used to create + // a binary reader if we assume an old binary file. This + // should be removed at some point. An alternative is to + // check the beginning of the file against an pattern + // of the XML file (e.g. '?File %1 contains the old binary format used by KMyMoney. Please use an older version of KMyMoney (0.8.x) that still supports this format to convert it to the new XML based format.").arg(fileName)); + } + + // Scan the first 70 bytes to see if we find something + // we know. For now, we support our own XML format and + // GNUCash XML format. If the file is smaller, then it + // contains no valid data and we reject it anyway. + qbaFileHeader.resize(70); + if (qfile->read(qbaFileHeader.data(), 70) != 70) + throw MYMONEYEXCEPTION(sFileToShort); + + if (haveAt) + qfile->seek(0); + else + ungetString(qfile, qbaFileHeader.data(), 70); + + IMyMoneyOperationsFormat* pReader = nullptr; + QRegExp kmyexp(""); + QByteArray txt(qbaFileHeader, 70); + if (kmyexp.indexIn(txt) != -1) { + pReader = new MyMoneyStorageXML; + } else { + throw MYMONEYEXCEPTION(QString::fromLatin1("%1").arg(i18n("File %1 contains an unknown file format.", fileName))); + } + + // disconnect the current storga manager from the engine + MyMoneyFile::instance()->detachStorage(); + + // attach the storage before reading the file, since the online + // onlineJobAdministration object queries the engine during + // loading. + MyMoneyFile::instance()->attachStorage(storage); + + pReader->setProgressCallback(appInterface()->progressCallback()); + pReader->readFile(qfile, storage); + pReader->setProgressCallback(0); + delete pReader; + + qfile->close(); + delete qfile; + + // if a temporary file was downloaded, then it will be removed + // with the next call. Otherwise, it stays untouched on the local + // filesystem. + if (downloadedFile) + QFile::remove(fileName); + + // encapsulate transactions to the engine to be able to commit/rollback + MyMoneyFileTransaction ft; + // make sure we setup the encryption key correctly + if (isEncrypted && MyMoneyFile::instance()->value("kmm-encryption-key").isEmpty()) + MyMoneyFile::instance()->setValue("kmm-encryption-key", KMyMoneySettings::gpgRecipientList().join(",")); + ft.commit(); + return true; +} + +bool XMLStorage::save(const QUrl &url) +{ + QString filename = url.path(); + + if (!appInterface()->fileOpen()) { + KMessageBox::error(nullptr, i18n("Tried to access a file when it has not been opened")); + return false; + } + + std::unique_ptr storageWriter; + + // If this file ends in ".ANON.XML" then this should be written using the + // anonymous writer. + bool plaintext = filename.right(4).toLower() == ".xml"; + if (filename.right(9).toLower() == ".anon.xml") + storageWriter = std::make_unique(); + else + storageWriter = std::make_unique(); + + QString keyList; + if (!appInterface()->filenameURL().isEmpty()) + keyList = MyMoneyFile::instance()->value("kmm-encryption-key"); + else + keyList = m_encryptionKeys; + + // actually, url should be the parameter to this function + // but for now, this would involve too many changes + auto rc = true; + try { + if (! url.isValid()) { + throw MYMONEYEXCEPTION(QString::fromLatin1("Malformed URL '%1'").arg(url.url())); + } + + if (url.isLocalFile()) { + filename = url.toLocalFile(); + try { + const unsigned int nbak = KMyMoneySettings::autoBackupCopies(); + if (nbak) { + KBackup::numberedBackupFile(filename, QString(), QStringLiteral("~"), nbak); + } + saveToLocalFile(filename, storageWriter.get(), plaintext, keyList); + } catch (const MyMoneyException &) { + throw MYMONEYEXCEPTION(QString::fromLatin1("Unable to write changes to '%1'").arg(filename)); + } + } else { + + QTemporaryFile tmpfile; + tmpfile.open(); // to obtain the name + tmpfile.close(); + saveToLocalFile(tmpfile.fileName(), storageWriter.get(), plaintext, keyList); + + Q_CONSTEXPR int permission = -1; + QFile file(tmpfile.fileName()); + file.open(QIODevice::ReadOnly); + KIO::StoredTransferJob *putjob = KIO::storedPut(file.readAll(), url, permission, KIO::JobFlag::Overwrite); + if (!putjob->exec()) { + throw MYMONEYEXCEPTION(QString::fromLatin1("Unable to upload to '%1'.
%2").arg(url.toDisplayString(), putjob->errorString())); + } + file.close(); + } + } catch (const MyMoneyException &e) { + KMessageBox::error(nullptr, QString::fromLatin1(e.what())); + MyMoneyFile::instance()->setDirty(); + rc = false; + } + return rc; +} + +QString XMLStorage::formatName() const +{ + return QStringLiteral("XML"); +} + +QString XMLStorage::fileExtension() const +{ + return QString(); +} + +void XMLStorage::createActions() +{ + m_saveAsXMLaction = actionCollection()->addAction("saveas_xml"); + m_saveAsXMLaction->setText(i18n("Save as XML...")); + m_saveAsXMLaction->setIcon(Icons::get(Icon::FileArchiver)); + connect(m_saveAsXMLaction, &QAction::triggered, this, &XMLStorage::slotSaveAsXML); +} + +void XMLStorage::ungetString(QIODevice *qfile, char *buf, int len) +{ + buf = &buf[len-1]; + while (len--) { + qfile->ungetChar(*buf--); + } +} + +void XMLStorage::saveToLocalFile(const QString& localFile, IMyMoneyOperationsFormat* pWriter, bool plaintext, const QString& keyList) +{ + // Check GPG encryption + bool encryptFile = true; + bool encryptRecover = false; + if (!keyList.isEmpty()) { + if (!KGPGFile::GPGAvailable()) { + KMessageBox::sorry(nullptr, i18n("GPG does not seem to be installed on your system. Please make sure that GPG can be found using the standard search path. This time, encryption is disabled."), i18n("GPG not found")); + encryptFile = false; + } else { + if (KMyMoneySettings::encryptRecover()) { + encryptRecover = true; + if (!KGPGFile::keyAvailable(QString(recoveryKeyId))) { + KMessageBox::sorry(nullptr, i18n("

You have selected to encrypt your data also with the KMyMoney recover key, but the key with id

%1

has not been found in your keyring at this time. Please make sure to import this key into your keyring. You can find it on the KMyMoney web-site. This time your data will not be encrypted with the KMyMoney recover key.

", QString(recoveryKeyId)), i18n("GPG Key not found")); + encryptRecover = false; + } + } + + for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { + if (!KGPGFile::keyAvailable(key)) { + KMessageBox::sorry(nullptr, i18n("

You have specified to encrypt your data for the user-id

%1.

Unfortunately, a valid key for this user-id was not found in your keyring. Please make sure to import a valid key for this user-id. This time, encryption is disabled.

", key), i18n("GPG Key not found")); + encryptFile = false; + break; + } + } + + if (encryptFile == true) { + QString msg = i18n("

You have configured to save your data in encrypted form using GPG. Make sure you understand that you might lose all your data if you encrypt it, but cannot decrypt it later on. If unsure, answer No.

"); + if (KMessageBox::questionYesNo(nullptr, msg, i18n("Store GPG encrypted"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "StoreEncrypted") == KMessageBox::No) { + encryptFile = false; + } + } + } + } + + + // Create a temporary file if needed + QString writeFile = localFile; + QTemporaryFile tmpFile; + if (QFile::exists(localFile)) { + tmpFile.open(); + writeFile = tmpFile.fileName(); + tmpFile.close(); + } + + /** + * @brief Automatically restore settings when scope is left + */ + struct restorePreviousSettingsHelper { + restorePreviousSettingsHelper() + : m_signalsWereBlocked{MyMoneyFile::instance()->signalsBlocked()} + { + MyMoneyFile::instance()->blockSignals(true); + } + + ~restorePreviousSettingsHelper() + { + MyMoneyFile::instance()->blockSignals(m_signalsWereBlocked); + } + const bool m_signalsWereBlocked; + } restoreHelper; + + MyMoneyFileTransaction ft; + MyMoneyFile::instance()->deletePair("kmm-encryption-key"); + std::unique_ptr device; + + if (!keyList.isEmpty() && encryptFile && !plaintext) { + std::unique_ptr kgpg = std::unique_ptr(new KGPGFile{writeFile}); + if (kgpg) { + for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { + kgpg->addRecipient(key.toLatin1()); + } + + if (encryptRecover) { + kgpg->addRecipient(recoveryKeyId); + } + MyMoneyFile::instance()->setValue("kmm-encryption-key", keyList); + device = std::unique_ptr(kgpg.release()); + } + } else { + QFile *file = new QFile(writeFile); + // The second parameter of KCompressionDevice means that KCompressionDevice will delete the QFile object + device = std::unique_ptr(new KCompressionDevice{file, true, (plaintext) ? KCompressionDevice::None : COMPRESSION_TYPE}); + } + + ft.commit(); + + if (!device || !device->open(QIODevice::WriteOnly)) { + throw MYMONEYEXCEPTION(QString::fromLatin1("Unable to open file '%1' for writing.").arg(localFile)); + } + + pWriter->setProgressCallback(appInterface()->progressCallback()); + pWriter->writeFile(device.get(), MyMoneyFile::instance()->storage()); + device->close(); + + // Check for errors if possible, only possible for KGPGFile + QFileDevice *fileDevice = qobject_cast(device.get()); + if (fileDevice && fileDevice->error() != QFileDevice::NoError) { + throw MYMONEYEXCEPTION(QString::fromLatin1("Failure while writing to '%1'").arg(localFile)); + } + + if (writeFile != localFile) { + // This simple comparison is possible because the strings are equal if no temporary file was created. + // If a temporary file was created, it is made in a way that the name is definitely different. So no + // symlinks etc. have to be evaluated. + if (!QFile::remove(localFile) || !QFile::rename(writeFile, localFile)) + throw MYMONEYEXCEPTION(QString::fromLatin1("Failure while writing to '%1'").arg(localFile)); + } + QFile::setPermissions(localFile, QFileDevice::ReadUser | QFileDevice::WriteUser); + pWriter->setProgressCallback(0); +} + +void XMLStorage::slotSaveAsXML() +{ + bool rc = false; + QStringList m_additionalGpgKeys; + m_encryptionKeys.clear(); + + QString selectedKeyName; + if (KGPGFile::GPGAvailable() && KMyMoneySettings::writeDataEncrypted()) { + // fill the secret key list and combo box + QStringList keyList; + KGPGFile::secretKeyList(keyList); + + QPointer dlg = new KGpgKeySelectionDlg(nullptr); + dlg->setSecretKeys(keyList, KMyMoneySettings::gpgRecipient()); + dlg->setAdditionalKeys(KMyMoneySettings::gpgRecipientList()); + rc = dlg->exec(); + if ((rc == QDialog::Accepted) && (dlg != 0)) { + m_additionalGpgKeys = dlg->additionalKeys(); + selectedKeyName = dlg->secretKey(); + } + delete dlg; + if (rc != QDialog::Accepted) { + return; + } + } + + QString prevDir; // don't prompt file name if not a native file + if (appInterface()->isNativeFile()) + prevDir = appInterface()->readLastUsedDir(); + + QPointer dlg = + new QFileDialog(nullptr, i18n("Save As"), prevDir, + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.kmy")).arg(i18nc("KMyMoney (Filefilter)", "KMyMoney files")) + + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.xml")).arg(i18nc("XML (Filefilter)", "XML files")) + + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.anon.xml")).arg(i18nc("Anonymous (Filefilter)", "Anonymous files")) + + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*")).arg(i18nc("All files (Filefilter)", "All files"))); + dlg->setAcceptMode(QFileDialog::AcceptSave); + + if (dlg->exec() == QDialog::Accepted && dlg != 0) { + QUrl newURL = dlg->selectedUrls().first(); + if (!newURL.fileName().isEmpty()) { + appInterface()->consistencyCheck(false); + QString newName = newURL.toDisplayString(QUrl::PreferLocalFile); + + // append extension if not present + if (!newName.endsWith(QLatin1String(".kmy"), Qt::CaseInsensitive) && + !newName.endsWith(QLatin1String(".xml"), Qt::CaseInsensitive)) + newName.append(QLatin1String(".kmy")); + newURL = QUrl::fromUserInput(newName); + appInterface()->addToRecentFiles(newURL); + + // If this is the anonymous file export, just save it, don't actually take the + // name, or remember it! Don't even try to encrypt it + if (newName.endsWith(QLatin1String(".anon.xml"), Qt::CaseInsensitive)) + rc = save(newURL); + else { + appInterface()->writeFilenameURL(newURL); + QRegExp keyExp(".* \\((.*)\\)"); + if (keyExp.indexIn(selectedKeyName) != -1) { + m_encryptionKeys = keyExp.cap(1); + if (!m_additionalGpgKeys.isEmpty()) { + if (!m_encryptionKeys.isEmpty()) + m_encryptionKeys.append(QLatin1Char(',')); + m_encryptionKeys.append(m_additionalGpgKeys.join(QLatin1Char(','))); + } + } + rc = save(newURL); + //write the directory used for this file as the default one for next time. + appInterface()->writeLastUsedDir(newURL.toDisplayString(QUrl::RemoveFilename | QUrl::PreferLocalFile | QUrl::StripTrailingSlash)); + appInterface()->writeLastUsedFile(newName); + } + appInterface()->autosaveTimer()->stop(); + } + } + (*appInterface()->progressCallback())(0,0, i18nc("Application is ready to use", "Ready.")); + delete dlg; + appInterface()->updateCaption(); +} + +K_PLUGIN_FACTORY_WITH_JSON(XMLStorageFactory, "xmlstorage.json", registerPlugin();) + +#include "xmlstorage.moc" diff --git a/kmymoney/plugins/xml/xmlstorage.json.in b/kmymoney/plugins/xml/xmlstorage.json.in new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/xml/xmlstorage.json.in @@ -0,0 +1,21 @@ +{ + "KPlugin": { + "Authors": [ + { + "Email": "ktambascio@users.sourceforge.net,tbaumgart@kde.org,acejones@users.sourceforge.net,lukasz.wojnilowicz@gmail.com", + "Name": "Kevin Tambascio,Thomas Baumgart,Ace Jones,Łukasz Wojniłowicz" + } + ], + "Description": "Adds XML storage support to KMyMoney", + "EnabledByDefault": true, + "Icon": "server-database", + "Id": "xmlstorage", + "License": "GPL", + "Name": "XML Storage", + "ServiceTypes": [ + "KMyMoney/Plugin" + ], + "Version": "@PROJECT_VERSION@@PROJECT_VERSION_SUFFIX@", + "Website": "https://kmymoney.org/plugins.html" + } +} diff --git a/kmymoney/plugins/xml/xmlstorage.rc b/kmymoney/plugins/xml/xmlstorage.rc new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/xml/xmlstorage.rc @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/kmymoney/reports/tests/pivottable-test.cpp b/kmymoney/reports/tests/pivottable-test.cpp --- a/kmymoney/reports/tests/pivottable-test.cpp +++ b/kmymoney/reports/tests/pivottable-test.cpp @@ -33,7 +33,6 @@ #include "mymoneypayee.h" #include "mymoneyexception.h" #include "mymoneystoragedump.h" -#include "mymoneystoragexml.h" #include "mymoneyenums.h" #include "pivottable.h" diff --git a/kmymoney/reports/tests/querytable-test.cpp b/kmymoney/reports/tests/querytable-test.cpp --- a/kmymoney/reports/tests/querytable-test.cpp +++ b/kmymoney/reports/tests/querytable-test.cpp @@ -33,7 +33,6 @@ #include "mymoneysplit.h" #include "mymoneypayee.h" #include "mymoneystatement.h" -#include "mymoneystoragexml.h" #include "mymoneyexception.h" #include "kmymoneysettings.h" diff --git a/kmymoney/reports/tests/reportstestcommon.cpp b/kmymoney/reports/tests/reportstestcommon.cpp --- a/kmymoney/reports/tests/reportstestcommon.cpp +++ b/kmymoney/reports/tests/reportstestcommon.cpp @@ -39,7 +39,6 @@ #include "mymoneyreport.h" #include "mymoneypayee.h" #include "mymoneystatement.h" -#include "mymoneystoragexml.h" namespace test { diff --git a/kmymoney/views/kmymoneyview.cpp b/kmymoney/views/kmymoneyview.cpp --- a/kmymoney/views/kmymoneyview.cpp +++ b/kmymoney/views/kmymoneyview.cpp @@ -59,13 +59,8 @@ #include "kmymoneysettings.h" #include "kmymoneytitlelabel.h" -#include #include "kcurrencyeditdlg.h" -#include "mymoneystoragemgr.h" -#include "mymoneystoragebin.h" #include "mymoneyexception.h" -#include "mymoneystoragexml.h" -#include "mymoneystorageanon.h" #include "khomeview.h" #include "kaccountsview.h" #include "kcategoriesview.h"