wip6
This commit is contained in:
+14
-25
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Антон Аксенов
|
||||
* Copyright (c) 2025-2026, Антон Аксенов
|
||||
* This file is part of iptvc project
|
||||
* MIT License: https://git.axenov.dev/IPTV/iptvc/src/branch/master/LICENSE
|
||||
*/
|
||||
@@ -24,15 +24,10 @@ var checkCmd = &cobra.Command{
|
||||
Short: "Check playlists",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app.Init()
|
||||
|
||||
files, _ := cmd.Flags().GetStringSlice("file")
|
||||
urls, _ := cmd.Flags().GetStringSlice("url")
|
||||
codes, _ := cmd.Flags().GetStringSlice("code")
|
||||
|
||||
waitSeconds := app.Args.RepeatEverySec
|
||||
if waitSeconds <= 0 {
|
||||
waitSeconds = 5
|
||||
}
|
||||
applyAppOverrides(cmd)
|
||||
applyCacheOverrides(cmd)
|
||||
applyCheckOverrides(cmd)
|
||||
app.InitCache()
|
||||
|
||||
currentIteration := 1
|
||||
for {
|
||||
@@ -45,11 +40,11 @@ var checkCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
var lists []playlist.Playlist
|
||||
if len(files) == 0 && len(urls) == 0 && len(codes) == 0 {
|
||||
lists = checker.PrepareListsToCheck(files, urls, codes)
|
||||
if len(app.Args.Files) == 0 && len(app.Args.Urls) == 0 && len(app.Args.Codes) == 0 {
|
||||
lists = checker.PrepareListsToCheck(app.Args.Files, app.Args.Urls, app.Args.Codes)
|
||||
} else {
|
||||
if currentIteration == 1 {
|
||||
lists = checker.PrepareListsToCheck(files, urls, codes)
|
||||
lists = checker.PrepareListsToCheck(app.Args.Files, app.Args.Urls, app.Args.Codes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,22 +74,16 @@ var checkCmd = &cobra.Command{
|
||||
}
|
||||
currentIteration++
|
||||
}
|
||||
log.Printf("Waiting for new iteration... seconds=%d\n", app.Args.RepeatEverySec)
|
||||
time.Sleep(time.Duration(app.Args.RepeatEverySec) * time.Second)
|
||||
// AllCooldown хранится в миллисекундах, поэтому переводим в time.Millisecond
|
||||
cooldown := time.Duration(app.Config.Check.Playlists.AllCooldown.Value()) * time.Millisecond
|
||||
log.Printf("Waiting for new iteration... cooldown=%s\n", cooldown)
|
||||
time.Sleep(cooldown)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
checkCmd.Flags().StringVarP(&app.Args.TagsPath, "tags", "t", "./channels.json", "path to a local tagfile")
|
||||
checkCmd.Flags().StringVarP(&app.Args.IniPath, "ini", "i", "./playlists.ini", "path to a local ini-file")
|
||||
checkCmd.Flags().UintVarP(&app.Args.RandomCount, "random", "r", 0, "take this count of random playlists to check from ini-file")
|
||||
checkCmd.Flags().UintVarP(&app.Args.RepeatCount, "repeat", "", 1, "repeat same check X times")
|
||||
checkCmd.Flags().UintVarP(&app.Args.RepeatEverySec, "every", "", 5, "wait N seconds after every check")
|
||||
checkCmd.Flags().BoolVarP(&app.Args.NeedJson, "json", "j", false, "print results in JSON format in the end")
|
||||
checkCmd.Flags().BoolVarP(&app.Args.NeedQuiet, "quiet", "q", false, "suppress logs (does not affect on -j)")
|
||||
checkCmd.Flags().StringSliceP("file", "f", []string{}, "path to a local playlist file (m3u/m3u8)")
|
||||
checkCmd.Flags().StringSliceP("url", "u", []string{}, "URL to a remote playlist (http/https)")
|
||||
checkCmd.Flags().StringSliceP("code", "c", []string{}, "code of playlist from ini-file")
|
||||
addCommonCheckFlags(checkCmd, 1)
|
||||
addCheckOnlyFlags(checkCmd)
|
||||
rootCmd.AddCommand(checkCmd)
|
||||
}
|
||||
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (c) 2025-2026, Антон Аксенов
|
||||
* This file is part of iptvc project
|
||||
* MIT License: https://git.axenov.dev/IPTV/iptvc/src/branch/master/LICENSE
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"axenov/iptv-checker/app"
|
||||
"axenov/iptv-checker/app/config"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// addCommonCheckFlags добавляет общие флаги, используемые как командой check,
|
||||
// так и serve. Флаги используют zero-value defaults; переопределение config.yml
|
||||
// происходит через cmd.Flags().Changed() в applyCacheOverrides() и applyCheckOverrides().
|
||||
func addCommonCheckFlags(cmd *cobra.Command, repeatDefault uint) {
|
||||
// paths
|
||||
cmd.Flags().StringVarP(&app.Args.IniPath, "ini", "i", "", "path to a local ini-file (overrides config.yml)")
|
||||
cmd.Flags().StringVarP(&app.Args.TagsPath, "tags", "t", "", "path to a local tagfile (overrides config.yml)")
|
||||
|
||||
// iteration
|
||||
cmd.Flags().UintVarP(&app.Args.RandomCount, "random", "r", 0, "take this count of random playlists to check from ini-file")
|
||||
cmd.Flags().UintVarP(&app.Args.RepeatCount, "repeat", "", repeatDefault, "repeat same check X times (0 = infinite)")
|
||||
|
||||
// check.playlists
|
||||
cmd.Flags().IntVar(&app.Args.PlTimeout, "playlists-timeout", 0, "playlist request timeout in seconds (overrides config.yml)")
|
||||
cmd.Flags().IntVar(&app.Args.PlAllCooldown, "playlists-all-cooldown", 0, "cooldown after all playlists in seconds (overrides config.yml)")
|
||||
cmd.Flags().IntVar(&app.Args.PlOneCooldown, "playlists-one-cooldown", 0, "cooldown after each playlist in seconds (overrides config.yml)")
|
||||
cmd.Flags().IntVar(&app.Args.PlMaxRoutines, "playlists-max-routines", 0, "max concurrent playlist checks (overrides config.yml)")
|
||||
cmd.Flags().IntVar(&app.Args.PlPerRoutine, "playlists-per-routine", 0, "playlists per routine (overrides config.yml)")
|
||||
cmd.Flags().StringSliceVar(&app.Args.PlUserAgent, "playlists-user-agent", nil, "user-agent for playlist requests (overrides config.yml)")
|
||||
|
||||
// check.channels
|
||||
cmd.Flags().IntVar(&app.Args.ChTimeout, "channels-timeout", 0, "channel request timeout in seconds (overrides config.yml)")
|
||||
cmd.Flags().IntVar(&app.Args.ChByteRange, "channels-byte-range", 0, "bytes to fetch from server per channel (overrides config.yml)")
|
||||
cmd.Flags().IntVar(&app.Args.ChCooldown, "channels-cooldown", 0, "cooldown after each channel in seconds (overrides config.yml)")
|
||||
cmd.Flags().IntVar(&app.Args.ChMaxRoutines, "channels-max-routines", 0, "max concurrent channel checks (overrides config.yml)")
|
||||
cmd.Flags().IntVar(&app.Args.ChPerRoutine, "channels-per-routine", 0, "channels per routine (overrides config.yml)")
|
||||
cmd.Flags().StringSliceVar(&app.Args.ChUserAgent, "channels-user-agent", nil, "user-agent for channel requests (overrides config.yml)")
|
||||
|
||||
// cache
|
||||
cmd.Flags().BoolVar(&app.Args.CacheEnabled, "cache-enabled", false, "enable cache (overrides config.yml)")
|
||||
cmd.Flags().StringVar(&app.Args.CacheHost, "cache-host", "", "cache host (overrides config.yml)")
|
||||
cmd.Flags().UintVar(&app.Args.CachePort, "cache-port", 0, "cache port (overrides config.yml)")
|
||||
cmd.Flags().StringVar(&app.Args.CacheUsername, "cache-username", "", "cache username (overrides config.yml)")
|
||||
cmd.Flags().StringVar(&app.Args.CachePassword, "cache-password", "", "cache password (overrides config.yml)")
|
||||
cmd.Flags().UintVar(&app.Args.CacheDb, "cache-db", 0, "cache database number (overrides config.yml)")
|
||||
cmd.Flags().UintVar(&app.Args.CacheTtl, "cache-ttl", 0, "cache TTL in seconds (overrides config.yml)")
|
||||
}
|
||||
|
||||
// addCheckOnlyFlags добавляет флаги, доступные только в команде check.
|
||||
func addCheckOnlyFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().BoolVarP(&app.Args.NeedJson, "json", "j", false, "print results in JSON format in the end")
|
||||
cmd.Flags().BoolVarP(&app.Args.NeedQuiet, "quiet", "q", false, "suppress logs (does not affect on -j)")
|
||||
cmd.Flags().StringSliceVarP(&app.Args.Files, "file", "f", []string{}, "path to a local playlist file (m3u/m3u8)")
|
||||
cmd.Flags().StringSliceVarP(&app.Args.Urls, "url", "u", []string{}, "URL to a remote playlist (http/https)")
|
||||
cmd.Flags().StringSliceVarP(&app.Args.Codes, "code", "c", []string{}, "code of playlist from ini-file")
|
||||
}
|
||||
|
||||
// applyAppOverrides применяет CLI-флаги для app.* поверх конфигурации.
|
||||
// Использует Changed() — переопределение срабатывает только при явной передаче флага.
|
||||
func applyAppOverrides(cmd *cobra.Command) {
|
||||
if cmd.Flags().Changed("debug") {
|
||||
app.Config.App.Debug = app.Args.Debug
|
||||
}
|
||||
if cmd.Flags().Changed("log-level") {
|
||||
app.Config.App.LogLevel = app.Args.LogLevel
|
||||
}
|
||||
if cmd.Flags().Changed("ini") {
|
||||
app.Config.App.Playlists = app.Args.IniPath
|
||||
}
|
||||
if cmd.Flags().Changed("tags") {
|
||||
app.Config.App.Tags = app.Args.TagsPath
|
||||
}
|
||||
}
|
||||
|
||||
// applyCacheOverrides применяет CLI-флаги для cache.* поверх конфигурации.
|
||||
// Использует Changed() — переопределение срабатывает только при явной передаче флага.
|
||||
// Вызывается между app.Init() и app.InitCache().
|
||||
func applyCacheOverrides(cmd *cobra.Command) {
|
||||
c := &app.Config.Cache
|
||||
if cmd.Flags().Changed("cache-enabled") {
|
||||
c.Enabled = app.Args.CacheEnabled
|
||||
}
|
||||
if cmd.Flags().Changed("cache-host") {
|
||||
c.Host = app.Args.CacheHost
|
||||
}
|
||||
if cmd.Flags().Changed("cache-port") {
|
||||
c.Port = app.Args.CachePort
|
||||
}
|
||||
if cmd.Flags().Changed("cache-username") {
|
||||
c.Username = app.Args.CacheUsername
|
||||
}
|
||||
if cmd.Flags().Changed("cache-password") {
|
||||
c.Password = app.Args.CachePassword
|
||||
}
|
||||
if cmd.Flags().Changed("cache-db") {
|
||||
c.Db = app.Args.CacheDb
|
||||
}
|
||||
if cmd.Flags().Changed("cache-ttl") {
|
||||
c.Ttl = app.Args.CacheTtl
|
||||
}
|
||||
}
|
||||
|
||||
// applyCheckOverrides применяет CLI-флаги для check.* поверх конфигурации.
|
||||
// Значения времени передаются в секундах и переводятся в миллисекунды.
|
||||
// Вызывается в обработчиках check и serve после app.Init().
|
||||
func applyCheckOverrides(cmd *cobra.Command) {
|
||||
pp := &app.Config.Check.Playlists
|
||||
if cmd.Flags().Changed("playlists-timeout") {
|
||||
pp.Timeout = config.DurationSeconds(app.Args.PlTimeout * 1000)
|
||||
}
|
||||
if cmd.Flags().Changed("playlists-all-cooldown") {
|
||||
pp.AllCooldown.Min = app.Args.PlAllCooldown * 1000
|
||||
pp.AllCooldown.Max = app.Args.PlAllCooldown * 1000
|
||||
}
|
||||
if cmd.Flags().Changed("playlists-one-cooldown") {
|
||||
pp.OneCooldown.Min = app.Args.PlOneCooldown * 1000
|
||||
pp.OneCooldown.Max = app.Args.PlOneCooldown * 1000
|
||||
}
|
||||
if cmd.Flags().Changed("playlists-max-routines") {
|
||||
pp.MaxRoutines = app.Args.PlMaxRoutines
|
||||
}
|
||||
if cmd.Flags().Changed("playlists-per-routine") {
|
||||
pp.PerRoutine = app.Args.PlPerRoutine
|
||||
}
|
||||
if cmd.Flags().Changed("playlists-user-agent") {
|
||||
pp.UserAgent = app.Args.PlUserAgent
|
||||
}
|
||||
|
||||
cc := &app.Config.Check.Channels
|
||||
if cmd.Flags().Changed("channels-timeout") {
|
||||
cc.Timeout = config.DurationSeconds(app.Args.ChTimeout * 1000)
|
||||
}
|
||||
if cmd.Flags().Changed("channels-byte-range") {
|
||||
cc.ByteRange = app.Args.ChByteRange
|
||||
}
|
||||
if cmd.Flags().Changed("channels-cooldown") {
|
||||
cc.Cooldown.Min = app.Args.ChCooldown * 1000
|
||||
cc.Cooldown.Max = app.Args.ChCooldown * 1000
|
||||
}
|
||||
if cmd.Flags().Changed("channels-max-routines") {
|
||||
cc.MaxRoutines = app.Args.ChMaxRoutines
|
||||
}
|
||||
if cmd.Flags().Changed("channels-per-routine") {
|
||||
cc.PerRoutine = app.Args.ChPerRoutine
|
||||
}
|
||||
if cmd.Flags().Changed("channels-user-agent") {
|
||||
cc.UserAgent = app.Args.ChUserAgent
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"axenov/iptv-checker/app"
|
||||
"axenov/iptv-checker/app/config"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newCmdWithFlags() *cobra.Command {
|
||||
cmd := &cobra.Command{Use: "test"}
|
||||
// persistent flags normally set on rootCmd
|
||||
cmd.PersistentFlags().BoolVar(&app.Args.Debug, "debug", false, "")
|
||||
cmd.PersistentFlags().StringVar(&app.Args.LogLevel, "log-level", "", "")
|
||||
addCommonCheckFlags(cmd, 0)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func resetArgs() {
|
||||
app.Args = app.Arguments{}
|
||||
}
|
||||
|
||||
func resetConfig() {
|
||||
app.Config = &config.Config{
|
||||
App: config.AppConfig{
|
||||
Debug: false,
|
||||
LogLevel: "info",
|
||||
Playlists: "./playlists.ini",
|
||||
Tags: "./channels.json",
|
||||
},
|
||||
Check: config.CheckConfig{
|
||||
Playlists: config.CheckPlaylistsConfig{
|
||||
Timeout: 10000,
|
||||
AllCooldown: config.IntRange{Min: 0, Max: 0},
|
||||
OneCooldown: config.IntRange{Min: 0, Max: 0},
|
||||
MaxRoutines: 5,
|
||||
PerRoutine: 1,
|
||||
UserAgent: config.UserAgents{"default"},
|
||||
},
|
||||
Channels: config.CheckChannelsConfig{
|
||||
Timeout: 10000,
|
||||
ByteRange: 512,
|
||||
Cooldown: config.IntRange{Min: 0, Max: 0},
|
||||
MaxRoutines: 50,
|
||||
PerRoutine: 10,
|
||||
UserAgent: config.UserAgents{"default"},
|
||||
},
|
||||
},
|
||||
Cache: config.CacheConfig{
|
||||
Enabled: false,
|
||||
Host: "localhost",
|
||||
Port: 6379,
|
||||
Ttl: 1800,
|
||||
},
|
||||
Server: config.ServerConfig{Port: 8800},
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyAppOverrides_NothingChanged(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{})
|
||||
applyAppOverrides(cmd)
|
||||
if app.Config.App.Debug {
|
||||
t.Error("debug should remain false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyAppOverrides_Debug(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--debug"})
|
||||
applyAppOverrides(cmd)
|
||||
if !app.Config.App.Debug {
|
||||
t.Error("expected debug=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyAppOverrides_LogLevel(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--log-level", "error"})
|
||||
applyAppOverrides(cmd)
|
||||
if app.Config.App.LogLevel != "error" {
|
||||
t.Errorf("expected 'error', got '%s'", app.Config.App.LogLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyAppOverrides_Ini(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"-i", "/custom/path.ini"})
|
||||
applyAppOverrides(cmd)
|
||||
if app.Config.App.Playlists != "/custom/path.ini" {
|
||||
t.Errorf("expected '/custom/path.ini', got '%s'", app.Config.App.Playlists)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyAppOverrides_Tags(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"-t", "/custom/tags.json"})
|
||||
applyAppOverrides(cmd)
|
||||
if app.Config.App.Tags != "/custom/tags.json" {
|
||||
t.Errorf("expected '/custom/tags.json', got '%s'", app.Config.App.Tags)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCacheOverrides_NothingChanged(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{})
|
||||
applyCacheOverrides(cmd)
|
||||
if app.Config.Cache.Enabled {
|
||||
t.Error("cache should remain disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCacheOverrides_Enable(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--cache-enabled"})
|
||||
applyCacheOverrides(cmd)
|
||||
if !app.Config.Cache.Enabled {
|
||||
t.Error("expected cache enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCacheOverrides_DisableWhenAlreadyEnabled(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
app.Config.Cache.Enabled = true
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--cache-enabled=false"})
|
||||
applyCacheOverrides(cmd)
|
||||
if app.Config.Cache.Enabled {
|
||||
t.Error("expected cache disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCacheOverrides_Host(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--cache-host", "db.local"})
|
||||
applyCacheOverrides(cmd)
|
||||
if app.Config.Cache.Host != "db.local" {
|
||||
t.Errorf("expected 'db.local', got '%s'", app.Config.Cache.Host)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCacheOverrides_Port(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--cache-port", "6380"})
|
||||
applyCacheOverrides(cmd)
|
||||
if app.Config.Cache.Port != 6380 {
|
||||
t.Errorf("expected 6380, got %d", app.Config.Cache.Port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCacheOverrides_Ttl(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--cache-ttl", "3600"})
|
||||
applyCacheOverrides(cmd)
|
||||
if app.Config.Cache.Ttl != 3600 {
|
||||
t.Errorf("expected 3600, got %d", app.Config.Cache.Ttl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCheckOverrides_NothingChanged(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{})
|
||||
applyCheckOverrides(cmd)
|
||||
if app.Config.Check.Playlists.Timeout != 10000 {
|
||||
t.Errorf("expected 10000, got %d", app.Config.Check.Playlists.Timeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCheckOverrides_PlaylistsTimeout(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--playlists-timeout", "5"})
|
||||
applyCheckOverrides(cmd)
|
||||
if app.Config.Check.Playlists.Timeout != 5000 {
|
||||
t.Errorf("expected 5000 ms from 5 s, got %d", app.Config.Check.Playlists.Timeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCheckOverrides_PlaylistsAllCooldown(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--playlists-all-cooldown", "3"})
|
||||
applyCheckOverrides(cmd)
|
||||
if app.Config.Check.Playlists.AllCooldown.Min != 3000 || app.Config.Check.Playlists.AllCooldown.Max != 3000 {
|
||||
t.Errorf("expected [3000,3000] ms from 3 s, got [%d,%d]",
|
||||
app.Config.Check.Playlists.AllCooldown.Min, app.Config.Check.Playlists.AllCooldown.Max)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCheckOverrides_PlaylistsMaxRoutines(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--playlists-max-routines", "10"})
|
||||
applyCheckOverrides(cmd)
|
||||
if app.Config.Check.Playlists.MaxRoutines != 10 {
|
||||
t.Errorf("expected 10, got %d", app.Config.Check.Playlists.MaxRoutines)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCheckOverrides_PlaylistsUserAgent(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--playlists-user-agent", "UA1,UA2"})
|
||||
applyCheckOverrides(cmd)
|
||||
if len(app.Config.Check.Playlists.UserAgent) != 2 {
|
||||
t.Fatalf("expected 2 user-agents, got %d", len(app.Config.Check.Playlists.UserAgent))
|
||||
}
|
||||
if app.Config.Check.Playlists.UserAgent[0] != "UA1" || app.Config.Check.Playlists.UserAgent[1] != "UA2" {
|
||||
t.Errorf("unexpected user-agents: %v", app.Config.Check.Playlists.UserAgent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCheckOverrides_ChannelsTimeout(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--channels-timeout", "8"})
|
||||
applyCheckOverrides(cmd)
|
||||
if app.Config.Check.Channels.Timeout != 8000 {
|
||||
t.Errorf("expected 8000 ms from 8 s, got %d", app.Config.Check.Channels.Timeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCheckOverrides_ChannelsByteRange(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--channels-byte-range", "1024"})
|
||||
applyCheckOverrides(cmd)
|
||||
if app.Config.Check.Channels.ByteRange != 1024 {
|
||||
t.Errorf("expected 1024, got %d", app.Config.Check.Channels.ByteRange)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCheckOverrides_ChannelsCooldown(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--channels-cooldown", "1"})
|
||||
applyCheckOverrides(cmd)
|
||||
if app.Config.Check.Channels.Cooldown.Min != 1000 || app.Config.Check.Channels.Cooldown.Max != 1000 {
|
||||
t.Errorf("expected [1000,1000] ms from 1 s, got [%d,%d]",
|
||||
app.Config.Check.Channels.Cooldown.Min, app.Config.Check.Channels.Cooldown.Max)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCheckOverrides_ChannelsMaxRoutines(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--channels-max-routines", "100"})
|
||||
applyCheckOverrides(cmd)
|
||||
if app.Config.Check.Channels.MaxRoutines != 100 {
|
||||
t.Errorf("expected 100, got %d", app.Config.Check.Channels.MaxRoutines)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCheckOverrides_ChannelsUserAgent(t *testing.T) {
|
||||
resetArgs()
|
||||
resetConfig()
|
||||
cmd := newCmdWithFlags()
|
||||
cmd.ParseFlags([]string{"--channels-user-agent", "VLC"})
|
||||
applyCheckOverrides(cmd)
|
||||
if len(app.Config.Check.Channels.UserAgent) != 1 || app.Config.Check.Channels.UserAgent[0] != "VLC" {
|
||||
t.Errorf("expected [VLC], got %v", app.Config.Check.Channels.UserAgent)
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Антон Аксенов
|
||||
* Copyright (c) 2025-2026, Антон Аксенов
|
||||
* This file is part of iptvc project
|
||||
* MIT License: https://git.axenov.dev/IPTV/iptvc/src/branch/master/LICENSE
|
||||
*/
|
||||
@@ -35,4 +35,7 @@ func Execute() {
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().BoolVarP(&app.Args.Verbose, "verbose", "v", false, "enable additional output")
|
||||
rootCmd.PersistentFlags().StringVar(&app.Args.ConfigPath, "config", "config.yml", "path to config file")
|
||||
rootCmd.PersistentFlags().BoolVar(&app.Args.Debug, "debug", false, "enable debug mode (overrides config.yml)")
|
||||
rootCmd.PersistentFlags().StringVar(&app.Args.LogLevel, "log-level", "", "log level: debug, info, warn, error (overrides config.yml)")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2025-2026, Антон Аксенов
|
||||
* This file is part of iptvc project
|
||||
* MIT License: https://git.axenov.dev/IPTV/iptvc/src/branch/master/LICENSE
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"axenov/iptv-checker/app"
|
||||
"axenov/iptv-checker/app/web"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// serveCmd represents the serve command
|
||||
var serveCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Start web interface",
|
||||
Long: `Start web interface for browsing IPTV playlists.
|
||||
Use --check to enable background playlist checking.
|
||||
Copyright (c) 2025, Антон Аксенов, MIT license.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app.Init()
|
||||
applyAppOverrides(cmd)
|
||||
applyCacheOverrides(cmd)
|
||||
applyCheckOverrides(cmd)
|
||||
app.InitCache()
|
||||
|
||||
// CLI-флаги переопределяют конфиг, только если заданы явно.
|
||||
if cmd.Flags().Changed("port") {
|
||||
app.Config.Server.Port = app.Args.ServerPort
|
||||
}
|
||||
if cmd.Flags().Changed("host") {
|
||||
app.Config.Server.Host = app.Args.ServerHost
|
||||
}
|
||||
|
||||
server := web.NewServer(app.Config, app.Cache)
|
||||
|
||||
// запускаем фоновую проверку, если передан --check или включён check.start-on-serve в конфиге
|
||||
if app.Args.NeedCheck || app.Config.Check.StartOnServe {
|
||||
opts := web.CheckOptions{
|
||||
Repeat: app.Args.RepeatCount,
|
||||
Random: app.Args.RandomCount,
|
||||
}
|
||||
go server.StartBackgroundChecker(opts)
|
||||
}
|
||||
|
||||
server.Start()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
serveCmd.Flags().UintVarP(&app.Args.ServerPort, "port", "p", 0, "port for web interface (overrides config.yml and env)")
|
||||
serveCmd.Flags().StringVar(&app.Args.ServerHost, "host", "", "host to bind web interface (overrides config.yml and env)")
|
||||
serveCmd.Flags().BoolVar(&app.Args.NeedCheck, "check", false, "enable background playlist checking")
|
||||
addCommonCheckFlags(serveCmd, 0)
|
||||
rootCmd.AddCommand(serveCmd)
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Антон Аксенов
|
||||
* Copyright (c) 2025-2026, Антон Аксенов
|
||||
* This file is part of iptvc project
|
||||
* MIT License: https://git.axenov.dev/IPTV/iptvc/src/branch/master/LICENSE
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user