~emersion/soju-dev

Support CHATHISTORY BETWEEN v1 NEEDS REVISION

Hubert Hirtz: 1
 Support CHATHISTORY BETWEEN

 4 files changed, 142 insertions(+), 23 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~emersion/soju-dev/patches/20767/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH] Support CHATHISTORY BETWEEN Export this patch

---

Please apply the patch on error messages [0] first

[0] https://lists.sr.ht/~emersion/soju-dev/patches/20765
 downstream.go  | 88 +++++++++++++++++++++++++++++++++++++-------------
 irc.go         | 22 +++++++++++++
 msgstore.go    |  1 +
 msgstore_fs.go | 54 +++++++++++++++++++++++++++++++
 4 files changed, 142 insertions(+), 23 deletions(-)

diff --git a/downstream.go b/downstream.go
index 93643a8..b3984a7 100644
--- a/downstream.go
+++ b/downstream.go
@@ -1806,23 +1806,40 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
			Params:  []string{upstreamUser, upstreamChannel},
		})
	case "CHATHISTORY":
		store, ok := dc.user.msgStore.(chatHistoryMessageStore)
		if !ok {
			return ircError{&irc.Message{
				Command: irc.ERR_UNKNOWNCOMMAND,
				Params:  []string{dc.nick, "CHATHISTORY", "Unknown command"},
			}}
		}

		var subcommand string
		if err := parseMessageParams(msg, &subcommand); err != nil {
			return err
		}
		var target, criteria, limitStr string
		if err := parseMessageParams(msg, nil, &target, &criteria, &limitStr); err != nil {
			return ircError{&irc.Message{
				Command: "FAIL",
				Params:  []string{"CHATHISTORY", "NEED_MORE_PARAMS", subcommand, "Missing parameters"},
			}}
		}

		store, ok := dc.user.msgStore.(chatHistoryMessageStore)
		if !ok {
		var target, criterionStr, criterion2Str, limitStr string
		switch subcommand {
		case "AFTER", "BEFORE":
			if err := parseMessageParams(msg, nil, &target, &criterionStr, &limitStr); err != nil {
				return ircError{&irc.Message{
					Command: "FAIL",
					Params:  []string{"CHATHISTORY", "NEED_MORE_PARAMS", subcommand, "Missing parameters"},
				}}
			}
		case "BETWEEN":
			if err := parseMessageParams(msg, nil, &target, &criterionStr, &criterion2Str, &limitStr); err != nil {
				return ircError{&irc.Message{
					Command: "FAIL",
					Params:  []string{"CHATHISTORY", "NEED_MORE_PARAMS", subcommand, "Missing parameters"},
				}}
			}
		default:
			// TODO: support LATEST, AROUND
			return ircError{&irc.Message{
				Command: irc.ERR_UNKNOWNCOMMAND,
				Params:  []string{dc.nick, "CHATHISTORY", "Unknown command"},
				Command: "FAIL",
				Params:  []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"},
			}}
		}

@@ -1831,23 +1848,50 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
			return err
		}

		// TODO: support msgid criteria
		criteriaParts := strings.SplitN(criteria, "=", 2)
		if len(criteriaParts) != 2 || criteriaParts[0] != "timestamp" {
		criterion := parseChathistoryCriterion(criterionStr)
		if criterion == nil {
			return ircError{&irc.Message{
				Command: "FAIL",
				Params:  []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criteria, "Unknown criteria"},
				Params:  []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criterionStr, "Unknown criterion"},
			}}
		}

		timestamp, err := time.Parse(serverTimeLayout, criteriaParts[1])
		if err != nil {
		// TODO: support msgid criteria
		var timestamp time.Time
		switch c := criterion.(type) {
		case time.Time:
			timestamp = c
		default:
			return ircError{&irc.Message{
				Command: "FAIL",
				Params:  []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criteria, "Invalid criteria"},
				Params:  []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criterionStr, "Unknown criterion"},
			}}
		}

		var timestamp2 time.Time
		switch subcommand {
		case "BETWEEN":
			criterion2 := parseChathistoryCriterion(criterion2Str)
			if criterion2 == nil {
				return ircError{&irc.Message{
					Command: "FAIL",
					Params:  []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criterion2Str, "Unknown criterion"},
				}}
			}

			// TODO: support msgid criteria
			switch c := criterion2.(type) {
			case time.Time:
				timestamp2 = c
			default:
				return ircError{&irc.Message{
					Command: "FAIL",
					Params:  []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criterion2Str, "Unknown criterion"},
				}}
			}
		default:
		}

		limit, err := strconv.Atoi(limitStr)
		if err != nil || limit < 0 || limit > dc.srv.HistoryLimit {
			return ircError{&irc.Message{
@@ -1862,14 +1906,12 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
		switch subcommand {
		case "BEFORE":
			history, err = store.LoadBeforeTime(uc.network, entityCM, timestamp, limit)
		case "BETWEEN":
			history, err = store.LoadBetweenTime(uc.network, entityCM, timestamp, timestamp2, limit)
		case "AFTER":
			history, err = store.LoadAfterTime(uc.network, entityCM, timestamp, limit)
		default:
			// TODO: support LATEST, BETWEEN
			return ircError{&irc.Message{
				Command: "FAIL",
				Params:  []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"},
			}}
			panic("unreachable")
		}
		if err != nil {
			dc.logger.Printf("failed parsing log messages for chathistory: %v", err)
diff --git a/irc.go b/irc.go
index 26cf699..9e98f28 100644
--- a/irc.go
+++ b/irc.go
@@ -4,6 +4,7 @@ import (
	"fmt"
	"sort"
	"strings"
	"time"

	"gopkg.in/irc.v3"
)
@@ -477,3 +478,24 @@ func partialCasemap(higher casemapping, name string) string {
	}
	return sb.String()
}

type chCriterion interface{}

func parseChathistoryCriterion(c string) chCriterion {
	parts := strings.SplitN(c, "=", 2)
	if len(parts) != 2 {
		return nil
	}
	switch parts[0] {
	case "timestamp":
		timestamp, err := time.Parse(serverTimeLayout, parts[1])
		if err != nil {
			return nil
		}
		return timestamp
	case "msgid":
		return parts[1]
	default:
		return nil
	}
}
diff --git a/msgstore.go b/msgstore.go
index 18cd0c9..fbadbc2 100644
--- a/msgstore.go
+++ b/msgstore.go
@@ -26,6 +26,7 @@ type chatHistoryMessageStore interface {
	messageStore

	LoadBeforeTime(network *network, entity string, t time.Time, limit int) ([]*irc.Message, error)
	LoadBetweenTime(network *network, entity string, t1 time.Time, t2 time.Time, limit int) ([]*irc.Message, error)
	LoadAfterTime(network *network, entity string, t time.Time, limit int) ([]*irc.Message, error)
}

diff --git a/msgstore_fs.go b/msgstore_fs.go
index a203ac3..98fad15 100644
--- a/msgstore_fs.go
+++ b/msgstore_fs.go
@@ -294,6 +294,38 @@ func (ms *fsMessageStore) parseMessagesBefore(network *network, entity string, r
	}
}

func (ms *fsMessageStore) parseMessagesBetween(network *network, entity string, ref time.Time, end time.Time, limit int) ([]*irc.Message, error) {
	path := ms.logPath(network, entity, ref)
	f, err := os.Open(path)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	defer f.Close()

	var history []*irc.Message
	sc := bufio.NewScanner(f)
	for sc.Scan() && len(history) < limit {
		msg, t, err := parseMessage(sc.Text(), entity, ref)
		if err != nil {
			return nil, err
		} else if msg == nil || t.Before(ref) {
			continue
		} else if t.After(end) {
			break
		}

		history = append(history, msg)
	}
	if sc.Err() != nil {
		return nil, sc.Err()
	}

	return history, nil
}

func (ms *fsMessageStore) parseMessagesAfter(network *network, entity string, ref time.Time, limit int) ([]*irc.Message, error) {
	path := ms.logPath(network, entity, ref)
	f, err := os.Open(path)
@@ -346,6 +378,28 @@ func (ms *fsMessageStore) LoadBeforeTime(network *network, entity string, t time
	return history[remaining:], nil
}

func (ms *fsMessageStore) LoadBetweenTime(network *network, entity string, t1 time.Time, t2 time.Time, limit int) ([]*irc.Message, error) {
	history := make([]*irc.Message, limit)
	remaining := limit
	tries := 0
	for t1.Before(t2) && remaining > 0 && tries < fsMessageStoreMaxTries {
		buf, err := ms.parseMessagesBetween(network, entity, t1, t2, remaining)
		if err != nil {
			return nil, err
		}
		if len(buf) == 0 {
			tries++
		} else {
			tries = 0
		}
		copy(history[remaining-len(buf):], buf)
		remaining -= len(buf)
		t1 = truncateDay(t1).AddDate(0, 0, 1)
	}

	return history[remaining:], nil
}

func (ms *fsMessageStore) LoadAfterTime(network *network, entity string, t time.Time, limit int) ([]*irc.Message, error) {
	var history []*irc.Message
	remaining := limit
-- 
2.30.1