-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
182 lines (156 loc) · 4.69 KB
/
Copy pathmain.go
File metadata and controls
182 lines (156 loc) · 4.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package main
import (
"context"
"flag"
"fmt"
"log/slog"
"os"
"os/signal"
"syscall"
"time"
"github.com/bluesky-social/jetstream/pkg/client"
"github.com/bluesky-social/jetstream/pkg/client/schedulers/sequential"
"github.com/bluesky-social/jetstream/pkg/models"
"github.com/charmbracelet/log"
"golang.org/x/crypto/ssh/terminal"
)
const (
serverAddr = "wss://jetstream2.us-east.bsky.network/subscribe"
pdsHost = "https://shimeji.us-east.host.bsky.network"
)
var cfg *Config
var fConfigName = flag.String("config", "feeds.hcl", "HCL Config file for feeds")
func main() {
flag.Parse()
hupRentry:
var needsHUP = false
ctx, cancelFunc := context.WithCancel(context.Background())
slog.SetDefault(slog.New(&charmSLogHandler{}))
logger := log.Default()
var err error
cfg, err = readConfig(*fConfigName)
if err != nil {
log.Error("Failed to read config: %v", err)
os.Exit(1)
}
log.Info("read config", "config", *cfg)
// os.Exit(0)
if *fPublishFeedName != "" {
for _, feed := range cfg.Feeds {
if feed.ID == *fPublishFeedName {
// publish it
fmt.Print("Enter your Bsky app password: ")
password, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
log.Fatalf("failed to read password: %v", err)
}
err = publishFeedGen(ctx, pdsHost, cfg.Owner, string(password), feed)
if err != nil {
log.Fatalf("failed to publish feed: %v", err)
}
return
}
}
log.Error("Failed to find feed in config!")
os.Exit(1)
}
config := client.DefaultClientConfig()
config.WebsocketURL = serverAddr
config.Compress = true
h := &handler{
seenSeqs: make(map[int64]struct{}),
}
scheduler := sequential.NewScheduler("jetstream_localdev", slog.Default(), h.HandleEvent)
c, err := client.NewClient(config, slog.Default(), scheduler)
if err != nil {
log.Error("failed to create client: %v", err)
os.Exit(1)
}
cursor := time.Now().Add(10 * -time.Minute).UnixMicro()
// Every 5 seconds print the events read and bytes read and average event size
go func() {
ticker := time.NewTicker(2 * time.Second)
lctx, _ := context.WithCancel(ctx)
for {
select {
case <-lctx.Done():
ticker.Stop()
return
case <-ticker.C:
cursor = time.Now().Add(100 * -time.Millisecond).UnixMicro()
}
}
}()
// start up services for feeds
for _, feed := range cfg.Feeds {
httpctx, _ := context.WithCancel(ctx)
startFeedService(httpctx, feed)
dbctx, _ := context.WithCancel(ctx)
postWriter(dbctx, feed)
feed.StartProcessing(logger)
}
// attach signal handlers
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGHUP, syscall.SIGTERM)
go func() {
signal := <-sigs
log.Info("signal received", "signal", signal)
switch signal {
case syscall.SIGHUP:
for _, feed := range cfg.Feeds {
feed.Stop()
}
cancelFunc()
needsHUP = true
case syscall.SIGTERM, syscall.SIGINT:
for _, feed := range cfg.Feeds {
feed.Stop()
}
cancelFunc()
log.Info("shutting down gracefully")
time.Sleep(5 * time.Second)
}
}()
retry:
// we begin reading from the jetstream here
// it sometimes will fail and disconnect, so we reset the cursor
// back one second and retry if it fails.
// database is keyed on post uri being unique, so there won't
// be dupes, and we reduce the risk of missing posts
if err := c.ConnectAndRead(ctx, &cursor); err != nil {
cursor = time.Now().Add(1000 * -time.Millisecond).UnixMicro()
goto retry
}
// if the signal handler flagged that SIGHUP has been received, we'll all time
// for context cancellations for running goroutines, and then restart
if needsHUP {
time.Sleep(5 * time.Second)
log.Info("=================================================================================")
log.Info(" A SERVICE HUP WAS REQUESTED")
log.Info("---------------------------------------------------------------------------------")
log.Info(" HTTP, Database and Feed Workers have been told to quit, and the service config")
log.Info(" will be re-read from disk, so that changes can be applied.")
log.Info("=================================================================================")
goto hupRentry
} else {
time.Sleep(5 * time.Second)
}
// service finally stopped
log.Info("shutdown")
}
type handler struct {
seenSeqs map[int64]struct{}
highwater int64
}
func (h *handler) HandleEvent(ctx context.Context, event *models.Event) error {
// Unmarshal the record if there is one
if event.Commit != nil && (event.Commit.Operation == models.CommitOperationCreate || event.Commit.Operation == models.CommitOperationUpdate) {
switch event.Commit.Collection {
case "app.bsky.feed.post":
for _, feed := range cfg.Feeds {
feed.worker.AddWork(event)
}
}
}
return nil
}