diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 39971ab2..13ec70bc 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -687,6 +687,30 @@ void MainWindow::refreshTableBrowsers(bool force_refresh)
QApplication::restoreOverrideCursor();
}
+bool MainWindow::fileSaveAs() {
+
+ QString fileName = FileDialog::getSaveFileName(
+ OpenDatabaseFile,
+ this,
+ tr("Choose a database file to save under"),
+ FileDialog::getSqlDatabaseFileFilter()
+ );
+ // catch situation where user has canceled file selection from dialog
+ if(!fileName.isEmpty()) {
+ bool result = db.saveAs(fileName.toStdString());
+ if(result) {
+ setCurrentFile(fileName);
+ addToRecentFilesMenu(fileName);
+ } else {
+ QMessageBox::warning(this, QApplication::applicationName(),
+ tr("Error while saving the database to the new file."));
+ }
+ return result;
+ } else {
+ return false;
+ }
+}
+
bool MainWindow::fileClose()
{
// Stop any running SQL statements before closing the database
@@ -1415,6 +1439,9 @@ void MainWindow::dbState(bool dirty)
{
ui->fileSaveAction->setEnabled(dirty);
ui->fileRevertAction->setEnabled(dirty);
+ // Unfortunately, sqlite does not allow to backup the DB while there are pending savepoints,
+ // so we cannot "Save As" when the DB is dirty.
+ ui->fileSaveAsAction->setEnabled(!dirty);
}
void MainWindow::fileSave()
@@ -1824,6 +1851,7 @@ void MainWindow::activateFields(bool enable)
bool tempDb = db.currentFile() == ":memory:";
ui->fileCloseAction->setEnabled(enable);
+ ui->fileSaveAsAction->setEnabled(enable);
ui->fileAttachAction->setEnabled(enable);
ui->fileCompactAction->setEnabled(enable && write);
ui->fileExportJsonAction->setEnabled(enable);
diff --git a/src/MainWindow.h b/src/MainWindow.h
index fcb0eda1..cd845523 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -167,6 +167,7 @@ private slots:
void fileNewInMemoryDatabase();
void refreshTableBrowsers(bool force_refresh = false);
bool fileClose();
+ bool fileSaveAs();
void createTable();
void createIndex();
void compact();
diff --git a/src/MainWindow.ui b/src/MainWindow.ui
index 5ec3b562..6076bce6 100644
--- a/src/MainWindow.ui
+++ b/src/MainWindow.ui
@@ -785,6 +785,7 @@ You can drag SQL statements from an object row and drop them into other applicat
+
@@ -2240,6 +2241,21 @@ You can drag SQL statements from the Schema column and drop them into the SQL ed
This shows the number of rows for each table and view in the database.
+
+
+ false
+
+
+
+ :/icons/db_save:/icons/db_save
+
+
+ Save Database &As...
+
+
+ Save the current database as a different file
+
+
@@ -2350,6 +2366,22 @@ You can drag SQL statements from the Schema column and drop them into the SQL ed
+
+ fileSaveAsAction
+ triggered()
+ MainWindow
+ fileSaveAs()
+
+
+ -1
+ -1
+
+
+ 399
+ 299
+
+
+
fileCompactAction
triggered()
diff --git a/src/sqlitedb.cpp b/src/sqlitedb.cpp
index 4268e45a..9414a23c 100644
--- a/src/sqlitedb.cpp
+++ b/src/sqlitedb.cpp
@@ -755,6 +755,62 @@ bool DBBrowserDB::close()
return true;
}
+bool DBBrowserDB::saveAs(const std::string& filename) {
+ int rc;
+ sqlite3_backup *pBackup;
+ sqlite3 *pTo;
+
+ if(!_db)
+ return false;
+
+ waitForDbRelease();
+
+ // Open the database file identified by filename. Exit early if this fails
+ // for any reason.
+ rc = sqlite3_open(filename.c_str(), &pTo);
+ if(rc!=SQLITE_OK) {
+ qWarning() << tr("Cannot open destination file: '%1'").arg(filename.c_str());
+ return false;
+ } else {
+ // Set up the backup procedure to copy from the "main" database of
+ // connection _db to the main database of connection pTo.
+ // If something goes wrong, pBackup will be set to nullptr and an error
+ // code and message left in connection pTo.
+ //
+ // If the backup object is successfully created, call backup_step()
+ // to copy data from _db to pTo. Then call backup_finish()
+ // to release resources associated with the pBackup object. If an
+ // error occurred, then an error code and message will be left in
+ // connection pTo. If no error occurred, then the error code belonging
+ // to pTo is set to SQLITE_OK.
+ //
+ pBackup = sqlite3_backup_init(pTo, "main", _db, "main");
+ if(pBackup == nullptr) {
+ qWarning() << tr("Cannot backup to file: '%1'. Message: %2").arg(filename.c_str()).arg(sqlite3_errmsg(pTo));
+ sqlite3_close(pTo);
+ return false;
+ } else {
+ sqlite3_backup_step(pBackup, -1);
+ sqlite3_backup_finish(pBackup);
+ }
+ rc = sqlite3_errcode(pTo);
+ }
+
+ if(rc == SQLITE_OK) {
+ // Close current database and set backup as current
+ sqlite3_close(_db);
+ _db = pTo;
+ curDBFilename = QString::fromStdString(filename);
+
+ return true;
+ } else {
+ qWarning() << tr("Cannot backup to file: '%1'. Message: %2").arg(filename.c_str()).arg(sqlite3_errmsg(pTo));
+ // Close failed database connection.
+ sqlite3_close(pTo);
+ return false;
+ }
+}
+
DBBrowserDB::db_pointer_type DBBrowserDB::get(const QString& user, bool force_wait)
{
if(!_db)
diff --git a/src/sqlitedb.h b/src/sqlitedb.h
index 90b6a36b..f8a68414 100644
--- a/src/sqlitedb.h
+++ b/src/sqlitedb.h
@@ -80,6 +80,7 @@ public:
bool detach(const std::string& attached_as);
bool create ( const QString & db);
bool close();
+ bool saveAs(const std::string& filename);
// This returns the SQLite version as well as the SQLCipher if DB4S is compiled with encryption support
static void getSqliteVersion(QString& sqlite, QString& sqlcipher);