app.go

 1package app
 2
 3import (
 4	"context"
 5	"database/sql"
 6	"maps"
 7	"sync"
 8	"time"
 9
10	"github.com/kujtimiihoxha/termai/internal/db"
11	"github.com/kujtimiihoxha/termai/internal/history"
12	"github.com/kujtimiihoxha/termai/internal/logging"
13	"github.com/kujtimiihoxha/termai/internal/lsp"
14	"github.com/kujtimiihoxha/termai/internal/message"
15	"github.com/kujtimiihoxha/termai/internal/permission"
16	"github.com/kujtimiihoxha/termai/internal/session"
17)
18
19type App struct {
20	Sessions    session.Service
21	Messages    message.Service
22	Files       history.Service
23	Permissions permission.Service
24
25	LSPClients map[string]*lsp.Client
26
27	clientsMutex sync.RWMutex
28
29	watcherCancelFuncs []context.CancelFunc
30	cancelFuncsMutex   sync.Mutex
31	watcherWG          sync.WaitGroup
32}
33
34func New(ctx context.Context, conn *sql.DB) *App {
35	q := db.New(conn)
36	sessions := session.NewService(q)
37	messages := message.NewService(q)
38	files := history.NewService(q)
39
40	app := &App{
41		Sessions:    sessions,
42		Messages:    messages,
43		Files:       files,
44		Permissions: permission.NewPermissionService(),
45		LSPClients:  make(map[string]*lsp.Client),
46	}
47
48	app.initLSPClients(ctx)
49
50	return app
51}
52
53// Shutdown performs a clean shutdown of the application
54func (app *App) Shutdown() {
55	// Cancel all watcher goroutines
56	app.cancelFuncsMutex.Lock()
57	for _, cancel := range app.watcherCancelFuncs {
58		cancel()
59	}
60	app.cancelFuncsMutex.Unlock()
61	app.watcherWG.Wait()
62
63	// Perform additional cleanup for LSP clients
64	app.clientsMutex.RLock()
65	clients := make(map[string]*lsp.Client, len(app.LSPClients))
66	maps.Copy(clients, app.LSPClients)
67	app.clientsMutex.RUnlock()
68
69	for name, client := range clients {
70		shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
71		if err := client.Shutdown(shutdownCtx); err != nil {
72			logging.Error("Failed to shutdown LSP client", "name", name, "error", err)
73		}
74		cancel()
75	}
76}