You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
559 lines
20 KiB
559 lines
20 KiB
https://github.com/hluk/CopyQ/pull/2471
|
|
https://github.com/hluk/CopyQ/issues/2463
|
|
https://github.com/hluk/CopyQ/commit/a7a891e1f84c6c046a7bfc904c5fc6ebb98dec94
|
|
|
|
From a7a891e1f84c6c046a7bfc904c5fc6ebb98dec94 Mon Sep 17 00:00:00 2001
|
|
From: Lukas Holecek <hluk@email.cz>
|
|
Date: Wed, 20 Sep 2023 19:42:08 +0200
|
|
Subject: [PATCH] itemencrypted: Fix managing keys with gpg 2.1 and above
|
|
(#2471)
|
|
|
|
* itemencrypted: Fix managing keys with gpg 2.1 and above
|
|
|
|
Fixes #2463, #1208
|
|
|
|
* Tests: Avoid skipping itemencrypted tests if gpg is not found
|
|
|
|
* Windows: Fix running itemencrypted plugin tests
|
|
|
|
* itemencrypted: Fix error logging
|
|
|
|
* Ensure config directory exists
|
|
|
|
* itemencrypted: Fix handling native/non-native key paths
|
|
|
|
* Appveyor: Fix stuck job waiting on gpg-agent
|
|
--- a/plugins/itemencrypted/itemencrypted.cpp
|
|
+++ b/plugins/itemencrypted/itemencrypted.cpp
|
|
@@ -57,20 +57,23 @@ bool waitOrTerminate(QProcess *p, int timeoutMs)
|
|
bool verifyProcess(QProcess *p, int timeoutMs = 30000)
|
|
{
|
|
if ( !waitOrTerminate(p, timeoutMs) ) {
|
|
- log( "ItemEncrypt ERROR: Process timed out; stderr: " + p->readAllStandardError(), LogError );
|
|
+ log( QStringLiteral("ItemEncrypt: Process timed out; stderr: %1")
|
|
+ .arg(QString::fromUtf8(p->readAllStandardError())), LogError );
|
|
return false;
|
|
}
|
|
|
|
const int exitCode = p->exitCode();
|
|
if ( p->exitStatus() != QProcess::NormalExit ) {
|
|
- log( "ItemEncrypt ERROR: Failed to run GnuPG: " + p->errorString(), LogError );
|
|
+ log( QStringLiteral("ItemEncrypt: Failed to run GnuPG: %1")
|
|
+ .arg(p->errorString()), LogError );
|
|
return false;
|
|
}
|
|
|
|
if (exitCode != 0) {
|
|
const QString errors = p->readAllStandardError();
|
|
if ( !errors.isEmpty() )
|
|
- log( "ItemEncrypt ERROR: GnuPG stderr:\n" + errors, LogError );
|
|
+ log( QStringLiteral("ItemEncrypt: GnuPG stderr:\n%1")
|
|
+ .arg(errors), LogError );
|
|
return false;
|
|
}
|
|
|
|
@@ -88,55 +91,106 @@ QString getGpgVersionOutput(const QString &executable) {
|
|
return p.readAllStandardOutput();
|
|
}
|
|
|
|
-bool checkGpgExecutable(const QString &executable)
|
|
+struct GpgVersion {
|
|
+ int major;
|
|
+ int minor;
|
|
+};
|
|
+
|
|
+GpgVersion parseVersion(const QString &versionOutput)
|
|
{
|
|
- const auto versionOutput = getGpgVersionOutput(executable);
|
|
- return versionOutput.contains(" 2.");
|
|
+ const int lineEndIndex = versionOutput.indexOf('\n');
|
|
+#if QT_VERSION < QT_VERSION_CHECK(5,15,2)
|
|
+ const QStringRef firstLine = versionOutput.midRef(0, lineEndIndex);
|
|
+#else
|
|
+ const auto firstLine = QStringView{versionOutput}.mid(0, lineEndIndex);
|
|
+#endif
|
|
+ const QRegularExpression versionRegex(QStringLiteral(R"( (\d+)\.(\d+))"));
|
|
+ const QRegularExpressionMatch match = versionRegex.match(firstLine);
|
|
+#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
|
|
+ const int major = match.hasMatch() ? match.capturedView(1).toInt() : 0;
|
|
+ const int minor = match.hasMatch() ? match.capturedView(2).toInt() : 0;
|
|
+#else
|
|
+ const int major = match.hasMatch() ? match.capturedRef(1).toInt() : 0;
|
|
+ const int minor = match.hasMatch() ? match.capturedRef(2).toInt() : 0;
|
|
+#endif
|
|
+ return GpgVersion{major, minor};
|
|
}
|
|
|
|
+class GpgExecutable {
|
|
+public:
|
|
+ GpgExecutable() = default;
|
|
+
|
|
+ explicit GpgExecutable(const QString &executable)
|
|
+ : m_executable(executable)
|
|
+ {
|
|
+ const auto versionOutput = getGpgVersionOutput(executable);
|
|
+ if ( !versionOutput.isEmpty() ) {
|
|
+ COPYQ_LOG_VERBOSE(
|
|
+ QStringLiteral("ItemEncrypt INFO: '%1 --version' output: %2")
|
|
+ .arg(executable, versionOutput) );
|
|
+
|
|
+ const GpgVersion version = parseVersion(versionOutput);
|
|
+ m_isSupported = version.major >= 2;
|
|
+ COPYQ_LOG( QStringLiteral("ItemEncrypt INFO: %1 gpg version: %2.%3")
|
|
+ .arg(m_isSupported ? "Supported" : "Unsupported")
|
|
+ .arg(version.major)
|
|
+ .arg(version.minor) );
|
|
+
|
|
+ const bool needsSecring = version.major == 2 && version.minor == 0;
|
|
+
|
|
+ const QString path = getConfigurationFilePath("");
|
|
+ m_pubring = path + ".pub";
|
|
+ m_pubringNative = QDir::toNativeSeparators(m_pubring);
|
|
+ if (needsSecring) {
|
|
+ m_secring = path + ".sec";
|
|
+ m_secringNative = QDir::toNativeSeparators(m_secring);
|
|
+ }
|
|
+
|
|
#ifdef Q_OS_WIN
|
|
-bool checkUnixGpg(const QString &executable)
|
|
-{
|
|
- static const auto unixGpg = getGpgVersionOutput(executable).contains("Home: /c/");
|
|
- return unixGpg;
|
|
-}
|
|
+ const bool isUnixGpg = versionOutput.contains("Home: /c/");
|
|
+ if (isUnixGpg) {
|
|
+ m_pubringNative = QString(m_pubring).replace(":", "").insert(0, '/');
|
|
+ if (needsSecring)
|
|
+ m_secringNative = QString(m_secring).replace(":", "").insert(0, '/');
|
|
+ }
|
|
#endif
|
|
+ }
|
|
+ }
|
|
+
|
|
+ const QString &executable() const { return m_executable; }
|
|
+ bool isSupported() const { return m_isSupported; }
|
|
+ bool needsSecring() const { return !m_secring.isEmpty(); }
|
|
+ const QString &pubring() const { return m_pubring; }
|
|
+ const QString &secring() const { return m_secring; }
|
|
+ const QString &pubringNative() const { return m_pubringNative; }
|
|
+ const QString &secringNative() const { return m_secringNative; }
|
|
+
|
|
+private:
|
|
+ QString m_executable;
|
|
+ QString m_pubring;
|
|
+ QString m_secring;
|
|
+ QString m_pubringNative;
|
|
+ QString m_secringNative;
|
|
+ bool m_isSupported = false;
|
|
+};
|
|
|
|
-QString findGpgExecutable()
|
|
+GpgExecutable findGpgExecutable()
|
|
{
|
|
for (const auto &executable : {"gpg2", "gpg"}) {
|
|
- if ( checkGpgExecutable(executable) )
|
|
- return executable;
|
|
+ GpgExecutable gpg(executable);
|
|
+ if ( gpg.isSupported() )
|
|
+ return gpg;
|
|
}
|
|
|
|
- return QString();
|
|
+ return GpgExecutable();
|
|
}
|
|
|
|
-const QString &gpgExecutable()
|
|
+const GpgExecutable &gpgExecutable()
|
|
{
|
|
static const auto gpg = findGpgExecutable();
|
|
return gpg;
|
|
}
|
|
|
|
-struct KeyPairPaths {
|
|
- KeyPairPaths()
|
|
- {
|
|
- const QString path = getConfigurationFilePath("");
|
|
- sec = QDir::toNativeSeparators(path + ".sec");
|
|
- pub = QDir::toNativeSeparators(path + ".pub");
|
|
-
|
|
-#ifdef Q_OS_WIN
|
|
- if (checkUnixGpg(gpgExecutable())) {
|
|
- pub = QDir::fromNativeSeparators(pub).replace(":", "").insert(0, '/');
|
|
- sec = QDir::fromNativeSeparators(sec).replace(":", "").insert(0, '/');
|
|
- }
|
|
-#endif
|
|
- }
|
|
-
|
|
- QString sec;
|
|
- QString pub;
|
|
-};
|
|
-
|
|
QStringList getDefaultEncryptCommandArguments(const QString &publicKeyPath)
|
|
{
|
|
return QStringList() << "--trust-model" << "always" << "--recipient" << "copyq"
|
|
@@ -146,16 +200,18 @@ QStringList getDefaultEncryptCommandArguments(const QString &publicKeyPath)
|
|
|
|
void startGpgProcess(QProcess *p, const QStringList &args, QIODevice::OpenModeFlag mode)
|
|
{
|
|
- KeyPairPaths keys;
|
|
- p->start(gpgExecutable(), getDefaultEncryptCommandArguments(keys.pub) + args, mode);
|
|
+ const auto &gpg = gpgExecutable();
|
|
+ p->start(gpg.executable(), getDefaultEncryptCommandArguments(gpg.pubringNative()) + args, mode);
|
|
}
|
|
|
|
QString importGpgKey()
|
|
{
|
|
- KeyPairPaths keys;
|
|
+ const auto &gpg = gpgExecutable();
|
|
+ if ( !gpg.needsSecring() )
|
|
+ return QString();
|
|
|
|
QProcess p;
|
|
- p.start(gpgExecutable(), getDefaultEncryptCommandArguments(keys.pub) << "--import" << keys.sec);
|
|
+ p.start(gpg.executable(), getDefaultEncryptCommandArguments(gpg.pubringNative()) << "--import" << gpg.secringNative());
|
|
if ( !verifyProcess(&p) )
|
|
return "Failed to import private key (see log).";
|
|
|
|
@@ -164,18 +220,20 @@ QString importGpgKey()
|
|
|
|
QString exportGpgKey()
|
|
{
|
|
- KeyPairPaths keys;
|
|
+ const auto &gpg = gpgExecutable();
|
|
+ if ( !gpg.needsSecring() )
|
|
+ return QString();
|
|
|
|
// Private key already created or exported.
|
|
- if ( QFile::exists(keys.sec) )
|
|
+ if ( QFile::exists(gpg.secring()) )
|
|
return QString();
|
|
|
|
QProcess p;
|
|
- p.start(gpgExecutable(), getDefaultEncryptCommandArguments(keys.pub) << "--export-secret-key" << "copyq");
|
|
+ p.start(gpg.executable(), getDefaultEncryptCommandArguments(gpg.pubringNative()) << "--export-secret-key" << gpg.secringNative());
|
|
if ( !verifyProcess(&p) )
|
|
return "Failed to export private key (see log).";
|
|
|
|
- QFile secKey(keys.sec);
|
|
+ QFile secKey(gpg.secring());
|
|
if ( !secKey.open(QIODevice::WriteOnly) )
|
|
return "Failed to create private key.";
|
|
|
|
@@ -240,7 +298,7 @@ bool encryptMimeData(const QVariantMap &data, const QModelIndex &index, QAbstrac
|
|
|
|
void startGenerateKeysProcess(QProcess *process, bool useTransientPasswordlessKey = false)
|
|
{
|
|
- const KeyPairPaths keys;
|
|
+ const auto &gpg = gpgExecutable();
|
|
|
|
auto args = QStringList() << "--batch" << "--gen-key";
|
|
|
|
@@ -253,15 +311,19 @@ void startGenerateKeysProcess(QProcess *process, bool useTransientPasswordlessKe
|
|
}
|
|
|
|
startGpgProcess(process, args, QIODevice::ReadWrite);
|
|
- process->write( "\nKey-Type: RSA"
|
|
- "\nKey-Usage: encrypt"
|
|
- "\nKey-Length: 4096"
|
|
- "\nName-Real: copyq"
|
|
- + transientOptions +
|
|
- "\n%secring " + keys.sec.toUtf8() +
|
|
- "\n%pubring " + keys.pub.toUtf8() +
|
|
- "\n%commit"
|
|
- "\n" );
|
|
+ process->write(
|
|
+ "\nKey-Type: RSA"
|
|
+ "\nKey-Usage: encrypt"
|
|
+ "\nKey-Length: 4096"
|
|
+ "\nName-Real: copyq"
|
|
+ + transientOptions +
|
|
+ "\n%pubring " + gpg.pubringNative().toUtf8()
|
|
+ );
|
|
+
|
|
+ if ( gpg.needsSecring() )
|
|
+ process->write("\n%secring " + gpg.secringNative().toUtf8());
|
|
+
|
|
+ process->write("\n%commit\n");
|
|
process->closeWriteChannel();
|
|
}
|
|
|
|
@@ -276,7 +338,7 @@ QString exportImportGpgKeys()
|
|
|
|
bool isGpgInstalled()
|
|
{
|
|
- return !gpgExecutable().isEmpty();
|
|
+ return gpgExecutable().isSupported();
|
|
}
|
|
|
|
} // namespace
|
|
@@ -314,7 +376,7 @@ bool ItemEncryptedSaver::saveItems(const QString &, const QAbstractItemModel &mo
|
|
bytes = readGpgOutput(QStringList("--encrypt"), bytes);
|
|
if ( bytes.isEmpty() ) {
|
|
emitEncryptFailed();
|
|
- COPYQ_LOG("ItemEncrypt ERROR: Failed to read encrypted data");
|
|
+ log("ItemEncrypt: Failed to read encrypted data", LogError);
|
|
return false;
|
|
}
|
|
|
|
@@ -325,7 +387,7 @@ bool ItemEncryptedSaver::saveItems(const QString &, const QAbstractItemModel &mo
|
|
|
|
if ( stream.status() != QDataStream::Ok ) {
|
|
emitEncryptFailed();
|
|
- COPYQ_LOG("ItemEncrypt ERROR: Failed to write encrypted data");
|
|
+ log("ItemEncrypt: Failed to write encrypted data", LogError);
|
|
return false;
|
|
}
|
|
|
|
@@ -510,17 +572,22 @@ void ItemEncryptedScriptable::pasteEncryptedItems()
|
|
|
|
QString ItemEncryptedScriptable::generateTestKeys()
|
|
{
|
|
- const KeyPairPaths keys;
|
|
- for ( const auto &keyFileName : {keys.sec, keys.pub} ) {
|
|
+ const auto &gpg = gpgExecutable();
|
|
+
|
|
+ const QStringList keys = gpg.needsSecring()
|
|
+ ? QStringList{gpg.pubring(), gpg.secring()}
|
|
+ : QStringList{gpg.pubring()};
|
|
+
|
|
+ for (const auto &keyFileName : keys) {
|
|
if ( QFile::exists(keyFileName) && !QFile::remove(keyFileName) )
|
|
- return QString("Failed to remove \"%1\"").arg(keys.sec);
|
|
+ return QString("Failed to remove \"%1\"").arg(keyFileName);
|
|
}
|
|
|
|
QProcess process;
|
|
startGenerateKeysProcess(&process, true);
|
|
|
|
if ( !verifyProcess(&process) ) {
|
|
- return QString("ItemEncrypt ERROR: %1; stderr: %2")
|
|
+ return QString("ItemEncrypt: %1; stderr: %2")
|
|
.arg( process.errorString(),
|
|
QString::fromUtf8(process.readAllStandardError()) );
|
|
}
|
|
@@ -529,9 +596,9 @@ QString ItemEncryptedScriptable::generateTestKeys()
|
|
if ( !error.isEmpty() )
|
|
return error;
|
|
|
|
- for ( const auto &keyFileName : {keys.sec, keys.pub} ) {
|
|
+ for (const auto &keyFileName : keys) {
|
|
if ( !QFile::exists(keyFileName) )
|
|
- return QString("Failed to create \"%1\"").arg(keys.sec);
|
|
+ return QString("Failed to create \"%1\"").arg(keyFileName);
|
|
}
|
|
|
|
return QString();
|
|
@@ -606,19 +673,29 @@ QWidget *ItemEncryptedLoader::createSettingsWidget(QWidget *parent)
|
|
m_encryptTabs.join('\n') );
|
|
|
|
if (status() != GpgNotInstalled) {
|
|
- KeyPairPaths keys;
|
|
+ const auto &gpg = gpgExecutable();
|
|
ui->labelShareInfo->setTextFormat(Qt::RichText);
|
|
- ui->labelShareInfo->setText( ItemEncryptedLoader::tr(
|
|
- "To share encrypted items on other computer or"
|
|
- " session, you'll need public and secret key files:"
|
|
- "<ul>"
|
|
- "<li>%1</li>"
|
|
- "<li>%2<br />(Keep this secret key in a safe place.)</li>"
|
|
- "</ul>"
|
|
- )
|
|
- .arg( quoteString(keys.pub),
|
|
- quoteString(keys.sec) )
|
|
- );
|
|
+ QString text = ItemEncryptedLoader::tr(
|
|
+ "To share encrypted items on other computer or"
|
|
+ " session, you'll need these secret key files (keep them in a safe place):"
|
|
+ );
|
|
+ if (gpg.needsSecring()) {
|
|
+ text.append( QStringLiteral(
|
|
+ "<ul>"
|
|
+ "<li>%1</li>"
|
|
+ "<li>%2</li>"
|
|
+ "</ul>"
|
|
+ ).arg(quoteString(gpg.pubringNative()), quoteString(gpg.secringNative()))
|
|
+ );
|
|
+ } else {
|
|
+ text.append( QStringLiteral(
|
|
+ "<ul>"
|
|
+ "<li>%1</li>"
|
|
+ "</ul>"
|
|
+ ).arg(quoteString(gpg.pubringNative()))
|
|
+ );
|
|
+ }
|
|
+ ui->labelShareInfo->setText(text);
|
|
}
|
|
|
|
updateUi();
|
|
@@ -689,7 +766,7 @@ ItemSaverPtr ItemEncryptedLoader::loadItems(const QString &, QAbstractItemModel
|
|
const int bytesRead = stream.readRawData(encryptedBytes, 4096);
|
|
if (bytesRead == -1) {
|
|
emitDecryptFailed();
|
|
- COPYQ_LOG("ItemEncrypted ERROR: Failed to read encrypted data");
|
|
+ log("ItemEncrypted: Failed to read encrypted data", LogError);
|
|
return nullptr;
|
|
}
|
|
p.write(encryptedBytes, bytesRead);
|
|
@@ -708,7 +785,7 @@ ItemSaverPtr ItemEncryptedLoader::loadItems(const QString &, QAbstractItemModel
|
|
const QByteArray bytes = p.readAllStandardOutput();
|
|
if ( bytes.isEmpty() ) {
|
|
emitDecryptFailed();
|
|
- COPYQ_LOG("ItemEncrypt ERROR: Failed to read encrypted data.");
|
|
+ log("ItemEncrypt: Failed to read encrypted data", LogError);
|
|
verifyProcess(&p);
|
|
return nullptr;
|
|
}
|
|
@@ -719,7 +796,7 @@ ItemSaverPtr ItemEncryptedLoader::loadItems(const QString &, QAbstractItemModel
|
|
stream2 >> length;
|
|
if ( stream2.status() != QDataStream::Ok ) {
|
|
emitDecryptFailed();
|
|
- COPYQ_LOG("ItemEncrypt ERROR: Failed to parse item count!");
|
|
+ log("ItemEncrypt: Failed to parse item count", LogError);
|
|
return nullptr;
|
|
}
|
|
length = qMin(length, static_cast<quint64>(maxItems)) - static_cast<quint64>(model->rowCount());
|
|
@@ -728,7 +805,7 @@ ItemSaverPtr ItemEncryptedLoader::loadItems(const QString &, QAbstractItemModel
|
|
for ( int i = 0; i < count && stream2.status() == QDataStream::Ok; ++i ) {
|
|
if ( !model->insertRow(i) ) {
|
|
emitDecryptFailed();
|
|
- COPYQ_LOG("ItemEncrypt ERROR: Failed to insert item!");
|
|
+ log("ItemEncrypt: Failed to insert item", LogError);
|
|
return nullptr;
|
|
}
|
|
QVariantMap dataMap;
|
|
@@ -738,7 +815,7 @@ ItemSaverPtr ItemEncryptedLoader::loadItems(const QString &, QAbstractItemModel
|
|
|
|
if ( stream2.status() != QDataStream::Ok ) {
|
|
emitDecryptFailed();
|
|
- COPYQ_LOG("ItemEncrypt ERROR: Failed to decrypt item!");
|
|
+ log("ItemEncrypt: Failed to decrypt item", LogError);
|
|
return nullptr;
|
|
}
|
|
|
|
--- a/plugins/itemencrypted/tests/itemencryptedtests.cpp
|
|
+++ b/plugins/itemencrypted/tests/itemencryptedtests.cpp
|
|
@@ -25,6 +25,8 @@ void ItemEncryptedTests::cleanupTestCase()
|
|
void ItemEncryptedTests::init()
|
|
{
|
|
TEST(m_test->init());
|
|
+
|
|
+ QVERIFY(isGpgInstalled());
|
|
}
|
|
|
|
void ItemEncryptedTests::cleanup()
|
|
@@ -34,13 +36,10 @@ void ItemEncryptedTests::cleanup()
|
|
|
|
void ItemEncryptedTests::encryptDecryptData()
|
|
{
|
|
- if ( !isGpgInstalled() )
|
|
- SKIP("gpg2 is required to run the test");
|
|
-
|
|
- RUN("-e" << "plugins.itemencrypted.generateTestKeys()", "\n");
|
|
+ RUN("plugins.itemencrypted.generateTestKeys()", "\n");
|
|
|
|
// Test gpg errors first.
|
|
- RUN("-e" << "plugins.itemencrypted.encrypt(input());print('')", "");
|
|
+ RUN("plugins.itemencrypted.encrypt(input());print('')", "");
|
|
|
|
const QByteArray input("\x00\x01\x02\x03\x04", 5);
|
|
QByteArray stdoutActual;
|
|
@@ -60,10 +59,7 @@ void ItemEncryptedTests::encryptDecryptItems()
|
|
SKIP("Ctrl+L shortcut doesn't seem work on OS X");
|
|
#endif
|
|
|
|
- if ( !isGpgInstalled() )
|
|
- SKIP("gpg2 is required to run the test");
|
|
-
|
|
- RUN("-e" << "plugins.itemencrypted.generateTestKeys()", "\n");
|
|
+ RUN("plugins.itemencrypted.generateTestKeys()", "\n");
|
|
|
|
// Load commands from the plugin generating keys.
|
|
RUN("keys" << "Ctrl+P" << "ENTER", "");
|
|
--- a/src/app/clipboardserver.cpp
|
|
+++ b/src/app/clipboardserver.cpp
|
|
@@ -124,6 +124,8 @@ ClipboardServer::ClipboardServer(QApplication *app, const QString &sessionName)
|
|
|
|
QApplication::setQuitOnLastWindowClosed(false);
|
|
|
|
+ ensureSettingsDirectoryExists();
|
|
+
|
|
m_sharedData = std::make_shared<ClipboardBrowserShared>();
|
|
m_sharedData->itemFactory = new ItemFactory(this);
|
|
m_sharedData->notifications = new NotificationDaemon(this);
|
|
--- a/src/common/config.cpp
|
|
+++ b/src/common/config.cpp
|
|
@@ -157,6 +157,20 @@ QString getConfigurationFilePathHelper()
|
|
|
|
} // namespace
|
|
|
|
+bool ensureSettingsDirectoryExists()
|
|
+{
|
|
+ QDir settingsDir( settingsDirectoryPath() );
|
|
+ if ( !settingsDir.mkpath(".") ) {
|
|
+ log( QStringLiteral("Failed to create the directory for settings: %1")
|
|
+ .arg(settingsDir.path()),
|
|
+ LogError );
|
|
+
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
const QString &getConfigurationFilePath()
|
|
{
|
|
static const QString path = getConfigurationFilePathHelper();
|
|
--- a/src/common/config.h
|
|
+++ b/src/common/config.h
|
|
@@ -9,6 +9,8 @@ class QString;
|
|
class QVariant;
|
|
class QWidget;
|
|
|
|
+bool ensureSettingsDirectoryExists();
|
|
+
|
|
const QString &getConfigurationFilePath();
|
|
|
|
QString getConfigurationFilePath(const char *suffix);
|
|
--- a/src/item/itemstore.cpp
|
|
+++ b/src/item/itemstore.cpp
|
|
@@ -22,20 +22,6 @@ QString itemFileName(const QString &id)
|
|
return getConfigurationFilePath("_tab_") + part + QLatin1String(".dat");
|
|
}
|
|
|
|
-bool createItemDirectory()
|
|
-{
|
|
- QDir settingsDir( settingsDirectoryPath() );
|
|
- if ( !settingsDir.mkpath(".") ) {
|
|
- log( QString("Cannot create directory for settings %1!")
|
|
- .arg(quoteString(settingsDir.path()) ),
|
|
- LogError );
|
|
-
|
|
- return false;
|
|
- }
|
|
-
|
|
- return true;
|
|
-}
|
|
-
|
|
void printItemFileError(
|
|
const QString &action, const QString &id, const QFileDevice &file)
|
|
{
|
|
@@ -83,9 +69,6 @@ ItemSaverPtr createTab(
|
|
|
|
ItemSaverPtr loadItems(const QString &tabName, QAbstractItemModel &model, ItemFactory *itemFactory, int maxItems)
|
|
{
|
|
- if ( !createItemDirectory() )
|
|
- return nullptr;
|
|
-
|
|
const QString tabFileName = itemFileName(tabName);
|
|
if ( !QFile::exists(tabFileName) )
|
|
return createTab(tabName, model, itemFactory, maxItems);
|
|
@@ -107,7 +90,7 @@ bool saveItems(const QString &tabName, const QAbstractItemModel &model, const It
|
|
{
|
|
const QString tabFileName = itemFileName(tabName);
|
|
|
|
- if ( !createItemDirectory() )
|
|
+ if ( !ensureSettingsDirectoryExists() )
|
|
return false;
|
|
|
|
// Save tab data to a new temporary file.
|