Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fa7f331
Pass /norestart to vcredist installer
robert-nix Aug 31, 2017
69b555a
Fix index updating when removing an indexed column from a table
MKleusberg Sep 2, 2017
e7b97e5
Don't automatically commit all changes when editing a table column
MKleusberg Sep 2, 2017
5025e4d
dbhub: Support pushing to different branches than "master"
MKleusberg Sep 3, 2017
ccccef9
dbhub: Add tooltip to cog tool button
MKleusberg Sep 1, 2017
9a4952a
dbhub: Redownload database if local copy has been deleted
MKleusberg Sep 1, 2017
8fa0180
cipher: Fix passphrases containing single quotes
MKleusberg Sep 1, 2017
9afb0fc
cipher: Change tab order in encryption dialog
MKleusberg Sep 1, 2017
74733b7
dbhub: Remove closing "." from the progress dialog
MKleusberg Sep 1, 2017
3534f57
dbhub: Enforce name and commit message length limits in push dialog
MKleusberg Sep 1, 2017
d939898
dbhub: Fix progress dialog for very large files
MKleusberg Sep 1, 2017
f72e96c
dbhub: Fix wrong file size being shown for very large files
MKleusberg Sep 1, 2017
9108122
dbhub: Tweak certificate UI in the preferences dialog
MKleusberg Sep 1, 2017
fe7707d
dbhub: Improve file size format
MKleusberg Sep 1, 2017
876d938
dbhub: Optimise code
MKleusberg Sep 1, 2017
aa62d12
Fix typo in Russian translation
justinclift Sep 1, 2017
3186c58
dbhub: Ask user what to do when trying to open an updated database
MKleusberg Sep 1, 2017
8021523
dbhub: Fix branch list in push dialog
MKleusberg Sep 8, 2017
2845cab
Add '<>NULL' filter
MKleusberg Sep 10, 2017
bbbfb45
Make text selectable in Edit dock even if db is opened as read only
MKleusberg Sep 10, 2017
4136c7c
Fix custom type saving when only focus changes for user-entered type
techee Sep 19, 2017
b645b1f
Fix merging error
MKleusberg Sep 19, 2017
9690220
Update version numbers to 3.10.1
justinclift Sep 20, 2017
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
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
set(CPACK_PACKAGE_VERSION_MAJOR "3")
set(CPACK_PACKAGE_VERSION_MINOR "10")
set(CPACK_PACKAGE_VERSION_PATCH "0")
set(CPACK_PACKAGE_VERSION_PATCH "1")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "DB Browser for SQLite")
if(WIN32 AND NOT UNIX)
# There is a bug in NSIS that does not handle full unix paths properly. Make
Expand All @@ -433,7 +433,7 @@ if(WIN32 AND NOT UNIX)

# VS redist
list(APPEND CPACK_NSIS_EXTRA_INSTALL_COMMANDS "
ExecWait '\\\"$INSTDIR\\\\redist\\\\${VSREDIST}\\\" /install /passive /quiet'
ExecWait '\\\"$INSTDIR\\\\redist\\\\${VSREDIST}\\\" /install /passive /norestart /quiet'
Delete '\\\"$INSTDIR\\\\redist\\\\${VSREDIST}\\\"'
")
else(WIN32 AND NOT UNIX)
Expand Down
2 changes: 1 addition & 1 deletion src/CipherDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ CipherDialog::KeyFormats CipherDialog::keyFormat() const
QString CipherDialog::password() const
{
if(keyFormat() == KeyFormats::Passphrase)
return QString("'%1'").arg(ui->editPassword->text());
return QString("'%1'").arg(ui->editPassword->text().replace("'", "''"));
else
return QString("\"x'%1'\"").arg(ui->editPassword->text().mid(2)); // Remove the '0x' part at the beginning
}
Expand Down
2 changes: 1 addition & 1 deletion src/CipherDialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@
</layout>
</widget>
<tabstops>
<tabstop>comboKeyFormat</tabstop>
<tabstop>editPassword</tabstop>
<tabstop>comboKeyFormat</tabstop>
<tabstop>editPassword2</tabstop>
<tabstop>spinPageSize</tabstop>
</tabstops>
Expand Down
2 changes: 2 additions & 0 deletions src/EditDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,8 @@ void EditDialog::setReadOnly(bool ro)
ui->buttonApply->setEnabled(!ro);
ui->buttonNull->setEnabled(!ro);
ui->buttonImport->setEnabled(!ro);
ui->editorText->setReadOnly(ro);
ui->editorBinary->setEnabled(!ro); // We disable the entire hex editor here instead of setting it to read only because it doesn't have a setReadOnly() method
}

// Update the information labels in the bottom left corner of the dialog
Expand Down
25 changes: 21 additions & 4 deletions src/EditTableDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ void EditTableDialog::populateFields()
index = typeBox->count() - 1;
}
typeBox->setCurrentIndex(index);
typeBox->installEventFilter(this);
connect(typeBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateTypes()));
ui->treeWidget->setItemWidget(tbitem, kType, typeBox);

Expand Down Expand Up @@ -222,13 +223,13 @@ void EditTableDialog::checkInput()
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);
}

void EditTableDialog::updateTypes()
void EditTableDialog::updateTypes(QObject *object)
{
QComboBox* typeBox = qobject_cast<QComboBox*>(sender());
QComboBox* typeBox = qobject_cast<QComboBox*>(object);
if(typeBox)
{
QString type = typeBox->currentText();
QString column = sender()->property("column").toString();
QString column = typeBox->property("column").toString();

int index;
for(index=0; index < m_table.fields().size(); ++index)
Expand All @@ -244,6 +245,20 @@ void EditTableDialog::updateTypes()
}
}

void EditTableDialog::updateTypes()
{
updateTypes(sender());
}

bool EditTableDialog::eventFilter(QObject *object, QEvent *event)
{
if(event->type() == QEvent::FocusOut)
{
updateTypes(object);
}
return false;
}

void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
{
int index = ui->treeWidget->indexOfTopLevelItem(item);
Expand Down Expand Up @@ -528,6 +543,7 @@ void EditTableDialog::addField()
}

ui->treeWidget->setItemWidget(tbitem, kType, typeBox);
typeBox->installEventFilter(this);
connect(typeBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateTypes()));

tbitem->setCheckState(kNotNull, Qt::Unchecked);
Expand Down Expand Up @@ -631,7 +647,8 @@ void EditTableDialog::moveCurrentField(bool down)
QComboBox* oldCombo = qobject_cast<QComboBox*>(ui->treeWidget->itemWidget(ui->treeWidget->topLevelItem(currentRow), kType));
QComboBox* newCombo = new QComboBox(ui->treeWidget);
newCombo->setProperty("column", oldCombo->property("column"));
connect(newCombo, SIGNAL(activated(int)), this, SLOT(updateTypes()));
newCombo->installEventFilter(this);
connect(newCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateTypes()));
newCombo->setEditable(true);
for(int i=0; i < oldCombo->count(); ++i)
newCombo->addItem(oldCombo->itemText(i));
Expand Down
2 changes: 2 additions & 0 deletions src/EditTableDialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ private slots:
virtual void reject();
void checkInput();
void itemChanged(QTreeWidgetItem* item, int column);
void updateTypes(QObject *object);
bool eventFilter(QObject *object, QEvent *event);
void updateTypes();
void moveUp();
void moveDown();
Expand Down
9 changes: 6 additions & 3 deletions src/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ void MainWindow::init()
ui->dockPlot->setWidget(plotDock);
ui->dockRemote->setWidget(remoteDock);

// Set up edit dock
editDock->setReadOnly(true);

// Restore window geometry
restoreGeometry(Settings::getValue("MainWindow", "geometry").toByteArray());
restoreState(Settings::getValue("MainWindow", "windowState").toByteArray());
Expand Down Expand Up @@ -851,7 +854,7 @@ void MainWindow::doubleClickTable(const QModelIndex& index)
}

// * Don't allow editing of other objects than tables (on the browse table) *
bool isEditingAllowed = (m_currentTabTableModel == m_browseTableModel) &&
bool isEditingAllowed = !db.readOnly() && m_currentTabTableModel == m_browseTableModel &&
(db.getObjectByName(ui->comboBrowseTable->currentText())->type() == sqlb::Object::Types::Table);

// Enable or disable the Apply, Null, & Import buttons in the Edit Cell
Expand All @@ -875,7 +878,7 @@ void MainWindow::dataTableSelectionChanged(const QModelIndex& index)
return;
}

bool editingAllowed = (m_currentTabTableModel == m_browseTableModel) &&
bool editingAllowed = !db.readOnly() && (m_currentTabTableModel == m_browseTableModel) &&
(db.getObjectByName(ui->comboBrowseTable->currentText())->type() == sqlb::Object::Types::Table);

// Don't allow editing of other objects than tables
Expand Down Expand Up @@ -1486,7 +1489,7 @@ void MainWindow::activateFields(bool enable)
ui->actionSaveProject->setEnabled(enable);
ui->actionEncryption->setEnabled(enable && write);
ui->buttonClearFilters->setEnabled(enable);
ui->dockEdit->setEnabled(enable && write);
ui->dockEdit->setEnabled(enable);
ui->dockPlot->setEnabled(enable);

remoteDock->enableButtons();
Expand Down
6 changes: 6 additions & 0 deletions src/PreferencesDialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,9 @@
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Subject CN</string>
Expand Down Expand Up @@ -1094,6 +1097,9 @@
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>File</string>
Expand Down
117 changes: 103 additions & 14 deletions src/RemoteDatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,27 @@ void RemoteDatabase::gotReply(QNetworkReply* reply)
emit gotLicenceList(licences);
break;
}
case RequestTypeBranchList:
{
// Read and check results
QJsonDocument json = QJsonDocument::fromJson(reply->readAll());
if(json.isNull() || !json.isObject())
break;
QJsonObject obj = json.object();
QJsonObject obj_branches = obj["branches"].toObject();

// Parse data and assemble branch list
QStringList branches;
for(auto it=obj_branches.constBegin();it!=obj_branches.constEnd();++it)
branches.append(it.key());

// Get default branch
QString default_branch = obj["default_branch"].toString("master");

// Send branch list to anyone who is interested
emit gotBranchList(branches, default_branch);
break;
}
case RequestTypePush:
emit uploadFinished(reply->url().toString());
break;
Expand Down Expand Up @@ -238,9 +259,14 @@ void RemoteDatabase::updateProgress(qint64 bytesTransmitted, qint64 bytesTotal)
m_progress->reset();
} else {
// It's still downloading and we know the current progress

// Were using a range 0 to 10000 here, the progress dialog will calculate 0% to 100% values from that. The reason we're not using
// the byte counts as-is is that they're 64bit wide while the progress dialog takes only 32bit values, so for large files the values
// would lose precision. The reason why we're not using a range 0 to 100 is that our range increases the precision a bit and this way
// we're prepared if the progress dialog will show decimal numbers one day on one platform.
m_progress->setMinimum(0);
m_progress->setMaximum(bytesTotal);
m_progress->setValue(bytesTransmitted);
m_progress->setMaximum(10000);
m_progress->setValue(static_cast<int>((static_cast<float>(bytesTransmitted) / static_cast<float>(bytesTotal)) * 10000.0f));
}

// Check if the Cancel button has been pressed
Expand Down Expand Up @@ -320,9 +346,9 @@ void RemoteDatabase::prepareProgressDialog(QNetworkReply* reply, bool upload, co

// Set dialog text
if(upload)
m_progress->setLabelText(tr("Uploading remote database to\n%1.").arg(url));
m_progress->setLabelText(tr("Uploading remote database to\n%1").arg(url));
else
m_progress->setLabelText(tr("Downloading remote database from\n%1.").arg(url));
m_progress->setLabelText(tr("Downloading remote database from\n%1").arg(url));

// Show dialog
m_progress->show();
Expand Down Expand Up @@ -387,7 +413,7 @@ void RemoteDatabase::fetch(const QString& url, RequestType type, const QString&
}

void RemoteDatabase::push(const QString& filename, const QString& url, const QString& clientCert, const QString& remotename,
const QString& commitMessage, const QString& licence, bool isPublic)
const QString& commitMessage, const QString& licence, bool isPublic, const QString& branch)
{
// Check if network is accessible. If not, abort right here
if(m_manager->networkAccessible() == QNetworkAccessManager::NotAccessible)
Expand Down Expand Up @@ -416,6 +442,7 @@ void RemoteDatabase::push(const QString& filename, const QString& url, const QSt
addPart(multipart, "commitmsg", commitMessage);
addPart(multipart, "licence", licence);
addPart(multipart, "public", isPublic ? "true" : "false");
addPart(multipart, "branch", branch);

// Set SSL configuration when trying to access a file via the HTTPS protocol
bool https = QUrl(url).scheme().compare("https", Qt::CaseInsensitive) == 0;
Expand Down Expand Up @@ -574,16 +601,14 @@ QString RemoteDatabase::localExists(const QUrl& url, QString identity)

// Extract commit id from url and remove query part afterwards
QString url_commit_id = QUrlQuery(url).queryItemValue("commit");
QUrl url_without_query = url;
url_without_query.setQuery(QString());

// Query commit id and filename for the given combination of url and identity
QString sql = QString("SELECT id, commit_id, file FROM local WHERE url=? AND identity=?");
sqlite3_stmt* stmt;
if(sqlite3_prepare_v2(m_dbLocal, sql.toUtf8(), -1, &stmt, 0) != SQLITE_OK)
return QString();

if(sqlite3_bind_text(stmt, 1, url_without_query.toString().toUtf8(), url_without_query.toString().toUtf8().length(), SQLITE_TRANSIENT))
if(sqlite3_bind_text(stmt, 1, url.toString(QUrl::PrettyDecoded | QUrl::RemoveQuery).toUtf8(), url.toString(QUrl::PrettyDecoded | QUrl::RemoveQuery).toUtf8().length(), SQLITE_TRANSIENT))
{
sqlite3_finalize(stmt);
return QString();
Expand Down Expand Up @@ -616,15 +641,79 @@ QString RemoteDatabase::localExists(const QUrl& url, QString identity)
// is newer, or the local commit id is newer.
if(local_commit_id == url_commit_id)
{
// Both commit ids are the same. That's the perfect match, so just return the path to the local file
return Settings::getValue("remote", "clonedirectory").toString() + "/" + local_file;
// Both commit ids are the same. That's the perfect match, so we can download the local file if it still exists
return localCheckFile(local_file);
} else {
// In all the other cases just treat the remote database as a completely new database for now.
// The commit ids differ. That means we have another version locally checked out than we're trying to download. Because the commit ids are
// only calculated on the server side and we're currently always checking out the latest version this can only mean that the remote version has
// been updated, i.e. is newer than the local version.

// TODO Support multiple checkouts of the same database at different versions at the same time. For this we need to be more intelligent with
// comparing the commit ids.

// Ask the user what to do: open the local version or updating to the new remote version
if(QMessageBox::question(nullptr, qApp->applicationName(),
tr("The remote database has been updated since the last checkout. Do you want to update the local database to the newest version? Note "
"that this discards any changes you have made locally! If you don't want to lose local changes, click No to open the local version."),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes)
{
// User wants to download the newest version. So delete the entry from the clones database and delete the local database copy and return an empty
// string to indicate a redownload request.

// Build full path to database file and delete it
QFile::remove(Settings::getValue("remote", "clonedirectory").toString() + "/" + local_file);

// Remove the old entry from the local clones database to enforce a redownload. The file column should be unique for the entire table because the
// files are all in the same directory and their names need to be unique because of this.
QString sql = QString("DELETE FROM local WHERE file=?");
sqlite3_stmt* stmt;
if(sqlite3_prepare_v2(m_dbLocal, sql.toUtf8(), -1, &stmt, 0) != SQLITE_OK)
return QString();
if(sqlite3_bind_text(stmt, 1, local_file.toUtf8(), local_file.toUtf8().length(), SQLITE_TRANSIENT))
{
sqlite3_finalize(stmt);
return QString();
}
sqlite3_step(stmt);
sqlite3_finalize(stmt);

// Return an empty string to indicate a redownload request
return QString();
} else {
// User wants to open the local version. So build the full path and return it if the file still exists.
return localCheckFile(local_file);
}
}
}

QString RemoteDatabase::localCheckFile(const QString& local_file)
{
// This function takes the file name of a locally cloned database and checks if this file still exists. If it has been deleted in the meantime it returns
// an empty string and deletes the file from the clone database. If the file still exists, it returns the full path to the file.

// TODO Add some way to update the local clone here. Maybe ask the user what to do because I don't really know what the
// most sensible way to go is in the two remaining cases. We can use the local_id variable (see above) to update the
// record afterwards.
// Build the full path to where the file should be
QString full_path = Settings::getValue("remote", "clonedirectory").toString() + "/" + local_file;

// Check if the database still exists. If so return its path, if not return an empty string to redownload it
if(QFile::exists(full_path))
{
return full_path;
} else {
// Remove the apparently invalid entry from the local clones database to avoid future lookups and confusions. The file column should
// be unique for the entire table because the files are all in the same directory and their names need to be unique because of this.
QString sql = QString("DELETE FROM local WHERE file=?");
sqlite3_stmt* stmt;
if(sqlite3_prepare_v2(m_dbLocal, sql.toUtf8(), -1, &stmt, 0) != SQLITE_OK)
return QString();
if(sqlite3_bind_text(stmt, 1, local_file.toUtf8(), local_file.toUtf8().length(), SQLITE_TRANSIENT))
{
sqlite3_finalize(stmt);
return QString();
}
sqlite3_step(stmt);
sqlite3_finalize(stmt);

// Return empty string to indicate a redownload request
return QString();
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/RemoteDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ class RemoteDatabase : public QObject
RequestTypeNewVersionCheck,
RequestTypePush,
RequestTypeLicenceList,
RequestTypeBranchList,
};

void fetch(const QString& url, RequestType type, const QString& clientCert = QString(), QVariant userdata = QVariant());
void push(const QString& filename, const QString& url, const QString& clientCert, const QString& remotename,
const QString& commitMessage = QString(), const QString& licence = QString(), bool isPublic = false);
const QString& commitMessage = QString(), const QString& licence = QString(), bool isPublic = false, const QString& branch = QString("master"));

signals:
// The openFile signal is emitted whenever a remote database file shall be opened in the main window. This happens when the
Expand All @@ -57,6 +58,7 @@ class RemoteDatabase : public QObject
void gotDirList(QString json, QVariant userdata);
void gotCurrentVersion(QString version, QString url);
void gotLicenceList(QMap<QString, QString> licences);
void gotBranchList(QStringList branches, QString default_branch);

// The uploadFinished() signal is emitted when a push() call is finished, i.e. a database upload has completed.
void uploadFinished(QString url);
Expand All @@ -73,6 +75,7 @@ class RemoteDatabase : public QObject
void localAssureOpened();
void localAdd(QString filename, QString identity, const QUrl& url);
QString localExists(const QUrl& url, QString identity);
QString localCheckFile(const QString& local_file);

// Helper functions for building multi-part HTTP requests
void addPart(QHttpMultiPart* multipart, const QString& name, const QString& value);
Expand Down
2 changes: 1 addition & 1 deletion src/RemoteDock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ void RemoteDock::pushDatabase()

// Push database
remoteDatabase.push(mainWindow->getDb().currentFile(), url, remoteModel->currentClientCertificate(), pushDialog.name(),
pushDialog.commitMessage(), pushDialog.licence(), pushDialog.isPublic());
pushDialog.commitMessage(), pushDialog.licence(), pushDialog.isPublic(), pushDialog.branch());
}

void RemoteDock::newDirectoryNode(const QModelIndex& parent)
Expand Down
Loading