193 lines
3.4 KiB
Go
193 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"runtime/debug"
|
|
"strconv"
|
|
"strings"
|
|
|
|
_ "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/console"
|
|
"go.sour.is/xt/internal/otel"
|
|
"go.yarn.social/lextwt"
|
|
"go.yarn.social/types"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
func run(ctx context.Context, c *console.C[args]) error {
|
|
ctx, span := otel.Span(ctx)
|
|
defer span.End()
|
|
|
|
bi, _ := debug.ReadBuildInfo()
|
|
span.AddEvent(name, trace.WithAttributes(attribute.String("version", bi.Main.Version)))
|
|
|
|
a := c.Args()
|
|
app := &appState{
|
|
args: a,
|
|
C: c,
|
|
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()
|
|
|
|
db, err := app.DB(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer db.Close()
|
|
|
|
var inFile io.Reader
|
|
|
|
if a.baseFeed != "" {
|
|
f, err := os.Open(a.baseFeed)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
err = storeRegistry(ctx, db, f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if a.URI != "" {
|
|
res, err := http.Get(a.URI)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
inFile = res.Body
|
|
defer res.Body.Close()
|
|
twtfile, err := lextwt.ParseFile(inFile, &types.Twter{
|
|
Nick: a.Nick,
|
|
URI: a.URI,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %w", ErrParseFailed, err)
|
|
}
|
|
|
|
return storeFeed(ctx, db, twtfile)
|
|
}
|
|
return nil
|
|
}(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
wg, ctx := errgroup.WithContext(ctx)
|
|
|
|
wg.Go(func() error {
|
|
return feedRefreshProcessor(ctx, app)
|
|
})
|
|
go httpServer(ctx, app)
|
|
|
|
err = wg.Wait()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ctx.Err()
|
|
}
|
|
|
|
type appState struct {
|
|
args args
|
|
queue *fibHeap[Feed]
|
|
*console.C[args]
|
|
}
|
|
|
|
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("xt"))
|
|
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
|
|
}
|