Skip to content

Commit

Permalink
robot: add import of ancient knowledge
Browse files Browse the repository at this point in the history
Fixes #69.
  • Loading branch information
zephyrtronium committed Aug 31, 2024
1 parent ed98169 commit 6bffce8
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 0 deletions.
104 changes: 104 additions & 0 deletions ancient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package main

import (
"fmt"
"iter"
"strings"

"zombiezen.com/go/sqlite"
"zombiezen.com/go/sqlite/sqlitex"
)

func ancientOpen(file string) (*sqlite.Conn, int, error) {
conn, err := sqlite.OpenConn(file, sqlite.OpenReadOnly, sqlite.OpenURI)
if err != nil {
return nil, 0, err
}
st, _, err := conn.PrepareTransient(`SELECT pfix FROM config`)
if err != nil {
return nil, 0, err
}
defer st.Finalize()
order, err := sqlitex.ResultInt(st)
return conn, order, err
}

type ancientMessage struct {
tag string
text string
}

func ancientMessages(conn *sqlite.Conn, order int) iter.Seq2[ancientMessage, error] {
// don't look at this code
return func(yield func(ancientMessage, error) bool) {
st, _, err := conn.PrepareTransient(fmt.Sprintf(`SELECT id, tag, IFNULL(p%d, ''), IFNULL(suffix, '') FROM tuples%d`, order-1, order))
if err != nil {
yield(ancientMessage{}, err)
return
}
defer st.Finalize()
var (
toks []string
id int64
tag, pre, suf string
)
for {
// Get the initial values for the current message.
for {
ok, err := st.Step()
if err != nil {
yield(ancientMessage{}, err)
return
}
if !ok {
return
}
pre = st.ColumnText(2)
if pre != "" {
continue
}
id = st.ColumnInt64(0)
tag = st.ColumnText(1)
suf = st.ColumnText(3)
break
}
toks = toks[:0]
for {
if suf == "" {
if !yield(ancientMessage{tag: tag, text: strings.Join(toks, " ")}, nil) {
return
}
break
}
toks = append(toks, suf)
ok, err := st.Step()
if err != nil {
yield(ancientMessage{}, err)
return
}
if !ok {
yield(ancientMessage{tag: tag, text: strings.Join(toks, " ")}, nil)
return
}
nid := st.ColumnInt64(0)
if nid != id+1 {
// Gap in IDs. Skip to the next message.
for suf != "" {
ok, err := st.Step()
if err != nil {
yield(ancientMessage{}, err)
return
}
if !ok {
return
}
suf = st.ColumnText(3)
}
break
}
id = nid
suf = st.ColumnText(3)
}
}
}
}
79 changes: 79 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import (
"os/signal"
"runtime"
"strings"
"time"

"github.com/urfave/cli/v3"
"golang.org/x/sync/errgroup"
"zombiezen.com/go/sqlite/sqlitex"

"github.com/zephyrtronium/robot/brain"
"github.com/zephyrtronium/robot/brain/kvbrain"
"github.com/zephyrtronium/robot/brain/sqlbrain"
"github.com/zephyrtronium/robot/userhash"
)

var app = cli.Command{
Expand Down Expand Up @@ -54,6 +57,18 @@ var app = cli.Command{
},
Action: cliSpeak,
},
{
Name: "ancient",
Usage: "Import messages from a v0.1.0 Robot database",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "db",
Usage: "Ancient version database",
Required: true,
},
},
Action: cliAncient,
},
},
Action: cliRun,

Expand Down Expand Up @@ -163,6 +178,70 @@ func cliSpeak(ctx context.Context, cmd *cli.Command) error {
return group.Wait()
}

func cliAncient(ctx context.Context, cmd *cli.Command) error {
slog.SetDefault(loggerFromFlags(cmd))
r, err := os.Open(cmd.String("config"))
if err != nil {
return fmt.Errorf("couldn't open config file: %w", err)
}
cfg, _, err := Load(ctx, r)
if err != nil {
return fmt.Errorf("couldn't load config: %w", err)
}
r.Close()
kv, sql, _, _, err := loadDBs(ctx, cfg.DB)
if err != nil {
return err
}
var br brain.Brain
if sql == nil {
if kv == nil {
panic("robot: no brain")
}
br = kvbrain.New(kv)
defer kv.Close()
} else {
conn, _ := sql.Take(ctx)
sqlitex.ExecuteScript(conn, `PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF`, nil)
sql.Put(conn)
br, err = sqlbrain.Open(ctx, sql)
defer sql.Close()
}
if err != nil {
return fmt.Errorf("couldn't open brain: %w", err)
}
file := cmd.String("db")
conn, order, err := ancientOpen(file)
if err != nil {
return fmt.Errorf("couldn't open ancient db: %w", err)
}
slog.InfoContext(ctx, "importing", slog.String("file", file), slog.Int("order", order))
var n int64
var toks []string
t := time.NewTicker(time.Second)
defer t.Stop()
for msg, err := range ancientMessages(conn, order) {
n++
if err != nil {
slog.ErrorContext(ctx, "error getting message", slog.Any("err", err))
return err
}
id := fmt.Sprintf("import:%s:%d", file, n)
toks = brain.Tokens(toks[:0], msg.text)
slog.DebugContext(ctx, "learn", slog.String("tag", msg.tag), slog.String("text", msg.text))
if err := brain.Learn(ctx, br, msg.tag, id, userhash.Hash{}, time.Now(), toks); err != nil {
slog.ErrorContext(ctx, "error learning message", slog.Any("err", err))
return err
}
select {
case <-t.C:
slog.InfoContext(ctx, "imported", slog.Int64("n", n))
default: // do nothing
}
}
return nil
}

var (
flagConfig = cli.StringFlag{
Name: "config",
Expand Down

0 comments on commit 6bffce8

Please sign in to comment.