package main import ( "bufio" "bytes" "encoding/binary" "encoding/json" "fmt" "io" "log" "net/http" "time" "git.tornberg.me/go-cart-actor/git.tornberg.me/go-cart-actor/messages" "github.com/matst80/slask-finder/pkg/index" "google.golang.org/protobuf/proto" ) type StorableMessage interface { GetBytes() ([]byte, error) FromReader(io.Reader, *Message) error } type Message struct { Type uint64 TimeStamp *int64 Content interface{} } func (m Message) GetBytes() ([]byte, error) { var b bytes.Buffer var err error w := bufio.NewWriter(&b) bytes := make([]byte, 8) binary.LittleEndian.PutUint64(bytes, m.Type) w.Write(bytes) binary.LittleEndian.PutUint64(bytes, uint64(*m.TimeStamp)) w.Write(bytes) var messageBytes []byte if m.Type == 1 { messageBytes, err = proto.Marshal(m.Content.(*messages.AddRequest)) } else if m.Type == 2 { messageBytes, err = proto.Marshal(m.Content.(*messages.AddItem)) } else { return nil, fmt.Errorf("unknown message type") } if err != nil { return nil, err } binary.LittleEndian.PutUint64(bytes, uint64(len(messageBytes))) w.Write(bytes) w.Write(messageBytes) w.Flush() return b.Bytes(), nil } func (i Message) FromReader(reader io.Reader, m *Message) error { bytes := make([]byte, 8) if _, err := reader.Read(bytes); err != nil { return err } m.Type = binary.LittleEndian.Uint64(bytes) if _, err := reader.Read(bytes); err != nil { return err } timestamp := int64(binary.LittleEndian.Uint64(bytes)) m.TimeStamp = ×tamp if _, err := reader.Read(bytes); err != nil { return err } messageBytes := make([]byte, binary.LittleEndian.Uint64(bytes)) if _, err := reader.Read(messageBytes); err != nil { return err } var err error if m.Type == 1 { msg := &messages.AddRequest{} err = proto.Unmarshal(messageBytes, msg) m.Content = msg } else if m.Type == 2 { msg := &messages.AddItem{} err = proto.Unmarshal(messageBytes, msg) m.Content = msg } else { return fmt.Errorf("unknown message type") } if err != nil { return err } return nil } type CartItem struct { Sku string `json:"sku"` Name string `json:"name"` Price int64 `json:"price"` Image string `json:"image"` } type CartGrain struct { storageMessages []Message Id string `json:"id"` Items []CartItem `json:"items"` TotalPrice int64 `json:"totalPrice"` } type Grain interface { GetId() string GetLastChange() int64 HandleMessage(message *Message, isReplay bool, reply *CartGrain) error GetStorageMessage(since int64) []StorableMessage } func (c *CartGrain) GetId() string { return c.Id } func (c *CartGrain) GetLastChange() int64 { if len(c.storageMessages) == 0 { return 0 } return *c.storageMessages[len(c.storageMessages)-1].TimeStamp } func getItemData(sku string) (*messages.AddItem, error) { res, err := http.Get("https://slask-finder.tornberg.me/api/get/" + sku) if err != nil { return nil, err } defer res.Body.Close() var item index.DataItem err = json.NewDecoder(res.Body).Decode(&item) if err != nil { return nil, err } price := item.GetPrice() if price == 0 { priceField, ok := item.GetFields()[4] if ok { pricef, ok := priceField.(float64) if !ok { price, ok = priceField.(int) if !ok { return nil, fmt.Errorf("invalid price type") } } else { price = int(pricef) } } } return &messages.AddItem{ Quantity: 1, Price: int64(price), Sku: sku, Name: item.Title, Image: item.Img, }, nil } func (c *CartGrain) AddItem(sku string, reply *CartGrain) error { cartItem, err := getItemData(sku) if err != nil { return err } return c.HandleMessage(&Message{ Type: 2, Content: cartItem, }, false, reply) } func (c *CartGrain) GetStorageMessage(since int64) []StorableMessage { ret := make([]StorableMessage, 0) for _, message := range c.storageMessages { if *message.TimeStamp > since { ret = append(ret, message) } } return ret } func (c *CartGrain) HandleMessage(message *Message, isReplay bool, reply *CartGrain) error { log.Printf("Handling message %d", message.Type) if message.TimeStamp == nil { now := time.Now().Unix() message.TimeStamp = &now } var err error switch message.Type { case 1: msg, ok := message.Content.(*messages.AddRequest) if !ok { err = fmt.Errorf("invalid content type") } else { return c.AddItem(msg.Sku, reply) } case 2: msg, ok := message.Content.(*messages.AddItem) if !ok { err = fmt.Errorf("invalid content type") } else { c.Items = append(c.Items, CartItem{ Sku: msg.Sku, Name: msg.Name, Price: msg.Price, Image: msg.Image, }) c.TotalPrice += msg.Price } default: err = fmt.Errorf("unknown message type") } if !isReplay { c.storageMessages = append(c.storageMessages, *message) } *reply = *c return err }