Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add auto-save delay per database #9100

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/images/database_settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/topics/DatabaseOperations.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ image::database_settings.png[]
* *Max. history size:* When the history of an entry gets above this size, it is truncated. For example, this happens when entries have large attachments. Set this value small to prevent the database from getting too large (we recommend 6 MiB).
* *Use recycle bin:* Select this check-box if you want deleted entries to move to the recycle bin instead of being permanently removed. The recycle bin will be created if it does not already exist after your first deletion. To delete entries permanently, you must empty the recycle bin manually.
* *Enable compression:* KeePassXC databases can be compressed before being encrypted. Compression reduces the size of the database and does not have any appreciable affect on speed. It is recommended to always save databases with compression.
* *Autosave delay:* Customize the automatic database save operation by delaying it for a set time since the last change. By default, this option is disabled for fast saving, but can be useful for large databases to avoid delays after each change.

3. Click the Security button in the left-hand menu bar to change your database credentials and change encryption settings.
+
Expand Down
20 changes: 20 additions & 0 deletions share/translations/keepassxc_en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2041,6 +2041,26 @@ Entries deleted from the recycle bin are
removed from the database.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Autosave delay since last change</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Autosave delay</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Autosave delay since last change in minutes</source>
jNullj marked this conversation as resolved.
Show resolved Hide resolved
<translation type="unfinished"></translation>
</message>
<message>
<source> min</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Autosave delay since last change checkbox</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>DatabaseSettingsWidgetKeeShare</name>
Expand Down
32 changes: 29 additions & 3 deletions src/core/Metadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,17 @@

const int Metadata::DefaultHistoryMaxItems = 10;
const int Metadata::DefaultHistoryMaxSize = 6 * 1024 * 1024;
const int Metadata::DefaultAutosaveDelayMin = 0;

// Fallback icon for return by reference
static const Metadata::CustomIconData NULL_ICON{};

namespace customDataKeys
{
static const QString savedSearch = QStringLiteral("KPXC_SavedSearch");
static const QString autosaveDelay = QStringLiteral("KPXC_autosaveDelayMin");
}; // namespace customDataKeys

Metadata::Metadata(QObject* parent)
: ModifiableObject(parent)
, m_customData(new CustomData(this))
Expand Down Expand Up @@ -265,6 +272,19 @@ int Metadata::historyMaxSize() const
return m_data.historyMaxSize;
}

int Metadata::autosaveDelayMin() const
jNullj marked this conversation as resolved.
Show resolved Hide resolved
{
QString autosaveDelayMinStr = m_customData->value(customDataKeys::autosaveDelay);
if (autosaveDelayMinStr.isNull()) {
// data is not set yet, use default
return Metadata::DefaultAutosaveDelayMin;
}
bool ok; // check for QString to int op failuer
int autosaveDelayMin = autosaveDelayMinStr.toInt(&ok);
Q_ASSERT(ok);
return autosaveDelayMin;
}

CustomData* Metadata::customData()
{
return m_customData;
Expand Down Expand Up @@ -478,6 +498,12 @@ void Metadata::setHistoryMaxSize(int value)
set(m_data.historyMaxSize, value);
}

void Metadata::setAutosaveDelayMin(int value)
{
Q_ASSERT(value >= 0 && value <= 420000000);
m_customData->set(customDataKeys::autosaveDelay, QString::number(value));
}

QDateTime Metadata::settingsChanged() const
{
return m_settingsChanged;
Expand All @@ -494,20 +520,20 @@ void Metadata::addSavedSearch(const QString& name, const QString& searchtext)
auto searches = savedSearches();
searches.insert(name, searchtext);
auto json = QJsonDocument::fromVariant(searches);
m_customData->set("KPXC_SavedSearch", json.toJson());
m_customData->set(customDataKeys::savedSearch, json.toJson());
}

void Metadata::deleteSavedSearch(const QString& name)
{
auto searches = savedSearches();
searches.remove(name);
auto json = QJsonDocument::fromVariant(searches);
m_customData->set("KPXC_SavedSearch", json.toJson());
m_customData->set(customDataKeys::savedSearch, json.toJson());
}

QVariantMap Metadata::savedSearches()
{
auto searches = m_customData->value("KPXC_SavedSearch");
auto searches = m_customData->value(customDataKeys::savedSearch);
auto json = QJsonDocument::fromJson(searches.toUtf8());
return json.toVariant().toMap();
}
3 changes: 3 additions & 0 deletions src/core/Metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,13 @@ class Metadata : public ModifiableObject
int databaseKeyChangeForce() const;
int historyMaxItems() const;
int historyMaxSize() const;
int autosaveDelayMin() const;
CustomData* customData();
const CustomData* customData() const;

static const int DefaultHistoryMaxItems;
static const int DefaultHistoryMaxSize;
static const int DefaultAutosaveDelayMin;

void setGenerator(const QString& value);
void setName(const QString& value);
Expand Down Expand Up @@ -150,6 +152,7 @@ class Metadata : public ModifiableObject
void setMasterKeyChangeForce(int value);
void setHistoryMaxItems(int value);
void setHistoryMaxSize(int value);
void setAutosaveDelayMin(int value);
void setUpdateDatetime(bool value);
void addSavedSearch(const QString& name, const QString& searchtext);
void deleteSavedSearch(const QString& name);
Expand Down
38 changes: 36 additions & 2 deletions src/gui/DatabaseWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)

m_blockAutoSave = false;

m_autosaveTimer = new QTimer(this);
m_autosaveTimer->setSingleShot(true);
connect(m_autosaveTimer, SIGNAL(timeout()), this, SLOT(onAutosaveDelayTimeout()));

m_searchLimitGroup = config()->get(Config::SearchLimitGroup).toBool();

#ifdef WITH_XC_KEESHARE
Expand Down Expand Up @@ -1528,13 +1532,42 @@ void DatabaseWidget::onGroupChanged()

void DatabaseWidget::onDatabaseModified()
jNullj marked this conversation as resolved.
Show resolved Hide resolved
{
if (!m_blockAutoSave && config()->get(Config::AutoSaveAfterEveryChange).toBool()) {
refreshSearch();
int autosaveDelayMs = m_db->metadata()->autosaveDelayMin() * 60 * 1000; // min to msec for QTimer
bool autosaveAfterEveryChangeConfig = config()->get(Config::AutoSaveAfterEveryChange).toBool();
jNullj marked this conversation as resolved.
Show resolved Hide resolved
if (autosaveDelayMs > 0 && autosaveAfterEveryChangeConfig) {
// reset delay when modified
m_autosaveTimer->start(autosaveDelayMs);
jNullj marked this conversation as resolved.
Show resolved Hide resolved
return;
}
if (!m_blockAutoSave && autosaveAfterEveryChangeConfig) {
save();
} else {
// Only block once, then reset
m_blockAutoSave = false;
}
refreshSearch();
jNullj marked this conversation as resolved.
Show resolved Hide resolved
}

void DatabaseWidget::onAutosaveDelayTimeout()
{
const bool isAutosaveDelayEnabled = m_db->metadata()->autosaveDelayMin() > 0;
const bool autosaveAfterEveryChangeConfig = config()->get(Config::AutoSaveAfterEveryChange).toBool();
if (!(isAutosaveDelayEnabled && autosaveAfterEveryChangeConfig)) {
// User might disable the delay/autosave while the timer is running
return;
}
if (!m_blockAutoSave) {
save();
} else {
// Only block once, then reset
m_blockAutoSave = false;
}
}

void DatabaseWidget::triggerAutosaveTimer()
{
m_autosaveTimer->stop();
jNullj marked this conversation as resolved.
Show resolved Hide resolved
QMetaObject::invokeMethod(m_autosaveTimer, "timeout");
jNullj marked this conversation as resolved.
Show resolved Hide resolved
}

QString DatabaseWidget::getCurrentSearch()
Expand Down Expand Up @@ -1986,6 +2019,7 @@ bool DatabaseWidget::save()
if (performSave(errorMessage)) {
m_saveAttempts = 0;
m_blockAutoSave = false;
m_autosaveTimer->stop(); // stop autosave delay to avoid triggering another save
return true;
}

Expand Down
5 changes: 5 additions & 0 deletions src/gui/DatabaseWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ public slots:
int autoHideTimeout = MessageWidget::DefaultAutoHideTimeout);
void showErrorMessage(const QString& errorMessage);
void hideMessage();
void triggerAutosaveTimer();

protected:
void closeEvent(QCloseEvent* event) override;
Expand All @@ -251,6 +252,7 @@ private slots:
void onEntryChanged(Entry* entry);
void onGroupChanged();
void onDatabaseModified();
void onAutosaveDelayTimeout();
void connectDatabaseSignals();
void loadDatabase(bool accepted);
void unlockDatabase(bool accepted);
Expand Down Expand Up @@ -309,6 +311,9 @@ private slots:
// Autoreload
bool m_blockAutoSave;

// Autosave delay
QPointer<QTimer> m_autosaveTimer;

// Auto-Type related
QString m_searchStringForAutoType;
};
Expand Down
14 changes: 14 additions & 0 deletions src/gui/dbsettings/DatabaseSettingsWidgetGeneral.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ DatabaseSettingsWidgetGeneral::DatabaseSettingsWidgetGeneral(QWidget* parent)

connect(m_ui->historyMaxItemsCheckBox, SIGNAL(toggled(bool)), m_ui->historyMaxItemsSpinBox, SLOT(setEnabled(bool)));
connect(m_ui->historyMaxSizeCheckBox, SIGNAL(toggled(bool)), m_ui->historyMaxSizeSpinBox, SLOT(setEnabled(bool)));
connect(m_ui->autosaveDelayCheckBox, SIGNAL(toggled(bool)), m_ui->autosaveDelaySpinBox, SLOT(setEnabled(bool)));
}

DatabaseSettingsWidgetGeneral::~DatabaseSettingsWidgetGeneral() = default;
Expand Down Expand Up @@ -62,6 +63,13 @@ void DatabaseSettingsWidgetGeneral::initialize()
m_ui->historyMaxSizeSpinBox->setEnabled(false);
m_ui->historyMaxSizeCheckBox->setChecked(false);
}
if (meta->autosaveDelayMin() > 0) {
m_ui->autosaveDelaySpinBox->setValue(meta->autosaveDelayMin());
m_ui->autosaveDelayCheckBox->setChecked(true);
} else {
m_ui->autosaveDelayCheckBox->setChecked(false);
m_ui->autosaveDelaySpinBox->setEnabled(false);
}
}

void DatabaseSettingsWidgetGeneral::uninitialize()
Expand Down Expand Up @@ -132,6 +140,12 @@ bool DatabaseSettingsWidgetGeneral::save()
truncate = true;
}

int autosaveDelayMin = 0;
if (m_ui->autosaveDelayCheckBox->isChecked()) {
autosaveDelayMin = m_ui->autosaveDelaySpinBox->value();
}
meta->setAutosaveDelayMin(autosaveDelayMin);

if (truncate) {
const QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(false);
for (Entry* entry : allEntries) {
Expand Down
54 changes: 53 additions & 1 deletion src/gui/dbsettings/DatabaseSettingsWidgetGeneral.ui
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>453</width>
<height>374</height>
<height>394</height>
</rect>
</property>
<property name="sizePolicy">
Expand Down Expand Up @@ -202,6 +202,58 @@ removed from the database.</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="autosaveDelayCheckBox">
<property name="toolTip">
<string>Autosave delay since last change</string>
</property>
<property name="accessibleName">
<string>Autosave delay since last change checkbox</string>
</property>
<property name="text">
<string>Autosave delay</string>
jNullj marked this conversation as resolved.
Show resolved Hide resolved
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="autosaveDelaySpinBox">
<property name="toolTip">
<string>Autosave delay since last change in minutes</string>
</property>
<property name="accessibleName">
<string>Autosave delay since last change in minutes</string>
</property>
<property name="suffix">
<string> min</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>420000000</number>
jNullj marked this conversation as resolved.
Show resolved Hide resolved
</property>
<property name="value">
<number>5</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
Expand Down
Loading