Files
yellowjacket/backend/app.go
T

173 lines
4.3 KiB
Go

// Package backend contains the main application logic.
package backend
//go:generate go tool templ generate
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
wailsruntime "github.com/wailsapp/wails/v2/pkg/runtime"
"yellowjacket/backend/assets"
"yellowjacket/backend/config"
"yellowjacket/backend/database"
"yellowjacket/backend/frontendutil"
"yellowjacket/backend/library"
"yellowjacket/backend/player"
"yellowjacket/backend/queue"
)
// YellowJacketApp is the main application struct for Wails.
type YellowJacketApp struct {
FEBindings []any
FrontendUtil *frontendutil.FrontendUtil
logger *slog.Logger
assetHandler *assets.Handler
database *database.DB
library *library.Library
player *player.Player
queue *queue.Queue
appContext context.Context
appConfig *config.Config
}
// NewYellowJacketApp creates and initializes the application.
func NewYellowJacketApp(
logger *slog.Logger,
assetHandler *assets.Handler,
) (*YellowJacketApp, error) {
// initialize anything that does not need access to the wails runtime here
yjApp := &YellowJacketApp{
logger: logger,
assetHandler: assetHandler,
appContext: context.Background(),
}
// create database
db, err := database.NewDB(logger)
if err != nil {
return nil, fmt.Errorf("could not connect to local database: %w", err)
}
yjApp.database = db
// create config
appConfig, err := config.NewConfig(yjApp.logger)
if err != nil {
return nil, fmt.Errorf("could not get config: %w", err)
}
yjApp.appConfig = appConfig
yjApp.assetHandler.RegisterHandler("/config", yjApp.appConfig)
// create frontendUtil
feUtil, err := frontendutil.NewFrontendUtil()
if err != nil {
return nil, fmt.Errorf("could not create frontendUtil: %w", err)
}
yjApp.FrontendUtil = feUtil
lib, err := library.NewLibrary(
yjApp.appContext,
yjApp.logger,
yjApp.appConfig.Library,
yjApp.database,
)
if err != nil {
return nil, fmt.Errorf("could not create library: %w", err)
}
yjApp.library = lib
// create cover art handler
coverHandler, err := library.NewCoverArtHandler()
if err != nil {
return nil, fmt.Errorf("could not create cover art handler: %w", err)
}
yjApp.assetHandler.RegisterHandler("/covers/", coverHandler)
yjApp.FEBindings = []any{
yjApp.FrontendUtil,
yjApp.library,
}
return yjApp, nil
}
var startupErr error
// OnStartup initializes components that require the Wails runtime context.
func (yj *YellowJacketApp) OnStartup(ctx context.Context) {
// initialize anything that needs to use the wails runtime AFTER its been initialized
// you CANNOT use the wails runtime during this function
yj.appContext = ctx
// Set context for components that need Wails runtime for events
yj.appConfig.SetContext(ctx)
yj.FrontendUtil.SetContext(ctx)
yj.library.SetContext(ctx)
var err error
// create player
yj.player, err = player.NewPlayer(ctx, yj.logger.WithGroup("player"), yj.database)
if err != nil {
startupErr = errors.Join(startupErr, fmt.Errorf("could not create player: %w", err))
}
yj.player.SetContext(ctx)
// create queue
yj.queue = queue.NewQueue(yj.logger, yj.database)
yj.queue.SetContext(ctx)
yj.queue.SetPlayer(yj.player)
yj.queue.RestoreState()
// Register playback finished handler to drive queue auto-advance.
yj.player.SetPlaybackFinishedHandler(yj.queue.OnPlaybackFinished)
// Add player to frontend bindings
yj.FEBindings = append(yj.FEBindings, yj.player)
}
// OnShutdown saves player state and cleans up resources before the application exits.
func (yj *YellowJacketApp) OnShutdown(_ context.Context) {
if yj.player != nil {
yj.player.SaveState()
}
if yj.queue != nil {
yj.queue.SaveState()
}
}
// OnDomReady handles post-DOM initialization and startup error reporting.
func (yj *YellowJacketApp) OnDomReady(ctx context.Context) {
if startupErr != nil {
yj.logger.Error("startup error", "err", startupErr.Error())
wailsruntime.Quit(ctx)
}
// Push current player and queue state to the frontend. The heavy lifting
// (file load, seek, volume) already happened during OnStartup via
// RestoreState; this just emits events. A short delay ensures the
// frontend JS modules have loaded and registered their event listeners.
go func() {
time.Sleep(200 * time.Millisecond)
if yj.player != nil {
yj.player.EmitCurrentState()
}
if yj.queue != nil {
yj.queue.EmitCurrentState()
}
}()
}