-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Remove lock file and cleanup file handling #1231
Changes from all commits
c2d0449
a80d1bb
36e5377
1a7b874
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,6 @@ | |
#include "DatabaseTabWidget.h" | ||
|
||
#include <QFileInfo> | ||
#include <QLockFile> | ||
#include <QTabWidget> | ||
#include <QPushButton> | ||
|
||
|
@@ -42,8 +41,6 @@ | |
|
||
DatabaseManagerStruct::DatabaseManagerStruct() | ||
: dbWidget(nullptr) | ||
, lockFile(nullptr) | ||
, saveToFilename(false) | ||
, modified(false) | ||
, readOnly(false) | ||
{ | ||
|
@@ -107,7 +104,7 @@ void DatabaseTabWidget::newDatabase() | |
void DatabaseTabWidget::openDatabase() | ||
{ | ||
QString filter = QString("%1 (*.kdbx);;%2 (*)").arg(tr("KeePass 2 Database"), tr("All files")); | ||
QString fileName = fileDialog()->getOpenFileName(this, tr("Open database"), QString(), | ||
QString fileName = fileDialog()->getOpenFileName(this, tr("Open database"), QDir::homePath(), | ||
filter); | ||
if (!fileName.isEmpty()) { | ||
openDatabase(fileName); | ||
|
@@ -128,10 +125,10 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, | |
QHashIterator<Database*, DatabaseManagerStruct> i(m_dbList); | ||
while (i.hasNext()) { | ||
i.next(); | ||
if (i.value().canonicalFilePath == canonicalFilePath) { | ||
if (i.value().fileInfo.canonicalFilePath() == canonicalFilePath) { | ||
if (!i.value().dbWidget->dbHasKey() && !(pw.isNull() && keyFile.isEmpty())) { | ||
// If the database is locked and a pw or keyfile is provided, unlock it | ||
i.value().dbWidget->switchToOpenDatabase(i.value().filePath, pw, keyFile); | ||
i.value().dbWidget->switchToOpenDatabase(i.value().fileInfo.absoluteFilePath(), pw, keyFile); | ||
} else { | ||
setCurrentIndex(databaseIndex(i.key())); | ||
} | ||
|
@@ -157,63 +154,22 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, | |
} | ||
file.close(); | ||
|
||
QLockFile* lockFile = new QLockFile(QString("%1/.%2.lock").arg(fileInfo.canonicalPath(), fileInfo.fileName())); | ||
lockFile->setStaleLockTime(0); | ||
|
||
if (!dbStruct.readOnly && !lockFile->tryLock()) { | ||
// for now silently ignore if we can't create a lock file | ||
// due to lack of permissions | ||
if (lockFile->error() != QLockFile::PermissionError) { | ||
QMessageBox msgBox; | ||
msgBox.setWindowTitle(tr("Database already opened")); | ||
msgBox.setText(tr("The database you are trying to open is locked by another instance of KeePassXC.\n\n" | ||
"Do you want to open it anyway?")); | ||
msgBox.setIcon(QMessageBox::Question); | ||
msgBox.addButton(QMessageBox::Yes); | ||
msgBox.addButton(QMessageBox::No); | ||
auto readOnlyButton = msgBox.addButton(tr("Open read-only"), QMessageBox::NoRole); | ||
msgBox.setDefaultButton(readOnlyButton); | ||
msgBox.setEscapeButton(QMessageBox::No); | ||
auto result = msgBox.exec(); | ||
|
||
if (msgBox.clickedButton() == readOnlyButton) { | ||
dbStruct.readOnly = true; | ||
delete lockFile; | ||
lockFile = nullptr; | ||
} else if (result == QMessageBox::Yes) { | ||
// take over the lock file if possible | ||
if (lockFile->removeStaleLockFile()) { | ||
lockFile->tryLock(); | ||
} | ||
} else { | ||
delete lockFile; | ||
return; | ||
} | ||
} | ||
} | ||
|
||
Database* db = new Database(); | ||
dbStruct.dbWidget = new DatabaseWidget(db, this); | ||
dbStruct.lockFile = lockFile; | ||
dbStruct.saveToFilename = !dbStruct.readOnly; | ||
|
||
dbStruct.filePath = fileInfo.absoluteFilePath(); | ||
dbStruct.canonicalFilePath = canonicalFilePath; | ||
dbStruct.fileName = fileInfo.fileName(); | ||
dbStruct.fileInfo = fileInfo; | ||
|
||
insertDatabase(db, dbStruct); | ||
|
||
if (dbStruct.readOnly) { | ||
emit messageTab(tr("File opened in read only mode."), MessageWidget::Warning); | ||
} | ||
|
||
updateLastDatabases(dbStruct.filePath); | ||
updateLastDatabases(dbStruct.fileInfo.absoluteFilePath()); | ||
|
||
if (!(pw.isNull() && keyFile.isEmpty())) { | ||
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath, pw, keyFile); | ||
} | ||
else { | ||
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath); | ||
if (!pw.isNull() || !keyFile.isEmpty()) { | ||
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.fileInfo.absoluteFilePath(), pw, keyFile); | ||
} else { | ||
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.fileInfo.absoluteFilePath()); | ||
} | ||
emit messageDismissGlobal(); | ||
} | ||
|
@@ -322,15 +278,14 @@ bool DatabaseTabWidget::closeDatabase(Database* db) | |
void DatabaseTabWidget::deleteDatabase(Database* db) | ||
{ | ||
const DatabaseManagerStruct dbStruct = m_dbList.value(db); | ||
bool emitDatabaseWithFileClosed = dbStruct.saveToFilename; | ||
QString filePath = dbStruct.filePath; | ||
bool emitDatabaseWithFileClosed = dbStruct.fileInfo.exists() && !dbStruct.readOnly; | ||
QString filePath = dbStruct.fileInfo.absoluteFilePath(); | ||
|
||
int index = databaseIndex(db); | ||
|
||
removeTab(index); | ||
toggleTabbar(); | ||
m_dbList.remove(db); | ||
delete dbStruct.lockFile; | ||
delete dbStruct.dbWidget; | ||
delete db; | ||
|
||
|
@@ -349,24 +304,31 @@ bool DatabaseTabWidget::closeAllDatabases() | |
return true; | ||
} | ||
|
||
bool DatabaseTabWidget::saveDatabase(Database* db) | ||
bool DatabaseTabWidget::saveDatabase(Database* db, QString filePath) | ||
{ | ||
DatabaseManagerStruct& dbStruct = m_dbList[db]; | ||
|
||
// Never allow saving a locked database; it causes corruption | ||
Q_ASSERT(dbStruct.dbWidget->currentMode() != DatabaseWidget::LockedMode); | ||
// Release build interlock | ||
if (dbStruct.dbWidget->currentMode() == DatabaseWidget::LockedMode) { | ||
// Never allow saving a locked database; it causes corruption | ||
// We return true since a save is not required | ||
return true; | ||
} | ||
|
||
if (dbStruct.saveToFilename) { | ||
if (!dbStruct.readOnly) { | ||
if (filePath.isEmpty()) { | ||
filePath = dbStruct.fileInfo.canonicalFilePath(); | ||
} | ||
|
||
dbStruct.dbWidget->blockAutoReload(true); | ||
QString errorMessage = db->saveToFile(dbStruct.canonicalFilePath); | ||
QString errorMessage = db->saveToFile(filePath); | ||
dbStruct.dbWidget->blockAutoReload(false); | ||
|
||
if (errorMessage.isEmpty()) { | ||
// successfully saved database file | ||
dbStruct.modified = false; | ||
dbStruct.fileInfo = QFileInfo(filePath); | ||
dbStruct.dbWidget->databaseSaved(); | ||
updateTabName(db); | ||
emit messageDismissTab(); | ||
|
@@ -387,76 +349,31 @@ bool DatabaseTabWidget::saveDatabaseAs(Database* db) | |
{ | ||
while (true) { | ||
DatabaseManagerStruct& dbStruct = m_dbList[db]; | ||
QString oldFileName; | ||
if (dbStruct.saveToFilename) { | ||
oldFileName = dbStruct.filePath; | ||
QString oldFilePath; | ||
if (dbStruct.fileInfo.exists()) { | ||
oldFilePath = dbStruct.fileInfo.absoluteFilePath(); | ||
} else { | ||
oldFileName = tr("Passwords").append(".kdbx"); | ||
oldFilePath = QDir::toNativeSeparators(QDir::homePath() + "/" + tr("Passwords").append(".kdbx")); | ||
} | ||
QString fileName = fileDialog()->getSaveFileName(this, tr("Save database as"), | ||
oldFileName, tr("KeePass 2 Database").append(" (*.kdbx)"), | ||
nullptr, 0, "kdbx"); | ||
if (!fileName.isEmpty()) { | ||
QFileInfo fileInfo(fileName); | ||
QString lockFilePath; | ||
if (fileInfo.exists()) { | ||
// returns empty string when file doesn't exist | ||
lockFilePath = fileInfo.canonicalPath(); | ||
} else { | ||
lockFilePath = fileInfo.absolutePath(); | ||
} | ||
QString lockFileName = QString("%1/.%2.lock").arg(lockFilePath, fileInfo.fileName()); | ||
QScopedPointer<QLockFile> lockFile(new QLockFile(lockFileName)); | ||
lockFile->setStaleLockTime(0); | ||
if (!lockFile->tryLock()) { | ||
// for now silently ignore if we can't create a lock file | ||
// due to lack of permissions | ||
if (lockFile->error() != QLockFile::PermissionError) { | ||
QMessageBox::StandardButton result = MessageBox::question(this, tr("Save database as"), | ||
tr("The database you are trying to save as is locked by another instance of KeePassXC.\n" | ||
"Do you want to save it anyway?"), | ||
QMessageBox::Yes | QMessageBox::No); | ||
|
||
if (result == QMessageBox::No) { | ||
return false; | ||
} else { | ||
// take over the lock file if possible | ||
if (lockFile->removeStaleLockFile()) { | ||
lockFile->tryLock(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
// setup variables so saveDatabase succeeds | ||
dbStruct.saveToFilename = true; | ||
dbStruct.canonicalFilePath = fileName; | ||
QString newFilePath = fileDialog()->getSaveFileName(this, tr("Save database as"), oldFilePath, | ||
tr("KeePass 2 Database").append(" (*.kdbx)"), | ||
nullptr, 0, "kdbx"); | ||
if (!newFilePath.isEmpty()) { | ||
// Ensure we don't recurse back into this function | ||
dbStruct.readOnly = false; | ||
|
||
if (!saveDatabase(db)) { | ||
// failed to save, revert back | ||
dbStruct.saveToFilename = false; | ||
dbStruct.canonicalFilePath = oldFileName; | ||
if (!saveDatabase(db, newFilePath)) { | ||
// Failed to save, try again | ||
continue; | ||
} | ||
|
||
// refresh fileinfo since the file didn't exist before | ||
fileInfo.refresh(); | ||
|
||
dbStruct.modified = false; | ||
dbStruct.saveToFilename = true; | ||
dbStruct.readOnly = false; | ||
dbStruct.filePath = fileInfo.absoluteFilePath(); | ||
dbStruct.canonicalFilePath = fileInfo.canonicalFilePath(); | ||
dbStruct.fileName = fileInfo.fileName(); | ||
dbStruct.dbWidget->updateFilename(dbStruct.filePath); | ||
delete dbStruct.lockFile; | ||
dbStruct.lockFile = lockFile.take(); | ||
updateTabName(db); | ||
updateLastDatabases(dbStruct.filePath); | ||
dbStruct.dbWidget->updateFilePath(dbStruct.fileInfo.absoluteFilePath()); | ||
updateLastDatabases(dbStruct.fileInfo.absoluteFilePath()); | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
|
||
// Canceled file selection | ||
return false; | ||
} | ||
} | ||
|
||
|
@@ -548,7 +465,7 @@ bool DatabaseTabWidget::canSave(int index) | |
} | ||
|
||
const DatabaseManagerStruct& dbStruct = indexDatabaseManagerStruct(index); | ||
return !dbStruct.saveToFilename || (dbStruct.modified && !dbStruct.readOnly); | ||
return dbStruct.modified && !dbStruct.readOnly; | ||
} | ||
|
||
bool DatabaseTabWidget::isModified(int index) | ||
|
@@ -566,7 +483,7 @@ QString DatabaseTabWidget::databasePath(int index) | |
index = currentIndex(); | ||
} | ||
|
||
return indexDatabaseManagerStruct(index).filePath; | ||
return indexDatabaseManagerStruct(index).fileInfo.absoluteFilePath(); | ||
} | ||
|
||
|
||
|
@@ -579,21 +496,18 @@ void DatabaseTabWidget::updateTabName(Database* db) | |
|
||
QString tabName; | ||
|
||
if (dbStruct.saveToFilename || dbStruct.readOnly) { | ||
if (dbStruct.fileInfo.exists()) { | ||
if (db->metadata()->name().isEmpty()) { | ||
tabName = dbStruct.fileName; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @droidmonkey was there a possibility that the database was There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure what you mean by this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @droidmonkey nevermind, I was thinking about a possible execution path that might have occurred with the previous code. |
||
} | ||
else { | ||
tabName = dbStruct.fileInfo.fileName(); | ||
} else { | ||
tabName = db->metadata()->name(); | ||
} | ||
|
||
setTabToolTip(index, dbStruct.filePath); | ||
} | ||
else { | ||
setTabToolTip(index, dbStruct.fileInfo.absoluteFilePath()); | ||
} else { | ||
if (db->metadata()->name().isEmpty()) { | ||
tabName = tr("New database"); | ||
} | ||
else { | ||
} else { | ||
tabName = QString("%1 [%2]").arg(db->metadata()->name(), tr("New database")); | ||
} | ||
} | ||
|
@@ -629,8 +543,7 @@ void DatabaseTabWidget::updateTabNameFromDbWidgetSender() | |
Group *autoload = db->rootGroup()->findChildByName("AutoOpen"); | ||
if (autoload) { | ||
const DatabaseManagerStruct& dbStruct = m_dbList.value(db); | ||
QFileInfo dbpath(dbStruct.canonicalFilePath); | ||
QDir dbFolder(dbpath.canonicalPath()); | ||
QDir dbFolder(dbStruct.fileInfo.canonicalPath()); | ||
for (auto entry : autoload->entries()) { | ||
if (entry->url().isEmpty() || entry->password().isEmpty()) { | ||
continue; | ||
|
@@ -756,17 +669,14 @@ void DatabaseTabWidget::lockDatabases() | |
DatabaseWidget* dbWidget = static_cast<DatabaseWidget*>(widget(i)); | ||
Database* db = databaseFromDatabaseWidget(dbWidget); | ||
|
||
DatabaseWidget::Mode mode = dbWidget->currentMode(); | ||
|
||
if ((mode != DatabaseWidget::ViewMode && mode != DatabaseWidget::EditMode) | ||
|| !dbWidget->dbHasKey()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @droidmonkey shouldn't we keep the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added back in, good idea. |
||
if (dbWidget->currentMode() == DatabaseWidget::LockedMode || !dbWidget->dbHasKey()) { | ||
continue; | ||
} | ||
|
||
// show the correct tab widget before we are asking questions about it | ||
setCurrentWidget(dbWidget); | ||
|
||
if (mode == DatabaseWidget::EditMode && dbWidget->isEditWidgetModified()) { | ||
if (dbWidget->currentMode() == DatabaseWidget::EditMode && dbWidget->isEditWidgetModified()) { | ||
QMessageBox::StandardButton result = | ||
MessageBox::question( | ||
this, tr("Lock database"), | ||
|
@@ -777,23 +687,7 @@ void DatabaseTabWidget::lockDatabases() | |
} | ||
} | ||
|
||
|
||
if (m_dbList[db].modified && !m_dbList[db].saveToFilename) { | ||
QMessageBox::StandardButton result = | ||
MessageBox::question( | ||
this, tr("Lock database"), | ||
tr("This database has never been saved.\nYou can save the database or stop locking it."), | ||
QMessageBox::Save | QMessageBox::Cancel, QMessageBox::Cancel); | ||
if (result == QMessageBox::Save) { | ||
if (!saveDatabase(db)) { | ||
continue; | ||
} | ||
} | ||
else if (result == QMessageBox::Cancel) { | ||
continue; | ||
} | ||
} | ||
else if (m_dbList[db].modified) { | ||
if (m_dbList[db].modified) { | ||
QMessageBox::StandardButton result = | ||
MessageBox::question( | ||
this, tr("Lock database"), | ||
|
@@ -828,7 +722,7 @@ void DatabaseTabWidget::modified() | |
Database* db = static_cast<Database*>(sender()); | ||
DatabaseManagerStruct& dbStruct = m_dbList[db]; | ||
|
||
if (config()->get("AutoSaveAfterEveryChange").toBool() && dbStruct.saveToFilename) { | ||
if (config()->get("AutoSaveAfterEveryChange").toBool() && !dbStruct.readOnly) { | ||
saveDatabase(db); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would like an assertion here for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I put one inside There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps you should leave both. The assertion will ensure we notice in debug mode, but the safeguard condition will ensure it never happens in production either. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking that too. Will do! |
||
return; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This
if
won't be reached if true since the above assert will abort the execution. Better remove one of themEDIT: Nevermind, the assert will only be executed in debug mode.