main.go
⎇
Raw
1package main
2
3import (
4 "context"
5 "fmt"
6 "log"
7 "os"
8 "os/exec"
9 "os/signal"
10 "path/filepath"
11 "syscall"
12 "time"
13
14 "vidarchive/internal/config"
15 "vidarchive/internal/database"
16 "vidarchive/internal/handler"
17 "vidarchive/internal/repository"
18 "vidarchive/internal/server"
19 "vidarchive/internal/service"
20 "vidarchive/internal/worker"
21)
22
23func main() {
24 // Cancelled on SIGINT/SIGTERM so the server drains, workers stop, running
25 // yt-dlp children are killed and the database is checkpointed and closed.
26 ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
27 defer stop()
28
29 cfg := config.New()
30
31 checkDependencies(cfg)
32
33 if err := os.MkdirAll(cfg.DataDir, 0755); err != nil {
34 log.Fatalf("Failed to create data dir: %v", err)
35 }
36 if err := os.MkdirAll(cfg.LibraryDir, 0755); err != nil {
37 log.Fatalf("Failed to create library dir: %v", err)
38 }
39 if err := os.MkdirAll(cfg.TempDir, 0755); err != nil {
40 log.Fatalf("Failed to create temp dir: %v", err)
41 }
42 if err := os.MkdirAll(filepath.Join(cfg.DataDir, "archives"), 0755); err != nil {
43 log.Fatalf("Failed to create archives dir: %v", err)
44 }
45
46 db, err := database.New(cfg)
47 if err != nil {
48 log.Fatalf("Failed to initialize database: %v", err)
49 }
50
51 presetRepo := repository.NewPresetRepository(db)
52 downloadRepo := repository.NewDownloadRepository(db)
53 settingsRepo := repository.NewSettingsRepository(db)
54 subscriptionRepo := repository.NewSubscriptionRepository(db)
55
56 presetSvc := service.NewPresetService(presetRepo)
57 librarySvc := service.NewLibraryService(cfg.LibraryDir, cfg.FFmpegPath, cfg.FFprobePath)
58 settingsSvc := service.NewSettingsService(settingsRepo)
59 subscriptionSvc := service.NewSubscriptionService(subscriptionRepo, cfg)
60 downloadSvc := service.NewDownloadService(downloadRepo, librarySvc, presetSvc, settingsSvc, subscriptionSvc, cfg)
61
62 workerPool := worker.New(downloadSvc, cfg.Workers)
63 workerPool.Start()
64
65 scheduler := worker.NewScheduler(subscriptionSvc, downloadSvc, workerPool, time.Duration(cfg.SchedulerInterval)*time.Second)
66 scheduler.Start()
67
68 h, err := handler.New(cfg, presetSvc, downloadSvc, librarySvc, settingsSvc, subscriptionSvc, workerPool)
69 if err != nil {
70 log.Fatalf("Failed to initialize handler: %v", err)
71 }
72
73 srv := server.New(cfg, h)
74
75 fmt.Printf("VidArchive starting...\n")
76 fmt.Printf("Data directory: %s\n", cfg.DataDir)
77 fmt.Printf("Library directory: %s\n", cfg.LibraryDir)
78 fmt.Printf("Temp directory: %s\n", cfg.TempDir)
79
80 srvErr := srv.Start(ctx)
81
82 // Shut down in dependency order: stop scheduling new runs, then stop the
83 // workers (killing any running yt-dlp), then checkpoint and close the DB.
84 // This runs on both a signal and a server error, so nothing is left orphaned.
85 log.Printf("Shutting down...")
86 scheduler.Stop()
87 workerPool.Stop()
88 if err := database.Checkpoint(db); err != nil {
89 log.Printf("Warning: %v", err)
90 }
91 if err := db.Close(); err != nil {
92 log.Printf("Warning: failed to close database: %v", err)
93 }
94 log.Printf("Shutdown complete")
95
96 if srvErr != nil {
97 log.Fatalf("Server error: %v", srvErr)
98 }
99}
100
101// checkDependencies warns (loudly, but without aborting) when an external binary
102// VidArchive relies on isn't on PATH, so a missing yt-dlp/ffmpeg surfaces as a
103// clear startup message rather than an opaque per-download failure later.
104func checkDependencies(cfg *config.Config) {
105 deps := []struct{ label, path string }{
106 {"yt-dlp", cfg.YTDLPPath},
107 {"ffmpeg", cfg.FFmpegPath},
108 {"ffprobe", cfg.FFprobePath},
109 }
110 for _, dep := range deps {
111 if _, err := exec.LookPath(dep.path); err != nil {
112 log.Printf("WARNING: %s (%q) not found in PATH; related features will fail until it is installed", dep.label, dep.path)
113 }
114 }
115}
116