Compare commits
	
		
			4 commits
		
	
	
		
			3e6a08231c
			...
			cc9453997e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cc9453997e | |||
| 3db384e7e4 | |||
| f778afd268 | |||
| 0caf0efeef | 
					 6 changed files with 74 additions and 6 deletions
				
			
		
							
								
								
									
										9
									
								
								CHANGELOG.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								CHANGELOG.md
									
										
									
									
									
										Normal 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. | ||||
|  | @ -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) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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,)) | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue