bouquin 0.4.3__tar.gz → 0.4.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {bouquin-0.4.3 → bouquin-0.4.4}/PKG-INFO +6 -6
  2. {bouquin-0.4.3 → bouquin-0.4.4}/README.md +5 -5
  3. bouquin-0.4.4/bouquin/keys/mig5.asc +109 -0
  4. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/locales/en.json +15 -1
  5. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/locales/fr.json +1 -1
  6. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/locales/it.json +1 -1
  7. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/main_window.py +112 -41
  8. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/markdown_editor.py +1 -13
  9. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/settings_dialog.py +1 -1
  10. bouquin-0.4.4/bouquin/version_check.py +386 -0
  11. {bouquin-0.4.3 → bouquin-0.4.4}/pyproject.toml +2 -2
  12. {bouquin-0.4.3 → bouquin-0.4.4}/LICENSE +0 -0
  13. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/__init__.py +0 -0
  14. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/__main__.py +0 -0
  15. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/bug_report_dialog.py +0 -0
  16. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/db.py +0 -0
  17. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/find_bar.py +0 -0
  18. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/flow_layout.py +0 -0
  19. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/fonts/NotoSansSymbols2-Regular.ttf +0 -0
  20. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/fonts/OFL.txt +0 -0
  21. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/history_dialog.py +0 -0
  22. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/icons/bouquin-light.svg +0 -0
  23. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/icons/bouquin.svg +0 -0
  24. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/key_prompt.py +0 -0
  25. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/lock_overlay.py +0 -0
  26. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/main.py +0 -0
  27. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/markdown_highlighter.py +0 -0
  28. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/save_dialog.py +0 -0
  29. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/search.py +0 -0
  30. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/settings.py +0 -0
  31. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/statistics_dialog.py +0 -0
  32. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/strings.py +0 -0
  33. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/tag_browser.py +0 -0
  34. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/tags_widget.py +0 -0
  35. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/theme.py +0 -0
  36. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/time_log.py +0 -0
  37. {bouquin-0.4.3 → bouquin-0.4.4}/bouquin/toolbar.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bouquin
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
4
  Summary: Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher.
5
5
  Home-page: https://git.mig5.net/mig5/bouquin
6
6
  License: GPL-3.0-or-later
@@ -39,7 +39,7 @@ To increase security, the SQLCipher key is requested when the app is opened, and
39
39
  to disk unless the user configures it to be in the settings.
40
40
 
41
41
  There is deliberately no network connectivity or syncing intended, other than the option to send a bug
42
- report from within the app.
42
+ report from within the app, or optionally to check for new versions to upgrade to.
43
43
 
44
44
  ## Screenshots
45
45
 
@@ -84,15 +84,15 @@ report from within the app.
84
84
  * Backup the database to encrypted SQLCipher format (which can then be loaded back in to a Bouquin)
85
85
  * Dark and light theme support
86
86
  * Automatically generate checkboxes when typing 'TODO'
87
- * It is possible to automatically move unchecked checkboxes from yesterday to today, on startup
87
+ * It is possible to automatically move unchecked checkboxes from the last 7 days to the next weekday.
88
88
  * English, French and Italian locales provided
89
- * Ability to set reminder alarms in the app against the current line of text on today's date
90
- * Ability to log time per day and run timesheet reports
89
+ * Ability to set reminder alarms in the app against the current line of text (which will be flashed as the reminder)
90
+ * Ability to log time per day for different projects/activities and run timesheet reports
91
91
 
92
92
 
93
93
  ## How to install
94
94
 
95
- Make sure you have `libxcb-cursor0` installed (it may be called something else on non-Debian distributions).
95
+ Make sure you have `libxcb-cursor0` installed (on Debian-based distributions) or `xcb-util-cursor` (RedHat/Fedora-based distributions).
96
96
 
97
97
  It's also recommended that you have Noto Sans fonts installed, but it's up to you. It just can impact the display of unicode symbols such as checkboxes.
98
98
 
@@ -19,7 +19,7 @@ To increase security, the SQLCipher key is requested when the app is opened, and
19
19
  to disk unless the user configures it to be in the settings.
20
20
 
21
21
  There is deliberately no network connectivity or syncing intended, other than the option to send a bug
22
- report from within the app.
22
+ report from within the app, or optionally to check for new versions to upgrade to.
23
23
 
24
24
  ## Screenshots
25
25
 
@@ -64,15 +64,15 @@ report from within the app.
64
64
  * Backup the database to encrypted SQLCipher format (which can then be loaded back in to a Bouquin)
65
65
  * Dark and light theme support
66
66
  * Automatically generate checkboxes when typing 'TODO'
67
- * It is possible to automatically move unchecked checkboxes from yesterday to today, on startup
67
+ * It is possible to automatically move unchecked checkboxes from the last 7 days to the next weekday.
68
68
  * English, French and Italian locales provided
69
- * Ability to set reminder alarms in the app against the current line of text on today's date
70
- * Ability to log time per day and run timesheet reports
69
+ * Ability to set reminder alarms in the app against the current line of text (which will be flashed as the reminder)
70
+ * Ability to log time per day for different projects/activities and run timesheet reports
71
71
 
72
72
 
73
73
  ## How to install
74
74
 
75
- Make sure you have `libxcb-cursor0` installed (it may be called something else on non-Debian distributions).
75
+ Make sure you have `libxcb-cursor0` installed (on Debian-based distributions) or `xcb-util-cursor` (RedHat/Fedora-based distributions).
76
76
 
77
77
  It's also recommended that you have Noto Sans fonts installed, but it's up to you. It just can impact the display of unicode symbols such as checkboxes.
78
78
 
@@ -0,0 +1,109 @@
1
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
2
+
3
+ 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
108
+ =IfFs
109
+ -----END PGP PUBLIC KEY BLOCK-----
@@ -57,6 +57,20 @@
57
57
  "couldnt_open": "Couldn't open",
58
58
  "report_a_bug": "Report a bug",
59
59
  "version": "Version",
60
+ "update": "Update",
61
+ "check_for_updates": "Check for updates",
62
+ "could_not_check_for_updates": "Could not check for updates:\n",
63
+ "update_server_returned_an_empty_version_string": "Update server returned an empty version string",
64
+ "you_are_running_the_latest_version": "You are running the latest version:\n",
65
+ "there_is_a_new_version_available": "There is a new version available:\n",
66
+ "download_the_appimage": "Download the AppImage?",
67
+ "downloading": "Downloading",
68
+ "download_cancelled": "Download cancelled",
69
+ "failed_to_download_update": "Failed to download update:\n",
70
+ "could_not_read_bundled_gpg_public_key": "Could not read bundled GPG public key:\n",
71
+ "could_not_find_gpg_executable": "Could not find the 'gpg' executable to verify the download.",
72
+ "gpg_key_verification_failed": "GPG signature verification failed. The downloaded files have been deleted.\n\n",
73
+ "downloaded_and_verified_new_appimage": "Downloaded and verified new AppImage:\n\n",
60
74
  "navigate": "Navigate",
61
75
  "current": "current",
62
76
  "selected": "selected",
@@ -82,7 +96,7 @@
82
96
  "open_in_new_tab": "Open in new tab",
83
97
  "autosave": "autosave",
84
98
  "unchecked_checkbox_items_moved_to_next_day": "Unchecked checkbox items moved to next day",
85
- "move_yesterdays_unchecked_todos_to_today_on_startup": "Move yesterday's unchecked TODOs to today on startup",
99
+ "move_unchecked_todos_to_today_on_startup": "Automatically move unchecked TODOs from the last 7 days to next weekday",
86
100
  "insert_images": "Insert images",
87
101
  "images": "Images",
88
102
  "reopen_failed": "Re-open failed",
@@ -81,7 +81,7 @@
81
81
  "open_in_new_tab": "Ouvrir dans un nouvel onglet",
82
82
  "autosave": "enregistrement automatique",
83
83
  "unchecked_checkbox_items_moved_to_next_day": "Les cases non cochées ont été reportées au jour suivant",
84
- "move_yesterdays_unchecked_todos_to_today_on_startup": "Au démarrage, déplacer les TODO non cochés d’hier vers aujourd’hui",
84
+ "move_unchecked_todos_to_today_on_startup": "Déplacer automatiquement les TODO non cochés au jour suivant",
85
85
  "insert_images": "Insérer des images",
86
86
  "images": "Images",
87
87
  "reopen_failed": "Échec de la réouverture",
@@ -81,7 +81,7 @@
81
81
  "open_in_new_tab": "Apri in una nuova scheda",
82
82
  "autosave": "salvataggio automatico",
83
83
  "unchecked_checkbox_items_moved_to_next_day": "Le caselle non spuntate sono state spostate al giorno successivo",
84
- "move_yesterdays_unchecked_todos_to_today_on_startup": "Sposta i TODO non completati di ieri a oggi all'avvio",
84
+ "move_unchecked_todos_to_today_on_startup": "Sposta i TODO non completati a oggi all'avvio",
85
85
  "insert_images": "Inserisci immagini",
86
86
  "images": "Immagini",
87
87
  "reopen_failed": "Riapertura fallita",
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import datetime
4
- import importlib.metadata
5
4
  import os
6
5
  import sys
7
6
  import re
@@ -68,6 +67,7 @@ from .tags_widget import PageTagsWidget
68
67
  from .theme import ThemeManager
69
68
  from .time_log import TimeLogWidget
70
69
  from .toolbar import ToolBar
70
+ from .version_check import VersionChecker
71
71
 
72
72
 
73
73
  class MainWindow(QMainWindow):
@@ -77,6 +77,7 @@ class MainWindow(QMainWindow):
77
77
  self.setMinimumSize(1000, 650)
78
78
 
79
79
  self.themes = themes # Store the themes manager
80
+ self.version_checker = VersionChecker(self)
80
81
 
81
82
  self.cfg = load_db_config()
82
83
  if not os.path.exists(self.cfg.path):
@@ -310,7 +311,7 @@ class MainWindow(QMainWindow):
310
311
  self._reminder_timers: list[QTimer] = []
311
312
 
312
313
  # First load + mark dates in calendar with content
313
- if not self._load_yesterday_todos():
314
+ if not self._load_unchecked_todos():
314
315
  self._load_selected_date()
315
316
  self._refresh_calendar_marks()
316
317
 
@@ -333,6 +334,12 @@ class MainWindow(QMainWindow):
333
334
  # Build any alarms for *today* from stored markdown
334
335
  self._rebuild_reminders_for_today()
335
336
 
337
+ # Rollover unchecked todos automatically when the calendar day changes
338
+ self._day_change_timer = QTimer(self)
339
+ self._day_change_timer.setSingleShot(True)
340
+ self._day_change_timer.timeout.connect(self._on_day_changed)
341
+ self._schedule_next_day_change()
342
+
336
343
  @property
337
344
  def editor(self) -> MarkdownEditor | None:
338
345
  """Get the currently active editor."""
@@ -783,46 +790,112 @@ class MainWindow(QMainWindow):
783
790
  today = QDate.currentDate()
784
791
  self._create_new_tab(today)
785
792
 
786
- def _load_yesterday_todos(self):
787
- if not self.cfg.move_todos:
788
- return
789
- yesterday_str = QDate.currentDate().addDays(-1).toString("yyyy-MM-dd")
790
- text = self.db.get_entry(yesterday_str)
791
- unchecked_items = []
792
-
793
- # Split into lines and find unchecked checkbox items
794
- lines = text.split("\n")
795
- remaining_lines = []
796
-
797
- for line in lines:
798
- # Check for unchecked markdown checkboxes: - [ ] or - [☐]
799
- if re.match(r"^\s*-\s*\[\s*\]\s+", line) or re.match(
800
- r"^\s*-\s*\[☐\]\s+", line
801
- ):
802
- # Extract the text after the checkbox
803
- item_text = re.sub(r"^\s*-\s*\[[\s☐]\]\s+", "", line)
804
- unchecked_items.append(f"- [ ] {item_text}")
805
- else:
806
- # Keep all other lines
807
- remaining_lines.append(line)
808
-
809
- # Save modified content back if we moved items
810
- if unchecked_items:
811
- modified_text = "\n".join(remaining_lines)
812
- self.db.save_new_version(
813
- yesterday_str,
814
- modified_text,
815
- strings._("unchecked_checkbox_items_moved_to_next_day"),
816
- )
793
+ def _rollover_target_date(self, day: QDate) -> QDate:
794
+ """
795
+ Given a 'new day' (system date), return the date we should move
796
+ unfinished todos *to*.
817
797
 
818
- # Join unchecked items into markdown format
819
- unchecked_str = "\n".join(unchecked_items) + "\n"
798
+ If the new day is Saturday or Sunday, we skip ahead to the next Monday.
799
+ Otherwise we just return the same day.
800
+ """
801
+ # Qt: Monday=1 ... Sunday=7
802
+ dow = day.dayOfWeek()
803
+ if dow >= 6: # Saturday (6) or Sunday (7)
804
+ return day.addDays(8 - dow) # 6 -> +2, 7 -> +1 (next Monday)
805
+ return day
820
806
 
821
- # Load the unchecked items into the current editor
822
- self._load_selected_date(False, unchecked_str)
823
- else:
807
+ def _schedule_next_day_change(self) -> None:
808
+ """
809
+ Schedule a one-shot timer to fire shortly after the next midnight.
810
+ """
811
+ now = QDateTime.currentDateTime()
812
+ tomorrow = now.date().addDays(1)
813
+ # A couple of minutes after midnight to be safe
814
+ next_run = QDateTime(tomorrow, QTime(0, 2))
815
+ msecs = max(60_000, now.msecsTo(next_run)) # at least 1 minute
816
+ self._day_change_timer.start(msecs)
817
+
818
+ @Slot()
819
+ def _on_day_changed(self) -> None:
820
+ """
821
+ Called when we've crossed into a new calendar day (according to the timer).
822
+ Re-runs the rollover logic and refreshes the UI.
823
+ """
824
+ # Make the calendar show the *real* new day first
825
+ today = QDate.currentDate()
826
+ with QSignalBlocker(self.calendar):
827
+ self.calendar.setSelectedDate(today)
828
+
829
+ # Same logic as on startup
830
+ if not self._load_unchecked_todos():
831
+ self._load_selected_date()
832
+
833
+ self._refresh_calendar_marks()
834
+ self._rebuild_reminders_for_today()
835
+ self._schedule_next_day_change()
836
+
837
+ def _load_unchecked_todos(self, days_back: int = 7) -> bool:
838
+ """
839
+ Move unchecked checkbox items from the last `days_back` days
840
+ into the rollover target date (today, or next Monday if today
841
+ is a weekend).
842
+
843
+ Returns True if any items were moved, False otherwise.
844
+ """
845
+ if not getattr(self.cfg, "move_todos", False):
846
+ return False
847
+
848
+ if not getattr(self, "db", None):
824
849
  return False
825
850
 
851
+ today = QDate.currentDate()
852
+ target_date = self._rollover_target_date(today)
853
+ target_iso = target_date.toString("yyyy-MM-dd")
854
+
855
+ all_unchecked: list[str] = []
856
+ any_moved = False
857
+
858
+ # Look back N days (yesterday = 1, up to `days_back`)
859
+ for delta in range(1, days_back + 1):
860
+ src_date = today.addDays(-delta)
861
+ src_iso = src_date.toString("yyyy-MM-dd")
862
+ text = self.db.get_entry(src_iso)
863
+ if not text:
864
+ continue
865
+
866
+ lines = text.split("\n")
867
+ remaining_lines: list[str] = []
868
+ moved_from_this_day = False
869
+
870
+ for line in lines:
871
+ # Unchecked markdown checkboxes: "- [ ] " or "- [☐] "
872
+ if re.match(r"^\s*-\s*\[\s*\]\s+", line) or re.match(
873
+ r"^\s*-\s*\[☐\]\s+", line
874
+ ):
875
+ item_text = re.sub(r"^\s*-\s*\[[\s☐]\]\s+", "", line)
876
+ all_unchecked.append(f"- [ ] {item_text}")
877
+ moved_from_this_day = True
878
+ any_moved = True
879
+ else:
880
+ remaining_lines.append(line)
881
+
882
+ if moved_from_this_day:
883
+ modified_text = "\n".join(remaining_lines)
884
+ # Save the cleaned-up source day
885
+ self.db.save_new_version(
886
+ src_iso,
887
+ modified_text,
888
+ strings._("unchecked_checkbox_items_moved_to_next_day"),
889
+ )
890
+
891
+ if not any_moved:
892
+ return False
893
+
894
+ # Append everything we collected to the *target* date
895
+ unchecked_str = "\n".join(all_unchecked) + "\n"
896
+ self._load_selected_date(target_iso, unchecked_str)
897
+ return True
898
+
826
899
  def _on_date_changed(self):
827
900
  """
828
901
  When the calendar selection changes, save the previous day's note if dirty,
@@ -1562,9 +1635,7 @@ class MainWindow(QMainWindow):
1562
1635
  dlg.exec()
1563
1636
 
1564
1637
  def _open_version(self):
1565
- version = importlib.metadata.version("bouquin")
1566
- version_formatted = f"{APP_NAME} {version}"
1567
- QMessageBox.information(self, strings._("version"), version_formatted)
1638
+ self.version_checker.show_version_dialog()
1568
1639
 
1569
1640
  # ----------------- Idle handlers ----------------- #
1570
1641
  def _apply_idle_minutes(self, minutes: int):
@@ -551,6 +551,7 @@ class MarkdownEditor(QTextEdit):
551
551
  c.setPosition(new_pos)
552
552
  self.setTextCursor(c)
553
553
  return
554
+
554
555
  # Step out of a code block with Down at EOF
555
556
  if event.key() == Qt.Key.Key_Down:
556
557
  c = self.textCursor()
@@ -758,19 +759,6 @@ class MarkdownEditor(QTextEdit):
758
759
  super().keyPressEvent(event)
759
760
  return
760
761
 
761
- # Auto-insert an extra blank line after headings (#, ##, ###)
762
- # when pressing Enter at the end of the line.
763
- if re.match(r"^#{1,3}\s+", stripped) and pos_in_block >= len(line_text):
764
- cursor.beginEditBlock()
765
- # First blank line: visual separator between heading and body
766
- cursor.insertBlock()
767
- # Second blank line: where body text will start (caret ends here)
768
- cursor.insertBlock()
769
- cursor.endEditBlock()
770
-
771
- self.setTextCursor(cursor)
772
- return
773
-
774
762
  # Check for list continuation
775
763
  list_type, prefix = self._detect_list_type(current_line)
776
764
 
@@ -160,7 +160,7 @@ class SettingsDialog(QDialog):
160
160
  features_layout = QVBoxLayout(features_group)
161
161
 
162
162
  self.move_todos = QCheckBox(
163
- strings._("move_yesterdays_unchecked_todos_to_today_on_startup")
163
+ strings._("move_unchecked_todos_to_today_on_startup")
164
164
  )
165
165
  self.move_todos.setChecked(self.current_settings.move_todos)
166
166
  self.move_todos.setCursor(Qt.PointingHandCursor)
@@ -0,0 +1,386 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib.metadata
4
+ import os
5
+ import re
6
+ import subprocess # nosec
7
+ import tempfile
8
+ from pathlib import Path
9
+
10
+ import requests
11
+ from importlib.resources import files
12
+ from PySide6.QtCore import QStandardPaths, Qt
13
+ from PySide6.QtWidgets import (
14
+ QApplication,
15
+ QMessageBox,
16
+ QWidget,
17
+ QProgressDialog,
18
+ )
19
+
20
+ from .settings import APP_NAME
21
+ from . import strings
22
+
23
+
24
+ # Where to fetch the latest version string from
25
+ VERSION_URL = "https://mig5.net/bouquin/version.txt"
26
+
27
+ # Name of the installed distribution according to pyproject.toml
28
+ # (used with importlib.metadata.version)
29
+ DIST_NAME = "bouquin"
30
+
31
+ # Base URL where AppImages are hosted
32
+ APPIMAGE_BASE_URL = "https://git.mig5.net/mig5/bouquin/releases/download"
33
+
34
+ # Where we expect to find the bundled public key, relative to the *installed* package.
35
+ GPG_PUBKEY_RESOURCE = ("bouquin", "keys", "mig5.asc")
36
+
37
+
38
+ class VersionChecker:
39
+ """
40
+ Handles:
41
+ * showing the version dialog
42
+ * checking for updates
43
+ * downloading & verifying a new AppImage
44
+
45
+ All dialogs use `parent` as their parent widget.
46
+ """
47
+
48
+ def __init__(self, parent: QWidget | None = None):
49
+ self._parent = parent
50
+
51
+ # ---------- Version helpers ---------- #
52
+
53
+ def current_version(self) -> str:
54
+ """
55
+ Return the current app version as reported by importlib.metadata
56
+ """
57
+ try:
58
+ return importlib.metadata.version(DIST_NAME)
59
+ except importlib.metadata.PackageNotFoundError:
60
+ # Fallback for editable installs / dev trees
61
+ return "0.0.0"
62
+
63
+ @staticmethod
64
+ def _parse_version(v: str) -> tuple[int, ...]:
65
+ """
66
+ Very small helper to compare simple semantic versions like 1.2.3.
67
+ Extracts numeric components and returns them as a tuple.
68
+ """
69
+ parts = re.findall(r"\d+", v)
70
+ if not parts:
71
+ return (0,)
72
+ return tuple(int(p) for p in parts)
73
+
74
+ def _is_newer_version(self, available: str, current: str) -> bool:
75
+ """
76
+ True if `available` > `current` according to _parse_version.
77
+ """
78
+ return self._parse_version(available) > self._parse_version(current)
79
+
80
+ # ---------- Public entrypoint for Help → Version ---------- #
81
+
82
+ def show_version_dialog(self) -> None:
83
+ """
84
+ Show the Version dialog with a 'Check for updates' button.
85
+ """
86
+ version = self.current_version()
87
+ version_formatted = f"{APP_NAME} {version}"
88
+
89
+ box = QMessageBox(self._parent)
90
+ box.setIcon(QMessageBox.Information)
91
+ box.setWindowTitle(strings._("version"))
92
+ box.setText(version_formatted)
93
+
94
+ check_button = box.addButton(
95
+ strings._("check_for_updates"), QMessageBox.ActionRole
96
+ )
97
+ box.addButton(QMessageBox.Close)
98
+
99
+ box.exec()
100
+
101
+ if box.clickedButton() is check_button:
102
+ self.check_for_updates()
103
+
104
+ # ---------- Core update logic ---------- #
105
+
106
+ def check_for_updates(self) -> None:
107
+ """
108
+ Fetch VERSION_URL, compare against the current version, and optionally
109
+ download + verify a new AppImage.
110
+ """
111
+ current = self.current_version()
112
+
113
+ try:
114
+ resp = requests.get(VERSION_URL, timeout=10)
115
+ resp.raise_for_status()
116
+ available_raw = resp.text.strip()
117
+ except Exception as e:
118
+ QMessageBox.warning(
119
+ self._parent,
120
+ strings._("update"),
121
+ strings._("could_not_check_for_updates") + e,
122
+ )
123
+ return
124
+
125
+ if not available_raw:
126
+ QMessageBox.warning(
127
+ self._parent,
128
+ strings._("update"),
129
+ strings._("update_server_returned_an_empty_version_string"),
130
+ )
131
+ return
132
+
133
+ if not self._is_newer_version(available_raw, current):
134
+ QMessageBox.information(
135
+ self._parent,
136
+ strings._("update"),
137
+ strings._("you_are_running_the_latest_version") + f"({current}).",
138
+ )
139
+ return
140
+
141
+ # Newer version is available
142
+ reply = QMessageBox.question(
143
+ self._parent,
144
+ strings._("update"),
145
+ (
146
+ strings._("there_is_a_new_version_available")
147
+ + available_raw
148
+ + "\n\n"
149
+ + strings._("download_the_appimage")
150
+ ),
151
+ QMessageBox.Yes | QMessageBox.No,
152
+ )
153
+ if reply != QMessageBox.Yes:
154
+ return
155
+
156
+ self._download_and_verify_appimage(available_raw)
157
+
158
+ # ---------- Download + verification helpers ---------- #
159
+ def _download_file(
160
+ self,
161
+ url: str,
162
+ dest_path: Path,
163
+ timeout: int = 30,
164
+ progress: QProgressDialog | None = None,
165
+ label: str | None = None,
166
+ ) -> None:
167
+ """
168
+ Stream a URL to a local file, optionally updating a QProgressDialog.
169
+ If the user cancels via the dialog, raises RuntimeError.
170
+ """
171
+ resp = requests.get(url, timeout=timeout, stream=True)
172
+ resp.raise_for_status()
173
+
174
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
175
+
176
+ total_bytes: int | None = None
177
+ content_length = resp.headers.get("Content-Length")
178
+ if content_length is not None:
179
+ try:
180
+ total_bytes = int(content_length)
181
+ except ValueError:
182
+ total_bytes = None
183
+
184
+ if progress is not None:
185
+ progress.setLabelText(
186
+ label or strings._("downloading") + f" {dest_path.name}..."
187
+ )
188
+ # Unknown size → busy indicator; known size → real range
189
+ if total_bytes is not None and total_bytes > 0:
190
+ progress.setRange(0, total_bytes)
191
+ else:
192
+ progress.setRange(0, 0) # indeterminate
193
+ progress.setValue(0)
194
+ progress.show()
195
+ QApplication.processEvents()
196
+
197
+ downloaded = 0
198
+ with dest_path.open("wb") as f:
199
+ for chunk in resp.iter_content(chunk_size=8192):
200
+ if not chunk:
201
+ continue
202
+
203
+ f.write(chunk)
204
+ downloaded += len(chunk)
205
+
206
+ if progress is not None:
207
+ if total_bytes is not None and total_bytes > 0:
208
+ progress.setValue(downloaded)
209
+ else:
210
+ # Just bump a little so the dialog looks alive
211
+ progress.setValue(progress.value() + 1)
212
+ QApplication.processEvents()
213
+
214
+ if progress.wasCanceled():
215
+ raise RuntimeError(strings._("download_cancelled"))
216
+
217
+ if progress is not None and total_bytes is not None and total_bytes > 0:
218
+ progress.setValue(total_bytes)
219
+ QApplication.processEvents()
220
+
221
+ def _download_and_verify_appimage(self, version: str) -> None:
222
+ """
223
+ Download the AppImage + its GPG signature to the user's Downloads dir,
224
+ then verify it with a bundled public key.
225
+ """
226
+ # Where to put the file
227
+ download_dir = QStandardPaths.writableLocation(QStandardPaths.DownloadLocation)
228
+ if not download_dir:
229
+ download_dir = os.path.expanduser("~/Downloads")
230
+ download_dir = Path(download_dir)
231
+ download_dir.mkdir(parents=True, exist_ok=True)
232
+
233
+ # Construct AppImage filename and URLs
234
+ appimage_path = download_dir / "Bouquin.AppImage"
235
+ sig_path = Path(str(appimage_path) + ".asc")
236
+
237
+ appimage_url = f"{APPIMAGE_BASE_URL}/{version}/Bouquin.AppImage"
238
+ sig_url = f"{appimage_url}.asc"
239
+
240
+ # Progress dialog covering both downloads
241
+ progress = QProgressDialog(
242
+ "Downloading update...",
243
+ "Cancel",
244
+ 0,
245
+ 100,
246
+ self._parent,
247
+ )
248
+ progress.setWindowTitle(strings._("update"))
249
+ progress.setWindowModality(Qt.WindowModal)
250
+ progress.setAutoClose(False)
251
+ progress.setAutoReset(False)
252
+
253
+ try:
254
+ # AppImage download
255
+ self._download_file(
256
+ appimage_url,
257
+ appimage_path,
258
+ progress=progress,
259
+ label=strings._("downloading") + " Bouquin.AppImage...",
260
+ )
261
+ # Signature download (usually tiny, but we still show it)
262
+ self._download_file(
263
+ sig_url,
264
+ sig_path,
265
+ progress=progress,
266
+ label=strings._("downloading") + " signature...",
267
+ )
268
+ except RuntimeError:
269
+ # User cancelled
270
+ for p in (appimage_path, sig_path):
271
+ try:
272
+ if p.exists():
273
+ p.unlink()
274
+ except OSError:
275
+ pass
276
+
277
+ progress.close()
278
+ QMessageBox.information(
279
+ self._parent,
280
+ strings._("update"),
281
+ strings._("download_cancelled"),
282
+ )
283
+ return
284
+ except Exception as e:
285
+ # Other error
286
+ for p in (appimage_path, sig_path):
287
+ try:
288
+ if p.exists():
289
+ p.unlink()
290
+ except OSError:
291
+ pass
292
+
293
+ progress.close()
294
+ QMessageBox.critical(
295
+ self._parent,
296
+ strings._("update"),
297
+ strings._("failed_to_download_update") + e,
298
+ )
299
+ return
300
+
301
+ progress.close()
302
+
303
+ # Load the bundled public key
304
+ try:
305
+ pkg, *rel = GPG_PUBKEY_RESOURCE
306
+ pubkey_bytes = (files(pkg) / "/".join(rel)).read_bytes()
307
+ except Exception as e:
308
+ QMessageBox.critical(
309
+ self._parent,
310
+ strings._("update"),
311
+ strings._("could_not_read_bundled_gpg_public_key") + e,
312
+ )
313
+ # On failure, delete the downloaded files for safety
314
+ for p in (appimage_path, sig_path):
315
+ try:
316
+ if p.exists():
317
+ p.unlink()
318
+ except OSError:
319
+ pass
320
+ return
321
+
322
+ # Use a temporary GNUPGHOME so we don't touch the user's main keyring
323
+ try:
324
+ with tempfile.TemporaryDirectory() as gnupg_home:
325
+ pubkey_path = Path(gnupg_home) / "pubkey.asc"
326
+ pubkey_path.write_bytes(pubkey_bytes)
327
+
328
+ # Import the key
329
+ subprocess.run(
330
+ ["gpg", "--homedir", gnupg_home, "--import", str(pubkey_path)],
331
+ check=True,
332
+ stdout=subprocess.DEVNULL,
333
+ stderr=subprocess.PIPE,
334
+ ) # nosec
335
+
336
+ # Verify the signature
337
+ subprocess.run(
338
+ [
339
+ "gpg",
340
+ "--homedir",
341
+ gnupg_home,
342
+ "--verify",
343
+ str(sig_path),
344
+ str(appimage_path),
345
+ ],
346
+ check=True,
347
+ stdout=subprocess.DEVNULL,
348
+ stderr=subprocess.PIPE,
349
+ ) # nosec
350
+ except FileNotFoundError:
351
+ # gpg not installed / not on PATH
352
+ for p in (appimage_path, sig_path):
353
+ try:
354
+ if p.exists():
355
+ p.unlink()
356
+ except OSError:
357
+ pass
358
+
359
+ QMessageBox.critical(
360
+ self._parent,
361
+ strings._("update"),
362
+ strings._("could_not_find_gpg_executable"),
363
+ )
364
+ return
365
+ except subprocess.CalledProcessError as e:
366
+ for p in (appimage_path, sig_path):
367
+ try:
368
+ if p.exists():
369
+ p.unlink()
370
+ except OSError:
371
+ pass
372
+
373
+ QMessageBox.critical(
374
+ self._parent,
375
+ strings._("update"),
376
+ strings._("gpg_signature_verification_failed")
377
+ + e.stderr.decode(errors="ignore"),
378
+ )
379
+ return
380
+
381
+ # Success
382
+ QMessageBox.information(
383
+ self._parent,
384
+ strings._("update"),
385
+ strings._("downloaded_and_verified_new_appimage") + appimage_path,
386
+ )
@@ -1,13 +1,13 @@
1
1
  [tool.poetry]
2
2
  name = "bouquin"
3
- version = "0.4.3"
3
+ version = "0.4.4"
4
4
  description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher."
5
5
  authors = ["Miguel Jacq <mig@mig5.net>"]
6
6
  readme = "README.md"
7
7
  license = "GPL-3.0-or-later"
8
8
  repository = "https://git.mig5.net/mig5/bouquin"
9
9
  packages = [{ include = "bouquin" }]
10
- include = ["bouquin/locales/*.json"]
10
+ include = ["bouquin/locales/*.json", "bouquin/keys/mig5.asc", "bouquin/fonts/NotoSansSymbols2-Regular.ttf", "bouquin/fonts/OFL.txt"]
11
11
 
12
12
  [tool.poetry.dependencies]
13
13
  python = ">=3.10,<3.14"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes