115 lines
3.2 KiB
Go
115 lines
3.2 KiB
Go
package gio
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"softbox.local/core/application"
|
|
)
|
|
|
|
var ErrIconRequestStale = errors.New("icon request no longer matches catalog")
|
|
|
|
type iconFailureState struct {
|
|
Identity application.IconEventIdentity
|
|
Code application.IconFailureCode
|
|
}
|
|
|
|
// ExpectIcon records the newest request identity on the UI goroutine.
|
|
func (shell *AppShell) ExpectIcon(identity application.IconEventIdentity) error {
|
|
validated, err := application.NewIconEventIdentity(
|
|
identity.RequestID,
|
|
identity.AppID,
|
|
identity.Reference,
|
|
identity.DPI,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
currentReference, exists := shell.iconReferences[validated.AppID]
|
|
if !exists || currentReference != validated.Reference {
|
|
return fmt.Errorf(
|
|
"%w: app=%q reference=%q",
|
|
ErrIconRequestStale,
|
|
validated.AppID,
|
|
validated.Reference,
|
|
)
|
|
}
|
|
if applied, exists := shell.iconApplied[validated.AppID]; !exists || !sameIconResource(applied, validated) {
|
|
delete(shell.icons, validated.AppID)
|
|
delete(shell.iconApplied, validated.AppID)
|
|
}
|
|
shell.iconRequests[validated.AppID] = validated
|
|
delete(shell.iconFailures, validated.AppID)
|
|
return nil
|
|
}
|
|
|
|
// CancelIconRequest invalidates the matching pending request on the UI goroutine.
|
|
func (shell *AppShell) CancelIconRequest(appID, requestID string) bool {
|
|
pending, exists := shell.iconRequests[appID]
|
|
if !exists || pending.RequestID != requestID {
|
|
return false
|
|
}
|
|
delete(shell.iconRequests, appID)
|
|
delete(shell.iconFailures, appID)
|
|
return true
|
|
}
|
|
|
|
// ApplyEvent validates and applies an application event on the UI goroutine.
|
|
func (shell *AppShell) ApplyEvent(event application.Event) error {
|
|
if handled, err := shell.applyCatalogEvent(event); err != nil || handled {
|
|
return err
|
|
}
|
|
iconEvent, handled, err := application.ParseIconEvent(event)
|
|
if err != nil || !handled {
|
|
return err
|
|
}
|
|
identity := iconEvent.Identity
|
|
pending, exists := shell.iconRequests[identity.AppID]
|
|
if !exists || pending != identity {
|
|
return nil
|
|
}
|
|
if shell.iconReferences[identity.AppID] != identity.Reference {
|
|
return nil
|
|
}
|
|
|
|
delete(shell.iconRequests, identity.AppID)
|
|
switch iconEvent.Type {
|
|
case application.EventIconReady:
|
|
shell.ApplyIcon(identity.AppID, iconEvent.Image)
|
|
shell.iconApplied[identity.AppID] = identity
|
|
case application.EventIconFailed:
|
|
applied, hasApplied := shell.iconApplied[identity.AppID]
|
|
if !hasApplied || !sameIconResource(applied, identity) {
|
|
shell.ApplyIcon(identity.AppID, nil)
|
|
}
|
|
shell.iconFailures[identity.AppID] = iconFailureState{
|
|
Identity: identity,
|
|
Code: iconEvent.ErrorCode,
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IconFailure exposes the last failure for diagnostics without raw network data.
|
|
func (shell *AppShell) IconFailure(appID string) (application.IconFailureCode, bool) {
|
|
failure, exists := shell.iconFailures[appID]
|
|
return failure.Code, exists
|
|
}
|
|
|
|
func canonicalIconReference(reference string) string {
|
|
canonical, err := application.NormalizeIconReference(reference)
|
|
if err != nil {
|
|
return reference
|
|
}
|
|
return canonical
|
|
}
|
|
|
|
func sameIconResource(
|
|
left application.IconEventIdentity,
|
|
right application.IconEventIdentity,
|
|
) bool {
|
|
return left.AppID == right.AppID &&
|
|
left.Reference == right.Reference &&
|
|
left.DPI == right.DPI
|
|
}
|