ref: a6ca677b242d01fac6c36c00f755416e98ec296c
parent: 64e034333bc1a4e35c778bb4508d1f4be913722c
author: penny <penny@limitedideas.org>
date: Mon Sep 22 11:19:22 EDT 2025
Add job dispatch system for API calls and stats
--- a/commands.go
+++ b/commands.go
@@ -4,7 +4,7 @@
"strings"
)
-var commands = []string{"examine", "reply", "like", "thread", "open", "preview", "download", "dm", "rt", "parent", "children", "rm", "mark", "unmark", "account", "vim", "import", "pause", "resume", "url", "fpost", "ufpost", "edit", "notice"}+var commands = []string{"examine", "reply", "like", "thread", "open", "preview", "download", "dm", "rt", "parent", "children", "rm", "mark", "unmark", "account", "vim", "import", "pause", "resume", "url", "fpost", "ufpost", "edit", "notice", "stats"} func processInput(input string) (command string, arguments string, found bool) {--- a/dispatch.go
+++ b/dispatch.go
@@ -1,18 +1,53 @@
package main
import (
+ "context"
"fmt"
+ "sync"
"time"
"github.com/mattn/go-mastodon"
)
-func (hc *Hellclient) queueManager() {+const ()
+type Job interface {+ Do()
+ Init()
+ Wait()
}
+type StatusJob struct {+ context context.Context
+ status *mastodon.Status
+ ID *mastodon.ID
+ jobfunc func(context.Context, *mastodon.ID)
+ err error
+}
+
+type GenericJob struct {+ jobfunc func(job *GenericJob)
+ result string
+ err error
+ wg sync.WaitGroup
+}
+
+func (job *GenericJob) Wait() {+ job.wg.Wait()
+}
+
+func (job *GenericJob) Init() {+ job.wg.Add(1)
+}
+
+func (job *GenericJob) Do() {+ job.jobfunc(job)
+ job.wg.Done()
+}
+
func (hc *Hellclient) clientDispatch() {var tootQueue []*mastodon.Toot
+ var jobQueue []Job
//Last time we sent an API call
var lastfire time.Time
//Last time the user sent us a line
@@ -28,16 +63,26 @@
tootQueue = append(tootQueue, statustoot)
}
+ receiveJob := func(job Job) {+ jobQueue = append(jobQueue, job)
+ }
+
for { select {+ case job := <-hc.jobdispatch:
+ receiveJob(job)
case statustoot := <-hc.dispatch:
receiveStatus(statustoot)
//API delay needs to be tracked without being reset by new inputs
- case <-time.After(hc.preferences.apidelay - time.Since(lastfire)):
- if 1 > len(tootQueue) {- //Queue is empty, block the loop until we get a new input
- statustoot := <-hc.dispatch
- receiveStatus(statustoot)
+ case <-time.After(time.Duration((hc.preferences.Apidelay * time.Second)) - time.Since(lastfire)):
+ if 1 > len(tootQueue) && 1 > len(jobQueue) {+ //Queues are empty, block the loop until we get a new input
+ select {+ case statustoot := <-hc.dispatch:
+ receiveStatus(statustoot)
+ case job := <-hc.jobdispatch:
+ receiveJob(job)
+ }
break
}
//User is sending lines faster than one a second, flood control
@@ -58,17 +103,28 @@
break
}
- toot := tootQueue[0]
- lastfire = time.Now()
- var err error
- status, err := postStatusDetailed(*hc.client, *toot)
- if err != nil {- fmt.Println(err)
+ hc.stats.slock.Lock()
+ hc.stats.APICalls++
+ hc.stats.slock.Unlock()
+ if len(tootQueue) > 0 {+ toot := tootQueue[0]
+ lastfire = time.Now()
+ var err error
+ status, err := postStatusDetailed(*hc.client, *toot)
+ if err != nil {+ fmt.Println(err)
+ }
+ hc.lock()
+ hc.recentpost = status
+ hc.unlock()
+ tootQueue = tootQueue[1:]
+ break
}
- hc.lock()
- hc.recentpost = status
- hc.unlock()
- tootQueue = tootQueue[1:]
+ //We wouldn't be here if there wasn't something in some queue
+ job := jobQueue[0]
+ jobQueue = jobQueue[1:]
+ job.Do()
+ lastfire = time.Now()
}
}
}
@@ -79,4 +135,18 @@
func (hc *Hellclient) dispatchReply(posttext string, replyto mastodon.ID, postItem *mastodon.Status) {hc.dispatch <- postReply(posttext, replyto, hc.currentuser.ID, postItem)
+}
+
+func (hc *Hellclient) dispatchJob(job Job) {+ job.Init()
+ hc.jobdispatch <- job
+}
+
+func (hc *Hellclient) dispatchFunc(enclosure func(*GenericJob)) Job {+ noticeJob := &GenericJob{+ jobfunc: enclosure,
+ }
+ noticeJob.Init()
+ hc.jobdispatch <- noticeJob
+ return noticeJob
}
--- a/hellclient.go
+++ b/hellclient.go
@@ -7,7 +7,6 @@
"os"
"strings"
"sync"
- "time"
"github.com/ergochat/readline"
"github.com/mattn/go-mastodon"
@@ -31,6 +30,7 @@
client *mastodon.Client
currentuser *mastodon.Account
dispatch chan *mastodon.Toot
+ jobdispatch chan Job
block sync.Mutex
recentpost *mastodon.Status
preferences *Hellprefs
@@ -45,10 +45,13 @@
urlMap map[string][]string
debugMap map[string]interface{}actionBuffer []func()
+
+ stats *Stats
}
-type Hellprefs struct {- apidelay time.Duration
+type Stats struct {+ slock sync.Mutex
+ APICalls int64
}
// Use this to make private versions of runes to stop default behavior
@@ -113,6 +116,7 @@
}
dispatch := make(chan *mastodon.Toot, 15)
+ jobdispatch := make(chan Job)
defer func() {//Got some stuff to do when we're done
@@ -128,7 +132,7 @@
initReferenceSystem()
lastReadID := markers["home"].LastID
- statuses, err := GetStatusesSince(lastReadID, hc.client.GetTimelineHome)
+ statuses, err := hc.GetStatusesSince(lastReadID, hc.client.GetTimelineHome)
if err != nil {return
}
@@ -156,7 +160,7 @@
debugMap := make(map[string]interface{})urlMap := make(map[string][]string)
- prefs := &Hellprefs{apidelay: time.Second * 3}+ prefs := &account.Preferences
hc = Hellclient{rl: rl,homeMap: homeMap,
ctxref: ctxref,
@@ -167,7 +171,9 @@
client: client,
currentuser: currentuser,
dispatch: dispatch,
+ jobdispatch: jobdispatch,
preferences: prefs,
+ stats: &Stats{},}
return &hc, nil
}
--- a/main.go
+++ b/main.go
@@ -99,11 +99,16 @@
//Contextual commands that need to handle their own requirements
switch command {+ case "stats":
+ hc.stats.slock.Lock()
+ fmt.Printf("API Calls: %v\n", hc.stats.APICalls)+ hc.stats.slock.Unlock()
+ return
case "notice":
notifications, err := hc.GetUnreadNotifications()
if len(notifications) > 0 {hc.PrintNotifications(notifications)
- err = hc.SetNotificationsRead(notifications[0].ID)
+ err = hc.SetNotificationsRead(notifications[len(notifications)-1].ID)
}
if err != nil {fmt.Println(err)
@@ -119,8 +124,12 @@
hc.pause(false)
return
case "rm":
- if index == "" && *recentpost != nil {+ deletefunc := func(job *GenericJob) {err = client.DeleteStatus(context.Background(), (*recentpost).ID)
+ }
+ if index == "" && *recentpost != nil {+ deleteJob := hc.dispatchFunc(deletefunc)
+ deleteJob.Wait()
if err != nil {fmt.Println(err)
}
@@ -131,7 +140,8 @@
fmt.Println("No recent status to delete or post index not valid")return
}
- err = client.DeleteStatus(context.Background(), postItem.ID)
+ deleteJob := hc.dispatchFunc(deletefunc)
+ deleteJob.Wait()
if err != nil {fmt.Println(err)
}
--- a/mastodon.go
+++ b/mastodon.go
@@ -113,7 +113,7 @@
hc.urlMap[index] = append(hc.urlMap[index], href)
refnode := &html.Node{Type: html.TextNode,
- Data: fmt.Sprintf("[%v]", len(hc.urlMap[index]))}+ Data: fmt.Sprintf(" [%v]", len(hc.urlMap[index]))} if node.Parent != nil {node.Parent.InsertBefore(refnode, node.NextSibling)
}
@@ -459,7 +459,16 @@
Timeline: timeline,
ID: *ID,
}
- hc.client.SetTimelineMarkers(context.Background(), &[]mastodon.Marker{*marker})+ var err error
+ setmarker := func(job *GenericJob) {+ err = hc.client.SetTimelineMarkers(context.Background(), &[]mastodon.Marker{*marker})+ }
+ job := hc.dispatchFunc(setmarker)
+ job.Wait()
+ if err != nil {+ fmt.Printf("Error: %s", err)+ }
+
}
// Periodically set the timeline read marker to the most recent status
--- a/notifications.go
+++ b/notifications.go
@@ -15,7 +15,17 @@
LastID := markers["notifications"].LastID
page := &mastodon.Pagination{SinceID: LastID}- notificationbuffer, err := hc.client.GetNotificationsExclude(context.Background(), nil, page)
+
+ var notificationbuffer []*mastodon.Notification
+
+ noticeFunc := func(job *GenericJob) {+ notificationbuffer, err = hc.client.GetNotificationsExclude(context.Background(), nil, page)
+ }
+ noticeJob := hc.dispatchFunc(noticeFunc)
+ noticeJob.Wait()
+ if err != nil {+ return
+ }
//Reverse to print from oldest to newest
for i := len(notificationbuffer) - 1; i >= 0; i-- {--- a/status.go
+++ b/status.go
@@ -3,12 +3,11 @@
import (
"context"
"fmt"
- "time"
"github.com/mattn/go-mastodon"
)
-func GetStatusesSince(ID mastodon.ID, GetTimeline func(ctx context.Context, pg *mastodon.Pagination) ([]*mastodon.Status, error)) ([]*mastodon.Status, error) {+func (hc *Hellclient) GetStatusesSince(ID mastodon.ID, GetTimeline func(ctx context.Context, pg *mastodon.Pagination) ([]*mastodon.Status, error)) ([]*mastodon.Status, error) { page := &mastodon.Pagination{MinID: ID}@@ -16,7 +15,11 @@
var statuses []*mastodon.Status
for {- statusbatch, err := GetTimeline(context.Background(), page)
+ var statusbatch []*mastodon.Status
+
+ statusfunc := func(job *GenericJob) { statusbatch, err = GetTimeline(context.Background(), page) }+ statusjob := hc.dispatchFunc(statusfunc)
+ statusjob.Wait()
if err != nil {return statuses, err
}
@@ -32,7 +35,6 @@
//I really don't understand the server's results but erase the max ID and it paginates up I don't know man
page.MaxID = ""
fmt.Printf("Loaded %v statuses....", len(statuses))- time.Sleep(1 * time.Second)
}
return statuses, err
--
⑨