I'm writing a small application to perform automatic postings on social networks.
My intention is that the user can via the web interface create the post at a specific time and the bot can be checking for new posts scheduled and execute them.
I'm having trouble working with routines and channels on Go.
I will leave below an example that reflects the reality of my code. It contains some comments to make it easier to understand.
What is the best way to implement a routine that checks for new posts at any time? Remembering:
package main
import (
"fmt"
"sync"
"time"
)
var botRunning = true
var wg = &sync.WaitGroup{}
func main() {
// I start the routine of checking for and posting scheduled appointments.
wg.Add(1)
go postingScheduled()
// Later the user passes the command to stop the post.
// At that moment I would like to stop the routine immediately without getting stuck in a loop.
// What is the best way to do this?
time.Sleep(3 * time.Second)
botRunning = false
// ignore down
time.Sleep(2 * time.Second)
panic("")
wg.Wait()
}
// Function that keeps checking whether the routine should continue or not.
// Check every 2 seconds.
// I think this is very wrong because it consumes unnecessary resources.
// -> Is there another way to do this?
func checkRunning() {
for {
fmt.Println("Pause/Running? - ", botRunning)
if botRunning {
break
}
time.Sleep(2 * time.Second)
}
}
// Routine that looks for the scheduled posts in the database.
// It inserts the date of the posts in the Ticker and when the time comes the posting takes place.
// This application will have hundreds of social network accounts and each will have its own function running in parallel.
// -> What better way to check constantly if there are scheduled items in the database consuming the least resources on the machine?
// -> Another important question. User can schedule posts to the database at any time. How do I check for new posts schedule while the Ticker is waiting for the time the last posting loaded?
func postingScheduled() {
fmt.Println("Init bot posting routine")
defer wg.Done()
for {
checkRunning()
<-time.NewTicker(2 * time.Second).C
fmt.Println("posted success")
}
}
With Peter's response I was able to adapt all the needs to put together a sketch.
I do not know if this would be the best way to do it, maybe some function will consume processing resources unnecessarily. If anyone has better ideas for refactoring, I'll be very grateful to hear.
package main
import (
"fmt"
"log"
"net/http"
"sort"
"time"
)
type posting struct {
caption string
scheduledTo time.Time
}
const dateLayoutFormat = "02-01-2006 15:04:05"
var botStatus = true
var indexPosting int
var tickerSchedule = time.NewTicker(1)
var posts = []posting{
{caption: "item 1", scheduledTo: time.Now().Add(5 * time.Second)},
{caption: "item 2", scheduledTo: time.Now().Add(25 * time.Second)},
}
func init() {
indexPosting = len(posts)
}
func main() {
http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Bem vindo ao bot")
})
http.HandleFunc("/stop", func (w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Parando o bot!")
stopBot()
})
http.HandleFunc("/start", func (w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Iniciando o bot!")
startBot()
})
http.HandleFunc("/add", func (w http.ResponseWriter, r *http.Request) {
t := time.Now().Add(5 * time.Second)
indexPosting++
addItemDB(posting{
caption: fmt.Sprint("item ", indexPosting),
scheduledTo: t,
})
fmt.Fprint(w, "Adicionando nova postagem
Próximo post será: ", t.Format(dateLayoutFormat))
})
if botStatus {
go workerScheduled()
}
log.Print("Inicnando server...")
if err := http.ListenAndServe(":9090", nil); err != nil {
log.Print("erro ao iniciar servidor => ", err)
}
}
func workerScheduled() {
for {
log.Print("listando as próximas postagens")
pts := getNextPostsDB()
if len(pts) == 0 {
log.Print("sem postagem agendada")
botStatus = false
return
}
p1 := pts[0]
log.Printf("Próxima postagem será: %s
", string(p1.scheduledTo.Format(dateLayoutFormat)))
<- updateTimer(p1.scheduledTo).C
if !botStatus {
log.Print("postagem cancelado, bot status = parado")
return
}
if time.Until(p1.scheduledTo) > 1 * time.Second {
updateTimer(p1.scheduledTo)
log.Print("timer resetado")
continue
}
post(p1)
if len(pts) > 1 {
p2 := pts[1]
updateTimer(p2.scheduledTo)
}
updatePostedDB()
}
}
func updateTimer(t time.Time) *time.Ticker {
tickerSchedule = time.NewTicker(t.Sub(time.Now()))
return tickerSchedule
}
func post(p posting) {
log.Printf("'%s' postado com sucesso", p.caption)
}
func addItemDB(p posting) {
posts = append(posts, p)
if botStatus {
next := getNextPostDB()
updateTimer(next.scheduledTo)
} else {
botStatus = true
go workerScheduled()
}
}
func getNextPostDB() posting {
return getNextPostsDB()[0]
}
func getNextPostsDB() []posting {
orderPostsList()
removePostExpired()
return posts
}
func removePostExpired() {
for _, p := range posts {
if p.scheduledTo.Before(time.Now()) {
log.Printf("removendo postagem expirada")
removePostByIndex(getIndexOf(p))
}
}
}
func removePostByIndex(i int) {
copy(posts[i:], posts[i+1:])
posts = posts[:len(posts)-1]
}
func getIndexOf(post posting) int {
for i, p := range posts {
if p.caption == post.caption {
return i
}
}
return -1
}
func updatePostedDB() {
removePostByIndex(0)
}
func orderPostsList() {
sort.Slice(posts, func(i, j int) bool {
return posts[i].scheduledTo.Before(posts[j].scheduledTo)
})
}
func startBot() {
if !botStatus {
log.Printf("comando 'iniciar bot'")
botStatus = true
go workerScheduled()
} else {
log.Printf("comando 'iniciar bot' (já iniciado)")
}
}
func stopBot() {
if botStatus {
log.Printf("comando 'pausar bot'")
botStatus = false
tickerSchedule.Stop()
} else {
log.Printf("comando 'pausar bot' (já pausado)")
}
}