Files
MyGoNavi/internal/app/methods_brand_icon.go
Syngnat 176b100274 🐛 fix(app): 修复品牌图标与 SQLite 查询展示
- 修复 Windows、macOS 与关于页的品牌图标展示及资源裁切问题
- 补齐 SQLite 表记录数与 SELECT 查询结果渲染
- 增加品牌图标和 SQLite 回归测试
2026-07-15 22:53:47 +08:00

103 lines
3.2 KiB
Go

package app
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"image"
_ "image/png"
"strings"
"GoNavi-Wails/internal/connection"
"GoNavi-Wails/internal/logger"
)
const applicationBrandIconMaxPNGBytes = 4 * 1024 * 1024
var (
errApplicationBrandIconPayloadEmpty = errors.New("empty icon payload")
errApplicationBrandIconPayloadInvalid = errors.New("invalid PNG icon payload")
)
// SetApplicationBrandIcon updates the OS application icon (macOS Dock) from a
// PNG payload. Frontend may pass raw base64 or a data URL (data:image/png;base64,...).
func (a *App) SetApplicationBrandIcon(imageBase64 string) (result connection.QueryResult) {
defer func() {
if recovered := recover(); recovered != nil {
logger.Errorf("设置应用图标失败:%v", recovered)
result = connection.QueryResult{
Success: false,
Message: a.appText("app.backend.error.set_brand_icon_failed", map[string]any{
"detail": fmt.Sprint(recovered),
}),
}
}
}()
png, err := decodeApplicationBrandIconPayload(imageBase64)
if err != nil {
key := "app.backend.error.set_brand_icon_invalid"
if errors.Is(err, errApplicationBrandIconPayloadEmpty) {
key = "app.backend.error.set_brand_icon_empty"
}
return connection.QueryResult{Success: false, Message: a.appText(key, map[string]any{
"detail": err.Error(),
})}
}
if err := setApplicationIconPNG(png); err != nil {
return connection.QueryResult{Success: false, Message: a.appText("app.backend.error.set_brand_icon_failed", map[string]any{
"detail": err.Error(),
})}
}
return connection.QueryResult{Success: true, Message: a.appText("app.backend.message.brand_icon_updated", nil)}
}
func decodeApplicationBrandIconPayload(imageBase64 string) ([]byte, error) {
raw := strings.TrimSpace(imageBase64)
if raw == "" {
return nil, errApplicationBrandIconPayloadEmpty
}
if idx := strings.Index(raw, ","); idx >= 0 && strings.Contains(strings.ToLower(raw[:idx]), "base64") {
raw = raw[idx+1:]
}
raw = strings.Map(func(r rune) rune {
if r == '\n' || r == '\r' || r == ' ' || r == '\t' {
return -1
}
return r
}, raw)
if raw == "" {
return nil, errApplicationBrandIconPayloadEmpty
}
if len(raw) > base64.StdEncoding.EncodedLen(applicationBrandIconMaxPNGBytes+1) {
return nil, fmt.Errorf("%w: payload exceeds %d MiB", errApplicationBrandIconPayloadInvalid, applicationBrandIconMaxPNGBytes/(1024*1024))
}
var decodeErr error
for _, encoding := range []*base64.Encoding{
base64.StdEncoding,
base64.RawStdEncoding,
base64.URLEncoding,
base64.RawURLEncoding,
} {
png, err := encoding.DecodeString(raw)
if err != nil {
decodeErr = err
continue
}
if len(png) > applicationBrandIconMaxPNGBytes {
return nil, fmt.Errorf("%w: decoded image exceeds %d MiB", errApplicationBrandIconPayloadInvalid, applicationBrandIconMaxPNGBytes/(1024*1024))
}
if _, format, err := image.DecodeConfig(bytes.NewReader(png)); err != nil || format != "png" {
if err != nil {
return nil, fmt.Errorf("%w: %v", errApplicationBrandIconPayloadInvalid, err)
}
return nil, fmt.Errorf("%w: expected PNG image", errApplicationBrandIconPayloadInvalid)
}
return png, nil
}
return nil, fmt.Errorf("%w: %v", errApplicationBrandIconPayloadInvalid, decodeErr)
}