package main import ( "context" "flag" "fmt" "log" "os" "os/signal" "syscall" "naviwatcher/internal/config" "naviwatcher/internal/database" "naviwatcher/internal/musicbrainz" ) // App holds all application dependencies for clean shutdown and testability. type App struct { cfg *config.Config db *database.DB mbClient *musicbrainz.MusicBrainzClient } const defaultConfigPath = "config.yaml" func main() { configPath := flag.String("config", defaultConfigPath, "Path to config file") flag.Parse() log.Println("NaviWatcher starting...") cfg, err := config.LoadConfig(*configPath) if err != nil { log.Fatalf("Failed to load config: %v", err) } log.Printf("Config loaded from %s (server: %s:%d)", *configPath, cfg.Server.Host, cfg.Server.Port) ctx, cancel := context.WithCancel(context.Background()) defer cancel() sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) go func() { sig := <-sigCh log.Printf("Received signal %v, shutting down...", sig) cancel() }() app, err := NewApp(ctx, cfg) if err != nil { log.Fatalf("Failed to initialize application: %v", err) } defer app.Close() if err := app.run(ctx); err != nil { log.Fatalf("Application error: %v", err) } log.Println("NaviWatcher stopped.") } // NewApp initializes all application components: config, database, and MusicBrainz client. func NewApp(ctx context.Context, cfg *config.Config) (*App, error) { // Initialize database (uses default path or could be made configurable). db, err := database.New("naviwatcher.db") if err != nil { return nil, fmt.Errorf("failed to initialize database: %w", err) } // Initialize MusicBrainz client with rate limiting. mbClient := musicbrainz.NewClient(cfg.MusicBrainz) log.Printf("MusicBrainz client initialized (user-agent: %s)", cfg.MusicBrainz.UserAgent) return &App{ cfg: cfg, db: db, mbClient: mbClient, }, nil } // Close cleans up all application resources in reverse order of initialization. func (a *App) Close() { if a.db != nil { if err := a.db.Close(); err != nil { log.Printf("Error closing database: %v", err) } } } func (a *App) run(ctx context.Context) error { // Main application loop — blocks until context is cancelled. // Business logic (scanner, notifier, web server) will be wired into // separate goroutines here in future tasks. <-ctx.Done() return nil }