package main import ( "context" "os" "os/signal" "syscall" "time" "github.com/jessevdk/go-flags" log "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" "github.com/mxpv/podsync/pkg/config" "github.com/mxpv/podsync/pkg/ytdl" ) type Opts struct { ConfigPath string `long:"config" short:"c" default:"config.toml" env:"PODSYNC_CONFIG_PATH"` Debug bool `long:"debug"` NoBanner bool `long:"no-banner"` } const banner = ` _______ _______ ______ _______ _ _______ ( ____ )( ___ )( __ \ ( ____ \|\ /|( ( /|( ____ \ | ( )|| ( ) || ( \ )| ( \/( \ / )| \ ( || ( \/ | (____)|| | | || | ) || (_____ \ (_) / | \ | || | | _____)| | | || | | |(_____ ) \ / | (\ \) || | | ( | | | || | ) | ) | ) ( | | \ || | | ) | (___) || (__/ )/\____) | | | | ) \ || (____/\ |/ (_______)(______/ \_______) \_/ |/ )_)(_______/ ` var ( version = "dev" commit = "none" date = "unknown" ) func main() { log.SetFormatter(&log.TextFormatter{ TimestampFormat: time.RFC3339, FullTimestamp: true, }) stop := make(chan os.Signal, 1) signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) ctx, cancel := context.WithCancel(context.Background()) defer cancel() group, ctx := errgroup.WithContext(ctx) // Parse args opts := Opts{} _, err := flags.Parse(&opts) if err != nil { log.WithError(err).Fatal("failed to parse command line arguments") } if opts.Debug { log.SetLevel(log.DebugLevel) } if !opts.NoBanner { log.Info(banner) } log.WithFields(log.Fields{ "version": version, "commit": commit, "date": date, }).Info("running podsync") // Load TOML file log.Debugf("loading configuration %q", opts.ConfigPath) cfg, err := config.LoadConfig(opts.ConfigPath) if err != nil { log.WithError(err).Fatal("failed to load configuration file") } downloader, err := ytdl.New(ctx) if err != nil { log.WithError(err).Fatal("youtube-dl error") } // Queue of feeds to update updates := make(chan *config.Feed, 16) defer close(updates) // Run updater thread log.Debug("creating updater") updater, err := NewUpdater(cfg, downloader) if err != nil { log.WithError(err).Fatal("failed to create updater") } group.Go(func() error { for { select { case feed := <-updates: if err := updater.Update(ctx, feed); err != nil { log.WithError(err).Errorf("failed to update feed: %s", feed.URL) } case <-ctx.Done(): return ctx.Err() } } }) // Run wait goroutines for each feed configuration for _, feed := range cfg.Feeds { _feed := feed group.Go(func() error { log.Debugf("-> %s (update every %s)", _feed.URL, _feed.UpdatePeriod) // Perform initial update after CLI restart updates <- _feed timer := time.NewTicker(_feed.UpdatePeriod.Duration) defer timer.Stop() for { select { case <-timer.C: log.Debugf("adding %q to update queue", _feed.URL) updates <- _feed case <-ctx.Done(): return ctx.Err() } } }) } // Run web server srv := NewServer(cfg) group.Go(func() error { log.Infof("running listener at %s", srv.Addr) return srv.ListenAndServe() }) group.Go(func() error { // Shutdown web server defer func() { log.Info("shutting down web server") if err := srv.Shutdown(ctx); err != nil { log.WithError(err).Error("server shutdown failed") } }() for { select { case <-ctx.Done(): return ctx.Err() case <-stop: cancel() return nil } } }) if err := group.Wait(); err != nil && err != context.Canceled { log.WithError(err).Error("wait error") } log.Info("gracefully stopped") }