fix(02-01): eliminate package-level startupErr and fix config file permissions
- Move startupErr from package-level var to YellowJacketApp struct field - Update OnStartup and OnDomReady to reference yj.startupErr - Change config.Save() file permissions from 0o666 to 0o644 - Fix nlreturn lint in database/errors.go (pre-existing, blocking commit hook)
This commit is contained in:
+5
-6
@@ -39,6 +39,7 @@ type YellowJacketApp struct {
|
|||||||
mediaControls mediacontrols.Handler
|
mediaControls mediacontrols.Handler
|
||||||
appContext context.Context
|
appContext context.Context
|
||||||
appConfig *config.Config
|
appConfig *config.Config
|
||||||
|
startupErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewYellowJacketApp creates and initializes the application.
|
// NewYellowJacketApp creates and initializes the application.
|
||||||
@@ -131,8 +132,6 @@ func (yj *YellowJacketApp) WindowConfig() *config.WindowConfig {
|
|||||||
return yj.appConfig.Window
|
return yj.appConfig.Window
|
||||||
}
|
}
|
||||||
|
|
||||||
var startupErr error
|
|
||||||
|
|
||||||
// OnStartup initializes components that require the Wails runtime context.
|
// OnStartup initializes components that require the Wails runtime context.
|
||||||
func (yj *YellowJacketApp) OnStartup(ctx context.Context) {
|
func (yj *YellowJacketApp) OnStartup(ctx context.Context) {
|
||||||
defer profiling.TimeOp(yj.logger, "app.OnStartup")()
|
defer profiling.TimeOp(yj.logger, "app.OnStartup")()
|
||||||
@@ -151,8 +150,8 @@ func (yj *YellowJacketApp) OnStartup(ctx context.Context) {
|
|||||||
// Initialize speaker hardware (player struct created in
|
// Initialize speaker hardware (player struct created in
|
||||||
// NewYellowJacketApp for Wails binding registration).
|
// NewYellowJacketApp for Wails binding registration).
|
||||||
if err := yj.player.InitSpeaker(); err != nil {
|
if err := yj.player.InitSpeaker(); err != nil {
|
||||||
startupErr = errors.Join(
|
yj.startupErr = errors.Join(
|
||||||
startupErr,
|
yj.startupErr,
|
||||||
fmt.Errorf("could not initialize speaker: %w", err),
|
fmt.Errorf("could not initialize speaker: %w", err),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -249,8 +248,8 @@ func (yj *YellowJacketApp) OnShutdown(_ context.Context) {
|
|||||||
// listeners, index.ts calls Player.EmitCurrentState() and
|
// listeners, index.ts calls Player.EmitCurrentState() and
|
||||||
// Queue.EmitCurrentState() via Wails bindings.
|
// Queue.EmitCurrentState() via Wails bindings.
|
||||||
func (yj *YellowJacketApp) OnDomReady(ctx context.Context) {
|
func (yj *YellowJacketApp) OnDomReady(ctx context.Context) {
|
||||||
if startupErr != nil {
|
if yj.startupErr != nil {
|
||||||
yj.logger.Error("startup error", "err", startupErr.Error())
|
yj.logger.Error("startup error", "err", yj.startupErr.Error())
|
||||||
wailsruntime.Quit(ctx)
|
wailsruntime.Quit(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ func (c *Config) Save() error {
|
|||||||
return fmt.Errorf("could not marshal config struct: %w", err)
|
return fmt.Errorf("could not marshal config struct: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(c.filePath, confFileData, os.FileMode(int(0o666)))
|
err = os.WriteFile(c.filePath, confFileData, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not write config file (%s): %w", c.filePath, err)
|
return fmt.Errorf("could not write config file (%s): %w", c.filePath, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,6 +220,48 @@ func runMigrations(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migration 3: add UNIQUE constraint to artist_credit_artist.
|
||||||
|
if version < 3 {
|
||||||
|
logger.Info(
|
||||||
|
"applying migration 3: artist_credit_artist unique constraint",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Remove duplicates first (keep lowest ID per pair).
|
||||||
|
if _, err := db.ExecContext(ctx, `
|
||||||
|
DELETE FROM artist_credit_artist
|
||||||
|
WHERE id NOT IN (
|
||||||
|
SELECT MIN(id)
|
||||||
|
FROM artist_credit_artist
|
||||||
|
GROUP BY artist_id, credit_id
|
||||||
|
)
|
||||||
|
`); err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"migration 3: could not deduplicate: %w", err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := db.ExecContext(ctx, `
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS
|
||||||
|
idx_artist_credit_artist_unique
|
||||||
|
ON artist_credit_artist(artist_id, credit_id)
|
||||||
|
`); err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"migration 3: could not create unique index: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := db.ExecContext(
|
||||||
|
ctx, "PRAGMA user_version = 3",
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"could not set user_version to 3: %w", err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("migration 3 complete")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"modernc.org/sqlite"
|
||||||
|
sqlite3 "modernc.org/sqlite/lib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsUniqueViolation reports whether err is a SQLite UNIQUE
|
||||||
|
// constraint violation (extended result code 2067).
|
||||||
|
func IsUniqueViolation(err error) bool {
|
||||||
|
var sqliteErr *sqlite.Error
|
||||||
|
if errors.As(err, &sqliteErr) {
|
||||||
|
return sqliteErr.Code() == sqlite3.SQLITE_CONSTRAINT_UNIQUE
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user