From 8711ec12459fe6e708004f79dae593179d5a3a26 Mon Sep 17 00:00:00 2001 From: Balakrishnan Balasubramanian Date: Mon, 26 Dec 2022 18:09:06 -0500 Subject: [PATCH] implement persistence --- glist/glist.go | 45 ++++++++++++++++++++++++++ main.go | 85 +++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 122 insertions(+), 8 deletions(-) diff --git a/glist/glist.go b/glist/glist.go index 00f26f4..af3dbaa 100644 --- a/glist/glist.go +++ b/glist/glist.go @@ -3,9 +3,15 @@ package glist import ( "encoding/json" "fmt" + "log" + "os" + "path" "sync" + "time" ) +var DataPath string + type Entry struct { Text string `json:"text"` Checked bool `json:"checked"` @@ -27,6 +33,42 @@ func NewGList(chatID int, items ...string) *GList { return &g } +var reqs chan *GList + +func DoPersist() { + reqs = make(chan *GList, 50) + go func() { + for range time.Tick(5 * time.Second) { + lists := map[*GList]struct{}{} + loop: + for { + select { + case g := <-reqs: + lists[g] = struct{}{} + default: + break loop + } + } + for g := range lists { + g.persist() + } + } + }() +} + +func (g *GList) persist() { + g.Mutex.Lock() + defer g.Mutex.Unlock() + data, err := json.Marshal(g) + if err != nil { + log.Panicln("failed to marshal") + } + filename := path.Join(DataPath, fmt.Sprintf("chkchat%d", g.ChatID)) + if err := os.WriteFile(filename, data, 0644); err != nil { + log.Panicln("failed to write to file") + } +} + func (g *GList) Add(text string) error { for _, item := range g.Items { if item.Text == text { @@ -34,6 +76,7 @@ func (g *GList) Add(text string) error { } } g.Items = append(g.Items, Entry{text, false}) + reqs <- g return nil } @@ -41,6 +84,7 @@ func (g *GList) Toggle(text string) error { for i, item := range g.Items { if item.Text == text { g.Items[i].Checked = !g.Items[i].Checked + reqs <- g return nil } } @@ -55,6 +99,7 @@ func (g *GList) ClearChecked() { } } g.Items = remaining + reqs <- g } type button struct { diff --git a/main.go b/main.go index f55005f..c4b6cf2 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,4 @@ +// This is the main package package main import ( @@ -7,18 +8,58 @@ import ( "io" "log" "net/http" + "os" + "path" + "strconv" + "strings" "sync" "time" "gitea.balki.me/telegram-msgchkbox/glist" ) -var apiToken = "421791796:AAE4wPbcqfLP1GNeGR3RTBiyX16fCj3HPAM" +var apiToken string func main() { + + apiToken = os.Getenv("CHKBOT_API_TOKEN") + + if apiToken == "" { + log.Panicln("CHKBOT_API_TOKEN is empty") + } + + port := func() int { + portStr := os.Getenv("CHKBOT_PORT") + port, err := strconv.Atoi(portStr) + if err == nil { + return port + } + return 28923 + }() + + dataPath := func() string { + dataPath := os.Getenv("CHKBOT_DATA_PATH") + if dataPath == "" { + dataPath = "." + } + err := os.MkdirAll(dataPath, 0755) + if err != nil { + log.Panicf("Failed to create datapath, path: %s, err: %s", dataPath, err) + } + return dataPath + }() + + glist.DataPath = dataPath + glist.DoPersist() + + log.Printf("List path starting with datapath:%s, port:%d\n", dataPath, port) + var chats sync.Map - fmt.Println("vim-go") - http.HandleFunc("/zeexkfcsdjpmncirkyelwzotjmmefcqtcogrfwnafidionxiacwnslwuhbwfuppjgwzbmazd", func(w http.ResponseWriter, r *http.Request) { + if err := loadData(dataPath, &chats); err != nil { + log.Panicf("failed to load data, err: %s", err) + } + botPath := fmt.Sprintf("/bot%s", apiToken) + http.HandleFunc(botPath, func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) body, err := io.ReadAll(r.Body) if err != nil { @@ -57,7 +98,9 @@ func main() { g, _ := chats.LoadOrStore(chatID, glist.NewGList(chatID)) gl := g.(*glist.GList) go handleTextAdded(gl, update.Message.Text) + } else if update.CallbackQuery != nil { + defer func() { go answerCallbackQuery(update.CallbackQuery.ID) }() chatID := update.CallbackQuery.Message.Chat.ID @@ -71,7 +114,6 @@ func main() { } }) - port := 28923 log.Panic(http.ListenAndServe(fmt.Sprintf(":%v", port), nil)) } @@ -129,8 +171,8 @@ func sendList(gl *glist.GList, method string) { } func answerCallbackQuery(callbackQueryID string) { - answerUrl := fmt.Sprintf("https://api.telegram.org/bot%s/answerCallbackQuery?callback_query_id=%stext=ok", apiToken, callbackQueryID) - resp, err := http.Get(answerUrl) + answerURL := fmt.Sprintf("https://api.telegram.org/bot%s/answerCallbackQuery?callback_query_id=%stext=ok", apiToken, callbackQueryID) + resp, err := http.Get(answerURL) if err != nil { log.Println(err) return @@ -139,8 +181,8 @@ func answerCallbackQuery(callbackQueryID string) { } func deleteMessage(chatID int, messageID int) { - deleteUrl := fmt.Sprintf("https://api.telegram.org/bot%s/deleteMessage?chat_id=%d&message_id=%d", apiToken, chatID, messageID) - resp, err := http.Get(deleteUrl) + deleteURL := fmt.Sprintf("https://api.telegram.org/bot%s/deleteMessage?chat_id=%d&message_id=%d", apiToken, chatID, messageID) + resp, err := http.Get(deleteURL) if err != nil { log.Println(err) return @@ -163,6 +205,33 @@ func logBody(respBody io.ReadCloser) { log.Println(string(body)) } +func loadData(dataPath string, chats *sync.Map) error { + items, err := os.ReadDir(dataPath) + if err != nil { + return err + } + for _, de := range items { + if de.IsDir() { + continue + } + name := de.Name() + if !strings.HasPrefix(name, "chkchat") { + continue + } + var gl glist.GList + data, err := os.ReadFile(path.Join(dataPath, name)) + if err != nil { + return fmt.Errorf("failed read file, name: %s, err:%w", name, err) + } + err = json.Unmarshal(data, &gl) + if err != nil { + return fmt.Errorf("failed to parse data, data:%s, err:%w", data, err) + } + chats.Store(gl.ChatID, &gl) + } + return nil +} + /* Example data update_id: 547400623 message: