Add "Save Database As..." action

The feature is implemented using the SQLite backup API.

https://sqlite.org/backup.html

This allows saving in-memory databases and saving database files to another
file name.
This commit is contained in:
mgrojo
2021-01-22 23:54:26 +01:00
parent 42ce8995c8
commit c8cd858699
5 changed files with 118 additions and 0 deletions
+28
View File
@@ -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);
+1
View File
@@ -167,6 +167,7 @@ private slots:
void fileNewInMemoryDatabase();
void refreshTableBrowsers(bool force_refresh = false);
bool fileClose();
bool fileSaveAs();
void createTable();
void createIndex();
void compact();
+32
View File
@@ -785,6 +785,7 @@ You can drag SQL statements from an object row and drop them into other applicat
<addaction name="fileOpenReadOnlyAction"/>
<addaction name="fileAttachAction"/>
<addaction name="fileRecentFiles"/>
<addaction name="fileSaveAsAction"/>
<addaction name="fileCloseAction"/>
<addaction name="separator"/>
<addaction name="fileSaveAction"/>
@@ -2240,6 +2241,21 @@ You can drag SQL statements from the Schema column and drop them into the SQL ed
<string>This shows the number of rows for each table and view in the database.</string>
</property>
</action>
<action name="fileSaveAsAction">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/db_save</normaloff>:/icons/db_save</iconset>
</property>
<property name="text">
<string>Save Database &amp;As...</string>
</property>
<property name="toolTip">
<string>Save the current database as a different file</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
@@ -2350,6 +2366,22 @@ You can drag SQL statements from the Schema column and drop them into the SQL ed
</hint>
</hints>
</connection>
<connection>
<sender>fileSaveAsAction</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>fileSaveAs()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
<y>299</y>
</hint>
</hints>
</connection>
<connection>
<sender>fileCompactAction</sender>
<signal>triggered()</signal>
+56
View File
@@ -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)
+1
View File
@@ -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);