Skip to content

Commit

Permalink
Implement 'Save Database Backup' option
Browse files Browse the repository at this point in the history
Add an option in the 'Database' menu to save a backup of the current database.

Add unit test for saving database copy

* Open a test database, mark it as modified, and save a copy
* Fail if the copy is not a valid database
* Fail if the original database is saved
* Fail if the original database is no longer marked as modified
  • Loading branch information
ameyer0 authored and droidmonkey committed May 16, 2020
1 parent 2237cf0 commit 45848c3
Show file tree
Hide file tree
Showing 12 changed files with 157 additions and 49 deletions.
93 changes: 47 additions & 46 deletions COPYING
Original file line number Diff line number Diff line change
Expand Up @@ -124,66 +124,67 @@ Copyright: 2003-2004, David Vignoni <david@icon-king.com>
License: LGPL-2.1
Comment: from Nuvola icon theme

Files: share/icons/application/scalable/categories/preferences-other.svg
share/icons/application/scalable/apps/keepassxc-dark.svg
share/icons/application/scalable/apps/preferences-system-network-sharing.svg
share/icons/application/scalable/apps/utilities-terminal.svg
share/icons/application/scalable/apps/keepassxc-locked.svg
share/icons/application/scalable/apps/keepassxc-unlocked.svg
share/icons/application/scalable/apps/keepassxc.svg
share/icons/application/scalable/apps/freedesktop.svg
share/icons/application/scalable/apps/internet-web-browser.svg
share/icons/application/scalable/apps/preferences-desktop-icons.svg
share/icons/application/scalable/status/dialog-information.svg
share/icons/application/scalable/status/dialog-warning.svg
share/icons/application/scalable/status/dialog-error.svg
share/icons/application/scalable/status/security-high.svg
share/icons/application/scalable/mimetypes/application-x-keepassxc.svg
share/icons/application/scalable/actions/document-close.svg
share/icons/application/scalable/actions/application-exit.svg
Files: share/icons/application/scalable/actions/application-exit.svg
share/icons/application/scalable/actions/auto-type.svg
share/icons/application/scalable/actions/chronometer.svg
share/icons/application/scalable/actions/clipboard-text.svg
share/icons/application/scalable/actions/configure.svg
share/icons/application/scalable/actions/database-change-key.svg
share/icons/application/scalable/actions/group-new.svg
share/icons/application/scalable/actions/database-lock.svg
share/icons/application/scalable/actions/dialog-close.svg
share/icons/application/scalable/actions/dialog-ok.svg
share/icons/application/scalable/actions/document-close.svg
share/icons/application/scalable/actions/document-edit.svg
share/icons/application/scalable/actions/document-new.svg
share/icons/application/scalable/actions/document-open.svg
share/icons/application/scalable/actions/document-properties.svg
share/icons/application/scalable/actions/group-empty-trash.svg
share/icons/application/scalable/actions/statistics.svg
share/icons/application/scalable/actions/document-save.svg
share/icons/application/scalable/actions/document-save-as.svg
share/icons/application/scalable/actions/document-save-copy.svg
share/icons/application/scalable/actions/edit-clear-locationbar-ltr.svg
share/icons/application/scalable/actions/entry-delete.svg
share/icons/application/scalable/actions/edit-clear-locationbar-rtl.svg
share/icons/application/scalable/actions/entry-clone.svg
share/icons/application/scalable/actions/entry-delete.svg
share/icons/application/scalable/actions/entry-edit.svg
share/icons/application/scalable/actions/password-generator.svg
share/icons/application/scalable/actions/dialog-ok.svg
share/icons/application/scalable/actions/chronometer.svg
share/icons/application/scalable/actions/document-new.svg
share/icons/application/scalable/actions/view-history.svg
share/icons/application/scalable/actions/entry-new.svg
share/icons/application/scalable/actions/favicon-download.svg
share/icons/application/scalable/actions/group-delete.svg
share/icons/application/scalable/actions/dialog-close.svg
share/icons/application/scalable/actions/group-edit.svg
share/icons/application/scalable/actions/document-save.svg
share/icons/application/scalable/actions/password-show-on.svg
share/icons/application/scalable/actions/group-empty-trash.svg
share/icons/application/scalable/actions/group-new.svg
share/icons/application/scalable/actions/help-about.svg
share/icons/application/scalable/actions/key-enter.svg
share/icons/application/scalable/actions/message-close.svg
share/icons/application/scalable/actions/entry-new.svg
share/icons/application/scalable/actions/url-copy.svg
share/icons/application/scalable/actions/username-copy.svg
share/icons/application/scalable/actions/auto-type.svg
share/icons/application/scalable/actions/password-show-off.svg
share/icons/application/scalable/actions/paperclip.svg
share/icons/application/scalable/actions/configure.svg
share/icons/application/scalable/actions/database-lock.svg
share/icons/application/scalable/actions/password-copy.svg
share/icons/application/scalable/actions/system-help.svg
share/icons/application/scalable/actions/help-about.svg
share/icons/application/scalable/actions/system-search.svg
share/icons/application/scalable/actions/key-enter.svg
share/icons/application/scalable/actions/document-edit.svg
share/icons/application/scalable/actions/edit-clear-locationbar-rtl.svg
share/icons/application/scalable/actions/password-generate.svg
share/icons/application/scalable/actions/favicon-download.svg
share/icons/application/scalable/actions/document-open.svg
share/icons/application/scalable/actions/document-save-as.svg
share/icons/application/scalable/actions/password-generator.svg
share/icons/application/scalable/actions/password-show-off.svg
share/icons/application/scalable/actions/password-show-on.svg
share/icons/application/scalable/actions/refresh.svg
share/icons/application/scalable/actions/clipboard-text.svg
share/icons/application/scalable/actions/reports.svg
share/icons/application/scalable/actions/reports-exclude.svg
share/icons/application/scalable/actions/statistics.svg
share/icons/application/scalable/actions/system-help.svg
share/icons/application/scalable/actions/system-search.svg
share/icons/application/scalable/actions/url-copy.svg
share/icons/application/scalable/actions/username-copy.svg
share/icons/application/scalable/actions/view-history.svg
share/icons/application/scalable/apps/freedesktop.svg
share/icons/application/scalable/apps/internet-web-browser.svg
share/icons/application/scalable/apps/keepassxc.svg
share/icons/application/scalable/apps/keepassxc-dark.svg
share/icons/application/scalable/apps/keepassxc-locked.svg
share/icons/application/scalable/apps/keepassxc-unlocked.svg
share/icons/application/scalable/apps/preferences-desktop-icons.svg
share/icons/application/scalable/apps/preferences-system-network-sharing.svg
share/icons/application/scalable/apps/utilities-terminal.svg
share/icons/application/scalable/categories/preferences-other.svg
share/icons/application/scalable/mimetypes/application-x-keepassxc.svg
share/icons/application/scalable/status/dialog-error.svg
share/icons/application/scalable/status/dialog-information.svg
share/icons/application/scalable/status/dialog-warning.svg
share/icons/application/scalable/status/security-high.svg
Copyright: 2019 Austin Andrews <http://templarian.com/>
License: SIL OPEN FONT LICENSE Version 1.1
Comment: Taken from Material Design icon set (https://github.com/templarian/MaterialDesign/)
Expand Down
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 share/icons/icons.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<file>application/scalable/actions/document-properties.svg</file>
<file>application/scalable/actions/document-save.svg</file>
<file>application/scalable/actions/document-save-as.svg</file>
<file>application/scalable/actions/document-save-copy.svg</file>
<file>application/scalable/actions/donate.svg</file>
<file>application/scalable/actions/edit-clear-locationbar-ltr.svg</file>
<file>application/scalable/actions/edit-clear-locationbar-rtl.svg</file>
Expand Down
14 changes: 14 additions & 0 deletions src/gui/DatabaseTabWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,20 @@ bool DatabaseTabWidget::saveDatabaseAs(int index)
return ok;
}

bool DatabaseTabWidget::saveDatabaseBackup(int index)
{
if (index == -1) {
index = currentIndex();
}

auto* dbWidget = databaseWidgetFromIndex(index);
bool ok = dbWidget->saveBackup();
if (ok) {
updateLastDatabases(dbWidget->database()->filePath());
}
return ok;
}

void DatabaseTabWidget::closeDatabaseFromSender()
{
auto* dbWidget = qobject_cast<DatabaseWidget*>(sender());
Expand Down
1 change: 1 addition & 0 deletions src/gui/DatabaseTabWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public slots:
void importOpVaultDatabase();
bool saveDatabase(int index = -1);
bool saveDatabaseAs(int index = -1);
bool saveDatabaseBackup(int index = -1);
void exportToCsv();
void exportToHtml();

Expand Down
47 changes: 47 additions & 0 deletions src/gui/DatabaseWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1866,6 +1866,53 @@ bool DatabaseWidget::saveAs()
}
}

/**
* Save copy of database under a new user-selected filename.
*
* @return true on success
*/
bool DatabaseWidget::saveBackup()
{
while (true) {
QString oldFilePath = m_db->filePath();
if (!QFileInfo::exists(oldFilePath)) {
oldFilePath = QDir::toNativeSeparators(config()->get(Config::LastDir).toString() + "/"
+ tr("Passwords").append(".kdbx"));
}
const QString newFilePath = fileDialog()->getSaveFileName(this,
tr("Save database backup"),
oldFilePath,
tr("KeePass 2 Database").append(" (*.kdbx)"),
nullptr,
nullptr);

if (!newFilePath.isEmpty()) {
// Ensure we don't recurse back into this function
m_db->setReadOnly(false);
m_db->setFilePath(newFilePath);
m_saveAttempts = 0;

bool modified = m_db->isModified();

if (!save()) {
// Failed to save, try again
m_db->setFilePath(oldFilePath);
continue;
}

m_db->setFilePath(oldFilePath);
if (modified) {
// Source database is marked as clean when copy is saved, even if source has unsaved changes
m_db->markAsModified();
}
return true;
}

// Canceled file selection
return false;
}
}

void DatabaseWidget::showMessage(const QString& text,
MessageWidget::MessageType type,
bool showClosebutton,
Expand Down
1 change: 1 addition & 0 deletions src/gui/DatabaseWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ public slots:
bool lock();
bool save();
bool saveAs();
bool saveBackup();

void replaceDatabase(QSharedPointer<Database> db);
void createEntry();
Expand Down
5 changes: 5 additions & 0 deletions src/gui/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ MainWindow::MainWindow()
m_ui->actionDatabaseOpen->setIcon(resources()->icon("document-open"));
m_ui->actionDatabaseSave->setIcon(resources()->icon("document-save"));
m_ui->actionDatabaseSaveAs->setIcon(resources()->icon("document-save-as"));
m_ui->actionDatabaseSaveBackup->setIcon(resources()->icon("document-save-copy"));
m_ui->actionDatabaseClose->setIcon(resources()->icon("document-close"));
m_ui->actionReports->setIcon(resources()->icon("reports"));
m_ui->actionChangeDatabaseSettings->setIcon(resources()->icon("document-edit"));
Expand Down Expand Up @@ -396,6 +397,7 @@ MainWindow::MainWindow()
connect(m_ui->actionDatabaseOpen, SIGNAL(triggered()), m_ui->tabWidget, SLOT(openDatabase()));
connect(m_ui->actionDatabaseSave, SIGNAL(triggered()), m_ui->tabWidget, SLOT(saveDatabase()));
connect(m_ui->actionDatabaseSaveAs, SIGNAL(triggered()), m_ui->tabWidget, SLOT(saveDatabaseAs()));
connect(m_ui->actionDatabaseSaveBackup, SIGNAL(triggered()), m_ui->tabWidget, SLOT(saveDatabaseBackup()));
connect(m_ui->actionDatabaseClose, SIGNAL(triggered()), m_ui->tabWidget, SLOT(closeCurrentDatabaseTab()));
connect(m_ui->actionDatabaseMerge, SIGNAL(triggered()), m_ui->tabWidget, SLOT(mergeDatabase()));
connect(m_ui->actionChangeMasterKey, SIGNAL(triggered()), m_ui->tabWidget, SLOT(changeMasterKey()));
Expand Down Expand Up @@ -681,6 +683,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionChangeDatabaseSettings->setEnabled(true);
m_ui->actionDatabaseSave->setEnabled(m_ui->tabWidget->canSave());
m_ui->actionDatabaseSaveAs->setEnabled(true);
m_ui->actionDatabaseSaveBackup->setEnabled(true);
m_ui->menuExport->setEnabled(true);
m_ui->actionExportCsv->setEnabled(true);
m_ui->actionExportHtml->setEnabled(true);
Expand Down Expand Up @@ -736,6 +739,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionChangeDatabaseSettings->setEnabled(false);
m_ui->actionDatabaseSave->setEnabled(false);
m_ui->actionDatabaseSaveAs->setEnabled(false);
m_ui->actionDatabaseSaveBackup->setEnabled(false);
m_ui->menuExport->setEnabled(false);
m_ui->actionExportCsv->setEnabled(false);
m_ui->actionExportHtml->setEnabled(false);
Expand Down Expand Up @@ -764,6 +768,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionChangeDatabaseSettings->setEnabled(false);
m_ui->actionDatabaseSave->setEnabled(false);
m_ui->actionDatabaseSaveAs->setEnabled(false);
m_ui->actionDatabaseSaveBackup->setEnabled(false);
m_ui->actionDatabaseClose->setEnabled(false);
m_ui->menuExport->setEnabled(false);
m_ui->actionExportCsv->setEnabled(false);
Expand Down
12 changes: 9 additions & 3 deletions src/gui/MainWindow.ui
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@
<addaction name="menuRecentDatabases"/>
<addaction name="actionDatabaseSave"/>
<addaction name="actionDatabaseSaveAs"/>
<addaction name="actionDatabaseSaveBackup"/>
<addaction name="actionDatabaseClose"/>
<addaction name="separator"/>
<addaction name="actionChangeMasterKey"/>
Expand Down Expand Up @@ -332,9 +333,6 @@
<addaction name="separator"/>
<addaction name="actionEntryOpenUrl"/>
<addaction name="actionEntryDownloadIcon"/>
<addaction name="separator"/>
<addaction name="actionEntryAddToAgent"/>
<addaction name="actionEntryRemoveFromAgent"/>
</widget>
<widget class="QMenu" name="menuGroups">
<property name="title">
Expand Down Expand Up @@ -808,6 +806,14 @@
<string notr="true">Ctrl+/</string>
</property>
</action>
<action name="actionDatabaseSaveBackup">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Save Database Backup...</string>
</property>
</action>
<action name="actionEntryAddToAgent">
<property name="text">
<string>Add key to SSH Agent</string>
Expand Down
29 changes: 29 additions & 0 deletions tests/gui/TestGui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,35 @@ void TestGui::testSaveAs()
tmpFile.remove();
}

void TestGui::testSaveBackup()
{
m_db->metadata()->setName("testSaveBackup");

QFileInfo fileInfo(m_dbFilePath);
QDateTime lastModified = fileInfo.lastModified();

// open temporary file so it creates a filename
TemporaryFile tmpFile;
QVERIFY(tmpFile.open());
QString tmpFileName = tmpFile.fileName();
tmpFile.remove();

// wait for modified timer
QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("testSaveBackup*"));

fileDialog()->setNextFileName(tmpFileName);

triggerAction("actionDatabaseSaveBackup");

QCOMPARE(m_tabWidget->tabName(m_tabWidget->currentIndex()), QString("testSaveBackup*"));

checkDatabase(tmpFileName);

fileInfo.refresh();
QCOMPARE(fileInfo.lastModified(), lastModified);
tmpFile.remove();
}

void TestGui::testSave()
{
m_db->metadata()->setName("testSave");
Expand Down
1 change: 1 addition & 0 deletions tests/gui/TestGui.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ private slots:
void testDragAndDropEntry();
void testDragAndDropGroup();
void testSaveAs();
void testSaveBackup();
void testSave();
void testDatabaseSettings();
void testKeePass1Import();
Expand Down
1 change: 1 addition & 0 deletions utils/makeicons.sh
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ map() {
document-properties) echo file-edit-outline ;;
document-save) echo content-save-outline ;;
document-save-as) echo content-save-all-outline ;;
document-save-copy) echo content-save-move-outline ;;
donate) echo gift-outline ;;
edit-clear-locationbar-ltr) echo backspace-reverse-outline ;;
edit-clear-locationbar-rtl) echo backspace-outline ;;
Expand Down

0 comments on commit 45848c3

Please sign in to comment.