chore: add last twt on support

This commit is contained in:
xuu 2025-03-25 17:05:21 -06:00
parent 2de06ec4d9
commit 2fdc43b7de
Signed by: xuu
GPG Key ID: 8B3B0604F164E04F
3 changed files with 84 additions and 36 deletions

20
feed.go
View File

@ -32,6 +32,7 @@ type Feed struct {
LastScanOn TwtTime LastScanOn TwtTime
RefreshRate int RefreshRate int
NextScanOn TwtTime NextScanOn TwtTime
LastTwtOn TwtTime
LastModified TwtTime LastModified TwtTime
LastError sql.NullString LastError sql.NullString
@ -102,20 +103,25 @@ var (
select select
feed_id, feed_id,
parent_id, parent_id,
coalesce(hashing_uri, uri) hash_uri, coalesce(hashing_uri, uri) hash_uri,
uri, uri,
nick, nick,
state, state,
last_scan_on, last_scan_on,
strftime( strftime(
'%Y-%m-%dT%H:%M:%fZ', '%Y-%m-%dT%H:%M:%fZ',
coalesce(last_scan_on, '1901-01-01'), coalesce(last_scan_on, '1901-01-01'),
'+'||refresh_rate||' seconds' '+'||abs(refresh_rate + cast(random() % 30 as int))||' seconds'
) next_scan_on, ) next_scan_on,
coalesce(last_twt_on, '1901-01-01T00:00:00Z') last_twt_on,
refresh_rate, refresh_rate,
last_modified_on, last_modified_on,
last_etag last_etag
from feeds from feeds
left join (
select feed_id, max(strftime('%Y-%m-%dT%H:%M:%fZ', (substring(text, 1, instr(text, ' ')-1)))) last_twt_on
from twts group by feed_id
) using (feed_id)
left join ( left join (
select select
feed_id parent_id, feed_id parent_id,
@ -125,8 +131,8 @@ var (
) using (parent_id) ) using (parent_id)
where datetime( where datetime(
coalesce(last_scan_on, '1901-01-01'), coalesce(last_scan_on, '1901-01-01'),
'+'||refresh_rate||' seconds' '+'||abs(refresh_rate+cast(random()%30 as int))||' seconds'
) < datetime(current_timestamp, '+2 minutes') ) < datetime(current_timestamp, '+3 minutes')
` `
) )
@ -180,6 +186,7 @@ func (f *Feed) Scan(res interface{ Scan(...any) error }) error {
&f.State, &f.State,
&f.LastScanOn, &f.LastScanOn,
&f.NextScanOn, &f.NextScanOn,
&f.LastTwtOn,
&f.RefreshRate, &f.RefreshRate,
&f.LastModified, &f.LastModified,
&f.ETag, &f.ETag,
@ -356,7 +363,6 @@ func storeFeed(ctx context.Context, db db, f types.TwtFile) error {
} }
func (feed *Feed) MakeHTTPRequest(ctx context.Context) (*http.Request, error) { func (feed *Feed) MakeHTTPRequest(ctx context.Context) (*http.Request, error) {
feed.State = "fetch"
if strings.Contains(feed.URI, "lublin.se") { if strings.Contains(feed.URI, "lublin.se") {
return nil, fmt.Errorf("%w: permaban: %s", ErrPermanentlyDead, feed.URI) return nil, fmt.Errorf("%w: permaban: %s", ErrPermanentlyDead, feed.URI)
} }

31
http.go
View File

@ -31,11 +31,11 @@ func httpServer(c *console, app *appState) error {
_, span := otel.Span(r.Context()) _, span := otel.Span(r.Context())
defer span.End() defer span.End()
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Write([]byte("ok")) w.Write([]byte("ok"))
}) })
http.HandleFunc("/conv/{hash}", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/plain/conv/{hash}", func(w http.ResponseWriter, r *http.Request) {
ctx, span := otel.Span(r.Context()) ctx, span := otel.Span(r.Context())
defer span.End() defer span.End()
@ -104,7 +104,7 @@ func httpServer(c *console, app *appState) error {
reg.WriteTo(w) reg.WriteTo(w)
}) })
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/plain/users", func(w http.ResponseWriter, r *http.Request) {
ctx, span := otel.Span(r.Context()) ctx, span := otel.Span(r.Context())
defer span.End() defer span.End()
@ -112,14 +112,20 @@ func httpServer(c *console, app *appState) error {
rows, err := db.QueryContext( rows, err := db.QueryContext(
ctx, ctx,
`SELECT `SELECT
feed_id, feed_id,
uri, uri,
nick, nick,
last_scan_on last_scan_on,
last_twt_on
FROM feeds FROM feeds
where parent_id is null left join (
order by nick, uri`, select feed_id, max(strftime('%Y-%m-%dT%H:%M:%fZ', (substring(text, 1, instr(text, ' ')-1)))) last_twt_on
from twts group by feed_id
) using (feed_id)
where parent_id is null and state not in ('permanantly-dead', 'frozen') and last_twt_on is not null
order by nick, uri
`,
) )
if err != nil { if err != nil {
span.RecordError(err) span.RecordError(err)
@ -134,15 +140,16 @@ func httpServer(c *console, app *appState) error {
URI string URI string
Nick string Nick string
Dt TwtTime Dt TwtTime
LastTwtOn TwtTime
} }
err = rows.Scan(&o.FeedID, &o.URI, &o.Nick, &o.Dt) err = rows.Scan(&o.FeedID, &o.URI, &o.Nick, &o.Dt, &o.LastTwtOn)
if err != nil { if err != nil {
span.RecordError(err) span.RecordError(err)
return return
} }
twts = append(twts, lextwt.NewTwt( twts = append(twts, lextwt.NewTwt(
types.NewTwter(o.Nick, o.URI), types.NewTwter(o.Nick, o.URI),
lextwt.NewDateTime(o.Dt.Time, o.Dt.Time.Format(time.RFC3339)), lextwt.NewDateTime(o.Dt.Time, o.LastTwtOn.Time.Format(time.RFC3339)),
nil, nil,
)) ))
} }
@ -150,7 +157,7 @@ func httpServer(c *console, app *appState) error {
reg.WriteTo(w) reg.WriteTo(w)
}) })
http.HandleFunc("/queue", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/plain/queue", func(w http.ResponseWriter, r *http.Request) {
lis := slices.Collect(app.queue.Iter()) lis := slices.Collect(app.queue.Iter())
sort.Slice(lis, func(i, j int) bool { sort.Slice(lis, func(i, j int) bool {
return lis[i].NextScanOn.Time.Before(lis[j].LastScanOn.Time) return lis[i].NextScanOn.Time.Before(lis[j].LastScanOn.Time)

View File

@ -23,7 +23,7 @@ const (
OneDay = 86400 OneDay = 86400
OneHour = 3600 OneHour = 3600
TenMinutes = 600 TenMinutes = 600
TwoMinutes = 60 TwoMinutes = 120
) )
func feedRefreshProcessor(c *console, app *appState) error { func feedRefreshProcessor(c *console, app *appState) error {
@ -66,9 +66,9 @@ func feedRefreshProcessor(c *console, app *appState) error {
f := queue.ExtractMin() f := queue.ExtractMin()
if f == nil { if f == nil {
sleeping_time.Add(ctx, int64(TwoMinutes)) sleeping_time.Add(ctx, int64(TwoMinutes))
span.AddEvent("sleeping for ", trace.WithAttributes(attribute.Int("seconds", int(TenMinutes)))) span.AddEvent("sleeping for ", trace.WithAttributes(attribute.Int("seconds", int(TwoMinutes))))
select { select {
case <-time.After(TenMinutes * time.Second): case <-time.After(TwoMinutes * time.Second):
case <-c.Done(): case <-c.Done():
return nil return nil
} }
@ -93,7 +93,7 @@ func feedRefreshProcessor(c *console, app *appState) error {
continue continue
} }
span.AddEvent( span.AddEvent(
"till next", "till next",
trace.WithAttributes(attribute.String("time", until.String()))) trace.WithAttributes(attribute.String("time", until.String())))
sleeping_time.Add(ctx, until.Milliseconds()) sleeping_time.Add(ctx, until.Milliseconds())
select { select {
@ -140,13 +140,14 @@ func processorLoop(ctx context.Context, db db, fetch *pool[*Feed, *Response]) {
err := res.err err := res.err
if res.err != nil { if res.err != nil {
if errors.Is(err, ErrPermanentlyDead) { if errors.Is(err, ErrPermanentlyDead) {
f.State = "permanantly-dead"
f.RefreshRate = TenYear f.RefreshRate = TenYear
} }
if errors.Is(err, ErrTemporarilyDead) { if errors.Is(err, ErrTemporarilyDead) {
f.RefreshRate = OneDay f.RefreshRate, f.State = tsTemp(f.LastTwtOn.Time)
} }
if errors.Is(err, ErrUnmodified) { if errors.Is(err, ErrUnmodified) {
f.RefreshRate = OneDay f.RefreshRate, f.State = tsTemp(f.LastTwtOn.Time)
} }
span.RecordError(err) span.RecordError(err)
@ -205,7 +206,7 @@ func processorLoop(ctx context.Context, db db, fetch *pool[*Feed, *Response]) {
continue continue
} }
f.RefreshRate = checkTemp(twtfile.Twts()) f.RefreshRate, f.State = checkTemp(twtfile.Twts())
f.LastError.String = "" f.LastError.String = ""
err = f.Save(ctx, db) err = f.Save(ctx, db)
@ -217,9 +218,9 @@ func processorLoop(ctx context.Context, db db, fetch *pool[*Feed, *Response]) {
span.RecordError(ctx.Err()) span.RecordError(ctx.Err())
} }
func checkTemp(twts types.Twts) int { func checkTemp(twts types.Twts) (int, State) {
if len(twts) < 5 { if len(twts) < 5 {
return 7*OneDay return 7*OneDay, "cold"
} }
sort.Sort(twts) sort.Sort(twts)
@ -227,32 +228,66 @@ func checkTemp(twts types.Twts) int {
since_fifth := -time.Until(twts[4].Created()) since_fifth := -time.Until(twts[4].Created())
if since_first < 2 * time.Hour || since_fifth < 8 * time.Hour { if since_first < 2 * time.Hour || since_fifth < 8 * time.Hour {
return TwoMinutes return TwoMinutes, "hot"
} }
if since_first < 4 * time.Hour || since_fifth < 16 * time.Hour{ if since_first < 4 * time.Hour || since_fifth < 16 * time.Hour{
return TenMinutes return TenMinutes, "hot"
} }
if since_first < 8 * time.Hour || since_fifth < 32 * time.Hour{ if since_first < 8 * time.Hour || since_fifth < 32 * time.Hour{
return 2*TenMinutes return 2*TenMinutes, "warm"
} }
if since_first < 16 * time.Hour || since_fifth < 64 * time.Hour{ if since_first < 16 * time.Hour || since_fifth < 64 * time.Hour{
return 4*TenMinutes return 4*TenMinutes, "warm"
} }
if since_first < 24 * time.Hour || since_fifth < 128 * time.Hour{ if since_first < 24 * time.Hour || since_fifth < 128 * time.Hour{
return OneDay return OneDay, "cold"
} }
if since_first < 48 * time.Hour || since_fifth < 256 * time.Hour{ if since_first < 48 * time.Hour || since_fifth < 256 * time.Hour{
return 2*OneDay return 2*OneDay, "cold"
} }
if since_first < 96 * time.Hour || since_fifth < 512 * time.Hour{ if since_first < 96 * time.Hour || since_fifth < 512 * time.Hour{
return 7*OneDay return 7*OneDay, "frozen"
} }
return OneMonth return OneMonth, "frozen"
}
func tsTemp(ts time.Time) (int, State) {
since_first := -time.Until(ts)
if since_first < 2 * time.Hour {
return TwoMinutes, "hot"
}
if since_first < 4 * time.Hour {
return TenMinutes, "hot"
}
if since_first < 8 * time.Hour {
return 2*TenMinutes, "warm"
}
if since_first < 16 * time.Hour {
return 4*TenMinutes, "warm"
}
if since_first < 24 * time.Hour {
return OneDay, "cold"
}
if since_first < 48 * time.Hour {
return 2*OneDay, "cold"
}
if since_first < 96 * time.Hour {
return 7*OneDay, "frozen"
}
return OneMonth, "frozen"
} }