blob: 01d719ff3785ce999629bd9dc119027ab3510974 [file] [log] [blame]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package googlegroups
import (
"encoding/json"
"iter"
"time"
"golang.org/x/oscar/internal/storage"
"golang.org/x/oscar/internal/storage/timed"
"rsc.io/ordered"
)
// Conversations returns an iterator over the group conversations.
// The first iterator value is the conversation URL.
// The second iterator value is a function that can be called to
// return information about the conversation, as with [storage.DB.Scan].
func (c *Client) Conversations(group string) iter.Seq2[string, func() *Conversation] {
return func(yield func(string, func() *Conversation) bool) {
for key, fn := range c.db.Scan(o(conversationKind, group), o(conversationKind, group, ordered.Inf)) {
var changeURL string
if err := ordered.Decode(key, nil, nil, &changeURL); err != nil {
c.db.Panic("ggroup client conversation decode", "key", storage.Fmt(key), "err", err)
}
cfn := func() *Conversation {
var conv Conversation
if err := json.Unmarshal(fn(), &conv); err != nil {
c.db.Panic("ggroup client conversation unmarshal", "key", storage.Fmt(key), "err", err)
}
return &conv
}
if !yield(changeURL, cfn) {
return
}
}
}
}
// timeStampLayout is the timestamp format used by Google Groups search.
const timeStampLayout = time.DateOnly
// Conversation is a Google Group conversation
// represented by HTML from Google Groups web page.
type Conversation struct {
// Group name.
Group string
// URL points to the Google Groups web
// page of the conversation. The page
// contains conversation messages.
URL string
// HTML is the raw html obtained from URL.
HTML []byte
updated string // for testing
interrupt bool // for testing
}
// A ConversationEvent is a Google Groups conversation
// change event returned by Conversationatcher.
type ConversationEvent struct {
DBTime timed.DBTime // time of the change
Group string // group name
URL string // group URL
}
// ConversationWatcher returns a new [timed.Watcher] with the given name.
// It picks up where any previous Watcher of the same name left off.
func (c *Client) ConversationWatcher(name string) *timed.Watcher[ConversationEvent] {
return timed.NewWatcher(c.slog, c.db, name, conversationKind, c.decodeConversationEvent)
}
// decodeConversationEvent decodes a conversationKind [timed.Entry] into
// a conversation event.
func (c *Client) decodeConversationEvent(t *timed.Entry) ConversationEvent {
ce := ConversationEvent{
DBTime: t.ModTime,
}
if err := ordered.Decode(t.Key, &ce.Group, &ce.URL, nil); err != nil {
c.db.Panic("ggroups conversation event decode", "key", storage.Fmt(t.Key), "err", err)
}
return ce
}