package main import ( "context" "database/sql" "fmt" "iter" "os" "runtime/debug" "strconv" "strings" "sync" _ "embed" _ "github.com/mattn/go-sqlite3" "github.com/uptrace/opentelemetry-go-extra/otelsql" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "go.opentelemetry.io/otel/trace" "go.sour.is/xt/internal/otel" "go.yarn.social/lextwt" "go.yarn.social/types" "golang.org/x/sync/errgroup" ) func run(c *console) error { ctx, span := otel.Span(c.Context) defer span.End() bi, _ := debug.ReadBuildInfo() span.AddEvent(name, trace.WithAttributes(attribute.String("version", bi.Main.Version))) a := c.Args() app := &appState{ args: a, feeds: sync.Map{}, queue: FibHeap(func(a, b *Feed) bool { return a.NextScanOn.Time.Before(b.NextScanOn.Time) }), } // Setup DB err := func(ctx context.Context) error { ctx, span := otel.Span(ctx) defer span.End() db, err := app.DB(ctx) if err != nil { return err } defer db.Close() for _, stmt := range strings.Split(initSQL, ";") { _, err = db.ExecContext(ctx, stmt) if err != nil { return err } } return nil }(ctx) if err != nil { return err } // Seed File err = func(ctx context.Context) error { ctx, span := otel.Span(ctx) defer span.End() f, err := os.Open(a.baseFeed) if err != nil { return err } defer f.Close() twtfile, err := lextwt.ParseFile(f, &types.Twter{ Nick: a.Nick, URI: a.URI, }) if err != nil { return fmt.Errorf("%w: %w", ErrParseFailed, err) } db, err := app.DB(ctx) if err != nil { return err } defer db.Close() return storeFeed(ctx, db, twtfile) }(ctx) if err != nil { return err } wg, ctx := errgroup.WithContext(ctx) c.Context = ctx wg.Go(func() error { return refreshLoop(c, app) }) go httpServer(c, app) err = wg.Wait() if err != nil { return err } return c.Context.Err() } type appState struct { args args feeds sync.Map queue *fibHeap[Feed] } type db struct { *sql.DB Version string Params map[string]string MaxLength int MaxVariableNumber int } func (app *appState) DB(ctx context.Context) (db, error) { // return sql.Open(app.args.dbtype, app.args.dbfile) var err error db := db{Params: make(map[string]string)} db.DB, err = otelsql.Open(app.args.dbtype, app.args.dbfile, otelsql.WithAttributes(semconv.DBSystemSqlite), otelsql.WithDBName("mydb")) if err != nil { return db, err } rows, err := db.DB.QueryContext(ctx, `select sqlite_version()`) if err != nil { return db, err } if rows.Next() { rows.Scan(&db.Version) } if err = rows.Err(); err != nil { return db, err } rows, err = db.DB.QueryContext(ctx, `pragma compile_options`) if err != nil { return db, err } for rows.Next() { var key, value string rows.Scan(&key) key, value, _ = strings.Cut(key, "=") db.Params[key] = value } if err = rows.Err(); err != nil { return db, err } if m, ok := db.Params["MAX_VARIABLE_NUMBER"]; ok { db.MaxVariableNumber, _ = strconv.Atoi(m) } if m, ok := db.Params["MAX_LENGTH"]; ok { db.MaxLength, _ = strconv.Atoi(m) } return db, err } func (app *appState) Feed(feedID string) *Feed { return nil } func (app *appState) Feeds() iter.Seq2[string, *Feed] { return func(yield func(string, *Feed) bool) { app.feeds.Range(func(k, v any) bool { key, _ := k.(string) value, ok := v.(*Feed) if !ok { return false } if !yield(key, value) { return false } return true }) } }