Compare commits

...

4 commits

6 changed files with 74 additions and 6 deletions

9
CHANGELOG.md Normal file
View file

@ -0,0 +1,9 @@
# 0.1.1
* Add ability to change the key
* Add ability to jump to today's date
* Add shortcut for Settings (Ctrl+E) so as not to collide with Ctrl+S (Save)
# 0.1.0
* Initial release.

View file

@ -31,7 +31,6 @@ There is deliberately no network connectivity or syncing intended.
* Search
* Taxonomy/tagging
* Ability to change the SQLCipher key
* Export to other formats (plaintext, json, sql etc)

View file

@ -64,6 +64,25 @@ class DBManager:
cur.execute("PRAGMA user_version = 1;")
self.conn.commit()
def rekey(self, new_key: str) -> None:
"""
Change the SQLCipher passphrase in-place, then reopen the connection
with the new key to verify.
"""
if self.conn is None:
raise RuntimeError("Database is not connected")
cur = self.conn.cursor()
# Change the encryption key of the currently open database
cur.execute(f"PRAGMA rekey = '{new_key}';")
self.conn.commit()
# Close and reopen with the new key to verify and restore PRAGMAs
self.conn.close()
self.conn = None
self.cfg.key = new_key
if not self.connect():
raise sqlite.Error("Re-open failed after rekey")
def get_entry(self, date_iso: str) -> str:
cur = self.conn.cursor()
cur.execute("SELECT content FROM entries WHERE date = ?;", (date_iso,))

View file

@ -72,7 +72,8 @@ class MainWindow(QMainWindow):
act_save.setShortcut("Ctrl+S")
act_save.triggered.connect(lambda: self._save_current(explicit=True))
file_menu.addAction(act_save)
act_settings = QAction("&Settings", self)
act_settings = QAction("S&ettings", self)
act_save.setShortcut("Ctrl+E")
act_settings.triggered.connect(self._open_settings)
file_menu.addAction(act_settings)
file_menu.addSeparator()
@ -97,6 +98,13 @@ class MainWindow(QMainWindow):
nav_menu.addAction(act_next)
self.addAction(act_next)
act_today = QAction("Today", self)
act_today.setShortcut("Ctrl+T")
act_today.setShortcutContext(Qt.ApplicationShortcut)
act_today.triggered.connect(self._adjust_today)
nav_menu.addAction(act_today)
self.addAction(act_today)
# Autosave
self._dirty = False
self._save_timer = QTimer(self)
@ -176,6 +184,11 @@ class MainWindow(QMainWindow):
d = self.calendar.selectedDate().addDays(delta)
self.calendar.setSelectedDate(d)
def _adjust_today(self):
"""Jump to today."""
today = QDate.currentDate()
self.calendar.setSelectedDate(today)
def _on_date_changed(self):
"""
When the calendar selection changes, save the previous day's note if dirty,
@ -219,7 +232,7 @@ class MainWindow(QMainWindow):
self._save_date(self._current_date_iso(), explicit)
def _open_settings(self):
dlg = SettingsDialog(self.cfg, self)
dlg = SettingsDialog(self.cfg, self.db, self)
if dlg.exec() == QDialog.Accepted:
new_cfg = dlg.config
if new_cfg.path != self.cfg.path:

View file

@ -13,17 +13,20 @@ from PySide6.QtWidgets import (
QFileDialog,
QDialogButtonBox,
QSizePolicy,
QMessageBox,
)
from .db import DBConfig
from .db import DBConfig, DBManager
from .settings import save_db_config
from .key_prompt import KeyPrompt
class SettingsDialog(QDialog):
def __init__(self, cfg: DBConfig, parent=None):
def __init__(self, cfg: DBConfig, db: DBManager, parent=None):
super().__init__(parent)
self.setWindowTitle("Settings")
self._cfg = DBConfig(path=cfg.path, key="")
self._db = db
form = QFormLayout()
form.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
@ -44,12 +47,17 @@ class SettingsDialog(QDialog):
h.setStretch(1, 0)
form.addRow("Database path", path_row)
# Change key button
self.rekey_btn = QPushButton("Change key")
self.rekey_btn.clicked.connect(self._change_key)
bb = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel)
bb.accepted.connect(self._save)
bb.rejected.connect(self.reject)
v = QVBoxLayout(self)
v.addLayout(form)
v.addWidget(self.rekey_btn)
v.addWidget(bb)
def _browse(self):
@ -67,6 +75,26 @@ class SettingsDialog(QDialog):
save_db_config(self._cfg)
self.accept()
def _change_key(self):
p1 = KeyPrompt(self, title="Change key", message="Enter new key")
if p1.exec() != QDialog.Accepted:
return
new_key = p1.key()
p2 = KeyPrompt(self, title="Change key", message="Re-enter new key")
if p2.exec() != QDialog.Accepted:
return
if new_key != p2.key():
QMessageBox.warning(self, "Key mismatch", "The two entries did not match.")
return
if not new_key:
QMessageBox.warning(self, "Empty key", "Key cannot be empty.")
return
try:
self._db.rekey(new_key)
QMessageBox.information(self, "Key changed", "The database key was updated.")
except Exception as e:
QMessageBox.critical(self, "Error", f"Could not change key:\n{e}")
@property
def config(self) -> DBConfig:
return self._cfg

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "bouquin"
version = "0.1.0"
version = "0.1.1"
description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher."
authors = ["Miguel Jacq <mig@mig5.net>"]
readme = "README.md"