Skip to content

Commit 7151831

Browse files
authored
Merge pull request #111 from Brain-Modulation-Lab/releasing-fix
Releasing fix
2 parents f4c5ca5 + bd7882f commit 7151831

9 files changed

Lines changed: 564 additions & 59 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- In-app **Update now** on the update-available dialog: downloads and runs the
13+
platform install scripts (``install.ps1`` on Windows, ``install.sh`` on macOS /
14+
Linux) for the selected GitHub release tag, then prompts you to restart the
15+
app from the Start menu or applications launcher.
16+
- **View release notes** on the same dialog opens full release notes (and a
17+
GitHub link) in a separate window.
18+
- ``dbs_annotator.utils.auto_update`` with optional ``dry_run=True`` (PowerShell
19+
``-WhatIf`` / ``install.sh --dry-run``) for maintainers to preview an install.
1220
- Read the Docs screenshot pipeline: home screen (light and dark theme), clinical
1321
and session scales settings dialogs, and regenerated workflow captures at
1422
native resolution (HiDPI-aware screen grab, improved PNG settings).
@@ -24,7 +32,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2432

2533
- **Help** dialog rewritten around the standard **Complete Workflow** programming
2634
pipeline, timestamped ``task-programming`` TSV output, and Word/PDF reports;
35+
notes that each stimulation configuration is saved and summarised in reports;
2736
copyright lists MGH, Wyss Center, and Charité; MIT license noted.
37+
- Update-available dialog: removed the extra “don't notify automatically”
38+
checkbox (that preference stays under **Help** only); release notes are no
39+
longer inlined—use **View release notes**; **Open download page** removed in
40+
favour of **Update now**.
41+
- Pre-release update notice: “If you encounter issues, please report them to …”.
2842
- Annotations-only file step header title: **Clinical Annotations Setup** (was
2943
"Output File").
3044
- Home screen buttons: **Complete Workflow** and **Annotations-only Workflow**.

docs/index.rst

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,26 @@ DBS Annotator
88
:align: center
99
:width: 180px
1010

11-
|
12-
1311
**DBS Annotator** is a desktop application for recording and analysing
14-
Deep Brain Stimulation (DBS) clinical programming sessions. It guides the
15-
clinician or researcher through the **Complete Workflow** — from initial
16-
electrode configuration and baseline scales, through real-time stimulation
17-
adjustments, to the automatic generation of structured Word and PDF reports.
12+
Deep Brain Stimulation (DBS) clinical programming sessions. It guides
13+
the clinician or researcher through a DBS programming pipeline: initial
14+
electrode configuration, clinical scales, and general annotations;
15+
real-time stimulation adjustments; and session-specific scale changes and
16+
notes. Finally, it can generate structured Word and PDF reports from the
17+
programming session data.
1818

19-
Developed at the **Brain Modulation Lab, Massachusetts General Hospital** (Boston, USA),
20-
the **Wyss Center for Bio and Neuroengineering** (Geneva, Switzerland), and
21-
**Charité Universitätsmedizin Berlin** (Germany).
19+
Developed at the **Brain Modulation Lab, Massachusetts General Hospital**
20+
(Boston, USA), the **Wyss Center for Bio and Neuroengineering** (Geneva,
21+
Switzerland), and **Charité Universitätsmedizin Berlin** (Germany).
2222

2323
.. note::
24-
| Version |release|.
25-
| Copyright © Massachusetts General Hospital, Wyss Center for Bio and Neuroengineering, and Charité Universitätsmedizin Berlin.
26-
| Contact: lucia.poma@wysscenter.ch
24+
25+
**Version:** |release|
26+
27+
Copyright © Massachusetts General Hospital, Wyss Center for Bio and
28+
Neuroengineering, and Charité Universitätsmedizin Berlin.
29+
30+
**Contact:** lucia.poma@wysscenter.ch
2731

2832
----
2933

@@ -68,17 +72,19 @@ Quick Overview
6872
:header-rows: 0
6973

7074
* - **Complete Workflow**
71-
- Record stimulation parameters, clinical scales, and notes step-by-step.
72-
Export a structured report (Word / PDF) with tables, electrode diagrams,
73-
and session-scale timeline charts.
75+
- Record stimulation parameters, clinical scales, and notes
76+
step-by-step in a timestamped TSV table. Export a structured report
77+
(Word / PDF) with tables, electrode diagrams, and session-scale
78+
timeline charts.
7479
* - **Annotations-only Workflow**
75-
- Quick timestamped text notes without the full stimulation workflow.
76-
* - **Longitudinal report**
77-
- Combine multiple session files into a single comparative document with
78-
overview tables, clinical and session-scale charts, electrode diagrams,
79-
and programming summaries.
80+
- Quick timestamped text notes.
81+
* - **Session and longitudinal reports**
82+
- Combine single or multiple session files into a single comparative
83+
document with overview tables, clinical and session-scale charts,
84+
electrode diagrams, and programming summaries.
8085
* - **BIDS-compliant output**
81-
- Data saved as ``sub-XX_ses-YYYYMMDD_task-programming_run-XX_events.tsv``.
86+
- Data saved as
87+
``sub-XXXX_ses-YYYYMMDD_task-<TASK>_run-XX_<type-of-data>.<ext>``.
8288
* - **Self-contained desktop app**
83-
- Packaged installers (``.msi``, ``.dmg``, ``.deb``); no separate Python
84-
runtime required.
89+
- Packaged installers (``.msi``, ``.dmg``, ``.deb``); no separate
90+
Python runtime required.

src/dbs_annotator/config.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,27 @@
3939
# Canonical upstream (releases + issue tracker; keep aligned with updater repo slug).
4040
APP_REPOSITORY_URL = "https://github.com/Brain-Modulation-Lab/DBSAnnotator"
4141
APP_ISSUES_URL = f"{APP_REPOSITORY_URL}/issues"
42+
43+
44+
def github_repository_slug(repository_url: str) -> str:
45+
"""Return ``owner/repo`` from a ``https://github.com/owner/repo`` URL."""
46+
prefix = "https://github.com/"
47+
if not repository_url.startswith(prefix):
48+
raise ValueError(
49+
f"Expected a GitHub repository URL starting with {prefix!r}, "
50+
f"got {repository_url!r}"
51+
)
52+
rest = repository_url.removeprefix(prefix).strip("/")
53+
owner, sep, repo = rest.partition("/")
54+
if not sep or not owner or not repo:
55+
raise ValueError(
56+
f"Could not parse owner/repo from GitHub URL {repository_url!r}"
57+
)
58+
return f"{owner}/{repo.split('/')[0]}"
59+
60+
61+
# GitHub Releases API slug for :mod:`dbs_annotator.utils.updater`.
62+
RELEASES_GITHUB_REPO = github_repository_slug(APP_REPOSITORY_URL)
4263
# Primary contact for feedback (same person as APP_LEAD_AUTHOR).
4364
UPDATE_FEEDBACK_EMAIL = "lucia.poma@wysscenter.ch"
4465

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
"""Launch platform install scripts to upgrade a packaged DBS Annotator build."""
2+
3+
from __future__ import annotations
4+
5+
import logging
6+
import os
7+
import ssl
8+
import subprocess
9+
import sys
10+
import tempfile
11+
import urllib.error
12+
import urllib.request
13+
from pathlib import Path
14+
15+
import certifi
16+
17+
from ..config import RELEASES_GITHUB_REPO
18+
19+
logger = logging.getLogger(__name__)
20+
21+
_INSTALL_SCRIPT_BRANCH = "main"
22+
_USER_AGENT = "DBSAnnotator-AutoUpdate/1.0"
23+
# Windows-only; absent on Linux/macOS (CI runs unit tests there too).
24+
_WINDOWS_NEW_CONSOLE = getattr(subprocess, "CREATE_NEW_CONSOLE", 0)
25+
26+
27+
def _install_script_url(filename: str) -> str:
28+
return (
29+
f"https://raw.githubusercontent.com/{RELEASES_GITHUB_REPO}/"
30+
f"{_INSTALL_SCRIPT_BRANCH}/scripts/{filename}"
31+
)
32+
33+
34+
def _download_install_script(filename: str) -> Path:
35+
url = _install_script_url(filename)
36+
request = urllib.request.Request(
37+
url,
38+
headers={"User-Agent": _USER_AGENT},
39+
)
40+
ctx = ssl.create_default_context(cafile=certifi.where())
41+
with urllib.request.urlopen(request, timeout=60, context=ctx) as response:
42+
data = response.read()
43+
suffix = ".ps1" if filename.endswith(".ps1") else ".sh"
44+
fd, path_str = tempfile.mkstemp(prefix="dbs_annotator_install_", suffix=suffix)
45+
os.close(fd)
46+
path = Path(path_str)
47+
path.write_bytes(data)
48+
if suffix == ".sh":
49+
path.chmod(0o755)
50+
return path
51+
52+
53+
def automatic_update_supported() -> bool:
54+
"""True on platforms where ``scripts/install.*`` can be launched."""
55+
return (
56+
sys.platform == "win32"
57+
or sys.platform == "darwin"
58+
or sys.platform.startswith("linux")
59+
)
60+
61+
62+
def automatic_update_targets_packaged_install() -> bool:
63+
"""True when the updater installs into the standard Briefcase location."""
64+
return bool(getattr(sys, "frozen", False))
65+
66+
67+
def launch_automatic_update(
68+
tag_name: str,
69+
*,
70+
dry_run: bool = False,
71+
) -> tuple[bool, str]:
72+
"""Start the GitHub release installer for *tag_name* (e.g. ``v0.4.0b2``).
73+
74+
Args:
75+
dry_run: If True, run the platform install script in preview mode only
76+
(PowerShell ``-WhatIf`` / ``install.sh --dry-run``). No files are
77+
written.
78+
79+
Returns:
80+
``(True, user_message)`` on success, ``(False, error_message)`` otherwise.
81+
The installer runs in a separate process; the user must restart the app.
82+
"""
83+
tag = tag_name.strip()
84+
if not tag:
85+
return False, "Release tag is missing."
86+
87+
try:
88+
if sys.platform == "win32":
89+
return _launch_windows(tag, dry_run=dry_run)
90+
if sys.platform == "darwin":
91+
return _launch_unix(tag, "install.sh", dry_run=dry_run)
92+
if sys.platform.startswith("linux"):
93+
return _launch_unix(tag, "install.sh", dry_run=dry_run)
94+
return False, f"Automatic update is not supported on {sys.platform!r}."
95+
except OSError as exc:
96+
logger.info("Automatic update failed: %s", exc)
97+
return False, str(exc)
98+
except urllib.error.URLError as exc:
99+
logger.info("Could not download install script: %s", exc)
100+
return False, f"Could not download the installer script:\n\n{exc}"
101+
102+
103+
def _launch_windows(tag: str, *, dry_run: bool = False) -> tuple[bool, str]:
104+
script = _download_install_script("install.ps1")
105+
try:
106+
cmd = [
107+
"powershell.exe",
108+
"-NoProfile",
109+
"-ExecutionPolicy",
110+
"Bypass",
111+
"-File",
112+
str(script),
113+
"-VersionTag",
114+
tag,
115+
"-GitHubRepository",
116+
RELEASES_GITHUB_REPO,
117+
]
118+
if dry_run:
119+
cmd.append("-WhatIf")
120+
subprocess.Popen(
121+
cmd,
122+
creationflags=_WINDOWS_NEW_CONSOLE,
123+
close_fds=True,
124+
)
125+
finally:
126+
if not dry_run:
127+
try:
128+
script.unlink(missing_ok=True)
129+
except OSError:
130+
pass
131+
132+
if dry_run:
133+
return True, (
134+
"Dry run: a PowerShell window will show what the installer would do "
135+
"(no files are changed). Check that window for errors before running "
136+
"a real update."
137+
)
138+
return True, (
139+
"The updater is running in a new window. When it finishes, close this "
140+
"application and open DBS Annotator again from the Start menu."
141+
)
142+
143+
144+
def _launch_unix(tag: str, filename: str, *, dry_run: bool = False) -> tuple[bool, str]:
145+
script = _download_install_script(filename)
146+
args = "--dry-run" if dry_run else ""
147+
wrapper = script.with_name(f"run_{script.name}")
148+
wrapper.write_text(
149+
"#!/bin/sh\n"
150+
f'export DBS_ANNOTATOR_INSTALL_REPO="{RELEASES_GITHUB_REPO}"\n'
151+
f'export DBS_ANNOTATOR_VERSION="{tag}"\n'
152+
f'exec "{script}" {args} "{tag}"\n',
153+
encoding="utf-8",
154+
)
155+
wrapper.chmod(0o755)
156+
157+
if sys.platform == "darwin":
158+
cmd = [
159+
"osascript",
160+
"-e",
161+
f'tell application "Terminal" to do script "{wrapper}"',
162+
]
163+
subprocess.Popen(cmd, start_new_session=True, close_fds=True)
164+
else:
165+
launched = False
166+
for term_cmd in (
167+
["x-terminal-emulator", "-e", str(wrapper)],
168+
["konsole", "-e", str(wrapper)],
169+
["gnome-terminal", "--", str(wrapper)],
170+
):
171+
if _which(term_cmd[0]):
172+
subprocess.Popen(
173+
term_cmd,
174+
start_new_session=True,
175+
close_fds=True,
176+
)
177+
launched = True
178+
break
179+
if not launched:
180+
env = os.environ.copy()
181+
env["DBS_ANNOTATOR_INSTALL_REPO"] = RELEASES_GITHUB_REPO
182+
env["DBS_ANNOTATOR_VERSION"] = tag
183+
cmd = [str(script)]
184+
if dry_run:
185+
cmd.append("--dry-run")
186+
cmd.append(tag)
187+
subprocess.Popen(
188+
cmd,
189+
env=env,
190+
start_new_session=True,
191+
close_fds=True,
192+
)
193+
194+
if dry_run:
195+
return True, (
196+
"Dry run: a terminal window will show what the installer would do "
197+
"(no files are changed). Check that window for errors before running "
198+
"a real update."
199+
)
200+
return True, (
201+
"The updater is running. When it finishes, quit this application and "
202+
"reopen DBS Annotator from your applications menu."
203+
)
204+
205+
206+
def _which(name: str) -> str | None:
207+
from shutil import which
208+
209+
return which(name)

0 commit comments

Comments
 (0)