go-toast

Send toast notifications in Windows
Log | Files | Refs | README | LICENSE

commit 24528066e08feab41da3d31468079ab29778add9
parent a1588bc9486857299402a168e245056e02d87b6b
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date:   Tue, 21 Mar 2023 14:58:25 +0800

bind: allow for overlapping usecases

This code allows SetAppData to be called once, globally,
or per notification.

If we set it globally, and the per-notification call provides
an empty struct, we early out - since the data is already saved.
This is instead of zeroing out the values in the registry.

This way the caller can decide to provide app data through the
use of the Notification struct or through the global SetAppData
func.

In particular this should allow consumers to set the Windows
metadata when using cross platform wrappers that don't expose
it.

Signed-off-by: Jack Mordaunt <jackmordaunt.dev@gmail.com>

Diffstat:
Ainternal/bind/bind_test.go | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minternal/bind/registry.go | 42+++++++++++++++++++++++++++++++++++++++++-
2 files changed, 119 insertions(+), 1 deletion(-)

diff --git a/internal/bind/bind_test.go b/internal/bind/bind_test.go @@ -0,0 +1,78 @@ +package bind + +import ( + "fmt" + "testing" +) + +// TestSetAppData ensures correct control flow. +func TestSetAppData(t *testing.T) { + + t.Run("set data", func(t *testing.T) { + var didEarlyOut = true + + setAppData = func(data AppData) error { + didEarlyOut = false + return nil + } + + appData = AppData{} + + input := AppData{ID: "test-id"} + + if err := SetAppData(input); err != nil { + t.Fatalf("error: %v", err) + } + + if appData != input { + t.Fatalf("want=%v, got %v", input, appData) + } + + if didEarlyOut { + t.Fatalf("expected to manipulate registry, instead early out") + } + }) + + t.Run("avoid setting empty data", func(t *testing.T) { + var didEarlyOut = true + + setAppData = func(data AppData) error { + didEarlyOut = false + return nil + } + + appData = AppData{ID: "test-id"} + + input := AppData{} + + if err := SetAppData(input); err != nil { + t.Fatalf("error: %v", err) + } + + if appData == input { + t.Fatalf("want=%v, got %v", appData, input) + } + + if !didEarlyOut { + t.Fatal("expected early out, instead registry was manipulated") + } + }) + + t.Run("cancel on error", func(t *testing.T) { + setAppData = func(data AppData) error { + return fmt.Errorf("fake error") + } + + appData = AppData{} + + input := AppData{ID: "test-id"} + + if err := SetAppData(input); err == nil { + t.Fatalf("expected error, got nil") + } + + if appData == input { + t.Fatalf("want=%v, got %v", appData, input) + } + }) +} diff --git a/internal/bind/registry.go b/internal/bind/registry.go @@ -7,10 +7,20 @@ import ( "errors" "fmt" "path/filepath" + "sync" "golang.org/x/sys/windows/registry" ) +var ( + // allows diffing the new call from the previous so that we can early-out, + // and avoid touching the registry more than necessary. + // It also allows empty app data to be supplied to the Notifcation type, + // without erasing the data that has been set via the global function. + appData AppData + appDataMu sync.Mutex +) + // AppData describes the application to the Windows Runtime. type AppData struct { ID string @@ -21,7 +31,37 @@ type AppData struct { // SetAppData teaches the Windows Runtime about our application and establishes the activation GUID // so Windows will know how to invoke us back. -func SetAppData(data AppData) error { +func SetAppData(data AppData) (err error) { + appDataMu.Lock() + defer appDataMu.Unlock() + + // Early out if we have already set this data. + // + // In the case the data is empty, we don't want to overrite + // all of the registry entries to empty. + // + // This allows the caller to either globally set the app data + // or provide it per notification. + if appData == data || data == (AppData{}) { + return nil + } + + // Keep a copy of the saved data for later. + defer func() { + if err == nil { + appData = data + } + }() + + if err := setAppData(data); err != nil { + return err + } + + return nil +} + +// Override for testing. +var setAppData = func(data AppData) error { if data.ID == "" { return fmt.Errorf("empty app ID") }