package application import ( "context" "errors" "fmt" "log/slog" "os" "strings" "time" "bunker/commons" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" ) var ( errApplication = errors.New("application") errNoMainWindow = errors.New("no main window service registered") ) // Application is a lifecycle controlling structure for application. type Application struct { ctx context.Context cancelFunc context.CancelFunc fyneApp fyne.App baseLogger *slog.Logger appLogger *slog.Logger services []Service } // New creates new instance of lifecycle controlling structure. func New() *Application { appl := &Application{} appl.initialize() return appl } func (a *Application) configure() error { // First iteration - core services. for _, service := range a.services { if !strings.Contains(service.Name(), "core/") { continue } a.appLogger.Debug("Launching configuration procedure for service", "service", service.Name()) if err := service.Configure(); err != nil { return fmt.Errorf("configure service '%s': %w", service.Name(), err) } } // Second iteration - rest of the services. for _, service := range a.services { if strings.Contains(service.Name(), "core/") { continue } a.appLogger.Debug("Launching configuration procedure for service", "service", service.Name()) if err := service.Configure(); err != nil { return fmt.Errorf("configure service '%s': %w", service.Name(), err) } } return nil } func (a *Application) connectDependencies() error { // First iteration - core services. for _, service := range a.services { if !strings.Contains(service.Name(), "core/") { continue } a.appLogger.Debug("Connecting dependencies for service.", "service", service.Name()) if err := service.ConnectDependencies(); err != nil { return fmt.Errorf("connect dependencies for service '%s': %w", service.Name(), err) } } // Second iteration - rest of the services. for _, service := range a.services { if strings.Contains(service.Name(), "core/") { continue } a.appLogger.Debug("Connecting dependencies for service.", "service", service.Name()) if err := service.ConnectDependencies(); err != nil { return fmt.Errorf("connect dependencies for service '%s': %w", service.Name(), err) } } return nil } // ContextWithTimeout returns context.Context with requested timeout. func (a *Application) ContextWithTimeout(timeout time.Duration) context.Context { ctx, cancelFunc := context.WithTimeout(a.ctx, timeout) // As we do not need to call cancelFunc - make linter happy. // This probably will lead to context leak, so it should be investigated. go func(_ context.CancelFunc) {}(cancelFunc) return ctx } func (a *Application) initialize() { a.ctx, a.cancelFunc = context.WithCancel(context.Background()) a.initializeLogger() a.fyneApp = app.NewWithID(commons.ClientAppID) a.services = make([]Service, 0) } func (a *Application) launchStartupTasks() error { for _, service := range a.services { if strings.Contains(service.Name(), "mainwindow") { continue } if !strings.Contains(service.Name(), "core/") { continue } a.appLogger.Debug("Launching startup tasks for service.", "service", service.Name()) if err := service.LaunchStartupTasks(); err != nil { return fmt.Errorf("launch startup tasks for core/%s: %w", service.Name(), err) } } for _, service := range a.services { if strings.Contains(service.Name(), "core/") { continue } a.appLogger.Debug("Launching startup tasks for service.", "service", service.Name()) if err := service.LaunchStartupTasks(); err != nil { return fmt.Errorf("launch startup tasks for core/%s: %w", service.Name(), err) } } var mainWindowService Service for _, srv := range a.services { if srv.Name() == "core/mainwindow" { mainWindowService = srv break } } if mainWindowService == nil { return fmt.Errorf("launch startup tasks: %w", errNoMainWindow) } if err := mainWindowService.LaunchStartupTasks(); err != nil { return fmt.Errorf("launch startup tasks for %s: %w", mainWindowService.Name(), err) } return nil } // Shutdown stops application. func (a *Application) Shutdown() error { a.appLogger.Info("Stopping pztrn's Bunker...") for _, service := range a.services { if !strings.Contains(service.Name(), "features/") { continue } a.appLogger.Debug("Shutting down service.", "service", service.Name()) if err := service.Shutdown(); err != nil { return fmt.Errorf("shutting down service '%s': %w", service.Name(), err) } } for _, service := range a.services { if !strings.Contains(service.Name(), "core/") { continue } a.appLogger.Debug("Shutting down service.", "service", service.Name()) if err := service.Shutdown(); err != nil { return fmt.Errorf("shutting down service '%s': %w", service.Name(), err) } } os.Exit(0) return nil } // Start starts application. func (a *Application) Start() error { if err := a.connectDependencies(); err != nil { return fmt.Errorf("%w: %w", errApplication, err) } if err := a.configure(); err != nil { return fmt.Errorf("%w: %w", errApplication, err) } if err := a.launchStartupTasks(); err != nil { return fmt.Errorf("%w: %w", errApplication, err) } a.fyneApp.Run() return nil }