Release version 0.2.4

Prevent repeated application launches by using a local single-instance control channel. A second process forwards a show command to the already running instance and exits.

Bump the application version to 0.2.4 and update README artifact examples plus docs/CHANGELOG.md.
This commit is contained in:
mixeme
2026-06-16 08:01:42 +03:00
parent e8e0060063
commit c1bd8c952c
4 changed files with 67 additions and 8 deletions
+7 -7
View File
@@ -86,7 +86,7 @@ The binary is written to:
```text
# GUI executable produced by scripts\build-windows.bat.
dist\windows\pysentry-0.2.3-windows-amd64.exe
dist\windows\pysentry-0.2.4-windows-amd64.exe
```
Linux:
@@ -101,7 +101,7 @@ The binary is written to:
```text
# Linux executable produced by scripts/build-linux.sh.
dist/linux/pysentry-0.2.3-linux-amd64
dist/linux/pysentry-0.2.4-linux-amd64
```
Linux using Docker:
@@ -118,7 +118,7 @@ The binary is copied to:
```text
# Linux executable copied out of the Docker build image.
dist\linux\pysentry-0.2.3-linux-amd64
dist\linux\pysentry-0.2.4-linux-amd64
```
Release build from Linux:
@@ -143,13 +143,13 @@ The binaries are copied to:
```text
# Linux artifact.
dist/linux/pysentry-0.2.3-linux-amd64
dist/linux/pysentry-0.2.4-linux-amd64
# Linux arm64 artifact.
dist/linux/pysentry-0.2.3-linux-arm64
dist/linux/pysentry-0.2.4-linux-arm64
# Windows artifact cross-compiled from Linux.
dist/windows/pysentry-0.2.3-windows-amd64.exe
dist/windows/pysentry-0.2.4-windows-amd64.exe
```
## Run From Source
@@ -292,7 +292,7 @@ Linux:
[Desktop Entry]
Type=Application
Name=PySentry
Exec=/opt/pysentry/pysentry-0.2.3-linux-amd64
Exec=/opt/pysentry/pysentry-0.2.4-linux-amd64
Terminal=false
```
+5
View File
@@ -2,6 +2,11 @@
All notable PySentry changes are recorded in this file.
## 0.2.4 - 2026-06-16
- Prevented repeated application launches by forwarding a second start attempt to the already running instance.
- A second instance now asks the first instance to show and focus the existing window, then exits.
## 0.2.3 - 2026-06-15
- Changed History to use chronological ordering with new records appended at the bottom.
+1 -1
View File
@@ -3,4 +3,4 @@ package core
// Version is the application version shown in the GUI and used by build
// scripts in artifact names. It is a var rather than a const so release builds
// can override it with Go ldflags when CI tags a build.
var Version = "0.2.3"
var Version = "0.2.4"
+54
View File
@@ -2,6 +2,8 @@ package gui
import (
"fmt"
"io"
"net"
"net/url"
"runtime"
"runtime/debug"
@@ -31,6 +33,8 @@ const settingsLabelWidth float32 = 140
const settingsControlWidth float32 = 330
const settingsStatusWidth float32 = 280
const projectRepositoryURL = "https://gitea.mixdep.ru/mix/gosentry"
const singleInstanceAddress = "127.0.0.1:37653"
const singleInstanceShowCommand = "show"
// The GUI package aliases core types to keep widget callbacks short. The actual
// durable model still lives in src/core, so GUI code does not define a second
@@ -40,6 +44,14 @@ type event = core.RunRecord
func Run() {
started := time.Now()
instanceListener, primary := acquireSingleInstance()
if !primary {
return
}
if instanceListener != nil {
defer instanceListener.Close()
}
// A stable app ID lets Fyne persist desktop preferences consistently across
// launches and gives tray/window integration a predictable identity.
a := app.NewWithID(appID)
@@ -49,6 +61,7 @@ func Run() {
configureSystemTray(a, w)
w.Resize(fyne.NewSize(1120, 720))
w.SetContent(newMainView(w, started))
serveSingleInstance(instanceListener, w)
w.ShowAndRun()
}
@@ -83,6 +96,47 @@ func configureSystemTray(a fyne.App, w fyne.Window) {
})
}
func acquireSingleInstance() (net.Listener, bool) {
listener, err := net.Listen("tcp", singleInstanceAddress)
if err == nil {
return listener, true
}
connection, dialErr := net.DialTimeout("tcp", singleInstanceAddress, time.Second)
if dialErr == nil {
_, _ = io.WriteString(connection, singleInstanceShowCommand)
_ = connection.Close()
return nil, false
}
// If the port is unavailable but does not answer as GoSentry, continue
// startup instead of making the application impossible to open because of an
// unrelated local listener. In the normal duplicate-start case the dial above
// succeeds and this process exits after waking the first instance.
return nil, true
}
func serveSingleInstance(listener net.Listener, w fyne.Window) {
if listener == nil {
return
}
go func() {
for {
connection, err := listener.Accept()
if err != nil {
return
}
command, _ := io.ReadAll(io.LimitReader(connection, 32))
_ = connection.Close()
if strings.TrimSpace(string(command)) != singleInstanceShowCommand {
continue
}
w.Show()
w.RequestFocus()
}
}()
}
func newMainView(w fyne.Window, started time.Time) fyne.CanvasObject {
store, jobs, err := core.OpenStore()
if err != nil {